rhiza 0.8.8__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rhiza/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Rhiza — Manage reusable configuration templates for Python projects.
2
2
 
3
- Rhiza is a commandline interface (CLI) that helps you maintain consistent
3
+ Rhiza is a command-line interface (CLI) that helps you maintain consistent
4
4
  configuration across multiple Python projects using templates stored in a
5
5
  central repository. It can initialize projects with standard configuration,
6
6
  materialize (inject) template files into a target repository, and validate the
@@ -11,8 +11,8 @@ template configuration.
11
11
  - Template initialization for new or existing projects.
12
12
  - Template materialization with selective include/exclude support.
13
13
  - Configuration validation (syntax and basic semantics).
14
- - Multihost support (GitHub and GitLab).
15
- - Nondestructive updates by default, with an explicit `--force` flag.
14
+ - Multi-host support (GitHub and GitLab).
15
+ - Non-destructive updates by default, with an explicit `--force` flag.
16
16
 
17
17
  ## Quick start
18
18
 
@@ -29,7 +29,7 @@ Validate your configuration:
29
29
  rhiza validate
30
30
  ```
31
31
 
32
- Customize `.github/rhiza/template.yml`, then materialize templates into your project:
32
+ Customize `.rhiza/template.yml`, then materialize templates into your project:
33
33
 
34
34
  ```bash
35
35
  rhiza materialize
rhiza/cli.py CHANGED
@@ -5,6 +5,7 @@ Commands are thin wrappers around implementations in `rhiza.commands.*`.
5
5
  """
6
6
 
7
7
  from pathlib import Path
8
+ from typing import Annotated
8
9
 
9
10
  import typer
10
11
 
@@ -13,6 +14,7 @@ from rhiza.commands import init as init_cmd
13
14
  from rhiza.commands import materialize as materialize_cmd
14
15
  from rhiza.commands import validate as validate_cmd
15
16
  from rhiza.commands.migrate import migrate as migrate_cmd
17
+ from rhiza.commands.summarise import summarise as summarise_cmd
16
18
  from rhiza.commands.uninstall import uninstall as uninstall_cmd
17
19
  from rhiza.commands.welcome import welcome as welcome_cmd
18
20
 
@@ -65,13 +67,15 @@ def main(
65
67
 
66
68
  @app.command()
67
69
  def 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
- ),
70
+ target: Annotated[
71
+ Path,
72
+ typer.Argument(
73
+ exists=True,
74
+ file_okay=False,
75
+ dir_okay=True,
76
+ help="Target directory (defaults to current directory)",
77
+ ),
78
+ ] = Path("."),
75
79
  project_name: str = typer.Option(
76
80
  None,
77
81
  "--project-name",
@@ -93,10 +97,20 @@ def init(
93
97
  help="Target Git hosting platform (github or gitlab). Determines which CI/CD files to include. "
94
98
  "If not provided, will prompt interactively.",
95
99
  ),
100
+ template_repository: str = typer.Option(
101
+ None,
102
+ "--template-repository",
103
+ help="Custom template repository (format: owner/repo). Defaults to 'jebel-quant/rhiza'.",
104
+ ),
105
+ template_branch: str = typer.Option(
106
+ None,
107
+ "--template-branch",
108
+ help="Custom template branch. Defaults to 'main'.",
109
+ ),
96
110
  ):
97
- r"""Initialize or validate .github/rhiza/template.yml.
111
+ r"""Initialize or validate .rhiza/template.yml.
98
112
 
99
- Creates a default `.github/rhiza/template.yml` configuration file if one
113
+ Creates a default `.rhiza/template.yml` configuration file if one
100
114
  doesn't exist, or validates the existing configuration.
101
115
 
102
116
  The default template includes common Python project files.
@@ -108,6 +122,8 @@ def init(
108
122
  rhiza init
109
123
  rhiza init --git-host github
110
124
  rhiza init --git-host gitlab
125
+ rhiza init --template-repository myorg/my-templates
126
+ rhiza init --template-repository myorg/my-templates --template-branch develop
111
127
  rhiza init /path/to/project
112
128
  rhiza init ..
113
129
  """
@@ -117,18 +133,22 @@ def init(
117
133
  package_name=package_name,
118
134
  with_dev_dependencies=with_dev_dependencies,
119
135
  git_host=git_host,
136
+ template_repository=template_repository,
137
+ template_branch=template_branch,
120
138
  )
121
139
 
122
140
 
123
141
  @app.command()
124
142
  def 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
- ),
143
+ target: Annotated[
144
+ Path,
145
+ typer.Argument(
146
+ exists=True,
147
+ file_okay=False,
148
+ dir_okay=True,
149
+ help="Target git repository (defaults to current directory)",
150
+ ),
151
+ ] = Path("."),
132
152
  branch: str = typer.Option("main", "--branch", "-b", help="Rhiza branch to use"),
