zrb 1.8.10__py3-none-any.whl → 1.21.29__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.
Potentially problematic release.
This version of zrb might be problematic. Click here for more details.
- zrb/__init__.py +126 -113
- zrb/__main__.py +1 -1
- zrb/attr/type.py +10 -7
- zrb/builtin/__init__.py +2 -50
- zrb/builtin/git.py +12 -1
- zrb/builtin/group.py +31 -15
- zrb/builtin/http.py +7 -8
- zrb/builtin/llm/attachment.py +40 -0
- zrb/builtin/llm/chat_completion.py +274 -0
- zrb/builtin/llm/chat_session.py +152 -85
- zrb/builtin/llm/chat_session_cmd.py +288 -0
- zrb/builtin/llm/chat_trigger.py +79 -0
- zrb/builtin/llm/history.py +7 -9
- zrb/builtin/llm/llm_ask.py +221 -98
- zrb/builtin/llm/tool/api.py +74 -52
- zrb/builtin/llm/tool/cli.py +46 -17
- zrb/builtin/llm/tool/code.py +71 -90
- zrb/builtin/llm/tool/file.py +301 -241
- zrb/builtin/llm/tool/note.py +84 -0
- zrb/builtin/llm/tool/rag.py +38 -8
- zrb/builtin/llm/tool/sub_agent.py +67 -50
- zrb/builtin/llm/tool/web.py +146 -122
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
- zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
- zrb/builtin/searxng/config/settings.yml +5671 -0
- zrb/builtin/searxng/start.py +21 -0
- zrb/builtin/setup/latex/ubuntu.py +1 -0
- zrb/builtin/setup/ubuntu.py +1 -1
- zrb/builtin/shell/autocomplete/bash.py +4 -3
- zrb/builtin/shell/autocomplete/zsh.py +4 -3
- zrb/builtin/todo.py +13 -2
- zrb/config/config.py +614 -0
- zrb/config/default_prompt/file_extractor_system_prompt.md +112 -0
- zrb/config/default_prompt/interactive_system_prompt.md +29 -0
- zrb/config/default_prompt/persona.md +1 -0
- zrb/config/default_prompt/repo_extractor_system_prompt.md +112 -0
- zrb/config/default_prompt/repo_summarizer_system_prompt.md +29 -0
- zrb/config/default_prompt/summarization_prompt.md +57 -0
- zrb/config/default_prompt/system_prompt.md +38 -0
- zrb/config/llm_config.py +339 -0
- zrb/config/llm_context/config.py +166 -0
- zrb/config/llm_context/config_parser.py +40 -0
- zrb/config/llm_context/workflow.py +81 -0
- zrb/config/llm_rate_limitter.py +190 -0
- zrb/{runner → config}/web_auth_config.py +17 -22
- zrb/context/any_shared_context.py +17 -1
- zrb/context/context.py +16 -2
- zrb/context/shared_context.py +18 -8
- zrb/group/any_group.py +12 -5
- zrb/group/group.py +67 -3
- zrb/input/any_input.py +5 -1
- zrb/input/base_input.py +18 -6
- zrb/input/option_input.py +13 -1
- zrb/input/text_input.py +8 -25
- zrb/runner/cli.py +25 -23
- zrb/runner/common_util.py +24 -19
- zrb/runner/web_app.py +3 -3
- zrb/runner/web_route/docs_route.py +1 -1
- zrb/runner/web_route/error_page/serve_default_404.py +1 -1
- zrb/runner/web_route/error_page/show_error_page.py +1 -1
- zrb/runner/web_route/home_page/home_page_route.py +2 -2
- zrb/runner/web_route/login_api_route.py +1 -1
- zrb/runner/web_route/login_page/login_page_route.py +2 -2
- zrb/runner/web_route/logout_api_route.py +1 -1
- zrb/runner/web_route/logout_page/logout_page_route.py +2 -2
- zrb/runner/web_route/node_page/group/show_group_page.py +1 -1
- zrb/runner/web_route/node_page/node_page_route.py +1 -1
- zrb/runner/web_route/node_page/task/show_task_page.py +1 -1
- zrb/runner/web_route/refresh_token_api_route.py +1 -1
- zrb/runner/web_route/static/static_route.py +1 -1
- zrb/runner/web_route/task_input_api_route.py +6 -6
- zrb/runner/web_route/task_session_api_route.py +20 -12
- zrb/runner/web_util/cookie.py +1 -1
- zrb/runner/web_util/token.py +1 -1
- zrb/runner/web_util/user.py +8 -4
- zrb/session/any_session.py +24 -17
- zrb/session/session.py +50 -25
- zrb/session_state_logger/any_session_state_logger.py +9 -4
- zrb/session_state_logger/file_session_state_logger.py +16 -6
- zrb/session_state_logger/session_state_logger_factory.py +1 -1
- zrb/task/any_task.py +30 -9
- zrb/task/base/context.py +17 -9
- zrb/task/base/execution.py +15 -8
- zrb/task/base/lifecycle.py +8 -4
- zrb/task/base/monitoring.py +12 -7
- zrb/task/base_task.py +69 -5
- zrb/task/base_trigger.py +12 -5
- zrb/task/cmd_task.py +1 -1
- zrb/task/llm/agent.py +154 -161
- zrb/task/llm/agent_runner.py +152 -0
- zrb/task/llm/config.py +47 -18
- zrb/task/llm/conversation_history.py +209 -0
- zrb/task/llm/conversation_history_model.py +67 -0
- zrb/task/llm/default_workflow/coding/workflow.md +41 -0
- zrb/task/llm/default_workflow/copywriting/workflow.md +68 -0
- zrb/task/llm/default_workflow/git/workflow.md +118 -0
- zrb/task/llm/default_workflow/golang/workflow.md +128 -0
- zrb/task/llm/default_workflow/html-css/workflow.md +135 -0
- zrb/task/llm/default_workflow/java/workflow.md +146 -0
- zrb/task/llm/default_workflow/javascript/workflow.md +158 -0
- zrb/task/llm/default_workflow/python/workflow.md +160 -0
- zrb/task/llm/default_workflow/researching/workflow.md +153 -0
- zrb/task/llm/default_workflow/rust/workflow.md +162 -0
- zrb/task/llm/default_workflow/shell/workflow.md +299 -0
- zrb/task/llm/error.py +24 -10
- zrb/task/llm/file_replacement.py +206 -0
- zrb/task/llm/file_tool_model.py +57 -0
- zrb/task/llm/history_processor.py +206 -0
- zrb/task/llm/history_summarization.py +11 -166
- zrb/task/llm/print_node.py +193 -69
- zrb/task/llm/prompt.py +242 -45
- zrb/task/llm/subagent_conversation_history.py +41 -0
- zrb/task/llm/tool_wrapper.py +260 -57
- zrb/task/llm/workflow.py +76 -0
- zrb/task/llm_task.py +182 -171
- zrb/task/make_task.py +2 -3
- zrb/task/rsync_task.py +26 -11
- zrb/task/scheduler.py +4 -4
- zrb/util/attr.py +54 -39
- zrb/util/callable.py +23 -0
- zrb/util/cli/markdown.py +12 -0
- zrb/util/cli/text.py +30 -0
- zrb/util/file.py +29 -11
- zrb/util/git.py +8 -11
- zrb/util/git_diff_model.py +10 -0
- zrb/util/git_subtree.py +9 -14
- zrb/util/git_subtree_model.py +32 -0
- zrb/util/init_path.py +1 -1
- zrb/util/markdown.py +62 -0
- zrb/util/string/conversion.py +2 -2
- zrb/util/todo.py +17 -50
- zrb/util/todo_model.py +46 -0
- zrb/util/truncate.py +23 -0
- zrb/util/yaml.py +204 -0
- zrb/xcom/xcom.py +10 -0
- zrb-1.21.29.dist-info/METADATA +270 -0
- {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/RECORD +140 -98
- {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/WHEEL +1 -1
- zrb/config.py +0 -335
- zrb/llm_config.py +0 -411
- zrb/llm_rate_limitter.py +0 -125
- zrb/task/llm/context.py +0 -102
- zrb/task/llm/context_enrichment.py +0 -199
- zrb/task/llm/history.py +0 -211
- zrb-1.8.10.dist-info/METADATA +0 -264
- {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/entry_points.txt +0 -0
zrb/util/attr.py
CHANGED
|
@@ -9,52 +9,59 @@ from zrb.attr.type import (
|
|
|
9
9
|
StrDictAttr,
|
|
10
10
|
StrListAttr,
|
|
11
11
|
)
|
|
12
|
+
from zrb.context.any_context import AnyContext
|
|
12
13
|
from zrb.context.any_shared_context import AnySharedContext
|
|
13
14
|
from zrb.util.string.conversion import to_boolean
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def get_str_list_attr(
|
|
17
|
-
|
|
18
|
+
ctx: AnyContext | AnySharedContext,
|
|
19
|
+
attr: StrListAttr | None,
|
|
20
|
+
auto_render: bool = True,
|
|
18
21
|
) -> list[str]:
|
|
19
22
|
"""
|
|
20
23
|
Retrieve a list of strings from shared context attributes.
|
|
21
24
|
|
|
22
25
|
Args:
|
|
23
|
-
|
|
26
|
+
ctx (AnyContext): The shared context object.
|
|
24
27
|
attr (StrListAttr | None): The string list attribute to retrieve.
|
|
25
28
|
auto_render (bool): Whether to auto-render the attribute values.
|
|
26
29
|
|
|
27
30
|
Returns:
|
|
28
31
|
list[str]: A list of string attributes.
|
|
29
32
|
"""
|
|
33
|
+
if attr is None:
|
|
34
|
+
return []
|
|
30
35
|
if callable(attr):
|
|
31
|
-
return attr(
|
|
32
|
-
return
|
|
36
|
+
return attr(ctx)
|
|
37
|
+
return [get_str_attr(ctx, val, "", auto_render) for val in attr]
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
def get_str_dict_attr(
|
|
36
|
-
|
|
41
|
+
ctx: AnyContext | AnySharedContext,
|
|
42
|
+
attr: StrDictAttr | None,
|
|
43
|
+
auto_render: bool = True,
|
|
37
44
|
) -> dict[str, Any]:
|
|
38
45
|
"""
|
|
39
46
|
Retrieve a dictionary of strings from shared context attributes.
|
|
40
47
|
|
|
41
48
|
Args:
|
|
42
|
-
|
|
49
|
+
ctx (AnyContext): The shared context object.
|
|
43
50
|
attr (StrDictAttr | None): The string dictionary attribute to retrieve.
|
|
44
51
|
auto_render (bool): Whether to auto-render the attribute values.
|
|
45
52
|
|
|
46
53
|
Returns:
|
|
47
54
|
dict[str, Any]: A dictionary of string attributes.
|
|
48
55
|
"""
|
|
56
|
+
if attr is None:
|
|
57
|
+
return {}
|
|
49
58
|
if callable(attr):
|
|
50
|
-
return attr(
|
|
51
|
-
return {
|
|
52
|
-
key: get_str_attr(shared_ctx, val, "", auto_render) for key, val in attr.items()
|
|
53
|
-
}
|
|
59
|
+
return attr(ctx)
|
|
60
|
+
return {key: get_str_attr(ctx, val, "", auto_render) for key, val in attr.items()}
|
|
54
61
|
|
|
55
62
|
|
|
56
63
|
def get_str_attr(
|
|
57
|
-
|
|
64
|
+
ctx: AnyContext | AnySharedContext,
|
|
58
65
|
attr: StrAttr | None,
|
|
59
66
|
default: StrAttr = "",
|
|
60
67
|
auto_render: bool = True,
|
|
@@ -63,7 +70,7 @@ def get_str_attr(
|
|
|
63
70
|
Retrieve a string from shared context attributes.
|
|
64
71
|
|
|
65
72
|
Args:
|
|
66
|
-
|
|
73
|
+
ctx (AnyContext): The shared context object.
|
|
67
74
|
attr (StrAttr | None): The string attribute to retrieve.
|
|
68
75
|
default (StrAttr): The default value if the attribute is None.
|
|
69
76
|
auto_render (bool): Whether to auto-render the attribute value.
|
|
@@ -71,14 +78,16 @@ def get_str_attr(
|
|
|
71
78
|
Returns:
|
|
72
79
|
str: The string attribute value.
|
|
73
80
|
"""
|
|
74
|
-
val = get_attr(
|
|
75
|
-
if
|
|
76
|
-
return
|
|
77
|
-
|
|
81
|
+
val = get_attr(ctx, attr, default, auto_render)
|
|
82
|
+
if isinstance(val, str):
|
|
83
|
+
return val
|
|
84
|
+
if val is None:
|
|
85
|
+
return ""
|
|
86
|
+
return str(val)
|
|
78
87
|
|
|
79
88
|
|
|
80
89
|
def get_bool_attr(
|
|
81
|
-
|
|
90
|
+
ctx: AnyContext | AnySharedContext,
|
|
82
91
|
attr: BoolAttr | None,
|
|
83
92
|
default: BoolAttr = False,
|
|
84
93
|
auto_render: bool = True,
|
|
@@ -87,7 +96,7 @@ def get_bool_attr(
|
|
|
87
96
|
Retrieve a boolean from shared context attributes.
|
|
88
97
|
|
|
89
98
|
Args:
|
|
90
|
-
|
|
99
|
+
ctx (AnyContext): The shared context object.
|
|
91
100
|
attr (BoolAttr | None): The boolean attribute to retrieve.
|
|
92
101
|
default (BoolAttr): The default value if the attribute is None.
|
|
93
102
|
auto_render (bool): Whether to auto-render the attribute value if it's a string.
|
|
@@ -95,14 +104,16 @@ def get_bool_attr(
|
|
|
95
104
|
Returns:
|
|
96
105
|
bool: The boolean attribute value.
|
|
97
106
|
"""
|
|
98
|
-
val = get_attr(
|
|
99
|
-
if isinstance(val,
|
|
100
|
-
return
|
|
101
|
-
|
|
107
|
+
val = get_attr(ctx, attr, default, auto_render)
|
|
108
|
+
if isinstance(val, bool):
|
|
109
|
+
return val
|
|
110
|
+
if val is None:
|
|
111
|
+
return False
|
|
112
|
+
return to_boolean(val)
|
|
102
113
|
|
|
103
114
|
|
|
104
115
|
def get_int_attr(
|
|
105
|
-
|
|
116
|
+
ctx: AnyContext | AnySharedContext,
|
|
106
117
|
attr: IntAttr | None,
|
|
107
118
|
default: IntAttr = 0,
|
|
108
119
|
auto_render: bool = True,
|
|
@@ -111,7 +122,7 @@ def get_int_attr(
|
|
|
111
122
|
Retrieve an integer from shared context attributes.
|
|
112
123
|
|
|
113
124
|
Args:
|
|
114
|
-
|
|
125
|
+
ctx (AnyContext): The shared context object.
|
|
115
126
|
attr (IntAttr | None): The integer attribute to retrieve.
|
|
116
127
|
default (IntAttr): The default value if the attribute is None.
|
|
117
128
|
auto_render (bool): Whether to auto-render the attribute value if it's a string.
|
|
@@ -119,14 +130,16 @@ def get_int_attr(
|
|
|
119
130
|
Returns:
|
|
120
131
|
int: The integer attribute value.
|
|
121
132
|
"""
|
|
122
|
-
val = get_attr(
|
|
123
|
-
if isinstance(val,
|
|
124
|
-
return
|
|
125
|
-
|
|
133
|
+
val = get_attr(ctx, attr, default, auto_render)
|
|
134
|
+
if isinstance(val, int):
|
|
135
|
+
return val
|
|
136
|
+
if val is None:
|
|
137
|
+
return 0
|
|
138
|
+
return int(val)
|
|
126
139
|
|
|
127
140
|
|
|
128
141
|
def get_float_attr(
|
|
129
|
-
|
|
142
|
+
ctx: AnyContext | AnySharedContext,
|
|
130
143
|
attr: FloatAttr | None,
|
|
131
144
|
default: FloatAttr = 0.0,
|
|
132
145
|
auto_render: bool = True,
|
|
@@ -135,7 +148,7 @@ def get_float_attr(
|
|
|
135
148
|
Retrieve a float from shared context attributes.
|
|
136
149
|
|
|
137
150
|
Args:
|
|
138
|
-
|
|
151
|
+
ctx (AnyContext): The shared context object.
|
|
139
152
|
attr (FloatAttr | None): The float attribute to retrieve.
|
|
140
153
|
default (FloatAttr): The default value if the attribute is None.
|
|
141
154
|
auto_render (bool): Whether to auto-render the attribute value if it's a string.
|
|
@@ -143,14 +156,16 @@ def get_float_attr(
|
|
|
143
156
|
Returns:
|
|
144
157
|
float | None: The float attribute value.
|
|
145
158
|
"""
|
|
146
|
-
val = get_attr(
|
|
147
|
-
if isinstance(val,
|
|
148
|
-
return
|
|
149
|
-
|
|
159
|
+
val = get_attr(ctx, attr, default, auto_render)
|
|
160
|
+
if isinstance(val, (int, float)):
|
|
161
|
+
return val
|
|
162
|
+
if val is None:
|
|
163
|
+
return 0.0
|
|
164
|
+
return float(val)
|
|
150
165
|
|
|
151
166
|
|
|
152
167
|
def get_attr(
|
|
153
|
-
|
|
168
|
+
ctx: AnyContext | AnySharedContext,
|
|
154
169
|
attr: AnyAttr,
|
|
155
170
|
default: AnyAttr,
|
|
156
171
|
auto_render: bool = True,
|
|
@@ -159,7 +174,7 @@ def get_attr(
|
|
|
159
174
|
Retrieve an attribute value from shared context, handling callables and rendering.
|
|
160
175
|
|
|
161
176
|
Args:
|
|
162
|
-
|
|
177
|
+
ctx (AnyContext): The shared context object.
|
|
163
178
|
attr (AnyAttr): The attribute to retrieve. Can be a value, a callable,
|
|
164
179
|
or a string to render.
|
|
165
180
|
default (AnyAttr): The default value if the attribute is None.
|
|
@@ -170,10 +185,10 @@ def get_attr(
|
|
|
170
185
|
"""
|
|
171
186
|
if attr is None:
|
|
172
187
|
if callable(default):
|
|
173
|
-
return default(
|
|
188
|
+
return default(ctx)
|
|
174
189
|
return default
|
|
175
190
|
if callable(attr):
|
|
176
|
-
return attr(
|
|
191
|
+
return attr(ctx)
|
|
177
192
|
if isinstance(attr, str) and auto_render:
|
|
178
|
-
return
|
|
193
|
+
return ctx.render(attr)
|
|
179
194
|
return attr
|
zrb/util/callable.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from types import BuiltinMethodType, MethodType
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_callable_name(obj):
|
|
5
|
+
import functools
|
|
6
|
+
import inspect
|
|
7
|
+
|
|
8
|
+
# 1. Unwrap decorated functions
|
|
9
|
+
obj = inspect.unwrap(obj, stop=lambda f: not hasattr(f, "__wrapped__"))
|
|
10
|
+
# 2. functools.partial – delegate to the wrapped function
|
|
11
|
+
if isinstance(obj, functools.partial):
|
|
12
|
+
return get_callable_name(obj.func)
|
|
13
|
+
# 3. Plain functions, built‑ins, methods
|
|
14
|
+
if hasattr(obj, "__name__"):
|
|
15
|
+
return obj.__name__
|
|
16
|
+
# 4. Bound or unbound methods of a class
|
|
17
|
+
if isinstance(obj, (MethodType, BuiltinMethodType)):
|
|
18
|
+
return obj.__func__.__name__
|
|
19
|
+
# 5. Instances of classes defining __call__
|
|
20
|
+
if callable(obj):
|
|
21
|
+
return type(obj).__name__
|
|
22
|
+
# 6. Fallback
|
|
23
|
+
return repr(obj)
|
zrb/util/cli/markdown.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
def render_markdown(markdown_text: str) -> str:
|
|
2
|
+
"""
|
|
3
|
+
Renders Markdown to a string, ensuring link URLs are visible.
|
|
4
|
+
"""
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.markdown import Markdown
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
markdown = Markdown(markdown_text, hyperlinks=False)
|
|
10
|
+
with console.capture() as capture:
|
|
11
|
+
console.print(markdown)
|
|
12
|
+
return capture.get()
|
zrb/util/cli/text.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import tempfile
|
|
4
|
+
|
|
5
|
+
from zrb.util.file import read_file
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def edit_text(
|
|
9
|
+
prompt_message: str,
|
|
10
|
+
value: str,
|
|
11
|
+
editor: str = "vi",
|
|
12
|
+
extension: str = ".txt",
|
|
13
|
+
) -> str:
|
|
14
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=extension) as temp_file:
|
|
15
|
+
temp_file_name = temp_file.name
|
|
16
|
+
if prompt_message.strip() != "":
|
|
17
|
+
prompt_message_eol = f"{prompt_message}\n"
|
|
18
|
+
temp_file.write(prompt_message_eol.encode())
|
|
19
|
+
# Pre-fill with default content
|
|
20
|
+
if value:
|
|
21
|
+
temp_file.write(value.encode())
|
|
22
|
+
temp_file.flush()
|
|
23
|
+
subprocess.call([editor, temp_file_name])
|
|
24
|
+
# Read the edited content
|
|
25
|
+
edited_content = read_file(temp_file_name)
|
|
26
|
+
if prompt_message.strip() != "":
|
|
27
|
+
parts = [text.strip() for text in edited_content.split(prompt_message, 1)]
|
|
28
|
+
edited_content = "\n".join(parts).lstrip()
|
|
29
|
+
os.remove(temp_file_name)
|
|
30
|
+
return edited_content
|
zrb/util/file.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
+
from typing import Literal
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
def read_file(file_path: str, replace_map: dict[str, str] = {}) -> str:
|
|
@@ -14,14 +15,21 @@ def read_file(file_path: str, replace_map: dict[str, str] = {}) -> str:
|
|
|
14
15
|
"""
|
|
15
16
|
abs_file_path = os.path.abspath(os.path.expanduser(file_path))
|
|
16
17
|
is_pdf = abs_file_path.lower().endswith(".pdf")
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
try:
|
|
19
|
+
content = (
|
|
20
|
+
_read_pdf_file_content(abs_file_path)
|
|
21
|
+
if is_pdf
|
|
22
|
+
else _read_text_file_content(abs_file_path)
|
|
23
|
+
)
|
|
24
|
+
for key, val in replace_map.items():
|
|
25
|
+
content = content.replace(key, val)
|
|
26
|
+
return content
|
|
27
|
+
except Exception:
|
|
28
|
+
import base64
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
data = Path(abs_file_path).read_bytes()
|
|
32
|
+
return base64.b64encode(data).decode("ascii")
|
|
25
33
|
|
|
26
34
|
|
|
27
35
|
def _read_text_file_content(file_path: str) -> str:
|
|
@@ -32,8 +40,10 @@ def _read_text_file_content(file_path: str) -> str:
|
|
|
32
40
|
|
|
33
41
|
def _read_pdf_file_content(file_path: str) -> str:
|
|
34
42
|
import pdfplumber
|
|
43
|
+
from pdfplumber.pdf import PDF
|
|
35
44
|
|
|
36
45
|
with pdfplumber.open(file_path) as pdf:
|
|
46
|
+
pdf: PDF
|
|
37
47
|
return "\n".join(
|
|
38
48
|
page.extract_text() for page in pdf.pages if page.extract_text()
|
|
39
49
|
)
|
|
@@ -52,6 +62,8 @@ def read_file_with_line_numbers(
|
|
|
52
62
|
The content of the file with line numbers and replacements applied.
|
|
53
63
|
"""
|
|
54
64
|
content = read_file(file_path, replace_map)
|
|
65
|
+
if not content:
|
|
66
|
+
return ""
|
|
55
67
|
lines = content.splitlines()
|
|
56
68
|
numbered_lines = [f"{i + 1} | {line}" for i, line in enumerate(lines)]
|
|
57
69
|
return "\n".join(numbered_lines)
|
|
@@ -69,16 +81,22 @@ def read_dir(dir_path: str) -> list[str]:
|
|
|
69
81
|
return [f for f in os.listdir(os.path.abspath(os.path.expanduser(dir_path)))]
|
|
70
82
|
|
|
71
83
|
|
|
72
|
-
def write_file(
|
|
84
|
+
def write_file(
|
|
85
|
+
file_path: str,
|
|
86
|
+
content: str | list[str],
|
|
87
|
+
mode: Literal["w", "wt", "tw", "a", "at", "ta", "x", "xt", "tx"] = "w",
|
|
88
|
+
):
|
|
73
89
|
"""Writes content to a file.
|
|
74
90
|
|
|
75
91
|
Args:
|
|
76
92
|
file_path: The path to the file.
|
|
77
93
|
content: The content to write, either a string or a list of strings.
|
|
94
|
+
mode: Writing mode (by default "w")
|
|
78
95
|
"""
|
|
79
96
|
if isinstance(content, list):
|
|
80
97
|
content = "\n".join([line for line in content if line is not None])
|
|
81
|
-
|
|
98
|
+
abs_file_path = os.path.abspath(os.path.expanduser(file_path))
|
|
99
|
+
dir_path = os.path.dirname(abs_file_path)
|
|
82
100
|
os.makedirs(dir_path, exist_ok=True)
|
|
83
101
|
should_add_eol = content.endswith("\n")
|
|
84
102
|
# Remove trailing newlines, but keep one if the file originally ended up with newline
|
|
@@ -86,5 +104,5 @@ def write_file(file_path: str, content: str | list[str]):
|
|
|
86
104
|
content = content.rstrip("\n")
|
|
87
105
|
if should_add_eol:
|
|
88
106
|
content += "\n"
|
|
89
|
-
with open(
|
|
107
|
+
with open(abs_file_path, mode) as f:
|
|
90
108
|
f.write(content)
|
zrb/util/git.py
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from collections.abc import Callable
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from pydantic import BaseModel
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
6
4
|
|
|
7
5
|
from zrb.util.cmd.command import run_command
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
created: list[str]
|
|
12
|
-
removed: list[str]
|
|
13
|
-
updated: list[str]
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from zrb.util.git_diff_model import DiffResult
|
|
14
9
|
|
|
15
10
|
|
|
16
11
|
async def get_diff(
|
|
@@ -18,7 +13,7 @@ async def get_diff(
|
|
|
18
13
|
source_commit: str,
|
|
19
14
|
current_commit: str,
|
|
20
15
|
print_method: Callable[..., Any] = print,
|
|
21
|
-
) -> DiffResult:
|
|
16
|
+
) -> "DiffResult":
|
|
22
17
|
"""
|
|
23
18
|
Get the difference between two commits in a Git repository.
|
|
24
19
|
|
|
@@ -34,6 +29,8 @@ async def get_diff(
|
|
|
34
29
|
Raises:
|
|
35
30
|
Exception: If the git command returns a non-zero exit code.
|
|
36
31
|
"""
|
|
32
|
+
from zrb.util.git_diff_model import DiffResult
|
|
33
|
+
|
|
37
34
|
cmd_result, exit_code = await run_command(
|
|
38
35
|
cmd=["git", "diff", source_commit, current_commit],
|
|
39
36
|
cwd=repo_dir,
|
|
@@ -134,7 +131,7 @@ async def get_branches(
|
|
|
134
131
|
Exception: If the git command returns a non-zero exit code.
|
|
135
132
|
"""
|
|
136
133
|
cmd_result, exit_code = await run_command(
|
|
137
|
-
cmd=["git", "
|
|
134
|
+
cmd=["git", "branch"],
|
|
138
135
|
cwd=repo_dir,
|
|
139
136
|
print_method=print_method,
|
|
140
137
|
)
|
|
@@ -163,7 +160,7 @@ async def delete_branch(
|
|
|
163
160
|
Exception: If the git command returns a non-zero exit code.
|
|
164
161
|
"""
|
|
165
162
|
cmd_result, exit_code = await run_command(
|
|
166
|
-
cmd=["git", "branch", "-
|
|
163
|
+
cmd=["git", "branch", "-d", branch_name],
|
|
167
164
|
cwd=repo_dir,
|
|
168
165
|
print_method=print_method,
|
|
169
166
|
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class DiffResult:
|
|
2
|
+
def __init__(
|
|
3
|
+
self,
|
|
4
|
+
created: list[str] | None = None,
|
|
5
|
+
removed: list[str] | None = None,
|
|
6
|
+
updated: list[str] | None = None,
|
|
7
|
+
):
|
|
8
|
+
self.created = created if created is not None else []
|
|
9
|
+
self.removed = removed if removed is not None else []
|
|
10
|
+
self.updated = updated if updated is not None else []
|
zrb/util/git_subtree.py
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from collections.abc import Callable
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from pydantic import BaseModel
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
6
4
|
|
|
7
5
|
from zrb.util.cmd.command import run_command
|
|
8
6
|
from zrb.util.file import read_file, write_file
|
|
9
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from zrb.util.git_subtree_model import SubTreeConfig
|
|
10
10
|
|
|
11
|
-
class SingleSubTreeConfig(BaseModel):
|
|
12
|
-
repo_url: str
|
|
13
|
-
branch: str
|
|
14
|
-
prefix: str
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class SubTreeConfig(BaseModel):
|
|
18
|
-
data: dict[str, SingleSubTreeConfig]
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
def load_config(repo_dir: str) -> SubTreeConfig:
|
|
12
|
+
def load_config(repo_dir: str):
|
|
22
13
|
"""
|
|
23
14
|
Load the subtree configuration from subtrees.json.
|
|
24
15
|
|
|
@@ -28,13 +19,15 @@ def load_config(repo_dir: str) -> SubTreeConfig:
|
|
|
28
19
|
Returns:
|
|
29
20
|
SubTreeConfig: The loaded subtree configuration.
|
|
30
21
|
"""
|
|
22
|
+
from zrb.util.git_subtree_model import SubTreeConfig
|
|
23
|
+
|
|
31
24
|
file_path = os.path.join(repo_dir, "subtrees.json")
|
|
32
25
|
if not os.path.exists(file_path):
|
|
33
26
|
return SubTreeConfig(data={})
|
|
34
27
|
return SubTreeConfig.model_validate_json(read_file(file_path))
|
|
35
28
|
|
|
36
29
|
|
|
37
|
-
def save_config(repo_dir: str, config: SubTreeConfig):
|
|
30
|
+
def save_config(repo_dir: str, config: "SubTreeConfig"):
|
|
38
31
|
"""
|
|
39
32
|
Save the subtree configuration to subtrees.json.
|
|
40
33
|
|
|
@@ -70,6 +63,8 @@ async def add_subtree(
|
|
|
70
63
|
name already exists.
|
|
71
64
|
Exception: If the git command returns a non-zero exit code.
|
|
72
65
|
"""
|
|
66
|
+
from zrb.util.git_subtree_model import SingleSubTreeConfig
|
|
67
|
+
|
|
73
68
|
config = load_config(repo_dir)
|
|
74
69
|
if os.path.isdir(prefix):
|
|
75
70
|
raise ValueError(f"Directory exists: {prefix}")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SingleSubTreeConfig:
|
|
6
|
+
def __init__(self, repo_url: str, branch: str, prefix: str):
|
|
7
|
+
self.repo_url = repo_url
|
|
8
|
+
self.branch = branch
|
|
9
|
+
self.prefix = prefix
|
|
10
|
+
|
|
11
|
+
def to_dict(self) -> dict[str, Any]:
|
|
12
|
+
return {
|
|
13
|
+
"repo_url": self.repo_url,
|
|
14
|
+
"branch": self.branch,
|
|
15
|
+
"prefix": self.prefix,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def model_dump_json(self, indent: int = 2) -> str:
|
|
19
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SubTreeConfig:
|
|
23
|
+
def __init__(self, data: dict[str, SingleSubTreeConfig]):
|
|
24
|
+
self.data = data
|
|
25
|
+
|
|
26
|
+
def to_dict(self) -> dict[str, Any]:
|
|
27
|
+
return {
|
|
28
|
+
"data": {k: self.data[k].to_dict() for k in self.data},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def model_dump_json(self, indent: int = 2) -> str:
|
|
32
|
+
return json.dumps(self.to_dict(), indent=indent)
|
zrb/util/init_path.py
CHANGED
zrb/util/markdown.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _adjust_markdown_headers(md: str, level_change: int) -> str:
|
|
5
|
+
lines = md.split("\n")
|
|
6
|
+
new_lines = []
|
|
7
|
+
fence_stack = []
|
|
8
|
+
for line in lines:
|
|
9
|
+
stripped_line = line.strip()
|
|
10
|
+
fence_match = re.match(r"^([`~]{3,})", stripped_line)
|
|
11
|
+
if fence_match:
|
|
12
|
+
current_fence = fence_match.group(1)
|
|
13
|
+
if (
|
|
14
|
+
fence_stack
|
|
15
|
+
and fence_stack[-1][0] == current_fence[0]
|
|
16
|
+
and len(current_fence) >= len(fence_stack[-1])
|
|
17
|
+
):
|
|
18
|
+
fence_stack.pop()
|
|
19
|
+
else:
|
|
20
|
+
fence_stack.append(current_fence)
|
|
21
|
+
new_lines.append(line)
|
|
22
|
+
elif fence_stack:
|
|
23
|
+
new_lines.append(line)
|
|
24
|
+
else:
|
|
25
|
+
match = re.match(r"^(#{1,6})(\s)", line)
|
|
26
|
+
if match:
|
|
27
|
+
current_level = len(match.group(1))
|
|
28
|
+
new_level = max(1, current_level + level_change)
|
|
29
|
+
new_header = "#" * new_level + line[current_level:]
|
|
30
|
+
new_lines.append(new_header)
|
|
31
|
+
else:
|
|
32
|
+
new_lines.append(line)
|
|
33
|
+
return "\n".join(new_lines).rstrip()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def demote_markdown_headers(md: str) -> str:
|
|
37
|
+
return _adjust_markdown_headers(md, level_change=1)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def promote_markdown_headers(md: str) -> str:
|
|
41
|
+
return _adjust_markdown_headers(md, level_change=-1)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def make_markdown_section(header: str, content: str, as_code: bool = False) -> str:
|
|
45
|
+
if content.strip() == "":
|
|
46
|
+
return ""
|
|
47
|
+
if as_code:
|
|
48
|
+
# Find the longest sequence of backticks in the content
|
|
49
|
+
longest_backtick_sequence = 0
|
|
50
|
+
# Use finditer to find all occurrences of backticks
|
|
51
|
+
for match in re.finditer(r"`+", content):
|
|
52
|
+
longest_backtick_sequence = max(
|
|
53
|
+
longest_backtick_sequence, len(match.group(0))
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# The fence should be one longer than the longest sequence found
|
|
57
|
+
fence_len = 4
|
|
58
|
+
if longest_backtick_sequence >= fence_len:
|
|
59
|
+
fence_len = longest_backtick_sequence + 1
|
|
60
|
+
fence = "`" * fence_len
|
|
61
|
+
return f"# {header}\n{fence}\n{content.strip()}\n{fence}\n"
|
|
62
|
+
return f"# {header}\n{demote_markdown_headers(content.strip())}\n"
|
zrb/util/string/conversion.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
2
|
|
|
3
3
|
NON_ALPHA_NUM = re.compile(r"[^a-zA-Z0-9]+")
|
|
4
|
-
TRUE_STRS = ["true", "1", "yes", "y", "active", "on"]
|
|
4
|
+
TRUE_STRS = ["true", "1", "yes", "y", "active", "on", "okay", "ok"]
|
|
5
5
|
FALSE_STRS = ["false", "0", "no", "n", "inactive", "off"]
|
|
6
6
|
|
|
7
7
|
|
|
@@ -178,7 +178,7 @@ def pluralize(noun: str) -> str:
|
|
|
178
178
|
if noun.lower() in irregulars:
|
|
179
179
|
return irregulars[noun.lower()]
|
|
180
180
|
# Handle words ending in 'y' preceded by a consonant
|
|
181
|
-
if noun.endswith("y") and not re.match(r"[aeiou]y$", noun):
|
|
181
|
+
if noun.endswith("y") and not re.match(r".*[aeiou]y$", noun, re.IGNORECASE):
|
|
182
182
|
return re.sub(r"y$", "ies", noun)
|
|
183
183
|
# Handle words ending in 's', 'x', 'z', 'ch', or 'sh'
|
|
184
184
|
if re.search(r"(s|x|z|ch|sh)$", noun):
|