rhiza_tools.cli

CLI commands for Rhiza Tools.

This module defines the main Typer application and all command-line interface commands for rhiza-tools. It provides commands for version bumping, coverage badge generation, release management, and README updates.

The CLI can be used either as a standalone tool (rhiza-tools) or as a subcommand of the rhiza CLI (rhiza tools).

Example:

Bump version to a specific version::

$ rhiza-tools bump 1.2.3
$ rhiza tools bump 1.2.3

Bump version interactively::

$ rhiza-tools bump

Generate a coverage badge::

$ rhiza-tools generate-coverage-badge --coverage-json _tests/coverage.json

Update README with make help output::

$ rhiza-tools update-readme
  1"""CLI commands for Rhiza Tools.
  2
  3This module defines the main Typer application and all command-line interface
  4commands for rhiza-tools. It provides commands for version bumping, coverage
  5badge generation, release management, and README updates.
  6
  7The CLI can be used either as a standalone tool (`rhiza-tools`) or as a
  8subcommand of the rhiza CLI (`rhiza tools`).
  9
 10Example:
 11    Bump version to a specific version::
 12
 13        $ rhiza-tools bump 1.2.3
 14        $ rhiza tools bump 1.2.3
 15
 16    Bump version interactively::
 17
 18        $ rhiza-tools bump
 19
 20    Generate a coverage badge::
 21
 22        $ rhiza-tools generate-coverage-badge --coverage-json _tests/coverage.json
 23
 24    Update README with make help output::
 25
 26        $ rhiza-tools update-readme
 27"""
 28
 29from pathlib import Path
 30from typing import Annotated
 31
 32import typer
 33
 34from rhiza_tools import __version__, console
 35from rhiza_tools.console import configure as configure_console
 36
 37from .commands.analyze_benchmarks import analyze_benchmarks_command
 38from .commands.bump import bump_command
 39from .commands.generate_badge import generate_coverage_badge_command
 40from .commands.release import release_command
 41from .commands.rollback import rollback_command
 42from .commands.update_readme import update_readme_command
 43from .commands.version_matrix import version_matrix_command
 44
 45
 46def version_callback(value: bool) -> None:
 47    """Display the version and exit."""
 48    if value:
 49        typer.echo(f"rhiza-tools version {__version__}")
 50        raise typer.Exit()
 51
 52
 53app = typer.Typer(help="Rhiza Tools - Extra utilities for Rhiza.")
 54
 55# Shared option so --verbose / -v works both before and after the subcommand.
 56VERBOSE_OPTION = typer.Option(False, "--verbose", "-v", help="Show verbose debug output.")
 57
 58
 59def _apply_verbose(verbose: bool) -> None:
 60    """Enable verbose output if the flag was passed on the subcommand."""
 61    if verbose:
 62        configure_console(verbose=True)
 63
 64
 65@app.callback()
 66def main(
 67    version: bool = typer.Option(
 68        None,
 69        "--version",
 70        help="Show the version and exit.",
 71        callback=version_callback,
 72        is_eager=True,
 73    ),
 74    verbose: bool = VERBOSE_OPTION,
 75) -> None:
 76    """Rhiza Tools - Extra utilities for Rhiza."""
 77    configure_console(verbose=verbose)
 78
 79
 80@app.command()
 81def bump(
 82    version: str | None = typer.Argument(None, help="The version to bump to (e.g., 1.0.1, major, minor, patch, etc)"),
 83    language: str | None = typer.Option(
 84        None, "--language", "-l", help="Programming language (python or go). Auto-detected if not specified."
 85    ),
 86    dry_run: bool = typer.Option(False, "--dry-run", help="Print what would happen without doing it."),
 87    commit: bool = typer.Option(False, "--commit", help="Commit the changes to git."),
 88    push: bool = typer.Option(False, "--push", help="Push changes to remote after commit (implies --commit)."),
 89    branch: str | None = typer.Option(
 90        None, "--branch", help="Branch to perform the bump on (default: current branch)."
 91    ),
 92    allow_dirty: bool = typer.Option(
 93        False, "--allow-dirty", help="Allow bumping even if the working directory is dirty."
 94    ),
 95    verbose: bool = VERBOSE_OPTION,
 96) -> None:
 97    """Bump the version of the project.
 98
 99    This command updates the version for Python (pyproject.toml) or Go (VERSION file)
100    projects using semantic versioning. You can provide an explicit version number,
101    a bump type (patch, minor, major), or leave it blank for an interactive prompt.
102
103    Args:
104        version: The version to bump to. Can be an explicit version (e.g., "1.2.3"),
105            a bump type ("patch", "minor", "major"), a prerelease type
106            ("alpha", "beta", "rc", "dev"), or None for interactive selection.
107        language: Programming language (python or go). Auto-detected if not specified.
108        dry_run: If True, show what would change without actually changing anything.
109        commit: If True, automatically commit the version change to git.
110        push: If True, push changes to remote after commit (implies --commit).
111        branch: Branch to perform the bump on (default: current branch).
112        allow_dirty: If True, allow bumping even with uncommitted changes.
113        verbose: If True, enable verbose debug output.
114
115    Example:
116        Bump to a specific version::
117
118            $ rhiza-tools bump 2.0.0
119
120        Bump patch version (1.2.3 -> 1.2.4)::
121
122            $ rhiza-tools bump patch
123
124        Bump a Go project explicitly::
125
126            $ rhiza-tools bump minor --language go
127
128        Preview changes without applying them::
129
130            $ rhiza-tools bump minor --dry-run
131
132        Interactive version selection::
133
134            $ rhiza-tools bump
135
136        Bump and push to remote::
137
138            $ rhiza-tools bump minor --push
139    """
140    _apply_verbose(verbose)
141    from rhiza_tools.commands.bump import BumpOptions, Language
142
143    # Parse language if provided
144    lang_enum = None
145    if language:
146        try:
147            lang_enum = Language(language.lower())
148        except ValueError:
149            console.error(f"Invalid language: {language}")
150            console.error("Supported languages: python, go")
151            raise typer.Exit(code=1) from None
152
153    options = BumpOptions(
154        version=version,
155        dry_run=dry_run,
156        commit=commit,
157        push=push,
158        branch=branch,
159        allow_dirty=allow_dirty,
160        language=lang_enum,
161    )
162    bump_command(options)
163
164
165@app.command()
166def generate_coverage_badge(
167    coverage_json: Annotated[
168        Path,
169        typer.Option(
170            "--coverage-json",
171            help="Path to coverage.json file",
172        ),
173    ] = Path("_tests/coverage.json"),
174    output: Annotated[
175        Path,
176        typer.Option(
177            help="Path to output badge JSON",
178        ),
179    ] = Path("_book/tests/coverage-badge.json"),
180    verbose: bool = VERBOSE_OPTION,
181) -> None:
182    """Generate a coverage badge for the project.
183
184    Reads a coverage report JSON file and creates a shields.io endpoint JSON file
185    for displaying a coverage badge. The badge color automatically adjusts based
186    on the coverage percentage.
187
188    Args:
189        coverage_json: Path to the coverage.json file generated by pytest-cov.
190        output: Path where the badge JSON file should be written.
191        verbose: If True, enable verbose debug output.
192
193    Example:
194        Generate badge with default paths::
195
196            $ rhiza-tools generate-coverage-badge
197
198        Generate badge with custom paths::
199
200            $ rhiza-tools generate-coverage-badge \
201                --coverage-json tests/coverage.json \
202                --output assets/badge.json
203    """
204    _apply_verbose(verbose)
205    generate_coverage_badge_command(coverage_json_path=coverage_json, output_path=output)
206
207
208@app.command()
209def release(
210    bump: str | None = typer.Option(None, "--bump", help="Bump type (MAJOR, MINOR, PATCH) before release."),
211    with_bump: bool = typer.Option(
212        False,
213        "--with-bump",
214        help="Interactively select bump type before release (works with --dry-run).",
215    ),
216    push: bool = typer.Option(False, "--push", help="Push changes to remote (default: prompt in interactive mode)."),
217    dry_run: bool = typer.Option(False, "--dry-run", help="Print what would happen without doing it."),
218    non_interactive: bool = typer.Option(False, "--non-interactive", "-y", help="Skip all confirmation prompts."),
219    language: str | None = typer.Option(
220        None, "--language", "-l", help="Programming language (python or go). Auto-detected if not specified."
221    ),
222    verbose: bool = VERBOSE_OPTION,
223) -> None:
224    """Push a release tag to remote to trigger the release workflow.
225
226    This command validates the repository state and pushes the git tag for the
227    current version to the remote repository, which triggers the automated release
228    workflow. Supports Python projects (pyproject.toml) and Go projects
229    (go.mod + VERSION file). The project language is auto-detected when not
230    explicitly specified.
231
232    Args:
233        bump: Bump type (MAJOR, MINOR, PATCH) to apply before release.
234        with_bump: If True, interactively select bump type before release.
235        push: If True, push changes without prompting (implies non-interactive for push).
236        dry_run: If True, show what would happen without actually pushing the tag.
237        non_interactive: If True, skip all confirmation prompts (useful for CI/CD).
238        language: Programming language (python or go). Auto-detected if not specified.
239        verbose: If True, enable verbose debug output.
240
241    Example:
242        Push a release tag (with prompts)::
243
244            $ rhiza-tools release
245
246        Preview what would happen::
247
248            $ rhiza-tools release --dry-run
249
250        Non-interactive mode (for CI/CD)::
251
252            $ rhiza-tools release --non-interactive
253
254        Bump version and release::
255
256            $ rhiza-tools release --bump MINOR --push
257
258        Interactive bump with dry-run preview::
259
260            $ rhiza-tools release --with-bump --push --dry-run
261
262        Release a Go project::
263
264            $ rhiza-tools release --language go
265    """
266    _apply_verbose(verbose)
267    from rhiza_tools.commands.bump import Language
268
269    lang_enum = None
270    if language:
271        try:
272            lang_enum = Language(language.lower())
273        except ValueError:
274            console.error(f"Invalid language: {language}")
275            console.error("Supported languages: python, go")
276            raise typer.Exit(code=1) from None
277
278    release_command(bump, push, dry_run, non_interactive, with_bump, lang_enum)
279
280
281@app.command()
282def rollback(
283    tag: str | None = typer.Argument(None, help="Tag to rollback (e.g., v1.2.3). Interactive if omitted."),
284    revert_bump: bool = typer.Option(False, "--revert-bump", help="Also revert the version bump commit."),
285    dry_run: bool = typer.Option(False, "--dry-run", help="Print what would happen without doing it."),
286    non_interactive: bool = typer.Option(False, "--non-interactive", "-y", help="Skip all confirmation prompts."),
287    verbose: bool = VERBOSE_OPTION,
288) -> None:
289    """Rollback a release and/or version bump.
290
291    This command safely reverses release and bump operations by deleting
292    the release tag from local and remote repositories, and optionally
293    reverting the version bump commit.
294
295    It uses ``git revert`` rather than ``git reset``, making it safe
296    even when changes have already been pushed to remote.
297
298    Args:
299        tag: The tag to rollback (e.g., "v1.2.3"). If omitted, an interactive
300            menu shows recent tags to choose from.
301        revert_bump: If True, also revert the version bump commit associated
302            with the tag.
303        dry_run: If True, show what would happen without actually making changes.
304        non_interactive: If True, skip all confirmation prompts (useful for CI/CD).
305        verbose: If True, enable verbose debug output.
306
307    Example:
308        Rollback the most recent release interactively::
309
310            $ rhiza-tools rollback
311
312        Preview rollback of a specific tag::
313
314            $ rhiza-tools rollback v1.2.3 --dry-run
315
316        Fully rollback including the bump commit::
317
318            $ rhiza-tools rollback v1.2.3 --revert-bump
319
320        Non-interactive rollback (for CI/CD)::
321
322            $ rhiza-tools rollback v1.2.3 --revert-bump -y
323    """
324    _apply_verbose(verbose)
325    from rhiza_tools.commands.rollback import RollbackOptions
326
327    options = RollbackOptions(
328        tag=tag,
329        revert_bump=revert_bump,
330        dry_run=dry_run,
331        non_interactive=non_interactive,
332    )
333    rollback_command(options)
334
335
336@app.command(name="update-readme")
337def update_readme(
338    dry_run: bool = typer.Option(False, "--dry-run", help="Print what would happen without doing it."),
339    verbose: bool = VERBOSE_OPTION,
340) -> None:
341    """Update README.md with the current output from `make help`.
342
343    This command runs `make help` and updates the README.md file with the current
344    help output, keeping the documentation in sync with available Make targets.
345
346    Args:
347        dry_run: If True, show the help output that would be inserted without
348            actually modifying README.md.
349        verbose: If True, enable verbose debug output.
350
351    Example:
352        Update README with make help output::
353
354            $ rhiza-tools update-readme
355
356        Preview changes without modifying README::
357
358            $ rhiza-tools update-readme --dry-run
359    """
360    _apply_verbose(verbose)
361    update_readme_command(dry_run)
362
363
364@app.command(name="version-matrix")
365def version_matrix(
366    pyproject: Annotated[
367        Path,
368        typer.Option(
369            "--pyproject",
370            help="Path to pyproject.toml file",
371        ),
372    ] = Path("pyproject.toml"),
373    candidates: Annotated[
374        str | None,
375        typer.Option(
376            "--candidates",
377            help="Comma-separated list of candidate Python versions (e.g., '3.11,3.12,3.13')",
378        ),
379    ] = None,
380    verbose: bool = VERBOSE_OPTION,
381) -> None:
382    """Emit supported Python versions from pyproject.toml as JSON.
383
384    This command reads the requires-python field from pyproject.toml and outputs
385    a JSON array of Python versions that satisfy the constraint. This is primarily
386    used in GitHub Actions to compute the test matrix.
387
388    Args:
389        pyproject: Path to the pyproject.toml file.
390        candidates: Comma-separated list of candidate Python versions to evaluate.
391            Defaults to "3.11,3.12,3.13,3.14".
392        verbose: If True, enable verbose debug output.
393
394    Example:
395        Get supported versions with defaults::
396
397            $ rhiza-tools version-matrix
398            ["3.11", "3.12"]
399
400        Use custom pyproject.toml path::
401
402            $ rhiza-tools version-matrix --pyproject /path/to/pyproject.toml
403
404        Use custom candidates::
405
406            $ rhiza-tools version-matrix --candidates "3.10,3.11,3.12"
407    """
408    _apply_verbose(verbose)
409    candidates_list = None
410    if candidates:
411        candidates_list = [v.strip() for v in candidates.split(",")]
412
413    version_matrix_command(pyproject_path=pyproject, candidates=candidates_list)
414
415
416@app.command(name="analyze-benchmarks")
417def analyze_benchmarks(
418    benchmarks_json: Annotated[
419        Path,
420        typer.Option(
421            "--benchmarks-json",
422            help="Path to benchmarks.json file",
423        ),
424    ] = Path("_benchmarks/benchmarks.json"),
425    output_html: Annotated[
426        Path,
427        typer.Option(
428            "--output-html",
429            help="Path to save HTML visualization",
430        ),
431    ] = Path("_benchmarks/benchmarks.html"),
432    verbose: bool = VERBOSE_OPTION,
433) -> None:
434    """Analyze pytest-benchmark results and visualize them.
435
436    This command reads a benchmarks.json file produced by pytest-benchmark,
437    prints a table with benchmark name, mean milliseconds, and operations per
438    second, and generates an interactive Plotly bar chart of mean runtimes.
439
440    Note: This command requires pandas and plotly. Install with:
441    uv pip install -e '.[dev]' or pip install 'rhiza-tools[dev]'
442
443    Args:
444        benchmarks_json: Path to the benchmarks.json file.
445        output_html: Path where the HTML visualization should be saved.
446        verbose: If True, enable verbose debug output.
447
448    Example:
449        Analyze benchmarks with default paths::
450
451            $ rhiza-tools analyze-benchmarks
452
453        Use custom paths::
454
455            $ rhiza-tools analyze-benchmarks \
456                --benchmarks-json tests/benchmarks.json \
457                --output-html reports/benchmarks.html
458    """
459    _apply_verbose(verbose)
460    analyze_benchmarks_command(benchmarks_json=benchmarks_json, output_html=output_html)
def version_callback(value: bool) -> None:
47def version_callback(value: bool) -> None:
48    """Display the version and exit."""
49    if value:
50        typer.echo(f"rhiza-tools version {__version__}")
51        raise typer.Exit()