133
153
  target_branch: str = typer.Option(
134
154
  None,
@@ -141,9 +161,9 @@ def materialize(
141
161
  r"""Inject Rhiza configuration templates into a target repository.
142
162
 
143
163
  Materializes configuration files from the template repository specified
144
- in .github/rhiza/template.yml into your project. This command:
164
+ in .rhiza/template.yml into your project. This command:
145
165
 
146
- - Reads .github/rhiza/template.yml configuration
166
+ - Reads .rhiza/template.yml configuration
147
167
  - Performs a sparse clone of the template repository
148
168
  - Copies specified files/directories to your project
149
169
  - Respects exclusion patterns defined in the configuration
@@ -161,17 +181,19 @@ def materialize(
161
181
 
162
182
  @app.command()
163
183
  def 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
- ),
184
+ target: Annotated[
185
+ Path,
186
+ typer.Argument(
187
+ exists=True,
188
+ file_okay=False,
189
+ dir_okay=True,
190
+ help="Target git repository (defaults to current directory)",
191
+ ),
192
+ ] = Path("."),
171
193
  ):
172
194
  r"""Validate Rhiza template configuration.
173
195
 
174
- Validates the .github/rhiza/template.yml file to ensure it is syntactically
196
+ Validates the .rhiza/template.yml file to ensure it is syntactically
175
197
  correct and semantically valid.
176
198
 
177
199
  Performs comprehensive validation:
@@ -196,13 +218,15 @@ def validate(
196
218
 
197
219
  @app.command()
198
220
  def 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
- ),
221
+ target: Annotated[
222
+ Path,
223
+ typer.Argument(
224
+ exists=True,
225
+ file_okay=False,
226
+ dir_okay=True,
227
+ help="Target git repository (defaults to current directory)",
228
+ ),
229
+ ] = Path("."),
206
230
  ):
207
231
  r"""Migrate project to the new .rhiza folder structure.
208
232
 
@@ -243,13 +267,15 @@ def welcome():
243
267
 
244
268
  @app.command()
245
269
  def 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
- ),
270
+ target: Annotated[
271
+ Path,
272
+ typer.Argument(
273
+ exists=True,
274
+ file_okay=False,
275
+ dir_okay=True,
276
+ help="Target git repository (defaults to current directory)",
277
+ ),
278
+ ] = Path("."),
253
279
  force: bool = typer.Option(
254
280
  False,
255
281
  "--force",
@@ -280,3 +306,50 @@ def uninstall(
280
306
  rhiza uninstall /path/to/project -y
281
307
  """
282
308
  uninstall_cmd(target, force)
309
+
310
+
311
+ @app.command()
312
+ def summarise(
313
+ target: Annotated[
314
+ Path,
315
+ typer.Argument(
316
+ exists=True,
317
+ file_okay=False,
318
+ dir_okay=True,
319
+ help="Target git repository (defaults to current directory)",
320
+ ),
321
+ ] = Path("."),
322
+ output: Annotated[
323
+ Path | None,
324
+ typer.Option(
325
+ "--output",
326
+ "-o",
327
+ help="Output file path (defaults to stdout)",
328
+ ),
329
+ ] = None,
330
+ ):
331
+ r"""Generate a summary of staged changes for PR descriptions.
332
+
333
+ Analyzes staged git changes and generates a structured PR description
334
+ that includes:
335
+
336
+ - Summary statistics (files added/modified/deleted)
337
+ - Changes categorized by type (workflows, configs, docs, tests, etc.)
338
+ - Template repository information
339
+ - Last sync date
340
+
341
+ This is useful when creating pull requests after running `rhiza materialize`
342
+ to provide reviewers with a clear overview of what changed.
343
+
344
+ Examples:
345
+ rhiza summarise
346
+ rhiza summarise --output pr-description.md
347
+ rhiza summarise /path/to/project -o description.md
348
+
349
+ Typical workflow:
350
+ rhiza materialize
351
+ git add .
352
+ rhiza summarise --output pr-body.md
353
+ gh pr create --title "chore: Sync with rhiza" --body-file pr-body.md
354
+ """
355
+ summarise_cmd(target, output)
@@ -8,7 +8,7 @@ configuration templates for Python projects.
8
8
 
9
9
  ### init
10
10
 
11
- Initialize or validate `.github/rhiza/template.yml` in a target directory.
11
+ Initialize or validate `.rhiza/template.yml` in a target directory.
12
12
 
13
13
  Creates a default configuration file if it doesn't exist, or validates
14
14
  an existing one. The default configuration includes common Python project
@@ -29,7 +29,7 @@ is used.
29
29
 
30
30
  Validate Rhiza template configuration.
31
31
 
32
- Validates the `.github/rhiza/template.yml` file to ensure it is syntactically
32
+ Validates the `.rhiza/template.yml` file to ensure it is syntactically
33
33
  correct and semantically valid. Performs comprehensive validation including
34
34
  YAML syntax checking, required field verification, field type validation,
35
35
  and repository format verification.
rhiza/commands/init.py CHANGED
@@ -1,7 +1,7 @@
1
- """Command to initialize or validate .github/rhiza/template.yml.
1
+ """Command to initialize or validate .rhiza/template.yml.
2
2
 
3
3
  This module provides the init command that creates or validates the
4
- .github/rhiza/template.yml file, which defines where templates come from
4
+ .rhiza/template.yml file, which defines where templates come from
5
5
  and what paths are governed by Rhiza.
6
6
  """
7
7
 
@@ -52,7 +52,7 @@ def _validate_git_host(git_host: str | None) -> str | None:
52
52
  git_host = git_host.lower()
53
53
  if git_host not in ["github", "gitlab"]:
54
54
  logger.error(f"Invalid git-host: {git_host}. Must be 'github' or 'gitlab'")
55
- raise ValueError(f"Invalid git-host: {git_host}. Must be 'github' or 'gitlab'")
55
+ raise ValueError(f"Invalid git-host: {git_host}. Must be 'github' or 'gitlab'") # noqa: TRY003
56
56
  return git_host
57
57
 
58
58
 
@@ -124,12 +124,19 @@ def _get_include_paths_for_host(git_host: str) -> list[str]:
124
124
  ]
125
125
 
126
126
 
127
- def _create_template_file(target: Path, git_host: str) -> None:
127
+ def _create_template_file(
128
+ target: Path,
129
+ git_host: str,
130
+ template_repository: str | None = None,
131
+ template_branch: str | None = None,
132
+ ) -> None:
128
133
  """Create default template.yml file.
129
134
 
130
135
  Args:
131
136
  target: Target repository path.
132
137
  git_host: Git hosting platform.
138
+ template_repository: Custom template repository (format: owner/repo).
139
+ template_branch: Custom template branch.
133
140
  """
134
141
  rhiza_dir = target / ".rhiza"
135
142
  template_file = rhiza_dir / "template.yml"
@@ -141,9 +148,20 @@ def _create_template_file(target: Path, git_host: str) -> None:
141
148
  logger.debug("Using default template configuration")
142
149
 
143
150
  include_paths = _get_include_paths_for_host(git_host)
151
+
152
+ # Use custom template repository/branch if provided, otherwise use defaults
153
+ repo = template_repository or "jebel-quant/rhiza"
154
+ branch = template_branch or "main"
155
+
156
+ # Log when custom values are used
157
+ if template_repository:
158
+ logger.info(f"Using custom template repository: {repo}")
159
+ if template_branch:
160
+ logger.info(f"Using custom template branch: {branch}")
161
+
144
162
  default_template = RhizaTemplate(
145
- template_repository="jebel-quant/rhiza",
146
- template_branch="main",
163
+ template_repository=repo,
164
+ template_branch=branch,
147
165
  include=include_paths,
148
166
  )
149
167
 
@@ -243,10 +261,12 @@ def init(
243
261
  package_name: str | None = None,
244
262
  with_dev_dependencies: bool = False,
245
263
  git_host: str | None = None,
264
+ template_repository: str | None = None,
265
+ template_branch: str | None = None,
246
266
  ):
247
- """Initialize or validate .github/rhiza/template.yml in the target repository.
267
+ """Initialize or validate .rhiza/template.yml in the target repository.
248
268
 
249
- Creates a default .github/rhiza/template.yml file if it doesn't exist,
269
+ Creates a default .rhiza/template.yml file if it doesn't exist,
250
270
  or validates an existing one.
251
271
 
252
272
  Args:
@@ -256,6 +276,9 @@ def init(
256
276
  with_dev_dependencies: Include development dependencies in pyproject.toml.
257
277
  git_host: Target Git hosting platform ("github" or "gitlab"). Determines which
258
278
  CI/CD configuration files to include. If None, will prompt user interactively.
279
+ template_repository: Custom template repository (format: owner/repo).
280
+ Defaults to 'jebel-quant/rhiza'.
281
+ template_branch: Custom template branch. Defaults to 'main'.
259
282
 
260
283
  Returns:
261
284
  bool: True if validation passes, False otherwise.
@@ -275,7 +298,7 @@ def init(
275
298
  git_host = _prompt_git_host()
276
299
 
277
300
  # Create template file
278
- _create_template_file(target, git_host)
301
+ _create_template_file(target, git_host, template_repository, template_branch)
279
302
 
280
303
  # Bootstrap Python project structure
281
304
  if project_name is None:
@@ -8,7 +8,7 @@ into the target Git repository, and records managed files in
8
8
 
9
9
  import os
10
10
  import shutil
11
- import subprocess
11
+ import subprocess # nosec B404
12
12
  import sys
13
13
  import tempfile
14
14
  from pathlib import Path
@@ -37,7 +37,7 @@ def _handle_target_branch(
37
37
  logger.info(f"Creating/checking out target branch: {target_branch}")
38
38
  try:
39
39
  # Check if branch already exists using git rev-parse
40
- result = subprocess.run(
40
+ result = subprocess.run( # nosec B603
41
41
  [git_executable, "rev-parse", "--verify", target_branch],
42
42
  cwd=target,
43
43
  capture_output=True,
@@ -48,7 +48,7 @@ def _handle_target_branch(
48
48
  if result.returncode == 0:
49
49
  # Branch exists, switch to it
50
50
  logger.info(f"Branch '{target_branch}' exists, checking out...")
51
- subprocess.run(
51
+ subprocess.run( # nosec B603
52
52
  [git_executable, "checkout", target_branch],
53
53
  cwd=target,
54
54
  check=True,
@@ -57,7 +57,7 @@ def _handle_target_branch(
57
57
  else:
58
58
  # Branch doesn't exist, create it from current HEAD
59
59
  logger.info(f"Creating new branch '{target_branch}'...")
60
- subprocess.run(
60
+ subprocess.run( # nosec B603
61
61
  [git_executable, "checkout", "-b", target_branch],
62
62
  cwd=target,
63
63
  check=True,
@@ -99,7 +99,7 @@ def _validate_and_load_template(target: Path, branch: str) -> tuple[RhizaTemplat
99
99
  if not include_paths:
100
100
  logger.error("No include paths found in template.yml")
101
101
  logger.error("Add at least one path to the 'include' list in template.yml")
102
- raise RuntimeError("No include paths found in template.yml")
102
+ raise RuntimeError("No include paths found in template.yml") # noqa: TRY003
103
103
 
104
104
  # Log the paths we'll be including
105
105
  logger.info("Include paths:")
@@ -136,7 +136,7 @@ def _construct_git_url(rhiza_repo: str, rhiza_host: str) -> str:
136
136
  else:
137
137
  logger.error(f"Unsupported template-host: {rhiza_host}")
138
138
  logger.error("template-host must be 'github' or 'gitlab'")
139
- raise ValueError(f"Unsupported template-host: {rhiza_host}. Must be 'github' or 'gitlab'.")
139
+ raise ValueError(f"Unsupported template-host: {rhiza_host}. Must be 'github' or 'gitlab'.") # noqa: TRY003
140
140
  return git_url
141
141
 
142
142
 
@@ -161,7 +161,7 @@ def _clone_template_repository(
161
161
  # Clone the repository using sparse checkout
162
162
  try:
163
163
  logger.debug("Executing git clone with sparse checkout")
164
- subprocess.run(
164
+ subprocess.run( # nosec B603
165
165
  [
166
166
  git_executable,
167
167
  "clone",
@@ -190,7 +190,7 @@ def _clone_template_repository(
190
190
  # Initialize sparse checkout in cone mode
191
191
  try:
192
192
  logger.debug("Initializing sparse checkout")
193
- subprocess.run(
193
+ subprocess.run( # nosec B603
194
194
  [git_executable, "sparse-checkout", "init", "--cone"],
195
195
  cwd=tmp_dir,
196
196
  check=True,
@@ -208,7 +208,7 @@ def _clone_template_repository(
208
208
  # Set sparse checkout paths
209
209
  try:
210
210
  logger.debug(f"Setting sparse checkout paths: {include_paths}")
211
- subprocess.run(
211
+ subprocess.run( # nosec B603
212
212
  [git_executable, "sparse-checkout", "set", "--skip-checks", *include_paths],
213
213
  cwd=tmp_dir,
214
214
  check=True,
rhiza/commands/migrate.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This module implements the `migrate` command. It helps transition projects to use
4
4
  the new `.rhiza/` folder structure for storing Rhiza state and configuration files,
5
- separate from `.github/rhiza/` which contains template configuration.
5
+ separate from `.github/` which contains GitHub-specific configurations.
6
6
  """
7
7
 
8
8
  import shutil
@@ -0,0 +1,416 @@
1
+ """Command for generating PR descriptions from staged changes.
2
+
3
+ This module provides functionality to analyze staged git changes and generate
4
+ structured PR descriptions for rhiza sync operations.
5
+ """
6
+
7
+ import subprocess # nosec B404
8
+ import sys
9
+ from collections import defaultdict
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+
13
+ from loguru import logger
14
+
15
+
16
+ def run_git_command(args: list[str], cwd: Path | None = None) -> str:
17
+ """Run a git command and return the output.
18
+
19
+ Args:
20
+ args: Git command arguments (without 'git' prefix)
21
+ cwd: Working directory for the command
22
+
23
+ Returns:
24
+ Command output as string
25
+ """
26
+ try:
27
+ result = subprocess.run( # nosec B603 B607
28
+ ["git", *args],
29
+ cwd=cwd,
30
+ capture_output=True,
31
+ text=True,
32
+ check=True,
33
+ )
34
+ return result.stdout.strip()
35
+ except subprocess.CalledProcessError as e:
36
+ logger.error(f"Error running git {' '.join(args)}: {e.stderr}")
37
+ return ""
38
+
39
+
40
+ def get_staged_changes(repo_path: Path) -> dict[str, list[str]]:
41
+ """Get list of staged changes categorized by type.
42
+
43
+ Args:
44
+ repo_path: Path to the repository
45
+
46
+ Returns:
47
+ Dictionary with keys 'added', 'modified', 'deleted' containing file lists
48
+ """
49
+ changes = {
50
+ "added": [],
51
+ "modified": [],
52
+ "deleted": [],
53
+ }
54
+
55
+ # Get staged changes
56
+ output = run_git_command(["diff", "--cached", "--name-status"], cwd=repo_path)
57
+
58
+ for line in output.split("\n"):
59
+ if not line:
60
+ continue
61
+ parts = line.split("\t", 1)
62
+ if len(parts) != 2:
63
+ continue
64
+ status, filepath = parts
65
+
66
+ if status == "A":
67
+ changes["added"].append(filepath)
68
+ elif status == "M":
69
+ changes["modified"].append(filepath)
70
+ elif status == "D":
71
+ changes["deleted"].append(filepath)
72
+ elif status.startswith("R"):
73
+ # Renamed file - treat as modified
74
+ changes["modified"].append(filepath)
75
+
76
+ return changes
77
+
78
+
79
+ def _get_config_files() -> set[str]:
80
+ """Get set of known configuration files.
81
+
82
+ Returns:
83
+ Set of configuration file names
84
+ """
85
+ return {
86
+ "Makefile",
87
+ "ruff.toml",
88
+ "pytest.ini",
89
+ ".editorconfig",
90
+ ".gitignore",
91
+ ".pre-commit-config.yaml",
92
+ "renovate.json",
93
+ ".python-version",
94
+ }
95
+
96
+
97
+ def _categorize_by_directory(first_dir: str, filepath: str) -> str | None:
98
+ """Categorize file based on its first directory.
99
+
100
+ Args:
101
+ first_dir: First directory in the path
102
+ filepath: Full file path
103
+
104
+ Returns:
105
+ Category name or None if no match
106
+ """
107
+ if first_dir == ".github":
108
+ path_parts = Path(filepath).parts
109
+ if len(path_parts) > 1 and path_parts[1] == "workflows":
110
+ return "GitHub Actions Workflows"
111
+ return "GitHub Configuration"
112
+
113
+ if first_dir == ".rhiza":
114
+ if "script" in filepath.lower():
115
+ return "Rhiza Scripts"
116
+ if "Makefile" in filepath:
117
+ return "Makefiles"
118
+ return "Rhiza Configuration"
119
+
120
+ if first_dir == "tests":
121
+ return "Tests"
122
+
123
+ if first_dir == "book":
124
+ return "Documentation"
125
+
126
+ return None
127
+
128
+
129
+ def _categorize_single_file(filepath: str) -> str:
130
+ """Categorize a single file path.
131
+
132
+ Args:
133
+ filepath: File path to categorize
134
+
135
+ Returns:
136
+ Category name
137
+ """
138
+ path_parts = Path(filepath).parts
139
+
140
+ if not path_parts:
141
+ return "Other"
142
+
143
+ # Try directory-based categorization first
144
+ category = _categorize_by_directory(path_parts[0], filepath)
145
+ if category:
146
+ return category
147
+
148
+ # Check file-based categories
149
+ if filepath.endswith(".md"):
150
+ return "Documentation"
151
+
152
+ if filepath in _get_config_files():
153
+ return "Configuration Files"
154
+
155
+ return "Other"
156
+
157
+
158
+ def categorize_files(files: list[str]) -> dict[str, list[str]]:
159
+ """Categorize files by type.
160
+
161
+ Args:
162
+ files: List of file paths
163
+
164
+ Returns:
165
+ Dictionary mapping category names to file lists
166
+ """
167
+ categories = defaultdict(list)
168
+
169
+ for filepath in files:
170
+ category = _categorize_single_file(filepath)
171
+ categories[category].append(filepath)
172
+
173
+ return dict(categories)
174
+
175
+
176
+ def get_template_info(repo_path: Path) -> tuple[str, str]:
177
+ """Get template repository and branch from template.yml.
178
+
179
+ Args:
180
+ repo_path: Path to the repository
181
+
182
+ Returns:
183
+ Tuple of (template_repo, template_branch)
184
+ """
185
+ template_file = repo_path / ".rhiza" / "template.yml"
186
+
187
+ if not template_file.exists():
188
+ return ("jebel-quant/rhiza", "main")
189
+
190
+ template_repo = "jebel-quant/rhiza"
191
+ template_branch = "main"
192
+
193
+ with open(template_file) as f:
194
+ for line in f:
195
+ line = line.strip()
196
+ if line.startswith("template-repository:"):
197
+ template_repo = line.split(":", 1)[1].strip().strip('"')
198
+ elif line.startswith("template-branch:"):
199
+ template_branch = line.split(":", 1)[1].strip().strip('"')
200
+
201
+ return template_repo, template_branch
202
+
203
+
204
+ def get_last_sync_date(repo_path: Path) -> str | None:
205
+ """Get the date of the last sync commit.
206
+
207
+ Args:
208
+ repo_path: Path to the repository
209
+
210
+ Returns:
211
+ ISO format date string or None if not found
212
+ """
213
+ # Look for the most recent commit with "rhiza" in the message
214
+ output = run_git_command(
215
+ ["log", "--grep=rhiza", "--grep=Sync", "--grep=template", "-i", "--format=%cI", "-1"], cwd=repo_path
216
+ )
217
+
218
+ if output:
219
+ return output
220
+
221
+ # Fallback: try to get date from history file if it exists
222
+ history_file = repo_path / ".rhiza" / "history"
223
+ if history_file.exists():
224
+ # Get the file modification time
225
+ stat = history_file.stat()
226
+ return datetime.fromtimestamp(stat.st_mtime).isoformat()
227
+
228
+ return None
229
+
230
+
231
+ def _format_file_list(files: list[str], status_emoji: str) -> list[str]:
232
+ """Format a list of files with the given status emoji.
233
+
234
+ Args:
235
+ files: List of file paths
236
+ status_emoji: Emoji to use (✅ for added, 📝 for modified, ❌ for deleted)
237
+
238
+ Returns:
239
+ List of formatted lines
240
+ """
241
+ lines = []
242
+ for f in sorted(files):
243
+ lines.append(f"- {status_emoji} `{f}`")
244
+ return lines
245
+
246
+
247
+ def _add_category_section(lines: list[str], title: str, count: int, files: list[str], emoji: str) -> None:
248
+ """Add a collapsible section for a category and change type.
249
+
250
+ Args:
251
+ lines: List to append lines to
252
+ title: Section title (e.g., "Added", "Modified")
253
+ count: Number of files
254
+ files: List of file paths
255
+ emoji: Status emoji
256
+ """
257
+ if not files:
258
+ return
259
+
260
+ lines.append("<details>")
261
+ lines.append(f"<summary>{title} ({count})</summary>")
262
+ lines.append("")
263
+ lines.extend(_format_file_list(files, emoji))
264
+ lines.append("")
265
+ lines.append("</details>")
266
+ lines.append("")
267
+
268
+
269
+ def _build_header(template_repo: str) -> list[str]:
270
+ """Build the PR description header.
271
+
272
+ Args:
273
+ template_repo: Template repository name
274
+
275
+ Returns:
276
+ List of header lines
277
+ """
278
+ return [
279
+ "## 🔄 Template Synchronization",
280
+ "",
281
+ f"This PR synchronizes the repository with the [{template_repo}](https://github.com/{template_repo}) template.",
282
+ "",
283
+ ]
284
+
285
+
286
+ def _build_summary(changes: dict[str, list[str]]) -> list[str]:
287
+ """Build the change summary section.
288
+
289
+ Args:
290
+ changes: Dictionary of changes by type
291
+
292
+ Returns:
293
+ List of summary lines
294
+ """
295
+ return [
296
+ "### 📊 Change Summary",
297
+ "",
298
+ f"- **{len(changes['added'])}** files added",
299
+ f"- **{len(changes['modified'])}** files modified",
300
+ f"- **{len(changes['deleted'])}** files deleted",
301
+ "",
302
+ ]
303
+
304
+
305
+ def _build_footer(template_repo: str, template_branch: str, last_sync: str | None) -> list[str]:
306
+ """Build the PR description footer with metadata.
307
+
308
+ Args:
309
+ template_repo: Template repository name
310
+ template_branch: Template branch name
311
+ last_sync: Last sync date string or None
312
+
313
+ Returns:
314
+ List of footer lines
315
+ """
316
+ lines = [
317
+ "---",
318
+ "",
319
+ "**🤖 Generated by [rhiza](https://github.com/jebel-quant/rhiza-cli)**",
320
+ "",
321
+ f"- Template: `{template_repo}@{template_branch}`",
322
+ ]
323
+ if last_sync:
324
+ lines.append(f"- Last sync: {last_sync}")
325
+ lines.append(f"- Sync date: {datetime.now().astimezone().isoformat()}")
326
+ return lines
327
+
328
+
329
+ def generate_pr_description(repo_path: Path) -> str:
330
+ """Generate PR description based on staged changes.
331
+
332
+ Args:
333
+ repo_path: Path to the repository
334
+
335
+ Returns:
336
+ Formatted PR description
337
+ """
338
+ changes = get_staged_changes(repo_path)
339
+ template_repo, template_branch = get_template_info(repo_path)
340
+ last_sync = get_last_sync_date(repo_path)
341
+
342
+ # Build header
343
+ lines = _build_header(template_repo)
344
+
345
+ # Check if there are any changes
346
+ total_changes = sum(len(files) for files in changes.values())
347
+ if total_changes == 0:
348
+ lines.append("No changes detected.")
349
+ return "\n".join(lines)
350
+
351
+ # Add summary
352
+ lines.extend(_build_summary(changes))
353
+
354
+ # Add detailed changes by category
355
+ all_changed_files = changes["added"] + changes["modified"] + changes["deleted"]
356
+ categories = categorize_files(all_changed_files)
357
+
358
+ if categories:
359
+ lines.append("### 📁 Changes by Category")
360
+ lines.append("")
361
+
362
+ for category, files in sorted(categories.items()):
363
+ lines.append(f"#### {category}")
364
+ lines.append("")
365
+
366
+ # Group files by change type
367
+ category_added = [f for f in files if f in changes["added"]]
368
+ category_modified = [f for f in files if f in changes["modified"]]
369
+ category_deleted = [f for f in files if f in changes["deleted"]]
370
+
371
+ _add_category_section(lines, "Added", len(category_added), category_added, "✅")
372
+ _add_category_section(lines, "Modified", len(category_modified), category_modified, "📝")
373
+ _add_category_section(lines, "Deleted", len(category_deleted), category_deleted, "❌")
374
+
375
+ # Add footer
376
+ lines.extend(_build_footer(template_repo, template_branch, last_sync))
377
+
378
+ return "\n".join(lines)
379
+
380
+
381
+ def summarise(target: Path, output: Path | None = None) -> None:
382
+ """Generate a summary of staged changes for rhiza sync operations.
383
+
384
+ This command analyzes staged git changes and generates a structured
385
+ PR description with:
386
+ - Summary statistics (files added/modified/deleted)
387
+ - Changes categorized by type (workflows, configs, docs, tests, etc.)
388
+ - Template repository information
389
+ - Last sync date
390
+
391
+ Args:
392
+ target: Path to the target repository.
393
+ output: Optional output file path. If not provided, prints to stdout.
394
+ """
395
+ target = target.resolve()
396
+ logger.info(f"Target repository: {target}")
397
+
398
+ # Check if target is a git repository
399
+ if not (target / ".git").is_dir():
400
+ logger.error(f"Target directory is not a git repository: {target}")
401
+ logger.error("Initialize a git repository with 'git init' first")
402
+ sys.exit(1)
403
+
404
+ # Generate the PR description
405
+ description = generate_pr_description(target)
406
+
407
+ # Output the description
408
+ if output:
409
+ output_path = output.resolve()
410
+ output_path.write_text(description)
411
+ logger.success(f"PR description written to {output_path}")
412
+ else:
413
+ # Print to stdout
414
+ print(description)
415
+
416
+ logger.success("Summary generated successfully")
@@ -143,10 +143,11 @@ def _remove_history_file(history_file: Path, target: Path) -> tuple[int, int]:
143
143
  try:
144
144
  history_file.unlink()
145
145
  logger.success(f"[DEL] {history_file.relative_to(target)}")
146
- return 1, 0
147
146
  except Exception as e:
148
147
  logger.error(f"Failed to delete {history_file.relative_to(target)}: {e}")
149
148
  return 0, 1
149
+ else:
150
+ return 1, 0
150
151
 
151
152
 
152
153
  def _print_summary(removed_count: int, skipped_count: int, empty_dirs_removed: int, error_count: int) -> None:
@@ -91,7 +91,7 @@ def _check_template_file_exists(target: Path) -> tuple[bool, Path]:
91
91
  logger.info(" • If you have an existing configuration, run: rhiza migrate")
92
92
  logger.info("")
93
93
  logger.info("The 'rhiza migrate' command will move your configuration from")
94
- logger.info(" .github/rhiza/template.yml → .rhiza/template.yml")
94
+ logger.info(" the old location → .rhiza/template.yml")
95
95
  return False, template_file
96
96
 
97
97
  logger.success(f"Template file exists: {template_file.relative_to(target)}")
rhiza/commands/welcome.py CHANGED
@@ -44,7 +44,7 @@ Python projects using reusable templates stored in a central repository.
44
44
  1. Initialize a project:
45
45
  $ rhiza init
46
46
 
47
- 2. Customize .github/rhiza/template.yml to match your needs
47
+ 2. Customize .rhiza/template.yml to match your needs
48
48
 
49
49
  3. Materialize templates into your project:
50
50
  $ rhiza materialize
rhiza/models.py CHANGED
@@ -36,7 +36,7 @@ def _normalize_to_list(value: str | list[str] | None) -> list[str]:
36
36
 
37
37
  @dataclass
38
38
  class RhizaTemplate:
39
- """Represents the structure of .github/rhiza/template.yml.
39
+ """Represents the structure of .rhiza/template.yml.
40
40
 
41
41
  Attributes:
42
42
  template_repository: The GitHub or GitLab repository containing templates (e.g., "jebel-quant/rhiza").
@@ -74,7 +74,7 @@ class RhizaTemplate:
74
74
  config = yaml.safe_load(f)
75
75
 
76
76
  if not config:
77
- raise ValueError("Template file is empty")
77
+ raise ValueError("Template file is empty") # noqa: TRY003
78
78
 
79
79
  return cls(
80
80
  template_repository=config.get("template-repository"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rhiza
3
- Version: 0.8.8
3
+ Version: 0.9.0
4
4
  Summary: Reusable configuration templates for modern Python projects
5
5
  Project-URL: Homepage, https://github.com/jebel-quant/rhiza-cli
6
6
  Project-URL: Repository, https://github.com/jebel-quant/rhiza-cli
@@ -146,11 +146,11 @@ rhiza --help
146
146
  rhiza init
147
147
  ```
148
148
 
149
- This creates a `.github/rhiza/template.yml` file with default configuration.
149
+ This creates a `.rhiza/template.yml` file with default configuration.
150
150
 
151
151
  2. **Customize the template configuration:**
152
152
 
153
- Edit `.github/rhiza/template.yml` to specify which files/directories to include from your template repository.
153
+ Edit `.rhiza/template.yml` to specify which files/directories to include from your template repository.
154
154
 
155
155
  3. **Materialize templates into your project:**
156
156
 
@@ -166,27 +166,38 @@ rhiza --help
166
166
  rhiza validate
167
167
  ```
168
168
 
169
- This checks that your `.github/rhiza/template.yml` is correctly formatted and valid.
169
+ This checks that your `.rhiza/template.yml` is correctly formatted and valid.
170
170
 
171
171
  ## Commands
172
172
 
173
173
  ### `rhiza init`
174
174
 
175
- Initialize or validate `.github/rhiza/template.yml` in a target directory.
175
+ Initialize or validate `.rhiza/template.yml` in a target directory.
176
176
 
177
177
  **Usage:**
178
178
 
179
179
  ```bash
180
- rhiza init [TARGET]
180
+ rhiza init [OPTIONS] [TARGET]
181
181
  ```
182
182
 
183
183
  **Arguments:**
184
184
 
185
185
  - `TARGET` - Target directory (defaults to current directory)
186
186
 
187
+ **Options:**
188
+
189
+ - `--project-name <name>` - Custom project name (defaults to directory name)
190
+ - `--package-name <name>` - Custom package name (defaults to normalized project name)
191
+ - `--with-dev-dependencies` - Include development dependencies in pyproject.toml
192
+ - `--git-host <host>` - Target Git hosting platform (github or gitlab). Determines which CI/CD files to include. If not provided, will prompt interactively.
193
+ - `--template-repository <owner/repo>` - Custom template repository (format: owner/repo). Defaults to 'jebel-quant/rhiza'.
194
+ - `--template-branch <branch>` - Custom template branch. Defaults to 'main'.
195
+
187
196
  **Description:**
188
197
 
189
- Creates a default `.github/rhiza/template.yml` file if it doesn't exist, or validates an existing one. The default configuration includes common Python project files like `.github`, `.editorconfig`, `.gitignore`, `.pre-commit-config.yaml`, `Makefile`, and `pytest.ini`.
198
+ Creates a default `.rhiza/template.yml` file if it doesn't exist, or validates an existing one. The default configuration includes common Python project files like `.github`, `.editorconfig`, `.gitignore`, `.pre-commit-config.yaml`, `Makefile`, and `pytest.ini`.
199
+
200
+ You can customize the template source by specifying your own template repository and branch using the `--template-repository` and `--template-branch` options.
190
201
 
191
202
  **Examples:**
192
203
 
@@ -197,6 +208,15 @@ rhiza init
197
208
  # Initialize in a specific directory
198
209
  rhiza init /path/to/project
199
210
 
211
+ # Initialize with GitLab CI configuration
212
+ rhiza init --git-host gitlab
213
+
214
+ # Use a custom template repository
215
+ rhiza init --template-repository myorg/my-templates
216
+
217
+ # Use a custom template repository and branch
218
+ rhiza init --template-repository myorg/my-templates --template-branch develop
219
+
200
220
  # Initialize in parent directory
201
221
  rhiza init ..
202
222
  ```
@@ -206,18 +226,18 @@ rhiza init ..
206
226
  When creating a new template file:
207
227
  ```
208
228
  [INFO] Initializing Rhiza configuration in: /path/to/project
209
- [INFO] Creating default .github/rhiza/template.yml
210
- ✓ Created .github/rhiza/template.yml
229
+ [INFO] Creating default .rhiza/template.yml
230
+ ✓ Created .rhiza/template.yml
211
231
 
212
232
  Next steps:
213
- 1. Review and customize .github/rhiza/template.yml to match your project needs
233
+ 1. Review and customize .rhiza/template.yml to match your project needs
214
234
  2. Run 'rhiza materialize' to inject templates into your repository
215
235
  ```
216
236
 
217
237
  When validating an existing file:
218
238
  ```
219
239
  [INFO] Validating template configuration in: /path/to/project
220
- ✓ Found template file: /path/to/project/.github/rhiza/template.yml
240
+ ✓ Found template file: /path/to/project/.rhiza/template.yml
221
241
  ✓ YAML syntax is valid
222
242
  ✓ Field 'template-repository' is present and valid
223
243
  ✓ Field 'include' is present and valid
@@ -252,7 +272,7 @@ rhiza materialize [OPTIONS] [TARGET]
252
272
 
253
273
  Materializes template files from the configured template repository into your target project. This command:
254
274
 
255
- 1. Reads the `.github/rhiza/template.yml` configuration
275
+ 1. Reads the `.rhiza/template.yml` configuration
256
276
  2. Performs a sparse clone of the template repository
257
277
  3. Copies specified files/directories to your project
258
278
  4. Respects exclusion patterns defined in the configuration
@@ -366,7 +386,7 @@ rhiza migrate /path/to/project
366
386
  [INFO] This will create the .rhiza folder and migrate configuration files
367
387
  [INFO] Creating .rhiza directory at: .rhiza
368
388
  ✓ Created .rhiza
369
- [INFO] Found template.yml at: .github/rhiza/template.yml
389
+ [INFO] Found template.yml at: .rhiza/template.yml
370
390
  [INFO] Moving to new location: .rhiza/template.yml
371
391
  ✓ Moved template.yml to .rhiza/template.yml
372
392
  ✓ Migration completed successfully
@@ -414,7 +434,7 @@ rhiza validate [TARGET]
414
434
 
415
435
  **Description:**
416
436
 
417
- Validates the `.github/rhiza/template.yml` file to ensure it is syntactically correct and semantically valid. This performs authoritative validation including:
437
+ Validates the `.rhiza/template.yml` file to ensure it is syntactically correct and semantically valid. This performs authoritative validation including:
418
438
 
419
439
  - Checking if the file exists
420
440
  - Validating YAML syntax
@@ -445,7 +465,7 @@ rhiza validate ..
445
465
 
446
466
  ```
447
467
  [INFO] Validating template configuration in: /path/to/project
448
- ✓ Found template file: /path/to/project/.github/rhiza/template.yml
468
+ ✓ Found template file: /path/to/project/.rhiza/template.yml
449
469
  ✓ YAML syntax is valid
450
470
  ✓ Field 'template-repository' is present and valid
451
471
  ✓ Field 'include' is present and valid
@@ -469,7 +489,7 @@ rhiza validate ..
469
489
  or
470
490
 
471
491
  ```
472
- [ERROR] Template file not found: /path/to/project/.github/rhiza/template.yml
492
+ [ERROR] Template file not found: /path/to/project/.rhiza/template.yml
473
493
  [INFO] Run 'rhiza materialize' or 'rhiza init' to create a default template.yml
474
494
  ```
475
495
 
@@ -477,7 +497,7 @@ or
477
497
 
478
498
  ## Configuration
479
499
 
480
- Rhiza uses a `.github/rhiza/template.yml` file to define template sources and what to include in your project.
500
+ Rhiza uses a `.rhiza/template.yml` file to define template sources and what to include in your project.
481
501
 
482
502
  ### Configuration File Format
483
503
 
@@ -618,7 +638,7 @@ git init
618
638
  rhiza init
619
639
 
620
640
  # Review the generated template.yml
621
- cat .github/rhiza/template.yml
641
+ cat .rhiza/template.yml
622
642
 
623
643
  # Materialize templates
624
644
  rhiza materialize
@@ -653,7 +673,7 @@ git commit -m "chore: update rhiza templates"
653
673
 
654
674
  ### Example 3: Using a custom template repository
655
675
 
656
- Edit `.github/rhiza/template.yml`:
676
+ Edit `.rhiza/template.yml`:
657
677
 
658
678
  ```yaml
659
679
  template-repository: myorg/my-templates
@@ -675,7 +695,7 @@ rhiza materialize --force
675
695
 
676
696
  ### Example 4: Using a GitLab template repository
677
697
 
678
- Edit `.github/rhiza/template.yml`:
698
+ Edit `.rhiza/template.yml`:
679
699
 
680
700
  ```yaml
681
701
  template-repository: mygroup/python-templates
@@ -815,7 +835,7 @@ Releasing and Versioning
815
835
  post-release perform post-release tasks
816
836
 
817
837
  Meta
818
- sync sync with template repository as defined in .github/rhiza/template.yml
838
+ sync sync with template repository as defined in .rhiza/template.yml
819
839
  help Display this help message
820
840
  customisations list available customisation scripts
821
841
  update-readme update README.md with current Makefile help output
@@ -887,7 +907,7 @@ export PATH="$HOME/.local/bin:$PATH"
887
907
  ### Template validation fails
888
908
 
889
909
  Check that:
890
- 1. Your `.github/rhiza/template.yml` file exists
910
+ 1. Your `.rhiza/template.yml` file exists
891
911
  2. The YAML syntax is valid
892
912
  3. Required fields (`template-repository` and `include`) are present
893
913
  4. The repository format is `owner/repo`
@@ -919,11 +939,11 @@ A: Yes, as long as you have Git credentials configured that allow access to the
919
939
 
920
940
  **Q: Does Rhiza support template repositories hosted outside GitHub?**
921
941
 
922
- A: Yes! Rhiza supports both GitHub and GitLab repositories. Use the `template-host` field in your `.github/rhiza/template.yml` to specify "github" (default) or "gitlab".
942
+ A: Yes! Rhiza supports both GitHub and GitLab repositories. Use the `template-host` field in your `.rhiza/template.yml` to specify "github" (default) or "gitlab".
923
943
 
924
944
  **Q: How do I use a GitLab repository as a template source?**
925
945
 
926
- A: Add `template-host: gitlab` to your `.github/rhiza/template.yml` file. For example:
946
+ A: Add `template-host: gitlab` to your `.rhiza/template.yml` file. For example:
927
947
  ```yaml
928
948
  template-repository: mygroup/myproject
929
949
  template-host: gitlab
@@ -938,7 +958,7 @@ A: Not directly. However, you can run `rhiza materialize` multiple times with di
938
958
 
939
959
  **Q: What's the difference between `rhiza init` and `rhiza materialize`?**
940
960
 
941
- A: `init` creates or validates the `.github/rhiza/template.yml` configuration file. `materialize` reads that configuration and actually copies the template files into your project.
961
+ A: `init` creates or validates the `.rhiza/template.yml` configuration file. `materialize` reads that configuration and actually copies the template files into your project.
942
962
 
943
963
  **Q: How do I update my project's templates?**
944
964
 
@@ -954,7 +974,7 @@ A: The update method depends on how you installed rhiza:
954
974
 
955
975
  **Q: Can I customize which files are included?**
956
976
 
957
- A: Yes, edit the `include` and `exclude` lists in `.github/rhiza/template.yml` to control exactly which files are copied.
977
+ A: Yes, edit the `include` and `exclude` lists in `.rhiza/template.yml` to control exactly which files are copied.
958
978
 
959
979
  ## Acknowledgments
960
980
 
@@ -0,0 +1,21 @@
1
+ rhiza/__init__.py,sha256=4-Dy7AKbJneQNBfv30WhSsUin4y-g4Yp4veO6-YdjVg,1926
2
+ rhiza/__main__.py,sha256=Q02upTGaJceknkDABdCwq5_vdMdGY8Cg3ej6WZIHs_s,829
3
+ rhiza/cli.py,sha256=uU0tenEgTe0Wz5lIj0SZIeXy5n_9-8xtUMMP_i71CWA,10609
4
+ rhiza/models.py,sha256=f4TT3XPMEE7ciyrpsXllje4eGJRkYOgpZOoFeRC-rVI,4225
5
+ rhiza/subprocess_utils.py,sha256=Pr5TysIKP76hc64fmqhTd6msMGn5DU43hOSR_v_GFb8,745
6
+ rhiza/_templates/basic/__init__.py.jinja2,sha256=gs8qN4LAKcdFd6iO9gZVLuVetODmZP_TGuEjWrbinC0,27
7
+ rhiza/_templates/basic/main.py.jinja2,sha256=uTCahxf9Bftao1IghHue4cSZ9YzBYmBEXeIhEmK9UXQ,362
8
+ rhiza/_templates/basic/pyproject.toml.jinja2,sha256=Mizpnnd_kFQd-pCWOxG-KWhvg4_ZhZaQppTt2pz0WOc,695
9
+ rhiza/commands/__init__.py,sha256=QWEEVvdW3gKV-FpKgHRJL_H8FpQqvfck9JvnFMDz3gY,1834
10
+ rhiza/commands/init.py,sha256=dpLwFbURyndkgw-v4O6gD0gg6ov6q023uOmaWGup2oA,9785
11
+ rhiza/commands/materialize.py,sha256=1YmzlPz9-IxnV4znDZQijZ3cjfbBXdepmps963z4Hsg,18668
12
+ rhiza/commands/migrate.py,sha256=A5t4nw7CrdtILCwuSoAqtmM0LpMK8KmX87gzlNgi7fQ,7522
13
+ rhiza/commands/summarise.py,sha256=KJ8v4lHSbn8AIt3tJM_lSCizzZ2Bv2mqTfI0z7-kyxI,11736
14
+ rhiza/commands/uninstall.py,sha256=6oO7kdv11Bq4JXjrBg9rsFtoRgttQ4m30zGr6NhZrkQ,7479
15
+ rhiza/commands/validate.py,sha256=VriXJxyuhvGJajw0qiAUmXYKRMbpgKiZ5_MMtE6WlSo,10801
16
+ rhiza/commands/welcome.py,sha256=u197cIlY1tXm-CN6YpyUX4Eq06pLeV0hGyNvT35tE8U,2375
17
+ rhiza-0.9.0.dist-info/METADATA,sha256=6AIK6bVC54vxBE0NB9jdLkBuTUcn9W0jod6ixm1-EB0,26298
18
+ rhiza-0.9.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
19
+ rhiza-0.9.0.dist-info/entry_points.txt,sha256=NAwZUpbXvfKv50a_Qq-PxMHl3lcjAyZO63IBeuUNgfY,45
20
+ rhiza-0.9.0.dist-info/licenses/LICENSE,sha256=4m5X7LhqX-6D0Ks79Ys8CLpmza8cxDG34g4S9XSNAGY,1077
21
+ rhiza-0.9.0.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- rhiza/__init__.py,sha256=iW3niLBjwRKxcMhIV_1eb78putjUTo2tbZsadofluJk,1939
2
- rhiza/__main__.py,sha256=Q02upTGaJceknkDABdCwq5_vdMdGY8Cg3ej6WZIHs_s,829
3
- rhiza/cli.py,sha256=xIsyfKjSFjVLjCS7o2om5o_YLZx9lIhsI0MTMI5Zs2k,8594
4
- rhiza/models.py,sha256=o_iD8P7d7RAHH6J91tSPmD6lY-2HUsCaIl8auWqm2NM,4216
5
- rhiza/subprocess_utils.py,sha256=Pr5TysIKP76hc64fmqhTd6msMGn5DU43hOSR_v_GFb8,745
6
- rhiza/_templates/basic/__init__.py.jinja2,sha256=gs8qN4LAKcdFd6iO9gZVLuVetODmZP_TGuEjWrbinC0,27
7
- rhiza/_templates/basic/main.py.jinja2,sha256=uTCahxf9Bftao1IghHue4cSZ9YzBYmBEXeIhEmK9UXQ,362
8
- rhiza/_templates/basic/pyproject.toml.jinja2,sha256=Mizpnnd_kFQd-pCWOxG-KWhvg4_ZhZaQppTt2pz0WOc,695
9
- rhiza/commands/__init__.py,sha256=Z5CeMh7ylX27H6dvwqRbEKzYo5pwQq-5TyTxABUSaQg,1848
10
- rhiza/commands/init.py,sha256=73MLPLp-M8U4fP8J5RXghS6FsZjx2PpeeBbKRZvLQ7U,8882
11
- rhiza/commands/materialize.py,sha256=U6MouBNrg_GjYIPD0vBb3nacFBKGP4l8RD-HH0u-mYk,18538
12
- rhiza/commands/migrate.py,sha256=pT8izKuX2eXCAkmNfcy4AU5HTB1DoOZoBXcZo2AOpXs,7520
13
- rhiza/commands/uninstall.py,sha256=MJbQtmdTgbzMvQz0gGLW3aw6S1dSV8nLv0SqWSDpyPk,7469
14
- rhiza/commands/validate.py,sha256=pg7SpgavvrjDyuZIphJ_GOMnXkwdVs9WtL2caa1XjcM,10811
15
- rhiza/commands/welcome.py,sha256=w3BziR042o6oYincd3EqDsFzF6qqInU7iYhWjF3yJqY,2382
16
- rhiza-0.8.8.dist-info/METADATA,sha256=PGkXXTlS2XGjxAm5ODJmDvrRerXnEmG1ZoUcJvYhiyo,25395
17
- rhiza-0.8.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
- rhiza-0.8.8.dist-info/entry_points.txt,sha256=NAwZUpbXvfKv50a_Qq-PxMHl3lcjAyZO63IBeuUNgfY,45
19
- rhiza-0.8.8.dist-info/licenses/LICENSE,sha256=4m5X7LhqX-6D0Ks79Ys8CLpmza8cxDG34g4S9XSNAGY,1077
20
- rhiza-0.8.8.dist-info/RECORD,,
File without changes