diff --git a/.github/workflows/bump_version.yaml b/.github/workflows/bump_version.yaml index 70ed123..de2b7c8 100644 --- a/.github/workflows/bump_version.yaml +++ b/.github/workflows/bump_version.yaml @@ -4,7 +4,22 @@ on: push: branches: - master + - main workflow_dispatch: + inputs: + increment: + description: 'Version increment (major, minor, patch)' + required: false + type: choice + options: + - major + - minor + - patch + default: patch + +permissions: + contents: write # 用于创建和推送标签 + pull-requests: write # 用于创建 PR jobs: bump-version: @@ -12,22 +27,24 @@ jobs: runs-on: ubuntu-latest name: "Bump version and create changelog with commitizen" steps: - - name: Load secret - uses: 1password/load-secrets-action@v2 - with: - export-env: true - env: - OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - PERSONAL_ACCESS_TOKEN: op://shawndengdev/github_access_token/credential - - name: Check out uses: actions/checkout@v4 with: fetch-depth: 0 - token: '${{ env.PERSONAL_ACCESS_TOKEN }}' + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - name: Create bump and changelog + - id: cz + name: Create bump and changelog uses: commitizen-tools/commitizen-action@master with: - github_token: ${{ env.PERSONAL_ACCESS_TOKEN }} - branch: master + github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + changelog_increment_filename: body.md + increment: ${{ github.event.inputs.increment }} + + - name: Release + uses: softprops/action-gh-release@v1 + with: + body_path: body.md + tag_name: ${{ env.REVISION }} + env: + GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} diff --git a/.github/workflows/deploy_docs.yaml b/.github/workflows/deploy_docs.yaml new file mode 100644 index 0000000..d8a36ec --- /dev/null +++ b/.github/workflows/deploy_docs.yaml @@ -0,0 +1,28 @@ +name: Deploy Docs + +on: + push: + tags: + - '*' # 匹配所有标签 + workflow_dispatch: # 保留手动触发选项 + +permissions: + contents: write # 用于部署到 GitHub Pages + +jobs: + setup: + uses: ./.github/workflows/setup.yaml + with: + install-deps: docs + secrets: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + + deploy: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Build and deploy documentation + run: uvx mkdocs gh-deploy --force + env: + GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 6dda08a..bf72057 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,39 +1,92 @@ name: lint_and_unittest -on: [ push, pull_request ] +on: + push: + branches: + - main + - master + pull_request: + workflow_dispatch: + +permissions: + contents: read + pull-requests: write # 用于在 PR 中添加评论 jobs: - lint_and_unittest: + setup: + uses: ./.github/workflows/setup.yaml + with: + install-deps: dev + python-version: "3.12" # 使用最新版本 + secrets: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + + lint: + needs: setup runs-on: ubuntu-latest steps: - - name: Load secret - uses: 1password/load-secrets-action@v2 - with: - export-env: true - env: - OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - PERSONAL_ACCESS_TOKEN: op://shawndengdev/github_access_token/credential + - name: Check Python version + run: python --version - - name: Check out - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: '${{ env.PERSONAL_ACCESS_TOKEN }}' + - name: Run lint checks + id: lint + run: uvx nox -s lint + continue-on-error: true - - uses: actions/setup-python@v5 + - name: Comment on PR + if: github.event_name == 'pull_request' && steps.lint.outcome == 'failure' + uses: actions/github-script@v7 with: - python-version: "3.12" + script: | + const output = `#### 🔍 Lint Check Failed + Please fix the linting issues before merging. + You can run \`nox -s lint\` locally to check and fix issues.`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - version: ">=0.4.0" + - name: Check lint result + if: steps.lint.outcome == 'failure' + run: exit 1 - - name: Install dependencies - run: uv sync --extra dev + test-all: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Run tests on all Python versions + id: test + run: uvx nox -s test_all + continue-on-error: true - - name: Run lint checks - run: uvx nox -s lint + - name: Upload coverage reports + if: success() + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./coverage.xml + flags: unittests + + - name: Comment on PR + if: github.event_name == 'pull_request' && steps.test.outcome == 'failure' + uses: actions/github-script@v7 + with: + script: | + const output = `#### ❌ Tests Failed + Please fix the test failures before merging. + You can run \`nox -s test_all\` locally to debug the failures.`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) - - name: Run tests - run: uvx nox -s test + - name: Check test result + if: steps.test.outcome == 'failure' + run: exit 1 diff --git a/.github/workflows/release_build.yaml b/.github/workflows/release_build.yaml index 93ef184..312df96 100644 --- a/.github/workflows/release_build.yaml +++ b/.github/workflows/release_build.yaml @@ -1,56 +1,52 @@ -name: release-build +name: release_build + on: - workflow_dispatch: push: tags: - - '*.*.*' + - '*' # 匹配所有标签 + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g. 1.0.0)' + required: true + type: string + +permissions: + contents: write # 用于创建 release + id-token: write # 用于发布到 PyPI jobs: - release-build: + setup: + uses: ./.github/workflows/setup.yaml + with: + install-deps: dev + python-version: "3.12" + secrets: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + + build: + needs: setup runs-on: ubuntu-latest - permissions: - contents: write # 用于创建 GitHub Release steps: - - uses: actions/checkout@v4 + - name: Run tests + run: uvx nox -s test_all - - name: Load secret - uses: 1password/load-secrets-action@v2 - with: - export-env: true - env: - OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - PERSONAL_ACCESS_TOKEN: op://shawndengdev/github_access_token/credential - PYPI_TOKEN: op://shawndengdev/pypi_token/credential - - - uses: actions/setup-python@v5 - with: - python-version: "3.12" + - name: Build package + run: uvx nox -s build - - name: Install uv - uses: astral-sh/setup-uv@v5 + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v2 with: - version: ">=0.4.0" - - - name: Install dependencies - run: uv sync --extra dev - - - name: Build and test - run: | - uvx nox -s lint - uvx nox -s test - uvx nox -s build + tag_name: ${{ github.event.inputs.version || github.ref_name }} + draft: false + prerelease: false + files: | + dist/* - name: Publish to PyPI - env: - UV_PUBLISH_TOKEN: ${{ env.PYPI_TOKEN }} - run: uv publish - - - name: Release - uses: softprops/action-gh-release@v2 + uses: pypa/gh-action-pypi-publish@release/v1 with: - files: | - dist/*.tar.gz - dist/*.whl - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ env.PERSONAL_ACCESS_TOKEN }} + packages-dir: dist/ + repository-url: https://upload.pypi.org/legacy/ diff --git a/.github/workflows/setup.yaml b/.github/workflows/setup.yaml new file mode 100644 index 0000000..149a7e7 --- /dev/null +++ b/.github/workflows/setup.yaml @@ -0,0 +1,75 @@ +name: Reusable Setup +on: + workflow_call: + inputs: + python-version: + required: false + type: string + default: "3.12" + install-deps: + required: false + type: string + default: "dev" # dev, docs, or none + secrets: + OP_SERVICE_ACCOUNT_TOKEN: + required: false + PERSONAL_ACCESS_TOKEN: + required: false + outputs: + python-version: + description: "The Python version that was set up" + value: ${{ jobs.setup.outputs.python-version }} + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + python-version: ${{ steps.setup-python.outputs.python-version }} + steps: + - name: Load secret + if: ${{ inputs.install-deps != 'none' }} + uses: 1password/load-secrets-action@v2 + with: + export-env: true + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + PERSONAL_ACCESS_TOKEN: op://shawndengdev/github_access_token/credential + + - name: Check out + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ env.PERSONAL_ACCESS_TOKEN || github.token }} + + - name: Setup Python + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + cache: 'pip' + cache-dependency-path: | + **/requirements*.txt + **/pyproject.toml + **/poetry.lock + **/Pipfile.lock + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: ">=0.4.0" + + - name: Get uv cache dir + id: get-uv-cache + run: echo "UV_CACHE_DIR=$(uv cache dir)" >> $GITHUB_OUTPUT + + - name: Cache uv packages + uses: actions/cache@v4 + with: + path: ${{ steps.get-uv-cache.outputs.UV_CACHE_DIR }} + key: ${{ runner.os }}-uv-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }} + restore-keys: | + ${{ runner.os }}-uv- + + - name: Install dependencies + if: ${{ inputs.install-deps != 'none' }} + run: uv sync --extra ${{ inputs.install-deps }} diff --git a/.gitignore b/.gitignore index 87b832e..81afea7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ /.zip/ /venv/ /dist/ -/__pycache__/ +**/__pycache__/ +*.pyc +# MkDocs build output +/site/ diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py new file mode 100644 index 0000000..4ba0a74 --- /dev/null +++ b/docs/gen_ref_pages.py @@ -0,0 +1,39 @@ +"""Generate the code reference pages.""" + +from pathlib import Path + +import mkdocs_gen_files + +nav = mkdocs_gen_files.Nav() + +# 排除的目录 +EXCLUDES = ["templates", "__pycache__"] + +for path in sorted(Path("repo_scaffold").rglob("*.py")): + # 检查路径中是否包含要排除的目录 + if any(exclude in str(path) for exclude in EXCLUDES): + continue + + module_path = path.relative_to(".").with_suffix("") + doc_path = path.relative_to(".").with_suffix(".md") + full_doc_path = Path("reference", doc_path) + + parts = tuple(module_path.parts) + + if parts[-1] == "__init__": + parts = parts[:-1] + doc_path = doc_path.with_name("index.md") + full_doc_path = full_doc_path.with_name("index.md") + elif parts[-1] == "__main__": + continue + + nav[parts] = doc_path.as_posix() + + with mkdocs_gen_files.open(full_doc_path, "w") as fd: + ident = ".".join(parts) + fd.write(f"::: {ident}") + + mkdocs_gen_files.set_edit_path(full_doc_path, path) + +with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: + nav_file.writelines(nav.build_literate_nav()) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..af29054 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,11 @@ +# Welcome to repo-scaffold + +A Python project template generator that helps you quickly scaffold new Python projects with best practices. + +## Features + +- Modern Python project structure +- Pre-configured development tools +- Documentation setup with MkDocs +- GitHub Actions for CI/CD +- And more... diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..bc8ff52 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,84 @@ +site_name: repo-scaffold +repo_url: https://github.com/ShawnDen-coder/repo-scaffold +site_url: https://ShawnDen-coder.github.io/repo-scaffold +site_description: A Python project template generator +site_author: ShawnDen-coder +edit_uri: edit/main/docs/ +repo_name: ShawnDen-coder/repo-scaffold +copyright: Maintained by ShawnDen-coder. + +nav: + - Home: index.md + - API Reference: reference/ + +plugins: + - search + - gen-files: + scripts: + - docs/gen_ref_pages.py + - literate-nav: + nav_file: SUMMARY.md + - mkdocstrings: + handlers: + python: + paths: ["repo_scaffold"] + options: + docstring_style: "google" + show_source: true + show_root_heading: true + show_signature_annotations: true + separate_signature: true + show_bases: true + merge_init_into_class: true + docstring_section_style: "spacy" + show_if_no_docstring: false + +theme: + name: material + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.sections + - navigation.expand + - navigation.indexes + - navigation.top + - search.highlight + - search.share + - search.suggest + - content.code.copy + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - tables + - toc: + permalink: true + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/ShawnDen-coder/repo-scaffold + - icon: fontawesome/brands/python + link: https://pypi.org/project/repo-scaffold diff --git a/noxfile.py b/noxfile.py index e61d0d5..95700f5 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,4 +1,4 @@ -"""Nox automation file for github-action-test project. +"""Nox automation file for repo-scaffold project. This module contains nox sessions for automating development tasks including: - Code linting and formatting @@ -6,64 +6,66 @@ - Package building - Project cleaning - Baseline creation for linting rules +- Documentation building Typical usage example: nox -s lint # Run linting - nox -s test # Run tests + nox -s test # Run tests with current Python version + nox -s test-all # Run tests with all supported Python versions nox -s build # Build package nox -s clean # Clean project nox -s baseline # Create a new baseline for linting rules + nox -s docs # Build documentation + nox -s docs-serve # Serve documentation locally """ import nox import shutil from pathlib import Path +# 支持的 Python 版本范围 +MIN_PYTHON = "3.10" +MAX_PYTHON = "3.12" -def install_with_uv(session: nox.Session, editable: bool = False) -> None: +# 生成版本列表 +PYTHON_VERSIONS = [ + f"3.{minor}" + for minor in range(int(MIN_PYTHON.split(".")[-1]), int(MAX_PYTHON.split(".")[-1]) + 1) +] + + +def install_with_uv(session: nox.Session, extras: list[str] | None = None) -> None: """Helper function to install packages using uv. Args: session: Nox session object for running commands - editable: Whether to install in editable mode + extras: Optional list of extra dependency groups to install (e.g. ["dev", "docs"]) """ session.install("uv") - if editable: - session.run("uv", "pip", "install", "-e", ".[dev]") + if extras: + session.run("uv", "sync", *(f"--extra={extra}" for extra in extras)) else: - session.run("uv", "pip", "install", ".") + session.run("uv", "sync") -@nox.session(reuse_venv=True) +@nox.session(python=PYTHON_VERSIONS[-1], reuse_venv=True) def lint(session: nox.Session) -> None: """Run code quality checks using ruff. Performs linting and formatting checks on the codebase using ruff. - Fixes auto-fixable issues and shows formatting differences. + Reports any issues found without auto-fixing. Args: session: Nox session object for running commands """ # Install dependencies - session.install("ruff") - install_with_uv(session, editable=True) - - # Run linting checks - session.run( - "ruff", - "check", - ".", - "--fix", - "--verbose" - ) - session.run( - "ruff", - "format", - "--verbose", - "--diff" - ) + install_with_uv(session, extras=["dev"]) + # Run ruff checks + session.run("ruff", "check", ".") + session.run("ruff", "format", "--check", ".") -@nox.session(reuse_venv=True) + +@nox.session(python=PYTHON_VERSIONS[-1], reuse_venv=True) def test(session: nox.Session) -> None: """Run the test suite with coverage reporting. @@ -74,35 +76,64 @@ def test(session: nox.Session) -> None: session: Nox session object for running commands """ # Install dependencies - install_with_uv(session, editable=True) - session.install("pytest", "pytest-cov", "pytest-mock") - - # Run tests + install_with_uv(session, extras=["dev"]) + + # Run pytest with coverage session.run( "pytest", "--cov=repo_scaffold", "--cov-report=term-missing", "--cov-report=xml", "-v", - "tests" + "tests", ) +@nox.session(python=PYTHON_VERSIONS) +def test_all(session: nox.Session) -> None: + """Run tests on all supported Python versions. + + This session runs the test suite against all supported Python versions. + It's useful for ensuring compatibility across different Python versions. + Also generates coverage report when running with the latest Python version. + + Args: + session: Nox session object for running commands + """ + # Install dependencies + install_with_uv(session, extras=["dev"]) + + # 确定是否是最新的 Python 版本 + is_latest_python = session.python == PYTHON_VERSIONS[-1] + + # 构建测试命令 + test_args = ["-v", "tests"] + if is_latest_python: + test_args = [ + "--cov=repo_scaffold", + "--cov-report=term-missing", + "--cov-report=xml", + ] + test_args + + # 运行测试 + session.run("pytest", *test_args) + + @nox.session(reuse_venv=True) def build(session: nox.Session) -> None: """Build the Python package. - Creates a distributable package using uv build command. + Creates a distributable package using build command. Args: session: Nox session object for running commands """ - session.install("uv") - session.run("uv", "build", "--wheel", "--sdist") + install_with_uv(session, extras=["dev"]) + session.run("python", "-m", "build") @nox.session(reuse_venv=True) -def clean(session: nox.Session) -> None: # pylint: disable=unused-argument +def clean(session: nox.Session) -> None: """Clean the project directory. Removes build artifacts, cache directories, and other temporary files: @@ -115,33 +146,39 @@ def clean(session: nox.Session) -> None: # pylint: disable=unused-argument - coverage.xml: Coverage report - **/*.pyc: Python bytecode - **/__pycache__/: Python cache directories - - **/*.egg-info: Package metadata - - Args: - session: Nox session object (unused) """ - root = Path(".") - patterns = [ + # 要清理的目录和文件 + paths_to_clean = [ + # 构建和分发 "build", "dist", + # 虚拟环境 ".nox", + # 缓存 ".pytest_cache", ".ruff_cache", + # 覆盖率报告 ".coverage", "coverage.xml", - "**/*.pyc", - "**/__pycache__", - "**/*.egg-info", + "site" ] - - for pattern in patterns: - for path in root.glob(pattern): - if path.is_file(): + + # 清理指定的目录和文件 + for path in paths_to_clean: + path = Path(path) + if path.exists(): + if path.is_dir(): + shutil.rmtree(path) + else: path.unlink() - print(f"Removed file: {path}") - elif path.is_dir(): + + # 清理 Python 缓存文件 + for pattern in ["**/*.pyc", "**/__pycache__"]: + for path in Path().glob(pattern): + if path.is_dir(): shutil.rmtree(path) - print(f"Removed directory: {path}") + else: + path.unlink() @nox.session(reuse_venv=True) @@ -157,32 +194,34 @@ def baseline(session: nox.Session) -> None: session: Nox session object for running commands """ # Install dependencies - session.install("ruff", "tomlkit") - install_with_uv(session, editable=True) - - # Get current violations - result = session.run( - "ruff", - "check", - ".", - "--verbose", - "--output-format=json", - silent=True, - success_codes=[0, 1] - ) - - if result: - # Add noqa comments to existing violations - session.run( - "ruff", - "check", - ".", - "--add-noqa", - "--verbose", - success_codes=[0, 1] - ) - - print("\nBaseline created! The following files were modified:") - session.run("git", "diff", "--name-only", external=True) - else: - print("No violations found. No baseline needed.") + install_with_uv(session, extras=["dev"]) + + # 运行 ruff 并自动修复所有问题 + session.run("ruff", "check", ".", "--add-noqa") + session.run("ruff", "format", ".") + + +@nox.session(reuse_venv=True) +def docs(session: nox.Session) -> None: + """Build the documentation. + + Uses MkDocs to build the documentation site. + + Args: + session: Nox session object for running commands + """ + install_with_uv(session, extras=["docs"]) + session.run("mkdocs", "build") + + +@nox.session(reuse_venv=True) +def docs_serve(session: nox.Session) -> None: + """Serve the documentation locally. + + Starts a local server to preview the documentation site. + + Args: + session: Nox session object for running commands + """ + install_with_uv(session, extras=["docs"]) + session.run("mkdocs", "serve") diff --git a/pyproject.toml b/pyproject.toml index f16b8f7..619e83e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,9 +24,21 @@ dev = [ "commitizen>=4.1.0", ] +docs = [ + "mkdocs>=1.5.3", + "mkdocs-material>=9.5.3", + "mkdocstrings>=0.24.0", + "mkdocstrings-python>=1.7.5", + "mkdocs-gen-files>=0.5.0", + "mkdocs-literate-nav>=0.6.1", +] + [project.scripts] repo-scaffold = "repo_scaffold.cli:cli" +[tool.setuptools.package-data] +repo_scaffold = ["templates/**/*", "cookiecutter.json"] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -34,7 +46,7 @@ build-backend = "hatchling.build" [tool.ruff] line-length = 120 include = ["pyproject.toml", "repo_scaffold/*.py"] -exclude = ["template-python/**/*"] +exclude = ["repo_scaffold/templates/**/*"] [tool.ruff.lint] select = [ diff --git a/repo_scaffold/cli.py b/repo_scaffold/cli.py index 3b3ed45..e08e0b0 100644 --- a/repo_scaffold/cli.py +++ b/repo_scaffold/cli.py @@ -1,64 +1,184 @@ -"""Command Line Interface module for project scaffolding. +"""Repository scaffolding CLI tool. -This module provides the CLI commands for creating new projects using cookiecutter -templates. It serves as the main entry point for the repo_scaffold tool and handles -all command-line interactions. +This module provides a command-line interface for creating new projects from templates. +It serves as the main entry point for the repo-scaffold tool. -Typical usage example: +Example: + To use this module as a CLI tool: + ```bash + # List available templates + $ repo-scaffold list + + # Create a new project + $ repo-scaffold create python + ``` + + To use this module in your code: + + ```python from repo_scaffold.cli import cli if __name__ == '__main__': cli() - -Attributes: - cli: The main Click command group that serves as the entry point. + ``` """ +import importlib.resources +import json import os +from pathlib import Path +from typing import Any +from typing import Dict import click from cookiecutter.main import cookiecutter +def get_package_path(relative_path: str) -> str: + """Get absolute path to a resource in the package. + + Args: + relative_path: Path relative to the package root + + Returns: + str: Absolute path to the resource + """ # noqa: W293 + # 使用 files() 获取包资源 + package_files = importlib.resources.files("repo_scaffold") + resource_path = package_files.joinpath(relative_path) + if not (resource_path.is_file() or resource_path.is_dir()): + raise FileNotFoundError(f"Resource not found: {relative_path}") + return str(resource_path) + + +def load_templates() -> Dict[str, Any]: + """Load available project templates configuration. + + Reads template configurations from the cookiecutter.json file in the templates directory. + Each template contains information about its name, path, title, and description. + + Returns: + Dict[str, Any]: Template configuration dictionary where keys are template names + and values are template information: + { + "template-name": { + "path": "relative/path", + "title": "Template Title", + "description": "Template description" + } + } + + Raises: + FileNotFoundError: If the configuration file doesn't exist + json.JSONDecodeError: If the configuration file is not valid JSON + """ + config_path = get_package_path("templates/cookiecutter.json") + with open(config_path, "r", encoding="utf-8") as f: + config = json.load(f) + return config["templates"] + + @click.group() def cli(): - """Repository scaffolding CLI tool. + """Modern project scaffolding tool. - A tool for creating new projects from templates. + Provides multiple project templates for quick project initialization. + Use `repo-scaffold list` to view available templates, + or `repo-scaffold create