Display the version and exit.

app = <typer.main.Typer object>
VERBOSE_OPTION = <typer.models.OptionInfo object>
@app.callback()
def main( version: bool = <typer.models.OptionInfo object>, verbose: bool = <typer.models.OptionInfo object>) -> None:
66@app.callback()
67def main(
68    version: bool = typer.Option(
69        None,
70        "--version",
71        help="Show the version and exit.",
72        callback=version_callback,
73        is_eager=True,
74    ),
75    verbose: bool = VERBOSE_OPTION,
76) -> None:
77    """Rhiza Tools - Extra utilities for Rhiza."""
78    configure_console(verbose=verbose)

Rhiza Tools - Extra utilities for Rhiza.

@app.command()
def bump( version: str | None = <typer.models.ArgumentInfo object>, language: str | None = <typer.models.OptionInfo object>, dry_run: bool = <typer.models.OptionInfo object>, commit: bool = <typer.models.OptionInfo object>, push: bool = <typer.models.OptionInfo object>, branch: str | None = <typer.models.OptionInfo object>, allow_dirty: bool = <typer.models.OptionInfo object>, verbose: bool = <typer.models.OptionInfo object>) -> None:
 81@app.command()
 82def bump(
 83    version: str | None = typer.Argument(None, help="The version to bump to (e.g., 1.0.1, major, minor, patch, etc)"),
 84    language: str | None = typer.Option(
 85        None, "--language", "-l", help="Programming language (python or go). Auto-detected if not specified."
 86    ),
 87    dry_run: bool = typer.Option(False, "--dry-run", help="Print what would happen without doing it."),
 88    commit: bool = typer.Option(False, "--commit", help="Commit the changes to git."),
 89    push: bool = typer.Option(False, "--push", help="Push changes to remote after commit (implies --commit)."),
 90    branch: str | None = typer.Option(
 91        None, "--branch", help="Branch to perform the bump on (default: current branch)."
 92    ),
 93    allow_dirty: bool = typer.Option(
 94        False, "--allow-dirty", help="Allow bumping even if the working directory is dirty."
 95    ),
 96    verbose: bool = VERBOSE_OPTION,
 97) -> None:
 98    """Bump the version of the project.
 99
100    This command updates the version for Python (pyproject.toml) or Go (VERSION file)
101    projects using semantic versioning. You can provide an explicit version number,
102    a bump type (patch, minor, major), or leave it blank for an interactive prompt.
103
104    Args:
105        version: The version to bump to. Can be an explicit version (e.g., "1.2.3"),
106            a bump type ("patch", "minor", "major"), a prerelease type
107            ("alpha", "beta", "rc", "dev"), or None for interactive selection.
108        language: Programming language (python or go). Auto-detected if not specified.
109        dry_run: If True, show what would change without actually changing anything.
110        commit: If True, automatically commit the version change to git.
111        push: If True, push changes to remote after commit (implies --commit).
112        branch: Branch to perform the bump on (default: current branch).
113        allow_dirty: If True, allow bumping even with uncommitted changes.
114        verbose: If True, enable verbose debug output.
115
116    Example:
117        Bump to a specific version::
118
119            $ rhiza-tools bump 2.0.0
120
121        Bump patch version (1.2.3 -> 1.2.4)::
122
123            $ rhiza-tools bump patch
124
125        Bump a Go project explicitly::
126
127            $ rhiza-tools bump minor --language go
128
129        Preview changes without applying them::
130
131            $ rhiza-tools bump minor --dry-run
132
133        Interactive version selection::
134
135            $ rhiza-tools bump
136
137        Bump and push to remote::
138
139            $ rhiza-tools bump minor --push
140    """
141    _apply_verbose(verbose)
142    from rhiza_tools.commands.bump import BumpOptions, Language
143
144    # Parse language if provided
145    lang_enum = None
146    if language:
147        try:
148            lang_enum = Language(language.lower())
149        except ValueError:
150            console.error(f"Invalid language: {language}")
151            console.error("Supported languages: python, go")
152            raise typer.Exit(code=1) from None
153
154    options = BumpOptions(
155        version=version,
156        dry_run=dry_run,
157        commit=commit,
158        push=push,
159        branch=branch,
160        allow_dirty=allow_dirty,
161        language=lang_enum,
162    )
163    bump_command(options)

