| #!/usr/bin/env python3 | |
| """Remove page-local contents directives from Sphinx documentation.""" | |
| from __future__ import annotations | |
| import argparse | |
| import re | |
| from pathlib import Path | |
| CONTENTS = re.compile( | |
| r"(?m)^```\{contents\}[^\n]*\n(?:.*\n)*?^```\n?|" | |
| r"\n*^\.\. contents::[^\n]*(?:\n(?:[ \t].*|[ \t]*))*\n*" | |
| ) | |
| SUFFIXES = (".rst", ".md", ".td") | |
| def iter_sources(root: Path): | |
| if root.is_file(): | |
| if root.suffix in SUFFIXES: | |
| yield root | |
| return | |
| for suffix in SUFFIXES: | |
| yield from root.rglob(f"*{suffix}") | |
| def rewrite(path: Path) -> int: | |
| text = path.read_text(encoding="utf-8") | |
| def replacement(match: re.Match[str]) -> str: | |
| if match.group(0).lstrip("\n").startswith("```") or not match.start(): | |
| return "" | |
| return "\n\n" if match.end() < len(text) else "\n" | |
| new_text, removed = CONTENTS.subn(replacement, text) | |
| if new_text != text: | |
| path.write_text(new_text, encoding="utf-8") | |
| return removed | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| "roots", | |
| nargs="*", | |
| type=Path, | |
| default=[ | |
| Path("clang/docs"), | |
| Path("clang/Maintainers.md"), | |
| Path("clang/include/clang/Basic"), | |
| Path("clang/include/clang/Options"), | |
| ], | |
| help="Documentation roots to scan, defaults to Clang docs and generated-doc inputs.", | |
| ) | |
| args = parser.parse_args() | |
| removals = [ | |
| (path, rewrite(path)) | |
| for root in args.roots | |
| for path in sorted(iter_sources(root)) | |
| ] | |
| changed = [(path, count) for path, count in removals if count] | |
| for path, count in changed: | |
| print(f"{path}: removed {count} contents directive(s)") | |
| print( | |
| f"removed {sum(count for _, count in changed)} contents directive(s) from {len(changed)} file(s)" | |
| ) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) |