rhiza 0.4.0__py3-none-any.whl → 0.5.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
@@ -3,3 +3,7 @@
3
3
  This package groups small, user-facing utilities that can be invoked from
4
4
  the command line or other automation scripts.
5
5
  """
6
+
7
+ from rhiza.models import RhizaTemplate
8
+
9
+ __all__ = ["RhizaTemplate"]
rhiza/cli.py CHANGED
@@ -8,16 +8,45 @@ from pathlib import Path
8
8
 
9
9
  import typer
10
10
 
11
- from rhiza.commands.hello import hello as hello_cmd
12
- from rhiza.commands.inject import inject as inject_cmd
11
+ from rhiza.commands.init import init as init_cmd
12
+ from rhiza.commands.materialize import materialize as materialize_cmd
13
+ from rhiza.commands.validate import validate as validate_cmd
13
14
 
14
- app = typer.Typer(help="rhiza — configuration materialization tools")
15
+ app = typer.Typer(
16
+ help="Rhiza - Manage reusable configuration templates for Python projects",
17
+ add_completion=True,
18
+ )
15
19
 
16
20
 
17
21
  @app.command()
18
- def hello():
19
- """Sanity check command."""
20
- hello_cmd()
22
+ def init(
23
+ target: Path = typer.Argument(
24
+ default=Path("."), # default to current directory
25
+ exists=True,
26
+ file_okay=False,
27
+ dir_okay=True,
28
+ help="Target directory (defaults to current directory)",
29
+ ),
30
+ ):
31
+ """Initialize or validate .github/template.yml.
32
+
33
+ Creates a default .github/template.yml configuration file if one doesn't
34
+ exist, or validates the existing configuration.
35
+
36
+ The default template includes common Python project files:
37
+ - .github (workflows, actions, etc.)
38
+ - .editorconfig
39
+ - .gitignore
40
+ - .pre-commit-config.yaml
41
+ - Makefile
42
+ - pytest.ini
43
+
44
+ Examples:
45
+ rhiza init
46
+ rhiza init /path/to/project
47
+ rhiza init ..
48
+ """
49
+ init_cmd(target)
21
50
 
22
51
 
23
52
  @app.command()
@@ -32,16 +61,55 @@ def materialize(
32
61
  branch: str = typer.Option("main", "--branch", "-b", help="Rhiza branch to use"),
33
62
  force: bool = typer.Option(False, "--force", "-y", help="Overwrite existing files"),
34
63
  ):
35
- """Inject Rhiza configuration into a target repository.
36
-
37
- Parameters
38
- ----------
39
- target:
40
- Path to the target Git repository directory. Defaults to the
41
- current working directory.
42
- branch:
43
- Name of the Rhiza branch to use when sourcing templates.
44
- force:
45
- If True, overwrite existing files without prompting.
64
+ """Inject Rhiza configuration templates into a target repository.
65
+
66
+ Materializes configuration files from the template repository specified
67
+ in .github/template.yml into your project. This command:
68
+
69
+ 1. Reads .github/template.yml configuration
70
+ 2. Performs a sparse clone of the template repository
71
+ 3. Copies specified files/directories to your project
72
+ 4. Respects exclusion patterns defined in the configuration
73
+
74
+ Files that already exist will NOT be overwritten unless --force is used.
75
+
76
+ Examples:
77
+ rhiza materialize
78
+ rhiza materialize --branch develop
79
+ rhiza materialize --force
80
+ rhiza materialize /path/to/project -b v2.0 -y
81
+ """
82
+ materialize_cmd(target, branch, force)
83
+
84
+
85
+ @app.command()
86
+ def validate(
87
+ target: Path = typer.Argument(
88
+ default=Path("."), # default to current directory
89
+ exists=True,
90
+ file_okay=False,
91
+ dir_okay=True,
92
+ help="Target git repository (defaults to current directory)",
93
+ ),
94
+ ):
95
+ """Validate Rhiza template configuration.
96
+
97
+ Validates the .github/template.yml file to ensure it is syntactically
98
+ correct and semantically valid. Performs comprehensive validation:
99
+
100
+ - Checks if template.yml exists
101
+ - Validates YAML syntax
102
+ - Verifies required fields are present (template-repository, include)
103
+ - Validates field types and formats
104
+ - Ensures repository name follows owner/repo format
105
+ - Confirms include paths are not empty
106
+
107
+ Returns exit code 0 on success, 1 on validation failure.
108
+
109
+ Examples:
110
+ rhiza validate
111
+ rhiza validate /path/to/project
112
+ rhiza validate ..
46
113
  """
47
- inject_cmd(target, branch, force)
114
+ if not validate_cmd(target):
115
+ raise typer.Exit(code=1)
rhiza/commands/init.py ADDED
@@ -0,0 +1,66 @@
1
+ """Command to initialize or validate .github/template.yml.
2
+
3
+ This module provides the init command that creates or validates the
4
+ .github/template.yml file, which defines where templates come from
5
+ and what paths are governed by Rhiza.
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ from loguru import logger
11
+
12
+ from rhiza.commands.validate import validate
13
+ from rhiza.models import RhizaTemplate
14
+
15
+
16
+ def init(target: Path):
17
+ """Initialize or validate .github/template.yml in the target repository.
18
+
19
+ Creates a default .github/template.yml file if it doesn't exist,
20
+ or validates an existing one.
21
+
22
+ Parameters
23
+ ----------
24
+ target:
25
+ Path to the target directory. Defaults to the current working directory.
26
+ """
27
+ # Convert to absolute path to avoid surprises
28
+ target = target.resolve()
29
+
30
+ logger.info(f"Initializing Rhiza configuration in: {target}")
31
+
32
+ # Create .github directory if it doesn't exist
33
+ github_dir = target / ".github"
34
+ github_dir.mkdir(parents=True, exist_ok=True)
35
+
36
+ # Define the template file path
37
+ template_file = github_dir / "template.yml"
38
+
39
+ if not template_file.exists():
40
+ # Create default template.yml
41
+ logger.info("Creating default .github/template.yml")
42
+
43
+ default_template = RhizaTemplate(
44
+ template_repository="jebel-quant/rhiza",
45
+ template_branch="main",
46
+ include=[
47
+ ".github",
48
+ ".editorconfig",
49
+ ".gitignore",
50
+ ".pre-commit-config.yaml",
51
+ "Makefile",
52
+ "pytest.ini",
53
+ ],
54
+ )
55
+
56
+ default_template.to_yaml(template_file)
57
+
58
+ logger.success("✓ Created .github/template.yml")
59
+ logger.info("""
60
+ Next steps:
61
+ 1. Review and customize .github/template.yml to match your project needs
62
+ 2. Run 'rhiza materialize' to inject templates into your repository
63
+ """)
64
+
65
+ # the template file exists, so validate it
66
+ validate(target)
@@ -12,9 +12,11 @@ import sys
12
12
  import tempfile
13
13
  from pathlib import Path
14
14
 
15
- import yaml
16
15
  from loguru import logger
17
16
 
17
+ from rhiza.commands.init import init
18
+ from rhiza.models import RhizaTemplate
19
+
18
20
 
19
21
  def expand_paths(base_dir: Path, paths: list[str]) -> list[Path]:
20
22
  """Expand files/directories relative to base_dir into a flat list of files.
