tasktree 0.0.21__py3-none-any.whl → 0.0.22__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 +91 -31
- tasktree/docker.py +24 -17
- tasktree/executor.py +263 -211
- tasktree/graph.py +15 -10
- tasktree/hasher.py +13 -6
- tasktree/parser.py +220 -121
- tasktree/state.py +7 -8
- tasktree/substitution.py +27 -15
- tasktree/types.py +29 -12
- {tasktree-0.0.21.dist-info → tasktree-0.0.22.dist-info}/METADATA +13 -15
- tasktree-0.0.22.dist-info/RECORD +14 -0
- tasktree-0.0.21.dist-info/RECORD +0 -14
- {tasktree-0.0.21.dist-info → tasktree-0.0.22.dist-info}/WHEEL +0 -0
- {tasktree-0.0.21.dist-info → tasktree-0.0.22.dist-info}/entry_points.txt +0 -0
tasktree/state.py
CHANGED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, Set
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
@@ -43,7 +43,7 @@ class TaskState:
|
|
|
43
43
|
class StateManager:
|
|
44
44
|
"""
|
|
45
45
|
Manages the .tasktree-state file.
|
|
46
|
-
@athena:
|
|
46
|
+
@athena: 3dd3447bb53b
|
|
47
47
|
"""
|
|
48
48
|
|
|
49
49
|
STATE_FILE = ".tasktree-state"
|
|
@@ -64,17 +64,16 @@ class StateManager:
|
|
|
64
64
|
def load(self) -> None:
|
|
65
65
|
"""
|
|
66
66
|
Load state from file if it exists.
|
|
67
|
-
@athena:
|
|
67
|
+
@athena: e0cf9097c590
|
|
68
68
|
"""
|
|
69
69
|
if self.state_path.exists():
|
|
70
70
|
try:
|
|
71
71
|
with open(self.state_path, "r") as f:
|
|
72
72
|
data = json.load(f)
|
|
73
73
|
self._state = {
|
|
74
|
-
key: TaskState.from_dict(value)
|
|
75
|
-
for key, value in data.items()
|
|
74
|
+
key: TaskState.from_dict(value) for key, value in data.items()
|
|
76
75
|
}
|
|
77
|
-
except (json.JSONDecodeError, KeyError)
|
|
76
|
+
except (json.JSONDecodeError, KeyError):
|
|
78
77
|
# If state file is corrupted, start fresh
|
|
79
78
|
self._state = {}
|
|
80
79
|
self._loaded = True
|
|
@@ -116,13 +115,13 @@ class StateManager:
|
|
|
116
115
|
self.load()
|
|
117
116
|
self._state[cache_key] = state
|
|
118
117
|
|
|
119
|
-
def prune(self, valid_task_hashes:
|
|
118
|
+
def prune(self, valid_task_hashes: Set[str]) -> None:
|
|
120
119
|
"""
|
|
121
120
|
Remove state entries for tasks that no longer exist.
|
|
122
121
|
|
|
123
122
|
Args:
|
|
124
123
|
valid_task_hashes: Set of valid task hashes from current recipe
|
|
125
|
-
@athena:
|
|
124
|
+
@athena: 2717c6c244d3
|
|
126
125
|
"""
|
|
127
126
|
if not self._loaded:
|
|
128
127
|
self.load()
|
tasktree/substitution.py
CHANGED
|
@@ -7,30 +7,30 @@ and {{ env.NAME }} placeholders with their corresponding values.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import re
|
|
10
|
-
from random import choice
|
|
11
10
|
from typing import Any
|
|
12
11
|
|
|
13
|
-
|
|
14
12
|
# Pattern matches: {{ prefix.name }} with optional whitespace
|
|
15
13
|
# Groups: (1) prefix (var|arg|env|tt), (2) name (identifier)
|
|
16
14
|
PLACEHOLDER_PATTERN = re.compile(
|
|
17
|
-
r
|
|
15
|
+
r"\{\{\s*(var|arg|env|tt)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*}}"
|
|
18
16
|
)
|
|
19
17
|
|
|
20
18
|
# Pattern matches: {{ dep.task_name.outputs.output_name }} with optional whitespace
|
|
21
19
|
# Groups: (1) task_name (can include dots for namespacing), (2) output_name (identifier)
|
|
22
20
|
DEP_OUTPUT_PATTERN = re.compile(
|
|
23
|
-
r
|
|
21
|
+
r"\{\{\s*dep\.([a-zA-Z_][a-zA-Z0-9_.-]*)\.outputs\.([a-zA-Z_][a-zA-Z0-9_]*)\s*}}"
|
|
24
22
|
)
|
|
25
23
|
|
|
26
24
|
# Pattern matches: {{ self.(inputs|outputs).name }} or {{ self.(inputs|outputs).0 }} with optional whitespace
|
|
27
25
|
# Groups: (1) field (inputs|outputs), (2) name (identifier) or index (numeric)
|
|
28
26
|
SELF_REFERENCE_PATTERN = re.compile(
|
|
29
|
-
r
|
|
27
|
+
r"\{\{\s*self\.(inputs|outputs)\.([a-zA-Z_][a-zA-Z0-9_]*|[0-9]+)\s*}}"
|
|
30
28
|
)
|
|
31
29
|
|
|
32
30
|
|
|
33
|
-
def substitute_variables(
|
|
31
|
+
def substitute_variables(
|
|
32
|
+
text: str | dict[str, Any], variables: dict[str, str]
|
|
33
|
+
) -> str | dict[str, Any]:
|
|
34
34
|
"""
|
|
35
35
|
Substitute {{ var.name }} placeholders with variable values.
|
|
36
36
|
|
|
@@ -52,13 +52,18 @@ def substitute_variables(text: str | dict[str, Any], variables: dict[str, str])
|
|
|
52
52
|
|
|
53
53
|
for arg_name in text.keys():
|
|
54
54
|
# Pull out and substitute the individual fields of an argument one at a time
|
|
55
|
-
for field in
|
|
55
|
+
for field in ["default", "min", "max"]:
|
|
56
56
|
if field in text[arg_name]:
|
|
57
|
-
text[arg_name][field] = substitute_variables(
|
|
57
|
+
text[arg_name][field] = substitute_variables(
|
|
58
|
+
text[arg_name][field], variables
|
|
59
|
+
)
|
|
58
60
|
|
|
59
61
|
# choices is a list of things
|
|
60
62
|
if "choices" in text[arg_name]:
|
|
61
|
-
text[arg_name]["choices"] = [
|
|
63
|
+
text[arg_name]["choices"] = [
|
|
64
|
+
substitute_variables(c, variables)
|
|
65
|
+
for c in text[arg_name]["choices"]
|
|
66
|
+
]
|
|
62
67
|
|
|
63
68
|
return text
|
|
64
69
|
else:
|
|
@@ -88,7 +93,9 @@ def substitute_variables(text: str | dict[str, Any], variables: dict[str, str])
|
|
|
88
93
|
return PLACEHOLDER_PATTERN.sub(replace_match, text)
|
|
89
94
|
|
|
90
95
|
|
|
91
|
-
def substitute_arguments(
|
|
96
|
+
def substitute_arguments(
|
|
97
|
+
text: str, args: dict[str, Any], exported_args: set[str] | None = None
|
|
98
|
+
) -> str:
|
|
92
99
|
"""
|
|
93
100
|
Substitute {{ arg.name }} placeholders with argument values.
|
|
94
101
|
|
|
@@ -172,9 +179,7 @@ def substitute_environment(text: str) -> str:
|
|
|
172
179
|
|
|
173
180
|
value = os.environ.get(name)
|
|
174
181
|
if value is None:
|
|
175
|
-
raise ValueError(
|
|
176
|
-
f"Environment variable '{name}' is not set"
|
|
177
|
-
)
|
|
182
|
+
raise ValueError(f"Environment variable '{name}' is not set")
|
|
178
183
|
|
|
179
184
|
return value
|
|
180
185
|
|
|
@@ -203,6 +208,7 @@ def substitute_builtin_variables(text: str, builtin_vars: dict[str, str]) -> str
|
|
|
203
208
|
'Root: /home/user/project'
|
|
204
209
|
@athena: 716250e3a71f
|
|
205
210
|
"""
|
|
211
|
+
|
|
206
212
|
def replace_match(match: re.Match) -> str:
|
|
207
213
|
prefix = match.group(1)
|
|
208
214
|
name = match.group(2)
|
|
@@ -226,7 +232,7 @@ def substitute_dependency_args(
|
|
|
226
232
|
template_value: str,
|
|
227
233
|
parent_task_name: str,
|
|
228
234
|
parent_args: dict[str, Any],
|
|
229
|
-
exported_args: set[str] | None = None
|
|
235
|
+
exported_args: set[str] | None = None,
|
|
230
236
|
) -> str:
|
|
231
237
|
"""
|
|
232
238
|
Substitute {{ arg.* }} templates in dependency argument values.
|
|
@@ -357,6 +363,7 @@ def substitute_dependency_outputs(
|
|
|
357
363
|
'Deploy dist/app.js'
|
|
358
364
|
@athena: 1e537c8d579c
|
|
359
365
|
"""
|
|
366
|
+
|
|
360
367
|
def replacer(match: re.Match) -> str:
|
|
361
368
|
dep_task_name = match.group(1)
|
|
362
369
|
output_name = match.group(2)
|
|
@@ -383,7 +390,11 @@ def substitute_dependency_outputs(
|
|
|
383
390
|
# Look up the named output
|
|
384
391
|
if output_name not in dep_task._output_map:
|
|
385
392
|
available = list(dep_task._output_map.keys())
|
|
386
|
-
available_msg =
|
|
393
|
+
available_msg = (
|
|
394
|
+
", ".join(available)
|
|
395
|
+
if available
|
|
396
|
+
else "(none - all outputs are anonymous)"
|
|
397
|
+
)
|
|
387
398
|
raise ValueError(
|
|
388
399
|
f"Task '{current_task_name}' references output '{output_name}' "
|
|
389
400
|
f"from task '{dep_task_name}', but '{dep_task_name}' has no output named '{output_name}'.\n"
|
|
@@ -447,6 +458,7 @@ def substitute_self_references(
|
|
|
447
458
|
'cp *.txt out/result.txt'
|
|
448
459
|
@athena: 9d997ff08eef
|
|
449
460
|
"""
|
|
461
|
+
|
|
450
462
|
def replacer(match: re.Match) -> str:
|
|
451
463
|
field = match.group(1) # "inputs" or "outputs"
|
|
452
464
|
identifier = match.group(2) # name or numeric index
|
tasktree/types.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import re
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from ipaddress import IPv4Address, IPv6Address, ip_address
|
|
6
|
-
from pathlib import Path
|
|
7
6
|
from typing import Any, Optional
|
|
8
7
|
|
|
9
8
|
import click
|
|
@@ -22,7 +21,9 @@ class HostnameType(click.ParamType):
|
|
|
22
21
|
r"^(?=.{1,253}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*\.?$"
|
|
23
22
|
)
|
|
24
23
|
|
|
25
|
-
def convert(
|
|
24
|
+
def convert(
|
|
25
|
+
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
|
|
26
|
+
) -> str:
|
|
26
27
|
"""
|
|
27
28
|
@athena: 8d921e52bcf2
|
|
28
29
|
"""
|
|
@@ -41,11 +42,11 @@ class EmailType(click.ParamType):
|
|
|
41
42
|
name = "email"
|
|
42
43
|
|
|
43
44
|
# Basic email validation (RFC 5322 simplified)
|
|
44
|
-
EMAIL_PATTERN = re.compile(
|
|
45
|
-
r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
|
|
46
|
-
)
|
|
45
|
+
EMAIL_PATTERN = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
|
|
47
46
|
|
|
48
|
-
def convert(
|
|
47
|
+
def convert(
|
|
48
|
+
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
|
|
49
|
+
) -> str:
|
|
49
50
|
"""
|
|
50
51
|
@athena: 25046aeb6e6f
|
|
51
52
|
"""
|
|
@@ -63,7 +64,9 @@ class IPType(click.ParamType):
|
|
|
63
64
|
|
|
64
65
|
name = "ip"
|
|
65
66
|
|
|
66
|
-
def convert(
|
|
67
|
+
def convert(
|
|
68
|
+
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
|
|
69
|
+
) -> str:
|
|
67
70
|
"""
|
|
68
71
|
@athena: d57618e5ad89
|
|
69
72
|
"""
|
|
@@ -82,7 +85,9 @@ class IPv4Type(click.ParamType):
|
|
|
82
85
|
|
|
83
86
|
name = "ipv4"
|
|
84
87
|
|
|
85
|
-
def convert(
|
|
88
|
+
def convert(
|
|
89
|
+
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
|
|
90
|
+
) -> str:
|
|
86
91
|
"""
|
|
87
92
|
@athena: 7ed2d17d1f1a
|
|
88
93
|
"""
|
|
@@ -101,7 +106,9 @@ class IPv6Type(click.ParamType):
|
|
|
101
106
|
|
|
102
107
|
name = "ipv6"
|
|
103
108
|
|
|
104
|
-
def convert(
|
|
109
|
+
def convert(
|
|
110
|
+
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
|
|
111
|
+
) -> str:
|
|
105
112
|
"""
|
|
106
113
|
@athena: 4b101e4d54cf
|
|
107
114
|
"""
|
|
@@ -120,7 +127,9 @@ class DateTimeType(click.ParamType):
|
|
|
120
127
|
|
|
121
128
|
name = "datetime"
|
|
122
129
|
|
|
123
|
-
def convert(
|
|
130
|
+
def convert(
|
|
131
|
+
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
|
|
132
|
+
) -> str:
|
|
124
133
|
"""
|
|
125
134
|
@athena: 13fa66adfe94
|
|
126
135
|
"""
|
|
@@ -130,7 +139,11 @@ class DateTimeType(click.ParamType):
|
|
|
130
139
|
return value
|
|
131
140
|
except ValueError:
|
|
132
141
|
pass
|
|
133
|
-
self.fail(
|
|
142
|
+
self.fail(
|
|
143
|
+
f"{value!r} is not a valid datetime (expected YYYY-MM-DDTHH:MM:SS format)",
|
|
144
|
+
param,
|
|
145
|
+
ctx,
|
|
146
|
+
)
|
|
134
147
|
|
|
135
148
|
|
|
136
149
|
# Type registry for dynamic parameter creation
|
|
@@ -149,7 +162,11 @@ TYPE_MAPPING = {
|
|
|
149
162
|
}
|
|
150
163
|
|
|
151
164
|
|
|
152
|
-
def get_click_type(
|
|
165
|
+
def get_click_type(
|
|
166
|
+
type_name: str,
|
|
167
|
+
min_val: int | float | None = None,
|
|
168
|
+
max_val: int | float | None = None,
|
|
169
|
+
) -> click.ParamType:
|
|
153
170
|
"""
|
|
154
171
|
Get Click parameter type by name with optional range constraints.
|
|
155
172
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tasktree
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.22
|
|
4
4
|
Summary: A task automation tool with incremental execution
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: click>=8.1.0
|
|
@@ -10,7 +10,9 @@ Requires-Dist: pyyaml>=6.0
|
|
|
10
10
|
Requires-Dist: rich>=13.0.0
|
|
11
11
|
Requires-Dist: typer>=0.9.0
|
|
12
12
|
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: black>=26.1.0; extra == 'dev'
|
|
13
14
|
Requires-Dist: pytest>=9.0.2; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.14.14; extra == 'dev'
|
|
14
16
|
Description-Content-Type: text/markdown
|
|
15
17
|
|
|
16
18
|
# Task Tree (tt)
|
|
@@ -255,20 +257,19 @@ tasks:
|
|
|
255
257
|
cmd: go build -o dist/binary # Command to execute
|
|
256
258
|
```
|
|
257
259
|
|
|
260
|
+
**Task name constraints:**
|
|
261
|
+
- Task names cannot contain dots (`.`) - they are reserved for namespacing imported tasks
|
|
262
|
+
- Example: `build.release` is invalid as a task name, but valid as a reference to task `release` in namespace `build`
|
|
263
|
+
|
|
258
264
|
### Commands
|
|
259
265
|
|
|
260
|
-
|
|
266
|
+
All commands are executed by writing them to temporary script files. This provides consistent behavior and better shell syntax support:
|
|
261
267
|
|
|
262
268
|
```yaml
|
|
263
269
|
tasks:
|
|
264
270
|
build:
|
|
265
271
|
cmd: cargo build --release
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
**Multi-line commands** are written to temporary script files for proper execution:
|
|
269
272
|
|
|
270
|
-
```yaml
|
|
271
|
-
tasks:
|
|
272
273
|
deploy:
|
|
273
274
|
cmd: |
|
|
274
275
|
mkdir -p dist
|
|
@@ -276,7 +277,7 @@ tasks:
|
|
|
276
277
|
rsync -av dist/ server:/opt/app/
|
|
277
278
|
```
|
|
278
279
|
|
|
279
|
-
|
|
280
|
+
Commands preserve shell syntax (line continuations, heredocs, etc.) and support shebangs on Unix/macOS.
|
|
280
281
|
|
|
281
282
|
Or use folded blocks for long single-line commands:
|
|
282
283
|
|
|
@@ -292,7 +293,7 @@ tasks:
|
|
|
292
293
|
|
|
293
294
|
### Execution Environments
|
|
294
295
|
|
|
295
|
-
Configure custom shell environments for task execution:
|
|
296
|
+
Configure custom shell environments for task execution. Use the `preamble` field to add initialization code to all commands:
|
|
296
297
|
|
|
297
298
|
```yaml
|
|
298
299
|
environments:
|
|
@@ -300,17 +301,14 @@ environments:
|
|
|
300
301
|
|
|
301
302
|
bash-strict:
|
|
302
303
|
shell: bash
|
|
303
|
-
|
|
304
|
-
preamble: | # For multi-line: prepended to script
|
|
304
|
+
preamble: | # Prepended to all commands
|
|
305
305
|
set -euo pipefail
|
|
306
306
|
|
|
307
307
|
python:
|
|
308
308
|
shell: python
|
|
309
|
-
args: ['-c']
|
|
310
309
|
|
|
311
310
|
powershell:
|
|
312
311
|
shell: powershell
|
|
313
|
-
args: ['-ExecutionPolicy', 'Bypass', '-Command']
|
|
314
312
|
preamble: |
|
|
315
313
|
$ErrorActionPreference = 'Stop'
|
|
316
314
|
|
|
@@ -339,8 +337,8 @@ tasks:
|
|
|
339
337
|
4. Platform default (bash on Unix, cmd on Windows)
|
|
340
338
|
|
|
341
339
|
**Platform defaults** when no environments are configured:
|
|
342
|
-
- **Unix/macOS**: bash
|
|
343
|
-
- **Windows**: cmd
|
|
340
|
+
- **Unix/macOS**: bash
|
|
341
|
+
- **Windows**: cmd
|
|
344
342
|
|
|
345
343
|
### Docker Environments
|
|
346
344
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
tasktree/__init__.py,sha256=ZfI6vy-TxinN7eK0_XjfHUFq83pFV1XiHFrai_SHNqw,1251
|
|
2
|
+
tasktree/cli.py,sha256=588ngMB27T6FqUp5LIJ_x_UNoPRlMv39b1_U0a88tfE,23577
|
|
3
|
+
tasktree/docker.py,sha256=DAvdr6kZqkhx8J9fhsz26Z41hFPreANC60vdunvPX00,15050
|
|
4
|
+
tasktree/executor.py,sha256=UF2H-qCgkJ5E2QoYt3To3a-ypwTIYJT9oyWRcBw2CzQ,46800
|
|
5
|
+
tasktree/graph.py,sha256=Ya4ATQidj0rlHxvvbWF0Xx2J18MTpz2XPAHTcJXFcBg,23637
|
|
6
|
+
tasktree/hasher.py,sha256=fvs7vNBrHdAA6pX-ZLhNnWLtd5qdxjAVFJC8SDF-DhQ,6730
|
|
7
|
+
tasktree/parser.py,sha256=OUuME_ZqITAMQEL90w-yJFLKoEquF_IIa-By8MifMvI,101074
|
|
8
|
+
tasktree/state.py,sha256=R4ZXgfs_rwrOH9nEb4mOjuHa7gAVqi7QPVCi0CouNQA,3961
|
|
9
|
+
tasktree/substitution.py,sha256=hwox1m9r8p5b0Fcln12C63GexJHc5ZMUoqOrpqQLuPk,18663
|
|
10
|
+
tasktree/types.py,sha256=nCiPrxg3r3ZX4CoZ6L5FrkJO9mgz_lFsZ-98X8tYgns,5015
|
|
11
|
+
tasktree-0.0.22.dist-info/METADATA,sha256=fuvZWDbtlwfbQNOP_TMCDRSDGKdZYUTeROo3eqVcJ6o,54519
|
|
12
|
+
tasktree-0.0.22.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
13
|
+
tasktree-0.0.22.dist-info/entry_points.txt,sha256=lQINlvRYnimvteBbnhH84A9clTg8NnpEjCWqWkqg8KE,40
|
|
14
|
+
tasktree-0.0.22.dist-info/RECORD,,
|
tasktree-0.0.21.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
tasktree/__init__.py,sha256=ZfI6vy-TxinN7eK0_XjfHUFq83pFV1XiHFrai_SHNqw,1251
|
|
2
|
-
tasktree/cli.py,sha256=pyXMUENz1ldl8z9SPXBZ1zhOHzmvIRUGu2y5VwRUdgo,22960
|
|
3
|
-
tasktree/docker.py,sha256=EeIUqptvDqSXbjWKXwWGhA4hWvsBjOk0Z-3cS3cqEOM,14857
|
|
4
|
-
tasktree/executor.py,sha256=x0K-TIVRmiKuUlur1UTOpB2xOJOE_Cv3-3MlE7xUNEk,47271
|
|
5
|
-
tasktree/graph.py,sha256=BjpLaa7EE2vmOcUlqhvp1KK7yT5VbXHQCbxljOhF84k,23603
|
|
6
|
-
tasktree/hasher.py,sha256=UM-2dqRE-rspQUbhA-noGHc80-Hu-QaxCTPRb_UzPto,6661
|
|
7
|
-
tasktree/parser.py,sha256=sXgotDdDQiNSmo7FaESFQ4j1OytaheyplJSdFrFXTYk,99286
|
|
8
|
-
tasktree/state.py,sha256=0fxKbMNYbo-dCpCTTnKTmJhGRy72G5kLnh99FScMLGU,3985
|
|
9
|
-
tasktree/substitution.py,sha256=2ubFn6jIX5qmtzyWSCxTut-_Cn0zew8KnwxMqDIPq1o,18545
|
|
10
|
-
tasktree/types.py,sha256=2uHdUncfyfw4jNWbQTBUirew-frmTjuk1ZYNd15cyQQ,4908
|
|
11
|
-
tasktree-0.0.21.dist-info/METADATA,sha256=nyjDhb1hdQMEiLx0t_qqonqPoyFZVMCrIg7xFIKxMs8,54353
|
|
12
|
-
tasktree-0.0.21.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
13
|
-
tasktree-0.0.21.dist-info/entry_points.txt,sha256=lQINlvRYnimvteBbnhH84A9clTg8NnpEjCWqWkqg8KE,40
|
|
14
|
-
tasktree-0.0.21.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|