zrb 1.0.0a2__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 (151) 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 +1 -1
  81. zrb/context/any_shared_context.py +3 -3
  82. zrb/context/context.py +10 -8
  83. zrb/context/shared_context.py +9 -8
  84. zrb/env/__init__.py +0 -3
  85. zrb/env/any_env.py +1 -1
  86. zrb/env/env.py +3 -4
  87. zrb/env/env_file.py +4 -4
  88. zrb/env/env_map.py +2 -2
  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 -45
  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 +8 -12
  107. zrb/runner/web_app/task_ui/view.html +2 -2
  108. zrb/runner/web_server.py +137 -211
  109. zrb/runner/web_util.py +5 -35
  110. zrb/session/any_session.py +13 -7
  111. zrb/session/session.py +78 -40
  112. zrb/session_state_log/session_state_log.py +7 -5
  113. zrb/session_state_logger/any_session_state_logger.py +1 -1
  114. zrb/session_state_logger/default_session_state_logger.py +2 -2
  115. zrb/session_state_logger/file_session_state_logger.py +19 -27
  116. zrb/task/any_task.py +4 -4
  117. zrb/task/base_task.py +33 -23
  118. zrb/task/base_trigger.py +11 -12
  119. zrb/task/cmd_task.py +48 -39
  120. zrb/task/http_check.py +8 -8
  121. zrb/task/llm_task.py +160 -0
  122. zrb/task/make_task.py +9 -9
  123. zrb/task/rsync_task.py +7 -7
  124. zrb/task/scaffolder.py +14 -11
  125. zrb/task/scheduler.py +6 -7
  126. zrb/task/task.py +1 -1
  127. zrb/task/tcp_check.py +8 -8
  128. zrb/util/attr.py +19 -3
  129. zrb/util/cli/style.py +71 -2
  130. zrb/util/cli/subcommand.py +2 -2
  131. zrb/util/codemod/__init__.py +0 -0
  132. zrb/util/codemod/add_code_to_class.py +35 -0
  133. zrb/util/codemod/add_code_to_function.py +36 -0
  134. zrb/util/codemod/add_code_to_method.py +55 -0
  135. zrb/util/codemod/add_key_to_dict.py +51 -0
  136. zrb/util/codemod/add_param_to_function_call.py +39 -0
  137. zrb/util/codemod/add_property_to_class.py +55 -0
  138. zrb/util/git.py +156 -0
  139. zrb/util/git_subtree.py +94 -0
  140. zrb/util/group.py +2 -2
  141. zrb/util/llm/tool.py +63 -0
  142. zrb/util/string/conversion.py +7 -0
  143. zrb/util/todo.py +135 -0
  144. {zrb-1.0.0a2.dist-info → zrb-1.0.0a3.dist-info}/METADATA +8 -5
  145. zrb-1.0.0a3.dist-info/RECORD +194 -0
  146. zrb/builtin/shell/_group.py +0 -9
  147. zrb/builtin/shell/autocomplete/_group.py +0 -6
  148. zrb/runner/web_app/any_request_handler.py +0 -24
  149. zrb-1.0.0a2.dist-info/RECORD +0 -120
  150. {zrb-1.0.0a2.dist-info → zrb-1.0.0a3.dist-info}/WHEEL +0 -0
  151. {zrb-1.0.0a2.dist-info → zrb-1.0.0a3.dist-info}/entry_points.txt +0 -0