@@ -35,16 +37,11 @@ def expand_paths(base_dir: Path, paths: list[str]) -> list[Path]:
35
37
  return all_files
36
38
 
37
39
 
38
- def inject(target: Path, branch: str, force: bool):
40
+ def materialize(target: Path, branch: str, force: bool):
39
41
  """Materialize rhiza templates into TARGET repository."""
40
42
  # Convert to absolute path to avoid surprises
41
43
  target = target.resolve()
42
44
 
43
- # Validate target is a git repository
44
- if not (target / ".git").is_dir():
45
- logger.error(f"Target directory is not a git repository: {target}")
46
- raise sys.exit(1)
47
-
48
45
  logger.info(f"Target repository: {target}")
49
46
  logger.info(f"Rhiza branch: {branch}")
50
47
 
@@ -52,38 +49,21 @@ def inject(target: Path, branch: str, force: bool):
52
49
  # Ensure template.yml
53
50
  # -----------------------
54
51
  template_file = target / ".github" / "template.yml"
55
- template_file.parent.mkdir(parents=True, exist_ok=True)
56
-
57
- if not template_file.exists():
58
- logger.info("Creating default .github/template.yml")
59
- template_content = {
60
- "template-repository": "jebel-quant/rhiza",
61
- "template-branch": branch,
62
- "include": [
63
- ".github",
64
- ".editorconfig",
65
- ".gitignore",
66
- ".pre-commit-config.yaml",
67
- "Makefile",
68
- "pytest.ini",
69
- ],
70
- }
71
- with open(template_file, "w") as f:
72
- yaml.dump(template_content, f)
73
- logger.success(".github/template.yml created")
74
- else:
75
- logger.info("Using existing .github/template.yml")
52
+ # template_file.parent.mkdir(parents=True, exist_ok=True)
53
+
54
+ # Initialize rhiza if not already initialized, e.g. construct a template.yml file
55
+ init(target)
76
56
 
77
57
  # -----------------------
78
58
  # Load template.yml
79
59
  # -----------------------
80
- with open(template_file) as f:
81
- config = yaml.safe_load(f)
60
+ template = RhizaTemplate.from_yaml(template_file)
82
61
 
83
- rhiza_repo = config.get("template-repository")
84
- rhiza_branch = config.get("template-branch", branch)
85
- include_paths = config.get("include", [])
86
- excluded_paths = config.get("exclude", [])
62
+ rhiza_repo = template.template_repository
63
+ # Use template branch if specified, otherwise fall back to CLI parameter
64
+ rhiza_branch = template.template_branch if template.template_branch else branch
65
+ include_paths = template.include
66
+ excluded_paths = template.exclude
87
67
 
88
68
  if not include_paths:
89
69
  logger.error("No include paths found in template.yml")
