tasktree 0.0.19__tar.gz → 0.0.21__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.19 → tasktree-0.0.21}/.claude/settings.local.json +4 -1
- {tasktree-0.0.19 → tasktree-0.0.21}/.gitignore +5 -2
- tasktree-0.0.21/CLAUDE.md +220 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/PKG-INFO +364 -1
- {tasktree-0.0.19 → tasktree-0.0.21}/README.md +363 -0
- tasktree-0.0.21/__init__.py +3 -0
- tasktree-0.0.21/example/tasktree.yaml +64 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/pyproject.toml +1 -1
- {tasktree-0.0.19 → tasktree-0.0.21}/schema/README.md +24 -6
- {tasktree-0.0.19 → tasktree-0.0.21}/schema/tasktree-schema.json +119 -38
- tasktree-0.0.21/src/__init__.py +4 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/__init__.py +6 -1
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/cli.py +119 -33
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/docker.py +87 -53
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/executor.py +208 -131
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/graph.py +176 -64
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/hasher.py +68 -19
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/parser.py +408 -233
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/state.py +46 -17
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/substitution.py +193 -61
- {tasktree-0.0.19 → tasktree-0.0.21}/src/tasktree/types.py +50 -12
- {tasktree-0.0.19 → tasktree-0.0.21}/tasktree.yaml +1 -1
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/e2e/__init__.py +4 -1
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/e2e/test_docker_basic.py +20 -5
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/e2e/test_docker_environment.py +20 -5
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/e2e/test_docker_ownership.py +16 -4
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/e2e/test_docker_volumes.py +24 -6
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/e2e/test_non_docker.py +16 -4
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_arg_choices.py +48 -12
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_arg_min_max.py +60 -15
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_builtin_variables.py +52 -13
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_clean_state.py +28 -7
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_cli_options.py +143 -36
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_dependency_execution.py +30 -8
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_dependency_outputs.py +40 -10
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_docker_build_args.py +12 -3
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_end_to_end.py +20 -5
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_exported_args.py +51 -13
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_input_detection.py +24 -6
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_missing_outputs.py +9 -2
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_nested_imports.py +24 -6
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_parameterized_deps_execution.py +24 -6
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_parameterized_deps_templates.py +32 -8
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_private_tasks_execution.py +40 -10
- tasktree-0.0.21/tests/integration/test_self_references.py +1829 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_state_persistence.py +24 -6
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_variables.py +108 -27
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_working_directory.py +24 -6
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_cli.py +92 -23
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_dependency_parsing.py +76 -19
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_docker.py +183 -46
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_environment_tracking.py +84 -21
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_executor.py +197 -47
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_graph.py +197 -15
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_hasher.py +43 -10
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_list_formatting.py +164 -42
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_parameterized_graph.py +44 -11
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_parser.py +1224 -210
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_private_tasks.py +28 -7
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_state.py +34 -7
- tasktree-0.0.21/tests/unit/test_substitution.py +1461 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/unit/test_types.py +144 -36
- tasktree-0.0.19/CLAUDE.md +0 -70
- tasktree-0.0.19/example/tasktree.yaml +0 -37
- tasktree-0.0.19/src/__init__.py +0 -0
- tasktree-0.0.19/tests/unit/test_substitution.py +0 -721
- {tasktree-0.0.19/.github/ISSUE_TEMPLATES → tasktree-0.0.21/.github/ISSUE_TEMPLATE}/bug_report.yml +0 -0
- {tasktree-0.0.19/.github/ISSUE_TEMPLATES → tasktree-0.0.21/.github/ISSUE_TEMPLATE}/feature_request.yml +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/.github/workflows/claude-code-review.yml +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/.github/workflows/claude.yml +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/.github/workflows/release.yml +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/.github/workflows/test.yml +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/.github/workflows/validate-pipx-install.yml +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/.python-version +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/example/source.txt +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/schema/vscode-settings-snippet.json +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/tests/integration/test_parameterized_dependencies.yaml +0 -0
- {tasktree-0.0.19 → tasktree-0.0.21}/uv.lock +0 -0
|
@@ -154,5 +154,8 @@ example/output.txt
|
|
|
154
154
|
example/archive.tar.gz
|
|
155
155
|
example/.tasktree-state
|
|
156
156
|
|
|
157
|
-
#
|
|
158
|
-
|
|
157
|
+
# Requirements files copied from GitHub issues (for driving Claude Code)
|
|
158
|
+
requirements/*
|
|
159
|
+
|
|
160
|
+
.athena-cache
|
|
161
|
+
|
|
@@ -0,0 +1,220 @@
|
|
|
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 and constraints (int, float, bool, str, path, datetime, hostname, email, IP addresses)
|
|
13
|
+
- **File imports**: Task definitions can be split across multiple files and namespaced
|
|
14
|
+
- **Environment definitions**: Named execution environments (shell with custom configuration, or Docker containers with full image building support)
|
|
15
|
+
- **Template substitution**: Rich variable system with access to arguments, environment variables, built-in variables, dependency outputs, and task inputs/outputs
|
|
16
|
+
- **Docker support**: Full Docker integration with volume mounts, port mappings, build arguments, and user mapping
|
|
17
|
+
- **Named inputs/outputs**: Reference specific inputs and outputs by name for better clarity and DRY principles
|
|
18
|
+
|
|
19
|
+
## IMPORTANT! Development philosophies
|
|
20
|
+
|
|
21
|
+
### Small, incremental changes
|
|
22
|
+
The project team requires that each commit contains a small number of changes. Ideally, just the addition of a new line or statement in the code and an accompanying unit test (or tests) that validate the functionality of that line.
|
|
23
|
+
|
|
24
|
+
Tickets and features should be iteratively broken down until they can be implemented as a series of small commits.
|
|
25
|
+
|
|
26
|
+
When prompting the user to move to the next stage, estimate the size of the work and indicate whether you think there is sufficient usage to implement the next stage, or not. ("estimated required tokens: X, remaining usage in this session: Y tokens")
|
|
27
|
+
|
|
28
|
+
#### Local Claude Code work
|
|
29
|
+
When working locally on a user's machine, Claude Code should NEVER make commits - only stop and ask the user to review and commit, before carrying on with the next incremental change.
|
|
30
|
+
|
|
31
|
+
#### GitHub Claude Code integration work
|
|
32
|
+
When working as a GitHub agent, claude should still BREAK DOWN THE TASK into small, incremental commits, but commit those changes to the feature branch as they are made. GitHub integration Claude Code does not need to ask for permission to commit each change.
|
|
33
|
+
|
|
34
|
+
### Write tests, not ad hoc test scripts
|
|
35
|
+
If you are checking that a feature you are implementing has been implemented correctly, DO NOT write a bespoke test script to check the output of the app with the new functionality. INSTEAD, write a unit/integration/end-to-end test that will confirm you have correctly implemented the feature and RUN JUST THAT TEST. If it passes, you have implemented things correctly; and you can either carry on with additional parts of the feature, or run all the tests to ensure no regressions.
|
|
36
|
+
|
|
37
|
+
It is still permissible to write and run an ad hoc script to investigate/confirm the current behaviour. Although, it is better to first search for a test that does the thing that you're investigating. If one exists and is passing: then the app does the thing.
|
|
38
|
+
|
|
39
|
+
### Testas we go!
|
|
40
|
+
We do not plan to implement all the code (maybe even with unit tests) and then write a bunch of integration tests. We PLAN END-TO-END incremental changes. This will involve writing high-level test of the functionality as early as possible, to ensure that the new feature is progressing as expected.
|
|
41
|
+
|
|
42
|
+
### Try to be efficient with token usage
|
|
43
|
+
Your sponsor is not made of money! Try to minimise token useage, so that we can maximise the effectiveness of Claude Code on a features per token basis. Obviously, if a thing needs doing and it takes a bunch of tokens, that's just the way it is. Just try to consider/avoid profligacy!
|
|
44
|
+
|
|
45
|
+
### Architectural philosophies
|
|
46
|
+
|
|
47
|
+
- Try to follow SOLID principles
|
|
48
|
+
- Try to follow the advice in "Clean Code", by Robert Martin.
|
|
49
|
+
- Try to keep algorithmic logic abstracted from the TYPES that the logic can be run on. This is a restatement of the Liskov Substitution principle covered in the SOLID principles
|
|
50
|
+
- **Small, named functions are preferred over comments**. If a comment on WHAT the code is doing feels warranted, then refactor that code into a function with an indicative name. Comment on WHY code is like it is are more permissible.
|
|
51
|
+
|
|
52
|
+
## Architecture
|
|
53
|
+
|
|
54
|
+
### Core Components
|
|
55
|
+
|
|
56
|
+
- **`src/tasktree/parser.py`** (2,415 lines): YAML recipe parsing, task and environment definitions, circular import detection, schema validation
|
|
57
|
+
- **`src/tasktree/executor.py`** (1,200 lines): Task execution logic, incremental execution engine, state tracking, built-in variables, subprocess management
|
|
58
|
+
- **`src/tasktree/cli.py`** (591 lines): Typer-based CLI with commands: `--list`, `--show`, `--tree`, `--force`, `--only`, `--dry-run`, `--verbose`
|
|
59
|
+
- **`src/tasktree/graph.py`** (545 lines): Dependency resolution using graphlib.TopologicalSorter, parameterized dependencies, cycle detection
|
|
60
|
+
- **`src/tasktree/docker.py`** (446 lines): Docker image building and container execution, user mapping, volume mounts, build args
|
|
61
|
+
- **`src/tasktree/substitution.py`** (374 lines): Template variable substitution engine supporting multiple prefixes (var, arg, env, tt, dep, self)
|
|
62
|
+
- **`src/tasktree/types.py`** (139 lines): Custom Click parameter types for argument validation (hostname, email, IP, IPv4, IPv6, datetime)
|
|
63
|
+
- **`src/tasktree/hasher.py`** (161 lines): Task hashing for incremental execution, cache key generation, environment definition hashing
|
|
64
|
+
- **`src/tasktree/state.py`** (119 lines): State file management (.tasktree-state), task execution state tracking
|
|
65
|
+
|
|
66
|
+
### Key Dependencies
|
|
67
|
+
|
|
68
|
+
- **PyYAML**: For recipe parsing
|
|
69
|
+
- **Typer, Click, Rich**: For CLI and rich terminal output
|
|
70
|
+
- **graphlib.TopologicalSorter**: For dependency resolution
|
|
71
|
+
- **pathlib**: For file operations and glob expansion
|
|
72
|
+
- **docker (Python SDK)**: For Docker image building and container management
|
|
73
|
+
- **jsonschema**: For YAML schema validation
|
|
74
|
+
|
|
75
|
+
## Development Commands
|
|
76
|
+
|
|
77
|
+
## IMPORTANT! Tool use
|
|
78
|
+
### Athena Code Knowledge
|
|
79
|
+
This repo uses Athena to store and retrieve knowledge about the codebase. YOU SHOULD USE THE TOOL TO MAKE INQUIRIES ABOUT THE CODE.
|
|
80
|
+
|
|
81
|
+
1. Find a function/class: `athena locate <function/class name>`
|
|
82
|
+
2. Find information about what a function/class does:
|
|
83
|
+
- `athena info path/to/module.py:ClassName`
|
|
84
|
+
- `athena info path/to/module.py:ClassName.method`
|
|
85
|
+
- `athena info path/to/module.py:some_function`
|
|
86
|
+
3. Search for relevant code information:
|
|
87
|
+
- `athena serch "generate JWT authentication tokens`
|
|
88
|
+
4. AFTER code changes, run `athena status` to check which docs should have been updated
|
|
89
|
+
5. AFTER checking/updating docs, run `athena sync` to register the changes
|
|
90
|
+
|
|
91
|
+
**prefer this workflow to using native `grep` and `find` tools** for understanding the code.
|
|
92
|
+
|
|
93
|
+
### Testing
|
|
94
|
+
```bash
|
|
95
|
+
python3 -m pytest tests/
|
|
96
|
+
```
|
|
97
|
+
The project has tests across three categories:
|
|
98
|
+
- **Unit tests** (`tests/unit/`): Should always be updated for any change. Makes up the bulk of code coverage
|
|
99
|
+
- **Integration tests** (`tests/integration/`): Test changes using the Typer CliRunner to cover end-to-end workflows across multiple modules
|
|
100
|
+
- **E2E tests** (`tests/e2e/`): Heavyweight tests that cover running the tool in a subprocess and/or containerized environment
|
|
101
|
+
|
|
102
|
+
### Running the Application
|
|
103
|
+
```bash
|
|
104
|
+
python3 main.py [task-names] [--options]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Package Management
|
|
108
|
+
This project uses `uv` for dependency management (indicated by `uv.lock` file). Configuration is in `pyproject.toml`.
|
|
109
|
+
|
|
110
|
+
## State Management
|
|
111
|
+
|
|
112
|
+
The application uses a `.tasktree-state` file at the project root to track:
|
|
113
|
+
- When tasks last ran
|
|
114
|
+
- Timestamps of input files at execution time
|
|
115
|
+
- Task hashes based on command, outputs, working directory, arguments, and environment definitions
|
|
116
|
+
- Cached results for incremental execution
|
|
117
|
+
|
|
118
|
+
## Testing Approach
|
|
119
|
+
|
|
120
|
+
The project uses Python's built-in `unittest` framework with:
|
|
121
|
+
- `unittest.mock` for mocking subprocess calls and external dependencies
|
|
122
|
+
- `click.testing.CliRunner` for CLI testing
|
|
123
|
+
- Comprehensive coverage across unit, integration, and E2E test layers
|
|
124
|
+
- Tests verify subprocess calls, Docker operations, state management, and CLI behavior
|
|
125
|
+
|
|
126
|
+
## Task Definition Format
|
|
127
|
+
|
|
128
|
+
Tasks are defined in YAML with the following structure:
|
|
129
|
+
```yaml
|
|
130
|
+
tasks:
|
|
131
|
+
task-name:
|
|
132
|
+
desc: Description (optional)
|
|
133
|
+
deps: [dependency-tasks] # Can include parameterized dependencies: dep-name(arg1, key=value)
|
|
134
|
+
inputs:
|
|
135
|
+
- pattern1 # Anonymous glob patterns
|
|
136
|
+
- name: glob-pattern # Named inputs for reference
|
|
137
|
+
outputs:
|
|
138
|
+
- pattern1 # Anonymous glob patterns
|
|
139
|
+
- name: path-or-pattern # Named outputs for reference
|
|
140
|
+
working_dir: execution-directory
|
|
141
|
+
env: environment-name # Reference to environment definition
|
|
142
|
+
args:
|
|
143
|
+
- name: arg-name
|
|
144
|
+
type: str|int|float|bool|path|datetime|hostname|email|ip|ipv4|ipv6
|
|
145
|
+
default: value
|
|
146
|
+
choices: [option1, option2] # Optional constraint
|
|
147
|
+
min: value # Optional for numeric types
|
|
148
|
+
max: value # Optional for numeric types
|
|
149
|
+
exported: true # Export as $ARG_NAME environment variable
|
|
150
|
+
cmd: shell-command # Can use {{ var.name }}, {{ arg.name }}, {{ env.NAME }}, {{ tt.* }}, {{ dep.task.outputs.name }}, {{ self.inputs.name }}
|
|
151
|
+
private: true # Hide from --list but still executable
|
|
152
|
+
|
|
153
|
+
environments:
|
|
154
|
+
env-name:
|
|
155
|
+
default: true # Make this the default environment
|
|
156
|
+
shell: /bin/bash # Shell environment
|
|
157
|
+
# OR
|
|
158
|
+
dockerfile: path/to/Dockerfile # Docker environment
|
|
159
|
+
context: build-context-dir
|
|
160
|
+
image: optional-image-name
|
|
161
|
+
volumes:
|
|
162
|
+
- host_path:container_path[:ro]
|
|
163
|
+
ports:
|
|
164
|
+
- "host:container"
|
|
165
|
+
build_args:
|
|
166
|
+
ARG_NAME: value
|
|
167
|
+
environment:
|
|
168
|
+
ENV_VAR: value
|
|
169
|
+
|
|
170
|
+
variables:
|
|
171
|
+
var-name: value # Simple string value
|
|
172
|
+
var-from-env: { env: ENV_VAR, default: fallback } # From environment
|
|
173
|
+
var-from-eval: { eval: "command to run" } # Runtime command evaluation
|
|
174
|
+
var-from-file: { read: path/to/file } # Read file contents
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Built-in Variables
|
|
178
|
+
|
|
179
|
+
Tasks have access to these built-in template variables:
|
|
180
|
+
- `{{ tt.project_root }}`: Root directory of the project
|
|
181
|
+
- `{{ tt.recipe_dir }}`: Directory containing the recipe file
|
|
182
|
+
- `{{ tt.task_name }}`: Name of the current task
|
|
183
|
+
- `{{ tt.working_dir }}`: Working directory for task execution
|
|
184
|
+
- `{{ tt.timestamp }}`: ISO 8601 timestamp
|
|
185
|
+
- `{{ tt.timestamp_unix }}`: Unix timestamp
|
|
186
|
+
- `{{ tt.user_home }}`: User's home directory
|
|
187
|
+
- `{{ tt.user_name }}`: Current username
|
|
188
|
+
|
|
189
|
+
## Key Features
|
|
190
|
+
|
|
191
|
+
### Template Substitution
|
|
192
|
+
Commands and paths support template substitution with multiple prefixes:
|
|
193
|
+
- `{{ var.name }}`: Variables defined in the `variables` section
|
|
194
|
+
- `{{ arg.name }}`: Task arguments passed on command line
|
|
195
|
+
- `{{ env.NAME }}`: Environment variables
|
|
196
|
+
- `{{ tt.* }}`: Built-in variables (see above)
|
|
197
|
+
- `{{ dep.task_name.outputs.output_name }}`: Outputs from dependency tasks
|
|
198
|
+
- `{{ self.inputs.input_name }}`: Named inputs of the current task
|
|
199
|
+
- `{{ self.outputs.output_name }}`: Named outputs of the current task
|
|
200
|
+
|
|
201
|
+
### Parameterized Dependencies
|
|
202
|
+
Tasks can pass arguments to their dependencies:
|
|
203
|
+
```yaml
|
|
204
|
+
tasks:
|
|
205
|
+
caller:
|
|
206
|
+
deps:
|
|
207
|
+
- dependency(value1, key=value2) # Positional and named args
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Docker Integration
|
|
211
|
+
Full Docker support with:
|
|
212
|
+
- Dockerfile-based image building
|
|
213
|
+
- Volume mounts (read-only and read-write)
|
|
214
|
+
- Port mappings
|
|
215
|
+
- User mapping (run as non-root on Unix/macOS)
|
|
216
|
+
- Build arguments separate from shell arguments
|
|
217
|
+
- Environment variable injection
|
|
218
|
+
|
|
219
|
+
### Schema Validation
|
|
220
|
+
The project includes JSON Schema definitions in `schema/` for validating recipe YAML files.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tasktree
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.21
|
|
4
4
|
Summary: A task automation tool with incremental execution
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: click>=8.1.0
|
|
@@ -833,6 +833,369 @@ Hint: Define named outputs like: outputs: [{ missing: 'path/to/file' }]
|
|
|
833
833
|
- **Deployment pipelines**: Reference exact artifacts to deploy
|
|
834
834
|
- **Configuration propagation**: Pass generated config files through build stages
|
|
835
835
|
|
|
836
|
+
### Self-References
|
|
837
|
+
|
|
838
|
+
Tasks can reference their own inputs and outputs using `{{ self.inputs.name }}` (named access) or `{{ self.inputs.0 }}` (positional access) templates. This eliminates repetition when paths contain variables or when tasks have multiple inputs/outputs.
|
|
839
|
+
|
|
840
|
+
**Named Inputs and Outputs:**
|
|
841
|
+
|
|
842
|
+
Just like dependency output references, inputs and outputs can have names:
|
|
843
|
+
|
|
844
|
+
```yaml
|
|
845
|
+
tasks:
|
|
846
|
+
process:
|
|
847
|
+
inputs:
|
|
848
|
+
- src: "data/input.json" # Named input
|
|
849
|
+
- config: "config.yaml" # Named input
|
|
850
|
+
outputs:
|
|
851
|
+
- result: "output/result.json" # Named output
|
|
852
|
+
- log: "output/process.log" # Named output
|
|
853
|
+
cmd: |
|
|
854
|
+
process-tool \
|
|
855
|
+
--input {{ self.inputs.src }} \
|
|
856
|
+
--config {{ self.inputs.config }} \
|
|
857
|
+
--output {{ self.outputs.result }} \
|
|
858
|
+
--log {{ self.outputs.log }}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
**Syntax:**
|
|
862
|
+
|
|
863
|
+
- **Defining named inputs**: `inputs: [{ name: "path/to/file" }]`
|
|
864
|
+
- **Defining named outputs**: `outputs: [{ name: "path/to/file" }]`
|
|
865
|
+
- **Defining anonymous inputs**: `inputs: ["path/to/file"]`
|
|
866
|
+
- **Defining anonymous outputs**: `outputs: ["path/to/file"]`
|
|
867
|
+
- **Referencing by name**: `{{ self.inputs.input_name }}` or `{{ self.outputs.output_name }}`
|
|
868
|
+
- **Referencing by index**: `{{ self.inputs.0 }}` or `{{ self.outputs.1 }}` (0-based)
|
|
869
|
+
- **Mixed format**: Can combine named and anonymous inputs/outputs in the same task
|
|
870
|
+
|
|
871
|
+
**Why Use Self-References?**
|
|
872
|
+
|
|
873
|
+
Self-references follow the DRY (Don't Repeat Yourself) principle:
|
|
874
|
+
|
|
875
|
+
```yaml
|
|
876
|
+
# Without self-references - repetitive
|
|
877
|
+
tasks:
|
|
878
|
+
build:
|
|
879
|
+
inputs: [src/app-{{ var.version }}.c]
|
|
880
|
+
outputs: [build/app-{{ var.version }}.o]
|
|
881
|
+
cmd: gcc src/app-{{ var.version }}.c -o build/app-{{ var.version }}.o
|
|
882
|
+
|
|
883
|
+
# With self-references - DRY
|
|
884
|
+
tasks:
|
|
885
|
+
build:
|
|
886
|
+
inputs:
|
|
887
|
+
- source: src/app-{{ var.version }}.c
|
|
888
|
+
outputs:
|
|
889
|
+
- object: build/app-{{ var.version }}.o
|
|
890
|
+
cmd: gcc {{ self.inputs.source }} -o {{ self.outputs.object }}
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
**Working with Variables:**
|
|
894
|
+
|
|
895
|
+
Self-references work seamlessly with variables:
|
|
896
|
+
|
|
897
|
+
```yaml
|
|
898
|
+
variables:
|
|
899
|
+
platform: linux
|
|
900
|
+
arch: x86_64
|
|
901
|
+
|
|
902
|
+
tasks:
|
|
903
|
+
compile:
|
|
904
|
+
inputs:
|
|
905
|
+
- src: src/{{ var.platform }}/main.c
|
|
906
|
+
- header: include/{{ var.arch }}/defs.h
|
|
907
|
+
outputs:
|
|
908
|
+
- binary: build/{{ var.platform }}-{{ var.arch }}/app
|
|
909
|
+
cmd: |
|
|
910
|
+
gcc {{ self.inputs.src }} \
|
|
911
|
+
-include {{ self.inputs.header }} \
|
|
912
|
+
-o {{ self.outputs.binary }}
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
Variables are evaluated first, then self-references substitute the expanded paths.
|
|
916
|
+
|
|
917
|
+
**Working with Arguments:**
|
|
918
|
+
|
|
919
|
+
Self-references work with parameterized tasks:
|
|
920
|
+
|
|
921
|
+
```yaml
|
|
922
|
+
tasks:
|
|
923
|
+
deploy:
|
|
924
|
+
args: [environment]
|
|
925
|
+
inputs:
|
|
926
|
+
- config: configs/{{ arg.environment }}/app.yaml
|
|
927
|
+
outputs:
|
|
928
|
+
- deployed: deployed-{{ arg.environment }}.yaml
|
|
929
|
+
cmd: |
|
|
930
|
+
validate {{ self.inputs.config }}
|
|
931
|
+
deploy {{ self.inputs.config }} > {{ self.outputs.deployed }}
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
```bash
|
|
935
|
+
tt deploy production # Uses configs/production/app.yaml
|
|
936
|
+
tt deploy staging # Uses configs/staging/app.yaml
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
**Working with Dependency Outputs:**
|
|
940
|
+
|
|
941
|
+
Self-references and dependency references can be used together:
|
|
942
|
+
|
|
943
|
+
```yaml
|
|
944
|
+
tasks:
|
|
945
|
+
build:
|
|
946
|
+
outputs:
|
|
947
|
+
- artifact: dist/app.js
|
|
948
|
+
cmd: webpack build
|
|
949
|
+
|
|
950
|
+
package:
|
|
951
|
+
deps: [build]
|
|
952
|
+
inputs:
|
|
953
|
+
- manifest: package.json
|
|
954
|
+
outputs:
|
|
955
|
+
- tarball: release.tar.gz
|
|
956
|
+
cmd: tar czf {{ self.outputs.tarball }} \
|
|
957
|
+
{{ self.inputs.manifest }} \
|
|
958
|
+
{{ dep.build.outputs.artifact }}
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
**Mixed Named and Anonymous:**
|
|
962
|
+
|
|
963
|
+
Tasks can mix named and anonymous inputs/outputs:
|
|
964
|
+
|
|
965
|
+
```yaml
|
|
966
|
+
tasks:
|
|
967
|
+
build:
|
|
968
|
+
inputs:
|
|
969
|
+
- config: build.yaml # Named - can reference
|
|
970
|
+
- src/**/*.c # Anonymous - tracked but not referenceable
|
|
971
|
+
outputs:
|
|
972
|
+
- binary: bin/app # Named - can reference
|
|
973
|
+
- bin/app.debug # Anonymous - tracked but not referenceable
|
|
974
|
+
cmd: build-tool --config {{ self.inputs.config }} --output {{ self.outputs.binary }}
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
**Positional Index References:**
|
|
978
|
+
|
|
979
|
+
In addition to named references, you can access inputs and outputs by their positional index using `{{ self.inputs.0 }}`, `{{ self.inputs.1 }}`, etc. This provides an alternative way to reference items, especially useful for:
|
|
980
|
+
|
|
981
|
+
- **Anonymous inputs/outputs**: Reference items that don't have names
|
|
982
|
+
- **Simple sequential access**: When order is more important than naming
|
|
983
|
+
- **Mixed with named access**: Use both styles in the same task
|
|
984
|
+
|
|
985
|
+
**Syntax:**
|
|
986
|
+
|
|
987
|
+
```yaml
|
|
988
|
+
tasks:
|
|
989
|
+
process:
|
|
990
|
+
inputs: ["file1.txt", "file2.txt", "file3.txt"]
|
|
991
|
+
outputs: ["output1.txt", "output2.txt"]
|
|
992
|
+
cmd: |
|
|
993
|
+
cat {{ self.inputs.0 }} {{ self.inputs.1 }} > {{ self.outputs.0 }}
|
|
994
|
+
cat {{ self.inputs.2 }} > {{ self.outputs.1 }}
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
Indices follow YAML declaration order, starting from 0 (zero-based indexing):
|
|
998
|
+
- First input/output = index 0
|
|
999
|
+
- Second input/output = index 1
|
|
1000
|
+
- Third input/output = index 2, etc.
|
|
1001
|
+
|
|
1002
|
+
**Works with Both Named and Anonymous:**
|
|
1003
|
+
|
|
1004
|
+
```yaml
|
|
1005
|
+
tasks:
|
|
1006
|
+
build:
|
|
1007
|
+
inputs:
|
|
1008
|
+
- config: "build.yaml" # Index 0, also accessible as {{ self.inputs.config }}
|
|
1009
|
+
- "src/**/*.c" # Index 1, ONLY accessible as {{ self.inputs.1 }}
|
|
1010
|
+
- headers: "include/*.h" # Index 2, also accessible as {{ self.inputs.headers }}
|
|
1011
|
+
outputs:
|
|
1012
|
+
- "dist/app.js" # Index 0
|
|
1013
|
+
- bundle: "dist/bundle.js" # Index 1, also accessible as {{ self.outputs.bundle }}
|
|
1014
|
+
cmd: |
|
|
1015
|
+
# Mix positional and named references
|
|
1016
|
+
build-tool \
|
|
1017
|
+
--config {{ self.inputs.0 }} \
|
|
1018
|
+
--sources {{ self.inputs.1 }} \
|
|
1019
|
+
--headers {{ self.inputs.headers }} \
|
|
1020
|
+
--output {{ self.outputs.bundle }}
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
**Same Item, Multiple Ways:**
|
|
1024
|
+
|
|
1025
|
+
Named items can be accessed by both name and index:
|
|
1026
|
+
|
|
1027
|
+
```yaml
|
|
1028
|
+
tasks:
|
|
1029
|
+
copy:
|
|
1030
|
+
inputs:
|
|
1031
|
+
- source: data.txt
|
|
1032
|
+
cmd: |
|
|
1033
|
+
# These are equivalent:
|
|
1034
|
+
cat {{ self.inputs.source }} > copy1.txt
|
|
1035
|
+
cat {{ self.inputs.0 }} > copy2.txt
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
**With Variables and Arguments:**
|
|
1039
|
+
|
|
1040
|
+
Positional references work with variable-expanded paths:
|
|
1041
|
+
|
|
1042
|
+
```yaml
|
|
1043
|
+
variables:
|
|
1044
|
+
version: "1.0"
|
|
1045
|
+
|
|
1046
|
+
tasks:
|
|
1047
|
+
package:
|
|
1048
|
+
args: [platform]
|
|
1049
|
+
inputs:
|
|
1050
|
+
- "dist/app-{{ var.version }}.js"
|
|
1051
|
+
- "dist/lib-{{ arg.platform }}.so"
|
|
1052
|
+
outputs: ["release-{{ var.version }}-{{ arg.platform }}.tar.gz"]
|
|
1053
|
+
cmd: tar czf {{ self.outputs.0 }} {{ self.inputs.0 }} {{ self.inputs.1 }}
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
**Index Boundaries:**
|
|
1057
|
+
|
|
1058
|
+
Indices are validated before execution:
|
|
1059
|
+
|
|
1060
|
+
```yaml
|
|
1061
|
+
tasks:
|
|
1062
|
+
build:
|
|
1063
|
+
inputs: ["file1.txt", "file2.txt"]
|
|
1064
|
+
cmd: cat {{ self.inputs.5 }} # Error: index 5 out of bounds!
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
Error message:
|
|
1068
|
+
```
|
|
1069
|
+
Task 'build' references input index '5' but only has 2 inputs (indices 0-1)
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
**Empty Lists:**
|
|
1073
|
+
|
|
1074
|
+
Referencing an index when no inputs/outputs exist:
|
|
1075
|
+
|
|
1076
|
+
```yaml
|
|
1077
|
+
tasks:
|
|
1078
|
+
generate:
|
|
1079
|
+
cmd: echo "test" > {{ self.outputs.0 }} # Error: no outputs defined!
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
Error message:
|
|
1083
|
+
```
|
|
1084
|
+
Task 'generate' references output index '0' but has no outputs defined
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
**When to Use Index References:**
|
|
1088
|
+
|
|
1089
|
+
- **Anonymous items**: Only way to reference inputs/outputs without names
|
|
1090
|
+
- **Order-based processing**: When the sequence matters more than naming
|
|
1091
|
+
- **Simple tasks**: Quick access without defining names
|
|
1092
|
+
- **Compatibility**: Accessing items in legacy YAML that uses anonymous format
|
|
1093
|
+
|
|
1094
|
+
**When to Use Named References:**
|
|
1095
|
+
|
|
1096
|
+
- **Clarity**: Names make commands more readable (`{{ self.inputs.config }}` vs `{{ self.inputs.2 }}`)
|
|
1097
|
+
- **Maintainability**: Adding/removing items doesn't break indices
|
|
1098
|
+
- **Complex tasks**: Many inputs/outputs are easier to manage with names
|
|
1099
|
+
|
|
1100
|
+
**Error Messages:**
|
|
1101
|
+
|
|
1102
|
+
If you reference a non-existent input or output:
|
|
1103
|
+
|
|
1104
|
+
```yaml
|
|
1105
|
+
tasks:
|
|
1106
|
+
build:
|
|
1107
|
+
inputs:
|
|
1108
|
+
- src: input.txt
|
|
1109
|
+
cmd: cat {{ self.inputs.missing }} # Error!
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
You'll get a clear error before execution:
|
|
1113
|
+
|
|
1114
|
+
```
|
|
1115
|
+
Task 'build' references input 'missing' but has no input named 'missing'.
|
|
1116
|
+
Available named inputs: src
|
|
1117
|
+
Hint: Define named inputs like: inputs: [{ missing: 'path/to/file' }]
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
Similarly, if you try to reference an anonymous input:
|
|
1121
|
+
|
|
1122
|
+
```yaml
|
|
1123
|
+
tasks:
|
|
1124
|
+
build:
|
|
1125
|
+
inputs: [file.txt] # Anonymous input
|
|
1126
|
+
cmd: cat {{ self.inputs.src }} # Error!
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
You'll get:
|
|
1130
|
+
|
|
1131
|
+
```
|
|
1132
|
+
Task 'build' references input 'src' but has no input named 'src'.
|
|
1133
|
+
Available named inputs: (none - all inputs are anonymous)
|
|
1134
|
+
Hint: Define named inputs like: inputs: [{ src: 'file.txt' }]
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
**Key Behaviors:**
|
|
1138
|
+
|
|
1139
|
+
- **Two access methods**: Reference by name (`{{ self.inputs.name }}`) or by index (`{{ self.inputs.0 }}`)
|
|
1140
|
+
- **Template resolution**: Self-references are resolved during dependency graph planning
|
|
1141
|
+
- **Substitution order**: Variables → Dependency outputs → Self-references → Arguments/Environment
|
|
1142
|
+
- **Fail-fast validation**: Errors are caught before execution begins (missing names, out-of-bounds indices)
|
|
1143
|
+
- **Clear error messages**: Lists available names/indices if reference doesn't exist
|
|
1144
|
+
- **Backward compatible**: Existing anonymous inputs/outputs work unchanged
|
|
1145
|
+
- **State tracking**: Works correctly with incremental execution and freshness checks
|
|
1146
|
+
- **Index order**: Positional indices follow YAML declaration order (0-based)
|
|
1147
|
+
|
|
1148
|
+
**Limitations:**
|
|
1149
|
+
|
|
1150
|
+
- **Anonymous not referenceable by name**: Anonymous inputs/outputs cannot be referenced by name (use positional index instead: `{{ self.inputs.0 }}`)
|
|
1151
|
+
- **Case sensitive**: `{{ self.inputs.Src }}` and `{{ self.inputs.src }}` are different
|
|
1152
|
+
- **Argument defaults**: Self-references in argument defaults are not supported (arguments are evaluated before self-references)
|
|
1153
|
+
- **No negative indices**: Python-style negative indexing (`{{ self.inputs.-1 }}`) is not supported
|
|
1154
|
+
|
|
1155
|
+
**Use Cases:**
|
|
1156
|
+
|
|
1157
|
+
- **Eliminate repetition**: Define complex paths once, use them multiple times
|
|
1158
|
+
- **Variable composition**: Combine variables with self-references for clean commands
|
|
1159
|
+
- **Multiple inputs/outputs**: Reference specific files when tasks have many
|
|
1160
|
+
- **Complex build pipelines**: Keep commands readable with named artifacts
|
|
1161
|
+
- **Glob patterns**: Use self-references with glob patterns for dynamic inputs
|
|
1162
|
+
|
|
1163
|
+
**Example: Multi-Stage Build:**
|
|
1164
|
+
|
|
1165
|
+
```yaml
|
|
1166
|
+
variables:
|
|
1167
|
+
version: "2.1.0"
|
|
1168
|
+
platform: "linux"
|
|
1169
|
+
|
|
1170
|
+
tasks:
|
|
1171
|
+
prepare:
|
|
1172
|
+
outputs:
|
|
1173
|
+
- builddir: build/{{ var.platform }}-{{ var.version }}
|
|
1174
|
+
cmd: mkdir -p {{ self.outputs.builddir }}
|
|
1175
|
+
|
|
1176
|
+
compile:
|
|
1177
|
+
deps: [prepare]
|
|
1178
|
+
inputs:
|
|
1179
|
+
- source: src/main.c
|
|
1180
|
+
- headers: include/*.h
|
|
1181
|
+
outputs:
|
|
1182
|
+
- object: build/{{ var.platform }}-{{ var.version }}/main.o
|
|
1183
|
+
cmd: |
|
|
1184
|
+
gcc -c {{ self.inputs.source }} \
|
|
1185
|
+
-I include \
|
|
1186
|
+
-o {{ self.outputs.object }}
|
|
1187
|
+
|
|
1188
|
+
link:
|
|
1189
|
+
deps: [compile]
|
|
1190
|
+
outputs:
|
|
1191
|
+
- executable: build/{{ var.platform }}-{{ var.version }}/app
|
|
1192
|
+
- symbols: build/{{ var.platform }}-{{ var.version }}/app.sym
|
|
1193
|
+
cmd: |
|
|
1194
|
+
gcc build/{{ var.platform }}-{{ var.version }}/main.o \
|
|
1195
|
+
-o {{ self.outputs.executable }}
|
|
1196
|
+
objcopy --only-keep-debug {{ self.outputs.executable }} {{ self.outputs.symbols }}
|
|
1197
|
+
```
|
|
1198
|
+
|
|
836
1199
|
|
|
837
1200
|
### Private Tasks
|
|
838
1201
|
|