zrb/builtin/todo.py ADDED
@@ -0,0 +1,186 @@
1
+ import datetime
2
+ import os
3
+
4
+ from zrb.builtin.group import todo_group
5
+ from zrb.config import TODO_DIR
6
+ from zrb.context.any_context import AnyContext
7
+ from zrb.input.str_input import StrInput
8
+ from zrb.input.text_input import TextInput
9
+ from zrb.task.make_task import make_task
10
+ from zrb.util.cli.style import (
11
+ stylize_bold_green,
12
+ stylize_cyan,
13
+ stylize_magenta,
14
+ stylize_yellow,
15
+ )
16
+ from zrb.util.string.name import get_random_name
17
+ from zrb.util.todo import (
18
+ TodoTask,
19
+ parse_todo_line,
20
+ read_todo_from_file,
21
+ todo_task_to_line,
22
+ write_todo_to_file,
23
+ )
24
+
25
+
26
+ @make_task(
27
+ name="todo-add",
28
+ input=[
29
+ StrInput(
30
+ name="description",
31
+ description="Task description",
32
+ prompt="Task description",
33
+ ),
34
+ StrInput(
35
+ name="priority",
36
+ description="Task priority",
37
+ prompt="Task priority",
38
+ default_str="E",
39
+ ),
40
+ StrInput(
41
+ name="project",
42
+ description="Task project",
43
+ prompt="Task project (space separated)",
44
+ ),
45
+ StrInput(
46
+ name="context",
47
+ description="Task context",
48
+ prompt="Task context (space separated)",
49
+ ),
50
+ ],
51
+ description="➕ Add todo",
52
+ group=todo_group,
53
+ alias="add",
54
+ )
55
+ def todo_add(ctx: AnyContext):
56
+ todo_file_path = os.path.join(TODO_DIR, "todo.txt")
57
+ todo_tasks: list[TodoTask] = []
58
+ if os.path.isfile(todo_file_path):
59
+ todo_tasks = read_todo_from_file(todo_file_path)
60
+ else:
61
+ os.makedirs(TODO_DIR, exist_ok=True)
62
+ todo_tasks.append(
63
+ _complete_todo_task(
64
+ TodoTask(
65
+ priority=ctx.input.priority.upper(),
66
+ description=ctx.input.description,
67
+ contexts=[
68
+ context.strip()
69
+ for context in ctx.input.context.split(" ")
70
+ if context.strip() != ""
71
+ ],
72
+ projects=[
73
+ project.strip()
74
+ for project in ctx.input.project.split(" ")
75
+ if project.strip() != ""
76
+ ],
77
+ )
78
+ )
79
+ )
80
+ write_todo_to_file(todo_file_path, todo_tasks)
81
+ return _get_visual_todo_list()
82
+
83
+
84
+ @make_task(name="todo-list", description="📋 List todo", group=todo_group, alias="list")
85
+ def todo_list(ctx: AnyContext):
86
+ return _get_visual_todo_list()
87
+
88
+
89
+ @make_task(
90
+ name="todo-edit",
91
+ input=[
92
+ TextInput(
93
+ name="text",
94
+ description="Todo.txt content",
95
+ prompt="Todo.txt content (will override existing)",
96
+ default_str=lambda _: _get_todo_txt_content(),
97
+ ),
98
+ ],
99
+ description="✏️ Edit todo",
100
+ group=todo_group,
101
+ alias="edit",
102
+ )
103
+ def todo_edit(ctx: AnyContext):
104
+ todo_tasks = [
105
+ _complete_todo_task(parse_todo_line(line))
106
+ for line in ctx.input.text.split("\n")
107
+ if line.strip() != ""
108
+ ]
109
+ new_content = "\n".join(todo_task_to_line(todo_task) for todo_task in todo_tasks)
110
+ todo_file_path = os.path.join(TODO_DIR, "todo.txt")
111
+ with open(todo_file_path, "w") as f:
112
+ f.write(new_content)
113
+ return _get_visual_todo_list()
114
+
115
+
116
+ def _complete_todo_task(todo_task: TodoTask):
117
+ if todo_task.creation_date is None:
118
+ todo_task.creation_date = datetime.date.today()
119
+ if "id" not in todo_task.keyval:
120
+ todo_task.keyval["id"] = get_random_name()
121
+ return todo_task
122
+
123
+
124
+ def _get_visual_todo_list() -> str:
125
+ todo_file_path = os.path.join(TODO_DIR, "todo.txt")
126
+ if not os.path.isfile(todo_file_path):
127
+ return "\n".join(["", " Todo.txt not found... 🌵🦖", ""])
128
+ todo_tasks = read_todo_from_file(todo_file_path)
129
+ if len(todo_tasks) == 0:
130
+ return "\n".join(["", " Empty todo list... 🌵🦖", ""])
131
+ max_desc_name_length = max(len(todo_task.description) for todo_task in todo_tasks)
132
+ if max_desc_name_length < len("DESCRIPTION"):
133
+ max_desc_name_length = len("DESCRIPTION")
134
+ # Headers
135
+ results = [
136
+ stylize_bold_green(
137
+ " ".join(
138
+ [
139
+ "".ljust(3), # priority
140
+ "".ljust(3), # completed
141
+ "COMPLETED AT".rjust(14), # completed date
142
+ "CREATED AT".rjust(14), # completed date
143
+ "DESCRIPTION".ljust(max_desc_name_length),
144
+ "PROJECT/CONTEXT/OTHERS",
145
+ ]
146
+ )
147
+ )
148
+ ]
149
+ for todo_task in todo_tasks:
150
+ completed = "[x]" if todo_task.completed else "[ ]"
151
+ priority = " " if todo_task.priority is None else f"({todo_task.priority})"
152
+ completion_date = stylize_yellow(_date_to_str(todo_task.completion_date))
153
+ creation_date = stylize_cyan(_date_to_str(todo_task.creation_date))
154
+ description = todo_task.description.ljust(max_desc_name_length)
155
+ additions = ", ".join(
156
+ [stylize_yellow(f"+{project}") for project in todo_task.projects]
157
+ + [stylize_cyan(f"@{context}") for context in todo_task.contexts]
158
+ + [stylize_magenta(f"{key}:{val}") for key, val in todo_task.keyval.items()]
159
+ )
160
+ results.append(
161
+ " ".join(
162
+ [
163
+ completed,
164
+ priority,
165
+ completion_date,
166
+ creation_date,
167
+ description,
168
+ additions,
169
+ ]
170
+ )
171
+ )
172
+ return "\n".join(results)
173
+
174
+
175
+ def _date_to_str(date: datetime.date | None) -> str:
176
+ if date is None:
177
+ return "".ljust(14)
178
+ return date.strftime("%a %Y-%m-%d")
179
+
180
+
181
+ def _get_todo_txt_content() -> str:
182
+ todo_file_path = os.path.join(TODO_DIR, "todo.txt")
183
+ if not os.path.isfile(todo_file_path):
184
+ return ""
185
+ with open(todo_file_path, "r") as f:
186
+ return f.read()
@@ -1,7 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import Any
3
3
 
