"""Render the repository's terminal aliases as a reference page. The ``aliases`` file is the source. An alias that only wraps a make target carries no comment by repository rule, so its description is taken from the target it calls; every other alias documents itself in the trailing comment. """ from __future__ import annotations import argparse import re from pathlib import Path from . import literal ALIAS = re.compile( r"^alias\s+([^=]+)=(['\"])(?P.*)\2(?:\s*#\s*(?P.*\S))?\s*$" ) MAKE_CALL = re.compile(r"(?:^|[\s;&(])(?:m|make)\s+([a-z][a-z0-9-]*)") HEADER = """\ Terminal Aliases ================ The ``i8*`` shell aliases installed by ``make install-alias``, read from the repository's ``aliases`` file. An alias that only wraps a make target is listed with that target; the rest carry their own description. Generated by the documentation build; edit ``aliases`` rather than this page. """ def parse(aliases_file: Path) -> list[tuple[str, str, str | None]]: """Return every alias as ``(name, body, comment)``. Args: aliases_file: the ``aliases`` file to read. Raises: ValueError: a line starts with ``alias`` but does not parse, which would otherwise drop it from the page without a trace. """ parsed: list[tuple[str, str, str | None]] = [] for line in aliases_file.read_text(encoding="utf-8").splitlines(): if not line.startswith("alias "): continue match = ALIAS.match(line) if match is None: raise ValueError(f"unparseable alias line: {line!r}") parsed.append( (match.group(1).strip(), match.group("body"), match.group("comment")) ) return parsed def _entry(name: str, body: str, comment: str | None) -> str: """Return one definition-list entry for an alias. Args: name: the alias name. body: what the alias expands to. comment: the trailing comment, absent for a plain make wrapper. """ targets = sorted(set(MAKE_CALL.findall(body))) if comment: description = literal(comment) elif targets: description = "Runs " + ", ".join(f"``make {t}``" for t in targets) + "." else: description = "Undocumented." return f"``{name}``\n {description}" def generate(aliases_file: Path, output_file: Path) -> int: """Write the alias reference page. Args: aliases_file: the ``aliases`` file to read. output_file: the ``.rst`` page to write. Returns: How many aliases the page documents. """ entries = parse(aliases_file) output_file.parent.mkdir(parents=True, exist_ok=True) output_file.write_text( HEADER + "\n\n".join(_entry(*entry) for entry in entries) + "\n", encoding="utf-8", ) return len(entries) def main(argv=None): parser = argparse.ArgumentParser( description="Generate the alias reference from the aliases file." ) parser.add_argument("--aliases-file", required=True, help="Path to aliases.") parser.add_argument("--output-file", required=True, help="Path to the .rst page.") args = parser.parse_args(argv) output = Path(args.output_file) count = generate(Path(args.aliases_file), output) print(f"{count} alias(es) written to {output}") return 0 if __name__ == "__main__": raise SystemExit(main())