Coverage for src / rhiza / commands / summarise / __init__.py: 100%
37 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-07-16 12:51 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-07-16 12:51 +0000
1"""Command for generating PR descriptions from staged changes.
3This package analyses staged git changes and generates structured PR
4descriptions for rhiza sync operations. Responsibilities are split across two
5private modules:
7* :mod:`._gather` — collects staged changes and reads template metadata.
8* :mod:`._render` — formats the gathered data into markdown, plain text, JSON,
9 or a custom Jinja2 template.
11The orchestration (:func:`generate_pr_description` and :func:`summarise`) lives
12here and wires the two together.
13"""
15from datetime import datetime
16from pathlib import Path
18from loguru import logger
20from ._gather import (
21 _categorize_single_file,
22 _TemplateInfo,
23 categorize_files,
24 get_last_sync_date,
25 get_staged_changes,
26 get_template_info,
27 run_git_command,
28)
29from ._render import (
30 SummariseOptions,
31 _generate_jinja2_output,
32 _generate_json_output,
33 _generate_plain_output,
34 _markdown_body,
35)
37__all__ = [
38 "SummariseOptions",
39 "_TemplateInfo",
40 "_categorize_single_file",
41 "categorize_files",
42 "generate_pr_description",
43 "get_last_sync_date",
44 "get_staged_changes",
45 "get_template_info",
46 "run_git_command",
47 "summarise",
48]
51def generate_pr_description(repo_path: Path, options: SummariseOptions | None = None) -> str:
52 """Generate PR description based on staged changes.
54 Args:
55 repo_path: Path to the repository
56 options: Output customisation options. Defaults to :class:`SummariseOptions`
57 with all fields at their defaults (markdown format, with header / footer /
58 categories, no custom title, staged-index diff).
60 Returns:
61 Formatted PR description
62 """
63 opts = options or SummariseOptions()
65 changes = get_staged_changes(repo_path, compare_ref=opts.compare_ref)
66 template_repo, template_branch = get_template_info(repo_path)
67 last_sync = get_last_sync_date(repo_path, template_repo=template_repo)
69 all_changed_files = changes["added"] + changes["modified"] + changes["deleted"]
70 categories = categorize_files(all_changed_files) if all_changed_files else {}
72 tmpl = _TemplateInfo(repo=template_repo, branch=template_branch, last_sync=last_sync)
74 # Custom Jinja2 template takes full precedence over all other options
75 if opts.jinja2_template:
76 context = {
77 "template_repo": tmpl.repo,
78 "template_branch": tmpl.branch,
79 "last_sync": tmpl.last_sync,
80 "sync_date": datetime.now().astimezone().isoformat(),
81 "changes": changes,
82 "categories": categories,
83 "title": opts.title,
84 }
85 return _generate_jinja2_output(opts.jinja2_template, context)
87 if opts.output_format == "json":
88 return _generate_json_output(changes, categories, tmpl)
90 if opts.output_format == "plain":
91 return _generate_plain_output(changes, categories, tmpl, opts)
93 return _markdown_body(changes, categories, tmpl, opts)
96def summarise(
97 target: Path,
98 output: Path | None = None,
99 *,
100 options: SummariseOptions | None = None,
101) -> None:
102 """Generate a summary of staged changes for rhiza sync operations.
104 This command analyzes staged git changes and generates a structured
105 PR description with:
106 - Summary statistics (files added/modified/deleted)
107 - Changes categorized by type (workflows, configs, docs, tests, etc.)
108 - Template repository information
109 - Last sync date
111 Args:
112 target: Path to the target repository.
113 output: Optional output file path. If not provided, prints to stdout.
114 options: Output customisation options. Defaults to :class:`SummariseOptions`
115 with all fields at their defaults.
116 """
117 target = target.resolve()
118 logger.info(f"Target repository: {target}")
120 # Check if target is a git repository
121 if not (target / ".git").is_dir():
122 err_msg = f"Target directory is not a git repository: {target}"
123 logger.error(err_msg)
124 logger.error("Initialize a git repository with 'git init' first")
125 raise RuntimeError(err_msg)
127 description = generate_pr_description(target, options)
129 if output:
130 output_path = output.resolve()
131 output_path.write_text(description, encoding="utf-8")
132 logger.success(f"PR description written to {output_path}")
133 else:
134 print(description)
136 logger.success("Summary generated successfully")