tasktree 0.0.3__py3-none-any.whl → 0.0.4__py3-none-any.whl
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/cli.py +13 -54
- tasktree/hasher.py +5 -55
- tasktree/types.py +7 -7
- {tasktree-0.0.3.dist-info → tasktree-0.0.4.dist-info}/METADATA +104 -3
- {tasktree-0.0.3.dist-info → tasktree-0.0.4.dist-info}/RECORD +7 -7
- {tasktree-0.0.3.dist-info → tasktree-0.0.4.dist-info}/WHEEL +0 -0
- {tasktree-0.0.3.dist-info → tasktree-0.0.4.dist-info}/entry_points.txt +0 -0
tasktree/cli.py
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
"""Command-line interface for Task Tree."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
|
-
import sys
|
|
6
3
|
from pathlib import Path
|
|
7
4
|
from typing import Any, List, Optional
|
|
8
5
|
|
|
@@ -120,7 +117,7 @@ def _init_recipe():
|
|
|
120
117
|
raise typer.Exit(1)
|
|
121
118
|
|
|
122
119
|
template = """# Task Tree Recipe
|
|
123
|
-
# See https://github.com/kevinchannon/
|
|
120
|
+
# See https://github.com/kevinchannon/task-tree for documentation
|
|
124
121
|
|
|
125
122
|
# Example task definitions:
|
|
126
123
|
|
|
@@ -168,22 +165,20 @@ def main(
|
|
|
168
165
|
is_eager=True,
|
|
169
166
|
help="Show version and exit",
|
|
170
167
|
),
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
),
|
|
174
|
-
show: Optional[str] = typer.Option(None, "--show", help="Show task definition"),
|
|
175
|
-
tree: Optional[str] = typer.Option(None, "--tree", help="Show dependency tree"),
|
|
168
|
+
list_opt: Optional[bool] = typer.Option(None, "--list", "-l", help="List all available tasks"),
|
|
169
|
+
show: Optional[str] = typer.Option(None, "--show", "-s", help="Show task definition"),
|
|
170
|
+
tree: Optional[str] = typer.Option(None, "--tree", "-t", help="Show dependency tree"),
|
|
176
171
|
init: Optional[bool] = typer.Option(
|
|
177
|
-
None, "--init", help="Create a blank tasktree.yaml"
|
|
172
|
+
None, "--init", "-i", help="Create a blank tasktree.yaml"
|
|
178
173
|
),
|
|
179
174
|
clean: Optional[bool] = typer.Option(
|
|
180
|
-
None, "--clean", help="Remove state file (reset task cache)"
|
|
175
|
+
None, "--clean", "-c", help="Remove state file (reset task cache)"
|
|
181
176
|
),
|
|
182
177
|
clean_state: Optional[bool] = typer.Option(
|
|
183
|
-
None, "--clean-state", help="Remove state file (reset task cache)"
|
|
178
|
+
None, "--clean-state", "-C", help="Remove state file (reset task cache)"
|
|
184
179
|
),
|
|
185
180
|
reset: Optional[bool] = typer.Option(
|
|
186
|
-
None, "--reset", help="Remove state file (reset task cache)"
|
|
181
|
+
None, "--reset", "-r", help="Remove state file (reset task cache)"
|
|
187
182
|
),
|
|
188
183
|
force: Optional[bool] = typer.Option(
|
|
189
184
|
None, "--force", "-f", help="Force re-run all tasks (ignore freshness)"
|
|
@@ -210,38 +205,32 @@ def main(
|
|
|
210
205
|
tt --list # List all tasks
|
|
211
206
|
tt --tree test # Show dependency tree for 'test'
|
|
212
207
|
"""
|
|
213
|
-
|
|
214
|
-
if
|
|
208
|
+
|
|
209
|
+
if list_opt:
|
|
215
210
|
_list_tasks()
|
|
216
211
|
raise typer.Exit()
|
|
217
212
|
|
|
218
|
-
# Handle show option
|
|
219
213
|
if show:
|
|
220
214
|
_show_task(show)
|
|
221
215
|
raise typer.Exit()
|
|
222
216
|
|
|
223
|
-
# Handle tree option
|
|
224
217
|
if tree:
|
|
225
218
|
_show_tree(tree)
|
|
226
219
|
raise typer.Exit()
|
|
227
220
|
|
|
228
|
-
# Handle init option
|
|
229
221
|
if init:
|
|
230
222
|
_init_recipe()
|
|
231
223
|
raise typer.Exit()
|
|
232
224
|
|
|
233
|
-
# Handle clean options (all three aliases)
|
|
234
225
|
if clean or clean_state or reset:
|
|
235
226
|
_clean_state()
|
|
236
227
|
raise typer.Exit()
|
|
237
228
|
|
|
238
|
-
# Handle task execution
|
|
239
229
|
if task_args:
|
|
240
|
-
#
|
|
230
|
+
# --only implies --force
|
|
241
231
|
force_execution = force or only or False
|
|
242
232
|
_execute_dynamic_task(task_args, force=force_execution, only=only or False, env=env)
|
|
243
233
|
else:
|
|
244
|
-
# No arguments - show available tasks
|
|
245
234
|
recipe = _get_recipe()
|
|
246
235
|
if recipe is None:
|
|
247
236
|
console.print("[red]No recipe file found (tasktree.yaml or tt.yaml)[/red]")
|
|
@@ -274,7 +263,7 @@ def _clean_state() -> None:
|
|
|
274
263
|
console.print(f"[yellow]No state file found at {state_path}[/yellow]")
|
|
275
264
|
|
|
276
265
|
|
|
277
|
-
def _get_recipe() -> Recipe
|
|
266
|
+
def _get_recipe() -> Optional[Recipe]:
|
|
278
267
|
"""Get parsed recipe or None if not found."""
|
|
279
268
|
recipe_path = find_recipe_file()
|
|
280
269
|
if recipe_path is None:
|
|
@@ -287,15 +276,7 @@ def _get_recipe() -> Recipe | None:
|
|
|
287
276
|
raise typer.Exit(1)
|
|
288
277
|
|
|
289
278
|
|
|
290
|
-
def _execute_dynamic_task(args: list[str], force: bool = False, only: bool = False, env: str
|
|
291
|
-
"""Execute a task specified by name with arguments.
|
|
292
|
-
|
|
293
|
-
Args:
|
|
294
|
-
args: Command line arguments (task name and task arguments)
|
|
295
|
-
force: If True, ignore freshness and re-run all tasks
|
|
296
|
-
only: If True, run only the specified task without dependencies
|
|
297
|
-
env: If provided, override environment for all tasks
|
|
298
|
-
"""
|
|
279
|
+
def _execute_dynamic_task(args: list[str], force: bool = False, only: bool = False, env: Optional[str] = None) -> None:
|
|
299
280
|
if not args:
|
|
300
281
|
return
|
|
301
282
|
|
|
@@ -350,31 +331,17 @@ def _execute_dynamic_task(args: list[str], force: bool = False, only: bool = Fal
|
|
|
350
331
|
|
|
351
332
|
|
|
352
333
|
def _parse_task_args(arg_specs: list[str], arg_values: list[str]) -> dict[str, Any]:
|
|
353
|
-
"""Parse command line arguments for a task.
|
|
354
|
-
|
|
355
|
-
Args:
|
|
356
|
-
arg_specs: Argument specifications from task definition
|
|
357
|
-
arg_values: Command line argument values
|
|
358
|
-
|
|
359
|
-
Returns:
|
|
360
|
-
Dictionary of argument names to values
|
|
361
|
-
|
|
362
|
-
Raises:
|
|
363
|
-
typer.Exit: If arguments are invalid
|
|
364
|
-
"""
|
|
365
334
|
if not arg_specs:
|
|
366
335
|
if arg_values:
|
|
367
336
|
console.print(f"[red]Task does not accept arguments[/red]")
|
|
368
337
|
raise typer.Exit(1)
|
|
369
338
|
return {}
|
|
370
339
|
|
|
371
|
-
# Parse argument specifications
|
|
372
340
|
parsed_specs = []
|
|
373
341
|
for spec in arg_specs:
|
|
374
342
|
name, arg_type, default = parse_arg_spec(spec)
|
|
375
343
|
parsed_specs.append((name, arg_type, default))
|
|
376
344
|
|
|
377
|
-
# Build argument dictionary
|
|
378
345
|
args_dict = {}
|
|
379
346
|
positional_index = 0
|
|
380
347
|
|
|
@@ -424,14 +391,6 @@ def _parse_task_args(arg_specs: list[str], arg_values: list[str]) -> dict[str, A
|
|
|
424
391
|
|
|
425
392
|
|
|
426
393
|
def _build_rich_tree(dep_tree: dict) -> Tree:
|
|
427
|
-
"""Build a Rich Tree from dependency tree structure.
|
|
428
|
-
|
|
429
|
-
Args:
|
|
430
|
-
dep_tree: Dependency tree structure
|
|
431
|
-
|
|
432
|
-
Returns:
|
|
433
|
-
Rich Tree for display
|
|
434
|
-
"""
|
|
435
394
|
task_name = dep_tree["name"]
|
|
436
395
|
tree = Tree(task_name)
|
|
437
396
|
|
tasktree/hasher.py
CHANGED
|
@@ -1,77 +1,27 @@
|
|
|
1
|
-
"""Hashing logic for tasks and arguments."""
|
|
2
|
-
|
|
3
1
|
import hashlib
|
|
4
2
|
import json
|
|
5
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Optional
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
def hash_task(cmd: str, outputs: list[str], working_dir: str, args: list[str], env: str = "") -> str:
|
|
9
|
-
"""Compute task definition hash.
|
|
10
|
-
|
|
11
|
-
The hash includes:
|
|
12
|
-
- cmd: The command to execute
|
|
13
|
-
- outputs: Declared output files
|
|
14
|
-
- working_dir: Execution directory
|
|
15
|
-
- args: Parameter definitions (names and types)
|
|
16
|
-
- env: Environment name (effective environment after resolution)
|
|
17
|
-
|
|
18
|
-
The hash excludes:
|
|
19
|
-
- deps: Only affects scheduling order
|
|
20
|
-
- inputs: Tracked separately via timestamps
|
|
21
|
-
- desc: Documentation only
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
cmd: Command to execute
|
|
25
|
-
outputs: List of output glob patterns
|
|
26
|
-
working_dir: Working directory for execution
|
|
27
|
-
args: List of argument definitions
|
|
28
|
-
env: Environment name (empty string for platform default)
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
8-character hex hash string
|
|
32
|
-
"""
|
|
33
|
-
# Create a stable representation
|
|
34
7
|
data = {
|
|
35
8
|
"cmd": cmd,
|
|
36
|
-
"outputs": sorted(outputs),
|
|
9
|
+
"outputs": sorted(outputs),
|
|
37
10
|
"working_dir": working_dir,
|
|
38
|
-
"args": sorted(args),
|
|
39
|
-
"env": env,
|
|
11
|
+
"args": sorted(args),
|
|
12
|
+
"env": env,
|
|
40
13
|
}
|
|
41
14
|
|
|
42
|
-
# Serialize to JSON with sorted keys for deterministic hashing
|
|
43
15
|
serialized = json.dumps(data, sort_keys=True, separators=(",", ":"))
|
|
44
|
-
|
|
45
|
-
# Compute hash and truncate to 8 characters
|
|
46
16
|
return hashlib.sha256(serialized.encode()).hexdigest()[:8]
|
|
47
17
|
|
|
48
18
|
|
|
49
19
|
def hash_args(args_dict: dict[str, Any]) -> str:
|
|
50
|
-
"""Compute hash of task arguments.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
args_dict: Dictionary of argument names to values
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
8-character hex hash string
|
|
57
|
-
"""
|
|
58
|
-
# Serialize arguments to JSON with sorted keys for deterministic hashing
|
|
59
20
|
serialized = json.dumps(args_dict, sort_keys=True, separators=(",", ":"))
|
|
60
|
-
|
|
61
|
-
# Compute hash and truncate to 8 characters
|
|
62
21
|
return hashlib.sha256(serialized.encode()).hexdigest()[:8]
|
|
63
22
|
|
|
64
23
|
|
|
65
|
-
def make_cache_key(task_hash: str, args_hash: str
|
|
66
|
-
"""Create cache key for task execution.
|
|
67
|
-
|
|
68
|
-
Args:
|
|
69
|
-
task_hash: Task definition hash
|
|
70
|
-
args_hash: Optional arguments hash
|
|
71
|
-
|
|
72
|
-
Returns:
|
|
73
|
-
Cache key string (task_hash or task_hash__args_hash)
|
|
74
|
-
"""
|
|
24
|
+
def make_cache_key(task_hash: str, args_hash: Optional[str] = None) -> str:
|
|
75
25
|
if args_hash:
|
|
76
26
|
return f"{task_hash}__{args_hash}"
|
|
77
27
|
return task_hash
|
tasktree/types.py
CHANGED
|
@@ -4,7 +4,7 @@ import re
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from ipaddress import IPv4Address, IPv6Address, ip_address
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, Optional
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
10
|
|
|
@@ -19,7 +19,7 @@ class HostnameType(click.ParamType):
|
|
|
19
19
|
r"^(?=.{1,253}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*\.?$"
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
def convert(self, value: Any, param: click.Parameter
|
|
22
|
+
def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
|
|
23
23
|
if isinstance(value, str):
|
|
24
24
|
if self.HOSTNAME_PATTERN.match(value):
|
|
25
25
|
return value
|
|
@@ -36,7 +36,7 @@ class EmailType(click.ParamType):
|
|
|
36
36
|
r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
-
def convert(self, value: Any, param: click.Parameter
|
|
39
|
+
def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
|
|
40
40
|
if isinstance(value, str):
|
|
41
41
|
if self.EMAIL_PATTERN.match(value):
|
|
42
42
|
return value
|
|
@@ -48,7 +48,7 @@ class IPType(click.ParamType):
|
|
|
48
48
|
|
|
49
49
|
name = "ip"
|
|
50
50
|
|
|
51
|
-
def convert(self, value: Any, param: click.Parameter
|
|
51
|
+
def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
|
|
52
52
|
try:
|
|
53
53
|
ip_address(value)
|
|
54
54
|
return str(value)
|
|
@@ -61,7 +61,7 @@ class IPv4Type(click.ParamType):
|
|
|
61
61
|
|
|
62
62
|
name = "ipv4"
|
|
63
63
|
|
|
64
|
-
def convert(self, value: Any, param: click.Parameter
|
|
64
|
+
def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
|
|
65
65
|
try:
|
|
66
66
|
IPv4Address(value)
|
|
67
67
|
return str(value)
|
|
@@ -74,7 +74,7 @@ class IPv6Type(click.ParamType):
|
|
|
74
74
|
|
|
75
75
|
name = "ipv6"
|
|
76
76
|
|
|
77
|
-
def convert(self, value: Any, param: click.Parameter
|
|
77
|
+
def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
|
|
78
78
|
try:
|
|
79
79
|
IPv6Address(value)
|
|
80
80
|
return str(value)
|
|
@@ -87,7 +87,7 @@ class DateTimeType(click.ParamType):
|
|
|
87
87
|
|
|
88
88
|
name = "datetime"
|
|
89
89
|
|
|
90
|
-
def convert(self, value: Any, param: click.Parameter
|
|
90
|
+
def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
|
|
91
91
|
if isinstance(value, str):
|
|
92
92
|
try:
|
|
93
93
|
datetime.fromisoformat(value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tasktree
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A task automation tool with incremental execution
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: click>=8.1.0
|
|
@@ -18,6 +18,95 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
|
|
19
19
|
A task automation tool that combines simple command execution with dependency tracking and incremental execution.
|
|
20
20
|
|
|
21
|
+
## Motivation
|
|
22
|
+
In any project of even moderate size, various scripts inevitably come into being along the way. These scripts often must be run in a particular order, or at a particular time. For historical reasons, this almost certainly a problem if your project is developed in a Linux environment; in Windows, an IDE like Visual Studio may be taking care of a significant proportion of your build, packaging and deployment tasks. Then again, it may not...
|
|
23
|
+
|
|
24
|
+
The various incantations that have to be issued to build, package, test and deploy a project can build up and then all of a sudden there's only a few people that remember which to invoke and when and then people start making helpful readme guides on what to do with the scripts and then those become out of date and start telling lies about things and so on.
|
|
25
|
+
|
|
26
|
+
Then there's the scripts themselves. In Linux, they're probably a big pile of Bash and Python, or something (Ruby, Perl, you name it). You can bet the house on people solving the problem of passing parameters to their scripts in a whole bunch of different and inconsistent ways.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
#!/usr/bin/env bash
|
|
30
|
+
# It's an environment variable defined.... somewhere?
|
|
31
|
+
echo "FOO is: $FOO"
|
|
32
|
+
```
|
|
33
|
+
```bash
|
|
34
|
+
#!/usr/bin/env bash
|
|
35
|
+
# Using simple positional arguments... guess what means what when you're invoking it!
|
|
36
|
+
echo "First: $1, Second: $2"
|
|
37
|
+
```
|
|
38
|
+
```bash
|
|
39
|
+
#!/usr/bin/env bash
|
|
40
|
+
# Oooooh fancy "make me look like a proper app" named option parsing... don't try and do --foo=bar though!
|
|
41
|
+
FOO=""
|
|
42
|
+
while [[ $# -gt 0 ]]; do
|
|
43
|
+
case "$1" in
|
|
44
|
+
--foo) FOO=$2; shift ;;
|
|
45
|
+
--) break ;;
|
|
46
|
+
*) echo "Unknown: $1";;
|
|
47
|
+
esac
|
|
48
|
+
shift
|
|
49
|
+
done
|
|
50
|
+
```
|
|
51
|
+
```bash
|
|
52
|
+
#!/usr/bin/env bash
|
|
53
|
+
# This thing...
|
|
54
|
+
ARGS=$(getopt -o f:b --long foo:,bar: -n 'myscript' -- "$@")
|
|
55
|
+
eval set -- "$ARGS"
|
|
56
|
+
while true; do
|
|
57
|
+
case "$1" in
|
|
58
|
+
-b|--bar) echo "Bar: $2"; shift 2 ;;
|
|
59
|
+
-f|--foo) echo "Foo: $2"; shift 2 ;;
|
|
60
|
+
--) shift; break ;;
|
|
61
|
+
*) break ;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
What about help info? Who has time to wire that in?
|
|
67
|
+
|
|
68
|
+
### The point
|
|
69
|
+
Is this just whining and moaning? Should we just man up and revel in our own ability to memorize all the right incantations like some kind of scripting shaman?
|
|
70
|
+
|
|
71
|
+
... No. That's **a dumb idea**.
|
|
72
|
+
|
|
73
|
+
Task Tree allows you to pile all the knowledge of **what** to run, **when** to run it, **where** to run it and **how** to run it into a single, readable place. Then you can delete all the scripts that no-one knows how to use and all the readme docs that lie to the few people that actually waste their time reading them.
|
|
74
|
+
|
|
75
|
+
The tasks you need to perform to deliver your project become summarised in an executable file that looks like:
|
|
76
|
+
```yaml
|
|
77
|
+
build:
|
|
78
|
+
desc: Compile stuff
|
|
79
|
+
outputs: [target/release/bin]
|
|
80
|
+
cmd: cargo build --release
|
|
81
|
+
|
|
82
|
+
package:
|
|
83
|
+
desc: build installers
|
|
84
|
+
deps: [build]
|
|
85
|
+
outputs: [awesome.deb]
|
|
86
|
+
cmd: |
|
|
87
|
+
for bin in target/release/*; do
|
|
88
|
+
if [[ -x "$bin" && ! -d "$bin" ]]; then
|
|
89
|
+
install -Dm755 "$bin" "debian/awesome/usr/bin/$(basename "$bin")"
|
|
90
|
+
fi
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
dpkg-buildpackage -us -uc
|
|
94
|
+
|
|
95
|
+
test:
|
|
96
|
+
desc: Run tests
|
|
97
|
+
deps: [package]
|
|
98
|
+
inputs: [tests/**/*.py]
|
|
99
|
+
cmd: PYTHONPATH=src python3 -m pytest tests/ -v
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If you want to run the tests then:
|
|
103
|
+
```bash
|
|
104
|
+
tt test
|
|
105
|
+
```
|
|
106
|
+
Boom! Done. `build` will always run, because there's no sensible way to know what Cargo did. However, if Cargo decided that nothing needed to be done and didn't touch the binaries, then `package` will realize that and not do anything. Then `test` will just run with the new tests that you just wrote. If you then immediately run `test` again, then `test` will figure out that none of the dependencies did anything and that none of the test files have changed and then just _do nothing_ - as it should.
|
|
107
|
+
|
|
108
|
+
This is a toy example, but you can image how it plays out on a more complex project.
|
|
109
|
+
|
|
21
110
|
## Installation
|
|
22
111
|
|
|
23
112
|
### From PyPI (Recommended)
|
|
@@ -26,6 +115,16 @@ A task automation tool that combines simple command execution with dependency tr
|
|
|
26
115
|
pipx install tasktree
|
|
27
116
|
```
|
|
28
117
|
|
|
118
|
+
If you have multiple Python interpreter versions installed, and the _default_ interpreter is a version <3.11, then you can use `pipx`'s `--python` option to specify an interpreter with a version >=3.11:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# If the target version is on the PATH
|
|
122
|
+
pipx install --python python3.12 tasktree
|
|
123
|
+
|
|
124
|
+
# With a path to an interpreter
|
|
125
|
+
pipx install --python /path/to/python3.12 tasktree
|
|
126
|
+
```
|
|
127
|
+
|
|
29
128
|
### From Source
|
|
30
129
|
|
|
31
130
|
For the latest unreleased version from GitHub:
|
|
@@ -61,9 +160,11 @@ test:
|
|
|
61
160
|
Run tasks:
|
|
62
161
|
|
|
63
162
|
```bash
|
|
64
|
-
tt
|
|
65
|
-
tt
|
|
163
|
+
tt # Print the help
|
|
164
|
+
tt --help # ...also print the help
|
|
66
165
|
tt --list # Show all available tasks
|
|
166
|
+
tt build # Build the application (assuming this is in your tasktree.yaml)
|
|
167
|
+
tt test # Run tests (builds first if needed)
|
|
67
168
|
```
|
|
68
169
|
|
|
69
170
|
## Core Concepts
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
tasktree/__init__.py,sha256=MVmdvKb3JdqLlo0x2_TPGMfgFC0HsDnP79HAzGnFnjI,1081
|
|
2
|
-
tasktree/cli.py,sha256=
|
|
2
|
+
tasktree/cli.py,sha256=2Pm0pxWdG4RaQbVMijaMUIrm3v0b9J98CUsUp7cDFvI,13236
|
|
3
3
|
tasktree/executor.py,sha256=_E37tShHuiOj0Mvx2GbS9y3GIozC3hpzAVhAjbvYJqg,18638
|
|
4
4
|
tasktree/graph.py,sha256=9ngfg93y7EkOIN_lUQa0u-JhnwiMN1UdQQvIFw8RYCE,4181
|
|
5
|
-
tasktree/hasher.py,sha256=
|
|
5
|
+
tasktree/hasher.py,sha256=puJey9wF_p37k_xqjhYr_6ICsbAfrTBWHec6MqKV4BU,814
|
|
6
6
|
tasktree/parser.py,sha256=apLfN3_YVa7lQIy0rcHFwo931Pg-8mKG1044AQE6fLQ,12690
|
|
7
7
|
tasktree/state.py,sha256=rxKtS3SbsPtAuraHbN807RGWfoYYkQ3pe8CxUstwo2k,3535
|
|
8
8
|
tasktree/tasks.py,sha256=2QdQZtJAX2rSGbyXKG1z9VF_siz1DUzdvzCgPkykxtU,173
|
|
9
|
-
tasktree/types.py,sha256=
|
|
10
|
-
tasktree-0.0.
|
|
11
|
-
tasktree-0.0.
|
|
12
|
-
tasktree-0.0.
|
|
13
|
-
tasktree-0.0.
|
|
9
|
+
tasktree/types.py,sha256=w--sKjRTc8mGYkU5eAduqV86SolDqOYspAPuVKIuSQQ,3797
|
|
10
|
+
tasktree-0.0.4.dist-info/METADATA,sha256=dxJ5QiaRWhBbae-cK358ESQuwbjDg04ITWomlgrO9Lc,16312
|
|
11
|
+
tasktree-0.0.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
+
tasktree-0.0.4.dist-info/entry_points.txt,sha256=lQINlvRYnimvteBbnhH84A9clTg8NnpEjCWqWkqg8KE,40
|
|
13
|
+
tasktree-0.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|