Skip to main content

cog_helpers.py

1"""
2Some helper functions for generating the README files in this repo.
3In particular, it can take a list of scripts as a Python object, and
4create an HTML definition list that describes them in a human-readable way.
6Here Cog is Ned Batchelder's file generation tool, described here:
7https://nedbatchelder.com/code/cog
8"""
10import os
11from pathlib import Path
12import textwrap
13from typing import TypedDict
15import cog
18class ScriptWithName(TypedDict):
19 name: str
20 description: str
23class ScriptWithVariants(TypedDict):
24 variants: list[str]
25 description: str
28class ScriptWithUsage(TypedDict):
29 usage: str
30 description: str
33Script = ScriptWithName | ScriptWithUsage | ScriptWithVariants
36def outl(s: str, indent: int = 0):
37 cog.outl(textwrap.indent(s, prefix=" " * indent))
40def create_description_table(
41 folder_name: str,
42 scripts: list[Script],
43 repo_name: str = "alexwlchan/scripts",
44 primary_branch: str = "main",
45 ignore_files: set[str] | None = None,
46) -> None:
47 documented_files = set()
49 if ignore_files is None:
50 ignore_files = set()
52 folder = Path(folder_name)
54 for f in ignore_files:
55 if not (folder / f).exists():
56 raise ValueError(f"Ignoring non-existing file {f!r}")
58 outl("<dl>")
60 for i, s in enumerate(scripts, start=1):
61 if "name" in s:
62 variants = [s["name"]]
63 elif "variants" in s:
64 variants = s["variants"]
65 else:
66 variants = [s["usage"].split()[0]]
68 outl("<dt>", indent=2)
70 for index, v in enumerate(variants, start=1):
71 name = v.split()[0]
73 path = folder / name
74 assert path.exists(), path
76 documented_files.add(name)
78 outl(
79 f'<a href="https://github.com/{repo_name}/blob/{primary_branch}/{folder_name}/{name}">',
80 indent=4,
81 )
83 try:
84 usage = s["usage"]
85 except KeyError:
86 usage = v
88 outl(f"<code>{usage}</code>", indent=6)
89 outl("</a>", indent=4)
91 if index != len(variants):
92 outl("/")
94 outl("</dt>", indent=2)
96 outl("<dd>", indent=2)
97 outl(textwrap.dedent(s["description"]).strip(), indent=4)
98 outl("</dd>", indent=2)
100 if i != len(scripts):
101 outl("")
103 outl("</dl>")
105 # Now check there isn't anything in the folder which should have
106 # been documented, but isn't.
107 undocumented_files = set()
109 for f in os.listdir(folder_name):
110 if os.path.isdir(folder / f):
111 continue
113 if f in {"README.md", "requirements.in", "requirements.txt"}:
114 continue
116 if f.startswith(("test_", "_", ".")):
117 continue
119 if f.endswith((".png", ".db")):
120 continue
122 if f in ignore_files:
123 continue
125 if f not in documented_files:
126 undocumented_files.add(f)
128 if undocumented_files:
129 raise ValueError(
130 f"Not all files in {folder_name} are documented: {undocumented_files}"
131 )