# type: ignore import re from pathlib import Path from invoke import task PYPROJECT = Path(__file__).parent / "pyproject.toml" @task def venv(c): """Sync dependencies.""" c.run("uv sync --group dev") @task def format(c): """Format code.""" c.run("uv run ruff format src tests") @task def lint(c): """Run linters.""" c.run("uv run ruff check src tests") c.run("uv run ruff format --check src tests") c.run("uv run mypy src") @task def test(c): """Run tests with coverage.""" c.run("uv run pytest --cov=src --cov-report=term-missing") @task def ci(c): """Run lint and tests in a clean Docker container.""" image = "{{ cookiecutter.project_slug }}-ci" c.run(f"docker build --network=host -t {image} .") cmd = ( "uv run ruff check src tests && " "uv run ruff format --check src tests && " "uv run mypy src && " "uv run pytest --cov=src --cov-report=term-missing" ) c.run(f"docker run --rm {image} sh -c '{cmd}'") @task def bump(c, part): """Bump version (patch/minor/major), commit, and tag.""" if part not in ("patch", "minor", "major"): raise SystemExit("Usage: inv bump ") result = c.run("git status --porcelain", hide=True) if result.stdout.strip(): raise SystemExit("Working tree is dirty. Commit or stash changes first.") text = PYPROJECT.read_text() m = re.search(r'version = "(\d+)\.(\d+)\.(\d+)"', text) if not m: raise SystemExit("Could not find version in pyproject.toml") major, minor, patch = int(m[1]), int(m[2]), int(m[3]) if part == "major": major, minor, patch = major + 1, 0, 0 elif part == "minor": minor, patch = minor + 1, 0 else: patch += 1 new_version = f"{major}.{minor}.{patch}" new_text = re.sub(r'(version = ")\d+\.\d+\.\d+(")', rf"\g<1>{new_version}\2", text) PYPROJECT.write_text(new_text) c.run(f'git add pyproject.toml && git commit -m "bump version to {new_version}"') c.run(f"git tag v{new_version}") print(f"Bumped to {new_version}") @task def clean(c): """Preview files to delete (safe mode).""" c.run("git clean -nfdx") if input("Delete? [y/N] ").lower() == "y": c.run("git clean -fdx")