for assorted things
1#!/usr/bin/env -S uv run --script --quiet
2# /// script
3# requires-python = ">=3.12"
4# ///
5"""
6Update the README.md file with a list of all the scripts in the current directory.
7
8Usage:
9
10```bash
11./update-readme
12```
13"""
14
15import ast
16import os
17import stat
18from pathlib import Path
19
20
21def get_docstring(path: Path) -> str:
22 try:
23 with open(path) as f:
24 tree = ast.parse(f.read())
25 if not tree.body:
26 return ""
27 if isinstance(tree.body[0], ast.Expr) and isinstance(
28 tree.body[0].value, ast.Constant
29 ):
30 return str(tree.body[0].value.value).strip()
31 return ""
32 except Exception:
33 return ""
34
35
36def get_scripts() -> list[tuple[Path, str]]:
37 scripts = []
38 for path in Path(".").iterdir():
39 if (
40 path.is_file()
41 and path.suffix in {".py", ""}
42 and not path.name.startswith(".")
43 and is_executable(path)
44 ):
45 doc = get_docstring(path)
46 scripts.append((path, doc))
47 return sorted(scripts)
48
49
50def is_executable(path: Path) -> bool:
51 # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html#tag_13_27_03_04
52 return bool(os.stat(path).st_mode & stat.S_IXUSR)
53
54
55def update_readme() -> None:
56 readme = Path("README.md")
57 content = readme.read_text()
58
59 parts = content.split("## scripts", 1)
60 base_content = parts[0].strip()
61
62 scripts = get_scripts()
63 script_list = "\n\n## scripts\n\n"
64
65 for script, _ in scripts:
66 script_list += f"- [`{script.name}`](#{script.name})\n"
67 script_list += "\n---\n\n"
68
69 for i, (script, doc) in enumerate(scripts):
70 script_list += f"### `{script.name}`\n\n{doc or 'no description'}\n\n"
71 if i < len(scripts) - 1:
72 script_list += "---\n\n"
73
74 new_content = base_content + script_list
75
76 if new_content != content:
77 readme.write_text(new_content)
78
79
80if __name__ == "__main__":
81 update_readme()