Coverage for src / rhiza / cli.py: 100%
35 statements
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-29 01:59 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-29 01:59 +0000
1"""Rhiza command-line interface (CLI).
3This module defines the Typer application entry points exposed by Rhiza.
4Commands are thin wrappers around implementations in `rhiza.commands.*`.
5"""
7from pathlib import Path
9import typer
11from rhiza import __version__
12from rhiza.commands import init as init_cmd
13from rhiza.commands import materialize as materialize_cmd
14from rhiza.commands import validate as validate_cmd
15from rhiza.commands.migrate import migrate as migrate_cmd
16from rhiza.commands.uninstall import uninstall as uninstall_cmd
17from rhiza.commands.welcome import welcome as welcome_cmd
19app = typer.Typer(
20 help=(
21 """
22 Rhiza - Manage reusable configuration templates for Python projects
24 \x1b]8;;https://jebel-quant.github.io/rhiza-cli/\x1b\\https://jebel-quant.github.io/rhiza-cli/\x1b]8;;\x1b\\
25 """
26 ),
27 add_completion=True,
28)
31def version_callback(value: bool):
32 """Print version information and exit.
34 Args:
35 value: Whether the --version flag was provided.
37 Raises:
38 typer.Exit: Always exits after printing version.
39 """
40 if value:
41 typer.echo(f"rhiza version {__version__}")
42 raise typer.Exit()
45@app.callback()
46def main(
47 version: bool = typer.Option(
48 False,
49 "--version",
50 "-v",
51 help="Show version and exit",
52 callback=version_callback,
53 is_eager=True,
54 ),
55):
56 """Rhiza CLI main callback.
58 This callback is executed before any command. It handles global options
59 like --version.
61 Args:
62 version: Version flag (handled by callback).
63 """
66@app.command()
67def init(
68 target: Path = typer.Argument(
69 default=Path("."), # default to current directory
70 exists=True,
71 file_okay=False,
72 dir_okay=True,
73 help="Target directory (defaults to current directory)",
74 ),
75 project_name: str = typer.Option(
76 None,
77 "--project-name",
78 help="Custom project name (defaults to directory name)",
79 ),
80 package_name: str = typer.Option(
81 None,
82 "--package-name",
83 help="Custom package name (defaults to normalized project name)",
84 ),
85 with_dev_dependencies: bool = typer.Option(
86 False,
87 "--with-dev-dependencies",
88 help="Include development dependencies in pyproject.toml",
89 ),
90 git_host: str = typer.Option(
91 None,
92 "--git-host",
93 help="Target Git hosting platform (github or gitlab). Determines which CI/CD files to include. "
94 "If not provided, will prompt interactively.",
95 ),
96):
97 r"""Initialize or validate .github/rhiza/template.yml.
99 Creates a default `.github/rhiza/template.yml` configuration file if one
100 doesn't exist, or validates the existing configuration.
102 The default template includes common Python project files.
103 The --git-host option determines which CI/CD configuration to include:
104 - github: includes .github folder (GitHub Actions workflows)
105 - gitlab: includes .gitlab-ci.yml (GitLab CI configuration)
107 Examples:
108 rhiza init
109 rhiza init --git-host github
110 rhiza init --git-host gitlab
111 rhiza init /path/to/project
112 rhiza init ..
113 """
114 init_cmd(
115 target,
116 project_name=project_name,
117 package_name=package_name,
118 with_dev_dependencies=with_dev_dependencies,
119 git_host=git_host,
120 )
123@app.command()
124def materialize(
125 target: Path = typer.Argument(
126 default=Path("."), # default to current directory
127 exists=True,
128 file_okay=False,
129 dir_okay=True,
130 help="Target git repository (defaults to current directory)",
131 ),
132 branch: str = typer.Option("main", "--branch", "-b", help="Rhiza branch to use"),
133 target_branch: str = typer.Option(
134 None,
135 "--target-branch",
136 "--checkout-branch",
137 help="Create and checkout a new branch in the target repository for changes",
138 ),
139 force: bool = typer.Option(False, "--force", "-y", help="Overwrite existing files"),
140):
141 r"""Inject Rhiza configuration templates into a target repository.
143 Materializes configuration files from the template repository specified
144 in .github/rhiza/template.yml into your project. This command:
146 - Reads .github/rhiza/template.yml configuration
147 - Performs a sparse clone of the template repository
148 - Copies specified files/directories to your project
149 - Respects exclusion patterns defined in the configuration
150 - Files that already exist will NOT be overwritten unless --force is used.
152 Examples:
153 rhiza materialize
154 rhiza materialize --branch develop
155 rhiza materialize --force
156 rhiza materialize --target-branch feature/update-templates
157 rhiza materialize /path/to/project -b v2.0 -y
158 """
159 materialize_cmd(target, branch, target_branch, force)
162@app.command()
163def validate(
164 target: Path = typer.Argument(
165 default=Path("."), # default to current directory
166 exists=True,
167 file_okay=False,
168 dir_okay=True,
169 help="Target git repository (defaults to current directory)",
170 ),
171):
172 r"""Validate Rhiza template configuration.
174 Validates the .github/rhiza/template.yml file to ensure it is syntactically
175 correct and semantically valid.
177 Performs comprehensive validation:
178 - Checks if template.yml exists
179 - Validates YAML syntax
180 - Verifies required fields are present (template-repository, include)
181 - Validates field types and formats
182 - Ensures repository name follows owner/repo format
183 - Confirms include paths are not empty
186 Returns exit code 0 on success, 1 on validation failure.
188 Examples:
189 rhiza validate
190 rhiza validate /path/to/project
191 rhiza validate ..
192 """
193 if not validate_cmd(target):
194 raise typer.Exit(code=1)
197@app.command()
198def migrate(
199 target: Path = typer.Argument(
200 default=Path("."), # default to current directory
201 exists=True,
202 file_okay=False,
203 dir_okay=True,
204 help="Target git repository (defaults to current directory)",
205 ),
206):
207 r"""Migrate project to the new .rhiza folder structure.
209 This command helps transition projects to use the new `.rhiza/` folder
210 structure for storing Rhiza state and configuration files. It performs
211 the following migrations:
213 - Creates the `.rhiza/` directory in the project root
214 - Moves `.github/rhiza/template.yml` or `.github/template.yml` to `.rhiza/template.yml`
215 - Moves `.rhiza.history` to `.rhiza/history`
217 The new `.rhiza/` folder structure separates Rhiza's state and configuration
218 from the `.github/` directory, providing better organization.
220 If files already exist in `.rhiza/`, the migration will skip them and leave
221 the old files in place. You can manually remove old files after verifying
222 the migration was successful.
224 Examples:
225 rhiza migrate
226 rhiza migrate /path/to/project
227 """
228 migrate_cmd(target)
231@app.command()
232def welcome():
233 r"""Display a friendly welcome message and explain what Rhiza is.
235 Shows a welcome message, explains Rhiza's purpose, key features,
236 and provides guidance on getting started with the tool.
238 Examples:
239 rhiza welcome
240 """
241 welcome_cmd()
244@app.command()
245def uninstall(
246 target: Path = typer.Argument(
247 default=Path("."), # default to current directory
248 exists=True,
249 file_okay=False,
250 dir_okay=True,
251 help="Target git repository (defaults to current directory)",
252 ),
253 force: bool = typer.Option(
254 False,
255 "--force",
256 "-y",
257 help="Skip confirmation prompt and proceed with deletion",
258 ),
259):
260 r"""Remove all Rhiza-managed files from the repository.
262 Reads the `.rhiza.history` file and removes all files that were
263 previously materialized by Rhiza templates. This provides a clean
264 way to uninstall all template-managed files from a project.
266 The command will:
267 - Read the list of files from `.rhiza.history`
268 - Prompt for confirmation (unless --force is used)
269 - Delete all listed files that exist
270 - Remove empty directories left behind
271 - Delete the `.rhiza.history` file itself
273 Use this command when you want to completely remove Rhiza templates
274 from your project.
276 Examples:
277 rhiza uninstall
278 rhiza uninstall --force
279 rhiza uninstall /path/to/project
280 rhiza uninstall /path/to/project -y
281 """
282 uninstall_cmd(target, force)