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.
- zrb/__init__.py +48 -39
- zrb/__main__.py +3 -3
- zrb/attr/type.py +2 -1
- zrb/builtin/__init__.py +40 -2
- zrb/builtin/base64.py +32 -0
- zrb/builtin/git.py +156 -0
- zrb/builtin/git_subtree.py +88 -0
- zrb/builtin/group.py +34 -0
- zrb/builtin/llm.py +31 -0
- zrb/builtin/md5.py +34 -0
- zrb/builtin/project/__init__.py +0 -0
- zrb/builtin/project/add/__init__.py +0 -0
- zrb/builtin/project/add/fastapp.py +72 -0
- zrb/builtin/project/add/fastapp_template/.gitignore +4 -0
- zrb/builtin/project/add/fastapp_template/README.md +7 -0
- zrb/builtin/project/add/fastapp_template/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/_zrb/config.py +17 -0
- zrb/builtin/project/add/fastapp_template/_zrb/group.py +16 -0
- zrb/builtin/project/add/fastapp_template/_zrb/helper.py +97 -0
- zrb/builtin/project/add/fastapp_template/_zrb/main.py +132 -0
- zrb/builtin/project/add/fastapp_template/_zrb/venv_task.py +22 -0
- zrb/builtin/project/add/fastapp_template/common/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/common/app.py +18 -0
- zrb/builtin/project/add/fastapp_template/common/db_engine.py +5 -0
- zrb/builtin/project/add/fastapp_template/common/db_repository.py +134 -0
- zrb/builtin/project/add/fastapp_template/common/error.py +8 -0
- zrb/builtin/project/add/fastapp_template/common/schema.py +5 -0
- zrb/builtin/project/add/fastapp_template/common/usecase.py +232 -0
- zrb/builtin/project/add/fastapp_template/config.py +29 -0
- zrb/builtin/project/add/fastapp_template/main.py +7 -0
- zrb/builtin/project/add/fastapp_template/migrate.py +3 -0
- zrb/builtin/project/add/fastapp_template/module/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/alembic.ini +117 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/api_client.py +7 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/base_client.py +27 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/direct_client.py +6 -0
- zrb/builtin/project/add/fastapp_template/module/auth/client/factory.py +9 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/README +1 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/env.py +108 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/script.py.mako +26 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration/versions/3093c7336477_add_user_table.py +37 -0
- zrb/builtin/project/add/fastapp_template/module/auth/migration_metadata.py +6 -0
- zrb/builtin/project/add/fastapp_template/module/auth/route.py +22 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/db_repository.py +39 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/factory.py +13 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/repository.py +34 -0
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/usecase.py +45 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/alembic.ini +117 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/README +1 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/env.py +108 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/script.py.mako +26 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration/versions/.gitkeep +0 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/migration_metadata.py +3 -0
- zrb/builtin/project/add/fastapp_template/module/gateway/route.py +27 -0
- zrb/builtin/project/add/fastapp_template/requirements.txt +6 -0
- zrb/builtin/project/add/fastapp_template/schema/__init__.py +0 -0
- zrb/builtin/project/add/fastapp_template/schema/role.py +31 -0
- zrb/builtin/project/add/fastapp_template/schema/user.py +31 -0
- zrb/builtin/project/add/fastapp_template/template.env +2 -0
- zrb/builtin/project/create/__init__.py +0 -0
- zrb/builtin/project/create/create.py +41 -0
- zrb/builtin/project/create/project-template/README.md +3 -0
- zrb/builtin/project/create/project-template/zrb_init.py +7 -0
- zrb/builtin/python.py +11 -0
- zrb/builtin/shell/__init__.py +0 -5
- zrb/builtin/shell/autocomplete/__init__.py +0 -9
- zrb/builtin/shell/autocomplete/bash.py +5 -6
- zrb/builtin/shell/autocomplete/subcmd.py +7 -8
- zrb/builtin/shell/autocomplete/zsh.py +5 -6
- zrb/builtin/todo.py +186 -0
- zrb/callback/any_callback.py +1 -1
- zrb/callback/callback.py +5 -5
- zrb/cmd/cmd_val.py +2 -2
- zrb/config.py +4 -1
- zrb/content_transformer/any_content_transformer.py +1 -1
- zrb/content_transformer/content_transformer.py +2 -2
- zrb/context/any_context.py +1 -1
- zrb/context/any_shared_context.py +3 -3
- zrb/context/context.py +10 -8
- zrb/context/shared_context.py +9 -8
- zrb/env/__init__.py +0 -3
- zrb/env/any_env.py +1 -1
- zrb/env/env.py +3 -4
- zrb/env/env_file.py +4 -4
- zrb/env/env_map.py +2 -2
- zrb/group/__init__.py +0 -3
- zrb/group/any_group.py +3 -3
- zrb/group/group.py +7 -6
- zrb/input/any_input.py +1 -1
- zrb/input/base_input.py +4 -4
- zrb/input/bool_input.py +5 -5
- zrb/input/float_input.py +3 -3
- zrb/input/int_input.py +3 -3
- zrb/input/option_input.py +51 -0
- zrb/input/password_input.py +2 -2
- zrb/input/str_input.py +1 -1
- zrb/input/text_input.py +12 -10
- zrb/runner/cli.py +79 -45
- zrb/runner/web_app/group_info_ui/controller.py +7 -8
- zrb/runner/web_app/group_info_ui/view.html +2 -2
- zrb/runner/web_app/home_page/controller.py +7 -6
- zrb/runner/web_app/home_page/view.html +2 -2
- zrb/runner/web_app/task_ui/controller.py +8 -12
- zrb/runner/web_app/task_ui/view.html +2 -2
- zrb/runner/web_server.py +137 -211
- zrb/runner/web_util.py +5 -35
- zrb/session/any_session.py +13 -7
- zrb/session/session.py +78 -40
- zrb/session_state_log/session_state_log.py +7 -5
- zrb/session_state_logger/any_session_state_logger.py +1 -1
- zrb/session_state_logger/default_session_state_logger.py +2 -2
- zrb/session_state_logger/file_session_state_logger.py +19 -27
- zrb/task/any_task.py +4 -4
- zrb/task/base_task.py +33 -23
- zrb/task/base_trigger.py +11 -12
- zrb/task/cmd_task.py +48 -39
- zrb/task/http_check.py +8 -8
- zrb/task/llm_task.py +160 -0
- zrb/task/make_task.py +9 -9
- zrb/task/rsync_task.py +7 -7
- zrb/task/scaffolder.py +14 -11
- zrb/task/scheduler.py +6 -7
- zrb/task/task.py +1 -1
- zrb/task/tcp_check.py +8 -8
- zrb/util/attr.py +19 -3
- zrb/util/cli/style.py +71 -2
- zrb/util/cli/subcommand.py +2 -2
- zrb/util/codemod/__init__.py +0 -0
- zrb/util/codemod/add_code_to_class.py +35 -0
- zrb/util/codemod/add_code_to_function.py +36 -0
- zrb/util/codemod/add_code_to_method.py +55 -0
- zrb/util/codemod/add_key_to_dict.py +51 -0
- zrb/util/codemod/add_param_to_function_call.py +39 -0
- zrb/util/codemod/add_property_to_class.py +55 -0
- zrb/util/git.py +156 -0
- zrb/util/git_subtree.py +94 -0
- zrb/util/group.py +2 -2
- zrb/util/llm/tool.py +63 -0
- zrb/util/string/conversion.py +7 -0
- zrb/util/todo.py +135 -0
- {zrb-1.0.0a2.dist-info → zrb-1.0.0a3.dist-info}/METADATA +8 -5
- zrb-1.0.0a3.dist-info/RECORD +194 -0
- zrb/builtin/shell/_group.py +0 -9
- zrb/builtin/shell/autocomplete/_group.py +0 -6
- zrb/runner/web_app/any_request_handler.py +0 -24
- zrb-1.0.0a2.dist-info/RECORD +0 -120
- {zrb-1.0.0a2.dist-info → zrb-1.0.0a3.dist-info}/WHEEL +0 -0
- {zrb-1.0.0a2.dist-info → zrb-1.0.0a3.dist-info}/entry_points.txt +0 -0
zrb/runner/cli.py
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
import sys
|
2
2
|
from typing import Any
|
3
3
|
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
13
|
-
|
4
|
+
from zrb.config import BANNER, WEB_HTTP_PORT
|
5
|
+
from zrb.context.any_context import AnyContext
|
6
|
+
from zrb.context.shared_context import SharedContext
|
7
|
+
from zrb.group.group import Group
|
8
|
+
from zrb.runner.web_server import create_app, run_web_server
|
9
|
+
from zrb.session.session import Session
|
10
|
+
from zrb.task.any_task import AnyTask
|
11
|
+
from zrb.task.make_task import make_task
|
12
|
+
from zrb.util.cli.style import (
|
13
|
+
stylize_bold_yellow,
|
14
|
+
stylize_faint,
|
15
|
+
stylize_section_header,
|
16
|
+
)
|
17
|
+
from zrb.util.group import extract_node_from_args, get_non_empty_subgroups, get_subtasks
|
18
|
+
from zrb.util.load import load_zrb_init
|
19
|
+
from zrb.util.string.conversion import double_quote
|
14
20
|
|
15
21
|
|
16
22
|
class Cli(Group):
|
@@ -25,12 +31,15 @@ class Cli(Group):
|
|
25
31
|
if "h" in kwargs or "help" in kwargs:
|
26
32
|
self._show_task_info(node)
|
27
33
|
return
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
run_kwargs = self._get_run_kwargs(node, args, kwargs)
|
35
|
+
try:
|
36
|
+
result = self._run_task(node, args, run_kwargs)
|
37
|
+
if result is not None:
|
38
|
+
print(result)
|
39
|
+
return result
|
40
|
+
finally:
|
41
|
+
run_command = self._get_run_command(node_path, run_kwargs)
|
42
|
+
self._print_run_command(run_command)
|
34
43
|
|
35
44
|
def _print_run_command(self, run_command: str):
|
36
45
|
print(
|
@@ -39,34 +48,51 @@ class Cli(Group):
|
|
39
48
|
file=sys.stderr,
|
40
49
|
)
|
41
50
|
|
42
|
-
def _get_run_command(
|
43
|
-
self, node_path: list[str], kwargs: dict[str, Any], args: list[str]
|
44
|
-
) -> str:
|
51
|
+
def _get_run_command(self, node_path: list[str], run_kwargs: dict[str, str]) -> str:
|
45
52
|
parts = [self.name] + node_path
|
46
|
-
if len(
|
47
|
-
parts += [
|
48
|
-
|
49
|
-
|
53
|
+
if len(run_kwargs) > 0:
|
54
|
+
parts += [
|
55
|
+
self._get_run_command_param(key, val) for key, val in run_kwargs.items()
|
56
|
+
]
|
50
57
|
return " ".join(parts)
|
51
58
|
|
52
|
-
def
|
59
|
+
def _get_run_command_param(self, key: str, val: str) -> str:
|
60
|
+
if '"' in val or "'" in val or " " in val:
|
61
|
+
return f"--{key} {double_quote(val)}"
|
62
|
+
return f"--{key} {val}"
|
63
|
+
|
64
|
+
def _run_task(
|
65
|
+
self, task: AnyTask, args: list[str], run_kwargs: dict[str, str]
|
66
|
+
) -> tuple[Any]:
|
67
|
+
shared_ctx = SharedContext(args=args)
|
68
|
+
for task_input in task.inputs:
|
69
|
+
if task_input.name in run_kwargs:
|
70
|
+
task_input.update_shared_context(
|
71
|
+
shared_ctx, run_kwargs[task_input.name]
|
72
|
+
)
|
73
|
+
continue
|
74
|
+
try:
|
75
|
+
return task.run(Session(shared_ctx=shared_ctx, root_group=self))
|
76
|
+
except KeyboardInterrupt:
|
77
|
+
pass
|
78
|
+
|
79
|
+
def _get_run_kwargs(
|
80
|
+
self, task: AnyTask, args: list[str], kwargs: dict[str, str]
|
81
|
+
) -> tuple[Any]:
|
53
82
|
arg_index = 0
|
54
|
-
str_kwargs = {key: val for key, val in
|
83
|
+
str_kwargs = {key: val for key, val in kwargs.items()}
|
84
|
+
run_kwargs = {**str_kwargs}
|
55
85
|
shared_ctx = SharedContext(args=args)
|
56
86
|
for task_input in task.inputs:
|
57
87
|
if task_input.name in str_kwargs:
|
58
88
|
continue
|
59
89
|
if arg_index < len(args):
|
60
|
-
|
90
|
+
run_kwargs[task_input.name] = args[arg_index]
|
61
91
|
arg_index += 1
|
62
92
|
continue
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
Session(shared_ctx=shared_ctx, root_group=self), str_kwargs=str_kwargs
|
67
|
-
)
|
68
|
-
except KeyboardInterrupt:
|
69
|
-
pass
|
93
|
+
str_value = task_input.prompt_cli_str(shared_ctx)
|
94
|
+
run_kwargs[task_input.name] = str_value
|
95
|
+
return run_kwargs
|
70
96
|
|
71
97
|
def _show_task_info(self, task: AnyTask):
|
72
98
|
description = task.description
|
@@ -77,8 +103,9 @@ class Cli(Group):
|
|
77
103
|
print()
|
78
104
|
if len(inputs) > 0:
|
79
105
|
print(stylize_section_header("INPUTS"))
|
106
|
+
max_input_name_length = max(len(task_input.name) for task_input in inputs)
|
80
107
|
for task_input in inputs:
|
81
|
-
task_input_name = task_input.name.ljust(
|
108
|
+
task_input_name = task_input.name.ljust(max_input_name_length + 1)
|
82
109
|
print(f" --{task_input_name}: {task_input.description}")
|
83
110
|
print()
|
84
111
|
|
@@ -93,15 +120,17 @@ class Cli(Group):
|
|
93
120
|
subgroups = get_non_empty_subgroups(group)
|
94
121
|
if len(subgroups) > 0:
|
95
122
|
print(stylize_section_header("GROUPS"))
|
123
|
+
max_subgroup_alias_length = max(len(s) for s in subgroups)
|
96
124
|
for alias, subgroup in subgroups.items():
|
97
|
-
alias = alias.ljust(
|
125
|
+
alias = alias.ljust(max_subgroup_alias_length + 1)
|
98
126
|
print(f" {alias}: {subgroup.description}")
|
99
127
|
print()
|
100
128
|
subtasks = get_subtasks(group)
|
101
129
|
if len(subtasks) > 0:
|
102
130
|
print(stylize_section_header("TASKS"))
|
131
|
+
max_subtask_alias_length = max(len(s) for s in subtasks)
|
103
132
|
for alias, subtask in subtasks.items():
|
104
|
-
alias = alias.ljust(
|
133
|
+
alias = alias.ljust(max_subtask_alias_length + 1)
|
105
134
|
print(f" {alias}: {subtask.description}")
|
106
135
|
print()
|
107
136
|
|
@@ -139,14 +168,19 @@ class Cli(Group):
|
|
139
168
|
|
140
169
|
|
141
170
|
cli = Cli(name="zrb", description="Your Automation Powerhouse", banner=BANNER)
|
142
|
-
|
143
|
-
server
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
171
|
+
server_group = cli.add_group(
|
172
|
+
Group(name="server", description="🌐 Server related command")
|
173
|
+
)
|
174
|
+
|
175
|
+
|
176
|
+
@make_task(
|
177
|
+
name="start-server",
|
178
|
+
description="🚀 Start Zrb Web Server",
|
179
|
+
cli_only=True,
|
180
|
+
retries=0,
|
181
|
+
group=server_group,
|
151
182
|
alias="start",
|
152
183
|
)
|
184
|
+
async def run(_: AnyContext):
|
185
|
+
app = create_app(cli, WEB_HTTP_PORT)
|
186
|
+
await run_web_server(app, WEB_HTTP_PORT)
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import os
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from
|
6
|
-
from
|
3
|
+
from fastapi.responses import HTMLResponse
|
4
|
+
|
5
|
+
from zrb.group.any_group import AnyGroup
|
6
|
+
from zrb.util.group import get_non_empty_subgroups, get_subtasks
|
7
|
+
from zrb.util.string.format import fstring_format
|
7
8
|
|
8
9
|
_DIR = os.path.dirname(__file__)
|
9
10
|
|
@@ -23,9 +24,7 @@ with open(os.path.join(_DIR, "partial", "task_li.html")) as f:
|
|
23
24
|
_TASK_LI_TEMPLATE = f.read()
|
24
25
|
|
25
26
|
|
26
|
-
def handle_group_info_ui(
|
27
|
-
handler: AnyRequestHandler, root_group: AnyGroup, group: AnyGroup, url: str
|
28
|
-
):
|
27
|
+
def handle_group_info_ui(root_group: AnyGroup, group: AnyGroup, url: str):
|
29
28
|
url_parts = url.split("/")
|
30
29
|
parent_url_parts = url_parts[:-2] + [""]
|
31
30
|
parent_url = "/".join(parent_url_parts)
|
@@ -75,7 +74,7 @@ def handle_group_info_ui(
|
|
75
74
|
},
|
76
75
|
)
|
77
76
|
)
|
78
|
-
|
77
|
+
return HTMLResponse(
|
79
78
|
fstring_format(
|
80
79
|
_VIEW_TEMPLATE,
|
81
80
|
{
|
@@ -4,8 +4,8 @@
|
|
4
4
|
<meta charset="utf-8">
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
6
|
<meta name="color-scheme" content="light dark">
|
7
|
-
<link rel="stylesheet" href="/pico.min.css">
|
8
|
-
<link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png">
|
7
|
+
<link rel="stylesheet" href="/static/pico.min.css">
|
8
|
+
<link rel="icon" href="/static/favicon-32x32.png" sizes="32x32" type="image/png">
|
9
9
|
<title>Zrb</title>
|
10
10
|
</head>
|
11
11
|
<body>
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import os
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from
|
6
|
-
from
|
3
|
+
from fastapi.responses import HTMLResponse
|
4
|
+
|
5
|
+
from zrb.group.any_group import AnyGroup
|
6
|
+
from zrb.util.group import get_non_empty_subgroups, get_subtasks
|
7
|
+
from zrb.util.string.format import fstring_format
|
7
8
|
|
8
9
|
_DIR = os.path.dirname(__file__)
|
9
10
|
|
@@ -23,7 +24,7 @@ with open(os.path.join(_DIR, "partial", "task_li.html")) as f:
|
|
23
24
|
_TASK_LI_TEMPLATE = f.read()
|
24
25
|
|
25
26
|
|
26
|
-
def handle_home_page(
|
27
|
+
def handle_home_page(root_group: AnyGroup):
|
27
28
|
subgroups = get_non_empty_subgroups(root_group, web_only=True)
|
28
29
|
group_info = (
|
29
30
|
""
|
@@ -62,7 +63,7 @@ def handle_home_page(handler: AnyRequestHandler, root_group: AnyGroup):
|
|
62
63
|
},
|
63
64
|
)
|
64
65
|
)
|
65
|
-
|
66
|
+
return HTMLResponse(
|
66
67
|
fstring_format(
|
67
68
|
_VIEW_TEMPLATE,
|
68
69
|
{
|
@@ -4,8 +4,8 @@
|
|
4
4
|
<meta charset="utf-8">
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
6
|
<meta name="color-scheme" content="light dark">
|
7
|
-
<link rel="stylesheet" href="/pico.min.css">
|
8
|
-
<link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png">
|
7
|
+
<link rel="stylesheet" href="/static/pico.min.css">
|
8
|
+
<link rel="icon" href="/static/favicon-32x32.png" sizes="32x32" type="image/png">
|
9
9
|
<title>Zrb</title>
|
10
10
|
</head>
|
11
11
|
<body>
|
@@ -1,10 +1,11 @@
|
|
1
1
|
import os
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
3
|
+
from fastapi.responses import HTMLResponse
|
4
|
+
|
5
|
+
from zrb.group.any_group import AnyGroup
|
6
|
+
from zrb.session.any_session import AnySession
|
7
|
+
from zrb.task.any_task import AnyTask
|
8
|
+
from zrb.util.string.format import fstring_format
|
8
9
|
|
9
10
|
_DIR = os.path.dirname(__file__)
|
10
11
|
|
@@ -28,12 +29,7 @@ with open(os.path.join(_DIR, "partial", "common-util.js")) as f:
|
|
28
29
|
|
29
30
|
|
30
31
|
def handle_task_ui(
|
31
|
-
|
32
|
-
root_group: AnyGroup,
|
33
|
-
task: AnyTask,
|
34
|
-
session: AnySession,
|
35
|
-
url: str,
|
36
|
-
args: list[str],
|
32
|
+
root_group: AnyGroup, task: AnyTask, session: AnySession, url: str, args: list[str]
|
37
33
|
):
|
38
34
|
session.register_task(task)
|
39
35
|
ctx = task.get_ctx(session)
|
@@ -56,7 +52,7 @@ def handle_task_ui(
|
|
56
52
|
]
|
57
53
|
)
|
58
54
|
session_name = args[0] if len(args) > 0 else ""
|
59
|
-
|
55
|
+
return HTMLResponse(
|
60
56
|
fstring_format(
|
61
57
|
_VIEW_TEMPLATE,
|
62
58
|
{
|
@@ -4,8 +4,8 @@
|
|
4
4
|
<meta charset="utf-8">
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
6
|
<meta name="color-scheme" content="light dark">
|
7
|
-
<link rel="stylesheet" href="/pico.min.css">
|
8
|
-
<link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png">
|
7
|
+
<link rel="stylesheet" href="/static/pico.min.css">
|
8
|
+
<link rel="icon" href="/static/favicon-32x32.png" sizes="32x32" type="image/png">
|
9
9
|
<title>Zrb</title>
|
10
10
|
</head>
|
11
11
|
<body>
|