Skip to content

Commit 8838439

Browse files
author
colyerdeng
committed
feat: update project template and cli
1 parent 2ef98a7 commit 8838439

File tree

29 files changed

+478
-138
lines changed

29 files changed

+478
-138
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

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ 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"

repo_scaffold/cli.py

Lines changed: 155 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,183 @@
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 json
1828
import os
29+
from pathlib import Path
30+
from typing import Dict, Any
31+
import importlib.resources
1932

2033
import click
2134
from cookiecutter.main import cookiecutter
2235

2336

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

3290

3391
@cli.command()
92+
def list():
93+
"""List all available project templates.
94+
95+
Displays the title and description of each template to help users
96+
choose the appropriate template for their needs.
97+
98+
Example:
99+
```bash
100+
$ repo-scaffold list
101+
Available templates:
102+
103+
python - template-python
104+
Description: template for python project
105+
```
106+
"""
107+
templates = load_templates()
108+
click.echo("\nAvailable templates:")
109+
for name, info in templates.items():
110+
click.echo(f"\n{info['title']} - {name}")
111+
click.echo(f" Description: {info['description']}")
112+
113+
114+
@cli.command()
115+
@click.argument("template", required=False)
34116
@click.option(
35-
"--template",
36-
"-t",
37-
default="https://github.com/ShawnDen-coder/repo-scaffold.git",
38-
help="Cookiecutter template URL or path",
117+
"--output-dir",
118+
"-o",
119+
default=".",
120+
type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
121+
help="Directory where the project will be created",
39122
)
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
123+
def create(template: str, output_dir: Path):
124+
"""Create a new project from a template.
125+
126+
Creates a new project based on the specified template. If no template is specified,
127+
displays a list of available templates. The project generation process is interactive
128+
and will prompt for necessary configuration values.
129+
130+
Args:
131+
template: Template name or title (e.g., 'template-python' or 'python')
132+
output_dir: Target directory where the project will be created
133+
134+
Example:
135+
Create a Python project:
136+
```bash
137+
$ repo-scaffold create python
138+
```
139+
140+
Specify output directory:
141+
```bash
142+
$ repo-scaffold create python -o ./projects
143+
```
144+
145+
View available templates:
146+
```bash
147+
$ repo-scaffold list
148+
```
57149
"""
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)
150+
templates = load_templates()
151+
152+
# 如果没有指定模板,让 cookiecutter 处理模板选择
153+
if not template:
154+
click.echo("Please select a template to use:")
155+
for name, info in templates.items():
156+
click.echo(f" {info['title']} - {name}")
157+
click.echo(f" {info['description']}")
158+
return
159+
160+
# 查找模板配置
161+
template_info = None
162+
for name, info in templates.items():
163+
if name == template or info["title"] == template:
164+
template_info = info
165+
break
166+
167+
if not template_info:
168+
click.echo(f"Error: Template '{template}' not found")
169+
click.echo("\nAvailable templates:")
170+
for name, info in templates.items():
171+
click.echo(f" {info['title']} - {name}")
172+
return
173+
174+
# 使用模板创建项目
175+
template_path = get_package_path(os.path.join("templates", template_info["path"]))
176+
cookiecutter(
177+
template=template_path,
178+
output_dir=str(output_dir),
179+
no_input=False, # 启用交互式输入,让 cookiecutter 处理所有选项
180+
)
62181

63182

64183
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()

template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml renamed to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ on:
55
tags:
66
- '*.*.*'
77

8+
permissions:
9+
contents: write # 用于创建 GitHub Release
10+
811
jobs:
912
release-build:
1013
runs-on: ubuntu-latest
11-
permissions:
12-
contents: write # 用于创建 GitHub Release
1314
steps:
1415
- uses: actions/checkout@v4
1516

@@ -32,7 +33,11 @@ jobs:
3233
version: ">=0.4.0"
3334

3435
- name: Install dependencies
35-
run: uv sync --extra dev
36+
run: |
37+
uv sync --extra dev
38+
{% if cookiecutter.use_mkdocs == "yes" %}
39+
uv sync --extra docs
40+
{% endif %}
3641
3742
- name: Build and test
3843
run: |
@@ -45,12 +50,18 @@ jobs:
4550
UV_PUBLISH_TOKEN: {% raw %}${{ env.PYPI_TOKEN }}{% endraw %}
4651
run: uv publish
4752

53+
{% if cookiecutter.use_mkdocs == "yes" %}
54+
- name: Deploy documentation
55+
run: uv run mkdocs gh-deploy --force
56+
env:
57+
GITHUB_TOKEN: {% raw %}${{ env.PERSONAL_ACCESS_TOKEN }}{% endraw %}
58+
{% endif %}
59+
4860
- name: Release
4961
uses: softprops/action-gh-release@v2
5062
with:
5163
files: |
52-
dist/*.tar.gz
53-
dist/*.whl
64+
dist/*
5465
generate_release_notes: true
5566
env:
5667
GITHUB_TOKEN: {% raw %}${{ env.PERSONAL_ACCESS_TOKEN }}{% endraw %}

0 commit comments

Comments
 (0)