Bump the version of the project.

This command updates the version for Python (pyproject.toml) or Go (VERSION file) projects using semantic versioning. You can provide an explicit version number, a bump type (patch, minor, major), or leave it blank for an interactive prompt.

Arguments:
  • version: The version to bump to. Can be an explicit version (e.g., "1.2.3"), a bump type ("patch", "minor", "major"), a prerelease type ("alpha", "beta", "rc", "dev"), or None for interactive selection.
  • language: Programming language (python or go). Auto-detected if not specified.
  • dry_run: If True, show what would change without actually changing anything.
  • commit: If True, automatically commit the version change to git.
  • push: If True, push changes to remote after commit (implies --commit).
  • branch: Branch to perform the bump on (default: current branch).
  • allow_dirty: If True, allow bumping even with uncommitted changes.
  • verbose: If True, enable verbose debug output.
Example:

Bump to a specific version::

$ rhiza-tools bump 2.0.0

Bump patch version (1.2.3 -> 1.2.4)::

$ rhiza-tools bump patch

Bump a Go project explicitly::

$ rhiza-tools bump minor --language go

Preview changes without applying them::

$ rhiza-tools bump minor --dry-run

Interactive version selection::

$ rhiza-tools bump

Bump and push to remote::

$ rhiza-tools bump minor --push
@app.command()
def generate_coverage_badge( coverage_json: Annotated[pathlib.Path, <typer.models.OptionInfo object>] = PosixPath('_tests/coverage.json'), output: Annotated[pathlib.Path, <typer.models.OptionInfo object>] = PosixPath('_book/tests/coverage-badge.json'), verbose: bool = <typer.models.OptionInfo object>) -> None:
166@app.command()
167def generate_coverage_badge(
168    coverage_json: Annotated[
169        Path,
170        typer.Option(
171            "--coverage-json",
172            help="Path to coverage.json file",
173        ),
174    ] = Path("_tests/coverage.json"),
175    output: Annotated[
176        Path,
177        typer.Option(
178            help="Path to output badge JSON",
179        ),
180    ] = Path("_book/tests/coverage-badge.json"),
181    verbose: bool = VERBOSE_OPTION,
182) -> None:
183    """Generate a coverage badge for the project.
184
185    Reads a coverage report JSON file and creates a shields.io endpoint JSON file
186    for displaying a coverage badge. The badge color automatically adjusts based
187    on the coverage percentage.
188
189    Args:
190        coverage_json: Path to the coverage.json file generated by pytest-cov.
191        output: Path where the badge JSON file should be written.
192        verbose: If True, enable verbose debug output.
193
194    Example:
195        Generate badge with default paths::
196
197            $ rhiza-tools generate-coverage-badge
198
199        Generate badge with custom paths::
200
201            $ rhiza-tools generate-coverage-badge \
202                --coverage-json tests/coverage.json \
203                --output assets/badge.json
204    """
205    _apply_verbose(verbose)
206    generate_coverage_badge_command(coverage_json_path=coverage_json, output_path=output)

