tasktree 0.0.12__py3-none-any.whl → 0.0.14__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 +45 -3
- tasktree/substitution.py +39 -19
- {tasktree-0.0.12.dist-info → tasktree-0.0.14.dist-info}/METADATA +1 -1
- {tasktree-0.0.12.dist-info → tasktree-0.0.14.dist-info}/RECORD +6 -6
- {tasktree-0.0.12.dist-info → tasktree-0.0.14.dist-info}/WHEEL +0 -0
- {tasktree-0.0.12.dist-info → tasktree-0.0.14.dist-info}/entry_points.txt +0 -0
tasktree/cli.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
3
5
|
from pathlib import Path
|
|
4
6
|
from typing import Any, List, Optional
|
|
5
7
|
|
|
@@ -26,6 +28,46 @@ app = typer.Typer(
|
|
|
26
28
|
console = Console()
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
def _supports_unicode() -> bool:
|
|
32
|
+
"""Check if the terminal supports Unicode characters.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
True if terminal supports UTF-8, False otherwise
|
|
36
|
+
"""
|
|
37
|
+
# Hard stop: classic Windows console (conhost)
|
|
38
|
+
if os.name == "nt" and "WT_SESSION" not in os.environ:
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
# Encoding check
|
|
42
|
+
encoding = sys.stdout.encoding
|
|
43
|
+
if not encoding:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
"✓✗".encode(encoding)
|
|
48
|
+
return True
|
|
49
|
+
except UnicodeEncodeError:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_action_success_string() -> str:
|
|
54
|
+
"""Get the appropriate success symbol based on terminal capabilities.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Unicode tick symbol (✓) if terminal supports UTF-8, otherwise "[ OK ]"
|
|
58
|
+
"""
|
|
59
|
+
return "✓" if _supports_unicode() else "[ OK ]"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_action_failure_string() -> str:
|
|
63
|
+
"""Get the appropriate failure symbol based on terminal capabilities.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Unicode cross symbol (✗) if terminal supports UTF-8, otherwise "[ FAIL ]"
|
|
67
|
+
"""
|
|
68
|
+
return "✗" if _supports_unicode() else "[ FAIL ]"
|
|
69
|
+
|
|
70
|
+
|
|
29
71
|
def _format_task_arguments(arg_specs: list[str | dict]) -> str:
|
|
30
72
|
"""Format task arguments for display in list output.
|
|
31
73
|
|
|
@@ -324,7 +366,7 @@ def _clean_state(tasks_file: Optional[str] = None) -> None:
|
|
|
324
366
|
|
|
325
367
|
if state_path.exists():
|
|
326
368
|
state_path.unlink()
|
|
327
|
-
console.print(f"[green]
|
|
369
|
+
console.print(f"[green]{get_action_success_string()} Removed {state_path}[/green]")
|
|
328
370
|
console.print("All tasks will run fresh on next execution")
|
|
329
371
|
else:
|
|
330
372
|
console.print(f"[yellow]No state file found at {state_path}[/yellow]")
|
|
@@ -410,9 +452,9 @@ def _execute_dynamic_task(args: list[str], force: bool = False, only: bool = Fal
|
|
|
410
452
|
state.save()
|
|
411
453
|
try:
|
|
412
454
|
executor.execute_task(task_name, args_dict, force=force, only=only)
|
|
413
|
-
console.print(f"[green]
|
|
455
|
+
console.print(f"[green]{get_action_success_string()} Task '{task_name}' completed successfully[/green]")
|
|
414
456
|
except Exception as e:
|
|
415
|
-
console.print(f"[red]
|
|
457
|
+
console.print(f"[red]{get_action_failure_string()} Task '{task_name}' failed: {e}[/red]")
|
|
416
458
|
raise typer.Exit(1)
|
|
417
459
|
|
|
418
460
|
|
tasktree/substitution.py
CHANGED
|
@@ -5,6 +5,7 @@ and {{ env.NAME }} placeholders with their corresponding values.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import re
|
|
8
|
+
from random import choice
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
10
11
|
|
|
@@ -15,11 +16,11 @@ PLACEHOLDER_PATTERN = re.compile(
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def substitute_variables(text: str, variables: dict[str, str]) -> str:
|
|
19
|
+
def substitute_variables(text: str | dict[str, Any], variables: dict[str, str]) -> str | dict[str, Any]:
|
|
19
20
|
"""Substitute {{ var.name }} placeholders with variable values.
|
|
20
21
|
|
|
21
22
|
Args:
|
|
22
|
-
text: Text containing {{ var.name }} placeholders
|
|
23
|
+
text: Text containing {{ var.name }} placeholders, or an argument dict with elements to be substituted
|
|
23
24
|
variables: Dictionary mapping variable names to their string values
|
|
24
25
|
|
|
25
26
|
Returns:
|
|
@@ -28,23 +29,42 @@ def substitute_variables(text: str, variables: dict[str, str]) -> str:
|
|
|
28
29
|
Raises:
|
|
29
30
|
ValueError: If a referenced variable is not defined
|
|
30
31
|
"""
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
if isinstance(text, dict):
|
|
33
|
+
# The dict will only contain a single key, the value of this key should also be a dictionary, which contains
|
|
34
|
+
# the actual details of the argument.
|
|
35
|
+
assert len(text.keys()) == 1
|
|
36
|
+
|
|
37
|
+
for arg_name in text.keys():
|
|
38
|
+
# Pull out and substitute the individual fields of an argument one at a time
|
|
39
|
+
for field in [ "default", "min", "max" ]:
|
|
40
|
+
if field in text[arg_name]:
|
|
41
|
+
text[arg_name][field] = substitute_variables(text[arg_name][field], variables)
|
|
42
|
+
|
|
43
|
+
# choices is a list of things
|
|
44
|
+
if "choices" in text[arg_name]:
|
|
45
|
+
text[arg_name]["choices"] = [substitute_variables(c, variables) for c in text[arg_name]["choices"]]
|
|
46
|
+
|
|
47
|
+
return text
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError("Empty arg dictionary")
|
|
50
|
+
else:
|
|
51
|
+
def replace_match(match: re.Match) -> str:
|
|
52
|
+
prefix = match.group(1)
|
|
53
|
+
name = match.group(2)
|
|
54
|
+
|
|
55
|
+
# Only substitute var: placeholders
|
|
56
|
+
if prefix != "var":
|
|
57
|
+
return match.group(0) # Return unchanged
|
|
58
|
+
|
|
59
|
+
if name not in variables:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Variable '{name}' is not defined. "
|
|
62
|
+
f"Variables must be defined before use."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return variables[name]
|
|
66
|
+
|
|
67
|
+
return PLACEHOLDER_PATTERN.sub(replace_match, text)
|
|
48
68
|
|
|
49
69
|
|
|
50
70
|
def substitute_arguments(text: str, args: dict[str, Any], exported_args: set[str] | None = None) -> str:
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
tasktree/__init__.py,sha256=MVmdvKb3JdqLlo0x2_TPGMfgFC0HsDnP79HAzGnFnjI,1081
|
|
2
|
-
tasktree/cli.py,sha256=
|
|
2
|
+
tasktree/cli.py,sha256=ywaLXEehOHcMsBioL4RxPQ1vTWIG1V7Hb0TZ1YI2F_g,18820
|
|
3
3
|
tasktree/docker.py,sha256=qvja8G63uAcC73YMVY739egda1_CcBtoqzm0qIJU_Q8,14443
|
|
4
4
|
tasktree/executor.py,sha256=Q7Bks5B88i-IyZDpxGSps9MM3uflz0U3yn4Rtq_uHMM,42266
|
|
5
5
|
tasktree/graph.py,sha256=oXLxX0Ix4zSkVBg8_3x9K7WxSFpg136sp4MF-d2mDEQ,9682
|
|
6
6
|
tasktree/hasher.py,sha256=0GrnCfwAXnwq_kpnHFFb12B5_2VFNXx6Ng7hTdcCyXo,4415
|
|
7
7
|
tasktree/parser.py,sha256=XdFuELqrrhHc45HeMpo6-gflopZM7kYTqO1lQcFwtFk,68782
|
|
8
8
|
tasktree/state.py,sha256=Cktl4D8iDZVd55aO2LqVyPrc-BnljkesxxkcMcdcfOY,3541
|
|
9
|
-
tasktree/substitution.py,sha256=
|
|
9
|
+
tasktree/substitution.py,sha256=UAq69YO4uGkvFfd5mtGy6CwFZ_uxZGf4HGpnvCBVBuc,7499
|
|
10
10
|
tasktree/tasks.py,sha256=2QdQZtJAX2rSGbyXKG1z9VF_siz1DUzdvzCgPkykxtU,173
|
|
11
11
|
tasktree/types.py,sha256=R_YAyO5bMLB6XZnkMRT7VAtlkA_Xx6xu0aIpzQjrBXs,4357
|
|
12
|
-
tasktree-0.0.
|
|
13
|
-
tasktree-0.0.
|
|
14
|
-
tasktree-0.0.
|
|
15
|
-
tasktree-0.0.
|
|
12
|
+
tasktree-0.0.14.dist-info/METADATA,sha256=JnlgocAOtAq_Q-vWrOkYM6mofrCeH8xiGFKYqaQmX4M,37234
|
|
13
|
+
tasktree-0.0.14.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
tasktree-0.0.14.dist-info/entry_points.txt,sha256=lQINlvRYnimvteBbnhH84A9clTg8NnpEjCWqWkqg8KE,40
|
|
15
|
+
tasktree-0.0.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|