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

1#!/usr/bin/env python3 

2"""Finish an `init --lib` skeleton into a rhiza-shaped one — behind `/rhiza:skeleton`. 

3 

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. 

9 

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: 

13 

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 

23 

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. 

27 

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""" 

33 

34from __future__ import annotations 

35 

36import argparse 

37import json 

38import sys 

39from pathlib import Path 

40from typing import Any 

41 

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 

48 

49 

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. 

60 

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] = [] 

66 

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 ) 

83 

84 note_bumpversion(target, language, result) 

85 return result 

86 

87 

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) 

113 

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 ) 

122 

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 

131 

132 

133if __name__ == "__main__": 

134 raise SystemExit(main())