Generate a coverage badge for the project.

Reads a coverage report JSON file and creates a shields.io endpoint JSON file for displaying a coverage badge. The badge color automatically adjusts based on the coverage percentage.

Arguments:
  • coverage_json: Path to the coverage.json file generated by pytest-cov.
  • output: Path where the badge JSON file should be written.
  • verbose: If True, enable verbose debug output.
Example:

Generate badge with default paths::

$ rhiza-tools generate-coverage-badge

Generate badge with custom paths::

$ rhiza-tools generate-coverage-badge                 --coverage-json tests/coverage.json                 --output assets/badge.json
@app.command()
def release( bump: str | None = <typer.models.OptionInfo object>, with_bump: bool = <typer.models.OptionInfo object>, push: bool = <typer.models.OptionInfo object>, dry_run: bool = <typer.models.OptionInfo object>, non_interactive: bool = <typer.models.OptionInfo object>, language: str | None = <typer.models.OptionInfo object>, verbose: bool = <typer.models.OptionInfo object>) -> None:
209@app.command()
210def release(
211    bump: str | None = typer.Option(None, "--bump", help="Bump type (MAJOR, MINOR, PATCH) before release."),
212    with_bump: bool = typer.Option(
213        False,
214        "--with-bump",
215        help="Interactively select bump type before release (works with --dry-run).",
216    ),
217    push: bool = typer.Option(False, "--push", help="Push changes to remote (default: prompt in interactive mode)."),
218    dry_run: bool = typer.Option(False, "--dry-run", help="Print what would happen without doing it."),
219    non_interactive: bool = typer.Option(False, "--non-interactive", "-y", help="Skip all confirmation prompts."),
220    language: str | None = typer.Option(
221        None, "--language", "-l", help="Programming language (python or go). Auto-detected if not specified."
222    ),
223    verbose: bool = VERBOSE_OPTION,
224) -> None:
225    """Push a release tag to remote to trigger the release workflow.
226
227    This command validates the repository state and pushes the git tag for the
228    current version to the remote repository, which triggers the automated release
229    workflow. Supports Python projects (pyproject.toml) and Go projects
230    (go.mod + VERSION file). The project language is auto-detected when not
231    explicitly specified.
232
233    Args:
234        bump: Bump type (MAJOR, MINOR, PATCH) to apply before release.
235        with_bump: If True, interactively select bump type before release.
236        push: If True, push changes without prompting (implies non-interactive for push).
237        dry_run: If True, show what would happen without actually pushing the tag.
238        non_interactive: If True, skip all confirmation prompts (useful for CI/CD).
239        language: Programming language (python or go). Auto-detected if not specified.
240        verbose: If True, enable verbose debug output.
241
242    Example:
243        Push a release tag (with prompts)::
244
245            $ rhiza-tools release
246
247        Preview what would happen::
248
249            $ rhiza-tools release --dry-run
250
251        Non-interactive mode (for CI/CD)::
252
253            $ rhiza-tools release --non-interactive
254
255        Bump version and release::
256
257            $ rhiza-tools release --bump MINOR --push
258
259        Interactive bump with dry-run preview::
260
261            $ rhiza-tools release --with-bump --push --dry-run
262
263        Release a Go project::
264
265            $ rhiza-tools release --language go
266    """
267    _apply_verbose(verbose)
268    from rhiza_tools.commands.bump import Language
269
270    lang_enum = None
271    if language:
272        try:
273            lang_enum = Language(language.lower())
274        except ValueError:
275            console.error(f"Invalid language: {language}")
276            console.error("Supported languages: python, go")
277            raise typer.Exit(code=1) from None
278
279    release_command(bump, push, dry_run, non_interactive, with_bump, lang_enum)

