1
0
mirror of https://codeberg.org/fediverse/fep.git synced 2026-08-09 05:56:10 +00:00

Create a FEP static site (#673)

### Preview version

https://helge.codeberg.page/fep/

click there to judge

### Tasks done

- [x] Add a proper index.md instead of final.md .... needs me to filter the readme.md
- [x] search is broken  --> will resolve itself when all feps are part of it
- [x] Update https://codeberg.org/fediverse/fep/src/branch/main/fep/7d8c/fep-7d8c.md
- [x] The admonition format (according to copilot I can blame this format on Microsoft https://learn.microsoft.com/en-us/contribute/content/markdown-reference#alerts-note-important-tip-warning-caution)

    ```
    >[!NOTE]
    >ActivityPub [requires][ActivityPub-Collections] ordered collections to be presented in reverse chronological order. However, an [erratum][ActivityPub-Errata] was proposed to relax this requirement.
    ```
    is not the one supported by python, see https://python-markdown.github.io/extensions/admonition/ . One can probably resolve this by adapting the code https://github.com/Python-Markdown/markdown/blob/f39cf84a24124526c1a0efbe52219fa9950774f6/markdown/extensions/admonition.py

- [x] FEPs miss meta information ....
- [x] Meta information for tracking issue and discussion to link should be formatted
- [x] copy script has problems with contained directories
- [x] add codeberg link to meta, e.g. https://helge.codeberg.page/fep/fep/1b12/
- [x] include implementation counts -> https://codeberg.org/fediverse/fep/issues/686

Co-authored-by: silverpill <silverpill@firemail.cc>
Reviewed-on: https://codeberg.org/fediverse/fep/pulls/673
Reviewed-by: silverpill <silverpill@noreply.codeberg.org>
Co-authored-by: Helge <helge.krueger@gmail.com>
Co-committed-by: Helge <helge.krueger@gmail.com>
This commit is contained in:
Helge
2025-11-01 08:40:20 +01:00
committed by helge
parent 57c5f10a1a
commit e4683fbcda
39 changed files with 847 additions and 133 deletions
+9
View File
@@ -1 +1,10 @@
config*.json
site
docs/fep
docs/withdrawn.md
docs/final.md
docs/draft.md
docs/index.md
build
fep.egg-info
uv.lock
+2 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
from argparse import ArgumentParser
from tools.issue import create_issue
from fep_tools.issue import create_issue
import json
@@ -12,5 +12,4 @@ args = parser.parse_args()
with open("scripts/config.json") as f:
config = json.load(f)
create_issue(config['owner'], config['repo'], config['token'], args.fep)
create_issue(config["owner"], config["repo"], config["token"], args.fep)
+2 -8
View File
@@ -2,17 +2,11 @@
import json
from tools import index, Readme
from fep_tools import data_for_json_file, Readme
with open("README.md", "w") as f1:
f1.writelines(Readme().content)
result = []
for fep in index():
data = fep.parsed_frontmatter
data["title"] = fep.title
data["implementations"] = fep.implementations
result.append(data)
with open("index.json", "w") as index_file:
json.dump(result, index_file, indent=2)
json.dump(data_for_json_file(), index_file, indent=2)
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env python
from fep_tools import index
from fep_tools.site_fep_page import make_page_for_fep
from fep_tools.site_overview_page import write_overview_file
from fep_tools import Readme
path_prefix = "scripts/docs"
with open(f"{path_prefix}/index.md", "w") as fp:
fp.write("""---
hide:
- navigation
---
# Fediverse Enhancement Proposals
<div class="grid cards" markdown>
- [:material-file-document: Final Proposals](./final.md)
- [:material-file-document-edit: Draft Proposals](./draft.md)
</div>
""")
fp.writelines(Readme(include_fep_table=False).content)
for file, status in [
("index.html", "FINAL"),
("draft.html", "DRAFT"),
("withdrawn.html", "WITHDRAWN"),
]:
filename = f"{path_prefix}/{status.lower()}.md"
write_overview_file(filename, status)
for fep in index():
make_page_for_fep(fep)
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+6
View File
@@ -0,0 +1,6 @@
document$.subscribe(function () {
var tables = document.querySelectorAll("article table:not([class])");
tables.forEach(function (table) {
new Tablesort(table);
});
});
@@ -0,0 +1,6 @@
/*!
* tablesort v5.2.1 (2021-10-30)
* http://tristen.ca/tablesort/demo/
* Copyright (c) 2021 ; Licensed MIT
*/
!function(){function a(b,c){if(!(this instanceof a))return new a(b,c);if(!b||"TABLE"!==b.tagName)throw new Error("Element must be a table");this.init(b,c||{})}var b=[],c=function(a){var b;return window.CustomEvent&&"function"==typeof window.CustomEvent?b=new CustomEvent(a):(b=document.createEvent("CustomEvent"),b.initCustomEvent(a,!1,!1,void 0)),b},d=function(a,b){return a.getAttribute(b.sortAttribute||"data-sort")||a.textContent||a.innerText||""},e=function(a,b){return a=a.trim().toLowerCase(),b=b.trim().toLowerCase(),a===b?0:a<b?1:-1},f=function(a,b){return[].slice.call(a).find(function(a){return a.getAttribute("data-sort-column-key")===b})},g=function(a,b){return function(c,d){var e=a(c.td,d.td);return 0===e?b?d.index-c.index:c.index-d.index:e}};a.extend=function(a,c,d){if("function"!=typeof c||"function"!=typeof d)throw new Error("Pattern and sort must be a function");b.push({name:a,pattern:c,sort:d})},a.prototype={init:function(a,b){var c,d,e,f,g=this;if(g.table=a,g.thead=!1,g.options=b,a.rows&&a.rows.length>0)if(a.tHead&&a.tHead.rows.length>0){for(e=0;e<a.tHead.rows.length;e++)if("thead"===a.tHead.rows[e].getAttribute("data-sort-method")){c=a.tHead.rows[e];break}c||(c=a.tHead.rows[a.tHead.rows.length-1]),g.thead=!0}else c=a.rows[0];if(c){var h=function(){g.current&&g.current!==this&&g.current.removeAttribute("aria-sort"),g.current=this,g.sortTable(this)};for(e=0;e<c.cells.length;e++)f=c.cells[e],f.setAttribute("role","columnheader"),"none"!==f.getAttribute("data-sort-method")&&(f.tabindex=0,f.addEventListener("click",h,!1),null!==f.getAttribute("data-sort-default")&&(d=f));d&&(g.current=d,g.sortTable(d))}},sortTable:function(a,h){var i=this,j=a.getAttribute("data-sort-column-key"),k=a.cellIndex,l=e,m="",n=[],o=i.thead?0:1,p=a.getAttribute("data-sort-method"),q=a.getAttribute("aria-sort");if(i.table.dispatchEvent(c("beforeSort")),h||(q="ascending"===q?"descending":"descending"===q?"ascending":i.options.descending?"descending":"ascending",a.setAttribute("aria-sort",q)),!(i.table.rows.length<2)){if(!p){for(var r;n.length<3&&o<i.table.tBodies[0].rows.length;)r=j?f(i.table.tBodies[0].rows[o].cells,j):i.table.tBodies[0].rows[o].cells[k],m=r?d(r,i.options):"",m=m.trim(),m.length>0&&n.push(m),o++;if(!n)return}for(o=0;o<b.length;o++)if(m=b[o],p){if(m.name===p){l=m.sort;break}}else if(n.every(m.pattern)){l=m.sort;break}for(i.col=k,o=0;o<i.table.tBodies.length;o++){var s,t=[],u={},v=0,w=0;if(!(i.table.tBodies[o].rows.length<2)){for(s=0;s<i.table.tBodies[o].rows.length;s++){var r;m=i.table.tBodies[o].rows[s],"none"===m.getAttribute("data-sort-method")?u[v]=m:(r=j?f(m.cells,j):m.cells[i.col],t.push({tr:m,td:r?d(r,i.options):"",index:v})),v++}for("descending"===q?t.sort(g(l,!0)):(t.sort(g(l,!1)),t.reverse()),s=0;s<v;s++)u[s]?(m=u[s],w++):m=t[s-w].tr,i.table.tBodies[o].appendChild(m)}}i.table.dispatchEvent(c("afterSort"))}},refresh:function(){void 0!==this.current&&this.sortTable(this.current,!0)}},"undefined"!=typeof module&&module.exports?module.exports=a:window.Tablesort=a}();
+9
View File
@@ -0,0 +1,9 @@
[data-md-color-primary="indigo"] {
--md-primary-fg-color: #2185d0;
}
tbody tr:nth-of-type(odd) {
/* Lighter than --md-typeset-table-color--light */
background-color: hsla(0, 0%, 0%, 0.025);
}
+64
View File
@@ -0,0 +1,64 @@
from dataclasses import dataclass
import glob
import hashlib
from .fep_file import FepFile
from .table import table_for_feps
def title_to_slug(title: str):
return hashlib.sha256(title.encode("utf-8")).hexdigest()[:4]
def get_fep_ids():
for fep in glob.glob("fep/*"):
yield fep.removeprefix("fep/")
def index():
fep_files = [FepFile(fep) for fep in get_fep_ids()]
fep_files = sorted(
fep_files,
key=lambda x: (
x.parsed_frontmatter["dateReceived"],
x.parsed_frontmatter["slug"],
),
)
return fep_files
@dataclass
class Readme:
path_prefix: str = "./scripts/snippets"
include_fep_table: bool = True
@property
def content(self):
if self.include_fep_table:
return (
self.snippet("frontmatter_git")
+ self.snippet("frontmatter")
+ self.table
+ self.snippet("backmatter")
)
return self.snippet("frontmatter") + self.snippet("backmatter")
def snippet(self, name: str):
with open(f"{self.path_prefix}/{name}.md") as f:
return f.readlines()
@property
def table(self):
return self.snippet("fep_title") + table_for_feps(index())
def data_for_json_file() -> list[dict[str, str]]:
result = []
for fep in index():
data = fep.parsed_frontmatter
data["title"] = fep.title
data["implementations"] = fep.implementation_count
result.append(data)
return result
@@ -1,22 +1,38 @@
def unquote(value):
from dataclasses import dataclass, field
def unquote(value: str) -> str:
if value.startswith('"') and value.endswith('"'):
return value[1:-1]
else:
return value
@dataclass
class FepFile:
def __init__(self, fep):
self.fep = fep
fep: str
frontmatter: list[str] = field(default_factory=list)
content: list[str] = field(default_factory=list)
def __post_init__(self):
with open(self.filename) as f:
self.frontmatter, self.content = FepFile.parsefile(f)
lines = f.readlines()
status = 0
for line in lines:
if line == "---\n" and status <= 2:
status += 1
elif status == 1:
self.frontmatter.append(line.removesuffix("\n"))
elif status >= 2:
self.content.append(line.removesuffix("\n"))
@property
def filename(self):
def filename(self) -> str:
return f"fep/{self.fep}/fep-{self.fep}.md"
@property
def summary(self):
def find_section_by_name(self, name):
result = []
is_summary = False
for x in self.content:
@@ -24,9 +40,13 @@ class FepFile:
if x.startswith("##"):
return "\n".join(result)
result.append(x)
elif x == "## Summary":
elif x == f"## {name}":
is_summary = True
@property
def summary(self):
return self.find_section_by_name("Summary")
def write(self):
with open(self.filename, "w") as f:
f.write("---\n")
@@ -42,7 +62,11 @@ class FepFile:
return {a: unquote(b.strip()) for a, b in split}
@property
def title(self):
def status(self):
return self.parsed_frontmatter["status"]
@property
def title(self) -> str:
titles = [x for x in self.content if x.startswith("# ")]
assert len(titles) > 0
@@ -56,25 +80,23 @@ class FepFile:
return true_title
@property
def implementations(self):
def implementation_count(self):
if self.parsed_frontmatter.get("type") != "implementation":
return 0
implementations = []
in_section = False
for line in self.content:
if line.startswith('#') and "Implementations" in line:
if line.startswith("#") and "Implementations" in line:
in_section = True
elif in_section is True and line.startswith('#'):
elif in_section is True and line.startswith("#"):
in_section = False
elif (
in_section is True
and (line.startswith("-") or line.startswith("*"))
):
elif in_section is True and (line.startswith("-") or line.startswith("*")):
implementations.append(line)
return len(implementations)
@staticmethod
def parsefile(f):
def parsefile(f) -> tuple[list[str], list[str]]:
lines = f.readlines()
status = 0
@@ -90,3 +112,21 @@ class FepFile:
content.append(line.removesuffix("\n"))
return frontmatter, content
def frontmatter_table(self) -> str:
keys = " | ".join(self.parsed_frontmatter.keys())
values = " | ".join(self.parsed_frontmatter.values())
divider = " | ".join(["---"] * len(self.parsed_frontmatter))
return f"""
| {keys} |
| {divider} |
| {values} |
"""
def content_and_title(self) -> tuple[str, str]:
for j, line in enumerate(self.content):
if line.startswith("# "):
return line, "\n".join(self.content[j + 1 :])
raise Exception("Could not determine title and content")
@@ -72,7 +72,7 @@ def update_fep_file_with_date_received(fep_file: FepFile, date_received: date):
fep_file.frontmatter.append(f"dateReceived: {date_received.isoformat()}")
def create_issue(owner, repo, token, slug):
def create_issue(owner: str, repo: str, token: str, slug: str):
fep_file = FepFile(slug)
if "trackingIssue" in fep_file.parsed_frontmatter:
+124
View File
@@ -0,0 +1,124 @@
import re
import shutil
from pathlib import Path
try:
import nh3
from markdown import markdown
except Exception:
...
from .fep_file import FepFile
from .table import TableLineBuilder
def transform_key(x: str) -> str:
if x in ["dateReceived", "dateWithdrawn", "trackingIssue"]:
return re.sub(r"(?<!^)(?=[A-Z])", "_", x).lower()
if x == "dateFinalized":
return "date_final"
if x == "discussionsTo":
return "discussions"
return x
possible_matter_keys = [
"authors",
"status",
"type",
"date_received",
"date_final",
"date_withdrawn",
"tracking_issue",
"discussions",
"repository",
]
def format_metatable(fep: FepFile) -> str:
"""Creates a markdown table containing FEP metadata"""
matter_keys = [transform_key(x) for x in fep.parsed_frontmatter.keys()]
matter_keys = [x for x in possible_matter_keys if x in matter_keys] + ["repository"]
line_builder = TableLineBuilder(fep=fep, columns=list(matter_keys))
keys = " | ".join(x.replace("_", " ").capitalize() for x in matter_keys)
values = line_builder.line
divider = " | ".join(["---"] * len(matter_keys))
return f"""
| {keys} |
| {divider} |
{values}
"""
def clean_markdown(text: str) -> str:
"""Transforms markdown to a plain text string. To be used in the frontmatter"""
transformed = nh3.clean(markdown(text), tags=set())
transformed = re.sub(r"\[(.+?)\]\[(.+?)\]", r"\1", transformed)
transformed = transformed.replace(
":", " "
) # See https://codeberg.org/fediverse/fep/pulls/673/files#issuecomment-7514311
return transformed.replace("\n", " ")
def frontmatter_for_fep(fep: FepFile) -> str:
summary = fep.summary
if summary is None:
return """---
hide:
- navigation
---
"""
description = clean_markdown(summary)
return f"""---
hide:
- navigation
description: {description}
---
"""
def transform_content(content: str) -> str:
"""Transforms links to other FEPs. These need to be adjusted due to
the renaming of `fep-slug.md` to `index.md`."""
content = re.sub(
r"\[(.+?)\]\((\.\./.+)/fep-(.+).md\)", r"[\1](\2/index.md)", content
)
content = re.sub(r"\[(.+?)\]: (\.\./.+)/fep-(.+).md", r"[\1]: \2/index.md", content)
return content
def fep_to_site_mkdocs(fep: FepFile) -> str:
"""Transforms the FEP to markdown suitable for publishing as a static website"""
title, content = fep.content_and_title()
file_content = (
frontmatter_for_fep(fep)
+ title
+ "\n\n"
+ format_metatable(fep)
+ "\n\n"
+ transform_content(content)
)
return file_content
def make_page_for_fep(fep: FepFile):
"""Copies relevant files for the FEP and adjusts index.md"""
slug = fep.fep
base_path = f"scripts/docs/fep/{slug}/"
Path(base_path).mkdir(exist_ok=True, parents=True)
shutil.rmtree(base_path)
shutil.copytree(f"fep/{slug}/", base_path)
shutil.move(f"{base_path}/fep-{slug}.md", f"{base_path}/index.md")
with open(f"{base_path}/index.md", "w") as fp:
fp.write(fep_to_site_mkdocs(fep))
+39
View File
@@ -0,0 +1,39 @@
from . import index, table_for_feps
markdown_frontmatter = """---
hide:
- toc
- navigation
---
"""
def columns_for_status(status: str) -> list[str]:
columns = [
"title",
"repo_link_image",
"tracking_issue",
"discussions",
"date_received",
]
if status == "FINAL":
columns.append("date_finalized")
elif status == "WITHDRAWN":
columns.append("date_withdrawn")
return columns
def write_overview_file(filename: str, status: str) -> None:
columns = columns_for_status(status)
feps = [x for x in index() if x.status == status]
table = "".join(
table_for_feps(feps, columns=columns, generate_title=True, format="static")
)
title = status[0] + status[1:].lower()
with open(filename, "w") as f:
f.write(markdown_frontmatter)
f.write(f"\n# {title}\n")
f.write(table)
+138
View File
@@ -0,0 +1,138 @@
from dataclasses import dataclass
from .fep_file import FepFile
repo_base = "https://codeberg.org/fediverse/fep/src/branch/main/"
@dataclass
class TableLineBuilder:
fep: FepFile
columns: list[str]
format: str = "readme"
@property
def parsed(self):
return self.fep.parsed_frontmatter
@property
def title(self):
if self.format == "readme":
return f"[FEP-{self.fep.fep}: {self.fep.title}](./{self.fep.filename})"
return f"[FEP-{self.fep.fep}: {self.fep.title}](fep/{self.fep.fep}/index.md)"
@property
def repo_link_image(self):
return f"[<img src='../assets/codeberg.png' width=20 height=20 alt='codeberg'>]({repo_base}{self.fep.filename})"
@property
def tracking_issue(self):
url = self.parsed["trackingIssue"]
url_number = url.split("/")[-1]
return f"[#{url_number}]({url})"
@property
def status(self):
status = self.parsed["status"]
return f"`{status}`"
@property
def date_received(self):
return self.parsed.get("dateReceived", "-")
@property
def date_finalized(self):
return self.parsed.get("dateFinalized", "-")
@property
def date_withdrawn(self):
return self.parsed.get("dateWithdrawn", "-")
@property
def date_final(self):
if "dateFinalized" in self.parsed:
return self.parsed["dateFinalized"]
if "dateWithdrawn" in self.parsed:
return self.parsed["dateWithdrawn"]
return "-"
@property
def discussions(self):
discussions = self.parsed["discussionsTo"]
if discussions == self.parsed["trackingIssue"]:
return "-"
return f"[Discussions]({discussions})"
@property
def repository(self) -> str:
url = f"{repo_base}fep/{self.fep.fep}/fep-{self.fep.fep}.md"
return f"[codeberg]({url})"
def get_attribute(self, column: str):
try:
return self.__getattribute__(column)
except AttributeError:
return self.parsed.get(column, "-")
@property
def line(self) -> str:
values = [self.get_attribute(column) for column in self.columns]
middle = " | ".join(values)
return f"| {middle} |\n"
@dataclass
class TableTitle:
columns: list[str]
column_to_title = {
"title": "Title",
"status": "Status",
"tracking_issue": "Tracking issue",
"discussions": "Discussions",
"date_received": "Received",
"date_final": "Finalized / Withdrawn",
"date_finalized": "Finalized",
"date_withdrawn": "Withdrawn",
}
@property
def title_line(self):
center = " | ".join(
[self.column_to_title.get(column, "") for column in self.columns]
)
return f"| {center} |\n"
@property
def second_line(self):
center = " | ".join(["---"] * len(self.columns))
return f"| {center} |\n"
@property
def result(self) -> list[str]:
return [self.title_line, self.second_line]
def table_for_feps(
feps: list[FepFile],
columns: list[str] = [
"title",
"status",
"tracking_issue",
"date_received",
# Finalized / Withdrawn
"date_final",
],
generate_title: bool = False,
format: str = "readme",
):
lines = [TableLineBuilder(fep, columns, format=format).line for fep in feps]
if generate_title:
return TableTitle(columns).result + lines
return lines
+13
View File
@@ -0,0 +1,13 @@
from . import fep_file
def test_fep_file():
file = fep_file.FepFile("a4ed")
assert file.status == "FINAL"
def test_implementation_count():
file = fep_file.FepFile("8b32")
assert file.implementation_count > 2
@@ -3,7 +3,7 @@ import pytest
from urllib.parse import urlparse
from scripts.tools import get_fep_ids, FepFile, title_to_slug
from scripts.fep_tools import get_fep_ids, FepFile, title_to_slug
@pytest.mark.parametrize("fep", get_fep_ids())
@@ -20,9 +20,9 @@ def test_fep_front_matter(fep):
discussions_to = parsed_frontmatter["discussionsTo"]
assert not urlparse(discussions_to).netloc.endswith(
".example"
), "Update discussionsTo to a valid URL for a discussion topic"
assert not urlparse(discussions_to).netloc.endswith(".example"), (
"Update discussionsTo to a valid URL for a discussion topic"
)
if parsed_frontmatter["status"] == "FINAL":
assert "dateFinalized" in parsed_frontmatter
@@ -36,6 +36,7 @@ def test_fep_front_matter(fep):
if "type" in parsed_frontmatter:
assert parsed_frontmatter["type"] in ["informational", "implementation"]
@pytest.mark.parametrize("fep", get_fep_ids())
def test_fep_content(fep):
fep_file = FepFile(fep)
+12
View File
@@ -0,0 +1,12 @@
import json
from . import data_for_json_file
def test_data_for_json_file():
result = data_for_json_file()
assert isinstance(result, list)
dumped = json.dumps(result)
assert isinstance(dumped, str)
@@ -1,6 +1,6 @@
import pytest
from scripts.tools import Readme
from scripts.fep_tools import Readme
@pytest.mark.skip("Only is correct for main branch and not pull requests")
@@ -11,3 +11,9 @@ def test_readme():
expected = Readme().content
assert lines == expected
def test_readme_can_be_created():
content = Readme().content
assert isinstance(content, list)
+71
View File
@@ -0,0 +1,71 @@
import pytest
from . import fep_file
from .site_fep_page import format_metatable, clean_markdown, transform_content
def skip_due_to_no_nh3_markdown():
try:
import nh3 # type: ignore # noqa
import markdown # type: ignore # noqa
except Exception:
return True
return False
def test_format_metatable():
file = fep_file.FepFile("a4ed")
table = format_metatable(file)
assert (
table
== """
| Authors | Status | Date received | Date final | Tracking issue | Discussions | Repository |
| --- | --- | --- | --- | --- | --- | --- |
| pukkamustard <pukkamustard@posteo.net> | `FINAL` | 2020-10-16 | 2021-01-18 | [#201](https://codeberg.org/fediverse/fep/issues/201) | - | [codeberg](https://codeberg.org/fediverse/fep/src/branch/main/fep/a4ed/fep-a4ed.md) |
"""
)
@pytest.mark.skipif(
skip_due_to_no_nh3_markdown(), reason="nh3 and markdown not installed"
)
@pytest.mark.parametrize(
"text,expected",
[
("[link](http://test.example)", "link"),
("[link][link]", "link"),
("[a][a] [b][b]", "a b"),
("a:b", "a b"),
],
)
def test_clean_markdown(text: str, expected: str):
result = clean_markdown(text)
assert result == expected
@pytest.mark.skipif(
skip_due_to_no_nh3_markdown(), reason="nh3 and markdown not installed"
)
@pytest.mark.parametrize(
"unchanged",
[
"[link](http://remote.tests)",
"[fep-f1d5](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md)",
"[fep-f1d5]: https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md",
],
)
def test_content_unchanged(unchanged: str):
assert transform_content(unchanged) == unchanged
@pytest.mark.skipif(
skip_due_to_no_nh3_markdown(), reason="nh3 and markdown not installed"
)
def test_content():
changed = "[link](../4adb/fep-4adb.md)"
assert transform_content(changed) == "[link](../4adb/index.md)"
changed = "[FEP-4adb]: ../4adb/fep-4adb.md"
assert transform_content(changed) == "[FEP-4adb]: ../4adb/index.md"
+33
View File
@@ -0,0 +1,33 @@
from .fep_file import FepFile
from .table import table_for_feps, TableTitle
def test_table_for_feps():
fep = FepFile("a4ed")
result = table_for_feps([fep])
assert len(result) == 1
line = result[0]
assert (
line
== "| [FEP-a4ed: The Fediverse Enhancement Proposal Process](./fep/a4ed/fep-a4ed.md) | `FINAL` | [#201](https://codeberg.org/fediverse/fep/issues/201) | 2020-10-16 | 2021-01-18 |\n"
)
def test_table_title():
columns = [
"title",
"tracking_issue",
"discussions",
"date_received",
"date_final",
]
result = TableTitle(columns).result
assert result == [
"| Title | Tracking issue | Discussions | Received | Finalized / Withdrawn |\n",
"| --- | --- | --- | --- | --- |\n",
]
+5 -3
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python
from tools import index
from fep_tools import index
result = []
print("| Title | Status | Count |")
@@ -9,6 +9,8 @@ for fep in index():
slug = fep.parsed_frontmatter["slug"]
status = fep.parsed_frontmatter["status"]
typ = fep.parsed_frontmatter.get("type") or "-"
if fep.implementations == 0:
if fep.implementation_count == 0:
continue
print(f"| [{fep.title}](https://codeberg.org/fediverse/fep/src/branch/main/fep/{slug}/fep-{slug}.md) | {status} | {fep.implementations} |")
print(
f"| [{fep.title}](https://codeberg.org/fediverse/fep/src/branch/main/fep/{slug}/fep-{slug}.md) | {status} | {fep.implementation_count} |"
)
+56
View File
@@ -0,0 +1,56 @@
site_name: Fediverse Enhancement Proposals
site_url: https://helge.codeberg.page/fep
repo_url: https://codeberg.org/fediverse/fep
repo_name: fediverse/fep
theme:
name: material
logo: assets/logo.png
favicon: assets/logo.png
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-4
name: Switch to light mode
features:
- navigation.tabs
font: false
markdown_extensions:
- toc:
permalink: true
- github-callouts
- tables
- attr_list
- md_in_html
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- admonition
- pymdownx.details
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- footnotes
extra_javascript:
- javascripts/tablesort@5.3.0/tablesort.min.js
- javascripts/tablesort.js
extra_css:
- styles/theme.css
nav:
- index.md
- final.md
- draft.md
- withdrawn.md
plugins:
- search
+1 -1
View File
@@ -4,7 +4,7 @@ from argparse import ArgumentParser
import os
import shutil
from tools import title_to_slug, FepFile
from fep_tools import title_to_slug, FepFile
def build_parser():
+1 -1
View File
@@ -1,6 +1,6 @@
from argparse import ArgumentParser
from datetime import date, timedelta
from tools import FepFile
from fep_tools import FepFile
import json
+19
View File
@@ -0,0 +1,19 @@
[project]
name = "fep"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"mkdocs>=1.6.1",
"mkdocs-material[imaging]>=9.5.40",
"pytest>=8.3.4",
"markdown>=3.7",
"pytest-watcher>=0.4.3",
"markdown-callouts>=0.4.0",
"fep",
"nh3>=0.3.0",
]
[tool.setuptools]
packages = ["fep_tools"]
+4
View File
@@ -0,0 +1,4 @@
## FEPs
| Title | Status | Tracking issue | `dateReceived` | `dateFinalized` (or `dateWithdrawn`) |
| --- | --- | ----- | ------- | ------ |
@@ -1,9 +1,3 @@
# Fediverse Enhancement Proposals
<!-- DO NOT EDIT! File automatically generated with `./scripts/create_readme.py`. -->
This is the Git repository containing Fediverse Enhancment Proposals (FEPs).
A Fediverse Enhancement Proposal (FEP) is a document that provides information to the Fediverse community. The goal of a FEP is to improve interoperability and well-being of diverse services, applications and communities that form the Fediverse.
The FEP Process is an initiative of the [SocialHub](https://socialhub.activitypub.rocks) developer community, a liaison of the [W3C Social Web Incubator Community Group](https://www.w3.org/community/SocialCG/). For ongoing and past discussion see the [SocialHub FEP category](https://socialhub.activitypub.rocks/c/standards/fep/54).
@@ -23,8 +17,8 @@ $ echo -n "The title of my proposal" | sha256sum | cut -c-4
b3f0
```
4. Create a subdirectory of [`fep/`](./fep/) using the identifier you just computed.
5. Copy the FEP template ([fep-xxxx-template.md](./fep-xxxx-template.md)) to this subdirectory and change the filename appropriately.
4. Create a subdirectory of [`fep/`](https://codeberg.org/fediverse/fep/src/branch/main/fep/) using the identifier you just computed.
5. Copy the FEP template ([fep-xxxx-template.md](https://codeberg.org/fediverse/fep/src/branch/main/fep-xxxx-template.md)) to this subdirectory and change the filename appropriately.
6. Use the identifer as the "slug" when filling out the frontmatter.
- For example, if your computed identifier was `abcd`, then your file would be located at `fep/abcd/fep-abcd.md` and your frontmatter would include `slug: "abcd"`.
@@ -33,7 +27,7 @@ b3f0
8. When you are ready to submit the FEP, change the value of `dateReceived` field in the frontmatter to the current date.
9. Create a discussion topic for your FEP. You can use [ActivityPub category](https://socialhub.activitypub.rocks/c/activitypub/5) on the SocialHub forum.
10. Add `discussionsTo` field containing the URL of the discussion topic to the front matter of your FEP.
11. Create a Pull Request to complete Step 1 of [FEP-a4ed: The Fediverse Enhancement Proposal Process](./fep/a4ed/fep-a4ed.md). Further process is described in FEP-a4ed.
11. Create a Pull Request to complete Step 1 of [FEP-a4ed: The Fediverse Enhancement Proposal Process](https://codeberg.org/fediverse/fep/src/branch/main/fep/a4ed/fep-a4ed.md). Further process is described in FEP-a4ed.
Alternatively to steps 3. to 6., you can run
@@ -45,15 +39,9 @@ that should create a prefilled template for you.
## Facilitators
The list of FEP's is maintained by the facilitators who are listed in the [FACILITATORS.md](FACILITATORS.md) file. Facilitators are neutral custodians of the FEP process, who merge PR's and create tracking issues.
The list of FEP's is maintained by the facilitators who are listed in the [FACILITATORS.md](https://codeberg.org/fediverse/fep/src/branch/main/FACILITATORS.md) file. Facilitators are neutral custodians of the FEP process, who merge PR's and create tracking issues.
## Contributing
Do you have ideas to improve the FEP Process? Post your suggestions to the issue tracker, or on the [SocialHub](https://socialhub.activitypub.rocks) forum. The SocialHub developer community is a "DoOcracy" which means: “pick up any task you want, and then steer it to completion”. Your contributions are most welcome, so delve in and find out how you can help.
## FEPs
<!-- TODO: This table is not CommonMark (as specified by FEP-a4ed) and requires maintenance. -->
| Title | Status | Tracking issue | `dateReceived` | `dateFinalized` (or `dateWithdrawn`) |
| --- | --- | ----- | ------- | ------ |
+6
View File
@@ -0,0 +1,6 @@
# Fediverse Enhancement Proposals
<!-- DO NOT EDIT! File automatically generated with `./scripts/create_readme.py`. -->
This is the Git repository containing Fediverse Enhancment Proposals (FEPs).
-64
View File
@@ -1,64 +0,0 @@
import glob
import hashlib
from .fep_file import FepFile
def title_to_slug(title):
return hashlib.sha256(title.encode("utf-8")).hexdigest()[:4]
def get_fep_ids():
for fep in glob.glob("fep/*"):
yield fep.removeprefix("fep/")
def build_url_link(url):
url_number = url.split("/")[-1]
return f"[#{url_number}]({url})"
def index():
fep_files = [FepFile(fep) for fep in get_fep_ids()]
fep_files = sorted(
fep_files,
key=lambda x: (x.parsed_frontmatter["dateReceived"], x.parsed_frontmatter["slug"]),
)
return fep_files
class Readme:
@property
def content(self):
return self.frontmatter + self.table + self.backmatter
@property
def frontmatter(self):
with open("scripts/frontmatter.md") as f:
return f.readlines()
@property
def backmatter(self):
with open("scripts/backmatter.md") as f:
return f.readlines()
@property
def table(self):
result = []
for fep in index():
link = f"[FEP-{fep.fep}: {fep.title}](./{fep.filename})"
parsed = fep.parsed_frontmatter
tracking_issue = build_url_link(parsed["trackingIssue"])
if "dateFinalized" in parsed:
date_final = parsed["dateFinalized"]
elif "dateWithdrawn" in parsed:
date_final = parsed["dateWithdrawn"]
else:
date_final = "-"
result.append(
f"""| {link} | `{parsed["status"]}` | {tracking_issue} | {parsed["dateReceived"]} | {date_final} |\n"""
)
return result
+16 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
from tools import get_fep_ids, FepFile
from tools.issue import create_issue
from fep_tools import get_fep_ids, FepFile
from fep_tools.issue import create_issue
import os
@@ -9,10 +9,23 @@ owner = os.environ.get("CI_REPO_OWNER")
repo = os.environ.get("CI_REPO_NAME")
token = os.environ.get("CODEBERG_API_TOKEN")
if not isinstance(owner, str):
print("Please set the owner of the repostory at CI_REPO_OWNER")
exit(1)
if not isinstance(repo, str):
print("Please set the repository at CI_REPO_NAME")
exit(1)
if not isinstance(token, str):
print("Please provide a codeberg api token")
exit(1)
for slug in get_fep_ids():
fep_file = FepFile(slug)
tracking_issue = fep_file.parsed_frontmatter.get("trackingIssue")
if not tracking_issue:
print(slug, tracking_issue)
create_issue(owner, repo, token, slug)
create_issue(owner, repo, token, slug)