@@ -0,0 +1,140 @@
1
+ """Command for validating Rhiza template configuration.
2
+
3
+ This module provides functionality to validate .github/template.yml files
4
+ to ensure they are syntactically correct and semantically valid.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ import yaml
10
+ from loguru import logger
11
+
12
+
13
+ def validate(target: Path) -> bool:
14
+ """Validate template.yml configuration in the target repository.
15
+
16
+ Performs authoritative validation of the template configuration:
17
+ - Checks if template.yml exists
18
+ - Validates YAML syntax
19
+ - Validates required fields
20
+ - Validates field values are appropriate
21
+
22
+ Parameters
23
+ ----------
24
+ target:
25
+ Path to the target Git repository directory.
26
+
27
+ Returns:
28
+ -------
29
+ bool
30
+ True if validation passes, False otherwise.
31
+ """
32
+ # Convert to absolute path
33
+ target = target.resolve()
34
+
35
+ # Check if target is a git repository
36
+ if not (target / ".git").is_dir():
37
+ logger.error(f"Target directory is not a git repository: {target}")
38
+ return False
39
+
40
+ logger.info(f"Validating template configuration in: {target}")
41
+
42
+ # Check if template.yml exists
43
+ template_file = target / ".github" / "template.yml"
44
+ if not template_file.exists():
45
+ logger.error(f"Template file not found: {template_file}")
46
+ logger.info("Run 'rhiza materialize' or 'rhiza inject' to create a default template.yml")
47
+ return False
48
+
49
+ logger.success(f"Found template file: {template_file}")
50
+
51
+ # Validate YAML syntax
52
+ try:
53
+ with open(template_file) as f:
54
+ config = yaml.safe_load(f)
55
+ except yaml.YAMLError as e:
56
+ logger.error(f"Invalid YAML syntax in template.yml: {e}")
57
+ return False
58
+
59
+ if config is None:
60
+ logger.error("template.yml is empty")
61
+ return False
62
+
63
+ logger.success("YAML syntax is valid")
64
+
65
+ # Validate required fields
66
+ required_fields = {
67
+ "template-repository": str,
68
+ "include": list,
69
+ }
70
+
71
+ validation_passed = True
72
+
73
+ for field, expected_type in required_fields.items():
74
+ if field not in config:
75
+ logger.error(f"Missing required field: {field}")
76
+ validation_passed = False
77
+ elif not isinstance(config[field], expected_type):
78
+ logger.error(
79
+ f"Field '{field}' must be of type {expected_type.__name__}, got {type(config[field]).__name__}"
80
+ )
81
+ validation_passed = False
82
+ else:
83
+ logger.success(f"Field '{field}' is present and valid")
84
+
85
+ # Validate template-repository format
86
+ if "template-repository" in config:
87
+ repo = config["template-repository"]
88
+ if not isinstance(repo, str):
89
+ logger.error(f"template-repository must be a string, got {type(repo).__name__}")
90
+ validation_passed = False
91
+ elif "/" not in repo:
92
+ logger.error(f"template-repository must be in format 'owner/repo', got: {repo}")
93
+ validation_passed = False
94
+ else:
95
+ logger.success(f"template-repository format is valid: {repo}")
96
+
97
+ # Validate include paths
98
+ if "include" in config:
99
+ include = config["include"]
100
+ if not isinstance(include, list):
101
+ logger.error(f"include must be a list, got {type(include).__name__}")
102
+ validation_passed = False
103
+ elif len(include) == 0:
104
+ logger.error("include list cannot be empty")
105
+ validation_passed = False
106
+ else:
107
+ logger.success(f"include list has {len(include)} path(s)")
108
+ for path in include:
109
+ if not isinstance(path, str):
110
+ logger.warning(f"include path should be a string, got {type(path).__name__}: {path}")
111
+ else:
112
+ logger.info(f" - {path}")
113
+
114
+ # Validate optional fields
115
+ if "template-branch" in config:
116
+ branch = config["template-branch"]
117
+ if not isinstance(branch, str):
118
+ logger.warning(f"template-branch should be a string, got {type(branch).__name__}: {branch}")
119
+ else:
120
+ logger.success(f"template-branch is valid: {branch}")
121
+
122
+ if "exclude" in config:
123
+ exclude = config["exclude"]
124
+ if not isinstance(exclude, list):
125
+ logger.warning(f"exclude should be a list, got {type(exclude).__name__}")
126
+ else:
127
+ logger.success(f"exclude list has {len(exclude)} path(s)")
128
+ for path in exclude:
129
+ if not isinstance(path, str):
130
+ logger.warning(f"exclude path should be a string, got {type(path).__name__}: {path}")
131
+ else:
132
+ logger.info(f" - {path}")
133
+
134
+ # Final verdict
135
+ if validation_passed:
136
+ logger.success("✓ Validation passed: template.yml is valid")
137
+ return True
138
+ else:
139
+ logger.error("✗ Validation failed: template.yml has errors")
140
+ return False
rhiza/models.py ADDED
@@ -0,0 +1,103 @@
1
+ """Data models for Rhiza configuration.
2
+
3
+ This module defines dataclasses that represent the structure of Rhiza
4
+ configuration files, making it easier to work with them without frequent
5
+ YAML parsing.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+
11
+ import yaml
12
+
13
+
14
+ @dataclass
15
+ class RhizaTemplate:
16
+ """Represents the structure of .github/template.yml.
17
+
18
+ Attributes:
19
+ ----------
20
+ template_repository : str | None
21
+ The GitHub repository containing templates (e.g., "jebel-quant/rhiza").
22
+ Can be None if not specified in the template file.
23
+ template_branch : str | None
24
+ The branch to use from the template repository.
25
+ Can be None if not specified in the template file (defaults to "main" when creating).
26
+ include : list[str]
27
+ List of paths to include from the template repository.
28
+ exclude : list[str]
29
+ List of paths to exclude from the template repository (default: empty list).
30
+ """
31
+
32
+ template_repository: str | None = None
33
+ template_branch: str | None = None
34
+ include: list[str] = field(default_factory=list)
35
+ exclude: list[str] = field(default_factory=list)
36
+
37
+ @classmethod
38
+ def from_yaml(cls, file_path: Path) -> "RhizaTemplate":
39
+ """Load RhizaTemplate from a YAML file.
40
+
41
+ Parameters
42
+ ----------
43
+ file_path : Path
44
+ Path to the template.yml file.
45
+
46
+ Returns:
47
+ -------
48
+ RhizaTemplate
49
+ The loaded template configuration.
50
+
51
+ Raises:
52
+ ------
53
+ FileNotFoundError
54
+ If the file does not exist.
55
+ yaml.YAMLError
56
+ If the YAML is malformed.
57
+ ValueError
58
+ If the file is empty.
59
+ """
60
+ with open(file_path) as f:
61
+ config = yaml.safe_load(f)
62
+
63
+ if not config:
64
+ raise ValueError("Template file is empty")
65
+
66
+ return cls(
67
+ template_repository=config.get("template-repository"),
68
+ template_branch=config.get("template-branch"),
69
+ include=config.get("include", []),
70
+ exclude=config.get("exclude", []),
71
+ )
72
+
73
+ def to_yaml(self, file_path: Path) -> None:
74
+ """Save RhizaTemplate to a YAML file.
75
+
76
+ Parameters
77
+ ----------
78
+ file_path : Path
79
+ Path where the template.yml file should be saved.
80
+ """
81
+ # Ensure parent directory exists
82
+ file_path.parent.mkdir(parents=True, exist_ok=True)
83
+
84
+ # Convert to dictionary with YAML-compatible keys
85
+ config = {}
86
+
87
+ # Only include template-repository if it's not None
88
+ if self.template_repository:
89
+ config["template-repository"] = self.template_repository
90
+
91
+ # Only include template-branch if it's not None
92
+ if self.template_branch:
93
+ config["template-branch"] = self.template_branch
94
+
95
+ # Include is always present as it's a required field for the config to be useful
96
+ config["include"] = self.include
97
+
98
+ # Only include exclude if it's not empty
99
+ if self.exclude:
100
+ config["exclude"] = self.exclude
101
+
102
+ with open(file_path, "w") as f:
103
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
@@ -0,0 +1,773 @@
1
+ Metadata-Version: 2.4
2
+ Name: rhiza
3
+ Version: 0.5.0
4
+ Summary: Reusable configuration templates for modern Python projects
5
+ Project-URL: Homepage, https://github.com/jebel-quant/rhiza-cli
6
+ Project-URL: Repository, https://github.com/jebel-quant/rhiza-cli
7
+ Project-URL: Issues, https://github.com/jebel-quant/rhiza/issues-cli
8
+ Author: Thomas Schmelzer
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ci,configuration,ruff,taskfile,templates
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Software Development :: Build Tools
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: loguru>=0.7.3
23
+ Requires-Dist: pyyaml==6.0.3
24
+ Requires-Dist: typer>=0.20.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: marimo==0.18.4; extra == 'dev'
27
+ Requires-Dist: pdoc>=16.0.0; extra == 'dev'
28
+ Requires-Dist: pre-commit==4.5.0; extra == 'dev'
29
+ Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
30
+ Requires-Dist: pytest-html>=4.1.1; extra == 'dev'
31
+ Requires-Dist: pytest==9.0.2; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # rhiza-cli
35
+
36
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
+
39
+ Command-line interface for managing reusable configuration templates for modern Python projects.
40
+
41
+ ## Overview
42
+
43
+ Rhiza is a CLI tool that helps you maintain consistent configuration across multiple Python projects by using templates stored in a central repository. It allows you to:
44
+
45
+ - Initialize projects with standard configuration templates
46
+ - Materialize (inject) templates into target repositories
47
+ - Validate template configurations
48
+ - Keep project configurations synchronized with template repositories
49
+
50
+ ## Table of Contents
51
+
52
+ - [Overview](#overview)
53
+ - [Installation](#installation)
54
+ - [Quick Start](#quick-start)
55
+ - [Commands](#commands)
56
+ - [init](#rhiza-init)
57
+ - [materialize](#rhiza-materialize)
58
+ - [validate](#rhiza-validate)
59
+ - [Configuration](#configuration)
60
+ - [Examples](#examples)
61
+ - [Development](#development)
62
+ - [Additional Documentation](#additional-documentation)
63
+
64
+ ## Additional Documentation
65
+
66
+ For more detailed information, see:
67
+
68
+ - **[CLI Quick Reference](CLI.md)** - Command syntax and quick examples
69
+ - **[Usage Guide](USAGE.md)** - Practical tutorials and workflows
70
+ - **[Contributing Guidelines](CONTRIBUTING.md)** - How to contribute to the project
71
+ - **[Code of Conduct](CODE_OF_CONDUCT.md)** - Community guidelines
72
+
73
+ ## Installation
74
+
75
+ ### Using pip
76
+
77
+ ```bash
78
+ pip install rhiza
79
+ ```
80
+
81
+ ### From source
82
+
83
+ ```bash
84
+ git clone https://github.com/jebel-quant/rhiza-cli.git
85
+ cd rhiza-cli
86
+ pip install -e .
87
+ ```
88
+
89
+ ### Using uv (recommended for development)
90
+
91
+ ```bash
92
+ git clone https://github.com/jebel-quant/rhiza-cli.git
93
+ cd rhiza-cli
94
+ make install
95
+ ```
96
+
97
+ ### Verify installation
98
+
99
+ ```bash
100
+ rhiza --help
101
+ ```
102
+
103
+ ## Quick Start
104
+
105
+ 1. **Initialize a project with Rhiza templates:**
106
+
107
+ ```bash
108
+ cd your-project
109
+ rhiza init
110
+ ```
111
+
112
+ This creates a `.github/template.yml` file with default configuration.
113
+
114
+ 2. **Customize the template configuration:**
115
+
116
+ Edit `.github/template.yml` to specify which files/directories to include from your template repository.
117
+
118
+ 3. **Materialize templates into your project:**
119
+
120
+ ```bash
121
+ rhiza materialize
122
+ ```
123
+
124
+ This fetches and copies template files into your project.
125
+
126
+ 4. **Validate your configuration:**
127
+
128
+ ```bash
129
+ rhiza validate
130
+ ```
131
+
132
+ This checks that your `.github/template.yml` is correctly formatted and valid.
133
+
134
+ ## Commands
135
+
136
+ ### `rhiza init`
137
+
138
+ Initialize or validate `.github/template.yml` in a target directory.
139
+
140
+ **Usage:**
141
+
142
+ ```bash
143
+ rhiza init [TARGET]
144
+ ```
145
+
146
+ **Arguments:**
147
+
148
+ - `TARGET` - Target directory (defaults to current directory)
149
+
150
+ **Description:**
151
+
152
+ Creates a default `.github/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`.
153
+
154
+ **Examples:**
155
+
156
+ ```bash
157
+ # Initialize in current directory
158
+ rhiza init
159
+
160
+ # Initialize in a specific directory
161
+ rhiza init /path/to/project
162
+
163
+ # Initialize in parent directory
164
+ rhiza init ..
165
+ ```
166
+
167
+ **Output:**
168
+
169
+ When creating a new template file:
170
+ ```
171
+ [INFO] Initializing Rhiza configuration in: /path/to/project
172
+ [INFO] Creating default .github/template.yml
173
+ ✓ Created .github/template.yml
174
+
175
+ Next steps:
176
+ 1. Review and customize .github/template.yml to match your project needs
177
+ 2. Run 'rhiza materialize' to inject templates into your repository
178
+ ```
179
+
180
+ When validating an existing file:
181
+ ```
182
+ [INFO] Validating template configuration in: /path/to/project
183
+ ✓ Found template file: /path/to/project/.github/template.yml
184
+ ✓ YAML syntax is valid
185
+ ✓ Field 'template-repository' is present and valid
186
+ ✓ Field 'include' is present and valid
187
+ ✓ template-repository format is valid: jebel-quant/rhiza
188
+ ✓ include list has 6 path(s)
189
+ ✓ Validation passed: template.yml is valid
190
+ ```
191
+
192
+ ---
193
+
194
+ ### `rhiza materialize`
195
+
196
+ Inject Rhiza configuration templates into a target repository.
197
+
198
+ **Usage:**
199
+
200
+ ```bash
201
+ rhiza materialize [OPTIONS] [TARGET]
202
+ ```
203
+
204
+ **Arguments:**
205
+
206
+ - `TARGET` - Target git repository directory (defaults to current directory)
207
+
208
+ **Options:**
209
+
210
+ - `--branch, -b TEXT` - Rhiza branch to use [default: main]
211
+ - `--force, -y` - Overwrite existing files without prompting
212
+ - `--help` - Show help message and exit
213
+
214
+ **Description:**
215
+
216
+ Materializes template files from the configured template repository into your target project. This command:
217
+
218
+ 1. Reads the `.github/template.yml` configuration
219
+ 2. Performs a sparse clone of the template repository
220
+ 3. Copies specified files/directories to your project
221
+ 4. Respects exclusion patterns defined in the configuration
222
+
223
+ **Examples:**
224
+
225
+ ```bash
226
+ # Materialize templates in current directory
227
+ rhiza materialize
228
+
229
+ # Materialize templates from a specific branch
230
+ rhiza materialize --branch develop
231
+
232
+ # Materialize and overwrite existing files
233
+ rhiza materialize --force
234
+
235
+ # Materialize in a specific directory with custom branch
236
+ rhiza materialize /path/to/project --branch v2.0 --force
237
+
238
+ # Short form with all options
239
+ rhiza materialize -b main -y
240
+ ```
241
+
242
+ **Output:**
243
+
244
+ ```
245
+ [INFO] Target repository: /path/to/project
246
+ [INFO] Rhiza branch: main
247
+ [INFO] Initializing Rhiza configuration in: /path/to/project
248
+ [INFO] Include paths:
249
+ - .github
250
+ - .editorconfig
251
+ - .gitignore
252
+ - .pre-commit-config.yaml
253
+ - Makefile
254
+ - pytest.ini
255
+ [INFO] Cloning jebel-quant/rhiza@main into temporary directory
256
+ [ADD] .github/workflows/ci.yml
257
+ [ADD] .editorconfig
258
+ [ADD] .gitignore
259
+ [ADD] Makefile
260
+ ✓ Rhiza templates materialized successfully
261
+
262
+ Next steps:
263
+ 1. Review changes:
264
+ git status
265
+ git diff
266
+
267
+ 2. Commit:
268
+ git add .
269
+ git commit -m "chore: import rhiza templates"
270
+
271
+ This is a one-shot snapshot.
272
+ Re-run this script to update templates explicitly.
273
+ ```
274
+
275
+ **Notes:**
276
+
277
+ - Files that already exist will not be overwritten unless `--force` is used
278
+ - The command performs a sparse clone for efficiency
279
+ - Template files are copied with their original permissions
280
+ - Excluded paths (if defined) are filtered out
281
+
282
+ ---
283
+
284
+ ### `rhiza validate`
285
+
286
+ Validate Rhiza template configuration.
287
+
288
+ **Usage:**
289
+
290
+ ```bash
291
+ rhiza validate [TARGET]
292
+ ```
293
+
294
+ **Arguments:**
295
+
296
+ - `TARGET` - Target git repository directory (defaults to current directory)
297
+
298
+ **Description:**
299
+
300
+ Validates the `.github/template.yml` file to ensure it is syntactically correct and semantically valid. This performs authoritative validation including:
301
+
302
+ - Checking if the file exists
303
+ - Validating YAML syntax
304
+ - Verifying required fields are present
305
+ - Checking field types and formats
306
+ - Validating repository name format
307
+ - Ensuring include paths are not empty
308
+
309
+ **Examples:**
310
+
311
+ ```bash
312
+ # Validate configuration in current directory
313
+ rhiza validate
314
+
315
+ # Validate configuration in a specific directory
316
+ rhiza validate /path/to/project
317
+
318
+ # Validate parent directory
319
+ rhiza validate ..
320
+ ```
321
+
322
+ **Exit codes:**
323
+
324
+ - `0` - Validation passed
325
+ - `1` - Validation failed
326
+
327
+ **Output (success):**
328
+
329
+ ```
330
+ [INFO] Validating template configuration in: /path/to/project
331
+ ✓ Found template file: /path/to/project/.github/template.yml
332
+ ✓ YAML syntax is valid
333
+ ✓ Field 'template-repository' is present and valid
334
+ ✓ Field 'include' is present and valid
335
+ ✓ template-repository format is valid: jebel-quant/rhiza
336
+ ✓ include list has 6 path(s)
337
+ - .github
338
+ - .editorconfig
339
+ - .gitignore
340
+ - .pre-commit-config.yaml
341
+ - Makefile
342
+ - pytest.ini
343
+ ✓ Validation passed: template.yml is valid
344
+ ```
345
+
346
+ **Output (failure):**
347
+
348
+ ```
349
+ [ERROR] Target directory is not a git repository: /path/to/project
350
+ ```
351
+
352
+ or
353
+
354
+ ```
355
+ [ERROR] Template file not found: /path/to/project/.github/template.yml
356
+ [INFO] Run 'rhiza materialize' or 'rhiza init' to create a default template.yml
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Configuration
362
+
363
+ Rhiza uses a `.github/template.yml` file to define template sources and what to include in your project.
364
+
365
+ ### Configuration File Format
366
+
367
+ The `template.yml` file uses YAML format with the following structure:
368
+
369
+ ```yaml
370
+ # Required: GitHub repository containing templates (format: owner/repo)
371
+ template-repository: jebel-quant/rhiza
372
+
373
+ # Optional: Branch to use from template repository (default: main)
374
+ template-branch: main
375
+
376
+ # Required: List of paths to include from template repository
377
+ include:
378
+ - .github
379
+ - .editorconfig
380
+ - .gitignore
381
+ - .pre-commit-config.yaml
382
+ - Makefile
383
+ - pytest.ini
384
+ - ruff.toml
385
+
386
+ # Optional: List of paths to exclude (filters out from included paths)
387
+ exclude:
388
+ - .github/workflows/specific-workflow.yml
389
+ - .github/CODEOWNERS
390
+ ```
391
+
392
+ ### Configuration Fields
393
+
394
+ #### `template-repository` (required)
395
+
396
+ - **Type:** String
397
+ - **Format:** `owner/repository`
398
+ - **Description:** GitHub repository containing your configuration templates
399
+ - **Example:** `jebel-quant/rhiza`, `myorg/python-templates`
400
+
401
+ #### `template-branch` (optional)
402
+
403
+ - **Type:** String
404
+ - **Default:** `main`
405
+ - **Description:** Git branch to use when fetching templates
406
+ - **Example:** `main`, `develop`, `v2.0`
407
+
408
+ #### `include` (required)
409
+
410
+ - **Type:** List of strings
411
+ - **Description:** Paths (files or directories) to copy from the template repository
412
+ - **Notes:**
413
+ - Paths are relative to the repository root
414
+ - Can include both files and directories
415
+ - Directories are recursively copied
416
+ - Must contain at least one path
417
+
418
+ **Example:**
419
+ ```yaml
420
+ include:
421
+ - .github # Entire directory
422
+ - .editorconfig # Single file
423
+ - src/config # Subdirectory
424
+ ```
425
+
426
+ #### `exclude` (optional)
427
+
428
+ - **Type:** List of strings
429
+ - **Description:** Paths to exclude from the included set
430
+ - **Notes:**
431
+ - Useful for excluding specific files from broader directory includes
432
+ - Paths are relative to the repository root
433
+
434
+ **Example:**
435
+ ```yaml
436
+ exclude:
437
+ - .github/workflows/deploy.yml # Exclude specific workflow
438
+ - .github/CODEOWNERS # Exclude specific file
439
+ ```
440
+
441
+ ### Complete Configuration Example
442
+
443
+ ```yaml
444
+ template-repository: jebel-quant/rhiza
445
+ template-branch: main
446
+ include:
447
+ - .github
448
+ - .editorconfig
449
+ - .gitignore
450
+ - .pre-commit-config.yaml
451
+ - CODE_OF_CONDUCT.md
452
+ - CONTRIBUTING.md
453
+ - Makefile
454
+ - pytest.ini
455
+ - ruff.toml
456
+ exclude:
457
+ - .github/workflows/release.yml
458
+ - .github/CODEOWNERS
459
+ ```
460
+
461
+ ## Examples
462
+
463
+ ### Example 1: Setting up a new Python project
464
+
465
+ ```bash
466
+ # Create a new project directory
467
+ mkdir my-python-project
468
+ cd my-python-project
469
+
470
+ # Initialize git
471
+ git init
472
+
473
+ # Initialize Rhiza
474
+ rhiza init
475
+
476
+ # Review the generated template.yml
477
+ cat .github/template.yml
478
+
479
+ # Materialize templates
480
+ rhiza materialize
481
+
482
+ # Review the imported files
483
+ git status
484
+
485
+ # Commit the changes
486
+ git add .
487
+ git commit -m "chore: initialize project with rhiza templates"
488
+ ```
489
+
490
+ ### Example 2: Updating existing project templates
491
+
492
+ ```bash
493
+ # Navigate to your project
494
+ cd existing-project
495
+
496
+ # Validate current configuration
497
+ rhiza validate
498
+
499
+ # Update templates (overwrite existing)
500
+ rhiza materialize --force
501
+
502
+ # Review changes
503
+ git diff
504
+
505
+ # Commit if satisfied
506
+ git add .
507
+ git commit -m "chore: update rhiza templates"
508
+ ```
509
+
510
+ ### Example 3: Using a custom template repository
511
+
512
+ Edit `.github/template.yml`:
513
+
514
+ ```yaml
515
+ template-repository: myorg/my-templates
516
+ template-branch: production
517
+ include:
518
+ - .github/workflows
519
+ - pyproject.toml
520
+ - Makefile
521
+ - docker-compose.yml
522
+ exclude:
523
+ - .github/workflows/experimental.yml
524
+ ```
525
+
526
+ Then materialize:
527
+
528
+ ```bash
529
+ rhiza materialize --force
530
+ ```
531
+
532
+ ### Example 4: Validating before CI/CD
533
+
534
+ Add to your CI pipeline:
535
+
536
+ ```yaml
537
+ # .github/workflows/validate-rhiza.yml
538
+ name: Validate Rhiza Configuration
539
+
540
+ on: [push, pull_request]
541
+
542
+ jobs:
543
+ validate:
544
+ runs-on: ubuntu-latest
545
+ steps:
546
+ - uses: actions/checkout@v4
547
+ - uses: actions/setup-python@v5
548
+ with:
549
+ python-version: '3.11'
550
+ - name: Install rhiza
551
+ run: pip install rhiza
552
+ - name: Validate configuration
553
+ run: rhiza validate
554
+ ```
555
+
556
+ ## Development
557
+
558
+ ### Prerequisites
559
+
560
+ - Python 3.11 or higher
561
+ - `uv` package manager (recommended) or `pip`
562
+ - Git
563
+
564
+ ### Setup Development Environment
565
+
566
+ ```bash
567
+ # Clone the repository
568
+ git clone https://github.com/jebel-quant/rhiza-cli.git
569
+ cd rhiza-cli
570
+
571
+ # Install dependencies
572
+ make install
573
+
574
+ # Run tests
575
+ make test
576
+
577
+ # Run linters and formatters
578
+ make fmt
579
+
580
+ # Generate documentation
581
+ make docs
582
+ ```
583
+
584
+ ### Running Tests
585
+
586
+ ```bash
587
+ # Run all tests with coverage
588
+ make test
589
+
590
+ # Run specific test file
591
+ pytest tests/test_cli.py
592
+
593
+ # Run with verbose output
594
+ pytest -v
595
+ ```
596
+
597
+ ### Code Quality
598
+
599
+ The project uses:
600
+
601
+ - **Ruff** for linting and formatting
602
+ - **pytest** for testing
603
+ - **pre-commit** hooks for automated checks
604
+
605
+ ```bash
606
+ # Run all quality checks
607
+ make fmt
608
+
609
+ # Run dependency checks
610
+ make deptry
611
+ ```
612
+
613
+ ### Building Documentation
614
+
615
+ ```bash
616
+ # Generate API documentation
617
+ make docs
618
+
619
+ # Build complete documentation book
620
+ make book
621
+ ```
622
+
623
+ ## Makefile Targets
624
+
625
+ The project includes a comprehensive Makefile for common development tasks:
626
+
627
+ ```
628
+ Bootstrap
629
+ install-uv ensure uv/uvx is installed
630
+ install-extras run custom build script (if exists)
631
+ install install
632
+ clean clean
633
+
634
+ Development and Testing
635
+ test run all tests
636
+ marimo fire up Marimo server
637
+ marimushka export Marimo notebooks to HTML
638
+ deptry run deptry if pyproject.toml exists
639
+
640
+ Documentation
641
+ docs create documentation with pdoc
642
+ book compile the companion book
643
+ fmt check the pre-commit hooks and the linting
644
+ all Run everything
645
+
646
+ Releasing and Versioning
647
+ bump bump version
648
+ release create tag and push to remote with prompts
649
+ post-release perform post-release tasks
650
+
651
+ Meta
652
+ sync sync with template repository as defined in .github/template.yml
653
+ help Display this help message
654
+ customisations list available customisation scripts
655
+ update-readme update README.md with current Makefile help output
656
+ ```
657
+
658
+ Run `make help` to see this list in your terminal.
659
+
660
+ ## Contributing
661
+
662
+ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
663
+
664
+ ### Reporting Issues
665
+
666
+ If you find a bug or have a feature request, please open an issue on [GitHub](https://github.com/jebel-quant/rhiza-cli/issues).
667
+
668
+ ### Code of Conduct
669
+
670
+ This project follows a [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
671
+
672
+ ## License
673
+
674
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
675
+
676
+ ## Links
677
+
678
+ - **Repository:** https://github.com/jebel-quant/rhiza-cli
679
+ - **Issues:** https://github.com/jebel-quant/rhiza-cli/issues
680
+ - **Documentation:** Generated with `make docs`
681
+
682
+ ## Architecture
683
+
684
+ Rhiza follows a modular architecture:
685
+
686
+ ```
687
+ src/rhiza/
688
+ ├── __init__.py # Package initialization
689
+ ├── __main__.py # Entry point for python -m rhiza
690
+ ├── cli.py # Typer app and CLI command definitions
691
+ ├── models.py # Data models (RhizaTemplate)
692
+ └── commands/ # Command implementations
693
+ ├── __init__.py
694
+ ├── init.py # Initialize template.yml
695
+ ├── materialize.py # Materialize templates
696
+ └── validate.py # Validate configuration
697
+ ```
698
+
699
+ ### Design Principles
700
+
701
+ 1. **Thin CLI Layer:** Commands in `cli.py` are thin wrappers that delegate to implementations in `commands/`
702
+ 2. **Separation of Concerns:** Each command has its own module with focused functionality
703
+ 3. **Type Safety:** Uses `pathlib.Path` for file operations and Typer for type-checked CLI arguments
704
+ 4. **Clear Logging:** Uses `loguru` for structured, colored logging output
705
+ 5. **Validation First:** Always validates configuration before performing operations
706
+
707
+ ## Troubleshooting
708
+
709
+ ### Command not found: rhiza
710
+
711
+ Ensure the package is installed and your Python scripts directory is in your PATH:
712
+
713
+ ```bash
714
+ pip install --user rhiza
715
+ # Add ~/.local/bin to PATH if needed
716
+ export PATH="$HOME/.local/bin:$PATH"
717
+ ```
718
+
719
+ ### Template validation fails
720
+
721
+ Check that:
722
+ 1. Your `.github/template.yml` file exists
723
+ 2. The YAML syntax is valid
724
+ 3. Required fields (`template-repository` and `include`) are present
725
+ 4. The repository format is `owner/repo`
726
+
727
+ Run `rhiza validate` for detailed error messages.
728
+
729
+ ### Git clone fails during materialize
730
+
731
+ Ensure:
732
+ 1. The template repository exists and is accessible
733
+ 2. The specified branch exists
734
+ 3. You have network connectivity to GitHub
735
+ 4. The repository is public (or you have appropriate credentials configured)
736
+
737
+ ### Files not being copied
738
+
739
+ Check:
740
+ 1. The paths in `include` are correct relative to the template repository root
741
+ 2. The paths exist in the specified branch
742
+ 3. Any `exclude` patterns are not filtering out wanted files
743
+ 4. You're using `--force` if files already exist and need to be overwritten
744
+
745
+ ## FAQ
746
+
747
+ **Q: Can I use Rhiza with private template repositories?**
748
+
749
+ A: Yes, as long as you have Git credentials configured that allow access to the repository.
750
+
751
+ **Q: Does Rhiza support template repositories hosted outside GitHub?**
752
+
753
+ A: Currently, Rhiza is designed for GitHub repositories. Support for other Git hosting services could be added in the future.
754
+
755
+ **Q: Can I materialize templates from multiple repositories?**
756
+
757
+ A: Not directly. However, you can run `rhiza materialize` multiple times with different configurations, or combine templates manually.
758
+
759
+ **Q: What's the difference between `rhiza init` and `rhiza materialize`?**
760
+
761
+ A: `init` creates or validates the `.github/template.yml` configuration file. `materialize` reads that configuration and actually copies the template files into your project.
762
+
763
+ **Q: How do I update my project's templates?**
764
+
765
+ A: Simply run `rhiza materialize --force` to fetch and overwrite with the latest versions from your template repository.
766
+
767
+ **Q: Can I customize which files are included?**
768
+
769
+ A: Yes, edit the `include` and `exclude` lists in `.github/template.yml` to control exactly which files are copied.
770
+
771
+ ## Acknowledgments
772
+
773
+ Rhiza is developed and maintained by the Jebel Quant team as part of their effort to standardize Python project configurations across their portfolio.
@@ -0,0 +1,13 @@
1
+ rhiza/__init__.py,sha256=fxaeT_K8bQAX5qt1DtRKWmyKpk7ABLomxhzZwL6Rml8,259
2
+ rhiza/__main__.py,sha256=Lx0GqVZo6ymm0f18_uYB6E7_SOWwJNYjb73Vr31oLoM,236
3
+ rhiza/cli.py,sha256=P130eveGtBUTby3A5LNDvgtJ2ekWcAiv84GqZ5kLcw4,3484
4
+ rhiza/models.py,sha256=HbWgHPS-sWur4ax7a8tu2B6apr6YEdbGoxOpWxyeP9s,3220
5
+ rhiza/commands/__init__.py,sha256=X5ZRDDl37X8mEbiMWoqjTGlLhebkYhZ2SaLJd4KcHdw,166
6
+ rhiza/commands/init.py,sha256=wAVlcTdCiU8bN98Gsx8MGryo8fraFLQCvDl5ZlR1ySg,1953
7
+ rhiza/commands/materialize.py,sha256=G1pDraC2gqIFuqC9nmIeXPnAHMna2KBIPfPAuLDllec,4412
8
+ rhiza/commands/validate.py,sha256=rY04vz71C4ILAcLDaf4y4AtP3Bs5KcaIPCLoA074SA8,4874
9
+ rhiza-0.5.0.dist-info/METADATA,sha256=OG-KEbNrDEd802tVMSUacQKuUH8SPOFV8pVxUbrAM3U,19323
10
+ rhiza-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
11
+ rhiza-0.5.0.dist-info/entry_points.txt,sha256=NAwZUpbXvfKv50a_Qq-PxMHl3lcjAyZO63IBeuUNgfY,45
12
+ rhiza-0.5.0.dist-info/licenses/LICENSE,sha256=4m5X7LhqX-6D0Ks79Ys8CLpmza8cxDG34g4S9XSNAGY,1077
13
+ rhiza-0.5.0.dist-info/RECORD,,
rhiza/commands/hello.py DELETED
@@ -1,9 +0,0 @@
1
- """Small demo CLI entrypoint for Rhiza tools.
2
-
3
- This module provides a minimal example command that prints a greeting.
4
- """
5
-
6
-
7
- def hello():
8
- """Print a friendly greeting from Rhiza."""
9
- print("Hello from Rhiza!")
@@ -1,35 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: rhiza
3
- Version: 0.4.0
4
- Summary: Reusable configuration templates for modern Python projects
5
- Project-URL: Homepage, https://github.com/jebel-quant/rhiza-cli
6
- Project-URL: Repository, https://github.com/jebel-quant/rhiza-cli
7
- Project-URL: Issues, https://github.com/jebel-quant/rhiza/issues-cli
8
- Author: Thomas Schmelzer
9
- License: MIT
10
- License-File: LICENSE
11
- Keywords: ci,configuration,ruff,taskfile,templates
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Classifier: Programming Language :: Python :: 3.13
19
- Classifier: Programming Language :: Python :: 3.14
20
- Classifier: Topic :: Software Development :: Build Tools
21
- Requires-Python: >=3.11
22
- Requires-Dist: loguru>=0.7.3
23
- Requires-Dist: pyyaml==6.0.3
24
- Requires-Dist: typer>=0.20.0
25
- Provides-Extra: dev
26
- Requires-Dist: marimo==0.18.4; extra == 'dev'
27
- Requires-Dist: pdoc>=16.0.0; extra == 'dev'
28
- Requires-Dist: pre-commit==4.5.0; extra == 'dev'
29
- Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
30
- Requires-Dist: pytest-html>=4.1.1; extra == 'dev'
31
- Requires-Dist: pytest==9.0.2; extra == 'dev'
32
- Description-Content-Type: text/markdown
33
-
34
- # rhiza-cli
35
- Command line interface for Rhiza
@@ -1,11 +0,0 @@
1
- rhiza/__init__.py,sha256=wI9tfPnLnVJRkAZrjCmAOvU4cGn-2BOTN_MFZc0c0OQ,190
2
- rhiza/__main__.py,sha256=Lx0GqVZo6ymm0f18_uYB6E7_SOWwJNYjb73Vr31oLoM,236
3
- rhiza/cli.py,sha256=FCCqwWZMDRSeDm6GERvlXkck0RSxq3rzWqUh3Q0XVJk,1343
4
- rhiza/commands/__init__.py,sha256=X5ZRDDl37X8mEbiMWoqjTGlLhebkYhZ2SaLJd4KcHdw,166
5
- rhiza/commands/hello.py,sha256=_zhLoGLM21cm5XBYohwm5gbsNMpJ96ZxCcLwHrwhKVs,216
6
- rhiza/commands/inject.py,sha256=9MpS08TtOhjxIturzN2ejvEnrvGJUCGMjuS4JTuE2G4,5028
7
- rhiza-0.4.0.dist-info/METADATA,sha256=as92HkPHqiwg4HWlwGkR7CCCRdw5s8cLASHnP2luFek,1388
8
- rhiza-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
9
- rhiza-0.4.0.dist-info/entry_points.txt,sha256=NAwZUpbXvfKv50a_Qq-PxMHl3lcjAyZO63IBeuUNgfY,45
10
- rhiza-0.4.0.dist-info/licenses/LICENSE,sha256=4m5X7LhqX-6D0Ks79Ys8CLpmza8cxDG34g4S9XSNAGY,1077
11
- rhiza-0.4.0.dist-info/RECORD,,
File without changes