Push a release tag to remote to trigger the release workflow.

This command validates the repository state and pushes the git tag for the current version to the remote repository, which triggers the automated release workflow. Supports Python projects (pyproject.toml) and Go projects (go.mod + VERSION file). The project language is auto-detected when not explicitly specified.

Arguments:
  • bump: Bump type (MAJOR, MINOR, PATCH) to apply before release.
  • with_bump: If True, interactively select bump type before release.
  • push: If True, push changes without prompting (implies non-interactive for push).
  • dry_run: If True, show what would happen without actually pushing the tag.
  • non_interactive: If True, skip all confirmation prompts (useful for CI/CD).
  • language: Programming language (python or go). Auto-detected if not specified.
  • verbose: If True, enable verbose debug output.
Example:

Push a release tag (with prompts)::

$ rhiza-tools release

Preview what would happen::

$ rhiza-tools release --dry-run

Non-interactive mode (for CI/CD)::

$ rhiza-tools release --non-interactive

Bump version and release::

$ rhiza-tools release --bump MINOR --push

Interactive bump with dry-run preview::

$ rhiza-tools release --with-bump --push --dry-run

Release a Go project::

$ rhiza-tools release --language go
@app.command()
def rollback( tag: str | None = <typer.models.ArgumentInfo object>, revert_bump: bool = <typer.models.OptionInfo object>, dry_run: bool = <typer.models.OptionInfo object>, non_interactive: bool = <typer.models.OptionInfo object>, verbose: bool = <typer.models.OptionInfo object>) -> None:
282@app.command()
283def rollback(
284    tag: str | None = typer.Argument(None, help="Tag to rollback (e.g., v1.2.3). Interactive if omitted."),
285    revert_bump: bool = typer.Option(False, "--revert-bump", help="Also revert the version bump commit."),
286    dry_run: bool = typer.Option(False, "--dry-run", help="Print what would happen without doing it."),
287    non_interactive: bool = typer.Option(False, "--non-interactive", "-y", help="Skip all confirmation prompts."),
288    verbose: bool = VERBOSE_OPTION,
289) -> None:
290    """Rollback a release and/or version bump.
291
292    This command safely reverses release and bump operations by deleting
293    the release tag from local and remote repositories, and optionally
294    reverting the version bump commit.
295
296    It uses ``git revert`` rather than ``git reset``, making it safe
297    even when changes have already been pushed to remote.
298
299    Args:
300        tag: The tag to rollback (e.g., "v1.2.3"). If omitted, an interactive
301            menu shows recent tags to choose from.
302        revert_bump: If True, also revert the version bump commit associated
303            with the tag.
304        dry_run: If True, show what would happen without actually making changes.
305        non_interactive: If True, skip all confirmation prompts (useful for CI/CD).
306        verbose: If True, enable verbose debug output.
307
308    Example:
309        Rollback the most recent release interactively::
310
311            $ rhiza-tools rollback
312
313        Preview rollback of a specific tag::
314
315            $ rhiza-tools rollback v1.2.3 --dry-run
316
317        Fully rollback including the bump commit::
318
319            $ rhiza-tools rollback v1.2.3 --revert-bump
320
321        Non-interactive rollback (for CI/CD)::
322
323            $ rhiza-tools rollback v1.2.3 --revert-bump -y
324    """
325    _apply_verbose(verbose)
326    from rhiza_tools.commands.rollback import RollbackOptions
327
328    options = RollbackOptions(
329        tag=tag,
330        revert_bump=revert_bump,
331        dry_run=dry_run,
332        non_interactive=non_interactive,
333    )
334    rollback_command(options)

