zrb 1.15.3__py3-none-any.whl → 2.0.0a4__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.

Files changed (204) hide show
  1. zrb/__init__.py +118 -133
  2. zrb/attr/type.py +10 -7
  3. zrb/builtin/__init__.py +55 -1
  4. zrb/builtin/git.py +12 -1
  5. zrb/builtin/group.py +31 -15
  6. zrb/builtin/llm/chat.py +147 -0
  7. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
  8. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
  9. zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
  10. zrb/builtin/searxng/config/settings.yml +5671 -0
  11. zrb/builtin/searxng/start.py +21 -0
  12. zrb/builtin/shell/autocomplete/bash.py +4 -3
  13. zrb/builtin/shell/autocomplete/zsh.py +4 -3
  14. zrb/callback/callback.py +8 -1
  15. zrb/cmd/cmd_result.py +2 -1
  16. zrb/config/config.py +555 -169
  17. zrb/config/helper.py +84 -0
  18. zrb/config/web_auth_config.py +50 -35
  19. zrb/context/any_shared_context.py +20 -3
  20. zrb/context/context.py +39 -5
  21. zrb/context/print_fn.py +13 -0
  22. zrb/context/shared_context.py +17 -8
  23. zrb/group/any_group.py +3 -3
  24. zrb/group/group.py +3 -3
  25. zrb/input/any_input.py +5 -1
  26. zrb/input/base_input.py +18 -6
  27. zrb/input/option_input.py +41 -1
  28. zrb/input/text_input.py +7 -24
  29. zrb/llm/agent/__init__.py +9 -0
  30. zrb/llm/agent/agent.py +215 -0
  31. zrb/llm/agent/summarizer.py +20 -0
  32. zrb/llm/app/__init__.py +10 -0
  33. zrb/llm/app/completion.py +281 -0
  34. zrb/llm/app/confirmation/allow_tool.py +66 -0
  35. zrb/llm/app/confirmation/handler.py +178 -0
  36. zrb/llm/app/confirmation/replace_confirmation.py +77 -0
  37. zrb/llm/app/keybinding.py +34 -0
  38. zrb/llm/app/layout.py +117 -0
  39. zrb/llm/app/lexer.py +155 -0
  40. zrb/llm/app/redirection.py +28 -0
  41. zrb/llm/app/style.py +16 -0
  42. zrb/llm/app/ui.py +733 -0
  43. zrb/llm/config/__init__.py +4 -0
  44. zrb/llm/config/config.py +122 -0
  45. zrb/llm/config/limiter.py +247 -0
  46. zrb/llm/history_manager/__init__.py +4 -0
  47. zrb/llm/history_manager/any_history_manager.py +23 -0
  48. zrb/llm/history_manager/file_history_manager.py +91 -0
  49. zrb/llm/history_processor/summarizer.py +108 -0
  50. zrb/llm/note/__init__.py +3 -0
  51. zrb/llm/note/manager.py +122 -0
  52. zrb/llm/prompt/__init__.py +29 -0
  53. zrb/llm/prompt/claude_compatibility.py +92 -0
  54. zrb/llm/prompt/compose.py +55 -0
  55. zrb/llm/prompt/default.py +51 -0
  56. zrb/llm/prompt/markdown/file_extractor.md +112 -0
  57. zrb/llm/prompt/markdown/mandate.md +23 -0
  58. zrb/llm/prompt/markdown/persona.md +3 -0
  59. zrb/llm/prompt/markdown/repo_extractor.md +112 -0
  60. zrb/llm/prompt/markdown/repo_summarizer.md +29 -0
  61. zrb/llm/prompt/markdown/summarizer.md +21 -0
  62. zrb/llm/prompt/note.py +41 -0
  63. zrb/llm/prompt/system_context.py +46 -0
  64. zrb/llm/prompt/zrb.py +41 -0
  65. zrb/llm/skill/__init__.py +3 -0
  66. zrb/llm/skill/manager.py +86 -0
  67. zrb/llm/task/__init__.py +4 -0
  68. zrb/llm/task/llm_chat_task.py +316 -0
  69. zrb/llm/task/llm_task.py +245 -0
  70. zrb/llm/tool/__init__.py +39 -0
  71. zrb/llm/tool/bash.py +75 -0
  72. zrb/llm/tool/code.py +266 -0
  73. zrb/llm/tool/file.py +419 -0
  74. zrb/llm/tool/note.py +70 -0
  75. zrb/{builtin/llm → llm}/tool/rag.py +33 -37
  76. zrb/llm/tool/search/brave.py +53 -0
  77. zrb/llm/tool/search/searxng.py +47 -0
  78. zrb/llm/tool/search/serpapi.py +47 -0
  79. zrb/llm/tool/skill.py +19 -0
  80. zrb/llm/tool/sub_agent.py +70 -0
  81. zrb/llm/tool/web.py +97 -0
  82. zrb/llm/tool/zrb_task.py +66 -0
  83. zrb/llm/util/attachment.py +101 -0
  84. zrb/llm/util/prompt.py +104 -0
  85. zrb/llm/util/stream_response.py +178 -0
  86. zrb/runner/cli.py +21 -20
  87. zrb/runner/common_util.py +24 -19
  88. zrb/runner/web_route/task_input_api_route.py +5 -5
  89. zrb/runner/web_util/user.py +7 -3
  90. zrb/session/any_session.py +12 -9
  91. zrb/session/session.py +38 -17
  92. zrb/task/any_task.py +24 -3
  93. zrb/task/base/context.py +42 -22
  94. zrb/task/base/execution.py +67 -55
  95. zrb/task/base/lifecycle.py +14 -7
  96. zrb/task/base/monitoring.py +12 -7
  97. zrb/task/base_task.py +113 -50
  98. zrb/task/base_trigger.py +16 -6
  99. zrb/task/cmd_task.py +6 -0
  100. zrb/task/http_check.py +11 -5
  101. zrb/task/make_task.py +5 -3
  102. zrb/task/rsync_task.py +30 -10
  103. zrb/task/scaffolder.py +7 -4
  104. zrb/task/scheduler.py +7 -4
  105. zrb/task/tcp_check.py +6 -4
  106. zrb/util/ascii_art/art/bee.txt +17 -0
  107. zrb/util/ascii_art/art/cat.txt +9 -0
  108. zrb/util/ascii_art/art/ghost.txt +16 -0
  109. zrb/util/ascii_art/art/panda.txt +17 -0
  110. zrb/util/ascii_art/art/rose.txt +14 -0
  111. zrb/util/ascii_art/art/unicorn.txt +15 -0
  112. zrb/util/ascii_art/banner.py +92 -0
  113. zrb/util/attr.py +54 -39
  114. zrb/util/cli/markdown.py +32 -0
  115. zrb/util/cli/text.py +30 -0
  116. zrb/util/cmd/command.py +33 -10
  117. zrb/util/file.py +61 -33
  118. zrb/util/git.py +2 -2
  119. zrb/util/{llm/prompt.py → markdown.py} +2 -3
  120. zrb/util/match.py +78 -0
  121. zrb/util/run.py +3 -3
  122. zrb/util/string/conversion.py +1 -1
  123. zrb/util/truncate.py +23 -0
  124. zrb/util/yaml.py +204 -0
  125. zrb/xcom/xcom.py +10 -0
  126. {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/METADATA +41 -27
  127. {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/RECORD +129 -131
  128. {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +1 -1
  129. zrb/attr/__init__.py +0 -0
  130. zrb/builtin/llm/chat_session.py +0 -311
  131. zrb/builtin/llm/history.py +0 -71
  132. zrb/builtin/llm/input.py +0 -27
  133. zrb/builtin/llm/llm_ask.py +0 -187
  134. zrb/builtin/llm/previous-session.js +0 -21
  135. zrb/builtin/llm/tool/__init__.py +0 -0
  136. zrb/builtin/llm/tool/api.py +0 -71
  137. zrb/builtin/llm/tool/cli.py +0 -38
  138. zrb/builtin/llm/tool/code.py +0 -254
  139. zrb/builtin/llm/tool/file.py +0 -626
  140. zrb/builtin/llm/tool/sub_agent.py +0 -137
  141. zrb/builtin/llm/tool/web.py +0 -195
  142. zrb/builtin/project/__init__.py +0 -0
  143. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/__init__.py +0 -0
  144. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/service/__init__.py +0 -0
  145. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/__init__.py +0 -0
  146. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/__init__.py +0 -0
  147. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/__init__.py +0 -0
  148. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/__init__.py +0 -0
  149. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/__init__.py +0 -0
  150. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/__init__.py +0 -0
  151. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/__init__.py +0 -0
  152. zrb/builtin/project/create/__init__.py +0 -0
  153. zrb/builtin/shell/__init__.py +0 -0
  154. zrb/builtin/shell/autocomplete/__init__.py +0 -0
  155. zrb/callback/__init__.py +0 -0
  156. zrb/cmd/__init__.py +0 -0
  157. zrb/config/default_prompt/file_extractor_system_prompt.md +0 -12
  158. zrb/config/default_prompt/interactive_system_prompt.md +0 -35
  159. zrb/config/default_prompt/persona.md +0 -1
  160. zrb/config/default_prompt/repo_extractor_system_prompt.md +0 -112
  161. zrb/config/default_prompt/repo_summarizer_system_prompt.md +0 -10
  162. zrb/config/default_prompt/summarization_prompt.md +0 -16
  163. zrb/config/default_prompt/system_prompt.md +0 -32
  164. zrb/config/llm_config.py +0 -243
  165. zrb/config/llm_context/config.py +0 -129
  166. zrb/config/llm_context/config_parser.py +0 -46
  167. zrb/config/llm_rate_limitter.py +0 -137
  168. zrb/content_transformer/__init__.py +0 -0
  169. zrb/context/__init__.py +0 -0
  170. zrb/dot_dict/__init__.py +0 -0
  171. zrb/env/__init__.py +0 -0
  172. zrb/group/__init__.py +0 -0
  173. zrb/input/__init__.py +0 -0
  174. zrb/runner/__init__.py +0 -0
  175. zrb/runner/web_route/__init__.py +0 -0
  176. zrb/runner/web_route/home_page/__init__.py +0 -0
  177. zrb/session/__init__.py +0 -0
  178. zrb/session_state_log/__init__.py +0 -0
  179. zrb/session_state_logger/__init__.py +0 -0
  180. zrb/task/__init__.py +0 -0
  181. zrb/task/base/__init__.py +0 -0
  182. zrb/task/llm/__init__.py +0 -0
  183. zrb/task/llm/agent.py +0 -243
  184. zrb/task/llm/config.py +0 -103
  185. zrb/task/llm/conversation_history.py +0 -128
  186. zrb/task/llm/conversation_history_model.py +0 -242
  187. zrb/task/llm/default_workflow/coding.md +0 -24
  188. zrb/task/llm/default_workflow/copywriting.md +0 -17
  189. zrb/task/llm/default_workflow/researching.md +0 -18
  190. zrb/task/llm/error.py +0 -95
  191. zrb/task/llm/history_summarization.py +0 -216
  192. zrb/task/llm/print_node.py +0 -101
  193. zrb/task/llm/prompt.py +0 -325
  194. zrb/task/llm/tool_wrapper.py +0 -220
  195. zrb/task/llm/typing.py +0 -3
  196. zrb/task/llm_task.py +0 -341
  197. zrb/task_status/__init__.py +0 -0
  198. zrb/util/__init__.py +0 -0
  199. zrb/util/cli/__init__.py +0 -0
  200. zrb/util/cmd/__init__.py +0 -0
  201. zrb/util/codemod/__init__.py +0 -0
  202. zrb/util/string/__init__.py +0 -0
  203. zrb/xcom/__init__.py +0 -0
  204. {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/entry_points.txt +0 -0
zrb/task/base_trigger.py CHANGED
@@ -5,7 +5,7 @@ from typing import Any
5
5
  from zrb.attr.type import fstring
6
6
  from zrb.callback.any_callback import AnyCallback
7
7
  from zrb.context.any_context import AnyContext
8
- from zrb.context.any_shared_context import AnySharedContext
8
+ from zrb.context.print_fn import PrintFn
9
9
  from zrb.context.shared_context import SharedContext
10
10
  from zrb.dot_dict.dot_dict import DotDict
11
11
  from zrb.env.any_env import AnyEnv
@@ -36,7 +36,7 @@ class BaseTrigger(BaseTask):
36
36
  input: list[AnyInput | None] | AnyInput | None = None,
37
37
  env: list[AnyEnv | None] | AnyEnv | None = None,
38
38
  action: fstring | Callable[[AnyContext], Any] | None = None,
39
- execute_condition: bool | str | Callable[[AnySharedContext], bool] = True,
39
+ execute_condition: bool | str | Callable[[AnyContext], bool] = True,
40
40
  queue_name: fstring | None = None,
41
41
  callback: list[AnyCallback] | AnyCallback = [],
42
42
  retries: int = 2,
@@ -50,6 +50,7 @@ class BaseTrigger(BaseTask):
50
50
  upstream: list[AnyTask] | AnyTask | None = None,
51
51
  fallback: list[AnyTask] | AnyTask | None = None,
52
52
  successor: list[AnyTask] | AnyTask | None = None,
53
+ print_fn: PrintFn | None = None,
53
54
  ):
54
55
  """
55
56
  Initializes a new instance of the BaseTrigger class.
@@ -103,6 +104,7 @@ class BaseTrigger(BaseTask):
103
104
  upstream=upstream,
104
105
  fallback=fallback,
105
106
  successor=successor,
107
+ print_fn=print_fn,
106
108
  )
107
109
  self._callbacks = callback
108
110
  self._queue_name = queue_name
@@ -127,7 +129,7 @@ class BaseTrigger(BaseTask):
127
129
  return self._callbacks
128
130
 
129
131
  async def exec_root_tasks(self, session: AnySession):
130
- exchange_xcom = self.get_exchange_xcom(session)
132
+ exchange_xcom = self._get_exchange_xcom(session)
131
133
  exchange_xcom.add_push_callback(lambda: self._exchange_push_callback(session))
132
134
  return await super().exec_root_tasks(session)
133
135
 
@@ -136,8 +138,7 @@ class BaseTrigger(BaseTask):
136
138
  session.defer_coro(coro)
137
139
 
138
140
  async def _fanout_and_trigger_callback(self, session: AnySession):
139
- exchange_xcom = self.get_exchange_xcom(session)
140
- data = exchange_xcom.pop()
141
+ data = self.pop_exchange_xcom(session)
141
142
  coros = []
142
143
  for callback in self.callbacks:
143
144
  xcom_dict = DotDict({self.queue_name: Xcom([data])})
@@ -145,6 +146,7 @@ class BaseTrigger(BaseTask):
145
146
  shared_ctx=SharedContext(
146
147
  input=dict(session.shared_ctx.input),
147
148
  xcom=xcom_dict,
149
+ print_fn=self._print_fn,
148
150
  ),
149
151
  parent=session,
150
152
  root_group=session.root_group,
@@ -156,8 +158,16 @@ class BaseTrigger(BaseTask):
156
158
  )
157
159
  await asyncio.gather(*coros)
158
160
 
159
- def get_exchange_xcom(self, session: AnySession) -> Xcom:
161
+ def _get_exchange_xcom(self, session: AnySession) -> Xcom:
160
162
  shared_ctx = session.shared_ctx
161
163
  if self.queue_name not in shared_ctx.xcom:
162
164
  shared_ctx.xcom[self.queue_name] = Xcom()
163
165
  return shared_ctx.xcom[self.queue_name]
166
+
167
+ def push_exchange_xcom(self, session: AnySession, data: Any):
168
+ exchange_xcom = self._get_exchange_xcom(session)
169
+ exchange_xcom.push(data)
170
+
171
+ def pop_exchange_xcom(self, session: AnySession) -> Any:
172
+ exchange_xcom = self._get_exchange_xcom(session)
173
+ return exchange_xcom.pop()
zrb/task/cmd_task.py CHANGED
@@ -6,6 +6,7 @@ from zrb.cmd.cmd_result import CmdResult
6
6
  from zrb.cmd.cmd_val import AnyCmdVal, CmdVal, SingleCmdVal
7
7
  from zrb.config.config import CFG
8
8
  from zrb.context.any_context import AnyContext
9
+ from zrb.context.print_fn import PrintFn
9
10
  from zrb.env.any_env import AnyEnv
10
11
  from zrb.input.any_input import AnyInput
11
12
  from zrb.task.any_task import AnyTask
@@ -48,6 +49,7 @@ class CmdTask(BaseTask):
48
49
  warn_unrecommended_command: bool | None = None,
49
50
  max_output_line: int = 1000,
50
51
  max_error_line: int = 1000,
52
+ execution_timeout: int = 3600,
51
53
  is_interactive: bool = False,
52
54
  execute_condition: BoolAttr = True,
53
55
  retries: int = 2,
@@ -61,6 +63,7 @@ class CmdTask(BaseTask):
61
63
  upstream: list[AnyTask] | AnyTask | None = None,
62
64
  fallback: list[AnyTask] | AnyTask | None = None,
63
65
  successor: list[AnyTask] | AnyTask | None = None,
66
+ print_fn: PrintFn | None = None,
64
67
  ):
65
68
  super().__init__(
66
69
  name=name,
@@ -82,6 +85,7 @@ class CmdTask(BaseTask):
82
85
  upstream=upstream,
83
86
  fallback=fallback,
84
87
  successor=successor,
88
+ print_fn=print_fn,
85
89
  )
86
90
  self._shell = shell
87
91
  self._render_shell = render_shell
@@ -103,6 +107,7 @@ class CmdTask(BaseTask):
103
107
  self._render_cwd = render_cwd
104
108
  self._max_output_line = max_output_line
105
109
  self._max_error_line = max_error_line
110
+ self._execution_timeout = execution_timeout
106
111
  self._should_plain_print = plain_print
107
112
  self._should_warn_unrecommended_command = warn_unrecommended_command
108
113
  self._is_interactive = is_interactive
@@ -142,6 +147,7 @@ class CmdTask(BaseTask):
142
147
  register_pid_method=lambda pid: ctx.xcom.get(xcom_pid_key).push(pid),
143
148
  max_output_line=self._max_output_line,
144
149
  max_error_line=self._max_error_line,
150
+ timeout=self._execution_timeout,
145
151
  is_interactive=self._is_interactive,
146
152
  )
147
153
  # Check for errors
zrb/task/http_check.py CHANGED
@@ -1,15 +1,19 @@
1
1
  import asyncio
2
2
  from collections.abc import Callable
3
+ from typing import TYPE_CHECKING
3
4
 
4
5
  from zrb.attr.type import StrAttr
5
6
  from zrb.context.any_context import AnyContext
6
- from zrb.context.context import Context
7
+ from zrb.context.print_fn import PrintFn
7
8
  from zrb.env.any_env import AnyEnv
8
9
  from zrb.input.any_input import AnyInput
9
10
  from zrb.task.any_task import AnyTask
10
11
  from zrb.task.base_task import BaseTask
11
12
  from zrb.util.attr import get_str_attr
12
13
 
14
+ if TYPE_CHECKING:
15
+ from requests import Response
16
+
13
17
 
14
18
  class HttpCheck(BaseTask):
15
19
  def __init__(
@@ -19,16 +23,17 @@ class HttpCheck(BaseTask):
19
23
  icon: str | None = None,
20
24
  description: str | None = None,
21
25
  cli_only: bool = False,
22
- input: list[AnyInput] | AnyInput | None = None,
23
- env: list[AnyEnv] | AnyEnv | None = None,
26
+ input: list[AnyInput | None] | AnyInput | None = None,
27
+ env: list[AnyEnv | None] | AnyEnv | None = None,
24
28
  url: StrAttr = "http://localhost",
25
29
  render_url: bool = True,
26
30
  http_method: StrAttr = "GET",
27
31
  interval: int = 5,
28
- execute_condition: bool | str | Callable[[Context], bool] = True,
32
+ execute_condition: bool | str | Callable[[AnyContext], bool] = True,
29
33
  upstream: list[AnyTask] | AnyTask | None = None,
30
34
  fallback: list[AnyTask] | AnyTask | None = None,
31
35
  successor: list[AnyTask] | AnyTask | None = None,
36
+ print_fn: PrintFn | None = None,
32
37
  ):
33
38
  super().__init__(
34
39
  name=name,
@@ -43,6 +48,7 @@ class HttpCheck(BaseTask):
43
48
  upstream=upstream,
44
49
  fallback=fallback,
45
50
  successor=successor,
51
+ print_fn=print_fn,
46
52
  )
47
53
  self._url = url
48
54
  self._render_url = render_url
@@ -57,7 +63,7 @@ class HttpCheck(BaseTask):
57
63
  def _get_http_method(self, ctx: AnyContext) -> str:
58
64
  return get_str_attr(ctx, self._http_method, "GET", auto_render=True).upper()
59
65
 
60
- async def _exec_action(self, ctx: AnyContext) -> bool:
66
+ async def _exec_action(self, ctx: AnyContext) -> "bool | Response":
61
67
  import requests
62
68
 
63
69
  url = self._get_url(ctx)
zrb/task/make_task.py CHANGED
@@ -2,7 +2,7 @@ from collections.abc import Callable
2
2
  from typing import Any
3
3
 
4
4
  from zrb.context.any_context import AnyContext
5
- from zrb.context.any_shared_context import AnySharedContext
5
+ from zrb.context.print_fn import PrintFn
6
6
  from zrb.env.any_env import AnyEnv
7
7
  from zrb.group.any_group import AnyGroup
8
8
  from zrb.input.any_input import AnyInput
@@ -18,7 +18,7 @@ def make_task(
18
18
  cli_only: bool = False,
19
19
  input: list[AnyInput | None] | AnyInput | None = None,
20
20
  env: list[AnyEnv | None] | AnyEnv | None = None,
21
- execute_condition: bool | str | Callable[[AnySharedContext], bool] = True,
21
+ execute_condition: bool | str | Callable[[AnyContext], bool] = True,
22
22
  retries: int = 2,
23
23
  retry_period: float = 0,
24
24
  readiness_check: list[AnyTask] | AnyTask | None = None,
@@ -30,10 +30,11 @@ def make_task(
30
30
  upstream: list[AnyTask] | AnyTask | None = None,
31
31
  fallback: list[AnyTask] | AnyTask | None = None,
32
32
  successor: list[AnyTask] | AnyTask | None = None,
33
+ print_fn: PrintFn | None = None,
33
34
  group: AnyGroup | None = None,
34
35
  alias: str | None = None,
35
36
  ) -> Callable[[Callable[[AnyContext], Any]], AnyTask]:
36
- def _make_task(fn: Callable[[AnyContext], Any]) -> BaseTask:
37
+ def _make_task(fn: Callable[[AnyContext], Any]) -> AnyTask:
37
38
  task = BaseTask(
38
39
  name=name,
39
40
  color=color,
@@ -55,6 +56,7 @@ def make_task(
55
56
  upstream=upstream,
56
57
  fallback=fallback,
57
58
  successor=successor,
59
+ print_fn=print_fn,
58
60
  )
59
61
  if group is not None:
60
62
  return group.add_task(task, alias=alias)
zrb/task/rsync_task.py CHANGED
@@ -1,7 +1,6 @@
1
- from collections.abc import Callable
2
-
3
- from zrb.attr.type import IntAttr, StrAttr
1
+ from zrb.attr.type import BoolAttr, IntAttr, StrAttr
4
2
  from zrb.context.any_context import AnyContext
3
+ from zrb.context.print_fn import PrintFn
5
4
  from zrb.env.any_env import AnyEnv
6
5
  from zrb.input.any_input import AnyInput
7
6
  from zrb.task.any_task import AnyTask
@@ -17,8 +16,8 @@ class RsyncTask(CmdTask):
17
16
  icon: str | None = None,
18
17
  description: str | None = None,
19
18
  cli_only: bool = False,
20
- input: list[AnyInput] | AnyInput | None = None,
21
- env: list[AnyEnv] | AnyEnv | None = None,
19
+ input: list[AnyInput | None] | AnyInput | None = None,
20
+ env: list[AnyEnv | None] | AnyEnv | None = None,
22
21
  shell: StrAttr | None = None,
23
22
  auto_render_shell: bool = True,
24
23
  remote_host: StrAttr | None = None,
@@ -39,18 +38,22 @@ class RsyncTask(CmdTask):
39
38
  render_local_source_path: bool = True,
40
39
  local_destination_path: StrAttr | None = None,
41
40
  render_local_destination_path: bool = True,
41
+ exclude_from: StrAttr | None = None,
42
+ render_exclude_from: bool = True,
42
43
  cwd: str | None = None,
43
44
  render_cwd: bool = True,
44
45
  plain_print: bool = False,
45
46
  max_output_line: int = 1000,
46
47
  max_error_line: int = 1000,
47
- execute_condition: bool | str | Callable[[AnyContext], bool] = True,
48
+ execution_timeout: int = 3600,
49
+ execute_condition: BoolAttr = True,
48
50
  retries: int = 2,
49
51
  retry_period: float = 0,
50
52
  readiness_check: list[AnyTask] | AnyTask | None = None,
51
53
  upstream: list[AnyTask] | AnyTask | None = None,
52
54
  fallback: list[AnyTask] | AnyTask | None = None,
53
55
  successor: list[AnyTask] | AnyTask | None = None,
56
+ print_fn: PrintFn | None = None,
54
57
  ):
55
58
  super().__init__(
56
59
  name=name,
@@ -77,6 +80,7 @@ class RsyncTask(CmdTask):
77
80
  plain_print=plain_print,
78
81
  max_output_line=max_output_line,
79
82
  max_error_line=max_error_line,
83
+ execution_timeout=execution_timeout,
80
84
  execute_condition=execute_condition,
81
85
  retries=retries,
82
86
  retry_period=retry_period,
@@ -84,6 +88,7 @@ class RsyncTask(CmdTask):
84
88
  upstream=upstream,
85
89
  fallback=fallback,
86
90
  successor=successor,
91
+ print_fn=print_fn,
87
92
  )
88
93
  self._remote_source_path = remote_source_path
89
94
  self._render_remote_source_path = render_remote_source_path
@@ -93,6 +98,8 @@ class RsyncTask(CmdTask):
93
98
  self._render_local_source_path = render_local_source_path
94
99
  self._local_destination_path = local_destination_path
95
100
  self._render_local_destination_path = render_local_destination_path
101
+ self._exclude_from = exclude_from
102
+ self._render_exclude_from = render_exclude_from
96
103
 
97
104
  def _get_source_path(self, ctx: AnyContext) -> str:
98
105
  local_source_path = self._get_local_source_path(ctx)
@@ -144,16 +151,29 @@ class RsyncTask(CmdTask):
144
151
  auto_render=self._render_local_destination_path,
145
152
  )
146
153
 
154
+ def _get_exclude_from_param(self, ctx: AnyContext) -> str:
155
+ exclude_from = get_str_attr(
156
+ ctx,
157
+ self._exclude_from,
158
+ "",
159
+ auto_render=self._render_exclude_from,
160
+ ).strip()
161
+ if exclude_from == "":
162
+ return ""
163
+ return f"--exclude-from='{exclude_from}'"
164
+
147
165
  def _get_cmd_script(self, ctx: AnyContext) -> str:
148
166
  port = self._get_remote_port(ctx)
149
167
  password = self._get_remote_password(ctx)
150
168
  key = self._get_remote_ssh_key(ctx)
151
169
  src = self._get_source_path(ctx)
152
170
  dst = self._get_destination_path(ctx)
171
+ exclude_from = self._get_exclude_from_param(ctx)
172
+ exclude_from_with_space = f"{exclude_from} " if exclude_from != "" else ""
153
173
  if key != "" and password != "":
154
- return f'sshpass -p "$_ZRB_SSH_PASSWORD" rsync --mkpath -avz -e "ssh -i {key} -p {port}" {src} {dst}' # noqa
174
+ return f'sshpass -p "$_ZRB_SSH_PASSWORD" rsync --mkpath -avz -e "ssh -i {key} -p {port}" {exclude_from_with_space}{src} {dst}' # noqa
155
175
  if key != "":
156
- return f'rsync --mkpath -avz -e "ssh -i {key} -p {port}" {src} {dst}'
176
+ return f'rsync --mkpath -avz -e "ssh -i {key} -p {port}" {exclude_from_with_space}{src} {dst}' # noqa
157
177
  if password != "":
158
- return f'sshpass -p "$_ZRB_SSH_PASSWORD" rsync --mkpath -avz -e "ssh -p {port}" {src} {dst}' # noqa
159
- return f'rsync --mkpath -avz -e "ssh -p {port}" {src} {dst}'
178
+ return f'sshpass -p "$_ZRB_SSH_PASSWORD" rsync --mkpath -avz -e "ssh -p {port}" {exclude_from_with_space}{src} {dst}' # noqa
179
+ return f'rsync --mkpath -avz -e "ssh -p {port}" {exclude_from_with_space}{src} {dst}'
zrb/task/scaffolder.py CHANGED
@@ -2,10 +2,11 @@ import os
2
2
  import shutil
3
3
  from collections.abc import Callable
4
4
 
5
- from zrb.attr.type import BoolAttr, StrAttr
5
+ from zrb.attr.type import StrAttr
6
6
  from zrb.content_transformer.any_content_transformer import AnyContentTransformer
7
7
  from zrb.content_transformer.content_transformer import ContentTransformer
8
8
  from zrb.context.any_context import AnyContext
9
+ from zrb.context.print_fn import PrintFn
9
10
  from zrb.env.any_env import AnyEnv
10
11
  from zrb.input.any_input import AnyInput
11
12
  from zrb.task.any_task import AnyTask
@@ -24,8 +25,8 @@ class Scaffolder(BaseTask):
24
25
  icon: str | None = None,
25
26
  description: str | None = None,
26
27
  cli_only: bool = False,
27
- input: list[AnyInput] | AnyInput | None = None,
28
- env: list[AnyEnv] | AnyEnv | None = None,
28
+ input: list[AnyInput | None] | AnyInput | None = None,
29
+ env: list[AnyEnv | None] | AnyEnv | None = None,
29
30
  source_path: StrAttr | None = None,
30
31
  render_source_path: bool = True,
31
32
  destination_path: StrAttr | None = None,
@@ -36,7 +37,7 @@ class Scaffolder(BaseTask):
36
37
  list[AnyContentTransformer] | AnyContentTransformer | TransformConfig
37
38
  ) = [],
38
39
  render_transform_content: bool = True,
39
- execute_condition: BoolAttr = True,
40
+ execute_condition: bool | str | Callable[[AnyContext], bool] = True,
40
41
  retries: int = 2,
41
42
  retry_period: float = 0,
42
43
  readiness_check: list[AnyTask] | AnyTask | None = None,
@@ -48,6 +49,7 @@ class Scaffolder(BaseTask):
48
49
  upstream: list[AnyTask] | AnyTask | None = None,
49
50
  fallback: list[AnyTask] | AnyTask | None = None,
50
51
  successor: list[AnyTask] | AnyTask | None = None,
52
+ print_fn: PrintFn | None = None,
51
53
  ):
52
54
  super().__init__(
53
55
  name=name,
@@ -69,6 +71,7 @@ class Scaffolder(BaseTask):
69
71
  upstream=upstream,
70
72
  fallback=fallback,
71
73
  successor=successor,
74
+ print_fn=print_fn,
72
75
  )
73
76
  self._source_path = source_path
74
77
  self._render_source_path = render_source_path
zrb/task/scheduler.py CHANGED
@@ -6,6 +6,7 @@ from zrb.attr.type import StrAttr, fstring
6
6
  from zrb.callback.any_callback import AnyCallback
7
7
  from zrb.context.any_context import AnyContext
8
8
  from zrb.context.any_shared_context import AnySharedContext
9
+ from zrb.context.print_fn import PrintFn
9
10
  from zrb.env.any_env import AnyEnv
10
11
  from zrb.input.any_input import AnyInput
11
12
  from zrb.task.any_task import AnyTask
@@ -24,8 +25,8 @@ class Scheduler(BaseTrigger):
24
25
  cli_only: bool = False,
25
26
  input: list[AnyInput | None] | AnyInput | None = None,
26
27
  env: list[AnyEnv | None] | AnyEnv | None = None,
27
- schedule: StrAttr = None,
28
- execute_condition: bool | str | Callable[[AnySharedContext], bool] = True,
28
+ schedule: StrAttr | None = None,
29
+ execute_condition: bool | str | Callable[[AnyContext], bool] = True,
29
30
  queue_name: fstring | None = None,
30
31
  callback: list[AnyCallback] | AnyCallback = [],
31
32
  retries: int = 2,
@@ -39,6 +40,7 @@ class Scheduler(BaseTrigger):
39
40
  upstream: list[AnyTask] | AnyTask | None = None,
40
41
  fallback: list[AnyTask] | AnyTask | None = None,
41
42
  successor: list[AnyTask] | AnyTask | None = None,
43
+ print_fn: PrintFn | None = None,
42
44
  ):
43
45
  super().__init__(
44
46
  name=name,
@@ -62,6 +64,7 @@ class Scheduler(BaseTrigger):
62
64
  upstream=upstream,
63
65
  fallback=fallback,
64
66
  successor=successor,
67
+ print_fn=print_fn,
65
68
  )
66
69
  self._cron_pattern = schedule
67
70
 
@@ -76,6 +79,6 @@ class Scheduler(BaseTrigger):
76
79
  ctx.print(f"Current time: {now}")
77
80
  if match_cron(cron_pattern, now):
78
81
  ctx.print(f"Matching {now} with pattern: {cron_pattern}")
79
- xcom = self.get_exchange_xcom(ctx.session)
80
- xcom.push(now)
82
+ if ctx.session is not None:
83
+ self.push_exchange_xcom(ctx.session, now)
81
84
  await asyncio.sleep(60)
zrb/task/tcp_check.py CHANGED
@@ -3,7 +3,7 @@ from collections.abc import Callable
3
3
 
4
4
  from zrb.attr.type import IntAttr, StrAttr
5
5
  from zrb.context.any_context import AnyContext
6
- from zrb.context.context import Context
6
+ from zrb.context.print_fn import PrintFn
7
7
  from zrb.env.any_env import AnyEnv
8
8
  from zrb.input.any_input import AnyInput
9
9
  from zrb.task.any_task import AnyTask
@@ -19,16 +19,17 @@ class TcpCheck(BaseTask):
19
19
  icon: str | None = None,
20
20
  description: str | None = None,
21
21
  cli_only: bool = False,
22
- input: list[AnyInput] | AnyInput | None = None,
23
- env: list[AnyEnv] | AnyEnv | None = None,
22
+ input: list[AnyInput | None] | AnyInput | None = None,
23
+ env: list[AnyEnv | None] | AnyEnv | None = None,
24
24
  host: StrAttr = "localhost",
25
25
  render_host: bool = True,
26
26
  port: IntAttr = 80,
27
27
  interval: int = 5,
28
- execute_condition: bool | str | Callable[[Context], bool] = True,
28
+ execute_condition: bool | str | Callable[[AnyContext], bool] = True,
29
29
  upstream: list[AnyTask] | AnyTask | None = None,
30
30
  fallback: list[AnyTask] | AnyTask | None = None,
31
31
  successor: list[AnyTask] | AnyTask | None = None,
32
+ print_fn: PrintFn | None = None,
32
33
  ):
33
34
  super().__init__(
34
35
  name=name,
@@ -43,6 +44,7 @@ class TcpCheck(BaseTask):
43
44
  upstream=upstream,
44
45
  fallback=fallback,
45
46
  successor=successor,
47
+ print_fn=print_fn,
46
48
  )
47
49
  self._host = host
48
50
  self._render_host = render_host
@@ -0,0 +1,17 @@
1
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⣉⡙⣿⣿⣿⣿⣿⣿⣿⣿
2
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⣡⣴⣿⣿⠇⣾⣿⣿⣿⣿⣿⣿⣿
3
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢃⣴⣿⣿⣿⣿⡟⢰⣿⣿⣿⣿⣿⣿⣿⣿
4
+ ⣿⣿⠟⢿⣿⣿⣿⣿⣿⡇⣸⣿⣿⣿⣿⡟⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿
5
+ ⣇⣀⡲⣦⢻⣿⣿⣿⣿⡇⣿⣿⣿⣿⡟⣰⣿⡿⠿⠿⢿⣿⣿⣿⣿⣿
6
+ ⣿⣿⣧⠙⠸⣿⣿⣿⣿⡇⣿⣿⣿⢏⠼⣋⣥⣶⣿⣿⣦⣤⣉⡛⠻⢿
7
+ ⣿⢟⡁⠀⠀⠀⠙⣿⣿⣇⢿⣿⠋⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠈
8
+ ⡇⠀⡿⠀⠀⠀⠀⠈⠉⣹⣾⠕⠾⣿⣿⣿⣿⡿⠿⠿⠟⠛⢛⣉⣤⣾
9
+ ⣇⠀⠀⢀⠀⠀⠠⢀⣴⣿⠏⠀⢀⣶⡶⣶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿
10
+ ⣿⣶⣤⣤⣤⣦⣶⡿⠟⠁⠀⢠⣾⣿⠃⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
11
+ ⣿⣿⣿⣿⣿⣿⡄⠀⣀⣠⣴⣿⠟⠁⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
12
+ ⣿⣿⣿⣿⣿⣿⣿⣿⠛⠛⠋⠁⠀⠀⣠⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿
13
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣤⣤⣶⣿⡿⠋⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿
14
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡁⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿
15
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
16
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
17
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
@@ -0,0 +1,9 @@
1
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢿⣿⣿⣿⣿
2
+ ⣿⣿⣿⣿⡿⢋⠹⣿⣿⣿⣿⣿⠟⢿⣿⣿⣿⣿⣿⣿⠃⠀⢨⣿⣿⣿⣿
3
+ ⣿⣿⣿⠟⣠⣿⣧⣈⣉⣉⣉⣁⣶⡌⢻⣿⣿⣿⣿⣿⠘⠀⣿⣿⣿⣿⣿
4
+ ⣿⣿⠏⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠛⠛⠛⠛⠛⠀⡀⠻⣿⣿⣿⣿
5
+ ⣿⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠘⣿⣿⣿⣿⣿⣿⣦⠈⢋⣉⠙
6
+ ⣿⣿⡎⢿⣿⣶⣶⣿⣿⣿⣿⣤⣼⣿⡟⢠⣿⣿⣿⡿⠿⠟⠛⣀⣡⣤⣴
7
+ ⠟⣁⣴⣦⠉⠛⠛⠛⠛⠛⠿⠟⠛⠁⠀⣋⣉⣥⣴⣶⣾⣿⣿⣿⣿⣿⣿
8
+ ⣤⣭⣤⣶⣶⣿⣿⣿⠋⣠⡼⢋⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
9
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣤⣤⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
@@ -0,0 +1,16 @@
1
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⢋⣉⣉⣉⣉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿
2
+ ⣿⣿⣿⣿⣿⣿⣿⡿⠋⣠⣾⣿⣿⣿⣿⣿⣷⣦⡈⢻⣿⣿⣿⣿⣿⣿⣿
3
+ ⣿⣿⣿⣿⣿⣿⡿⢁⣾⡟⠁⣨⣿⣿⡅⠈⢻⣿⣿⡄⢻⣿⣿⣿⣿⣿⣿
4
+ ⣿⠏⣤⣤⣉⠛⠃⢸⣿⣷⣾⡟⠉⠉⢻⣶⣾⣿⣿⡇⢸⣿⣿⣿⣿⣿⣿
5
+ ⣿⣧⠄⣹⣿⣿⣷⣬⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⡇⢸⡿⠿⠿⠿⢿⣿
6
+ ⣿⠇⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣴⣿⣿⣿⣿⣿⣥⣤⣶⣶⣶⡶⠆⣹
7
+ ⡏⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⢰⣾⣿
8
+ ⣧⠘⢿⡿⠟⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⣿⣿
9
+ ⣿⣷⣦⣴⠂⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⣿⣿
10
+ ⣿⣿⣿⣿⡀⠻⠿⠋⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⢠⣈⠛⣁⣼⣿⣿
11
+ ⣿⣿⣿⣿⣿⣿⣾⣿⣆⠘⣿⣿⣿⣿⣿⣿⡟⠙⠛⠃⣼⣿⣿⣿⣿⣿⣿
12
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⡘⢿⣿⣿⣿⣿⡀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿
13
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⡙⢿⣿⣿⣧⡈⠻⣿⣿⣿⣿⣿⣿⣿⣿
14
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣉⡙⠻⠿⣦⠙⢿⣿⣿⣿⣿⣿⣿
15
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣌⠡⠘⣿⣿⣿⣿⣿⣿
16
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣠⣿⣿⣿⣿⣿⣿
@@ -0,0 +1,17 @@
1
+ ⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
2
+ ⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠙⠿⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
3
+ ⣿⣿⣿⣿⣿⣿⡀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣮⣝⠻⢿⣿⣿⣿⣿⣿
4
+ ⣿⣿⣿⣿⣿⡟⣡⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠙⢿
5
+ ⣿⠿⣿⣿⡿⢰⣿⡿⠋⠉⠉⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⣸
6
+ ⠁⠀⠀⠙⠃⢿⣿⡅⠀⠀⢀⣼⣿⣿⣿⣿⠟⠛⠻⣿⣿⣷⢀⣴⣿
7
+ ⡀⠀⠀⠀⠀⠸⣿⣛⣳⣾⣿⢿⡍⢉⣻⡇⠰⠀⠀⣿⣿⣿⢸⣿⣿
8
+ ⣷⡀⠀⠀⠀⠀⠈⠻⢿⣿⣿⣷⣶⣬⣽⣿⣦⣤⣤⣟⣿⢇⣾⣿⣿
9
+ ⣿⣿⣄⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⣿⣿⣿⣿⣮⣿⠟⣡⣾⣿⣿⣿
10
+ ⣿⣿⣿⡇⢰⣶⣤⣤⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿
11
+ ⣿⣿⣿⣇⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⡀⠀⠀⠀⠀⢻⣿⣿⣿⣿
12
+ ⣿⣿⣿⡈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⢿⣿⣿⣿
13
+ ⣿⣿⣿⡇⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⢸⣿⣿⣿
14
+ ⣿⣿⣿⡇⠀⠙⠿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⢠⣄⣀⣠⣿⣿⣿⣿
15
+ ⣿⣿⣿⣿⠀⠀⠀⠀⠉⣉⣛⣛⡛⠉⠁⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿
16
+ ⣿⣿⣿⣿⣆⠀⠀⠀⣰⣿⣿⣿⣷⡀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿
17
+ ⣿⣿⣿⣿⣿⣷⣶⣶⣿⣿⣿⣿⣿⣿⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿
@@ -0,0 +1,14 @@
1
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⣿⣿⣿⣿⠁⠀⠉⠉⠛⠛⠛⠉⠛⢻⣿⣿⣿⣿⣿⣿
2
+ ⣿⣿⣿⣿⣿⠟⠋⠁⠀⠀⠀⠈⠙⢧⣤⣤⡀⠀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿
3
+ ⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⢀⣠⡴⠾⠛⠛⠿⣷⣄⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿
4
+ ⣿⣿⣿⠋⠀⠀⠀⠀⣠⣶⣿⠁⠀⠀⣀⡀⠀⠈⠻⣦⡀⠀⠀⠀⠀⠀⣿⣿⣿⣿
5
+ ⣿⣿⡏⠀⠀⠀⠀⣰⡿⠛⠙⠿⠛⠉⠛⠟⢿⣷⣄⣸⡇⠐⢦⣤⣤⠔⠛⠻⣿⣿
6
+ ⣿⣿⡃⠀⠀⠀⠀⡟⠀⣠⠀⠀⠀⠀⢀⠀⠀⠹⠛⠛⣧⠀⠀⠙⠁⠀⠀⠀⠘⣿
7
+ ⣿⣿⣷⡀⠀⠀⢸⠀⢰⣿⡀⠀⠀⠀⢹⣿⠀⢀⡆⠀⠘⣆⣴⣿⠀⠀⠀⠀⠀⠘
8
+ ⣿⡟⠀⠙⣄⠀⢸⠀⠀⢻⣷⣄⠀⠤⠞⠋⣠⡾⠁⠀⢠⠟⠉⠻⡇⠀⠀⠀⠀⢰
9
+ ⡿⠁⠀⠀⣿⣷⣾⣧⠀⠀⠈⠛⢳⠤⠒⠚⠉⠀⢀⡴⠃⠀⠀⠀⠃⠀⠀⠀⣠⣿
10
+ ⡁⠀⠀⠀⣿⠀⠀⠹⣷⠄⢀⣠⣴⣿⣶⣤⣶⡾⠋⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿
11
+ ⣷⡄⠀⠀⢻⠀⠀⠀⠘⢧⡀⠈⠉⠛⠛⠋⠉⠀⠀⠀⣀⣠⣴⣂⣀⠠⠊⢹⣿⣿
12
+ ⣿⣿⣦⡀⢈⡄⠀⠀⠀⠀⠉⠙⠒⠒⠦⠤⠤⣶⣶⣿⣿⠿⠛⠋⠁⠀⢀⣾⣿⣿
13
+ ⣿⣿⣿⣿⣿⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠋⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿
14
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⡀⠀⠀⢀⣀⣠⣴⣾⣿⣶⣤⣠⣴⣿⣿⣿⣿⣿⣿⣿
@@ -0,0 +1,15 @@
1
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
2
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠛⠉⢠⡟⠋⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿
3
+ ⣿⣿⣿⣿⣿⣿⠿⠛⠋⠉⠑⠁⠀⠀⠈⠀⣼⣿⣿⣿⣿⠿⠟⢛⣫⣽⣶
4
+ ⣿⣿⣿⡿⠋⠁⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⢁⣠⣴⣾⣿⣿⣿⣿
5
+ ⣿⣿⣯⡴⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿
6
+ ⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠈⠁⢻⣿⣿⣿⣿⣿⣿⣿⣿
7
+ ⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⣧⡀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿
8
+ ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣷⣶⣦⡀⠀⠀⠀⢻⣿⣿⣿⣿⣿⣿⣿
9
+ ⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⣷⠀⡀⠀⢸⣿⣿⣿⣿⣿⣿⣿
10
+ ⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣧⣷⣤⣾⣿⣿⣿⣿⣿⣿⣿
11
+ ⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
12
+ ⣿⣿⣿⣿⣦⣄⡀⠀⠀⠈⢦⡀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
13
+ ⣿⣿⣿⣿⣿⣿⣿⣧⣤⣀⠀⣿⡀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
14
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣷⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
15
+ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
@@ -0,0 +1,92 @@
1
+ import os
2
+ import random
3
+
4
+
5
+ def create_banner(art: str | None = None, text: str | None = None) -> str:
6
+ # First get art using _get_art_only
7
+ art_content = _get_art_only(art)
8
+
9
+ # If no text provided, just return the art
10
+ if text is None or text.strip() == "":
11
+ return art_content
12
+
13
+ # Find the longest line in the art, make every line has the same length
14
+ art_lines = art_content.splitlines()
15
+ if not art_lines:
16
+ return text
17
+
18
+ # Find the maximum line length in the art
19
+ max_art_length = max(len(line) for line in art_lines)
20
+
21
+ # Pad all art lines to the same length
22
+ padded_art_lines = [line.ljust(max_art_length) for line in art_lines]
23
+
24
+ # Split text into lines
25
+ text_lines = text.splitlines()
26
+
27
+ # Combine art and text lines
28
+ combined_lines = []
29
+
30
+ # Determine the maximum number of lines we need
31
+ max_lines = max(len(padded_art_lines), len(text_lines))
32
+
33
+ # Calculate vertical offsets for centering
34
+ art_offset = (max_lines - len(padded_art_lines)) // 2
35
+ text_offset = (max_lines - len(text_lines)) // 2
36
+
37
+ for i in range(max_lines):
38
+ # Get art line (or empty string if we've run out of art lines)
39
+ art_index = i - art_offset
40
+ if 0 <= art_index < len(padded_art_lines):
41
+ art_line = padded_art_lines[art_index]
42
+ else:
43
+ art_line = " " * max_art_length
44
+
45
+ # Get text line (or empty string if we've run out of text lines)
46
+ text_index = i - text_offset
47
+ if 0 <= text_index < len(text_lines):
48
+ text_line = text_lines[text_index]
49
+ else:
50
+ text_line = ""
51
+
52
+ # Combine art and text lines
53
+ combined_line = art_line + " " + text_line
54
+ combined_lines.append(combined_line)
55
+
56
+ # Return the combined result
57
+ return "\n".join(combined_lines)
58
+
59
+
60
+ def _get_art_only(art: str | None = None) -> str:
61
+ # If name is provided
62
+ if art is not None:
63
+ # 1) name is a file, load the content of the file, return
64
+ expanded_name = os.path.expanduser(art)
65
+ if os.path.isfile(expanded_name):
66
+ with open(expanded_name, "r") as f:
67
+ return f.read()
68
+
69
+ # 2) name is a string, but not a file
70
+ # Check if art/name.txt exists in the script directory
71
+ cwd = os.path.dirname(__file__)
72
+ art_path = os.path.join(cwd, "art", f"{art}.txt")
73
+ if os.path.isfile(art_path):
74
+ with open(art_path, "r") as f:
75
+ return f.read()
76
+
77
+ # 3) otherwise load random file from art/ directory
78
+ cwd = os.path.dirname(__file__)
79
+ art_dir = os.path.join(cwd, "art")
80
+ # Get all .txt files in the art directory
81
+ try:
82
+ art_files = [f for f in os.listdir(art_dir) if f.endswith(".txt")]
83
+ except FileNotFoundError:
84
+ # If art directory doesn't exist, return empty string
85
+ return ""
86
+ if not art_files:
87
+ return ""
88
+ # Select a random file
89
+ random_file = random.choice(art_files)
90
+ random_file_path = os.path.join(art_dir, random_file)
91
+ with open(random_file_path, "r") as f:
92
+ return f.read()