Skip to content

Commit 533b095

Browse files
authored
Create 2025-03-12-zargs.md
1 parent c32a4d8 commit 533b095

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed

content/post/2025-03-12-zargs.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
---
2+
title: 分享一个编译时命令构建库-zargs
3+
author: KiozWang
4+
date: 2025-03-12T20:12:00+08:00
5+
---
6+
7+
See [zargs](https://github.com/kioz-wang/zargs)!
8+
9+
# 为什么要再造一个轮子
10+
11+
重度命令行用户,工作中喜欢手搓一些小工具。命令行参数的设计决定了工具的易用程度,语言的表达能力决定了命令行参数设计的上限。
12+
13+
Rust 有 [clap](https://docs.rs/clap/latest/clap/):可以通过注解直接获得命令解析器,也可以通过方法的链式调用显式构建命令。可以生成良好的帮助信息,生成 shell 补全规则……
14+
```rust
15+
use clap::Parser;
16+
17+
/// Simple program to greet a person
18+
#[derive(Parser, Debug)]
19+
#[command(version, about, long_about = None)]
20+
struct Args {
21+
/// Name of the person to greet
22+
#[arg(short, long)]
23+
name: String,
24+
/// Number of times to greet
25+
#[arg(short, long, default_value_t = 1)]
26+
count: u8,
27+
}
28+
29+
fn main() {
30+
let args = Args::parse();
31+
for _ in 0..args.count {
32+
println!("Hello {}!", args.name);
33+
}
34+
}
35+
```
36+
37+
Python 有 [argparse](https://docs.python.org/zh-cn/3.13/library/argparse.html):动态弱类型语言的解析器,通过方法调用显式构建命令,运行时动态生成参数结构体,十分轻松的参数访问……
38+
```python
39+
parser = argparse.ArgumentParser(prog='PROG', allow_abbrev=False)
40+
parser.add_argument('--foobar', action='store_true')
41+
parser.add_argument('--foonley', action='store_false')
42+
parser.parse_args(['--foon'])
43+
```
44+
45+
Cpp 有 [argparse](https://github.com/p-ranav/argparse):通过方法调用显式构建命令,方便的参数访问
46+
```cpp
47+
#include <argparse/argparse.hpp>
48+
49+
int main(int argc, char *argv[]) {
50+
argparse::ArgumentParser program("program_name");
51+
52+
program.add_argument("square")
53+
.help("display the square of a given integer")
54+
.scan<'i', int>();
55+
56+
try {
57+
program.parse_args(argc, argv);
58+
}
59+
catch (const std::exception& err) {
60+
std::cerr << err.what() << std::endl;
61+
std::cerr << program;
62+
return 1;
63+
}
64+
65+
auto input = program.get<int>("square");
66+
std::cout << (input * input) << std::endl;
67+
68+
return 0;
69+
}
70+
```
71+
72+
这些参数解析方案都有一些共同点:充分利用语言特性,尽可能集中的表达,方便的参数访问……
73+
74+
Zig 是一个具有强大 comptime 能力的新系统级语言,为何还没有一个类似的方案?
75+
76+
# zargs 做了什么努力?
77+
78+
[zig-clap](https://github.com/Hejsil/zig-clap) 使用 comptime 解析 help message 来生成命令,独特而奇怪…… 为什么不能使用方法调用来实现?调用`clap.parse`返回解析结果,但位置参数混在Slice中且没有得到解析。
79+
80+
[flags](https://github.com/joegm/flags) 要求用户直接定义出参数结构体,再在另外的地方描述参数配置,如选项、帮助信息等…… 有点太凌乱了
81+
82+
zargs 想要为 Zig 实现一个充分利用 comptime 特性、尽可能集中描述参数配置、方便访问解析后的参数的参数解析方案。自认为 zargs 做到了!
83+
84+
以下语句创建一个编译时变量,用于构建命令 Command。其中,`.use_subCmd = "action"`表示该命令(demo)会包含子命令,同时可以通过参数结构体中名为`action`的联合枚举成员访问到:
85+
86+
```zig
87+
comptime var cmd: Command = .{ .name = "demo", .use_subCmd = "action", .description = "This is a simple demo" };
88+
```
89+
90+
使用链式的方法调用,为 Command 添加选项或参数,在一个位置,完成选项或参数的新增和配置,包括默认值、自定义解析器、回调等……:
91+
92+
```zig
93+
_ = cmd.opt("verbose", u32, .{ .short = 'v' }).optArg("output", []const u8, .{ .short = 'o', .long = "out", .default = "demo.bin" });
94+
```
95+
96+
运行时调用编译时生成的解析器,得到参数结构体:
97+
98+
```zig
99+
const args = cmd.parseAlloc(&it, allocator) catch |e| {
100+
std.debug.print("\nError => {}\n", .{e});
101+
std.debug.print("{s}\n", .{cmd.usage()});
102+
std.process.exit(1);
103+
};
104+
```
105+
106+
访问解析得到的参数,就像访问自己在某处定义的结构体成员一样简单明了:
107+
108+
```zig
109+
switch (args.action) {
110+
.install => |a| {
111+
std.debug.print("Installing {s}\n", .{a.name});
112+
},
113+
.remove => |a| {
114+
std.debug.print("Removing {s}\n", .{a.name});
115+
},
116+
}
117+
```
118+
119+
内置的 help 选项,当传入`-h``--help`时自动输出生成的帮助信息:
120+
121+
```bash
122+
$ zig build ex-02.simple -- -h
123+
Usage: demo [-h|--help] [-v]... -o|--out {OUTPUT} [--] {install|remove}
124+
125+
This is a simple demo
126+
127+
[-h|--help] Show this help then exit
128+
[-v]...
129+
130+
-o|--out {OUTPUT}
131+
132+
install
133+
remove
134+
```
135+
136+
# 继续探索?
137+
138+
阅读源代码、运行实例:
139+
140+
```bash
141+
git clone [email protected]:kioz-wang/zargs.git
142+
```
143+
144+
或直接访问 https://github.com/kioz-wang/zargs ,先从 README 看起!
145+
146+
## Command
147+
148+
zargs 的核心能力:编译时命令构建器,就在 src/command.zig !
149+
150+
## TokenIter
151+
152+
zargs 提供了多样的迭代器选择,可以方便地为应用的命令行接口编写测试用例。
153+
154+
## parseAny
155+
156+
zargs 提供了通用的解析函数,当命令解析器完成任务后,还可以手动处理余下的参数。
157+
158+
# 未来?
159+
160+
欢迎大家使用、宣传、提出改进建议!谢谢。

0 commit comments

Comments
 (0)