|
10 | 10 | import inspect |
11 | 11 | import re |
12 | 12 | import shutil |
| 13 | +import subprocess |
13 | 14 | from collections.abc import Callable, Iterable, Sequence |
14 | 15 | from dataclasses import dataclass |
15 | 16 | from pathlib import Path |
16 | 17 | from textwrap import dedent, indent |
17 | 18 | from typing import TypeVar |
18 | 19 |
|
| 20 | +from interfacy import Interfacy |
19 | 21 | from jinja2 import Template |
20 | 22 |
|
21 | 23 | from rattle.config import BUILTIN_RULE_COLLECTIONS, find_rules |
22 | 24 | from rattle.ftypes import Invalid, QualifiedRule, Valid |
23 | 25 | from rattle.rule import LintRule, RuleReference, RuleSetting |
24 | 26 |
|
25 | | -DOCS_DIR = Path(__file__).parent.parent / "docs" |
| 27 | +PROJECT_ROOT = Path(__file__).parent.parent |
| 28 | +DOCS_DIR = PROJECT_ROOT / "docs" |
26 | 29 | RULES_INDEX_DOC = DOCS_DIR / "guide" / "builtins.md" |
27 | 30 | RULES_CATEGORY_DIR = DOCS_DIR / "guide" / "rule-collections" |
28 | 31 | RULES_DETAIL_DIR = DOCS_DIR / "guide" / "rules" |
@@ -534,12 +537,62 @@ def render_rule_details(rules: Iterable[RuleDoc]) -> None: |
534 | 537 | (RULES_DETAIL_DIR / f"{rule.slug}.md").write_text(rendered.rstrip() + "\n") |
535 | 538 |
|
536 | 539 |
|
537 | | -def main() -> None: |
| 540 | +def generated_paths() -> tuple[Path, ...]: |
| 541 | + return (RULES_INDEX_DOC, RULES_CATEGORY_DIR, RULES_DETAIL_DIR) |
| 542 | + |
| 543 | + |
| 544 | +def git_path(path: Path) -> str: |
| 545 | + return path.relative_to(PROJECT_ROOT).as_posix() |
| 546 | + |
| 547 | + |
| 548 | +def git_executable() -> str: |
| 549 | + git = shutil.which("git") |
| 550 | + if git is None: |
| 551 | + raise RuntimeError("git executable not found") |
| 552 | + return git |
| 553 | + |
| 554 | + |
| 555 | +def commit_generated_docs(message: str) -> None: |
| 556 | + git = git_executable() |
| 557 | + paths = [git_path(path) for path in generated_paths()] |
| 558 | + subprocess.run([git, "add", "--", *paths], cwd=PROJECT_ROOT, check=True) # noqa: S603 |
| 559 | + diff = subprocess.run( # noqa: S603 |
| 560 | + [git, "diff", "--cached", "--quiet", "--", *paths], |
| 561 | + cwd=PROJECT_ROOT, |
| 562 | + check=False, |
| 563 | + ) |
| 564 | + if diff.returncode == 0: |
| 565 | + return |
| 566 | + if diff.returncode != 1: |
| 567 | + raise subprocess.CalledProcessError(diff.returncode, diff.args) |
| 568 | + |
| 569 | + subprocess.run( # noqa: S603 |
| 570 | + [git, "commit", "-m", message, "--", *paths], cwd=PROJECT_ROOT, check=True |
| 571 | + ) |
| 572 | + |
| 573 | + |
| 574 | +def generate_rule_docs( |
| 575 | + *, |
| 576 | + commit: bool = False, |
| 577 | + message: str = "docs: regenerate rule documentation", |
| 578 | +) -> None: |
| 579 | + """Generate rule documentation. |
| 580 | +
|
| 581 | + Args: |
| 582 | + commit: Commit generated documentation changes with a Conventional Commit message. |
| 583 | + message: Commit message to use when commit is enabled. |
| 584 | + """ |
538 | 585 | categories = build_categories() |
539 | 586 | render_rule_categories(categories) |
540 | 587 | render_rule_details(rule for category in categories for rule in category.rules) |
541 | 588 | rendered = INDEX_TPL.render(categories=categories) |
542 | 589 | RULES_INDEX_DOC.write_text(rendered.rstrip() + "\n") |
| 590 | + if commit: |
| 591 | + commit_generated_docs(message) |
| 592 | + |
| 593 | + |
| 594 | +def main(args: list[str] | None = None, *, sys_exit_enabled: bool = True) -> object: |
| 595 | + return Interfacy(sys_exit_enabled=sys_exit_enabled).run(generate_rule_docs, args=args) |
543 | 596 |
|
544 | 597 |
|
545 | 598 | if __name__ == "__main__": |
|
0 commit comments