build-release-notes: add change author info DB
This allows us to have links to peoples' GitHub and Forgejo profiles. I used YAML because I don't want to introduce a dependency on having a working Nix evaluator to be able to build release notes, and we already have a YAML parser in this script. Change-Id: Idf2813f79e0407460c796cba6c383496465e152d
This commit is contained in:
parent
272db8af1b
commit
c8329e5667
1 changed files with 65 additions and 11 deletions
|
@ -4,9 +4,13 @@ import sys
|
||||||
import pathlib
|
import pathlib
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Any, Tuple
|
from typing import Any, Tuple
|
||||||
|
import dataclasses
|
||||||
|
import yaml
|
||||||
|
|
||||||
GH_BASE = "https://github.com/NixOS/nix"
|
GH_ROOT = "https://github.com/"
|
||||||
FORGEJO_BASE = "https://git.lix.systems/lix-project/lix"
|
GH_REPO_BASE = "https://github.com/NixOS/nix"
|
||||||
|
FORGEJO_REPO_BASE = "https://git.lix.systems/lix-project/lix"
|
||||||
|
FORGEJO_ROOT = "https://git.lix.systems/"
|
||||||
GERRIT_BASE = "https://gerrit.lix.systems/c/lix/+"
|
GERRIT_BASE = "https://gerrit.lix.systems/c/lix/+"
|
||||||
KNOWN_KEYS = ('synopsis', 'cls', 'issues', 'prs', 'significance', 'category', 'credits')
|
KNOWN_KEYS = ('synopsis', 'cls', 'issues', 'prs', 'significance', 'category', 'credits')
|
||||||
|
|
||||||
|
@ -26,14 +30,49 @@ CATEGORIES = [
|
||||||
'Miscellany',
|
'Miscellany',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class AuthorInfo:
|
||||||
|
name: str
|
||||||
|
github: str | None = None
|
||||||
|
forgejo: str | None = None
|
||||||
|
display_name: str | None = None
|
||||||
|
|
||||||
|
def show_name(self) -> str:
|
||||||
|
return self.display_name or self.name
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if self.forgejo:
|
||||||
|
return f'[{self.show_name()}]({FORGEJO_ROOT}{self.forgejo})'
|
||||||
|
elif self.github:
|
||||||
|
return f'[{self.show_name()}]({GH_ROOT}{self.github})'
|
||||||
|
else:
|
||||||
|
return self.show_name()
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorInfoDB:
|
||||||
|
def __init__(self, author_info: dict[str, dict], throw_on_missing: bool):
|
||||||
|
self.author_info = {name: AuthorInfo(name=name, **d) for (name, d) in author_info.items()}
|
||||||
|
self.throw_on_missing = throw_on_missing
|
||||||
|
|
||||||
|
def __getitem__(self, name) -> str:
|
||||||
|
if name in self.author_info:
|
||||||
|
return str(self.author_info[name])
|
||||||
|
else:
|
||||||
|
if self.throw_on_missing:
|
||||||
|
raise Exception(f'Missing author info for author {name}')
|
||||||
|
else:
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
def format_link(ident: str, gh_part: str, fj_part: str) -> str:
|
def format_link(ident: str, gh_part: str, fj_part: str) -> str:
|
||||||
# FIXME: deprecate github as default
|
# FIXME: deprecate github as default
|
||||||
if ident.isdigit():
|
if ident.isdigit():
|
||||||
num, link, base = int(ident), f"#{ident}", f"{GH_BASE}/{gh_part}"
|
num, link, base = int(ident), f"#{ident}", f"{GH_REPO_BASE}/{gh_part}"
|
||||||
elif ident.startswith("gh#"):
|
elif ident.startswith("gh#"):
|
||||||
num, link, base = int(ident[3:]), ident, f"{GH_BASE}/{gh_part}"
|
num, link, base = int(ident[3:]), ident, f"{GH_REPO_BASE}/{gh_part}"
|
||||||
elif ident.startswith("fj#"):
|
elif ident.startswith("fj#"):
|
||||||
num, link, base = int(ident[3:]), ident, f"{FORGEJO_BASE}/{fj_part}"
|
num, link, base = int(ident[3:]), ident, f"{FORGEJO_REPO_BASE}/{fj_part}"
|
||||||
else:
|
else:
|
||||||
raise Exception("unrecognized reference format", ident)
|
raise Exception("unrecognized reference format", ident)
|
||||||
return f"[{link}]({base}/{num})"
|
return f"[{link}]({base}/{num})"
|
||||||
|
@ -58,7 +97,7 @@ def listify(l: list | int) -> list:
|
||||||
else:
|
else:
|
||||||
return l
|
return l
|
||||||
|
|
||||||
def do_category(entries: list[Tuple[pathlib.Path, Any]]):
|
def do_category(author_info: AuthorInfoDB, entries: list[Tuple[pathlib.Path, Any]]):
|
||||||
for p, entry in sorted(entries, key=lambda e: (-SIGNIFICANCECES[e[1].metadata.get('significance')], e[0])):
|
for p, entry in sorted(entries, key=lambda e: (-SIGNIFICANCECES[e[1].metadata.get('significance')], e[0])):
|
||||||
try:
|
try:
|
||||||
header = entry.metadata['synopsis']
|
header = entry.metadata['synopsis']
|
||||||
|
@ -74,13 +113,13 @@ def do_category(entries: list[Tuple[pathlib.Path, Any]]):
|
||||||
print(textwrap.indent(entry.content, ' '))
|
print(textwrap.indent(entry.content, ' '))
|
||||||
if credits := listify(entry.metadata.get('credits', [])):
|
if credits := listify(entry.metadata.get('credits', [])):
|
||||||
print()
|
print()
|
||||||
print(textwrap.indent('Many thanks to {} for this.'.format(plural_list(credits)), ' '))
|
print(textwrap.indent('Many thanks to {} for this.'.format(plural_list(list(author_info[c] for c in credits))), ' '))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
e.add_note(f"in {p}")
|
e.add_note(f"in {p}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def run_on_dir(d):
|
def run_on_dir(author_info: AuthorInfoDB, d):
|
||||||
d = pathlib.Path(d)
|
d = pathlib.Path(d)
|
||||||
if not d.is_dir():
|
if not d.is_dir():
|
||||||
raise ValueError(f'provided path {d} is not a directory')
|
raise ValueError(f'provided path {d} is not a directory')
|
||||||
|
@ -105,9 +144,24 @@ def run_on_dir(d):
|
||||||
for category in CATEGORIES:
|
for category in CATEGORIES:
|
||||||
if entries[category]:
|
if entries[category]:
|
||||||
print('\n#', category)
|
print('\n#', category)
|
||||||
do_category(entries[category])
|
do_category(author_info, entries[category])
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument('--change-authors', help='File name of the change authors metadata YAML file', type=argparse.FileType('r'))
|
||||||
|
ap.add_argument('dirs', help='Directories to run on', nargs='+')
|
||||||
|
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
author_info = AuthorInfoDB(yaml.safe_load(args.change_authors), throw_on_missing=True) \
|
||||||
|
if args.change_authors \
|
||||||
|
else AuthorInfoDB({}, throw_on_missing=False)
|
||||||
|
|
||||||
|
for d in args.dirs:
|
||||||
|
run_on_dir(author_info, d)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
for d in sys.argv[1:]:
|
main()
|
||||||
run_on_dir(d)
|
|
||||||
|
|
Loading…
Reference in a new issue