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 ` to create a new project.
"""
- ...
@cli.command()
+def list():
+ """List all available project templates.
+
+ Displays the title and description of each template to help users
+ choose the appropriate template for their needs.
+
+ Example:
+ ```bash
+ $ repo-scaffold list
+ Available templates:
+
+ python - template-python
+ Description: template for python project
+ ```
+ """ # noqa: W293
+ templates = load_templates()
+ click.echo("\nAvailable templates:")
+ for name, info in templates.items():
+ click.echo(f"\n{info['title']} - {name}")
+ click.echo(f" Description: {info['description']}")
+
+
+@cli.command()
+@click.argument("template", required=False)
@click.option(
- "--template",
- "-t",
- default="https://github.com/ShawnDen-coder/repo-scaffold.git",
- help="Cookiecutter template URL or path",
+ "--output-dir",
+ "-o",
+ default=".",
+ type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
+ help="Directory where the project will be created",
)
-@click.option("--output-dir", "-o", default=".", help="Where to output the generated project dir")
-@click.option("--local", "-l", is_flag=True, help="Use local template in ./template-python")
-def create(template, output_dir, local):
- r"""Create a new project from template.
-
- \b
- Usage:
- repo-scaffold create [OPTIONS]
-
- \b
- Examples:
- $ repo-scaffold create
- $ repo-scaffold create --local
- $ repo-scaffold create -o ./my-projects
- $ repo-scaffold create -t https://github.com/user/custom-template.git
- $ repo-scaffold create -t ../path/to/local/template
- $ repo-scaffold create -t gh:user/template-name
- """
- if local:
- template = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
-
- cookiecutter(template=template, output_dir=output_dir, no_input=False)
+def create(template: str, output_dir: Path):
+ """Create a new project from a template.
+
+ Creates a new project based on the specified template. If no template is specified,
+ displays a list of available templates. The project generation process is interactive
+ and will prompt for necessary configuration values.
+
+ Args:
+ template: Template name or title (e.g., 'template-python' or 'python')
+ output_dir: Target directory where the project will be created
+
+ Example:
+ Create a Python project:
+ ```bash
+ $ repo-scaffold create python
+ ```
+
+ Specify output directory:
+ ```bash
+ $ repo-scaffold create python -o ./projects
+ ```
+
+ View available templates:
+ ```bash
+ $ repo-scaffold list
+ ```
+ """ # noqa: W293
+ templates = load_templates()
+
+ # 如果没有指定模板,让 cookiecutter 处理模板选择
+ if not template:
+ click.echo("Please select a template to use:")
+ for name, info in templates.items():
+ click.echo(f" {info['title']} - {name}")
+ click.echo(f" {info['description']}")
+ return
+
+ # 查找模板配置
+ template_info = None
+ for name, info in templates.items():
+ if name == template or info["title"] == template:
+ template_info = info
+ break
+
+ if not template_info:
+ click.echo(f"Error: Template '{template}' not found")
+ click.echo("\nAvailable templates:")
+ for name, info in templates.items():
+ click.echo(f" {info['title']} - {name}")
+ return
+
+ # 使用模板创建项目
+ template_path = get_package_path(os.path.join("templates", template_info["path"]))
+ cookiecutter(
+ template=template_path,
+ output_dir=str(output_dir),
+ no_input=False, # 启用交互式输入,让 cookiecutter 处理所有选项
+ )
if __name__ == "__main__":
diff --git a/cookiecutter.json b/repo_scaffold/templates/cookiecutter.json
similarity index 78%
rename from cookiecutter.json
rename to repo_scaffold/templates/cookiecutter.json
index 1b99955..6d52947 100644
--- a/cookiecutter.json
+++ b/repo_scaffold/templates/cookiecutter.json
@@ -1,7 +1,7 @@
{
"templates": {
"template-python": {
- "path": "./template-python",
+ "path": "template-python",
"title": "python",
"description": "template for python project"
}
diff --git a/repo_scaffold/templates/template-python/cookiecutter.json b/repo_scaffold/templates/template-python/cookiecutter.json
new file mode 100644
index 0000000..059eab6
--- /dev/null
+++ b/repo_scaffold/templates/template-python/cookiecutter.json
@@ -0,0 +1,28 @@
+{
+ "repo_name": "my-python-project",
+ "full_name": "Shawn Deng",
+ "email": "shawndeng1109@qq.com",
+ "github_username": "ShawnDen-coder",
+ "project_slug": "{{ cookiecutter.repo_name.strip().lower().replace('-', '_') }}",
+ "description": "A short description of the project.",
+ "min_python_version": ["3.8", "3.9", "3.10", "3.11"],
+ "max_python_version": ["3.12"],
+ "use_mkdocs": ["yes", "no"],
+ "use_docker": ["yes", "no"],
+ "include_cli": ["yes", "no"],
+ "use_github_actions": ["yes", "no"],
+ "__prompts__": {
+ "repo_name": "项目名称 (使用 - 分隔, 例如: my-awesome-project)",
+ "full_name": "你的全名",
+ "email": "你的邮箱地址",
+ "github_username": "你的 GitHub 用户名",
+ "project_slug": "Python 包名 (用于 import)",
+ "description": "项目简短描述",
+ "min_python_version": "最低支持的 Python 版本 (这将影响测试范围和依赖兼容性)",
+ "max_python_version": "最高支持的 Python 版本",
+ "use_mkdocs": "是否使用 MkDocs 生成文档?",
+ "use_docker": "是否添加 Docker 支持?",
+ "include_cli": "是否添加命令行界面?",
+ "use_github_actions": "是否添加 GitHub Actions 支持?"
+ }
+}
diff --git a/template-python/hooks/__init__.py b/repo_scaffold/templates/template-python/hooks/__init__.py
similarity index 100%
rename from template-python/hooks/__init__.py
rename to repo_scaffold/templates/template-python/hooks/__init__.py
diff --git a/repo_scaffold/templates/template-python/hooks/post_gen_project.py b/repo_scaffold/templates/template-python/hooks/post_gen_project.py
new file mode 100644
index 0000000..6859e73
--- /dev/null
+++ b/repo_scaffold/templates/template-python/hooks/post_gen_project.py
@@ -0,0 +1,58 @@
+import os
+import shutil
+import subprocess
+
+
+def remove_cli():
+ """Remove CLI related files if not needed."""
+ cli_file = os.path.join("{{cookiecutter.project_slug}}", "cli.py")
+ if os.path.exists(cli_file):
+ os.remove(cli_file)
+
+
+def remove_github_actions():
+ """Remove GitHub Actions configuration if not needed."""
+ github_dir = os.path.join("{{cookiecutter.project_slug}}", ".github")
+ if os.path.exists(github_dir):
+ shutil.rmtree(github_dir)
+
+
+def remove_mkdocs():
+ """Remove MkDocs related files if not needed."""
+ files_to_remove = [
+ "mkdocs.yml",
+ os.path.join("docs"),
+ ]
+ for file in files_to_remove:
+ path = os.path.join("{{cookiecutter.project_slug}}", file)
+ if os.path.exists(path):
+ if os.path.isdir(path):
+ shutil.rmtree(path)
+ else:
+ os.remove(path)
+
+
+def init_project_depends():
+ """Initialize project dependencies using uv."""
+ project_dir = os.path.abspath("{{cookiecutter.project_slug}}")
+ os.chdir(project_dir)
+
+ # 安装基础开发依赖
+ subprocess.run(["uv", "sync", "--extra", "dev"], check=True)
+
+ # 如果启用了文档功能,安装文档依赖
+ if "{{cookiecutter.use_mkdocs}}" == "yes":
+ subprocess.run(["uv", "sync", "--extra", "docs"], check=True)
+
+
+if __name__ == "__main__":
+ if "{{cookiecutter.include_cli}}" == "no":
+ remove_cli()
+
+ if "{{cookiecutter.use_github_actions}}" == "no":
+ remove_github_actions()
+
+ if "{{cookiecutter.use_mkdocs}}" == "no":
+ remove_mkdocs()
+
+ init_project_depends()
\ No newline at end of file
diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml
new file mode 100644
index 0000000..de2b7c8
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml
@@ -0,0 +1,50 @@
+name: Bump version
+
+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:
+ if: "!startsWith(github.event.head_commit.message, 'bump:')"
+ runs-on: ubuntu-latest
+ name: "Bump version and create changelog with commitizen"
+ steps:
+ - name: Check out
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+
+ - id: cz
+ name: Create bump and changelog
+ uses: commitizen-tools/commitizen-action@master
+ with:
+ 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/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/deploy_docs.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/deploy_docs.yaml
new file mode 100644
index 0000000..41e76eb
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/deploy_docs.yaml
@@ -0,0 +1,31 @@
+{% if cookiecutter.use_mkdocs == "yes" %}
+name: Deploy Docs
+
+on:
+ push:
+ tags:
+ - '*' # 匹配所有标签
+ workflow_dispatch: # 保留手动触发选项
+
+permissions:
+ contents: write # 用于部署到 GitHub Pages
+
+jobs:
+ setup:
+ uses: ./.github/workflows/setup.yaml
+ with:
+ install-deps: docs
+ python-version: "{{ cookiecutter.max_python_version }}"
+ 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 }}
+{% endif %}
diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint.yaml
new file mode 100644
index 0000000..ae142da
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint.yaml
@@ -0,0 +1,92 @@
+name: lint_and_unittest
+
+on:
+ push:
+ branches:
+ - main
+ - master
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pull-requests: write # 用于在 PR 中添加评论
+
+jobs:
+ setup:
+ uses: ./.github/workflows/setup.yaml
+ with:
+ install-deps: dev
+ python-version: "{{cookiecutter.max_python_version}}" # 使用最新版本
+ 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: Check Python version
+ run: python --version
+
+ - name: Run lint checks
+ id: lint
+ run: uvx nox -s lint
+ continue-on-error: true
+
+ - name: Comment on PR
+ if: github.event_name == 'pull_request' && steps.lint.outcome == 'failure'
+ uses: actions/github-script@v7
+ with:
+ 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: Check lint result
+ if: steps.lint.outcome == 'failure'
+ run: exit 1
+
+ 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: 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: Check test result
+ if: steps.test.outcome == 'failure'
+ run: exit 1
diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml
new file mode 100644
index 0000000..4a5d1f0
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml
@@ -0,0 +1,52 @@
+name: release_build
+
+on:
+ 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:
+ setup:
+ uses: ./.github/workflows/setup.yaml
+ with:
+ install-deps: dev
+ python-version: "{{ cookiecutter.max_python_version }}"
+ secrets:
+ OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
+ PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+
+ build:
+ needs: setup
+ runs-on: ubuntu-latest
+ steps:
+ - name: Run tests
+ run: uvx nox -s test_all
+
+ - name: Build package
+ run: uvx nox -s build
+
+ - name: Create Release
+ id: create_release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ github.event.inputs.version || github.ref_name }}
+ draft: false
+ prerelease: false
+ files: |
+ dist/*
+
+ - name: Publish to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ packages-dir: dist/
+ repository-url: https://upload.pypi.org/legacy/
diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/setup.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/setup.yaml
new file mode 100644
index 0000000..138edcf
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/setup.yaml
@@ -0,0 +1,76 @@
+name: Reusable Setup
+
+on:
+ workflow_call:
+ inputs:
+ python-version:
+ required: false
+ type: string
+ default: "{{cookiecutter.max_python_version}}"
+ 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/template-python/{{cookiecutter.project_slug}}/.gitignore b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.gitignore
similarity index 52%
rename from template-python/{{cookiecutter.project_slug}}/.gitignore
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.gitignore
index 2d9539c..012cb2f 100644
--- a/template-python/{{cookiecutter.project_slug}}/.gitignore
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.gitignore
@@ -5,4 +5,9 @@
/build/
/coverage.xml
/.zip/
-/venv/
\ No newline at end of file
+/venv/
+/dist/
+**/__pycache__/
+*.pyc
+# MkDocs build output
+/site/
\ No newline at end of file
diff --git a/template-python/{{cookiecutter.project_slug}}/README.md b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/README.md
similarity index 100%
rename from template-python/{{cookiecutter.project_slug}}/README.md
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/README.md
diff --git a/template-python/{{cookiecutter.project_slug}}/__init__.py b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/__init__.py
similarity index 100%
rename from template-python/{{cookiecutter.project_slug}}/__init__.py
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/__init__.py
diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/api.md b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/api.md
new file mode 100644
index 0000000..b61e795
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/api.md
@@ -0,0 +1,5 @@
+# API Reference
+
+This page contains the API reference for {{cookiecutter.project_slug}}.
+
+::: {{cookiecutter.project_slug}}
diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/contributing.md b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/contributing.md
new file mode 100644
index 0000000..9f442f1
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/contributing.md
@@ -0,0 +1,20 @@
+# Contributing
+
+We love your input! We want to make contributing to {{cookiecutter.project_slug}} as easy and transparent as possible, whether it's:
+
+- Reporting a bug
+- Discussing the current state of the code
+- Submitting a fix
+- Proposing new features
+- Becoming a maintainer
+
+## Development Process
+
+We use GitHub to host code, to track issues and feature requests, as well as accept pull requests.
+
+1. Fork the repo and create your branch from `main`.
+2. If you've added code that should be tested, add tests.
+3. If you've changed APIs, update the documentation.
+4. Ensure the test suite passes.
+5. Make sure your code lints.
+6. Issue that pull request!
diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/gen_ref_pages.py b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/gen_ref_pages.py
new file mode 100644
index 0000000..5455d8c
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/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 = ["__pycache__"]
+
+for path in sorted(Path("{{cookiecutter.project_slug}}").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/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/index.md b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/index.md
new file mode 100644
index 0000000..a679ca9
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docs/index.md
@@ -0,0 +1,17 @@
+# {{cookiecutter.project_slug}}
+
+{{cookiecutter.description}}
+
+## Installation
+
+```bash
+pip install {{cookiecutter.project_slug}}
+```
+
+## Quick Start
+
+[Add quick start guide here]
+
+## Features
+
+[List main features here]
diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/mkdocs.yml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/mkdocs.yml
new file mode 100644
index 0000000..156f312
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/mkdocs.yml
@@ -0,0 +1,84 @@
+site_name: {{cookiecutter.project_slug}}
+repo_url: https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.project_slug}}
+site_url: https://{{cookiecutter.github_username}}.github.io/{{cookiecutter.project_slug}}
+site_description: {{cookiecutter.description}}
+site_author: {{cookiecutter.full_name}}
+edit_uri: edit/main/docs/
+repo_name: {{cookiecutter.github_username}}/{{cookiecutter.project_slug}}
+copyright: Maintained by {{cookiecutter.github_username}}.
+
+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: ["{{cookiecutter.project_slug}}"]
+ 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/{{cookiecutter.github_username}}/{{cookiecutter.project_slug}}
+ - icon: fontawesome/brands/python
+ link: https://pypi.org/project/{{cookiecutter.project_slug}}
diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/noxfile.py b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/noxfile.py
new file mode 100644
index 0000000..9aebebf
--- /dev/null
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/noxfile.py
@@ -0,0 +1,229 @@
+"""Nox automation file for {{cookiecutter.project_slug}} project.
+
+This module contains nox sessions for automating development tasks including:
+- Code linting and formatting
+- Unit testing with coverage reporting
+- Package building
+- Project cleaning
+- Baseline creation for linting rules
+- Documentation building
+
+Typical usage example:
+ nox -s lint # Run linting
+ 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 = "{{cookiecutter.min_python_version}}"
+MAX_PYTHON = "{{cookiecutter.max_python_version}}"
+
+# 生成版本列表
+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
+ extras: Optional list of extra dependency groups to install (e.g. ["dev", "docs"])
+ """
+ session.install("uv")
+ if extras:
+ session.run("uv", "sync", *(f"--extra={extra}" for extra in extras))
+ else:
+ session.run("uv", "sync")
+
+
+@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.
+ Reports any issues found without auto-fixing.
+
+ Args:
+ session: Nox session object for running commands
+ """
+ # Install dependencies
+ install_with_uv(session, extras=["dev"])
+
+ # Run ruff checks
+ session.run("ruff", "check", ".")
+ session.run("ruff", "format", "--check", ".")
+
+
+@nox.session(python=PYTHON_VERSIONS[-1], reuse_venv=True)
+def test(session: nox.Session) -> None:
+ """Run the test suite with coverage reporting.
+
+ Executes pytest with coverage reporting for the {{cookiecutter.project_slug}} package.
+ Generates both terminal and XML coverage reports.
+
+ Args:
+ session: Nox session object for running commands
+ """
+ # Install dependencies
+ install_with_uv(session, extras=["dev"])
+
+ # Run pytest with coverage
+ session.run(
+ "pytest",
+ "--cov={{cookiecutter.project_slug}}",
+ "--cov-report=term-missing",
+ "--cov-report=xml",
+ "-v",
+ "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={{cookiecutter.project_slug}}",
+ "--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 build command.
+
+ Args:
+ session: Nox session object for running commands
+ """
+ install_with_uv(session, extras=["dev"])
+ session.run("python", "-m", "build")
+
+
+@nox.session(reuse_venv=True)
+def clean(session: nox.Session) -> None:
+ """Clean the project directory.
+
+ Removes build artifacts, cache directories, and other temporary files:
+ - build/: Build artifacts
+ - dist/: Distribution packages
+ - .nox/: Nox virtual environments
+ - .pytest_cache/: Pytest cache
+ - .ruff_cache/: Ruff cache
+ - .coverage: Coverage data
+ - coverage.xml: Coverage report
+ - **/*.pyc: Python bytecode
+ - **/__pycache__/: Python cache directories
+ """
+ # 要清理的目录和文件
+ paths_to_clean = [
+ # 构建和分发
+ "build",
+ "dist",
+ # 虚拟环境
+ ".nox",
+ # 缓存
+ ".pytest_cache",
+ ".ruff_cache",
+ # 覆盖率报告
+ ".coverage",
+ "coverage.xml",
+ "site"
+ ]
+
+ # 清理指定的目录和文件
+ for path in paths_to_clean:
+ path = Path(path)
+ if path.exists():
+ if path.is_dir():
+ shutil.rmtree(path)
+ else:
+ path.unlink()
+
+ # 清理 Python 缓存文件
+ for pattern in ["**/*.pyc", "**/__pycache__"]:
+ for path in Path().glob(pattern):
+ if path.is_dir():
+ shutil.rmtree(path)
+ else:
+ path.unlink()
+
+
+@nox.session(reuse_venv=True)
+def baseline(session: nox.Session) -> None:
+ """Create a new baseline for linting rules.
+
+ This command will:
+ 1. Add # noqa comments to all existing violations
+ 2. Update the pyproject.toml with new extend-ignore rules
+ 3. Show a summary of changes made
+
+ Args:
+ session: Nox session object for running commands
+ """
+ # Install dependencies
+ install_with_uv(session, extras=["dev"])
+
+ # 运行 ruff 并自动修复所有问题
+ session.run("ruff", "check", ".", "--add-noqa")
+ session.run("ruff", "format", ".")
+
+
+{% if cookiecutter.use_mkdocs == "yes" %}
+@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")
+{% endif %}
diff --git a/template-python/{{cookiecutter.project_slug}}/pyproject.toml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/pyproject.toml
similarity index 82%
rename from template-python/{{cookiecutter.project_slug}}/pyproject.toml
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/pyproject.toml
index 1c9699a..7659321 100644
--- a/template-python/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/pyproject.toml
@@ -7,7 +7,7 @@ authors = [
]
license = "MIT"
readme = "README.md"
-requires-python = ">=3.12"
+requires-python = ">={{cookiecutter.min_python_version}},<{{cookiecutter.max_python_version}}.99"
dependencies = [
{% if cookiecutter.include_cli == 'y' %}
"click>=8.1.8",
@@ -23,6 +23,14 @@ dev = [
"pytest-cov>=6.0.0",
"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",
+]
{% if cookiecutter.include_cli == 'y' %}
[project.scripts]
@@ -36,6 +44,7 @@ build-backend = "hatchling.build"
[tool.ruff]
line-length = 120
include = ["pyproject.toml", "{{cookiecutter.project_slug}}/**/*.py"]
+target-version = "{{cookiecutter.min_python_version}}"
[tool.ruff.lint]
select = [
diff --git a/template-python/{{cookiecutter.project_slug}}/tests/__init__.py b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/tests/__init__.py
similarity index 100%
rename from template-python/{{cookiecutter.project_slug}}/tests/__init__.py
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/tests/__init__.py
diff --git a/template-python/{{cookiecutter.project_slug}}/tests/test_import.py b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/tests/test_import.py
similarity index 100%
rename from template-python/{{cookiecutter.project_slug}}/tests/test_import.py
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/tests/test_import.py
diff --git a/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py
similarity index 100%
rename from template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py
diff --git a/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cli.py b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cli.py
similarity index 100%
rename from template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cli.py
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cli.py
diff --git a/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/constants.py b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/constants.py
similarity index 100%
rename from template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/constants.py
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/constants.py
diff --git a/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/core.py b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/core.py
similarity index 100%
rename from template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/core.py
rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/core.py
diff --git a/template-python/cookiecutter.json b/template-python/cookiecutter.json
deleted file mode 100644
index ffe1fd5..0000000
--- a/template-python/cookiecutter.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "repo_name": "My-Package",
- "full_name": "Shawn Deng",
- "email": "shawndeng1109@qq.com",
- "project_slug": "{{ cookiecutter.repo_name.strip().lower().replace('-', '_') }}",
- "description": "A short description of the project.",
- "include_cli": [
- "y",
- "n"
- ]
-}
diff --git a/template-python/hooks/post_gen_project.py b/template-python/hooks/post_gen_project.py
deleted file mode 100644
index 0592af3..0000000
--- a/template-python/hooks/post_gen_project.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import os
-import subprocess
-
-
-def remove_cli():
- cli_file = os.path.join("{{cookiecutter.project_slug}}", "cli.py")
- if os.path.exists(cli_file):
- os.remove(cli_file)
-
-
-def init_poetry():
- project_dir = os.path.abspath("{{cookiecutter.project_slug}}")
- os.chdir(project_dir)
- subprocess.run(["poetry", "install"], check=True)
-
-if __name__ == "__main__":
- if "{{cookiecutter.project_slug}}" == "n":
- remove_cli()
- init_poetry()
\ No newline at end of file
diff --git a/template-python/hooks/pre_gen_project.py b/template-python/hooks/pre_gen_project.py
deleted file mode 100644
index e69de29..0000000
diff --git a/template-python/hooks/pre_prompt.py b/template-python/hooks/pre_prompt.py
deleted file mode 100644
index e69de29..0000000
diff --git a/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml b/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml
deleted file mode 100644
index ee94982..0000000
--- a/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Bump version
-
-on:
- push:
- branches:
- - master
- workflow_dispatch:
-
-jobs:
- bump-version:
- if: "!startsWith(github.event.head_commit.message, 'bump:')"
- 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: {% raw %}${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}{% endraw %}
- PERSONAL_ACCESS_TOKEN: op://shawndengdev/github_access_token/credential
-
- - name: Check out
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- token: '{% raw %}${{ env.PERSONAL_ACCESS_TOKEN }}{% endraw %}'
-
- - name: Create bump and changelog
- uses: commitizen-tools/commitizen-action@master
- with:
- github_token: {% raw %}${{ env.PERSONAL_ACCESS_TOKEN }}{% endraw %}
- branch: master
diff --git a/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint.yaml b/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint.yaml
deleted file mode 100644
index 3066c2d..0000000
--- a/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: lint_and_unittest
-
-on: [ push, pull_request ]
-
-jobs:
- lint_and_unittest:
- runs-on: ubuntu-latest
- steps:
- - name: Load secret
- uses: 1password/load-secrets-action@v2
- with:
- export-env: true
- env:
- OP_SERVICE_ACCOUNT_TOKEN: {% raw %}${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}{% endraw %}
- PERSONAL_ACCESS_TOKEN: op://shawndengdev/github_access_token/credential
-
- - name: Check out
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- token: '{% raw %}${{ env.PERSONAL_ACCESS_TOKEN }}{% endraw %}'
-
- - uses: actions/setup-python@v5
- with:
- python-version: "3.12"
-
- - name: Install uv
- uses: astral-sh/setup-uv@v5
- with:
- version: ">=0.4.0"
-
- - name: Install dependencies
- run: uv sync --extra dev
-
- - name: Run lint checks
- run: uvx nox -s lint
-
- - name: Run tests
- run: uvx nox -s test
diff --git a/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml b/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml
deleted file mode 100644
index 50eb788..0000000
--- a/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: release-build
-on:
- workflow_dispatch:
- push:
- tags:
- - '*.*.*'
-
-jobs:
- release-build:
- runs-on: ubuntu-latest
- permissions:
- contents: write # 用于创建 GitHub Release
- steps:
- - uses: actions/checkout@v4
-
- - name: Load secret
- uses: 1password/load-secrets-action@v2
- with:
- export-env: true
- env:
- OP_SERVICE_ACCOUNT_TOKEN: {% raw %}${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}{% endraw %}
- 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: Install uv
- uses: astral-sh/setup-uv@v5
- 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
-
- - name: Publish to PyPI
- env:
- UV_PUBLISH_TOKEN: {% raw %}${{ env.PYPI_TOKEN }}{% endraw %}
- run: uv publish
-
- - name: Release
- uses: softprops/action-gh-release@v2
- with:
- files: |
- dist/*.tar.gz
- dist/*.whl
- generate_release_notes: true
- env:
- GITHUB_TOKEN: {% raw %}${{ env.PERSONAL_ACCESS_TOKEN }}{% endraw %}
diff --git a/template-python/{{cookiecutter.project_slug}}/noxfile.py b/template-python/{{cookiecutter.project_slug}}/noxfile.py
deleted file mode 100644
index b92d679..0000000
--- a/template-python/{{cookiecutter.project_slug}}/noxfile.py
+++ /dev/null
@@ -1,188 +0,0 @@
-"""Nox automation file for {{cookiecutter.project_slug}} project.
-
-This module contains nox sessions for automating development tasks including:
-- Code linting and formatting
-- Unit testing with coverage reporting
-- Package building
-- Project cleaning
-- Baseline creation for linting rules
-
-Typical usage example:
- nox -s lint # Run linting
- nox -s test # Run tests
- nox -s build # Build package
- nox -s clean # Clean project
- nox -s baseline # Create a new baseline for linting rules
-"""
-import nox
-import shutil
-from pathlib import Path
-
-
-def install_with_uv(session: nox.Session, editable: bool = False) -> None:
- """Helper function to install packages using uv.
-
- Args:
- session: Nox session object for running commands
- editable: Whether to install in editable mode
- """
- session.install("uv")
- if editable:
- session.run("uv", "pip", "install", "-e", ".[dev]")
- else:
- session.run("uv", "pip", "install", ".")
-
-
-@nox.session(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.
-
- 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"
- )
-
-
-@nox.session(reuse_venv=True)
-def test(session: nox.Session) -> None:
- """Run the test suite with coverage reporting.
-
- Executes pytest with coverage reporting for the {{cookiecutter.project_slug}} package.
- Generates both terminal and XML coverage reports.
-
- Args:
- session: Nox session object for running commands
- """
- # Install dependencies
- install_with_uv(session, editable=True)
- session.install("pytest", "pytest-cov", "pytest-mock")
-
- # Run tests
- session.run(
- "pytest",
- "--cov={{cookiecutter.project_slug}}",
- "--cov-report=term-missing",
- "--cov-report=xml",
- "-v",
- "tests"
- )
-
-
-@nox.session(reuse_venv=True)
-def build(session: nox.Session) -> None:
- """Build the Python package.
-
- Creates a distributable package using uv build command.
-
- Args:
- session: Nox session object for running commands
- """
- session.install("uv")
- session.run("uv", "build", "--wheel", "--sdist")
-
-
-@nox.session(reuse_venv=True)
-def clean(session: nox.Session) -> None: # pylint: disable=unused-argument
- """Clean the project directory.
-
- Removes build artifacts, cache directories, and other temporary files:
- - build/: Build artifacts
- - dist/: Distribution packages
- - .nox/: Nox virtual environments
- - .pytest_cache/: Pytest cache
- - .ruff_cache/: Ruff cache
- - .coverage: Coverage data
- - coverage.xml: Coverage report
- - **/*.pyc: Python bytecode
- - **/__pycache__/: Python cache directories
- - **/*.egg-info: Package metadata
-
- Args:
- session: Nox session object (unused)
- """
- root = Path(".")
- patterns = [
- "build",
- "dist",
- ".nox",
- ".pytest_cache",
- ".ruff_cache",
- ".coverage",
- "coverage.xml",
- "**/*.pyc",
- "**/__pycache__",
- "**/*.egg-info",
- ]
-
- for pattern in patterns:
- for path in root.glob(pattern):
- if path.is_file():
- path.unlink()
- print(f"Removed file: {path}")
- elif path.is_dir():
- shutil.rmtree(path)
- print(f"Removed directory: {path}")
-
-
-@nox.session(reuse_venv=True)
-def baseline(session: nox.Session) -> None:
- """Create a new baseline for linting rules.
-
- This command will:
- 1. Add # noqa comments to all existing violations
- 2. Update the pyproject.toml with new extend-ignore rules
- 3. Show a summary of changes made
-
- Args:
- 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.")
diff --git a/uv.lock b/uv.lock
index dbd784d..afefbf1 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,4 +1,5 @@
version = 1
+revision = 1
requires-python = ">=3.12"
[[package]]
@@ -32,6 +33,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 },
]
+[[package]]
+name = "babel"
+version = "2.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 },
+]
+
[[package]]
name = "binaryornot"
version = "0.4.4"
@@ -248,6 +258,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 },
]
+[[package]]
+name = "ghp-import"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 },
+]
+
+[[package]]
+name = "griffe"
+version = "1.5.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/59/80/13b6456bfbf8bc854875e58d3a3bad297ee19ebdd693ce62a10fab007e7a/griffe-1.5.7.tar.gz", hash = "sha256:465238c86deaf1137761f700fb343edd8ffc846d72f6de43c3c345ccdfbebe92", size = 391503 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/76/67/b43330ed76f96be098c165338d47ccb952964ed77ba1d075247fbdf05c04/griffe-1.5.7-py3-none-any.whl", hash = "sha256:4af8ec834b64de954d447c7b6672426bb145e71605c74a4e22d510cc79fe7d8b", size = 128294 },
+]
+
[[package]]
name = "idna"
version = "3.10"
@@ -278,6 +312,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 },
]
+[[package]]
+name = "markdown"
+version = "3.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 },
+]
+
[[package]]
name = "markdown-it-py"
version = "3.0.0"
@@ -337,6 +380,154 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
]
+[[package]]
+name = "mergedeep"
+version = "1.3.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 },
+]
+
+[[package]]
+name = "mkdocs"
+version = "1.6.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "ghp-import" },
+ { name = "jinja2" },
+ { name = "markdown" },
+ { name = "markupsafe" },
+ { name = "mergedeep" },
+ { name = "mkdocs-get-deps" },
+ { name = "packaging" },
+ { name = "pathspec" },
+ { name = "pyyaml" },
+ { name = "pyyaml-env-tag" },
+ { name = "watchdog" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 },
+]
+
+[[package]]
+name = "mkdocs-autorefs"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown" },
+ { name = "markupsafe" },
+ { name = "mkdocs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/52/f4/77e3cf5e7ba54dca168bc718688127844721982ae88b08684669c5b5752d/mkdocs_autorefs-1.3.1.tar.gz", hash = "sha256:a6d30cbcccae336d622a66c2418a3c92a8196b69782774529ad441abb23c0902", size = 2056416 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/db/19/f20edc082c1de2987dbaf30fcc514ed7a908d465a15aba7cba595c3b245a/mkdocs_autorefs-1.3.1-py3-none-any.whl", hash = "sha256:18c504ae4d3ee7f344369bb26cb31d4105569ee252aab7d75ec2734c2c8b0474", size = 2837887 },
+]
+
+[[package]]
+name = "mkdocs-gen-files"
+version = "0.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mkdocs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/48/85/2d634462fd59136197d3126ca431ffb666f412e3db38fd5ce3a60566303e/mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc", size = 7539 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/0f/1e55b3fd490ad2cecb6e7b31892d27cb9fc4218ec1dab780440ba8579e74/mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea", size = 8380 },
+]
+
+[[package]]
+name = "mkdocs-get-deps"
+version = "0.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mergedeep" },
+ { name = "platformdirs" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 },
+]
+
+[[package]]
+name = "mkdocs-literate-nav"
+version = "0.6.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mkdocs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4d/f9/c48a04f3cf484f8016a343c1d7d99c3a1ef01dbb33ceabb1d02e0ecabda7/mkdocs_literate_nav-0.6.1.tar.gz", hash = "sha256:78a7ab6d878371728acb0cdc6235c9b0ffc6e83c997b037f4a5c6ff7cef7d759", size = 16437 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/3b/e00d839d3242844c77e248f9572dd34644a04300839a60fe7d6bf652ab19/mkdocs_literate_nav-0.6.1-py3-none-any.whl", hash = "sha256:e70bdc4a07050d32da79c0b697bd88e9a104cf3294282e9cb20eec94c6b0f401", size = 13182 },
+]
+
+[[package]]
+name = "mkdocs-material"
+version = "9.6.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "babel" },
+ { name = "colorama" },
+ { name = "jinja2" },
+ { name = "markdown" },
+ { name = "mkdocs" },
+ { name = "mkdocs-material-extensions" },
+ { name = "paginate" },
+ { name = "pygments" },
+ { name = "pymdown-extensions" },
+ { name = "regex" },
+ { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9b/80/4efbd3df76c6c1ec27130b43662612f9033adc5a4166f1df2acb8dd6fb1b/mkdocs_material-9.6.4.tar.gz", hash = "sha256:4d1d35e1c1d3e15294cb7fa5d02e0abaee70d408f75027dc7be6e30fb32e6867", size = 3942628 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5b/a5/f3c0e86c1d28fe04f1b724700ff3dd8b3647c89df03a8e10c4bc6b4db1b8/mkdocs_material-9.6.4-py3-none-any.whl", hash = "sha256:414e8376551def6d644b8e6f77226022868532a792eb2c9accf52199009f568f", size = 8688727 },
+]
+
+[[package]]
+name = "mkdocs-material-extensions"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 },
+]
+
+[[package]]
+name = "mkdocstrings"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jinja2" },
+ { name = "markdown" },
+ { name = "markupsafe" },
+ { name = "mkdocs" },
+ { name = "mkdocs-autorefs" },
+ { name = "mkdocs-get-deps" },
+ { name = "pymdown-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/82/6a/3980e05d7522423dc4ca547771d16d399fc3f4266df652f624f4f4dd7890/mkdocstrings-0.28.1.tar.gz", hash = "sha256:fb64576906771b7701e8e962fd90073650ff689e95eb86e86751a66d65ab4489", size = 4551690 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6f/5d/8580b426396d8cbbe98df364ef891487c4942e36356d56bb5a6dd91f51a9/mkdocstrings-0.28.1-py3-none-any.whl", hash = "sha256:a5878ae5cd1e26f491ff084c1f9ab995687d52d39a5c558e9b7023d0e4e0b740", size = 6426938 },
+]
+
+[[package]]
+name = "mkdocstrings-python"
+version = "1.15.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "griffe" },
+ { name = "mkdocs-autorefs" },
+ { name = "mkdocstrings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/28/5e/ea531f1798d6b614f87b7a1191f8bfda864767adecef3c75ec87f30e0a3d/mkdocstrings_python-1.15.0.tar.gz", hash = "sha256:2bfecbbe1252c67281408a6567d59545f4979931110f01ab625aa8c227c06edc", size = 422613 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6f/d7/1d35cce198f76e8ae4010a71ff5acabe8b75aeb35f8c3d920e175a6476ca/mkdocstrings_python-1.15.0-py3-none-any.whl", hash = "sha256:77aced1bb28840d7d3510f77353319eeb450961880d87f9c53fdab331ba0120d", size = 449068 },
+]
+
[[package]]
name = "nox"
version = "2025.2.9"
@@ -363,6 +554,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
]
+[[package]]
+name = "paginate"
+version = "0.5.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 },
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
+]
+
[[package]]
name = "platformdirs"
version = "4.3.6"
@@ -402,6 +611,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
]
+[[package]]
+name = "pymdown-extensions"
+version = "10.14.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7c/44/e6de2fdc880ad0ec7547ca2e087212be815efbc9a425a8d5ba9ede602cbb/pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b", size = 846846 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/eb/f5/b9e2a42aa8f9e34d52d66de87941ecd236570c7ed2e87775ed23bbe4e224/pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9", size = 264467 },
+]
+
[[package]]
name = "pytest"
version = "8.3.4"
@@ -492,6 +714,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
]
+[[package]]
+name = "pyyaml-env-tag"
+version = "0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 },
+]
+
[[package]]
name = "questionary"
version = "2.1.0"
@@ -504,9 +738,47 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ad/3f/11dd4cd4f39e05128bfd20138faea57bec56f9ffba6185d276e3107ba5b2/questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec", size = 36747 },
]
+[[package]]
+name = "regex"
+version = "2024.11.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 },
+ { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 },
+ { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 },
+ { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 },
+ { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 },
+ { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 },
+ { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 },
+ { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 },
+ { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 },
+ { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 },
+ { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 },
+ { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 },
+ { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 },
+ { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 },
+ { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 },
+ { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 },
+ { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 },
+ { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 },
+ { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 },
+ { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 },
+ { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 },
+ { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 },
+ { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 },
+ { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 },
+ { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 },
+ { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 },
+ { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 },
+ { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 },
+ { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 },
+ { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 },
+]
+
[[package]]
name = "repo-scaffold"
-version = "0.3.0"
+version = "0.4.1"
source = { editable = "." }
dependencies = [
{ name = "click" },
@@ -523,12 +795,26 @@ dev = [
{ name = "pytest-mock" },
{ name = "ruff" },
]
+docs = [
+ { name = "mkdocs" },
+ { name = "mkdocs-gen-files" },
+ { name = "mkdocs-literate-nav" },
+ { name = "mkdocs-material" },
+ { name = "mkdocstrings" },
+ { name = "mkdocstrings-python" },
+]
[package.metadata]
requires-dist = [
{ name = "click", specifier = ">=8.1.8" },
{ name = "commitizen", marker = "extra == 'dev'", specifier = ">=4.1.0" },
{ name = "cookiecutter", specifier = ">=2.6.0" },
+ { name = "mkdocs", marker = "extra == 'docs'", specifier = ">=1.5.3" },
+ { name = "mkdocs-gen-files", marker = "extra == 'docs'", specifier = ">=0.5.0" },
+ { name = "mkdocs-literate-nav", marker = "extra == 'docs'", specifier = ">=0.6.1" },
+ { name = "mkdocs-material", marker = "extra == 'docs'", specifier = ">=9.5.3" },
+ { name = "mkdocstrings", marker = "extra == 'docs'", specifier = ">=0.24.0" },
+ { name = "mkdocstrings-python", marker = "extra == 'docs'", specifier = ">=1.7.5" },
{ name = "nox", marker = "extra == 'dev'", specifier = ">=2024.10.9" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.4" },
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" },
@@ -536,6 +822,7 @@ requires-dist = [
{ name = "ruff", specifier = ">=0.9.6" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.4" },
]
+provides-extras = ["dev", "docs"]
[[package]]
name = "requests"
@@ -658,6 +945,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/93/fa/849483d56773ae29740ae70043ad88e068f98a6401aa819b5d6bee604683/virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a", size = 4301478 },
]
+[[package]]
+name = "watchdog"
+version = "6.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 },
+ { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 },
+ { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 },
+ { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 },
+ { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 },
+ { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 },
+ { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 },
+ { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 },
+ { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 },
+ { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 },
+ { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 },
+ { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 },
+ { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 },
+ { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 },
+ { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 },
+ { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 },
+]
+
[[package]]
name = "wcwidth"
version = "0.2.13"