zrb 1.0.0a1__py3-none-any.whl → 1.0.0a3__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.
- zrb/__init__.py +48 -39
- zrb/__main__.py +3 -3
- zrb/attr/type.py +2 -1
- zrb/builtin/__init__.py +40 -2
- zrb/builtin/base64.py +32 -0
- zrb/builtin/git.py +156 -0
- zrb/builtin/git_subtree.py +88 -0
- zrb/builtin/group.py +34 -0
- zrb/builtin/llm.py +31 -0
- zrb/builtin/md5.py +34 -0
- zrb/builtin/project/__init__.py +0 -0
- zrb/builtin/project/add/__init__.py +0 -0
- zrb/builtin/project/add/fastapp.py +72 -0
- zrb/builtin/project/add/fastapp_template/.gitignore +4 -0
- zrb/builtin/project/add/fastapp_template/README.md +7 -0
- zrb/builtin/project/add/fastapp_template/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/_zrb/config.py +17 -0
- zrb/builtin/project/add/fastapp_template/_zrb/group.py +16 -0
- zrb/builtin/project/add/fastapp_template/_zrb/helper.py +97 -0
- zrb/builtin/project/add/fastapp_template/_zrb/main.py +132 -0
- zrb/builtin/project/add/fastapp_template/_zrb/venv_task.py +22 -0
- zrb/builtin/project/add/fastapp_template/common/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/common/app.py +18 -0
- zrb/builtin/project/add/fastapp_template/common/db_engine.py +5 -0
- zrb/builtin/project/add/fastapp_template/common/db_repository.py +134 -0
- zrb/builtin/project/add/fastapp_template/common/error.py +8 -0
- zrb/builtin/project/add/fastapp_template/common/schema.py +5 -0
- zrb/builtin/project/add/fastapp_template/common/usecase.py +232 -0
- zrb/builtin/project/add/fastapp_template/config.py +29 -0
- zrb/builtin/project/add/fastapp_template/main.py +7 -0
- zrb/builtin/project/add/fastapp_template/migrate.py +3 -0
- zrb/builtin/project/add/fastapp_template/module/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/alembic.ini +117 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/api_client.py +7 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/base_client.py +27 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/direct_client.py +6 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/factory.py +9 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/README +1 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/env.py +108 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/script.py.mako +26 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/versions/3093c7336477_add_user_table.py +37 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration_metadata.py +6 -0
- zrb/builtin/project/add/fastapp_template/module/auth/route.py +22 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/db_repository.py +39 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/factory.py +13 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/repository.py +34 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/usecase.py +45 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/alembic.ini +117 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/README +1 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/env.py +108 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/script.py.mako +26 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/versions/.gitkeep +0 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration_metadata.py +3 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/route.py +27 -0
- zrb/builtin/project/add/fastapp_template/requirements.txt +6 -0
- zrb/builtin/project/add/fastapp_template/schema/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/schema/role.py +31 -0
- zrb/builtin/project/add/fastapp_template/schema/user.py +31 -0
- zrb/builtin/project/add/fastapp_template/template.env +2 -0
- zrb/builtin/project/create/__init__.py +0 -0
- zrb/builtin/project/create/create.py +41 -0
- zrb/builtin/project/create/project-template/README.md +3 -0
- zrb/builtin/project/create/project-template/zrb_init.py +7 -0
- zrb/builtin/python.py +11 -0
- zrb/builtin/shell/__init__.py +0 -5
- zrb/builtin/shell/autocomplete/__init__.py +0 -9
- zrb/builtin/shell/autocomplete/bash.py +5 -6
- zrb/builtin/shell/autocomplete/subcmd.py +7 -8
- zrb/builtin/shell/autocomplete/zsh.py +5 -6
- zrb/builtin/todo.py +186 -0
- zrb/callback/any_callback.py +1 -1
- zrb/callback/callback.py +5 -5
- zrb/cmd/cmd_val.py +2 -2
- zrb/config.py +4 -1
- zrb/content_transformer/any_content_transformer.py +1 -1
- zrb/content_transformer/content_transformer.py +2 -2
- zrb/context/any_context.py +5 -1
- zrb/context/any_shared_context.py +3 -3
- zrb/context/context.py +15 -9
- zrb/context/shared_context.py +9 -8
- zrb/env/__init__.py +0 -3
- zrb/env/any_env.py +2 -2
- zrb/env/env.py +4 -5
- zrb/env/env_file.py +4 -4
- zrb/env/env_map.py +4 -4
- zrb/group/__init__.py +0 -3
- zrb/group/any_group.py +3 -3
- zrb/group/group.py +7 -6
- zrb/input/any_input.py +1 -1
- zrb/input/base_input.py +4 -4
- zrb/input/bool_input.py +5 -5
- zrb/input/float_input.py +3 -3
- zrb/input/int_input.py +3 -3
- zrb/input/option_input.py +51 -0
- zrb/input/password_input.py +2 -2
- zrb/input/str_input.py +1 -1
- zrb/input/text_input.py +12 -10
- zrb/runner/cli.py +79 -44
- zrb/runner/web_app/group_info_ui/controller.py +7 -8
- zrb/runner/web_app/group_info_ui/view.html +2 -2
- zrb/runner/web_app/home_page/controller.py +7 -6
- zrb/runner/web_app/home_page/view.html +2 -2
- zrb/runner/web_app/task_ui/controller.py +13 -13
- zrb/runner/web_app/task_ui/partial/common-util.js +37 -0
- zrb/runner/web_app/task_ui/partial/main.js +9 -2
- zrb/runner/web_app/task_ui/partial/show-existing-session.js +20 -5
- zrb/runner/web_app/task_ui/partial/visualize-history.js +1 -41
- zrb/runner/web_app/task_ui/view.html +4 -2
- zrb/runner/web_server.py +137 -211
- zrb/runner/web_util.py +5 -35
- zrb/session/any_session.py +13 -7
- zrb/session/session.py +80 -41
- zrb/session_state_log/session_state_log.py +7 -5
- zrb/session_state_logger/any_session_state_logger.py +1 -1
- zrb/session_state_logger/default_session_state_logger.py +2 -2
- zrb/session_state_logger/file_session_state_logger.py +19 -27
- zrb/task/any_task.py +8 -3
- zrb/task/base_task.py +47 -33
- zrb/task/base_trigger.py +11 -12
- zrb/task/cmd_task.py +55 -43
- zrb/task/http_check.py +8 -8
- zrb/task/llm_task.py +160 -0
- zrb/task/make_task.py +9 -9
- zrb/task/rsync_task.py +7 -7
- zrb/task/scaffolder.py +14 -11
- zrb/task/scheduler.py +6 -7
- zrb/task/task.py +1 -1
- zrb/task/tcp_check.py +8 -8
- zrb/util/attr.py +19 -3
- zrb/util/cli/style.py +71 -2
- zrb/util/cli/subcommand.py +2 -2
- zrb/util/codemod/__init__.py +0 -0
- zrb/util/codemod/add_code_to_class.py +35 -0
- zrb/util/codemod/add_code_to_function.py +36 -0
- zrb/util/codemod/add_code_to_method.py +55 -0
- zrb/util/codemod/add_key_to_dict.py +51 -0
- zrb/util/codemod/add_param_to_function_call.py +39 -0
- zrb/util/codemod/add_property_to_class.py +55 -0
- zrb/util/git.py +156 -0
- zrb/util/git_subtree.py +94 -0
- zrb/util/group.py +2 -2
- zrb/util/llm/tool.py +63 -0
- zrb/util/string/conversion.py +7 -0
- zrb/util/todo.py +135 -0
- {zrb-1.0.0a1.dist-info → zrb-1.0.0a3.dist-info}/METADATA +11 -7
- zrb-1.0.0a3.dist-info/RECORD +194 -0
- zrb/builtin/shell/_group.py +0 -9
- zrb/builtin/shell/autocomplete/_group.py +0 -6
- zrb/runner/web_app/any_request_handler.py +0 -24
- zrb/runner/web_server.bak.py +0 -208
- zrb-1.0.0a1.dist-info/RECORD +0 -120
- {zrb-1.0.0a1.dist-info → zrb-1.0.0a3.dist-info}/WHEEL +0 -0
- {zrb-1.0.0a1.dist-info → zrb-1.0.0a3.dist-info}/entry_points.txt +0 -0
zrb/util/git.py
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
import subprocess
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class DiffResult(BaseModel):
|
7
|
+
created: list[str]
|
8
|
+
removed: list[str]
|
9
|
+
updated: list[str]
|
10
|
+
|
11
|
+
|
12
|
+
def get_diff(source_commit: str, current_commit: str) -> DiffResult:
|
13
|
+
# git show b176b5a main
|
14
|
+
exit_status, output = subprocess.getstatusoutput(
|
15
|
+
f"git diff {source_commit} {current_commit}"
|
16
|
+
)
|
17
|
+
if exit_status != 0:
|
18
|
+
raise Exception(output)
|
19
|
+
lines = output.split("\n")
|
20
|
+
diff: dict[str, dict[str, bool]] = {}
|
21
|
+
for line in lines:
|
22
|
+
if not line.startswith("---") and not line.startswith("+++"):
|
23
|
+
continue
|
24
|
+
if line[4:6] != "a/" and line[4:6] != "b/":
|
25
|
+
continue
|
26
|
+
# line should contains something like `--- a/some-file.txt`
|
27
|
+
file = line[6:]
|
28
|
+
if file not in diff:
|
29
|
+
diff[file] = {"plus": False, "minus": False}
|
30
|
+
if line.startswith("---"):
|
31
|
+
diff[file]["minus"] = True
|
32
|
+
if line.startswith("+++"):
|
33
|
+
diff[file]["plus"] = True
|
34
|
+
return DiffResult(
|
35
|
+
created=[
|
36
|
+
file for file, state in diff.items() if state["plus"] and not state["minus"]
|
37
|
+
],
|
38
|
+
removed=[
|
39
|
+
file for file, state in diff.items() if not state["plus"] and state["minus"]
|
40
|
+
],
|
41
|
+
updated=[
|
42
|
+
file for file, state in diff.items() if state["plus"] and state["minus"]
|
43
|
+
],
|
44
|
+
)
|
45
|
+
|
46
|
+
|
47
|
+
def get_repo_dir() -> str:
|
48
|
+
try:
|
49
|
+
# Run the Git command to get the repository's top-level directory
|
50
|
+
result = subprocess.run(
|
51
|
+
["git", "rev-parse", "--show-toplevel"],
|
52
|
+
stdout=subprocess.PIPE,
|
53
|
+
stderr=subprocess.PIPE,
|
54
|
+
text=True,
|
55
|
+
check=True,
|
56
|
+
)
|
57
|
+
# Return the directory path
|
58
|
+
return result.stdout.strip()
|
59
|
+
except subprocess.CalledProcessError as e:
|
60
|
+
raise Exception(e.stderr or e.stdout)
|
61
|
+
|
62
|
+
|
63
|
+
def get_current_branch() -> str:
|
64
|
+
try:
|
65
|
+
result = subprocess.run(
|
66
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
67
|
+
stdout=subprocess.PIPE,
|
68
|
+
stderr=subprocess.PIPE,
|
69
|
+
text=True,
|
70
|
+
check=True,
|
71
|
+
)
|
72
|
+
return result.stdout.strip()
|
73
|
+
except subprocess.CalledProcessError as e:
|
74
|
+
raise Exception(e.stderr or e.stdout)
|
75
|
+
|
76
|
+
|
77
|
+
def get_branches() -> list[str]:
|
78
|
+
try:
|
79
|
+
result = subprocess.run(
|
80
|
+
["git", "branch"],
|
81
|
+
stdout=subprocess.PIPE,
|
82
|
+
stderr=subprocess.PIPE,
|
83
|
+
text=True,
|
84
|
+
check=True,
|
85
|
+
)
|
86
|
+
return [
|
87
|
+
branch.lstrip("*").strip() for branch in result.stdout.strip().split("\n")
|
88
|
+
]
|
89
|
+
except subprocess.CalledProcessError as e:
|
90
|
+
raise Exception(e.stderr or e.stdout)
|
91
|
+
|
92
|
+
|
93
|
+
def delete_branch(branch_name: str) -> str:
|
94
|
+
try:
|
95
|
+
result = subprocess.run(
|
96
|
+
["git", "branch", "-D", branch_name],
|
97
|
+
stdout=subprocess.PIPE,
|
98
|
+
stderr=subprocess.PIPE,
|
99
|
+
text=True,
|
100
|
+
check=True,
|
101
|
+
)
|
102
|
+
return result.stdout.strip()
|
103
|
+
except subprocess.CalledProcessError as e:
|
104
|
+
raise Exception(e.stderr or e.stdout)
|
105
|
+
|
106
|
+
|
107
|
+
def add() -> str:
|
108
|
+
try:
|
109
|
+
subprocess.run(
|
110
|
+
["git", "add", ".", "-A"],
|
111
|
+
stdout=subprocess.PIPE,
|
112
|
+
stderr=subprocess.PIPE,
|
113
|
+
text=True,
|
114
|
+
check=True,
|
115
|
+
)
|
116
|
+
except subprocess.CalledProcessError as e:
|
117
|
+
raise Exception(e.stderr or e.stdout)
|
118
|
+
|
119
|
+
|
120
|
+
def commit(message: str) -> str:
|
121
|
+
try:
|
122
|
+
subprocess.run(
|
123
|
+
["git", "commit", "-m", message],
|
124
|
+
stdout=subprocess.PIPE,
|
125
|
+
stderr=subprocess.PIPE,
|
126
|
+
text=True,
|
127
|
+
check=True,
|
128
|
+
)
|
129
|
+
except subprocess.CalledProcessError as e:
|
130
|
+
raise Exception(e.stderr or e.stdout)
|
131
|
+
|
132
|
+
|
133
|
+
def pull(remote: str, branch: str) -> str:
|
134
|
+
try:
|
135
|
+
subprocess.run(
|
136
|
+
["git", "pull", remote, branch],
|
137
|
+
stdout=subprocess.PIPE,
|
138
|
+
stderr=subprocess.PIPE,
|
139
|
+
text=True,
|
140
|
+
check=True,
|
141
|
+
)
|
142
|
+
except subprocess.CalledProcessError as e:
|
143
|
+
raise Exception(e.stderr or e.stdout)
|
144
|
+
|
145
|
+
|
146
|
+
def push(remote: str, branch: str) -> str:
|
147
|
+
try:
|
148
|
+
subprocess.run(
|
149
|
+
["git", "push", "-u", remote, branch],
|
150
|
+
stdout=subprocess.PIPE,
|
151
|
+
stderr=subprocess.PIPE,
|
152
|
+
text=True,
|
153
|
+
check=True,
|
154
|
+
)
|
155
|
+
except subprocess.CalledProcessError as e:
|
156
|
+
raise Exception(e.stderr or e.stdout)
|
zrb/util/git_subtree.py
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
import os
|
2
|
+
import subprocess
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
from zrb.util.git import get_repo_dir
|
7
|
+
|
8
|
+
|
9
|
+
class SingleSubTreeConfig(BaseModel):
|
10
|
+
repo_url: str
|
11
|
+
branch: str
|
12
|
+
prefix: str
|
13
|
+
|
14
|
+
|
15
|
+
class SubTreeConfig(BaseModel):
|
16
|
+
data: dict[str, SingleSubTreeConfig]
|
17
|
+
|
18
|
+
|
19
|
+
def load_config() -> SubTreeConfig:
|
20
|
+
file_path = os.path.join(get_repo_dir(), "subtrees.json")
|
21
|
+
if not os.path.exists(file_path):
|
22
|
+
return SubTreeConfig(data={})
|
23
|
+
with open(file_path, "r") as f:
|
24
|
+
return SubTreeConfig.model_validate_json(f.read())
|
25
|
+
|
26
|
+
|
27
|
+
def save_config(config: SubTreeConfig):
|
28
|
+
file_path = os.path.join(get_repo_dir(), "subtrees.json")
|
29
|
+
with open(file_path, "w") as f:
|
30
|
+
f.write(config.model_dump_json(indent=2))
|
31
|
+
|
32
|
+
|
33
|
+
def add_subtree(name: str, repo_url: str, branch: str, prefix: str):
|
34
|
+
config = load_config()
|
35
|
+
if os.path.isdir(prefix):
|
36
|
+
raise ValueError(f"Directory exists: {prefix}")
|
37
|
+
if name in config.data:
|
38
|
+
raise ValueError(f"Subtree config already exists: {name}")
|
39
|
+
try:
|
40
|
+
subprocess.run(
|
41
|
+
["git", "subtree", "add", "--prefix", prefix, repo_url, branch],
|
42
|
+
stdout=subprocess.PIPE,
|
43
|
+
stderr=subprocess.PIPE,
|
44
|
+
text=True,
|
45
|
+
check=True,
|
46
|
+
)
|
47
|
+
except subprocess.CalledProcessError as e:
|
48
|
+
raise Exception(e.stderr or e.stdout)
|
49
|
+
config.data[name] = SingleSubTreeConfig(
|
50
|
+
repo_url=repo_url, branch=branch, prefix=prefix
|
51
|
+
)
|
52
|
+
save_config(config)
|
53
|
+
|
54
|
+
|
55
|
+
def pull_subtree(prefix: str, repo_url: str, branch: str):
|
56
|
+
try:
|
57
|
+
subprocess.run(
|
58
|
+
[
|
59
|
+
"git",
|
60
|
+
"subtree",
|
61
|
+
"pull",
|
62
|
+
"--prefix",
|
63
|
+
prefix,
|
64
|
+
repo_url,
|
65
|
+
branch,
|
66
|
+
],
|
67
|
+
stdout=subprocess.PIPE,
|
68
|
+
stderr=subprocess.PIPE,
|
69
|
+
text=True,
|
70
|
+
check=True,
|
71
|
+
)
|
72
|
+
except subprocess.CalledProcessError as e:
|
73
|
+
raise Exception(e.stderr or e.stdout)
|
74
|
+
|
75
|
+
|
76
|
+
def push_subtree(prefix: str, repo_url: str, branch: str):
|
77
|
+
try:
|
78
|
+
subprocess.run(
|
79
|
+
[
|
80
|
+
"git",
|
81
|
+
"subtree",
|
82
|
+
"push",
|
83
|
+
"--prefix",
|
84
|
+
prefix,
|
85
|
+
repo_url,
|
86
|
+
branch,
|
87
|
+
],
|
88
|
+
stdout=subprocess.PIPE,
|
89
|
+
stderr=subprocess.PIPE,
|
90
|
+
text=True,
|
91
|
+
check=True,
|
92
|
+
)
|
93
|
+
except subprocess.CalledProcessError as e:
|
94
|
+
raise Exception(e.stderr or e.stdout)
|
zrb/util/group.py
CHANGED
zrb/util/llm/tool.py
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
import inspect
|
2
|
+
from collections.abc import Callable
|
3
|
+
from typing import Any, get_type_hints
|
4
|
+
|
5
|
+
|
6
|
+
def callable_to_tool_schema(
|
7
|
+
callable_obj: Callable, name: str | None = None, description: str | None = None
|
8
|
+
) -> dict[str, Any]:
|
9
|
+
"""
|
10
|
+
Convert a callable into a tool schema dictionary by deriving the parameter schema.
|
11
|
+
|
12
|
+
:param callable_obj: The callable object (e.g., a function).
|
13
|
+
:param name: The name to assign to the function in the schema.
|
14
|
+
:param description: A description of the function.
|
15
|
+
:return: A dictionary representing the tool schema.
|
16
|
+
"""
|
17
|
+
if not callable(callable_obj):
|
18
|
+
raise ValueError("Provided object is not callable")
|
19
|
+
# Derive name and description
|
20
|
+
name = name or callable_obj.__name__
|
21
|
+
description = description or (callable_obj.__doc__ or "").strip()
|
22
|
+
# Get function signature
|
23
|
+
sig = inspect.signature(callable_obj)
|
24
|
+
hints = get_type_hints(callable_obj)
|
25
|
+
# Build parameter schema
|
26
|
+
param_schema = {"type": "object", "properties": {}, "required": []}
|
27
|
+
for param_name, param in sig.parameters.items():
|
28
|
+
param_type = hints.get(param_name, str) # Default type is string
|
29
|
+
param_schema["properties"][param_name] = {
|
30
|
+
"type": _python_type_to_json_type(param_type)
|
31
|
+
}
|
32
|
+
if param.default is inspect.Parameter.empty:
|
33
|
+
param_schema["required"].append(param_name)
|
34
|
+
return {
|
35
|
+
"type": "function",
|
36
|
+
"function": {
|
37
|
+
"name": name,
|
38
|
+
"description": description,
|
39
|
+
"parameters": param_schema,
|
40
|
+
},
|
41
|
+
}
|
42
|
+
|
43
|
+
|
44
|
+
def _python_type_to_json_type(py_type):
|
45
|
+
"""
|
46
|
+
Map Python types to JSON Schema types.
|
47
|
+
"""
|
48
|
+
if py_type is str:
|
49
|
+
return "string"
|
50
|
+
elif py_type is int:
|
51
|
+
return "integer"
|
52
|
+
elif py_type is float:
|
53
|
+
return "number"
|
54
|
+
elif py_type is bool:
|
55
|
+
return "boolean"
|
56
|
+
elif py_type is list:
|
57
|
+
return "array"
|
58
|
+
elif py_type is dict:
|
59
|
+
return "object"
|
60
|
+
elif py_type in {None, type(None)}:
|
61
|
+
return "null"
|
62
|
+
else:
|
63
|
+
return "string" # Default to string for unsupported types
|
zrb/util/string/conversion.py
CHANGED
@@ -5,6 +5,13 @@ TRUE_STRS = ["true", "1", "yes", "y", "active", "on"]
|
|
5
5
|
FALSE_STRS = ["false", "0", "no", "n", "inactive", "off"]
|
6
6
|
|
7
7
|
|
8
|
+
def double_quote(input_string: str) -> str:
|
9
|
+
# Escape necessary characters: backslashes and double quotes
|
10
|
+
escaped_string = re.sub(r'([\\"])', r"\\\1", input_string)
|
11
|
+
# Wrap in double quotes
|
12
|
+
return f'"{escaped_string}"'
|
13
|
+
|
14
|
+
|
8
15
|
def to_boolean(text: str) -> bool:
|
9
16
|
if text.lower() in TRUE_STRS:
|
10
17
|
return True
|
zrb/util/todo.py
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
import datetime
|
2
|
+
import re
|
3
|
+
|
4
|
+
from pydantic import BaseModel, Field, model_validator
|
5
|
+
|
6
|
+
|
7
|
+
class TodoTask(BaseModel):
|
8
|
+
priority: str | None = Field("D", pattern=r"^[A-Z]$") # Priority like A, B, ...
|
9
|
+
completed: bool = False # True if completed, False otherwise
|
10
|
+
description: str # Main task description
|
11
|
+
projects: list[str] = [] # List of projects (e.g., +Project)
|
12
|
+
contexts: list[str] = [] # List of contexts (e.g., @Context)
|
13
|
+
keyval: dict[str, str] = {} # Special key (e.g., due:2016-05-30)
|
14
|
+
creation_date: datetime.date | None = None # Creation date
|
15
|
+
completion_date: datetime.date | None = None # Completion date
|
16
|
+
|
17
|
+
@model_validator(mode="before")
|
18
|
+
def validate_dates(cls, values):
|
19
|
+
completion_date = values.get("completion_date")
|
20
|
+
creation_date = values.get("creation_date")
|
21
|
+
if completion_date and not creation_date:
|
22
|
+
raise ValueError(
|
23
|
+
"creation_date must be specified if completion_date is set."
|
24
|
+
)
|
25
|
+
return values
|
26
|
+
|
27
|
+
|
28
|
+
TODO_TXT_PATTERN = re.compile(
|
29
|
+
r"^(?P<status>x)?\s*" # Optional completion mark ('x')
|
30
|
+
r"(?:\((?P<priority>[A-Z])\)\s+)?" # Optional priority (e.g., '(A)')
|
31
|
+
r"(?P<date1>\d{4}-\d{2}-\d{2})?\s*" # Optional first date
|
32
|
+
r"(?P<date2>\d{4}-\d{2}-\d{2})?\s*" # Optional second date
|
33
|
+
r"(?P<description>.*?)$" # Main description
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
def read_todo_from_file(todo_file_path: str) -> list[TodoTask]:
|
38
|
+
with open(todo_file_path, "r") as f:
|
39
|
+
todo_lines = f.read().strip().split("\n")
|
40
|
+
todo_tasks: list[TodoTask] = []
|
41
|
+
for todo_line in todo_lines:
|
42
|
+
todo_line = todo_line.strip()
|
43
|
+
if todo_line == "":
|
44
|
+
continue
|
45
|
+
todo_tasks.append(parse_todo_line(todo_line))
|
46
|
+
todo_tasks.sort(
|
47
|
+
key=lambda task: (
|
48
|
+
task.completed,
|
49
|
+
task.priority if task.priority else "Z",
|
50
|
+
task.projects[0] if task.projects else "zzz",
|
51
|
+
task.creation_date if task.creation_date else datetime.date.max,
|
52
|
+
)
|
53
|
+
)
|
54
|
+
return todo_tasks
|
55
|
+
|
56
|
+
|
57
|
+
def write_todo_to_file(todo_file_path: str, todo_task_list: list[TodoTask]):
|
58
|
+
with open(todo_file_path, "w") as f:
|
59
|
+
for todo_task in todo_task_list:
|
60
|
+
f.write(todo_task_to_line(todo_task))
|
61
|
+
|
62
|
+
|
63
|
+
def parse_todo_line(line: str) -> TodoTask:
|
64
|
+
"""Parses a single todo.txt line into a TodoTask model."""
|
65
|
+
match = TODO_TXT_PATTERN.match(line)
|
66
|
+
if not match:
|
67
|
+
raise ValueError(f"Invalid todo.txt line: {line}")
|
68
|
+
groups = match.groupdict()
|
69
|
+
# Extract completion status
|
70
|
+
is_completed = groups["status"] == "x"
|
71
|
+
# Extract dates
|
72
|
+
date1 = parse_date(groups["date1"])
|
73
|
+
date2 = parse_date(groups["date2"])
|
74
|
+
# Determine creation_date and completion_date
|
75
|
+
completion_date, creation_date = None, None
|
76
|
+
if date2 is None:
|
77
|
+
creation_date = date1
|
78
|
+
else:
|
79
|
+
completion_date = date1
|
80
|
+
creation_date = date2
|
81
|
+
# Extract and clean description
|
82
|
+
raw_description = groups["description"] or ""
|
83
|
+
projects = re.findall(r"\+(\S+)", raw_description)
|
84
|
+
contexts = re.findall(r"@(\S+)", raw_description)
|
85
|
+
keyval = {}
|
86
|
+
for keyval_str in re.findall(r"(\S+:\S+)", raw_description):
|
87
|
+
key, val = keyval_str.split(":", 1)
|
88
|
+
keyval[key] = val
|
89
|
+
description = re.sub(r"\s*\+\S+|\s*@\S+|\s*\S+:\S+", "", raw_description).strip()
|
90
|
+
return TodoTask(
|
91
|
+
priority=groups["priority"],
|
92
|
+
completed=is_completed,
|
93
|
+
description=description,
|
94
|
+
projects=projects,
|
95
|
+
contexts=contexts,
|
96
|
+
keyval=keyval,
|
97
|
+
creation_date=creation_date,
|
98
|
+
completion_date=completion_date,
|
99
|
+
)
|
100
|
+
|
101
|
+
|
102
|
+
def parse_date(date_str: str | None) -> datetime.date | None:
|
103
|
+
"""Parses a date string in the format YYYY-MM-DD."""
|
104
|
+
if date_str:
|
105
|
+
return datetime.date.fromisoformat(date_str)
|
106
|
+
return None
|
107
|
+
|
108
|
+
|
109
|
+
def todo_task_to_line(task: TodoTask) -> str:
|
110
|
+
"""Converts a TodoTask instance back into a todo.txt formatted line."""
|
111
|
+
parts = []
|
112
|
+
# Add completion mark if task is completed
|
113
|
+
if task.completed:
|
114
|
+
parts.append("x")
|
115
|
+
# Add priority if present
|
116
|
+
if task.priority:
|
117
|
+
parts.append(f"({task.priority})")
|
118
|
+
# Add completion and creation dates if present
|
119
|
+
if task.completion_date:
|
120
|
+
parts.append(task.completion_date.isoformat())
|
121
|
+
if task.creation_date:
|
122
|
+
parts.append(task.creation_date.isoformat())
|
123
|
+
# Add description
|
124
|
+
parts.append(task.description)
|
125
|
+
# Append projects
|
126
|
+
for project in task.projects:
|
127
|
+
parts.append(f"+{project}")
|
128
|
+
# Append contexts
|
129
|
+
for context in task.contexts:
|
130
|
+
parts.append(f"@{context}")
|
131
|
+
# Append keyval
|
132
|
+
for key, val in task.keyval.items():
|
133
|
+
parts.append(f"{key}:{val}")
|
134
|
+
# Join all parts with a space
|
135
|
+
return " ".join(parts)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: zrb
|
3
|
-
Version: 1.0.
|
4
|
-
Summary:
|
3
|
+
Version: 1.0.0a3
|
4
|
+
Summary: Your Automation Powerhouse
|
5
5
|
Home-page: https://github.com/state-alchemists/zrb
|
6
6
|
License: AGPL-3.0-or-later
|
7
7
|
Keywords: Automation,Task Runner,Code Generator,Monorepo,Low Code
|
@@ -13,10 +13,13 @@ Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
16
|
-
Requires-Dist: autopep8 (>=2.0.4,<
|
17
|
-
Requires-Dist:
|
16
|
+
Requires-Dist: autopep8 (>=2.0.4,<3.0.0)
|
17
|
+
Requires-Dist: black (>=24.10.0,<24.11.0)
|
18
|
+
Requires-Dist: isort (>=5.13.2,<5.14.0)
|
19
|
+
Requires-Dist: libcst (>=1.5.0,<2.0.0)
|
20
|
+
Requires-Dist: litellm (>=1.52.12,<2.0.0)
|
21
|
+
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
18
22
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
19
|
-
Requires-Dist: tomlkit (>=0.12.4,<0.13.0)
|
20
23
|
Project-URL: Documentation, https://github.com/state-alchemists/zrb
|
21
24
|
Project-URL: Repository, https://github.com/state-alchemists/zrb
|
22
25
|
Description-Content-Type: text/markdown
|
@@ -88,7 +91,7 @@ To run again: zrb math add 4
|
|
88
91
|
__Using Web Interface__
|
89
92
|
|
90
93
|
```bash
|
91
|
-
zrb start
|
94
|
+
zrb server start
|
92
95
|
```
|
93
96
|
|
94
97
|
Result (you need to access `http://localhost:21213`)
|
@@ -111,7 +114,8 @@ pip install --pre zrb
|
|
111
114
|
Alternatively, you can also use our installation script to install Zrb along with some prerequisites:
|
112
115
|
|
113
116
|
```bash
|
114
|
-
bash -c "$(curl -fsSL https://raw.githubusercontent.com/state-alchemists/zrb/
|
117
|
+
bash -c "$(curl -fsSL https://raw.githubusercontent.com/state-alchemists/zrb/refs/heads/1.0.0/install.sh)"
|
118
|
+
# bash -c "$(curl -fsSL https://raw.githubusercontent.com/state-alchemists/zrb/main/install.sh)"
|
115
119
|
```
|
116
120
|
|
117
121
|
# 🐞 Bug Report + Feature Request
|