zrb 1.0.0a10__py3-none-any.whl → 1.0.0a14__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 (79) hide show
  1. zrb/builtin/__init__.py +15 -5
  2. zrb/builtin/project/add/fastapp.py +32 -17
  3. zrb/builtin/project/add/fastapp_template/_zrb/column/create_column_task.py +11 -0
  4. zrb/builtin/project/add/fastapp_template/_zrb/config.py +4 -4
  5. zrb/builtin/project/add/fastapp_template/_zrb/entity/create_entity_task.py +196 -0
  6. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/my_entity_usecase.py +66 -0
  7. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/factory.py +13 -0
  8. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/my_entity_db_repository.py +33 -0
  9. zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/my_entity_repository.py +39 -0
  10. zrb/builtin/project/add/fastapp_template/_zrb/entity/schema.template.py +29 -0
  11. zrb/builtin/project/add/fastapp_template/_zrb/group.py +9 -5
  12. zrb/builtin/project/add/fastapp_template/_zrb/helper.py +25 -11
  13. zrb/builtin/project/add/fastapp_template/_zrb/input.py +43 -0
  14. zrb/builtin/project/add/fastapp_template/_zrb/main.py +30 -21
  15. zrb/builtin/project/add/fastapp_template/_zrb/module/create_module_task.py +136 -0
  16. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/alembic.ini +117 -0
  17. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/api_client.py +6 -0
  18. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/direct_client.py +6 -0
  19. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/factory.py +9 -0
  20. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/README +1 -0
  21. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/env.py +108 -0
  22. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/script.py.mako +26 -0
  23. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration/versions/3093c7336477_add_user_table.py +37 -0
  24. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/migration_metadata.py +3 -0
  25. zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/route.py +19 -0
  26. zrb/builtin/project/add/fastapp_template/_zrb/module/run_module.template.py +26 -0
  27. zrb/builtin/project/add/fastapp_template/_zrb/venv_task.py +2 -5
  28. zrb/builtin/project/add/fastapp_template/common/app.py +3 -1
  29. zrb/builtin/project/add/fastapp_template/config.py +7 -7
  30. zrb/builtin/project/add/fastapp_template/module/auth/client/any_client.py +27 -0
  31. zrb/builtin/project/add/fastapp_template/module/auth/client/api_client.py +2 -2
  32. zrb/builtin/project/add/fastapp_template/module/auth/client/direct_client.py +2 -2
  33. zrb/builtin/project/add/fastapp_template/module/auth/client/factory.py +1 -1
  34. zrb/builtin/project/add/fastapp_template/module/auth/route.py +1 -1
  35. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/factory.py +2 -2
  36. zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/{db_repository.py → user_db_repository.py} +2 -2
  37. zrb/builtin/project/add/fastapp_template/module/auth/service/user/{usecase.py → user_usecase.py} +15 -8
  38. zrb/builtin/project/add/fastapp_template/requirements.txt +5 -6
  39. zrb/builtin/project/add/fastapp_template/schema/permission.py +31 -0
  40. zrb/builtin/project/add/fastapp_template/template.env +2 -2
  41. zrb/builtin/project/create/create.py +2 -2
  42. zrb/builtin/project/create/project-template/zrb_init.py +0 -4
  43. zrb/builtin/setup/{dev → asdf}/asdf.py +6 -6
  44. zrb/builtin/setup/{system/latex → latex}/ubuntu.py +1 -1
  45. zrb/builtin/setup/{dev → tmux}/tmux.py +1 -1
  46. zrb/builtin/setup/tmux/tmux_config.sh +12 -0
  47. zrb/builtin/todo.py +101 -15
  48. zrb/config.py +2 -2
  49. zrb/content_transformer/content_transformer.py +14 -2
  50. zrb/context/context.py +13 -10
  51. zrb/input/base_input.py +3 -2
  52. zrb/input/bool_input.py +1 -1
  53. zrb/input/float_input.py +1 -1
  54. zrb/input/int_input.py +1 -1
  55. zrb/input/option_input.py +1 -1
  56. zrb/input/password_input.py +1 -1
  57. zrb/input/text_input.py +1 -1
  58. zrb/runner/cli.py +16 -5
  59. zrb/runner/web_app.py +30 -18
  60. zrb/runner/web_controller/task_ui/controller.py +8 -6
  61. zrb/session/session.py +4 -1
  62. zrb/task/scaffolder.py +7 -9
  63. zrb/util/cli/style.py +7 -0
  64. zrb/util/codemod/add_code_to_module.py +12 -0
  65. zrb/util/load.py +9 -7
  66. zrb/util/string/conversion.py +52 -0
  67. zrb/util/todo.py +152 -34
  68. {zrb-1.0.0a10.dist-info → zrb-1.0.0a14.dist-info}/METADATA +1 -2
  69. {zrb-1.0.0a10.dist-info → zrb-1.0.0a14.dist-info}/RECORD +79 -55
  70. {zrb-1.0.0a10.dist-info → zrb-1.0.0a14.dist-info}/WHEEL +1 -1
  71. /zrb/builtin/project/add/fastapp_template/{module/auth/client/base_client.py → _zrb/module/module_template/client/any_client.py} +0 -0
  72. /zrb/builtin/{setup/dev/tmux_config.sh → project/add/fastapp_template/_zrb/module/module_template/service/__init__.py} +0 -0
  73. /zrb/builtin/project/add/fastapp_template/common/{db_repository.py → base_db_repository.py} +0 -0
  74. /zrb/builtin/project/add/fastapp_template/common/{usecase.py → base_usecase.py} +0 -0
  75. /zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/{repository.py → user_repository.py} +0 -0
  76. /zrb/builtin/setup/{dev → asdf}/asdf_helper.py +0 -0
  77. /zrb/builtin/setup/{dev → tmux}/tmux_helper.py +0 -0
  78. /zrb/builtin/setup/{system/ubuntu.py → ubuntu.py} +0 -0
  79. {zrb-1.0.0a10.dist-info → zrb-1.0.0a14.dist-info}/entry_points.txt +0 -0
