tasktree 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tasktree-0.0.2/.github/workflows/release.yml +62 -0
- tasktree-0.0.2/.github/workflows/test.yml +61 -0
- tasktree-0.0.2/.gitignore +161 -0
- tasktree-0.0.2/CLAUDE.md +69 -0
- tasktree-0.0.2/PKG-INFO +375 -0
- tasktree-0.0.2/README.md +361 -0
- tasktree-0.0.2/example/source.txt +1 -0
- tasktree-0.0.2/example/tasktree.yaml +15 -0
- tasktree-0.0.2/pyproject.toml +29 -0
- tasktree-0.0.2/src/__init__.py +0 -0
- tasktree-0.0.2/src/tasktree/__init__.py +42 -0
- tasktree-0.0.2/src/tasktree/cli.py +506 -0
- tasktree-0.0.2/src/tasktree/executor.py +378 -0
- tasktree-0.0.2/src/tasktree/graph.py +139 -0
- tasktree-0.0.2/src/tasktree/hasher.py +74 -0
- tasktree-0.0.2/src/tasktree/parser.py +300 -0
- tasktree-0.0.2/src/tasktree/state.py +119 -0
- tasktree-0.0.2/src/tasktree/tasks.py +8 -0
- tasktree-0.0.2/src/tasktree/types.py +130 -0
- tasktree-0.0.2/tasktree.yaml +48 -0
- tasktree-0.0.2/tests/integration/test_clean_state.py +162 -0
- tasktree-0.0.2/tests/integration/test_cli_options.py +353 -0
- tasktree-0.0.2/tests/integration/test_dependency_execution.py +209 -0
- tasktree-0.0.2/tests/integration/test_end_to_end.py +129 -0
- tasktree-0.0.2/tests/integration/test_input_detection.py +180 -0
- tasktree-0.0.2/tests/integration/test_missing_outputs.py +114 -0
- tasktree-0.0.2/tests/integration/test_nested_imports.py +246 -0
- tasktree-0.0.2/tests/integration/test_state_persistence.py +177 -0
- tasktree-0.0.2/tests/integration/test_working_directory.py +105 -0
- tasktree-0.0.2/tests/unit/test_cli.py +109 -0
- tasktree-0.0.2/tests/unit/test_executor.py +583 -0
- tasktree-0.0.2/tests/unit/test_graph.py +195 -0
- tasktree-0.0.2/tests/unit/test_hasher.py +76 -0
- tasktree-0.0.2/tests/unit/test_parser.py +884 -0
- tasktree-0.0.2/tests/unit/test_state.py +114 -0
- tasktree-0.0.2/tests/unit/test_tasks.py +18 -0
- tasktree-0.0.2/tests/unit/test_types.py +278 -0
- tasktree-0.0.2/uv.lock +226 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: Release to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*.*.*' # Triggers on tags like v1.0.0, v1.2.3, etc.
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write # For creating GitHub release
|
|
13
|
+
id-token: write # For trusted publishing to PyPI
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0 # Full history for proper version detection
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: '3.11'
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v4
|
|
28
|
+
|
|
29
|
+
- name: Extract version from tag
|
|
30
|
+
id: get_version
|
|
31
|
+
run: |
|
|
32
|
+
VERSION=${GITHUB_REF#refs/tags/v}
|
|
33
|
+
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
|
34
|
+
echo "Releasing version $VERSION"
|
|
35
|
+
|
|
36
|
+
- name: Update version in pyproject.toml
|
|
37
|
+
run: |
|
|
38
|
+
sed -i 's/version = ".*"/version = "${{ steps.get_version.outputs.VERSION }}"/' pyproject.toml
|
|
39
|
+
cat pyproject.toml | grep version
|
|
40
|
+
|
|
41
|
+
- name: Build package
|
|
42
|
+
run: uv build
|
|
43
|
+
|
|
44
|
+
- name: Verify wheel contents
|
|
45
|
+
run: |
|
|
46
|
+
unzip -l dist/*.whl
|
|
47
|
+
ls -lh dist/
|
|
48
|
+
|
|
49
|
+
- name: Create GitHub Release
|
|
50
|
+
uses: softprops/action-gh-release@v2
|
|
51
|
+
with:
|
|
52
|
+
files: dist/*
|
|
53
|
+
generate_release_notes: true
|
|
54
|
+
draft: false
|
|
55
|
+
prerelease: false
|
|
56
|
+
|
|
57
|
+
- name: Publish to PyPI
|
|
58
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
59
|
+
with:
|
|
60
|
+
# Uses trusted publisher authentication (no API token needed)
|
|
61
|
+
# Configure at https://pypi.org/manage/account/publishing/
|
|
62
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, develop ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main, develop ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
16
|
+
python-version: ['3.11', '3.12']
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v4
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: |
|
|
31
|
+
uv pip install --system -e ".[dev]"
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: |
|
|
35
|
+
python -m pytest tests/ -v --tb=short
|
|
36
|
+
|
|
37
|
+
- name: Test CLI commands
|
|
38
|
+
run: |
|
|
39
|
+
python -m tasktree.cli --help
|
|
40
|
+
python -m tasktree.cli --list || true
|
|
41
|
+
|
|
42
|
+
lint:
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
|
|
47
|
+
- name: Set up Python
|
|
48
|
+
uses: actions/setup-python@v5
|
|
49
|
+
with:
|
|
50
|
+
python-version: '3.12'
|
|
51
|
+
|
|
52
|
+
- name: Install uv
|
|
53
|
+
uses: astral-sh/setup-uv@v4
|
|
54
|
+
|
|
55
|
+
- name: Install dependencies
|
|
56
|
+
run: |
|
|
57
|
+
uv pip install --system -e ".[dev]"
|
|
58
|
+
|
|
59
|
+
- name: Check code can be imported
|
|
60
|
+
run: |
|
|
61
|
+
python -c "from tasktree import Executor, Recipe, Task; print('Import successful')"
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.nox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
*.py,cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
cover/
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Django stuff:
|
|
57
|
+
*.log
|
|
58
|
+
local_settings.py
|
|
59
|
+
db.sqlite3
|
|
60
|
+
db.sqlite3-journal
|
|
61
|
+
|
|
62
|
+
# Flask stuff:
|
|
63
|
+
instance/
|
|
64
|
+
.webassets-cache
|
|
65
|
+
|
|
66
|
+
# Scrapy stuff:
|
|
67
|
+
.scrapy
|
|
68
|
+
|
|
69
|
+
# Sphinx documentation
|
|
70
|
+
docs/_build/
|
|
71
|
+
|
|
72
|
+
# PyBuilder
|
|
73
|
+
.pybuilder/
|
|
74
|
+
target/
|
|
75
|
+
|
|
76
|
+
# Jupyter Notebook
|
|
77
|
+
.ipynb_checkpoints
|
|
78
|
+
|
|
79
|
+
# IPython
|
|
80
|
+
profile_default/
|
|
81
|
+
ipython_config.py
|
|
82
|
+
|
|
83
|
+
# pyenv
|
|
84
|
+
.python-version
|
|
85
|
+
|
|
86
|
+
# pipenv
|
|
87
|
+
Pipfile.lock
|
|
88
|
+
|
|
89
|
+
# PEP 582
|
|
90
|
+
__pypackages__/
|
|
91
|
+
|
|
92
|
+
# Celery stuff
|
|
93
|
+
celerybeat-schedule
|
|
94
|
+
celerybeat.pid
|
|
95
|
+
|
|
96
|
+
# SageMath parsed files
|
|
97
|
+
*.sage.py
|
|
98
|
+
|
|
99
|
+
# Environments
|
|
100
|
+
.env
|
|
101
|
+
.venv
|
|
102
|
+
env/
|
|
103
|
+
venv/
|
|
104
|
+
ENV/
|
|
105
|
+
env.bak/
|
|
106
|
+
venv.bak/
|
|
107
|
+
|
|
108
|
+
# Spyder project settings
|
|
109
|
+
.spyderproject
|
|
110
|
+
.spyproject
|
|
111
|
+
|
|
112
|
+
# Rope project settings
|
|
113
|
+
.ropeproject
|
|
114
|
+
|
|
115
|
+
# mkdocs documentation
|
|
116
|
+
/site
|
|
117
|
+
|
|
118
|
+
# mypy
|
|
119
|
+
.mypy_cache/
|
|
120
|
+
.dmypy.json
|
|
121
|
+
dmypy.json
|
|
122
|
+
|
|
123
|
+
# Pyre type checker
|
|
124
|
+
.pyre/
|
|
125
|
+
|
|
126
|
+
# pytype static type analyzer
|
|
127
|
+
.pytype/
|
|
128
|
+
|
|
129
|
+
# Cython debug symbols
|
|
130
|
+
cython_debug/
|
|
131
|
+
|
|
132
|
+
# IDEs
|
|
133
|
+
.idea/
|
|
134
|
+
.vscode/
|
|
135
|
+
*.swp
|
|
136
|
+
*.swo
|
|
137
|
+
*~
|
|
138
|
+
.project
|
|
139
|
+
.pydevproject
|
|
140
|
+
.settings/
|
|
141
|
+
|
|
142
|
+
# OS
|
|
143
|
+
.DS_Store
|
|
144
|
+
.DS_Store?
|
|
145
|
+
._*
|
|
146
|
+
.Spotlight-V100
|
|
147
|
+
.Trashes
|
|
148
|
+
ehthumbs.db
|
|
149
|
+
Thumbs.db
|
|
150
|
+
Desktop.ini
|
|
151
|
+
|
|
152
|
+
# Task Tree specific
|
|
153
|
+
.tasktree-state
|
|
154
|
+
|
|
155
|
+
# Example outputs (keep source files but ignore generated outputs)
|
|
156
|
+
example/output.txt
|
|
157
|
+
example/archive.tar.gz
|
|
158
|
+
example/.tasktree-state
|
|
159
|
+
|
|
160
|
+
# UV lock file (optional - some prefer to commit this)
|
|
161
|
+
# uv.lock
|
tasktree-0.0.2/CLAUDE.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Task Tree (tt) is a task automation tool that combines simple command execution with intelligent dependency tracking and incremental execution. The project is a Python application built with a focus on:
|
|
8
|
+
|
|
9
|
+
- **Intelligent incremental execution**: Tasks only run when necessary based on input changes, dependency updates, or task definition changes
|
|
10
|
+
- **YAML-based task definition**: Tasks are defined in `tasktree.yaml` or `tt.yaml` files with dependencies, inputs, outputs, and commands
|
|
11
|
+
- **Automatic input inheritance**: Tasks automatically inherit inputs from dependencies
|
|
12
|
+
- **Parameterized tasks**: Tasks can accept typed arguments with defaults
|
|
13
|
+
- **File imports**: Task definitions can be split across multiple files and namespaced
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
### Core Components
|
|
18
|
+
|
|
19
|
+
- **`src/tasktree/tasks.py`**: Core task execution logic using subprocess to run shell commands
|
|
20
|
+
- **`src/tasktree/cli.py`**: Command-line interface (currently minimal)
|
|
21
|
+
- **`main.py`**: Entry point for the application
|
|
22
|
+
- **`tests/unit/test_tasks.py`**: Unit tests using Python's unittest framework
|
|
23
|
+
|
|
24
|
+
### Key Dependencies
|
|
25
|
+
|
|
26
|
+
- **PyYAML**: For recipe parsing
|
|
27
|
+
- **Typer, Click, Rich**: For CLI (mentioned in README but not yet implemented)
|
|
28
|
+
- **graphlib.TopologicalSorter**: For dependency resolution
|
|
29
|
+
- **pathlib**: For file operations and glob expansion
|
|
30
|
+
|
|
31
|
+
## Development Commands
|
|
32
|
+
|
|
33
|
+
### Testing
|
|
34
|
+
```bash
|
|
35
|
+
python -m pytest tests/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Running the Application
|
|
39
|
+
```bash
|
|
40
|
+
python main.py
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Package Management
|
|
44
|
+
This project uses `uv` for dependency management (indicated by `uv.lock` file).
|
|
45
|
+
|
|
46
|
+
## State Management
|
|
47
|
+
|
|
48
|
+
The application uses a `.tasktree-state` file at the project root to track:
|
|
49
|
+
- When tasks last ran
|
|
50
|
+
- Timestamps of input files at execution time
|
|
51
|
+
- Task hashes based on command, outputs, and working directory
|
|
52
|
+
|
|
53
|
+
## Testing Approach
|
|
54
|
+
|
|
55
|
+
The project uses Python's built-in `unittest` framework with mocking via `unittest.mock`. Tests focus on verifying subprocess calls for task execution.
|
|
56
|
+
|
|
57
|
+
## Task Definition Format
|
|
58
|
+
|
|
59
|
+
Tasks are defined in YAML with the following structure:
|
|
60
|
+
```yaml
|
|
61
|
+
task-name:
|
|
62
|
+
desc: Description (optional)
|
|
63
|
+
deps: [dependency-tasks]
|
|
64
|
+
inputs: [glob-patterns]
|
|
65
|
+
outputs: [glob-patterns]
|
|
66
|
+
working_dir: execution-directory
|
|
67
|
+
args: [typed-parameters]
|
|
68
|
+
cmd: shell-command
|
|
69
|
+
```
|
tasktree-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tasktree
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A task automation tool with incremental execution
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: click>=8.1.0
|
|
7
|
+
Requires-Dist: colorama>=0.4.6
|
|
8
|
+
Requires-Dist: pyyaml>=6.0
|
|
9
|
+
Requires-Dist: rich>=13.0.0
|
|
10
|
+
Requires-Dist: typer>=0.9.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=9.0.2; extra == 'dev'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Task Tree (tt)
|
|
16
|
+
|
|
17
|
+
[](https://github.com/kevinchannon/task-tree/actions/workflows/test.yml)
|
|
18
|
+
|
|
19
|
+
A task automation tool that combines simple command execution with dependency tracking and incremental execution.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### From PyPI (Recommended)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pipx install tasktree
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### From Source
|
|
30
|
+
|
|
31
|
+
For the latest unreleased version from GitHub:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pipx install git+https://github.com/kevinchannon/task-tree.git
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or to install from a local clone:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git clone https://github.com/kevinchannon/task-tree.git
|
|
41
|
+
cd tasktree
|
|
42
|
+
pipx install .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
Create a `tasktree.yaml` (or `tt.yaml`) in your project:
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
build:
|
|
51
|
+
desc: Compile the application
|
|
52
|
+
outputs: [target/release/bin]
|
|
53
|
+
cmd: cargo build --release
|
|
54
|
+
|
|
55
|
+
test:
|
|
56
|
+
desc: Run tests
|
|
57
|
+
deps: [build]
|
|
58
|
+
cmd: cargo test
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Run tasks:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
tt build # Build the application
|
|
65
|
+
tt test # Run tests (builds first if needed)
|
|
66
|
+
tt --list # Show all available tasks
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Core Concepts
|
|
70
|
+
|
|
71
|
+
### Intelligent Incremental Execution
|
|
72
|
+
|
|
73
|
+
Task Tree only runs tasks when necessary. A task executes if:
|
|
74
|
+
|
|
75
|
+
- Its definition (command, outputs, working directory) has changed
|
|
76
|
+
- Any input files have changed since the last run
|
|
77
|
+
- Any dependencies have re-run
|
|
78
|
+
- It has never been executed before
|
|
79
|
+
- It has no inputs or outputs (always runs)
|
|
80
|
+
|
|
81
|
+
### Automatic Input Inheritance
|
|
82
|
+
|
|
83
|
+
Tasks automatically inherit inputs from dependencies, eliminating redundant declarations:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
build:
|
|
87
|
+
outputs: [dist/app]
|
|
88
|
+
cmd: go build -o dist/app
|
|
89
|
+
|
|
90
|
+
package:
|
|
91
|
+
deps: [build]
|
|
92
|
+
outputs: [dist/app.tar.gz]
|
|
93
|
+
cmd: tar czf dist/app.tar.gz dist/app
|
|
94
|
+
# Automatically tracks dist/app as an input
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Single State File
|
|
98
|
+
|
|
99
|
+
All state lives in `.tasktree-state` at your project root. Stale entries are automatically pruned—no manual cleanup needed.
|
|
100
|
+
|
|
101
|
+
## Task Definition
|
|
102
|
+
|
|
103
|
+
### Basic Structure
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
task-name:
|
|
107
|
+
desc: Human-readable description (optional)
|
|
108
|
+
deps: [other-task] # Task dependencies
|
|
109
|
+
inputs: [src/**/*.go] # Explicit input files (glob patterns)
|
|
110
|
+
outputs: [dist/binary] # Output files (glob patterns)
|
|
111
|
+
working_dir: subproject/ # Execution directory (default: project root)
|
|
112
|
+
args: [param1, param2:path=default] # Task parameters
|
|
113
|
+
cmd: go build -o dist/binary # Command to execute
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Commands
|
|
117
|
+
|
|
118
|
+
Multi-line commands using YAML literal blocks:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
deploy:
|
|
122
|
+
cmd: |
|
|
123
|
+
mkdir -p dist
|
|
124
|
+
cp build/* dist/
|
|
125
|
+
rsync -av dist/ server:/opt/app/
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or folded blocks for long single-line commands:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
compile:
|
|
132
|
+
cmd: >
|
|
133
|
+
gcc -o bin/app
|
|
134
|
+
src/*.c
|
|
135
|
+
-I include
|
|
136
|
+
-L lib -lm
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Parameterised Tasks
|
|
140
|
+
|
|
141
|
+
Tasks can accept arguments with optional defaults:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
deploy:
|
|
145
|
+
args: [environment, region=eu-west-1]
|
|
146
|
+
deps: [build]
|
|
147
|
+
cmd: |
|
|
148
|
+
aws s3 cp dist/app.zip s3://{{environment}}-{{region}}/
|
|
149
|
+
aws lambda update-function-code --function-name app-{{environment}}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Invoke with: `tt deploy production` or `tt deploy staging us-east-1` or `tt deploy staging region=us-east-1`.
|
|
153
|
+
|
|
154
|
+
Arguments may be typed, or not and have a default, or not. Valid argument types are:
|
|
155
|
+
|
|
156
|
+
* int - an integer value (e.g. 0, 10, 123, -9)
|
|
157
|
+
* float - a floating point value (e.g. 1.234, -3.1415, 2e-4)
|
|
158
|
+
* bool - Boolean-ish value (e.g. true, false, yes, no, 1, 0, etc)
|
|
159
|
+
* str - a string
|
|
160
|
+
* path - a pathlike string
|
|
161
|
+
* datetime - a datetime in the format 2025-12-17T16:56:12
|
|
162
|
+
* ip - an ip address (v4 or v6)
|
|
163
|
+
* ipv4 - an IPv4 value
|
|
164
|
+
* ipv6 - an IPv6 value
|
|
165
|
+
* email - String validated, but not positively confirmed to be a reachable address.
|
|
166
|
+
* hostname - looks like a hostname, resolution of the name is not attempted as part of the validation
|
|
167
|
+
|
|
168
|
+
Different argument values are tracked separately—tasks re-run when invoked with new arguments.
|
|
169
|
+
|
|
170
|
+
## File Imports
|
|
171
|
+
|
|
172
|
+
Split task definitions across multiple files for better organisation:
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
# tasktree.yaml
|
|
176
|
+
import:
|
|
177
|
+
- file: build/tasks.yml
|
|
178
|
+
as: build
|
|
179
|
+
- file: deploy/tasks.yml
|
|
180
|
+
as: deploy
|
|
181
|
+
|
|
182
|
+
test:
|
|
183
|
+
deps: [build.compile, build.test-compile]
|
|
184
|
+
cmd: ./run-tests.sh
|
|
185
|
+
|
|
186
|
+
ci:
|
|
187
|
+
deps: [build.all, test, deploy.staging]
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Imported tasks are namespaced and can be referenced as dependencies. Each imported file is self-contained—it cannot depend on tasks in the importing file.
|
|
191
|
+
|
|
192
|
+
## Glob Patterns
|
|
193
|
+
|
|
194
|
+
Input and output patterns support standard glob syntax:
|
|
195
|
+
|
|
196
|
+
- `src/*.rs` — All Rust files in `src/`
|
|
197
|
+
- `src/**/*.rs` — All Rust files recursively
|
|
198
|
+
- `{file1,file2}` — Specific files
|
|
199
|
+
- `**/*.{js,ts}` — Multiple extensions recursively
|
|
200
|
+
|
|
201
|
+
## State Management
|
|
202
|
+
|
|
203
|
+
### How State Works
|
|
204
|
+
|
|
205
|
+
Each task is identified by a hash of its definition (command, outputs, working directory). State tracks:
|
|
206
|
+
|
|
207
|
+
- When the task last ran
|
|
208
|
+
- Timestamps of input files at that time
|
|
209
|
+
|
|
210
|
+
Tasks are re-run when their definition changes or inputs are newer than the last run.
|
|
211
|
+
|
|
212
|
+
### What's Not In The Hash
|
|
213
|
+
|
|
214
|
+
Changes to these don't invalidate cached state:
|
|
215
|
+
|
|
216
|
+
- Task name (tasks can be renamed freely)
|
|
217
|
+
- Description
|
|
218
|
+
- Dependencies (only affects execution order)
|
|
219
|
+
- Explicit inputs (tracked by timestamp, not definition)
|
|
220
|
+
|
|
221
|
+
### Automatic Cleanup
|
|
222
|
+
|
|
223
|
+
At the start of each invocation, state is checked for invalid task hashes and non-existent ones are automatically removed. Delete a task from your recipe file and its state disappears the next time you run `tt <cmd>`
|
|
224
|
+
|
|
225
|
+
## Example: Full Build Pipeline
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
imports:
|
|
229
|
+
- file: common/docker.yml
|
|
230
|
+
as: docker
|
|
231
|
+
|
|
232
|
+
compile:
|
|
233
|
+
desc: Build application binaries
|
|
234
|
+
outputs: [target/release/app]
|
|
235
|
+
cmd: cargo build --release
|
|
236
|
+
|
|
237
|
+
test-unit:
|
|
238
|
+
desc: Run unit tests
|
|
239
|
+
deps: [compile]
|
|
240
|
+
cmd: cargo test
|
|
241
|
+
|
|
242
|
+
package:
|
|
243
|
+
desc: Create distribution archive
|
|
244
|
+
deps: [compile]
|
|
245
|
+
outputs: [dist/app-{{version}}.tar.gz]
|
|
246
|
+
args: [version]
|
|
247
|
+
cmd: |
|
|
248
|
+
mkdir -p dist
|
|
249
|
+
tar czf dist/app-{{version}}.tar.gz \
|
|
250
|
+
target/release/app \
|
|
251
|
+
config/ \
|
|
252
|
+
migrations/
|
|
253
|
+
|
|
254
|
+
deploy:
|
|
255
|
+
desc: Deploy to environment
|
|
256
|
+
deps: [package, docker.build-runtime]
|
|
257
|
+
args: [environment, version]
|
|
258
|
+
cmd: |
|
|
259
|
+
scp dist/app-{{version}}.tar.gz {{environment}}:/opt/
|
|
260
|
+
ssh {{environment}} /opt/deploy.sh {{version}}
|
|
261
|
+
|
|
262
|
+
integration-test:
|
|
263
|
+
desc: Run integration tests against deployed environment
|
|
264
|
+
deps: [deploy]
|
|
265
|
+
args: [environment, version]
|
|
266
|
+
cmd: pytest tests/integration/ --env={{environment}}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Run the full pipeline:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
tt integration-test staging version=1.2.3
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This will:
|
|
276
|
+
1. Compile if sources have changed
|
|
277
|
+
2. Run unit tests if compilation ran
|
|
278
|
+
3. Package if compilation ran or version argument is new
|
|
279
|
+
4. Build Docker runtime (from imported file) if needed
|
|
280
|
+
5. Deploy if package or Docker image changed
|
|
281
|
+
6. Run integration tests (always runs)
|
|
282
|
+
|
|
283
|
+
## Implementation Notes
|
|
284
|
+
|
|
285
|
+
Built with Python 3.11+ using:
|
|
286
|
+
|
|
287
|
+
- **PyYAML** for recipe parsing
|
|
288
|
+
- **Typer**, **Click**, **Rich** for CLI
|
|
289
|
+
- **graphlib.TopologicalSorter** for dependency resolution
|
|
290
|
+
- **pathlib** for file operations and glob expansion
|
|
291
|
+
|
|
292
|
+
State file uses JSON format for simplicity and standard library compatibility.
|
|
293
|
+
|
|
294
|
+
## Development
|
|
295
|
+
|
|
296
|
+
### Setup Development Environment
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# Clone repository
|
|
300
|
+
git clone https://github.com/kevinchannon/task-tree.git
|
|
301
|
+
cd tasktree
|
|
302
|
+
|
|
303
|
+
# Install uv (if not already installed)
|
|
304
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
305
|
+
|
|
306
|
+
# Install dependencies
|
|
307
|
+
uv sync
|
|
308
|
+
|
|
309
|
+
# Install in editable mode
|
|
310
|
+
pipx install -e .
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Running Tests
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Run all tests
|
|
317
|
+
uv run pytest
|
|
318
|
+
|
|
319
|
+
# Run with verbose output
|
|
320
|
+
uv run pytest -v
|
|
321
|
+
|
|
322
|
+
# Run specific test file
|
|
323
|
+
uv run pytest tests/unit/test_executor.py
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Using Task Tree for Development
|
|
327
|
+
|
|
328
|
+
The repository includes a `tasktree.yaml` with development tasks:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
tt test # Run tests
|
|
332
|
+
tt build # Build wheel package
|
|
333
|
+
tt install-dev # Install package in development mode
|
|
334
|
+
tt clean # Remove build artifacts
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Releasing
|
|
338
|
+
|
|
339
|
+
New releases are created by pushing version tags to GitHub. The release workflow automatically:
|
|
340
|
+
- Builds wheel and source distributions
|
|
341
|
+
- Creates a GitHub Release with artifacts
|
|
342
|
+
- Publishes to PyPI via trusted publishing
|
|
343
|
+
|
|
344
|
+
### Release Process
|
|
345
|
+
|
|
346
|
+
1. Ensure main branch is ready:
|
|
347
|
+
```bash
|
|
348
|
+
git checkout main
|
|
349
|
+
git pull
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
2. Create and push a version tag:
|
|
353
|
+
```bash
|
|
354
|
+
git tag v1.0.0
|
|
355
|
+
git push origin v1.0.0
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
3. GitHub Actions will automatically:
|
|
359
|
+
- Extract version from tag (e.g., `v1.0.0` → `1.0.0`)
|
|
360
|
+
- Update `pyproject.toml` with the version
|
|
361
|
+
- Build wheel and sdist
|
|
362
|
+
- Create GitHub Release
|
|
363
|
+
- Publish to PyPI
|
|
364
|
+
|
|
365
|
+
4. Verify the release:
|
|
366
|
+
- GitHub: https://github.com/kevinchannon/task-tree/releases
|
|
367
|
+
- PyPI: https://pypi.org/kevinchannon/tasktree/
|
|
368
|
+
- Test: `pipx install --force tasktree`
|
|
369
|
+
|
|
370
|
+
### Version Numbering
|
|
371
|
+
|
|
372
|
+
Follow semantic versioning:
|
|
373
|
+
- `v1.0.0` - Major release (breaking changes)
|
|
374
|
+
- `v1.1.0` - Minor release (new features, backward compatible)
|
|
375
|
+
- `v1.1.1` - Patch release (bug fixes)
|