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:
@@ -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
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
Executable
+42
@@ -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 |
@@ -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}();
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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} |"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -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,6 +1,6 @@
|
||||
from argparse import ArgumentParser
|
||||
from datetime import date, timedelta
|
||||
from tools import FepFile
|
||||
from fep_tools import FepFile
|
||||
|
||||
|
||||
import json
|
||||
|
||||
@@ -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"]
|
||||
@@ -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`) |
|
||||
| --- | --- | ----- | ------- | ------ |
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user