@@ -1,22 +1,19 @@
1
1
  import os
2
2
 
3
3
  from fastapp_template._zrb.config import ACTIVATE_VENV_SCRIPT, APP_DIR
4
- from fastapp_template._zrb.group import app_group
5
4
 
6
5
  from zrb import CmdTask
7
6
 
8
7
  create_venv = CmdTask(
9
- name="create-app-name-venv",
8
+ name="create-my-app-name-venv",
10
9
  cwd=APP_DIR,
11
10
  cmd="python -m venv .venv",
12
11
  execute_condition=lambda _: not os.path.isdir(os.path.join(APP_DIR, ".venv")),
13
12
  )
14
13
 
15
14
  prepare_venv = CmdTask(
16
- name="prepare-app-name-venv",
15
+ name="prepare-my-app-name-venv",
17
16
  cmd=[ACTIVATE_VENV_SCRIPT, "pip install -r requirements.txt"],
18
17
  cwd=APP_DIR,
19
18
  )
20
19
  create_venv >> prepare_venv
21
-
22
- app_group.add_task(create_venv, alias="prepare")
@@ -13,6 +13,8 @@ async def lifespan(app: FastAPI):
13
13
 
14
14
 
15
15
  app_title = (
16
- "App Name" if APP_MODE == "monolith" else f"App Name - {', '.join(APP_MODULES)}"
16
+ "My App Name"
17
+ if APP_MODE == "monolith"
18
+ else f"My App Name - {', '.join(APP_MODULES)}"
17
19
  )
18
20
  app = FastAPI(title=app_title, lifespan=lifespan)
@@ -2,15 +2,15 @@ import os
2
2
 
3
3
  APP_PATH = os.path.dirname(__file__)
4
4
 
5
- APP_MODE = os.getenv("APP_NAME_MODE", "monolith")
5
+ APP_MODE = os.getenv("MY_APP_NAME_MODE", "monolith")
6
6
  APP_MODULES = [
7
7
  module.strip()
8
- for module in os.getenv("APP_NAME_MODULES", "").split(",")
8
+ for module in os.getenv("MY_APP_NAME_MODULES", "").split(",")
9
9
  if module.strip() != ""
10
10
  ]
11
- APP_PORT = int(os.getenv("APP_NAME_PORT", "3000"))
11
+ APP_PORT = int(os.getenv("MY_APP_NAME_PORT", "3000"))
12
12
  APP_COMMUNICATION = os.getenv(
13
- "APP_NAME_COMMUNICATION", "direct" if APP_MODE == "monolith" else "api"
13
+ "MY_APP_NAME_COMMUNICATION", "direct" if APP_MODE == "monolith" else "api"
14
14
  )
15
15
  APP_REPOSITORY_TYPE = os.getenv("APP_REPOSITORY_TYPE", "db")
16
16
  APP_DB_URL = os.getenv(
@@ -21,9 +21,9 @@ APP_DB_URL = os.getenv(
21
21
  else f"sqlite:///{APP_PATH}/{APP_MODULES[0]}_microservices.db"
22
22
  ),
23
23
  )