4
- from ..context.any_shared_context import AnySharedContext
4
+ from zrb.context.any_shared_context import AnySharedContext
5
5
 
6
6
 
7
7
  class AnyCallback(ABC):
zrb/callback/callback.py CHANGED
@@ -1,10 +1,10 @@
1
1
  from typing import Any
2
2
 
3
- from ..attr.type import StrDictAttr
4
- from ..session.any_session import AnySession
5
- from ..task.any_task import AnyTask
6
- from ..util.attr import get_str_dict_attr
7
- from .any_callback import AnyCallback
3
+ from zrb.attr.type import StrDictAttr
4
+ from zrb.callback.any_callback import AnyCallback
5
+ from zrb.session.any_session import AnySession
6
+ from zrb.task.any_task import AnyTask
7
+ from zrb.util.attr import get_str_dict_attr
8
8
 
9
9
 
10
10
  class Callback(AnyCallback):
zrb/cmd/cmd_val.py CHANGED
@@ -1,8 +1,8 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections.abc import Callable
3
3
 
4
- from ..attr.type import fstring
5
- from ..context.context import Context
4
+ from zrb.attr.type import fstring
5
+ from zrb.context.context import Context
6
6
 
7
7
 
8
8
  class AnyCmdVal(ABC):
zrb/config.py CHANGED
@@ -3,7 +3,7 @@ import logging
3
3
  import os
4
4
  import platform
5
5
 
6
- from .util.string.conversion import to_boolean
6
+ from zrb.util.string.conversion import to_boolean
7
7
 
8
8
 
9
9
  def _get_current_shell() -> str:
@@ -53,8 +53,11 @@ SHOW_PROMPT = to_boolean(os.getenv("ZRB_SHOW_PROMPT", "1"))
53
53
  SESSION_LOG_DIR = os.getenv(
54
54
  "ZRB_SESSION_LOG_DIR", os.path.expanduser(os.path.join("~", ".zrb-session"))
55
55
  )
56
+ TODO_DIR = os.getenv("ZRB_TODO_DIR", os.path.expanduser(os.path.join("~", "todo")))
56
57
  VERSION = metadata.version("zrb")
57
58
  WEB_HTTP_PORT = int(os.getenv("ZRB_WEB_HTTP_PORT", "21213"))