Rollback a release and/or version bump.

This command safely reverses release and bump operations by deleting the release tag from local and remote repositories, and optionally reverting the version bump commit.

It uses git revert rather than git reset, making it safe even when changes have already been pushed to remote.

Arguments:
  • tag: The tag to rollback (e.g., "v1.2.3"). If omitted, an interactive menu shows recent tags to choose from.
  • revert_bump: If True, also revert the version bump commit associated with the tag.
  • dry_run: If True, show what would happen without actually making changes.
  • non_interactive: If True, skip all confirmation prompts (useful for CI/CD).
  • verbose: If True, enable verbose debug output.
Example:

Rollback the most recent release interactively::

$ rhiza-tools rollback

Preview rollback of a specific tag::

$ rhiza-tools rollback v1.2.3 --dry-run

Fully rollback including the bump commit::

$ rhiza-tools rollback v1.2.3 --revert-bump

Non-interactive rollback (for CI/CD)::

$ rhiza-tools rollback v1.2.3 --revert-bump -y
@app.command(name='update-readme')
def update_readme( dry_run: bool = <typer.models.OptionInfo object>, verbose: bool = <typer.models.OptionInfo object>) -> None:
337@app.command(name="update-readme")
338def update_readme(
339    dry_run: bool = typer.Option(False, "--dry-run", help="Print what would happen without doing it."),
340    verbose: bool = VERBOSE_OPTION,
341) -> None:
342    """Update README.md with the current output from `make help`.
343
344    This command runs `make help` and updates the README.md file with the current
345    help output, keeping the documentation in sync with available Make targets.
346
347    Args:
348        dry_run: If True, show the help output that would be inserted without
349            actually modifying README.md.
350        verbose: If True, enable verbose debug output.
351
352    Example:
353        Update README with make help output::
354
355            $ rhiza-tools update-readme
356
357        Preview changes without modifying README::
358
359            $ rhiza-tools update-readme --dry-run
360    """
361    _apply_verbose(verbose)
362    update_readme_command(dry_run)

