"""napalm-standard: the commands the shared workflow runs in every driver repo. napalm-standard check [--tag vX.Y.Z] [PATH] does the repo meet the standard? napalm-standard ratchet [--write] [PATH] hold the quality baseline, or rewrite it napalm-standard notes vX.Y.Z [PATH] print that version's CHANGELOG section napalm-standard release vX.Y.Z [PATH] create or update the Gitea release; reads GITEA_URL, GITEA_REPOSITORY, GITEA_TOKEN napalm-standard package [PATH] print the import package (for mypy, bandit, cov) """ from __future__ import annotations import argparse import os import pathlib import sys import tomllib from napalm_standard import ratchet from napalm_standard.changelog import changelog_section from napalm_standard.check import Repo, check_repo from napalm_standard.release import gitea_api, publish _RELEASE_ENV = ("GITEA_URL", "GITEA_REPOSITORY", "GITEA_TOKEN") def _repo(path: pathlib.Path) -> Repo: return Repo(path, tomllib.loads((path / "pyproject.toml").read_text())) def _check(args: argparse.Namespace) -> int: findings = check_repo(args.path, tag=args.tag) for finding in findings: print(finding) if findings: print(f"{len(findings)} finding(s); see STANDARDS.md in NAPALM/driver-standard") return 1 print("meets the standard") return 0 def _ratchet(args: argparse.Namespace) -> int: try: code, text = ratchet.run(args.path, _repo(args.path).package, write=args.write) except FileNotFoundError as exc: print(exc, file=sys.stderr) return 2 print(text) return code def _notes(args: argparse.Namespace) -> int: section = changelog_section((args.path / "CHANGELOG.md").read_text(), args.tag) if section is None: return 1 print(section) return 0 def _release(args: argparse.Namespace) -> int: missing = [name for name in _RELEASE_ENV if not os.environ.get(name)] if missing: print(f"missing environment: {', '.join(missing)}", file=sys.stderr) return 2 api = gitea_api(*(os.environ[name] for name in _RELEASE_ENV)) changelog = (args.path / "CHANGELOG.md").read_text() result = publish(args.tag, _repo(args.path).name, changelog, api) print(f"release {result.get('id')} for {args.tag}") return 0 def _package(args: argparse.Namespace) -> int: print(_repo(args.path).package) return 0 def _parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="napalm-standard", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, ) commands = parser.add_subparsers(dest="command", required=True) def command(name: str, handler: object) -> argparse.ArgumentParser: sub = commands.add_parser(name) sub.set_defaults(handler=handler) return sub check = command("check", _check) check.add_argument("--tag", help="also apply the release rules for this tag") ratchet_cmd = command("ratchet", _ratchet) ratchet_cmd.add_argument("--write", action="store_true", help="record today's state") for name, handler in (("notes", _notes), ("release", _release)): command(name, handler).add_argument("tag") command("package", _package) for sub in commands.choices.values(): sub.add_argument("path", nargs="?", type=pathlib.Path, default=pathlib.Path(".")) return parser def main(argv: list[str] | None = None) -> int: args = _parser().parse_args(argv) code: int = args.handler(args) return code