Coverage for plugin/scripts/init_skeleton.py: 100%
39 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-15 14:46 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-15 14:46 +0000
1#!/usr/bin/env python3
2"""Finish an `init --lib` skeleton into a rhiza-shaped one — behind `/rhiza:skeleton`.
4Three languages, one remit: close the gap between what the language's own initialiser
5writes and what a rhiza-managed repo needs, so `/rhiza:update`'s synced gates have
6something to pass. Running `uv init --lib` / `cargo init --lib` / `go mod init` is the
7caller's job — this only finishes the result, and reports what's missing when there is
8nothing to finish.
10**This module is the dispatcher and the CLI, nothing else.** Each language's gap is
11different in kind rather than in degree, so each has its own module and this one only
12picks between them:
14 `_skeleton_python` uv's placeholder `hello()`, the empty README, and the four
15 `[project]` keys the template's pyproject gate requires
16 `_skeleton_rust` the `[package]` metadata crates.io wants, plus the doc comments
17 `-D missing_docs` denies — cargo's stub is added to, never replaced
18 `_skeleton_go` a `README.md` and a `doc.go`; `go.mod` holds no metadata to fill in
19 `_skeleton_version` `[tool.bumpversion]`, so `/rhiza:release` never guesses a version
20 `_skeleton_common` the README stub and git identity all three need
21 `_rhiza_toml` the shared "add a key, reformat nothing" TOML primitives, which
22 `set_license` and `set_python_version` use as well
24Every edit is idempotent and additive: real code and hand-written metadata are never
25overwritten, and each placeholder is only rewritten while it still *is* the initialiser's
26placeholder. Stdlib-only, so `/skeleton` can run it with no install step.
28Usage:
29 uv run --python 3.12 --no-project python \
30 scripts/init_skeleton.py [TARGET] --owner OWNER --repo NAME \
31 [--host github|gitlab] [--language python|rust|go] [--description TEXT] [--json]
32"""
34from __future__ import annotations
36import argparse
37import json
38import sys
39from pathlib import Path
40from typing import Any
42sys.path.insert(0, str(Path(__file__).resolve().parent))
43import _skeleton_common as common # noqa: E402
44from _skeleton_go import finish_go # noqa: E402
45from _skeleton_python import finish_python # noqa: E402
46from _skeleton_rust import finish_rust # noqa: E402
47from _skeleton_version import note_bumpversion # noqa: E402
50def finish_skeleton(
51 target: Path,
52 *,
53 owner: str,
54 repo: str,
55 host: str,
56 description: str | None,
57 language: str = "python",
58) -> dict[str, Any]:
59 """Finish the `uv init` / `cargo init` / `go mod init` skeleton; return a summary.
61 The version location is declared last for every language, and only when the manifest
62 work succeeded — it anchors to the version that manifest declares.
63 """
64 modified: list[str] = []
65 notes: list[str] = []
67 if language == "go":
68 # Go takes no owner or host: `go.mod` has no field either one could fill.
69 result = finish_go(
70 target, repo=repo, description=description, modified=modified, notes=notes
71 )
72 else:
73 finish = finish_rust if language == "rust" else finish_python
74 result = finish(
75 target,
76 owner=owner,
77 repo=repo,
78 domain=common.host_domain(host),
79 description=description,
80 modified=modified,
81 notes=notes,
82 )
84 note_bumpversion(target, language, result)
85 return result
88def main(argv: list[str] | None = None) -> int:
89 """Entry point: parse args, finish the skeleton, return an exit code."""
90 parser = argparse.ArgumentParser(
91 description="Finish a `uv init` / `cargo init` skeleton into a rhiza-shaped one.",
92 )
93 parser.add_argument(
94 "target", nargs="?", default=".", help="Repository root (default: current directory)."
95 )
96 parser.add_argument("--owner", required=True, help="GitHub/GitLab owner or org.")
97 parser.add_argument("--repo", required=True, help="Repository name (for the project URLs).")
98 parser.add_argument(
99 "--host", choices=("github", "gitlab"), default="github", help="Git hosting platform."
100 )
101 parser.add_argument(
102 "--language",
103 choices=("python", "rust", "go"),
104 default="python",
105 help="Which skeleton to finish: uv's pyproject.toml, cargo's Cargo.toml, "
106 "or go mod init's go.mod.",
107 )
108 parser.add_argument("--description", help="Project description (replaces uv's placeholder).")
109 parser.add_argument(
110 "--json", dest="json_output", action="store_true", help="Emit the summary as JSON."
111 )
112 args = parser.parse_args(argv)
114 summary = finish_skeleton(
115 Path(args.target).resolve(),
116 owner=args.owner,
117 repo=args.repo,
118 host=args.host,
119 description=args.description,
120 language=args.language,
121 )
123 if args.json_output:
124 print(json.dumps(summary, indent=2))
125 else:
126 for path in summary["modified"]:
127 print(f"modified {path}")
128 for note in summary["notes"]:
129 print(f"note {note}", file=sys.stderr)
130 return 0 if summary["ok"] else 1
133if __name__ == "__main__":
134 raise SystemExit(main())