esclak 0.0.5__tar.gz
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.
- esclak-0.0.5/.gitignore +44 -0
- esclak-0.0.5/PKG-INFO +171 -0
- esclak-0.0.5/README.md +162 -0
- esclak-0.0.5/pyproject.toml +34 -0
- esclak-0.0.5/src/clak/__init__.py +3 -0
- esclak-0.0.5/src/clak/__main__.py +4 -0
- esclak-0.0.5/src/clak/cli.py +60 -0
- esclak-0.0.5/src/clak/commands/__init__.py +1 -0
- esclak-0.0.5/src/clak/commands/_registry.py +40 -0
- esclak-0.0.5/src/clak/commands/clear.py +21 -0
- esclak-0.0.5/src/clak/commands/quit.py +17 -0
- esclak-0.0.5/src/clak/commands/status.py +21 -0
- esclak-0.0.5/src/clak/commands/types.py +71 -0
- esclak-0.0.5/src/clak/commands/version.py +22 -0
- esclak-0.0.5/src/clak/runtime/__init__.py +0 -0
- esclak-0.0.5/src/clak/runtime/ask/__init__.py +12 -0
- esclak-0.0.5/src/clak/runtime/ask/answer.py +34 -0
- esclak-0.0.5/src/clak/runtime/ask/request.py +25 -0
- esclak-0.0.5/src/clak/runtime/headless.py +101 -0
- esclak-0.0.5/src/clak/runtime/params.py +48 -0
- esclak-0.0.5/src/clak/runtime/session.py +113 -0
- esclak-0.0.5/src/clak/runtime/slash.py +72 -0
- esclak-0.0.5/src/clak/runtime/step_list.py +146 -0
- esclak-0.0.5/src/clak/scripts/__init__.py +5 -0
- esclak-0.0.5/src/clak/scripts/_registry.py +63 -0
- esclak-0.0.5/src/clak/scripts/pack_game_res.py +103 -0
- esclak-0.0.5/src/clak/scripts/select_server.py +75 -0
- esclak-0.0.5/src/clak/tui/__init__.py +0 -0
- esclak-0.0.5/src/clak/tui/app.py +155 -0
- esclak-0.0.5/src/clak/tui/event.py +28 -0
- esclak-0.0.5/src/clak/tui/screen.py +130 -0
- esclak-0.0.5/src/clak/tui/state.py +425 -0
- esclak-0.0.5/src/clak/tui/theme.py +10 -0
- esclak-0.0.5/src/clak/tui/views/__init__.py +0 -0
- esclak-0.0.5/src/clak/tui/views/ask.py +324 -0
- esclak-0.0.5/src/clak/tui/views/ask_controller.py +193 -0
- esclak-0.0.5/src/clak/tui/views/command_menu.py +58 -0
- esclak-0.0.5/src/clak/tui/views/context.py +22 -0
- esclak-0.0.5/src/clak/tui/views/history.py +108 -0
- esclak-0.0.5/src/clak/tui/views/input.py +233 -0
esclak-0.0.5/.gitignore
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
*.egg
|
|
12
|
+
pip-wheel-metadata/
|
|
13
|
+
|
|
14
|
+
# 虚拟环境
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
ENV/
|
|
18
|
+
|
|
19
|
+
# 测试与类型检查
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
.coverage
|
|
24
|
+
htmlcov/
|
|
25
|
+
.tox/
|
|
26
|
+
.nox/
|
|
27
|
+
|
|
28
|
+
# 环境与密钥
|
|
29
|
+
.env
|
|
30
|
+
.env.*
|
|
31
|
+
!.env.example
|
|
32
|
+
|
|
33
|
+
# IDE / 编辑器
|
|
34
|
+
.idea/
|
|
35
|
+
.vscode/
|
|
36
|
+
*.swp
|
|
37
|
+
*.swo
|
|
38
|
+
*~
|
|
39
|
+
.DS_Store
|
|
40
|
+
|
|
41
|
+
# pyinstaller 构建产物(build.sh 产出上级目录的 clak 二进制)
|
|
42
|
+
/clak
|
|
43
|
+
/clak.spec
|
|
44
|
+
|
esclak-0.0.5/PKG-INFO
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: esclak
|
|
3
|
+
Version: 0.0.5
|
|
4
|
+
Summary: 固定流程脚本 TUI 与无头执行
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: textual>=8.0.0
|
|
7
|
+
Requires-Dist: wcwidth>=0.2.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# clak
|
|
11
|
+
|
|
12
|
+
固定流程脚本 TUI 与无头执行(`StepList` + `ScriptSession`)。
|
|
13
|
+
|
|
14
|
+
CLAK = **C**ommand-**L**ine **A**utomation **K**it。
|
|
15
|
+
|
|
16
|
+
## 能力
|
|
17
|
+
|
|
18
|
+
- history / slash 菜单 / 多行输入 / context / ask(单选、多选、过滤)
|
|
19
|
+
- `/` 补全
|
|
20
|
+
- 脚本:`/pack-game-res`(版本 + 打包目标)、`/select-server`(目标服务器)
|
|
21
|
+
- 无头:`clak exec /pack-game-res --use-defaults` 或 `--set field=value`
|
|
22
|
+
|
|
23
|
+
## 目录结构
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
src/clak/
|
|
27
|
+
├── cli.py # argparse 入口
|
|
28
|
+
├── runtime/ # 脚本运行时(与 UI 解耦)
|
|
29
|
+
│ ├── step_list.py # 步骤执行器
|
|
30
|
+
│ ├── session.py # 会话 + ask 网关
|
|
31
|
+
│ ├── params.py # 无头 --set 参数
|
|
32
|
+
│ ├── headless.py # 无头执行入口
|
|
33
|
+
│ ├── slash.py # SlashEngine 分发
|
|
34
|
+
│ └── ask/ # ask 协议(request/answer)
|
|
35
|
+
├── commands/ # 纯 slash 命令
|
|
36
|
+
│ ├── types.py # SlashCommand / ScriptEntry / SlashEntry
|
|
37
|
+
│ ├── _registry.py # 聚合 builtin + 脚本
|
|
38
|
+
│ └── version/status/clear/quit.py
|
|
39
|
+
├── scripts/ # 业务脚本(自动发现)
|
|
40
|
+
│ ├── _registry.py # @script 装饰器 + 扫描注册
|
|
41
|
+
│ └── pack_game_res/select_server.py
|
|
42
|
+
└── tui/ # 渲染层
|
|
43
|
+
├── app.py / state.py / event.py / theme.py / screen.py
|
|
44
|
+
└── views/ # 渲染单元平铺
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
依赖链单向:`tui → runtime/slash → commands/_registry → scripts/_registry → runtime`(数据结构)。
|
|
48
|
+
|
|
49
|
+
## 安装与运行
|
|
50
|
+
|
|
51
|
+
需要 Python ≥ 3.10。依赖 `textual` + `wcwidth`。
|
|
52
|
+
|
|
53
|
+
### 用 uv
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv sync
|
|
57
|
+
uv run clak # 打开 TUI
|
|
58
|
+
uv run clak exec /version # 无头执行
|
|
59
|
+
uv run clak exec /pack-game-res --set version=1.0 --set targets=base,cn
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 不用 uv(pip + python)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install -e . # 装依赖 + 入口命令
|
|
66
|
+
python -m clak # 打开 TUI
|
|
67
|
+
python -m clak exec /version
|
|
68
|
+
python -m clak exec /pack-game-res --use-defaults
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
若依赖已装(`pip install textual wcwidth`),可直接跑无需安装:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python3 -m clak # 直接运行
|
|
75
|
+
python3 -m clak exec /version
|
|
76
|
+
python3 -m pytest # 跑测试
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
入口命令:`clak`(pip install 后可用)。PyPI 包名为 `esclak`(绕开 `clack` 相似性),import 名为 `clak`(`from clak... import`)。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install esclak # 装包,得到 clak 命令
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 构建单文件二进制(组内分发)
|
|
86
|
+
|
|
87
|
+
无需 Python 环境即可分发,用 pyinstaller 打成单文件:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
./build.sh # 产出上级目录的 clak 二进制
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
部署到游戏代码仓库时,目录约定:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
tools/
|
|
97
|
+
├── clak_src/ # clak 源码(入库)
|
|
98
|
+
│ ├── build.sh
|
|
99
|
+
│ └── src/...
|
|
100
|
+
└── clak # 构建产物(gitignore,不入库)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
组员拉代码后,在 `tools/clak_src/` 执行 `./build.sh` 即生成 `tools/clak`,直接 `./tools/clak` 使用。
|
|
104
|
+
|
|
105
|
+
> 跨平台需分别构建:macOS 上构建得到 macOS 二进制,Linux 同理。onefile 模式首次启动会解压到临时目录,约慢几百毫秒。
|
|
106
|
+
|
|
107
|
+
## 无头执行
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
clak exec /<script> [--set field=value ...] [--use-defaults]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- `--set version=1.0`:传入脚本参数(可重复)
|
|
114
|
+
- `--use-defaults`:未 `--set` 的 ask 字段用 `AskRequest.default`,无交互批量执行
|
|
115
|
+
- 缺参且未开 `--use-defaults` 时报错退出码 2
|
|
116
|
+
|
|
117
|
+
## 扩展脚本
|
|
118
|
+
|
|
119
|
+
在 `src/clak/scripts/` 新建模块,用 `@script` 装饰器注册,启动时自动扫描,**无需改框架任何文件**:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# scripts/my_script.py
|
|
123
|
+
from clak.runtime.ask import AskChoice, AskMode, AskRequest
|
|
124
|
+
from clak.runtime.session import ScriptSession
|
|
125
|
+
from clak.scripts import script
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@script("/my-script", "我的脚本说明")
|
|
129
|
+
def run(session: ScriptSession) -> None:
|
|
130
|
+
state = {"name": ""}
|
|
131
|
+
|
|
132
|
+
def step_ask_name() -> int:
|
|
133
|
+
def on_answer(answer) -> None:
|
|
134
|
+
state["name"] = answer.primary
|
|
135
|
+
session.log(f"已选: {state['name']}")
|
|
136
|
+
|
|
137
|
+
return session.ask_step(
|
|
138
|
+
AskRequest(
|
|
139
|
+
field_id="name",
|
|
140
|
+
prompt="选择名称",
|
|
141
|
+
default="default",
|
|
142
|
+
choices=(AskChoice(label="默认", value="default"),),
|
|
143
|
+
),
|
|
144
|
+
on_answer,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def step_done() -> int:
|
|
148
|
+
session.log(f"my-script: 完成 ({state['name']})")
|
|
149
|
+
return 0
|
|
150
|
+
|
|
151
|
+
step_list = session.step_list
|
|
152
|
+
session.bind_step_end()
|
|
153
|
+
step_list.clear()
|
|
154
|
+
step_list.add(True, step_ask_name, "ask_name")
|
|
155
|
+
step_list.add(True, step_done, "done")
|
|
156
|
+
session.run()
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
加完即可用:`clak exec /my-script --set name=default`。
|
|
160
|
+
|
|
161
|
+
## 扩展 slash 命令(非脚本)
|
|
162
|
+
|
|
163
|
+
在 `src/clak/commands/` 新建模块,定义 `COMMAND = SlashCommand(...)`,加入 `commands/_registry.py` 的 `_BUILTINS`。
|
|
164
|
+
|
|
165
|
+
## 测试
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
python -m pytest # 或 uv run pytest
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
测试覆盖:reducer、headless、pack_game_res、select_server、ask(view/multi/navigation)、view_lifecycle、tui_script、exec_list、key_from_textual。
|
esclak-0.0.5/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# clak
|
|
2
|
+
|
|
3
|
+
固定流程脚本 TUI 与无头执行(`StepList` + `ScriptSession`)。
|
|
4
|
+
|
|
5
|
+
CLAK = **C**ommand-**L**ine **A**utomation **K**it。
|
|
6
|
+
|
|
7
|
+
## 能力
|
|
8
|
+
|
|
9
|
+
- history / slash 菜单 / 多行输入 / context / ask(单选、多选、过滤)
|
|
10
|
+
- `/` 补全
|
|
11
|
+
- 脚本:`/pack-game-res`(版本 + 打包目标)、`/select-server`(目标服务器)
|
|
12
|
+
- 无头:`clak exec /pack-game-res --use-defaults` 或 `--set field=value`
|
|
13
|
+
|
|
14
|
+
## 目录结构
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
src/clak/
|
|
18
|
+
├── cli.py # argparse 入口
|
|
19
|
+
├── runtime/ # 脚本运行时(与 UI 解耦)
|
|
20
|
+
│ ├── step_list.py # 步骤执行器
|
|
21
|
+
│ ├── session.py # 会话 + ask 网关
|
|
22
|
+
│ ├── params.py # 无头 --set 参数
|
|
23
|
+
│ ├── headless.py # 无头执行入口
|
|
24
|
+
│ ├── slash.py # SlashEngine 分发
|
|
25
|
+
│ └── ask/ # ask 协议(request/answer)
|
|
26
|
+
├── commands/ # 纯 slash 命令
|
|
27
|
+
│ ├── types.py # SlashCommand / ScriptEntry / SlashEntry
|
|
28
|
+
│ ├── _registry.py # 聚合 builtin + 脚本
|
|
29
|
+
│ └── version/status/clear/quit.py
|
|
30
|
+
├── scripts/ # 业务脚本(自动发现)
|
|
31
|
+
│ ├── _registry.py # @script 装饰器 + 扫描注册
|
|
32
|
+
│ └── pack_game_res/select_server.py
|
|
33
|
+
└── tui/ # 渲染层
|
|
34
|
+
├── app.py / state.py / event.py / theme.py / screen.py
|
|
35
|
+
└── views/ # 渲染单元平铺
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
依赖链单向:`tui → runtime/slash → commands/_registry → scripts/_registry → runtime`(数据结构)。
|
|
39
|
+
|
|
40
|
+
## 安装与运行
|
|
41
|
+
|
|
42
|
+
需要 Python ≥ 3.10。依赖 `textual` + `wcwidth`。
|
|
43
|
+
|
|
44
|
+
### 用 uv
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv sync
|
|
48
|
+
uv run clak # 打开 TUI
|
|
49
|
+
uv run clak exec /version # 无头执行
|
|
50
|
+
uv run clak exec /pack-game-res --set version=1.0 --set targets=base,cn
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 不用 uv(pip + python)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install -e . # 装依赖 + 入口命令
|
|
57
|
+
python -m clak # 打开 TUI
|
|
58
|
+
python -m clak exec /version
|
|
59
|
+
python -m clak exec /pack-game-res --use-defaults
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
若依赖已装(`pip install textual wcwidth`),可直接跑无需安装:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python3 -m clak # 直接运行
|
|
66
|
+
python3 -m clak exec /version
|
|
67
|
+
python3 -m pytest # 跑测试
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
入口命令:`clak`(pip install 后可用)。PyPI 包名为 `esclak`(绕开 `clack` 相似性),import 名为 `clak`(`from clak... import`)。
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install esclak # 装包,得到 clak 命令
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 构建单文件二进制(组内分发)
|
|
77
|
+
|
|
78
|
+
无需 Python 环境即可分发,用 pyinstaller 打成单文件:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
./build.sh # 产出上级目录的 clak 二进制
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
部署到游戏代码仓库时,目录约定:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
tools/
|
|
88
|
+
├── clak_src/ # clak 源码(入库)
|
|
89
|
+
│ ├── build.sh
|
|
90
|
+
│ └── src/...
|
|
91
|
+
└── clak # 构建产物(gitignore,不入库)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
组员拉代码后,在 `tools/clak_src/` 执行 `./build.sh` 即生成 `tools/clak`,直接 `./tools/clak` 使用。
|
|
95
|
+
|
|
96
|
+
> 跨平台需分别构建:macOS 上构建得到 macOS 二进制,Linux 同理。onefile 模式首次启动会解压到临时目录,约慢几百毫秒。
|
|
97
|
+
|
|
98
|
+
## 无头执行
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
clak exec /<script> [--set field=value ...] [--use-defaults]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- `--set version=1.0`:传入脚本参数(可重复)
|
|
105
|
+
- `--use-defaults`:未 `--set` 的 ask 字段用 `AskRequest.default`,无交互批量执行
|
|
106
|
+
- 缺参且未开 `--use-defaults` 时报错退出码 2
|
|
107
|
+
|
|
108
|
+
## 扩展脚本
|
|
109
|
+
|
|
110
|
+
在 `src/clak/scripts/` 新建模块,用 `@script` 装饰器注册,启动时自动扫描,**无需改框架任何文件**:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# scripts/my_script.py
|
|
114
|
+
from clak.runtime.ask import AskChoice, AskMode, AskRequest
|
|
115
|
+
from clak.runtime.session import ScriptSession
|
|
116
|
+
from clak.scripts import script
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@script("/my-script", "我的脚本说明")
|
|
120
|
+
def run(session: ScriptSession) -> None:
|
|
121
|
+
state = {"name": ""}
|
|
122
|
+
|
|
123
|
+
def step_ask_name() -> int:
|
|
124
|
+
def on_answer(answer) -> None:
|
|
125
|
+
state["name"] = answer.primary
|
|
126
|
+
session.log(f"已选: {state['name']}")
|
|
127
|
+
|
|
128
|
+
return session.ask_step(
|
|
129
|
+
AskRequest(
|
|
130
|
+
field_id="name",
|
|
131
|
+
prompt="选择名称",
|
|
132
|
+
default="default",
|
|
133
|
+
choices=(AskChoice(label="默认", value="default"),),
|
|
134
|
+
),
|
|
135
|
+
on_answer,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def step_done() -> int:
|
|
139
|
+
session.log(f"my-script: 完成 ({state['name']})")
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
step_list = session.step_list
|
|
143
|
+
session.bind_step_end()
|
|
144
|
+
step_list.clear()
|
|
145
|
+
step_list.add(True, step_ask_name, "ask_name")
|
|
146
|
+
step_list.add(True, step_done, "done")
|
|
147
|
+
session.run()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
加完即可用:`clak exec /my-script --set name=default`。
|
|
151
|
+
|
|
152
|
+
## 扩展 slash 命令(非脚本)
|
|
153
|
+
|
|
154
|
+
在 `src/clak/commands/` 新建模块,定义 `COMMAND = SlashCommand(...)`,加入 `commands/_registry.py` 的 `_BUILTINS`。
|
|
155
|
+
|
|
156
|
+
## 测试
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
python -m pytest # 或 uv run pytest
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
测试覆盖:reducer、headless、pack_game_res、select_server、ask(view/multi/navigation)、view_lifecycle、tui_script、exec_list、key_from_textual。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "esclak"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "固定流程脚本 TUI 与无头执行"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"textual>=8.0.0",
|
|
9
|
+
"wcwidth>=0.2.13",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
clak = "clak.cli:main"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["hatchling"]
|
|
17
|
+
build-backend = "hatchling.build"
|
|
18
|
+
|
|
19
|
+
[tool.hatch.version]
|
|
20
|
+
path = "src/clak/__init__.py"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["src/clak"]
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.sdist]
|
|
26
|
+
include = ["src/clak", "README.md"]
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=8.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from clak.runtime.headless import HeadlessError, emit, run_line
|
|
7
|
+
from clak.tui.app import run_tui
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
11
|
+
parser = argparse.ArgumentParser(prog="clak", description="固定流程脚本 TUI")
|
|
12
|
+
sub = parser.add_subparsers(dest="command")
|
|
13
|
+
|
|
14
|
+
sub.add_parser("tui", help="打开交互 TUI(默认)")
|
|
15
|
+
|
|
16
|
+
exec_parser = sub.add_parser("exec", help="无头执行一条 slash 或 prompt")
|
|
17
|
+
exec_parser.add_argument(
|
|
18
|
+
"line",
|
|
19
|
+
help='要执行的行,例如 "/version" 或 "/pack-game-res"',
|
|
20
|
+
)
|
|
21
|
+
exec_parser.add_argument(
|
|
22
|
+
"--set",
|
|
23
|
+
action="append",
|
|
24
|
+
default=[],
|
|
25
|
+
metavar="FIELD=VALUE",
|
|
26
|
+
help="脚本参数(可重复),如 --set version=1.0 --set targets=base,cn",
|
|
27
|
+
)
|
|
28
|
+
exec_parser.add_argument(
|
|
29
|
+
"--use-defaults",
|
|
30
|
+
action="store_true",
|
|
31
|
+
help="未 --set 的 ask 字段使用 AskRequest.default(无交互批量)",
|
|
32
|
+
)
|
|
33
|
+
return parser
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main(argv: list[str] | None = None) -> None:
|
|
37
|
+
parser = build_parser()
|
|
38
|
+
args = parser.parse_args(argv)
|
|
39
|
+
command = args.command
|
|
40
|
+
|
|
41
|
+
if command == "exec":
|
|
42
|
+
from clak.runtime.params import ScriptInputs
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
inputs = ScriptInputs.parse_set_flags(
|
|
46
|
+
args.set,
|
|
47
|
+
allow_defaults=args.use_defaults or not args.set,
|
|
48
|
+
)
|
|
49
|
+
run = run_line(args.line, script_inputs=inputs)
|
|
50
|
+
except (HeadlessError, ValueError) as error:
|
|
51
|
+
print(f"clak: {error}", file=sys.stderr)
|
|
52
|
+
code = error.code if isinstance(error, HeadlessError) else 2
|
|
53
|
+
raise SystemExit(code) from error
|
|
54
|
+
raise SystemExit(emit(run))
|
|
55
|
+
|
|
56
|
+
if command in (None, "tui"):
|
|
57
|
+
run_tui()
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
parser.error(f"未知子命令: {command}")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 各 slash 子命令见同目录模块;聚合在 registry。
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from clak.commands import (
|
|
2
|
+
clear,
|
|
3
|
+
quit,
|
|
4
|
+
status,
|
|
5
|
+
version,
|
|
6
|
+
)
|
|
7
|
+
from clak.commands.types import ScriptEntry, SlashCommand, SlashEntry
|
|
8
|
+
from clak.runtime.session import ScriptSession
|
|
9
|
+
from clak.scripts._registry import all_scripts
|
|
10
|
+
|
|
11
|
+
_BUILTINS: tuple[SlashCommand, ...] = (
|
|
12
|
+
clear.COMMAND,
|
|
13
|
+
version.COMMAND,
|
|
14
|
+
status.COMMAND,
|
|
15
|
+
quit.COMMAND,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
COMMANDS: tuple[SlashEntry, ...] = _BUILTINS + all_scripts()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def command_for_token(token: str) -> SlashEntry | None:
|
|
22
|
+
needle = token.lower()
|
|
23
|
+
for cmd in COMMANDS:
|
|
24
|
+
if needle in cmd.all_names():
|
|
25
|
+
return cmd
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def script_command(script_id: str) -> ScriptEntry | None:
|
|
30
|
+
for cmd in all_scripts():
|
|
31
|
+
if cmd.script_id == script_id:
|
|
32
|
+
return cmd
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def run_script(session: ScriptSession, script_id: str) -> None:
|
|
37
|
+
cmd = script_command(script_id)
|
|
38
|
+
if not cmd:
|
|
39
|
+
raise ValueError(f"未知脚本: {script_id}")
|
|
40
|
+
cmd.script_run(session)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from clak.commands.types import (
|
|
2
|
+
SlashAction,
|
|
3
|
+
SlashCommand,
|
|
4
|
+
SlashDispatchResult,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dispatch() -> SlashDispatchResult:
|
|
9
|
+
return SlashDispatchResult(
|
|
10
|
+
action=SlashAction.NEW_SESSION,
|
|
11
|
+
message="已清空 history",
|
|
12
|
+
status="Session cleared",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
COMMAND = SlashCommand(
|
|
17
|
+
"/clear",
|
|
18
|
+
("/new",),
|
|
19
|
+
"清空 history",
|
|
20
|
+
handler=dispatch,
|
|
21
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from clak.commands.types import (
|
|
2
|
+
SlashAction,
|
|
3
|
+
SlashCommand,
|
|
4
|
+
SlashDispatchResult,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dispatch() -> SlashDispatchResult:
|
|
9
|
+
return SlashDispatchResult(action=SlashAction.QUIT, status="再见")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
COMMAND = SlashCommand(
|
|
13
|
+
"/quit",
|
|
14
|
+
("/exit",),
|
|
15
|
+
"退出 TUI",
|
|
16
|
+
handler=dispatch,
|
|
17
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from clak.commands.types import (
|
|
2
|
+
SlashAction,
|
|
3
|
+
SlashCommand,
|
|
4
|
+
SlashDispatchResult,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dispatch() -> SlashDispatchResult:
|
|
9
|
+
return SlashDispatchResult(
|
|
10
|
+
action=SlashAction.SHOW_STATUS,
|
|
11
|
+
message="clak 脚本聚合",
|
|
12
|
+
status="已显示状态",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
COMMAND = SlashCommand(
|
|
17
|
+
"/status",
|
|
18
|
+
("/context",),
|
|
19
|
+
"软件名与定位",
|
|
20
|
+
handler=dispatch,
|
|
21
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum, auto
|
|
6
|
+
|
|
7
|
+
from clak.runtime.session import ScriptSession
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SlashAction(Enum):
|
|
11
|
+
NONE = auto()
|
|
12
|
+
NEW_SESSION = auto()
|
|
13
|
+
SHOW_STATUS = auto()
|
|
14
|
+
RUN_SCRIPT = auto()
|
|
15
|
+
QUIT = auto()
|
|
16
|
+
UNKNOWN = auto()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class SlashMatch:
|
|
21
|
+
name: str
|
|
22
|
+
description: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class SlashDispatchResult:
|
|
27
|
+
action: SlashAction
|
|
28
|
+
message: str | None = None
|
|
29
|
+
status: str | None = None
|
|
30
|
+
command: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SlashTokenKind(Enum):
|
|
34
|
+
NOT_SLASH = auto()
|
|
35
|
+
INCOMPLETE = auto()
|
|
36
|
+
AVAILABLE = auto()
|
|
37
|
+
UNKNOWN = auto()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
SlashHandler = Callable[[], SlashDispatchResult]
|
|
41
|
+
ScriptRunner = Callable[[ScriptSession], None]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class SlashCommand:
|
|
46
|
+
"""纯 slash 命令:只分发,不跑脚本。"""
|
|
47
|
+
|
|
48
|
+
name: str
|
|
49
|
+
aliases: tuple[str, ...]
|
|
50
|
+
description: str
|
|
51
|
+
handler: SlashHandler
|
|
52
|
+
|
|
53
|
+
def all_names(self) -> tuple[str, ...]:
|
|
54
|
+
return (self.name, *self.aliases)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class ScriptEntry:
|
|
59
|
+
"""脚本入口:slash 名 + 脚本 runner。"""
|
|
60
|
+
|
|
61
|
+
name: str
|
|
62
|
+
aliases: tuple[str, ...]
|
|
63
|
+
description: str
|
|
64
|
+
script_id: str
|
|
65
|
+
script_run: ScriptRunner
|
|
66
|
+
|
|
67
|
+
def all_names(self) -> tuple[str, ...]:
|
|
68
|
+
return (self.name, *self.aliases)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
SlashEntry = SlashCommand | ScriptEntry
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from clak import __version__
|
|
2
|
+
from clak.commands.types import (
|
|
3
|
+
SlashAction,
|
|
4
|
+
SlashCommand,
|
|
5
|
+
SlashDispatchResult,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def dispatch() -> SlashDispatchResult:
|
|
10
|
+
return SlashDispatchResult(
|
|
11
|
+
action=SlashAction.SHOW_STATUS,
|
|
12
|
+
message=f"clak {__version__}",
|
|
13
|
+
status="已显示版本",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
COMMAND = SlashCommand(
|
|
18
|
+
"/version",
|
|
19
|
+
(),
|
|
20
|
+
"显示工具版本",
|
|
21
|
+
handler=dispatch,
|
|
22
|
+
)
|
|
File without changes
|