24
- APP_AUTH_SUPER_USER = os.getenv("APP_NAME_AUTH_SUPER_USER", "admin")
24
+ APP_AUTH_SUPER_USER = os.getenv("MY_APP_NAME_AUTH_SUPER_USER", "admin")
25
25
  APP_AUTH_SUPER_USER_PASSWORD = os.getenv(
26
- "APP_NAME_AUTH_SUPER_USER_PASSWORD", "secure-password"
26
+ "MY_APP_NAME_AUTH_SUPER_USER_PASSWORD", "my-secure-password"
27
27
  )
28
28
 
29
- APP_AUTH_BASE_URL = os.getenv("APP_NAME_AUTH_BASE_URL", "http://localhost:3001")
29
+ APP_AUTH_BASE_URL = os.getenv("MY_APP_NAME_AUTH_BASE_URL", "http://localhost:3001")
@@ -0,0 +1,27 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from fastapp_template.schema.user import UserCreate, UserResponse, UserUpdate
4
+
5
+
6
+ class BaseClient(ABC):
7
+ @abstractmethod
8
+ async def get_user_by_id(self, user_id: str) -> UserResponse:
9
+ pass
10
+
11
+ @abstractmethod
12
+ async def get_all_users(self) -> list[UserResponse]:
13
+ pass
14
+
15
+ @abstractmethod
16
+ async def create_user(
17
+ self, data: UserCreate | list[UserCreate]
18
+ ) -> UserResponse | list[UserResponse]:
19
+ pass
20
+
21
+ @abstractmethod
22
+ async def update_user(self, user_id: str, data: UserUpdate) -> UserResponse:
23
+ pass
24
+
25
+ @abstractmethod
26
+ async def delete_user(self, user_id: str) -> UserResponse:
27
+ pass
@@ -1,6 +1,6 @@
1
1
  from fastapp_template.config import APP_AUTH_BASE_URL
2
- from fastapp_template.module.auth.client.base_client import BaseClient
3
- from fastapp_template.module.auth.service.user.usecase import user_usecase
2
+ from fastapp_template.module.auth.client.any_client import BaseClient
3
+ from fastapp_template.module.auth.service.user.user_usecase import user_usecase
4
4
 
5
5
 
6
6
  class APIClient(user_usecase.as_api_client(base_url=APP_AUTH_BASE_URL), BaseClient):
@@ -1,5 +1,5 @@
1
- from fastapp_template.module.auth.client.base_client import BaseClient
2
- from fastapp_template.module.auth.service.user.usecase import user_usecase
1
+ from fastapp_template.module.auth.client.any_client import BaseClient
2
+ from fastapp_template.module.auth.service.user.user_usecase import user_usecase
3
3
 
4
4
 
5
5
  class DirectClient(user_usecase.as_direct_client(), BaseClient):
@@ -1,6 +1,6 @@
1
1
  from fastapp_template.config import APP_COMMUNICATION
2
+ from fastapp_template.module.auth.client.any_client import BaseClient
2
3
  from fastapp_template.module.auth.client.api_client import APIClient
3
- from fastapp_template.module.auth.client.base_client import BaseClient
4
4
  from fastapp_template.module.auth.client.direct_client import DirectClient
5
5
 
6
6
  if APP_COMMUNICATION == "direct":
@@ -1,7 +1,7 @@
1
1
  from fastapp_template.common.app import app
2
2
  from fastapp_template.common.schema import BasicResponse
3
3
  from fastapp_template.config import APP_MODE, APP_MODULES
4
- from fastapp_template.module.auth.service.user.usecase import user_usecase
4
+ from fastapp_template.module.auth.service.user.user_usecase import user_usecase
5
5
 
6
6
  if APP_MODE == "microservices" and "auth" in APP_MODULES:
7
7
 
@@ -1,9 +1,9 @@
1
1
  from fastapp_template.common.db_engine import engine
2
2
  from fastapp_template.config import APP_REPOSITORY_TYPE