59
+ LLM_MODEL = os.getenv("ZRB_LLM_MODEL", "ollama_chat/llama3.1")
60
+ LLM_SYSTEM_PROMPT = os.getenv("ZRB_LLM_SYSTEM_PROMPT", "You are a helpful assistant")
58
61
 
59
62
  BANNER = f"""
60
63
  bb
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
 
3
- from ..context.any_context import AnyContext
3
+ from zrb.context.any_context import AnyContext
4
4
 
5
5
 
6
6
  class AnyContentTransformer(ABC):
@@ -2,8 +2,8 @@ import fnmatch
2
2
  import re
3
3
  from collections.abc import Callable
4
4
 
5
- from ..context.any_context import AnyContext
6
- from .any_content_transformer import AnyContentTransformer
5
+ from zrb.content_transformer.any_content_transformer import AnyContentTransformer
6
+ from zrb.context.any_context import AnyContext
7
7
 
8
8
 
9
9
  class ContentTransformer(AnyContentTransformer):
@@ -2,7 +2,7 @@ import sys
2
2
  from abc import abstractmethod
3
3
  from typing import TextIO
4
4
 
5
- from .any_shared_context import AnySharedContext
5
+ from zrb.context.any_shared_context import AnySharedContext
6
6
 
7
7
 
8
8
  class AnyContext(AnySharedContext):
@@ -3,11 +3,11 @@ from __future__ import annotations # Enables forward references
3
3
  from abc import ABC, abstractmethod
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
- from ..dot_dict.dot_dict import DotDict
7
- from ..xcom.xcom import Xcom
6
+ from zrb.dot_dict.dot_dict import DotDict
7
+ from zrb.xcom.xcom import Xcom
8
8
 
9
9
  if TYPE_CHECKING:
10
- from ..session import any_session
10
+ from zrb.session import any_session
11
11
 
12
12
 
13
13
  class AnySharedContext(ABC):
zrb/context/context.py CHANGED
@@ -4,12 +4,12 @@ import re
4
4
  import sys
5
5
  from typing import Any, TextIO
6
6
 
7
- from ..dot_dict.dot_dict import DotDict
8
- from ..session.any_session import AnySession
9
- from ..util.cli.style import stylize, stylize_error, stylize_log, stylize_warning
10
- from ..util.string.conversion import to_boolean
11
- from .any_context import AnyContext
12
- from .any_shared_context import AnySharedContext
7
+ from zrb.context.any_context import AnyContext
8
+ from zrb.context.any_shared_context import AnySharedContext
9
+ from zrb.dot_dict.dot_dict import DotDict
10
+ from zrb.session.any_session import AnySession
11
+ from zrb.util.cli.style import stylize, stylize_error, stylize_log, stylize_warning
12
+ from zrb.util.string.conversion import to_boolean
13
13
 
14
14
 
15
15
  def _remove_ansi_escape_sequences(text):
@@ -103,14 +103,16 @@ class Context(AnyContext):
103
103
  ):
104
104
  color = self._color
105
105
  icon = self._icon
106
- task_name = self._task_name.rjust(15)
106
+ max_name_length = max(len(name) + len(icon) for name in self.session.task_names)
107
+ styled_task_name = f"{icon} {self._task_name}"
108
+ padded_styled_task_name = styled_task_name.rjust(max_name_length + 1)
107
109
  if self._attempt == 0:
108
110
  attempt_status = "".ljust(5)
109
111
  else:
110
112
  attempt_status = f"{self._attempt}/{self._max_attempt}".ljust(5)
111
113
  now = datetime.datetime.now()
112
114
  formatted_time = now.strftime("%y%m%d %H:%M:%S.%f")[:19]
113
- prefix = f"{formatted_time} {attempt_status} {icon} {task_name} ⬤ "
115
+ prefix = f"{formatted_time} {attempt_status} {padded_styled_task_name} ⬤ "
114
116
  message = sep.join([f"{value}" for value in values])
115
117
  self.append_to_shared_log(_remove_ansi_escape_sequences(f"{prefix} {message}"))
116
118
  stylized_prefix = stylize(prefix, color=color)
@@ -1,10 +1,12 @@
1
1
  import datetime
2
2
  from typing import Any
3
3
 
4
- from ..config import LOGGING_LEVEL
5
- from ..dot_dict.dot_dict import DotDict
6
- from ..session.any_session import AnySession
7
- from ..util.string.conversion import (
4
+ from zrb.config import LOGGING_LEVEL
5
+ from zrb.context.any_shared_context import AnySharedContext
6
+ from zrb.dot_dict.dot_dict import DotDict
7
+ from zrb.session.any_session import AnySession
8
+ from zrb.util.string.conversion import (
9
+ double_quote,
8
10
  to_boolean,
9
11
  to_camel_case,
10
12
  to_human_case,
@@ -12,9 +14,8 @@ from ..util.string.conversion import (
12
14
  to_pascal_case,
13
15
  to_snake_case,
14
16
  )
15
- from ..util.string.format import fstring_format
16
- from ..xcom.xcom import Xcom
17
- from .any_shared_context import AnySharedContext
17
+ from zrb.util.string.format import fstring_format
18
+ from zrb.xcom.xcom import Xcom
18
19
 
19
20
 
20
21
  class SharedContext(AnySharedContext):
@@ -86,12 +87,12 @@ class SharedContext(AnySharedContext):
86
87
  data={
87
88
  "ctx": self,
88
89
  "datetime": datetime,
89
- "Xcom": Xcom,
90
90
  "to_boolean": to_boolean,
91
91
  "to_camel_case": to_camel_case,
92
92
  "to_human_case": to_human_case,
93
93
  "to_kebab_case": to_kebab_case,
94
94
  "to_pascal_case": to_pascal_case,
95
95
  "to_snake_case": to_snake_case,
96
+ "double_quote": double_quote,
96
97
  },
97
98
  )
zrb/env/__init__.py CHANGED
@@ -1,3 +0,0 @@
1
- from .env import Env
2
-
3
- assert Env
zrb/env/any_env.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
 
3
- from ..context.any_shared_context import AnySharedContext
3
+ from zrb.context.any_shared_context import AnySharedContext
4
4
 
5
5
 
6
6
  class AnyEnv(ABC):
zrb/env/env.py CHANGED
@@ -1,10 +1,9 @@
1
1
  import os
2
2
 
3
+ from zrb.attr.type import StrAttr
3
4
  from zrb.context.any_shared_context import AnySharedContext
4
-
5
- from ..attr.type import StrAttr
6
- from ..util.attr import get_str_attr
7
- from .any_env import AnyEnv
5
+ from zrb.env.any_env import AnyEnv
6
+ from zrb.util.attr import get_str_attr
8
7
 
9
8
 
10
9
  class Env(AnyEnv):
zrb/env/env_file.py CHANGED
@@ -1,9 +1,9 @@
1
1
  from dotenv import dotenv_values
2
2
 
3
- from ..attr.type import StrAttr
4
- from ..context.shared_context import SharedContext
5
- from ..util.attr import get_str_attr
6
- from .env_map import EnvMap
3
+ from zrb.attr.type import StrAttr
4
+ from zrb.context.shared_context import SharedContext
5
+ from zrb.env.env_map import EnvMap
6
+ from zrb.util.attr import get_str_attr
7
7
 
8
8
 
9
9
  class EnvFile(EnvMap):
zrb/env/env_map.py CHANGED
@@ -1,8 +1,8 @@
1
1
  import os
2
2
  from collections.abc import Callable
3
3
 
4
- from ..context.shared_context import SharedContext
5
- from .any_env import AnyEnv
4
+ from zrb.context.shared_context import SharedContext
5
+ from zrb.env.any_env import AnyEnv
6
6
 
7
7
 
8
8
  class EnvMap(AnyEnv):
zrb/group/__init__.py CHANGED
@@ -1,3 +0,0 @@
1
- from .group import Group
2
-
3
- assert Group
zrb/group/any_group.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Optional
2
+ from typing import Optional, Union
3
3
 
4
- from ..task.any_task import AnyTask
4
+ from zrb.task.any_task import AnyTask
5
5
 
6
6
 
7
7
  class AnyGroup(ABC):
@@ -36,7 +36,7 @@ class AnyGroup(ABC):
36
36
  pass
37
37
 
38
38
  @abstractmethod
39
- def add_group(self, group: "AnyGroup") -> "AnyGroup":
39
+ def add_group(self, group: Union["AnyGroup", str]) -> "AnyGroup":
40
40
  pass
41
41
 
42
42
  @abstractmethod
zrb/group/group.py CHANGED
@@ -1,5 +1,5 @@
1
- from ..task.any_task import AnyTask
2
- from .any_group import AnyGroup
1
+ from zrb.group.any_group import AnyGroup
2
+ from zrb.task.any_task import AnyTask
3
3
 
4
4
 
5
5
  class Group(AnyGroup):
@@ -42,10 +42,11 @@ class Group(AnyGroup):
42
42
  alias.sort()
43
43
  return {name: self._tasks.get(name) for name in alias}
44
44
 
45
- def add_group(self, group: AnyGroup, alias: str | None = None) -> AnyGroup:
46
- alias = alias if alias is not None else group.name
47
- self._groups[alias] = group
48
- return group
45
+ def add_group(self, group: AnyGroup | str, alias: str | None = None) -> AnyGroup:
46
+ real_group = Group(group) if isinstance(group, str) else group
47
+ alias = alias if alias is not None else real_group.name
48
+ self._groups[alias] = real_group
49
+ return real_group
49
50
 
50
51
  def add_task(self, task: AnyTask, alias: str | None = None) -> AnyTask:
51
52
  alias = alias if alias is not None else task.name
zrb/input/any_input.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
 
3
- from ..context.any_shared_context import AnySharedContext
3
+ from zrb.context.any_shared_context import AnySharedContext
4
4
 
5
5
 
6
6
  class AnyInput(ABC):
zrb/input/base_input.py CHANGED
@@ -1,9 +1,9 @@
1
1
  from typing import Any
2
2
 
3
- from ..attr.type import StrAttr
4
- from ..context.any_shared_context import AnySharedContext
5
- from ..util.attr import get_str_attr
6
- from .any_input import AnyInput
3
+ from zrb.attr.type import StrAttr
4
+ from zrb.context.any_shared_context import AnySharedContext
5
+ from zrb.input.any_input import AnyInput
6
+ from zrb.util.attr import get_str_attr
7
7
 
8
8
 
9
9
  class BaseInput(AnyInput):
zrb/input/bool_input.py CHANGED
@@ -1,10 +1,10 @@
1
- from ..attr.type import StrAttr
2
- from ..context.any_shared_context import AnySharedContext
3
- from ..util.string.conversion import to_boolean
4
- from .base_input import BaseInput
1
+ from zrb.attr.type import StrAttr
2
+ from zrb.context.any_shared_context import AnySharedContext
3
+ from zrb.input.base_input import BaseInput
4
+ from zrb.util.string.conversion import to_boolean
5
5
 
6
6
 
7
- class IntInput(BaseInput):
7
+ class BoolInput(BaseInput):
8
8
  def __init__(
9
9
  self,
10
10
  name: str,
zrb/input/float_input.py CHANGED
@@ -1,6 +1,6 @@
1
- from ..attr.type import StrAttr
2
- from ..context.any_shared_context import AnySharedContext
3
- from .base_input import BaseInput
1
+ from zrb.attr.type import StrAttr
2
+ from zrb.context.any_shared_context import AnySharedContext
3
+ from zrb.input.base_input import BaseInput
4
4
 
5
5
 
6
6
  class FloatInput(BaseInput):
zrb/input/int_input.py CHANGED
@@ -1,6 +1,6 @@
1
- from ..attr.type import StrAttr
2
- from ..context.any_shared_context import AnySharedContext
3
- from .base_input import BaseInput
1
+ from zrb.attr.type import StrAttr
2
+ from zrb.context.any_shared_context import AnySharedContext
3
+ from zrb.input.base_input import BaseInput
4
4
 
5
5
 
6
6
  class IntInput(BaseInput):
@@ -0,0 +1,51 @@
1
+ from zrb.attr.type import StrAttr, StrListAttr
2
+ from zrb.context.any_shared_context import AnySharedContext
3
+ from zrb.input.base_input import BaseInput
4
+ from zrb.util.attr import get_str_list_attr
5
+
6
+
7
+ class OptionInput(BaseInput):
8
+ def __init__(
9
+ self,
10
+ name: str,
11
+ description: str | None = None,
12
+ prompt: str | None = None,
13
+ options: StrListAttr = [],
14
+ default_str: StrAttr = "",
15
+ auto_render: bool = True,
16
+ allow_empty: bool = True,
17
+ ):
18
+ super().__init__(
19
+ name=name,
20
+ description=description,
21
+ prompt=prompt,
22
+ default_str=default_str,
23
+ auto_render=auto_render,
24
+ allow_empty=allow_empty,
25
+ )
26
+ self._options = options
27
+
28
+ def to_html(self, ctx: AnySharedContext) -> str:
29
+ name = self.name
30
+ description = self.description
31
+ default = self._get_default_str(ctx)
32
+ html = [f'<select name="{name}" placeholder="{description}">']
33
+ for value in get_str_list_attr(ctx, self._options, self._auto_render):
34
+ selected = "selected" if value == default else ""
35
+ html.append(f'<option value="{value}" {selected}>{value}</option>')
36
+ html.append("</select>")
37
+ return "\n".join(html)
38
+
39
+ def _prompt_cli_str(self, shared_ctx: AnySharedContext) -> str:
40
+ prompt_message = self.prompt_message
41
+ default_value = self._get_default_str(shared_ctx)
42
+ options = get_str_list_attr(shared_ctx, self._options, self._auto_render)
43
+ option_str = ", ".join(options)
44
+ if default_value != "":
45
+ prompt_message = f"{prompt_message} ({option_str}) [{default_value}]"
46
+ value = input(f"{prompt_message}: ")
47
+ if value.strip() != "" and value.strip() not in options:
48
+ value = self._prompt_cli_str(shared_ctx)
49
+ if value.strip() == "":
50
+ value = default_value
51
+ return value
@@ -1,8 +1,8 @@
1
1
  import getpass
2
2
  from collections.abc import Callable
3
3
 
4
- from ..context.any_shared_context import AnySharedContext
5
- from .base_input import BaseInput
4
+ from zrb.context.any_shared_context import AnySharedContext
5
+ from zrb.input.base_input import BaseInput
6
6
 
7
7
 
8
8
  class PasswordInput(BaseInput):
zrb/input/str_input.py CHANGED
@@ -1,4 +1,4 @@
1
- from .base_input import BaseInput
1
+ from zrb.input.base_input import BaseInput
2
2
 
3
3
 
4
4
  class StrInput(BaseInput):
zrb/input/text_input.py CHANGED
@@ -1,10 +1,11 @@
1
+ import os
1
2
  import subprocess
2
3
  import tempfile
3
4
  from collections.abc import Callable
4
5
 
5
- from ..config import DEFAULT_EDITOR
6
- from ..context.any_shared_context import AnySharedContext
7
- from .base_input import BaseInput
6
+ from zrb.config import DEFAULT_EDITOR
7
+ from zrb.context.any_shared_context import AnySharedContext
8
+ from zrb.input.base_input import BaseInput
8
9
 
9
10
 
10
11
  class TextInput(BaseInput):
@@ -66,14 +67,15 @@ class TextInput(BaseInput):
66
67
 
67
68
  def _prompt_cli_str(self, shared_ctx: AnySharedContext) -> str:
68
69
  prompt_message = (
69
- f"{self.comment_start}{super().prompt_message}{self.comment_end}\n"
70
+ f"{self.comment_start}{super().prompt_message}{self.comment_end}"
70
71
  )
72
+ prompt_message_eol = f"{prompt_message}\n"
71
73
  default_value = self._get_default_str(shared_ctx)
72
74
  with tempfile.NamedTemporaryFile(
73
75
  delete=False, suffix=self._extension
74
76
  ) as temp_file:
75
77
  temp_file_name = temp_file.name
76
- temp_file.write(prompt_message.encode())
78
+ temp_file.write(prompt_message_eol.encode())
77
79
  # Pre-fill with default content
78
80
  if default_value:
79
81
  temp_file.write(default_value.encode())
@@ -82,8 +84,8 @@ class TextInput(BaseInput):
82
84
  subprocess.call([self._editor, temp_file_name])
83
85
  # Read the edited content
84
86
  with open(temp_file_name, "r") as temp_file:
85
- edited_content = temp_file.read().strip()
86
- parts = edited_content.split(prompt_message)
87
- if len(parts) == 2 and parts[0].strip() == "":
88
- edited_content = parts[1]
89
- return edited_content.strip() if edited_content.strip() else default_value
87
+ edited_content = temp_file.read()
88
+ parts = [text.strip() for text in edited_content.split(prompt_message, 1)]
89
+ edited_content = "\n".join(parts).lstrip()
90
+ os.remove(temp_file_name)
91
+ return edited_content