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 +4 -0
- rhiza/cli.py +86 -18
- rhiza/commands/init.py +66 -0
- rhiza/commands/{inject.py → materialize.py} +14 -34
- rhiza/commands/validate.py +140 -0
- rhiza/models.py +103 -0
- rhiza-0.5.0.dist-info/METADATA +773 -0
- rhiza-0.5.0.dist-info/RECORD +13 -0
- rhiza/commands/hello.py +0 -9
- rhiza-0.4.0.dist-info/METADATA +0 -35
- rhiza-0.4.0.dist-info/RECORD +0 -11
- {rhiza-0.4.0.dist-info → rhiza-0.5.0.dist-info}/WHEEL +0 -0
- {rhiza-0.4.0.dist-info → rhiza-0.5.0.dist-info}/entry_points.txt +0 -0
- {rhiza-0.4.0.dist-info → rhiza-0.5.0.dist-info}/licenses/LICENSE +0 -0
rhiza/__init__.py
CHANGED
rhiza/cli.py
CHANGED
|
@@ -8,16 +8,45 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
11
|
-
from rhiza.commands.
|
|
12
|
-
from rhiza.commands.
|
|
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(
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
58
|
-
|
|
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
|
-
|
|
81
|
-
config = yaml.safe_load(f)
|
|
60
|
+
template = RhizaTemplate.from_yaml(template_file)
|
|
82
61
|
|
|
83
|
-
rhiza_repo =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
[](https://www.python.org/downloads/)
|
|
37
|
+
[](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
rhiza-0.4.0.dist-info/METADATA
DELETED
|
@@ -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
|
rhiza-0.4.0.dist-info/RECORD
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|