Update README.md with the current output from make help.

This command runs make help and updates the README.md file with the current help output, keeping the documentation in sync with available Make targets.

Arguments:
  • dry_run: If True, show the help output that would be inserted without actually modifying README.md.
  • verbose: If True, enable verbose debug output.
Example:

Update README with make help output::

$ rhiza-tools update-readme

Preview changes without modifying README::

$ rhiza-tools update-readme --dry-run
@app.command(name='version-matrix')
def version_matrix( pyproject: Annotated[pathlib.Path, <typer.models.OptionInfo object>] = PosixPath('pyproject.toml'), candidates: Annotated[str | None, <typer.models.OptionInfo object>] = None, verbose: bool = <typer.models.OptionInfo object>) -> None:
365@app.command(name="version-matrix")
366def version_matrix(
367    pyproject: Annotated[
368        Path,
369        typer.Option(
370            "--pyproject",
371            help="Path to pyproject.toml file",
372        ),
373    ] = Path("pyproject.toml"),
374    candidates: Annotated[
375        str | None,
376        typer.Option(
377            "--candidates",
378            help="Comma-separated list of candidate Python versions (e.g., '3.11,3.12,3.13')",
379        ),
380    ] = None,
381    verbose: bool = VERBOSE_OPTION,
382) -> None:
383    """Emit supported Python versions from pyproject.toml as JSON.
384
385    This command reads the requires-python field from pyproject.toml and outputs
386    a JSON array of Python versions that satisfy the constraint. This is primarily
387    used in GitHub Actions to compute the test matrix.
388
389    Args:
390        pyproject: Path to the pyproject.toml file.
391        candidates: Comma-separated list of candidate Python versions to evaluate.
392            Defaults to "3.11,3.12,3.13,3.14".
393        verbose: If True, enable verbose debug output.
394
395    Example:
396        Get supported versions with defaults::
397
398            $ rhiza-tools version-matrix
399            ["3.11", "3.12"]
400
401        Use custom pyproject.toml path::
402
403            $ rhiza-tools version-matrix --pyproject /path/to/pyproject.toml
404
405        Use custom candidates::
406
407            $ rhiza-tools version-matrix --candidates "3.10,3.11,3.12"
408    """
409    _apply_verbose(verbose)
410    candidates_list = None
411    if candidates:
412        candidates_list = [v.strip() for v in candidates.split(",")]
413
414    version_matrix_command(pyproject_path=pyproject, candidates=candidates_list)

