Development Guide¶
This guide covers setting up a development environment and contributing to taskfile-help.
Development Setup¶
Prerequisites¶
- Python 3.11 or higher
- uv (recommended) or pip
- Git
Clone and Install¶
# Clone the repository
git clone https://github.com/royw/taskfile-help.git
cd taskfile-help
# Install with development dependencies using uv
uv sync --dev
# Or using pip
pip install -e ".[dev]"
# Install git hooks (optional but recommended)
task git:hooks:install
Git Hooks¶
The project uses git hooks to enforce commit standards and automate CHANGELOG updates.
Installation¶
# Install hooks (recommended for all contributors)
task git:hooks:install
# Uninstall hooks
task git:hooks:uninstall
# Test hooks
task git:hooks:test
Available Hooks¶
commit-msg - Enforces conventional commit format:
- Validates commit messages before commit is created
- Rejects invalid messages with helpful error
- Ensures all commits follow project standards
post-commit - Automates CHANGELOG updates:
- Parses conventional commit messages
- Adds entries to appropriate CHANGELOG sections
- Amends commit to include CHANGELOG changes
Conventional Commits¶
All commits must follow the conventional commit format:
Supported types:
feat:- New feature (added to CHANGELOG)fix:- Bug fix (added to CHANGELOG)docs:- Documentation changes (added to CHANGELOG)refactor:- Code refactoring (added to CHANGELOG)perf:- Performance improvements (added to CHANGELOG)test:- Test-related changeschore:- Maintenance tasksstyle:- Code style/formattingci:- CI/CD changesbuild:- Build system changesrevert:- Revert previous commits
Examples:
git commit -m "feat: add shell auto-completion support"
git commit -m "fix: resolve memory leak in parser"
git commit -m "docs: update installation guide"
git commit -m "test: add edge case tests for discovery"
See Git Hooks Documentation for complete documentation.
Development Dependencies¶
The project includes these development tools:
- pytest: Testing framework
- pytest-cov: Coverage reporting
- mypy: Static type checking
- ruff: Linting and formatting
- pylint: Additional linting
- pymarkdownlnt: Markdown linting
- deadcode: Dead code detection
- radon: Code complexity analysis
Project Structure¶
taskfile-help/
├── src/
│ └── taskfile_help/
│ ├── __init__.py
│ ├── __main__.py # Module entry point
│ ├── taskfile_help.py # Main entry point
│ ├── config.py # Configuration management
│ ├── discovery.py # Taskfile discovery
│ ├── parser.py # Taskfile parsing
│ └── output.py # Output formatting
├── tests/
│ ├── unit/ # Unit tests
│ │ ├── test_config.py
│ │ ├── test_discovery.py
│ │ ├── test_output.py
│ │ ├── test_parser.py
│ │ └── test_taskfile_help.py
│ └── e2e/ # End-to-end tests
│ └── test_cli.py
├── docs/ # Documentation
├── pyproject.toml # Project configuration
├── Taskfile.yml # Development tasks
└── README.md
Development Workflow¶
Using Task (Recommended)¶
The project includes a Taskfile with common development tasks:
# Show available tasks
task help
# Run all checks (format, lint, test)
task make
# Format code
task format
# Run linters
task lint
# Run tests
task test
# Run tests with coverage
task test:coverage
# Build documentation
task docs:build
# Serve documentation locally
task docs:serve
# Build distribution packages
task build
Manual Commands¶
If you prefer not to use Task:
# Format code
uv run ruff format src/
uv run ruff check --fix src/
# Type checking
uv run mypy src/
# Linting
uv run ruff check src/
uv run pylint src/
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=src/taskfile_help --cov-report=html
# Build package
uv build
Testing¶
Running Tests¶
# Run all tests
uv run pytest
# Run specific test file
uv run pytest tests/unit/test_parser.py
# Run specific test
uv run pytest tests/unit/test_parser.py::TestParseTaskfile::test_parse_simple_taskfile
# Run with coverage
uv run pytest --cov=src/taskfile_help --cov-report=html
# Run only unit tests
uv run pytest tests/unit/
# Run only e2e tests
uv run pytest tests/e2e/
Writing Tests¶
Unit Tests¶
from pathlib import Path
import pytest
from taskfile_help.parser import parse_taskfile
from taskfile_help.output import TextOutputter
def test_parse_simple_taskfile(tmp_path: Path) -> None:
"""Test parsing a simple Taskfile."""
taskfile = tmp_path / "Taskfile.yml"
taskfile.write_text("""version: '3'
tasks:
build:
desc: Build the project
cmds:
- echo "Building..."
""")
outputter = TextOutputter()
tasks = parse_taskfile(taskfile, "", outputter)
assert len(tasks) == 1
assert tasks[0] == ("Other", "build", "Build the project")
End-to-End Tests¶
E2E tests can use two approaches:
Direct main() calls (preferred for most tests)¶
- Simpler and faster
- Easier to inject dependencies and mock behavior
- Better for testing application logic
from taskfile_help.taskfile_help import main
from unittest.mock import patch
def test_main_taskfile(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test main taskfile display."""
taskfile = tmp_path / "Taskfile.yml"
taskfile.write_text("""version: '3'
tasks:
build:
desc: Build the project
""")
monkeypatch.chdir(tmp_path)
with patch("sys.stdout.isatty", return_value=False):
result = main(["taskfile-help"])
assert result == 0
Subprocess calls (for external access testing)¶
- Tests actual installed scripts and module invocation
- Validates package installation and entry points
- Use for testing:
python -m taskfile_help, console scripts, etc.
import subprocess
import sys
def test_module_invocation() -> None:
"""Test that module can be invoked with -m flag."""
result = subprocess.run(
[sys.executable, "-m", "taskfile_help", "-h"],
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0
assert "Dynamic Taskfile help generator" in result.stdout
Test Coverage¶
Aim for >90% code coverage. Check coverage report:
Code Style¶
Formatting¶
The project uses ruff for formatting:
Linting¶
Multiple linters ensure code quality:
# Ruff (fast Python linter)
uv run ruff check src/
# MyPy (type checking)
uv run mypy src/
# Pylint (additional checks)
uv run pylint src/
Type Hints¶
All code must include modern type hints (at least what python 3.11 supports (PEP 484, 563, 585, 604, 673, 646)):
def parse_taskfile(
filepath: Path,
namespace: str,
outputter: Outputter
) -> list[tuple[str, str, str]]:
"""Parse a Taskfile and extract tasks."""
...
Docstrings¶
Use Google-style docstrings:
def find_taskfile(search_dirs: list[Path]) -> Path | None:
"""Find a Taskfile in the given search directories.
Args:
search_dirs: List of directories to search
Returns:
Path to the Taskfile if found, None otherwise
Raises:
ValueError: If search_dirs is empty
"""
...
Making Changes¶
1. Create a Branch¶
2. Make Changes¶
- Write code following style guidelines
- Add tests for new functionality
- Update documentation as needed
- Ensure all tests pass
3. Run Checks¶
4. Commit Changes¶
Use conventional commit messages:
feat:New featurefix:Bug fixdocs:Documentation changestest:Test changesrefactor:Code refactoringchore:Maintenance tasks
5. Push and Create PR¶
Then create a pull request on GitHub.
Debugging¶
Using Print Statements¶
# Temporary debugging
print(f"DEBUG: tasks = {tasks}")
# Use outputter for consistent output
outputter.output_message(f"DEBUG: {value}")
Using Python Debugger¶
Running with Verbose Output¶
CHANGELOG Maintenance¶
The project uses automated CHANGELOG updates via git hooks.
Automatic Updates¶
When you commit with a conventional commit message, the post-commit hook automatically:
- Parses your commit message
- Adds an entry to the appropriate section in
CHANGELOG.md - Amends your commit to include the CHANGELOG update
Example workflow:
# Make your changes
git add src/taskfile_help/parser.py
# Commit with conventional format
git commit -m "feat: add dependency parsing support"
# Hook automatically updates CHANGELOG.md:
# ### Added
# - add dependency parsing support (abc123)
CHANGELOG Structure¶
The CHANGELOG.md follows Keep a Changelog format:
- [Unreleased] - Current development work (auto-updated by hooks)
- Added - New features (
feat:commits) - Changed - Changes in existing functionality (
docs:,refactor:,perf:commits) - Fixed - Bug fixes (
fix:commits) - [Version] - Released versions with date
Manual Updates¶
You can manually edit CHANGELOG.md when needed:
- Reorganizing entries for clarity
- Adding breaking changes notes
- Preparing for a release
- Adding migration guides
Release Process¶
The project uses a two-step release workflow:
Step 1: Bump Version¶
This increments the patch version in pyproject.toml and reminds you to run the release task.
Step 2: Prepare Release¶
This comprehensive task:
- Verifies release readiness:
- Checks for uncommitted changes
- Verifies version changed from last release
-
Confirms CHANGELOG has unreleased content
-
Runs full test suite:
- Executes
task make(all checks, tests, build, docs) -
Ensures quality before release
-
Prepares release:
- Moves
[Unreleased]content to new version section - Creates empty
[Unreleased]section for next development - Commits changes with
--no-verify -
Builds release package
-
Shows next steps:
- Tag release:
task release:tag - Or continue development
Complete Release Workflow¶
# Development cycle - commits auto-update CHANGELOG
git commit -m "feat: add new feature"
git commit -m "fix: resolve bug"
# Ready to release
task version:bump # 0.1.0 → 0.1.1
task release # Verify, test, prepare, build
# Publish release (optional)
task release:tag # Create and push git tag
# Or continue development for next release
git commit -m "feat: next feature" # Auto-updates CHANGELOG
Manual CHANGELOG Edits¶
You can manually edit CHANGELOG.md before running task release:
- Reorganize entries for clarity
- Add breaking changes notes
- Include migration guides
- Polish descriptions
Publishing to PyPI¶
After running task release:tag, GitHub Actions will automatically:
- Publish the package to PyPI
- Create a GitHub release from the tag
No manual publishing step required!
Documentation¶
Building Documentation¶
# Build docs
task docs:build
# Serve docs locally
task docs:serve
# Open in browser
open http://127.0.0.1:8000
Writing Documentation¶
- Use Markdown for all documentation
- Include code examples
- Add diagrams where helpful (Mermaid supported)
- Keep language clear and concise
Getting Help¶
- Check existing issues on GitHub
- Review documentation
- Ask questions in discussions
- Join community chat (if available)
Code Review Guidelines¶
When reviewing PRs:
- Check code style and formatting
- Verify tests are included
- Ensure documentation is updated
- Test functionality locally
- Provide constructive feedback