Skip to content

Commit 4b21984

Browse files
colyerdengShawnDen-coder
authored andcommitted
feat: update project template and cli
feat: update template and cli tools feat: update template and update cli tools
1 parent 2ef98a7 commit 4b21984

30 files changed

+482
-142
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
/.zip/
88
/venv/
99
/dist/
10-
/__pycache__/
10+
**/__pycache__/
11+
*.pyc

noxfile.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ def lint(session: nox.Session) -> None:
5858
session.run(
5959
"ruff",
6060
"format",
61-
"--verbose",
62-
"--diff"
61+
"--verbose"
6362
)
6463

6564

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,17 @@ dev = [
2727
[project.scripts]
2828
repo-scaffold = "repo_scaffold.cli:cli"
2929

30+
[tool.setuptools.package-data]
31+
repo_scaffold = ["templates/**/*", "cookiecutter.json"]
32+
3033
[build-system]
3134
requires = ["hatchling"]
3235
build-backend = "hatchling.build"
3336

3437
[tool.ruff]
3538
line-length = 120
3639
include = ["pyproject.toml", "repo_scaffold/*.py"]
37-
exclude = ["template-python/**/*"]
40+
exclude = ["repo_scaffold/templates/**/*"]
3841

3942
[tool.ruff.lint]
4043
select = [

repo_scaffold/cli.py

Lines changed: 157 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,184 @@
1-
"""Command Line Interface module for project scaffolding.
1+
"""Repository scaffolding CLI tool.
22
3-
This module provides the CLI commands for creating new projects using cookiecutter
4-
templates. It serves as the main entry point for the repo_scaffold tool and handles
5-
all command-line interactions.
3+
This module provides a command-line interface for creating new projects from templates.
4+
It serves as the main entry point for the repo-scaffold tool.
65
7-
Typical usage example:
6+
Example:
7+
To use this module as a CLI tool:
88
9+
```bash
10+
# List available templates
11+
$ repo-scaffold list
12+
13+
# Create a new project
14+
$ repo-scaffold create python
15+
```
16+
17+
To use this module in your code:
18+
19+
```python
920
from repo_scaffold.cli import cli
1021
1122
if __name__ == '__main__':
1223
cli()
13-
14-
Attributes:
15-
cli: The main Click command group that serves as the entry point.
24+
```
1625
"""
1726

27+
import importlib.resources
28+
import json
1829
import os
30+
from pathlib import Path
31+
from typing import Any
32+
from typing import Dict
1933

2034
import click
2135
from cookiecutter.main import cookiecutter
2236

2337

38+
def get_package_path(relative_path: str) -> str:
39+
"""Get absolute path to a resource in the package.
40+
41+
Args:
42+
relative_path: Path relative to the package root
43+
44+
Returns:
45+
str: Absolute path to the resource
46+
""" # noqa: W293
47+
# 使用 files() 获取包资源
48+
package_files = importlib.resources.files("repo_scaffold")
49+
resource_path = package_files.joinpath(relative_path)
50+
if not (resource_path.is_file() or resource_path.is_dir()):
51+
raise FileNotFoundError(f"Resource not found: {relative_path}")
52+
return str(resource_path)
53+
54+
55+
def load_templates() -> Dict[str, Any]:
56+
"""Load available project templates configuration.
57+
58+
Reads template configurations from the cookiecutter.json file in the templates directory.
59+
Each template contains information about its name, path, title, and description.
60+
61+
Returns:
62+
Dict[str, Any]: Template configuration dictionary where keys are template names
63+
and values are template information:
64+
{
65+
"template-name": {
66+
"path": "relative/path",
67+
"title": "Template Title",
68+
"description": "Template description"
69+
}
70+
}
71+
72+
Raises:
73+
FileNotFoundError: If the configuration file doesn't exist
74+
json.JSONDecodeError: If the configuration file is not valid JSON
75+
"""
76+
config_path = get_package_path("templates/cookiecutter.json")
77+
with open(config_path, "r", encoding="utf-8") as f:
78+
config = json.load(f)
79+
return config["templates"]
80+
81+
2482
@click.group()
2583
def cli():
26-
"""Repository scaffolding CLI tool.
84+
"""Modern project scaffolding tool.
2785
28-
A tool for creating new projects from templates.
86+
Provides multiple project templates for quick project initialization.
87+
Use `repo-scaffold list` to view available templates,
88+
or `repo-scaffold create <template>` to create a new project.
2989
"""
30-
...
3190

3291

3392
@cli.command()
93+
def list():
94+
"""List all available project templates.
95+
96+
Displays the title and description of each template to help users
97+
choose the appropriate template for their needs.
98+
99+
Example:
100+
```bash
101+
$ repo-scaffold list
102+
Available templates:
103+
104+
python - template-python
105+
Description: template for python project
106+
```
107+
""" # noqa: W293
108+
templates = load_templates()
109+
click.echo("\nAvailable templates:")
110+
for name, info in templates.items():
111+
click.echo(f"\n{info['title']} - {name}")
112+
click.echo(f" Description: {info['description']}")
113+
114+
115+
@cli.command()
116+
@click.argument("template", required=False)
34117
@click.option(
35-
"--template",
36-
"-t",
37-
default="https://github.com/ShawnDen-coder/repo-scaffold.git",
38-
help="Cookiecutter template URL or path",
118+
"--output-dir",
119+
"-o",
120+
default=".",
121+
type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
122+
help="Directory where the project will be created",
39123
)
40-
@click.option("--output-dir", "-o", default=".", help="Where to output the generated project dir")
41-
@click.option("--local", "-l", is_flag=True, help="Use local template in ./template-python")
42-
def create(template, output_dir, local):
43-
r"""Create a new project from template.
44-
45-
\b
46-
Usage:
47-
repo-scaffold create [OPTIONS]
48-
49-
\b
50-
Examples:
51-
$ repo-scaffold create
52-
$ repo-scaffold create --local
53-
$ repo-scaffold create -o ./my-projects
54-
$ repo-scaffold create -t https://github.com/user/custom-template.git
55-
$ repo-scaffold create -t ../path/to/local/template
56-
$ repo-scaffold create -t gh:user/template-name
57-
"""
58-
if local:
59-
template = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
60-
61-
cookiecutter(template=template, output_dir=output_dir, no_input=False)
124+
def create(template: str, output_dir: Path):
125+
"""Create a new project from a template.
126+
127+
Creates a new project based on the specified template. If no template is specified,
128+
displays a list of available templates. The project generation process is interactive
129+
and will prompt for necessary configuration values.
130+
131+
Args:
132+
template: Template name or title (e.g., 'template-python' or 'python')
133+
output_dir: Target directory where the project will be created
134+
135+
Example:
136+
Create a Python project:
137+
```bash
138+
$ repo-scaffold create python
139+
```
140+
141+
Specify output directory:
142+
```bash
143+
$ repo-scaffold create python -o ./projects
144+
```
145+
146+
View available templates:
147+
```bash
148+
$ repo-scaffold list
149+
```
150+
""" # noqa: W293
151+
templates = load_templates()
152+
153+
# 如果没有指定模板,让 cookiecutter 处理模板选择
154+
if not template:
155+
click.echo("Please select a template to use:")
156+
for name, info in templates.items():
157+
click.echo(f" {info['title']} - {name}")
158+
click.echo(f" {info['description']}")
159+
return
160+
161+
# 查找模板配置
162+
template_info = None
163+
for name, info in templates.items():
164+
if name == template or info["title"] == template:
165+
template_info = info
166+
break
167+
168+
if not template_info:
169+
click.echo(f"Error: Template '{template}' not found")
170+
click.echo("\nAvailable templates:")
171+
for name, info in templates.items():
172+
click.echo(f" {info['title']} - {name}")
173+
return
174+
175+
# 使用模板创建项目
176+
template_path = get_package_path(os.path.join("templates", template_info["path"]))
177+
cookiecutter(
178+
template=template_path,
179+
output_dir=str(output_dir),
180+
no_input=False, # 启用交互式输入,让 cookiecutter 处理所有选项
181+
)
62182

63183

64184
if __name__ == "__main__":

cookiecutter.json renamed to repo_scaffold/templates/cookiecutter.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"templates": {
33
"template-python": {
4-
"path": "./template-python",
4+
"path": "template-python",
55
"title": "python",
66
"description": "template for python project"
77
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"repo_name": "my-python-project",
3+
"full_name": "Shawn Deng",
4+
"email": "[email protected]",
5+
"github_username": "ShawnDen-coder",
6+
"project_slug": "{{ cookiecutter.repo_name.strip().lower().replace('-', '_') }}",
7+
"description": "A short description of the project.",
8+
"min_python_version": ["3.8", "3.9", "3.10", "3.11"],
9+
"max_python_version": ["3.12"],
10+
"use_mkdocs": ["yes", "no"],
11+
"use_docker": ["yes", "no"],
12+
"include_cli": ["yes", "no"],
13+
"use_github_actions": ["yes", "no"],
14+
"__prompts__": {
15+
"repo_name": "项目名称 (使用 - 分隔, 例如: my-awesome-project)",
16+
"full_name": "你的全名",
17+
"email": "你的邮箱地址",
18+
"github_username": "你的 GitHub 用户名",
19+
"project_slug": "Python 包名 (用于 import)",
20+
"description": "项目简短描述",
21+
"min_python_version": "最低支持的 Python 版本 (这将影响测试范围和依赖兼容性)",
22+
"max_python_version": "最高支持的 Python 版本",
23+
"use_mkdocs": "是否使用 MkDocs 生成文档?",
24+
"use_docker": "是否添加 Docker 支持?",
25+
"include_cli": "是否添加命令行界面?",
26+
"use_github_actions": "是否添加 GitHub Actions 支持?"
27+
}
28+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import os
2+
import shutil
3+
import subprocess
4+
5+
6+
def remove_cli():
7+
"""Remove CLI related files if not needed."""
8+
cli_file = os.path.join("{{cookiecutter.project_slug}}", "cli.py")
9+
if os.path.exists(cli_file):
10+
os.remove(cli_file)
11+
12+
13+
def remove_github_actions():
14+
"""Remove GitHub Actions configuration if not needed."""
15+
github_dir = os.path.join("{{cookiecutter.project_slug}}", ".github")
16+
if os.path.exists(github_dir):
17+
shutil.rmtree(github_dir)
18+
19+
20+
def init_project_depends():
21+
"""Initialize project dependencies using uv."""
22+
project_dir = os.path.abspath("{{cookiecutter.project_slug}}")
23+
os.chdir(project_dir)
24+
25+
# 安装基础开发依赖
26+
subprocess.run(["uv", "sync", "--extra", "dev"], check=True)
27+
28+
# 如果启用了文档功能,安装文档依赖
29+
if "{{cookiecutter.use_mkdocs}}" == "yes":
30+
subprocess.run(["uv", "sync", "--extra", "docs"], check=True)
31+
32+
33+
if __name__ == "__main__":
34+
if "{{cookiecutter.include_cli}}" == "no":
35+
remove_cli()
36+
37+
if "{{cookiecutter.use_github_actions}}" == "no":
38+
remove_github_actions()
39+
40+
init_project_depends()

0 commit comments

Comments
 (0)