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.
Files changed (156) hide show
  1. zrb/__init__.py +48 -39
  2. zrb/__main__.py +3 -3
  3. zrb/attr/type.py +2 -1
  4. zrb/builtin/__init__.py +40 -2
  5. zrb/builtin/base64.py +32 -0
  6. zrb/builtin/git.py +156 -0
  7. zrb/builtin/git_subtree.py +88 -0
  8. zrb/builtin/group.py +34 -0
  9. zrb/builtin/llm.py +31 -0
  10. zrb/builtin/md5.py +34 -0
  11. zrb/builtin/project/__init__.py +0 -0
  12. zrb/builtin/project/add/__init__.py +0 -0
  13. zrb/builtin/project/add/fastapp.py +72 -0
  14. zrb/builtin/project/add/fastapp_template/.gitignore +4 -0
  15. zrb/builtin/project/add/fastapp_template/README.md +7 -0
  16. zrb/builtin/project/add/fastapp_template/__init__.py +0 -0
  17. zrb/builtin/project/add/fastapp_template/_zrb/config.py +17 -0
  18. zrb/builtin/project/add/fastapp_template/_zrb/group.py +16 -0
  19. zrb/builtin/project/add/fastapp_template/_zrb/helper.py +97 -0
  20. zrb/builtin/project/add/fastapp_template/_zrb/main.py +132 -0
  21. zrb/builtin/project/add/fastapp_template/_zrb/venv_task.py +22 -0
  22. zrb/builtin/project/add/fastapp_template/common/__init__.py +0 -0
  23. zrb/builtin/project/add/fastapp_template/common/app.py +18 -0
  24. zrb/builtin/project/add/fastapp_template/common/db_engine.py +5 -0
  25. zrb/builtin/project/add/fastapp_template/common/db_repository.py +134 -0
  26. zrb/builtin/project/add/fastapp_template/common/error.py +8 -0
  27. zrb/builtin/project/add/fastapp_template/common/schema.py +5 -0
  28. zrb/builtin/project/add/fastapp_template/common/usecase.py +232 -0
  29. zrb/builtin/project/add/fastapp_template/config.py +29 -0
  30. zrb/builtin/project/add/fastapp_template/main.py +7 -0
  31. zrb/builtin/project/add/fastapp_template/migrate.py +3 -0
  32. zrb/builtin/project/add/fastapp_template/module/__init__.py +0 -0
  33. zrb/builtin/project/add/fastapp_template/module/auth/alembic.ini +117 -0
  34. zrb/builtin/project/add/fastapp_template/module/auth/client/api_client.py +7 -0
  35. zrb/builtin/project/add/fastapp_template/module/auth/client/base_client.py +27 -0
  36. zrb/builtin/project/add/fastapp_template/module/auth/client/direct_client.py +6 -0
  37. zrb/builtin/project/add/fastapp_template/module/auth/client/factory.py +9 -0
  38. zrb/builtin/project/add/fastapp_template/module/auth/migration/README +1 -0
  39. zrb/builtin/project/add/fastapp_template/module/auth/migration/env.py +108 -0
  40. zrb/builtin/project/add/fastapp_template/module/auth/migration/script.py.mako +26 -0
  41. zrb/builtin/project/add/fastapp_template/module/auth/migration/versions/3093c7336477_add_user_table.py +37 -0
  42. zrb/builtin/project/add/fastapp_template/module/auth/migration_metadata.py +6 -0
  43. zrb/builtin/project/add/fastapp_template/module/auth/route.py +22 -0
  44. zrb/builtin/project/add/fastapp_template/module/auth/service/__init__.py +0 -0
  45. zrb/builtin/project/add/fastapp_template/module/auth/service/user/__init__.py +0 -0
  46. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/__init__.py +0 -0
  47. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/db_repository.py +39 -0
  48. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/factory.py +13 -0
  49. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/repository.py +34 -0
  50. zrb/builtin/project/add/fastapp_template/module/auth/service/user/usecase.py +45 -0
  51. zrb/builtin/project/add/fastapp_template/module/gateway/alembic.ini +117 -0
  52. zrb/builtin/project/add/fastapp_template/module/gateway/migration/README +1 -0
  53. zrb/builtin/project/add/fastapp_template/module/gateway/migration/env.py +108 -0
  54. zrb/builtin/project/add/fastapp_template/module/gateway/migration/script.py.mako +26 -0
  55. zrb/builtin/project/add/fastapp_template/module/gateway/migration/versions/.gitkeep +0 -0
  56. zrb/builtin/project/add/fastapp_template/module/gateway/migration_metadata.py +3 -0
  57. zrb/builtin/project/add/fastapp_template/module/gateway/route.py +27 -0
  58. zrb/builtin/project/add/fastapp_template/requirements.txt +6 -0
  59. zrb/builtin/project/add/fastapp_template/schema/__init__.py +0 -0
  60. zrb/builtin/project/add/fastapp_template/schema/role.py +31 -0
  61. zrb/builtin/project/add/fastapp_template/schema/user.py +31 -0
  62. zrb/builtin/project/add/fastapp_template/template.env +2 -0
  63. zrb/builtin/project/create/__init__.py +0 -0
  64. zrb/builtin/project/create/create.py +41 -0
  65. zrb/builtin/project/create/project-template/README.md +3 -0
  66. zrb/builtin/project/create/project-template/zrb_init.py +7 -0
  67. zrb/builtin/python.py +11 -0
  68. zrb/builtin/shell/__init__.py +0 -5
  69. zrb/builtin/shell/autocomplete/__init__.py +0 -9
  70. zrb/builtin/shell/autocomplete/bash.py +5 -6
  71. zrb/builtin/shell/autocomplete/subcmd.py +7 -8
  72. zrb/builtin/shell/autocomplete/zsh.py +5 -6
  73. zrb/builtin/todo.py +186 -0
  74. zrb/callback/any_callback.py +1 -1
  75. zrb/callback/callback.py +5 -5
  76. zrb/cmd/cmd_val.py +2 -2
  77. zrb/config.py +4 -1
  78. zrb/content_transformer/any_content_transformer.py +1 -1
  79. zrb/content_transformer/content_transformer.py +2 -2
  80. zrb/context/any_context.py +5 -1
  81. zrb/context/any_shared_context.py +3 -3
  82. zrb/context/context.py +15 -9
  83. zrb/context/shared_context.py +9 -8
  84. zrb/env/__init__.py +0 -3
  85. zrb/env/any_env.py +2 -2
  86. zrb/env/env.py +4 -5
  87. zrb/env/env_file.py +4 -4
  88. zrb/env/env_map.py +4 -4
  89. zrb/group/__init__.py +0 -3
  90. zrb/group/any_group.py +3 -3
  91. zrb/group/group.py +7 -6
  92. zrb/input/any_input.py +1 -1
  93. zrb/input/base_input.py +4 -4
  94. zrb/input/bool_input.py +5 -5
  95. zrb/input/float_input.py +3 -3
  96. zrb/input/int_input.py +3 -3
  97. zrb/input/option_input.py +51 -0
  98. zrb/input/password_input.py +2 -2
  99. zrb/input/str_input.py +1 -1
  100. zrb/input/text_input.py +12 -10
  101. zrb/runner/cli.py +79 -44
  102. zrb/runner/web_app/group_info_ui/controller.py +7 -8
  103. zrb/runner/web_app/group_info_ui/view.html +2 -2
  104. zrb/runner/web_app/home_page/controller.py +7 -6
  105. zrb/runner/web_app/home_page/view.html +2 -2
  106. zrb/runner/web_app/task_ui/controller.py +13 -13
  107. zrb/runner/web_app/task_ui/partial/common-util.js +37 -0
  108. zrb/runner/web_app/task_ui/partial/main.js +9 -2
  109. zrb/runner/web_app/task_ui/partial/show-existing-session.js +20 -5
  110. zrb/runner/web_app/task_ui/partial/visualize-history.js +1 -41
  111. zrb/runner/web_app/task_ui/view.html +4 -2
  112. zrb/runner/web_server.py +137 -211
  113. zrb/runner/web_util.py +5 -35
  114. zrb/session/any_session.py +13 -7
  115. zrb/session/session.py +80 -41
  116. zrb/session_state_log/session_state_log.py +7 -5
  117. zrb/session_state_logger/any_session_state_logger.py +1 -1
  118. zrb/session_state_logger/default_session_state_logger.py +2 -2
  119. zrb/session_state_logger/file_session_state_logger.py +19 -27
  120. zrb/task/any_task.py +8 -3
  121. zrb/task/base_task.py +47 -33
  122. zrb/task/base_trigger.py +11 -12
  123. zrb/task/cmd_task.py +55 -43
  124. zrb/task/http_check.py +8 -8
  125. zrb/task/llm_task.py +160 -0
  126. zrb/task/make_task.py +9 -9
  127. zrb/task/rsync_task.py +7 -7
  128. zrb/task/scaffolder.py +14 -11
  129. zrb/task/scheduler.py +6 -7
  130. zrb/task/task.py +1 -1
  131. zrb/task/tcp_check.py +8 -8
  132. zrb/util/attr.py +19 -3
  133. zrb/util/cli/style.py +71 -2
  134. zrb/util/cli/subcommand.py +2 -2
  135. zrb/util/codemod/__init__.py +0 -0
  136. zrb/util/codemod/add_code_to_class.py +35 -0
  137. zrb/util/codemod/add_code_to_function.py +36 -0
  138. zrb/util/codemod/add_code_to_method.py +55 -0
  139. zrb/util/codemod/add_key_to_dict.py +51 -0
  140. zrb/util/codemod/add_param_to_function_call.py +39 -0
  141. zrb/util/codemod/add_property_to_class.py +55 -0
  142. zrb/util/git.py +156 -0
  143. zrb/util/git_subtree.py +94 -0
  144. zrb/util/group.py +2 -2
  145. zrb/util/llm/tool.py +63 -0
  146. zrb/util/string/conversion.py +7 -0
  147. zrb/util/todo.py +135 -0
  148. {zrb-1.0.0a1.dist-info → zrb-1.0.0a3.dist-info}/METADATA +11 -7
  149. zrb-1.0.0a3.dist-info/RECORD +194 -0
  150. zrb/builtin/shell/_group.py +0 -9
  151. zrb/builtin/shell/autocomplete/_group.py +0 -6
  152. zrb/runner/web_app/any_request_handler.py +0 -24
  153. zrb/runner/web_server.bak.py +0 -208
  154. zrb-1.0.0a1.dist-info/RECORD +0 -120
  155. {zrb-1.0.0a1.dist-info → zrb-1.0.0a3.dist-info}/WHEEL +0 -0
  156. {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)
@@ -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
@@ -1,5 +1,5 @@
1
- from ..group.any_group import AnyGroup
2
- from ..task.any_task import AnyTask
1
+ from zrb.group.any_group import AnyGroup
2
+ from zrb.task.any_task import AnyTask
3
3
 
4
4
 
5
5
  class NodeNotFoundError(ValueError):
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
@@ -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.0a1
4
- Summary: A Framework to Enhance Your Workflow
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,<2.1.0)
17
- Requires-Dist: python-dotenv (>=1.0.1,<1.1.0)
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-server
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/main/install.sh)"
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