Emit supported Python versions from pyproject.toml as JSON.

This command reads the requires-python field from pyproject.toml and outputs a JSON array of Python versions that satisfy the constraint. This is primarily used in GitHub Actions to compute the test matrix.

Arguments:
  • pyproject: Path to the pyproject.toml file.
  • candidates: Comma-separated list of candidate Python versions to evaluate. Defaults to "3.11,3.12,3.13,3.14".
  • verbose: If True, enable verbose debug output.
Example:

Get supported versions with defaults::

$ rhiza-tools version-matrix
["3.11", "3.12"]

Use custom pyproject.toml path::

$ rhiza-tools version-matrix --pyproject /path/to/pyproject.toml

Use custom candidates::

$ rhiza-tools version-matrix --candidates "3.10,3.11,3.12"
@app.command(name='analyze-benchmarks')
def analyze_benchmarks( benchmarks_json: Annotated[pathlib.Path, <typer.models.OptionInfo object>] = PosixPath('_benchmarks/benchmarks.json'), output_html: Annotated[pathlib.Path, <typer.models.OptionInfo object>] = PosixPath('_benchmarks/benchmarks.html'), verbose: bool = <typer.models.OptionInfo object>) -> None:
417@app.command(name="analyze-benchmarks")
418def analyze_benchmarks(
419    benchmarks_json: Annotated[
420        Path,
421        typer.Option(
422            "--benchmarks-json",
423            help="Path to benchmarks.json file",
424        ),
425    ] = Path("_benchmarks/benchmarks.json"),
426    output_html: Annotated[
427        Path,
428        typer.Option(
429            "--output-html",
430            help="Path to save HTML visualization",
431        ),
432    ] = Path("_benchmarks/benchmarks.html"),
433    verbose: bool = VERBOSE_OPTION,
434) -> None:
435    """Analyze pytest-benchmark results and visualize them.
436
437    This command reads a benchmarks.json file produced by pytest-benchmark,
438    prints a table with benchmark name, mean milliseconds, and operations per
439    second, and generates an interactive Plotly bar chart of mean runtimes.
440
441    Note: This command requires pandas and plotly. Install with:
442    uv pip install -e '.[dev]' or pip install 'rhiza-tools[dev]'
443
444    Args:
445        benchmarks_json: Path to the benchmarks.json file.
446        output_html: Path where the HTML visualization should be saved.
447        verbose: If True, enable verbose debug output.
448
449    Example:
450        Analyze benchmarks with default paths::
451
452            $ rhiza-tools analyze-benchmarks
453
454        Use custom paths::
455
456            $ rhiza-tools analyze-benchmarks \
457                --benchmarks-json tests/benchmarks.json \
458                --output-html reports/benchmarks.html
459    """
460    _apply_verbose(verbose)
461    analyze_benchmarks_command(benchmarks_json=benchmarks_json, output_html=output_html)

Analyze pytest-benchmark results and visualize them.

This command reads a benchmarks.json file produced by pytest-benchmark, prints a table with benchmark name, mean milliseconds, and operations per second, and generates an interactive Plotly bar chart of mean runtimes.

Note: This command requires pandas and plotly. Install with: uv pip install -e '.[dev]' or pip install 'rhiza-tools[dev]'

Arguments:
  • benchmarks_json: Path to the benchmarks.json file.
  • output_html: Path where the HTML visualization should be saved.
  • verbose: If True, enable verbose debug output.
Example:

Analyze benchmarks with default paths::

$ rhiza-tools analyze-benchmarks

Use custom paths::

$ rhiza-tools analyze-benchmarks                 --benchmarks-json tests/benchmarks.json                 --output-html reports/benchmarks.html