mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-07-09 12:41:05 +02:00
## ℹ️ Description - Link to the related issue(s): N/A - Keeps the README Usage command block in sync with the CLI help by generating it as part of the artifact pipeline. ## 📋 Changes Summary - Added a README Usage generator that renders normalized English CLI help between explicit README markers. - Added `generate-readme-commands` and wired it into `generate-artifacts`. - Extended generated artifact checks to detect README Usage drift without mutating files. - Added focused unit tests for marker replacement, locale/executable normalization, ANSI stripping, and command coverage. - No dependency changes introduced. ### ⚙️ Type of Change Select the type(s) of change(s) included in this pull request: - [ ] 🐞 Bug fix (non-breaking change which fixes an issue) - [x] ✨ New feature (adds new functionality without breaking existing usage) - [ ] 💥 Breaking change (changes that might break existing user setups, scripts, or configurations) ## ✅ Checklist Before requesting a review, confirm the following: - [x] I have reviewed my changes to ensure they meet the project's standards. - [x] I have tested my changes and ensured that all tests pass (`pdm run test`). - [x] I have formatted the code (`pdm run format`). - [x] I have verified that linting passes (`pdm run lint`). - [x] I have updated documentation where necessary. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Kept the README “Usage” section automatically synchronized with the app’s CLI help. * Added a new `--preserve-local-settings` option to the CLI help output. * **Bug Fixes** * Improved and clarified CLI command/option descriptions (notably around `verify`, `extend`, and `update` behavior). * CI now fails early if the generated README “Usage” content is out of date. * **Documentation** * Regenerated the README “Usage” console block to reflect the latest help text and command details. * Updated agent guidance to match the expanded README generation behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
185 lines
6.5 KiB
Python
185 lines
6.5 KiB
Python
# SPDX-FileCopyrightText: © Jens Bergmann and contributors
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
|
"""CI guard: verifies generated schema, default-config, and README artifacts are up-to-date."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import difflib
|
|
import subprocess # noqa: S404
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Final
|
|
|
|
from generate_readme_commands import build_usage_block, replace_marked_section
|
|
from schema_utils import generate_schema_content
|
|
|
|
from kleinanzeigen_bot.model.ad_model import AdPartial
|
|
from kleinanzeigen_bot.model.config_model import Config
|
|
|
|
if TYPE_CHECKING:
|
|
from pydantic import BaseModel
|
|
|
|
SCHEMA_DEFINITIONS:Final[tuple[tuple[str, type[BaseModel], str], ...]] = (
|
|
("schemas/config.schema.json", Config, "Config"),
|
|
("schemas/ad.schema.json", AdPartial, "Ad"),
|
|
)
|
|
DEFAULT_CONFIG_PATH:Final[Path] = Path("docs/config.default.yaml")
|
|
|
|
|
|
def generate_default_config_via_cli(path:Path, repo_root:Path) -> None:
|
|
"""
|
|
Run `python -m kleinanzeigen_bot --config <path> create-config` to generate a default config snapshot.
|
|
"""
|
|
try:
|
|
subprocess.run( # noqa: S603 trusted, static command arguments
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"kleinanzeigen_bot",
|
|
"--config",
|
|
str(path),
|
|
"create-config",
|
|
],
|
|
cwd = repo_root,
|
|
check = True,
|
|
timeout = 60,
|
|
capture_output = True,
|
|
text = True,
|
|
)
|
|
except subprocess.CalledProcessError as error:
|
|
stderr = error.stderr.strip() if error.stderr else "<empty>"
|
|
stdout = error.stdout.strip() if error.stdout else "<empty>"
|
|
raise RuntimeError(
|
|
"Failed to generate default config via CLI.\n"
|
|
f"Return code: {error.returncode}\n"
|
|
f"stderr:\n{stderr}\n"
|
|
f"stdout:\n{stdout}"
|
|
) from error
|
|
|
|
|
|
def get_schema_diffs(repo_root:Path) -> dict[str, str]:
|
|
"""
|
|
Compare committed schema files with freshly generated schema content and return unified diffs per path.
|
|
"""
|
|
diffs:dict[str, str] = {}
|
|
for schema_path, model, schema_name in SCHEMA_DEFINITIONS:
|
|
expected_schema_path = repo_root / schema_path
|
|
expected = expected_schema_path.read_text(encoding = "utf-8") if expected_schema_path.is_file() else ""
|
|
|
|
generated = generate_schema_content(model, schema_name)
|
|
if expected == generated:
|
|
continue
|
|
|
|
diffs[schema_path] = "".join(
|
|
difflib.unified_diff(
|
|
expected.splitlines(keepends = True),
|
|
generated.splitlines(keepends = True),
|
|
fromfile = schema_path,
|
|
tofile = f"<generated via: {model.__name__}.model_json_schema>",
|
|
)
|
|
)
|
|
|
|
return diffs
|
|
|
|
|
|
def get_default_config_diff(repo_root:Path) -> str:
|
|
"""
|
|
Compare docs/config.default.yaml with a freshly generated config artifact and return a unified diff string.
|
|
"""
|
|
expected_config_path = repo_root / DEFAULT_CONFIG_PATH
|
|
if not expected_config_path.is_file():
|
|
raise FileNotFoundError(f"Missing required default config file: {DEFAULT_CONFIG_PATH}")
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
generated_config_path = Path(tmpdir) / "config.default.yaml"
|
|
generate_default_config_via_cli(generated_config_path, repo_root)
|
|
|
|
expected = expected_config_path.read_text(encoding = "utf-8")
|
|
generated = generated_config_path.read_text(encoding = "utf-8")
|
|
|
|
if expected == generated:
|
|
return ""
|
|
|
|
return "".join(
|
|
difflib.unified_diff(
|
|
expected.splitlines(keepends = True),
|
|
generated.splitlines(keepends = True),
|
|
fromfile = str(DEFAULT_CONFIG_PATH),
|
|
tofile = "<generated via: python -m kleinanzeigen_bot --config /path/to/config.default.yaml create-config>",
|
|
)
|
|
)
|
|
|
|
|
|
def get_readme_diff(repo_root:Path) -> str:
|
|
"""
|
|
Compare committed README.md with in-memory generated content and return a unified diff string.
|
|
|
|
Never mutates README.md — computes expected content entirely in memory.
|
|
"""
|
|
readme_path = repo_root / "README.md"
|
|
if not readme_path.is_file():
|
|
raise FileNotFoundError(f"Missing README.md: {readme_path}")
|
|
|
|
committed = readme_path.read_text(encoding = "utf-8")
|
|
new_block = build_usage_block()
|
|
|
|
try:
|
|
expected = replace_marked_section(committed, new_block)
|
|
except ValueError as exc:
|
|
raise RuntimeError(f"README marker validation failed: {exc}") from exc
|
|
|
|
if committed == expected:
|
|
return ""
|
|
|
|
return "".join(
|
|
difflib.unified_diff(
|
|
committed.splitlines(keepends = True),
|
|
expected.splitlines(keepends = True),
|
|
fromfile = "README.md",
|
|
tofile = "<generated via: pdm run generate-readme-commands>",
|
|
)
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
repo_root = Path(__file__).resolve().parent.parent
|
|
|
|
try:
|
|
schema_diffs = get_schema_diffs(repo_root)
|
|
default_config_diff = get_default_config_diff(repo_root)
|
|
readme_diff = get_readme_diff(repo_root)
|
|
except (FileNotFoundError, RuntimeError) as exc:
|
|
raise SystemExit(f"Error while checking generated artifacts: {exc}") from exc
|
|
|
|
if schema_diffs or default_config_diff or readme_diff:
|
|
messages:list[str] = ["Generated artifacts are not up-to-date."]
|
|
|
|
if schema_diffs:
|
|
messages.append("Outdated schema files detected:")
|
|
for path, schema_diff in schema_diffs.items():
|
|
messages.append(f"- {path}")
|
|
messages.append(schema_diff)
|
|
|
|
if default_config_diff:
|
|
messages.append("Outdated docs/config.default.yaml detected.")
|
|
messages.append(default_config_diff)
|
|
|
|
if readme_diff:
|
|
messages.append("Outdated README.md detected (Usage section).")
|
|
messages.append(readme_diff)
|
|
|
|
messages.append("Regenerate with one of the following:")
|
|
messages.append("- Schema files: pdm run generate-schemas")
|
|
messages.append("- Default config snapshot: pdm run generate-config")
|
|
messages.append("- README Usage section: pdm run generate-readme-commands")
|
|
messages.append("- All: pdm run generate-artifacts")
|
|
raise SystemExit("\n".join(messages))
|
|
|
|
print("Generated schemas, docs/config.default.yaml, and README.md are up-to-date.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|