3
- from fastapp_template.module.auth.service.user.repository.db_repository import (
3
+ from fastapp_template.module.auth.service.user.repository.user_db_repository import (
4
4
  UserDBRepository,
5
5
  )
6
- from fastapp_template.module.auth.service.user.repository.repository import (
6
+ from fastapp_template.module.auth.service.user.repository.user_repository import (
7
7
  UserRepository,
8
8
  )
9
9
 
@@ -1,6 +1,6 @@
1
- from fastapp_template.common.db_repository import BaseDBRepository
1
+ from fastapp_template.common.base_db_repository import BaseDBRepository
2
2
  from fastapp_template.common.error import NotFoundError
3
- from fastapp_template.module.auth.service.user.repository.repository import (
3
+ from fastapp_template.module.auth.service.user.repository.user_repository import (
4
4
  UserRepository,
5
5
  )
6
6
  from fastapp_template.schema.user import User, UserCreate, UserResponse, UserUpdate
@@ -1,21 +1,28 @@
1
- from fastapp_template.common.usecase import BaseUsecase
1
+ from fastapp_template.common.base_usecase import BaseUsecase
2
2
  from fastapp_template.module.auth.service.user.repository.factory import user_repository
3
+ from fastapp_template.module.auth.service.user.repository.user_repository import (
4
+ UserRepository,
5
+ )
3
6
  from fastapp_template.schema.user import UserCreate, UserResponse, UserUpdate
4
7
 
5
8
 
6
9
  class UserUsecase(BaseUsecase):
7
10
 
11
+ def __init__(self, user_repository: UserRepository):
12
+ super().__init__()
13
+ self.user_repository = user_repository
14
+
8
15
  @BaseUsecase.route(
9
16
  "/api/v1/users/{user_id}", methods=["get"], response_model=UserResponse
10
17
  )
11
18
  async def get_user_by_id(self, user_id: str) -> UserResponse:
12
- return await user_repository.get_by_id(user_id)
19
+ return await self.user_repository.get_by_id(user_id)
13
20
 
14
21
  @BaseUsecase.route(
15
22
  "/api/v1/users", methods=["get"], response_model=list[UserResponse]
16
23
  )
17
24
  async def get_all_users(self) -> list[UserResponse]:
18
- return await user_repository.get_all()
25
+ return await self.user_repository.get_all()
19
26
 
20
27
  @BaseUsecase.route(
21
28
  "/api/v1/users",
@@ -26,20 +33,20 @@ class UserUsecase(BaseUsecase):
26
33
  self, data: UserCreate | list[UserCreate]
27
34
  ) -> UserResponse | list[UserResponse]:
28
35
  if isinstance(data, UserCreate):
29
- return await user_repository.create(data)
30
- return await user_repository.create_bulk(data)
36
+ return await self.user_repository.create(data)
37
+ return await self.user_repository.create_bulk(data)
31
38
 
32
39
  @BaseUsecase.route(
33
40
  "/api/v1/users/{user_id}", methods=["put"], response_model=UserResponse
34
41
  )
35
42
  async def update_user(self, user_id: str, data: UserUpdate) -> UserResponse:
36
- return await user_repository.update(user_id, data)
43
+ return await self.user_repository.update(user_id, data)
37
44
 
38
45
  @BaseUsecase.route(
39
46
  "/api/v1/users/{user_id}", methods=["delete"], response_model=UserResponse
40
47
  )
41
48
  async def delete_user(self, user_id: str) -> UserResponse:
42
- return await user_repository.delete(user_id)
49
+ return await self.user_repository.delete(user_id)
43
50
 
44
51
 
45
- user_usecase = UserUsecase()
52
+ user_usecase = UserUsecase(user_repository=user_repository)
@@ -1,6 +1,5 @@
1
- fastapi[standard]==0.115.4
2
- aiosqlite==0.20.0
3
- alembic==1.14.0
4
- sqlmodel==0.0.22
5
- ulid-py==1.1.0
6
- passlib==1.7.4
1
+ fastapi[standard]~=0.115.5
2
+ alembic~=1.14.0
3
+ sqlmodel~=0.0.22
4
+ ulid-py~=1.1.0
5
+ passlib~=1.7.4
@@ -0,0 +1,31 @@
1
+ import datetime
2
+
3
+ import ulid
4
+ from sqlmodel import Field, SQLModel
5
+
6
+
7
+ class PermissionBase(SQLModel):
8
+ name: str
9
+
10
+
11
+ class PermissionCreate(PermissionBase):
12
+ description: str
13
+
14
+
15
+ class PermissionUpdate(SQLModel):
16
+ name: str | None = None
17
+ description: str | None = None
18
+
19
+
20
+ class PermissionResponse(PermissionBase):
21
+ id: str
22
+
23
+
24
+ class Permission(SQLModel, table=True):
25
+ id: str = Field(default_factory=lambda: ulid.new().str, primary_key=True)
26
+ created_at: datetime.datetime | None
27
+ created_by: str | None
28
+ updated_at: datetime.datetime | None
29
+ updated_by: str | None
30
+ name: str
31
+ description: str
@@ -1,2 +1,2 @@
1
- export APP_NAME_MODE=monolith
2
- export APP_NAME_PORT=3000
1
+ export MY_APP_NAME_MODE=monolith
2
+ export MY_APP_NAME_PORT=3000
@@ -18,7 +18,7 @@ scaffold_project = Scaffolder(
18
18
  default_str=lambda _: os.getcwd(),
19
19
  ),
20
20
  StrInput(
21
- name="project-name",
21
+ name="project",
22
22
  description="Project name",
23
23
  prompt="Project name",
24
24
  default_str=lambda ctx: os.path.basename(ctx.input["project-dir"]),
@@ -27,7 +27,7 @@ scaffold_project = Scaffolder(
27
27
  source_path=os.path.join(_DIR, "project-template"),
28
28
  render_source_path=False,
29
29
  destination_path="{ctx.input['project-dir']}",
30
- transform_content={"Project Name": "{ctx.input['project-name'].title()}"},
30
+ transform_content={"Project Name": "{ctx.input.project.title()}"},
31
31
  retries=0,
32
32
  )
33
33
 
@@ -1,7 +1,3 @@
1
1
  import os
2
2
 
3
- from zrb import load_file
4
-
5
3
  _DIR = os.path.dirname(__file__)
6
-
7
- assert load_file
@@ -1,6 +1,12 @@
1
1
  import os
2
2
 
3
3
  from zrb.builtin.group import setup_group
4
+ from zrb.builtin.setup.asdf.asdf_helper import (
5
+ check_inexist_asdf_dir,
6
+ get_install_prerequisites_cmd,
7
+ setup_asdf_ps_config,
8
+ setup_asdf_sh_config,
9
+ )
4
10
  from zrb.builtin.setup.common_input import (
5
11
  package_manager_input,
6
12
  setup_bash_input,
@@ -8,12 +14,6 @@ from zrb.builtin.setup.common_input import (
8
14
  setup_zsh_input,
9
15
  use_sudo_input,
10
16
  )
11
- from zrb.builtin.setup.dev.asdf_helper import (
12
- check_inexist_asdf_dir,
13
- get_install_prerequisites_cmd,
14
- setup_asdf_ps_config,
15
- setup_asdf_sh_config,
16
- )
17
17
  from zrb.context.any_context import AnyContext
18
18
  from zrb.task.cmd_task import CmdTask
19
19
  from zrb.task.make_task import make_task
@@ -1,5 +1,5 @@
1
1
  from zrb.builtin.group import setup_latex_group
2
- from zrb.builtin.setup.system.ubuntu import setup_ubuntu
2
+ from zrb.builtin.setup.ubuntu import setup_ubuntu
3
3
  from zrb.task.cmd_task import CmdTask
4
4
 
5
5
  setup_latex_on_ubuntu = setup_latex_group.add_task(
@@ -2,7 +2,7 @@ import os
2
2
 
3
3
  from zrb.builtin.group import setup_group
4
4
  from zrb.builtin.setup.common_input import package_manager_input, use_sudo_input
5
- from zrb.builtin.setup.dev.tmux_helper import get_install_tmux_cmd
5
+ from zrb.builtin.setup.tmux.tmux_helper import get_install_tmux_cmd
6
6
  from zrb.context.any_context import AnyContext
7
7
  from zrb.input.str_input import StrInput
8
8
  from zrb.task.cmd_task import CmdTask
@@ -0,0 +1,12 @@
1
+ set -g @plugin 'tmux-plugins/tpm'
2
+ set -g @plugin 'tmux-plugins/tmux-sensible'
3
+ run '~/.tmux/plugins/tpm/tpm'
4
+
5
+ set-option -sg escape-time 10
6
+ set-option -g focus-events On
7
+ set-option -g default-terminal "screen-256color"
8
+ set-option -sa terminal-overrides ',xterm-256color:RGB'
9
+
10
+ bind c new-window -c "#{pane_current_path}"
11
+ bind '"' split-window -c "#{pane_current_path}"
12
+ bind % split-window -h -c "#{pane_current_path}"
zrb/builtin/todo.py CHANGED
@@ -4,7 +4,7 @@ import os
4
4
  from typing import Any
5
5
 
6
6
  from zrb.builtin.group import todo_group
7
- from zrb.config import TODO_DIR
7
+ from zrb.config import TODO_DIR, TODO_VISUAL_FILTER
8
8
  from zrb.context.any_context import AnyContext
9
9
  from zrb.input.str_input import StrInput
10
10
  from zrb.input.text_input import TextInput
@@ -13,6 +13,7 @@ from zrb.util.todo import (
13
13
  TodoTaskModel,
14
14
  add_durations,
15
15
  cascade_todo_task,
16
+ get_visual_todo_card,
16
17
  get_visual_todo_list,
17
18
  line_to_todo_task,
18
19
  load_todo_list,
@@ -23,7 +24,7 @@ from zrb.util.todo import (
23
24
 
24
25
 
25
26
  @make_task(
26
- name="todo-add",
27
+ name="add-todo",
27
28
  input=[
28
29
  StrInput(
29
30
  name="description",
@@ -40,11 +41,13 @@ from zrb.util.todo import (
40
41
  name="project",
41
42
  description="Task project",
42
43
  prompt="Task project (space separated)",
44
+ allow_empty=True,
43
45
  ),
44
46
  StrInput(
45
47
  name="context",
46
48
  description="Task context",
47
49
  prompt="Task context (space separated)",
50
+ allow_empty=True,
48
51
  ),
49
52
  ],
50
53
  description="➕ Add todo",
@@ -77,20 +80,52 @@ def add_todo(ctx: AnyContext):
77
80
  )
78
81
  )
79
82
  save_todo_list(todo_file_path, todo_list)
80
- return get_visual_todo_list(todo_list)
83
+ return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
81
84
 
82
85
 
83
- @make_task(name="todo-list", description="📋 List todo", group=todo_group, alias="list")
86
+ @make_task(name="list-todo", description="📋 List todo", group=todo_group, alias="list")
84
87
  def list_todo(ctx: AnyContext):
85
88
  todo_file_path = os.path.join(TODO_DIR, "todo.txt")
86
- todo_tasks: list[TodoTaskModel] = []
89
+ todo_list: list[TodoTaskModel] = []
87
90
  if os.path.isfile(todo_file_path):
88
- todo_tasks = load_todo_list(todo_file_path)
89
- return get_visual_todo_list(todo_tasks)
91
+ todo_list = load_todo_list(todo_file_path)
92
+ return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
90
93
 
91
94
 
92
95
  @make_task(
93
- name="todo-complete",
96
+ name="show-todo",
97
+ input=StrInput(name="keyword", prompt="Task keyword", description="Task Keyword"),
98
+ description="🔍 Show todo",
99
+ group=todo_group,
100
+ alias="show",
101
+ )
102
+ def show_todo(ctx: AnyContext):
103
+ todo_file_path = os.path.join(TODO_DIR, "todo.txt")
104
+ todo_list: list[TodoTaskModel] = []
105
+ todo_list: list[TodoTaskModel] = []
106
+ if os.path.isfile(todo_file_path):
107
+ todo_list = load_todo_list(todo_file_path)
108
+ # Get todo task
109
+ todo_task = select_todo_task(todo_list, ctx.input.keyword)
110
+ if todo_task is None:
111
+ ctx.log_error("Task not found")
112
+ return
113
+ if todo_task.completed:
114
+ ctx.log_error("Task already completed")
115
+ return
116
+ # Update todo task
117
+ todo_task = cascade_todo_task(todo_task)
118
+ task_id = todo_task.keyval.get("id", "")
119
+ log_work_path = os.path.join(TODO_DIR, "log-work", f"{task_id}.json")
120
+ log_work_list = []
121
+ if os.path.isfile(log_work_path):
122
+ with open(log_work_path, "r") as f:
123
+ log_work_list = json.loads(f.read())
124
+ return get_visual_todo_card(todo_task, log_work_list)
125
+
126
+
127
+ @make_task(
128
+ name="complete-todo",
94
129
  input=StrInput(name="keyword", prompt="Task keyword", description="Task Keyword"),
95
130
  description="✅ Complete todo",
96
131
  group=todo_group,
@@ -105,7 +140,10 @@ def complete_todo(ctx: AnyContext):
105
140
  todo_task = select_todo_task(todo_list, ctx.input.keyword)
106
141
  if todo_task is None:
107
142
  ctx.log_error("Task not found")
108
- return get_visual_todo_list(todo_list)
143
+ return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
144
+ if todo_task.completed:
145
+ ctx.log_error("Task already completed")
146
+ return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
109
147
  # Update todo task
110
148
  todo_task = cascade_todo_task(todo_task)
111
149
  if todo_task.creation_date is not None:
@@ -113,11 +151,45 @@ def complete_todo(ctx: AnyContext):
113
151
  todo_task.completed = True
114
152
  # Save todo list
115
153
  save_todo_list(todo_file_path, todo_list)
116
- return get_visual_todo_list(todo_list)
154
+ return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
117
155
 
118
156
 
119
157
  @make_task(
120
- name="todo-log",
158
+ name="archive-todo",
159
+ description="📚 Archive todo",
160
+ group=todo_group,
161
+ alias="archive",
162
+ )
163
+ def archive_todo(ctx: AnyContext):
164
+ todo_file_path = os.path.join(TODO_DIR, "todo.txt")
165
+ todo_list: list[TodoTaskModel] = []
166
+ if os.path.isfile(todo_file_path):
167
+ todo_list = load_todo_list(todo_file_path)
168
+ working_todo_list = [
169
+ todo_task for todo_task in todo_list if not todo_task.completed
170
+ ]
171
+ new_archived_todo_list = [
172
+ todo_task for todo_task in todo_list if todo_task.completed
173
+ ]
174
+ if len(new_archived_todo_list) == 0:
175
+ ctx.print("No completed task to archive")
176
+ return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
177
+ archive_file_path = os.path.join(TODO_DIR, "archive.txt")
178
+ if not os.path.isdir(TODO_DIR):
179
+ os.make_dirs(TODO_DIR, exist_ok=True)
180
+ # Get archived todo list
181
+ archived_todo_list = []
182
+ if os.path.isfile(archive_file_path):
183
+ archived_todo_list = load_todo_list(archive_file_path)
184
+ archived_todo_list += new_archived_todo_list
185
+ # Save the new todo list and add the archived ones
186
+ save_todo_list(archive_file_path, archived_todo_list)
187
+ save_todo_list(todo_file_path, working_todo_list)
188
+ return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
189
+
190
+
191
+ @make_task(
192
+ name="log-todo",
121
193
  input=[
122
194
  StrInput(name="keyword", prompt="Task keyword", description="Task Keyword"),
123
195
  StrInput(
@@ -151,7 +223,7 @@ def log_todo(ctx: AnyContext):
151
223
  todo_task = select_todo_task(todo_list, ctx.input.keyword)
152
224
  if todo_task is None:
153
225
  ctx.log_error("Task not found")
154
- return get_visual_todo_list(todo_list)
226
+ return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
155
227
  # Update todo task
156
228
  todo_task = cascade_todo_task(todo_task)
157
229
  current_duration = todo_task.keyval.get("duration", "0")
@@ -173,9 +245,23 @@ def log_todo(ctx: AnyContext):
173
245
  log_work.append(
174
246
  {"log": ctx.input.log, "duration": ctx.input.duration, "start": ctx.input.start}
175
247
  )
248
+ # save todo with log work
176
249
  with open(log_work_file_path, "w") as f:
177
250
  f.write(json.dumps(log_work, indent=2))
178
- return get_visual_todo_list(todo_list)
251
+ # get log work list
252
+ task_id = todo_task.keyval.get("id", "")
253
+ log_work_path = os.path.join(TODO_DIR, "log-work", f"{task_id}.json")
254
+ log_work_list = []
255
+ if os.path.isfile(log_work_path):
256
+ with open(log_work_path, "r") as f:
257
+ log_work_list = json.loads(f.read())
258
+ return "\n".join(
259
+ [
260
+ get_visual_todo_list(todo_list, TODO_VISUAL_FILTER),
261
+ "",
262
+ get_visual_todo_card(todo_task, log_work_list),
263
+ ]
264
+ )
179
265
 
180
266
 
181
267
  def _get_default_start() -> str:
@@ -183,7 +269,7 @@ def _get_default_start() -> str:
183
269
 
184
270
 
185
271
  @make_task(
186
- name="todo-edit",
272
+ name="edit-todo",
187
273
  input=[
188
274
  TextInput(
189
275
  name="text",
@@ -207,7 +293,7 @@ def edit_todo(ctx: AnyContext):
207
293
  with open(todo_file_path, "w") as f:
208
294
  f.write(new_content)
209
295
  todo_list = load_todo_list(todo_file_path)
210
- return get_visual_todo_list(todo_list)
296
+ return get_visual_todo_list(todo_list, TODO_VISUAL_FILTER)
211
297
 
212
298
 
213
299
  def _get_todo_txt_content() -> str:
zrb/config.py CHANGED
@@ -47,8 +47,7 @@ INIT_SCRIPTS = (
47
47
  )
48
48
  LOGGING_LEVEL = _get_log_level(os.getenv("ZRB_LOGGING_LEVEL", "WARNING"))
49
49
  LOAD_BUILTIN = to_boolean(os.getenv("ZRB_LOAD_BUILTIN", "1"))
50
- ENV_PREFIX = os.getenv("ZRB_ENV", "")
51
- SHOW_PROMPT = to_boolean(os.getenv("ZRB_SHOW_PROMPT", "1"))
50
+ SHOW_TIME = to_boolean(os.getenv("ZRB_SHOW_TIME", "1"))
52
51
  WARN_UNRECOMMENDED_COMMAND = to_boolean(
53
52
  os.getenv("ZRB_WARN_UNRECOMMENDED_COMMAND", "1")
54
53
  )
@@ -56,6 +55,7 @@ SESSION_LOG_DIR = os.getenv(
56
55
  "ZRB_SESSION_LOG_DIR", os.path.expanduser(os.path.join("~", ".zrb-session"))
57
56
  )
58
57
  TODO_DIR = os.getenv("ZRB_TODO_DIR", os.path.expanduser(os.path.join("~", "todo")))
58
+ TODO_VISUAL_FILTER = os.getenv("ZRB_TODO_FILTER", "")
59
59
  VERSION = metadata.version("zrb")
60
60
  WEB_HTTP_PORT = int(os.getenv("ZRB_WEB_HTTP_PORT", "21213"))
61
61
  LLM_MODEL = os.getenv("ZRB_LLM_MODEL", "ollama_chat/llama3.1")
@@ -11,7 +11,10 @@ class ContentTransformer(AnyContentTransformer):
11
11
  def __init__(
12
12
  self,
13
13
  match: list[str] | str | Callable[[AnyContext, str], bool],
14
- transform: dict[str, str] | Callable[[AnyContext, str], str],
14
+ transform: (
15
+ dict[str, str | Callable[[AnyContext], str]]
16
+ | Callable[[AnyContext, str], str]
17
+ ),
15
18
  auto_render: bool = True,
16
19
  ):
17
20
  self._match = match
@@ -34,7 +37,7 @@ class ContentTransformer(AnyContentTransformer):
34
37
  if callable(self._transform_file):
35
38
  return self._transform_file(ctx, file_path)
36
39
  transform_map = {
37
- keyword: ctx.render(replacement) if self._auto_render else replacement
40
+ keyword: self._get_str_replacement(ctx, replacement)
38
41
  for keyword, replacement in self._transform_file.items()
39
42
  }
40
43
  with open(file_path, "r") as f:
@@ -43,3 +46,12 @@ class ContentTransformer(AnyContentTransformer):
43
46
  content = content.replace(keyword, replacement)
44
47
  with open(file_path, "w") as f:
45
48
  f.write(content)
49
+
50
+ def _get_str_replacement(
51
+ self, ctx: AnyContext, replacement: str | Callable[[AnyContext], str]
52
+ ) -> str:
53
+ if callable(replacement):
54
+ return replacement(ctx)
55
+ if self._auto_render:
56
+ return ctx.render(replacement)
57
+ return replacement
zrb/context/context.py CHANGED
@@ -1,22 +1,23 @@
1
1
  import datetime
2
2
  import logging
3
- import re
4
3
  import sys
5
4
  from typing import Any, TextIO
6
5
 
6
+ from zrb.config import SHOW_TIME
7
7
  from zrb.context.any_context import AnyContext
8
8
  from zrb.context.any_shared_context import AnySharedContext
9
9
  from zrb.dot_dict.dot_dict import DotDict
10
10
  from zrb.session.any_session import AnySession
11
- from zrb.util.cli.style import stylize, stylize_error, stylize_log, stylize_warning
11
+ from zrb.util.cli.style import (
12
+ remove_style,
13
+ stylize,
14
+ stylize_error,
15
+ stylize_log,
16
+ stylize_warning,
17
+ )
12
18
  from zrb.util.string.conversion import to_boolean
13
19
 
14
20
 
15
- def _remove_ansi_escape_sequences(text):
16
- ansi_escape = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]")
17
- return ansi_escape.sub("", text)
18
-
19
-
20
21
  class Context(AnyContext):
21
22
  def __init__(
22
23
  self, shared_ctx: AnySharedContext, task_name: str, color: int, icon: str
@@ -111,10 +112,12 @@ class Context(AnyContext):
111
112
  else:
112
113
  attempt_status = f"{self._attempt}/{self._max_attempt}".ljust(5)
113
114
  now = datetime.datetime.now()
114
- formatted_time = now.strftime("%y%m%d %H:%M:%S.%f")[:19]
115
- prefix = f"{formatted_time} {attempt_status} {padded_styled_task_name} "
115
+ formatted_time = (
116
+ now.strftime("%y%m%d %H:%M:%S.%f")[:19] + " " if SHOW_TIME else ""
117
+ )
118
+ prefix = f"{formatted_time}{attempt_status} {padded_styled_task_name} ⬤ "
116
119
  message = sep.join([f"{value}" for value in values])
117
- self.append_to_shared_log(_remove_ansi_escape_sequences(f"{prefix} {message}"))
120
+ self.append_to_shared_log(remove_style(f"{prefix} {message}"))
118
121
  stylized_prefix = stylize(prefix, color=color)
119
122
  print(f"{stylized_prefix} {message}", sep=sep, end=end, file=file, flush=flush)
120
123