Skip to content

Commit 0324384

Browse files
committed
update
1 parent 17e9ce0 commit 0324384

File tree

9 files changed

+369
-345
lines changed

9 files changed

+369
-345
lines changed

README.md

Lines changed: 3 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,11 @@
11
# cmdline
22

3-
C++23 命令行解析库,风格参考 clap。模块化设计,单 import,无裸指针 API。
4-
5-
- **C++23** 模块
6-
- **类型命名**`ParsedArgs`(解析结果)、`OptionValue`(选项取值)
7-
- **解析 API**`parse(argc, argv)``parse_from(span<string>)``parse_from(string_view)`,支持多种输入
8-
- **子命令分发**`on_run(lambda)` + `dispatch(parsed)`,也可分离构建后自行根据 `subcommand_name()` 分发
9-
- **全局选项**`get_flag()` / `get_one()``std::expected` 解析结果
10-
11-
## 快速开始
12-
13-
```cpp
14-
import std;
15-
import cmdline;
16-
17-
int main(int argc, char* argv[]) {
18-
using namespace cmdline;
19-
20-
auto app = App("myapp")
21-
.version("1.0.0")
22-
.about("My CLI")
23-
.arg(Arg("input").help("Input file").required())
24-
.opt(Opt("verbose").short_name('v').long_opt("verbose").help("Verbose"))
25-
.opt(Opt("config").long_opt("config").takes_value().value_name("FILE"));
26-
27-
auto result = app.parse(argc, argv);
28-
if (!result) {
29-
if (result.error() == "help requested" || result.error() == "version requested")
30-
return 0;
31-
std::println("Error: {}", result.error());
32-
return 1;
33-
}
34-
const ParsedArgs& parsed = *result;
35-
if (parsed.get_flag("verbose")) { /* ... */ }
36-
if (auto c = parsed.get_one("config")) std::println("Config: {}", *c);
37-
std::println("Input: {}", parsed.arg(0));
38-
return 0;
39-
}
40-
```
41-
42-
## 多种输入
43-
44-
```cpp
45-
// C 风格
46-
auto result = app.parse(argc, argv);
47-
48-
// 字符串列表
49-
std::vector<std::string> args = {"myapp", "add", "python", "3.12"};
50-
auto result = app.parse_from(args);
51-
52-
// 单条命令行字符串(按空格/引号拆分)
53-
auto result = app.parse_from("myapp add python 3.12 --yes");
54-
```
55-
56-
## 绑定分发(on_run + dispatch)
57-
58-
子命令推荐用字符串链式:`.subcommand("name").about("...").arg(...).on_run(...)`;也可传入完整 `App` 对象。
59-
60-
```cpp
61-
App app("demo");
62-
app.opt(Opt("yes").long_opt("yes").global().help("Auto confirm"))
63-
.subcommand("add")
64-
.about("Add a target")
65-
.arg(Arg("target").required())
66-
.arg(Arg("version").required())
67-
.on_run([](const ParsedArgs& args) {
68-
std::println("add: {}@{}", args.get_one("target").value_or(""), args.get_one("version").value_or(""));
69-
})
70-
.subcommand("remove")
71-
.about("Remove")
72-
.arg(Arg("target").required())
73-
.on_run([](const ParsedArgs& args) { std::println("remove: {}", args.arg(0)); });
74-
75-
auto result = app.parse(argc, argv);
76-
if (result) app.dispatch(*result);
77-
```
78-
79-
## 分离模式(自行分发)
80-
81-
```cpp
82-
auto result = app.parse(argc, argv);
83-
if (!result) return 1;
84-
const ParsedArgs& parsed = *result;
85-
if (parsed.has_subcommand()) {
86-
auto sub = parsed.subcommand();
87-
if (sub && parsed.subcommand_name() == "add") {
88-
const ParsedArgs& sub_args = sub->get();
89-
// ...
90-
}
91-
}
92-
```
93-
94-
## 构建
3+
C++23 命令行解析库,API 见名知意,单 import。
954

965
```bash
976
cd cmdline && xmake -P .
987
xmake run basic
99-
xmake run with_dispatch -- add python 3.12
100-
xmake run parse_from_string
8+
xmake -y run cmdline_test # 测试(-y 自动安装 gtest)
1019
```
10210

103-
测试需 Google Test,使用 **`xmake -y`** 可自动安装依赖(如 gtest)并编译:
104-
105-
```bash
106-
xmake -y # 自动确认并安装 gtest,编译全部(含测试)
107-
xmake -y run cmdline_test # 编译并运行测试
108-
```
109-
110-
## CI
111-
112-
GitHub Actions 在 `push` / `pull_request``main``master` 时自动构建并测试(见 [.github/workflows/ci.yml](.github/workflows/ci.yml)):
113-
114-
- 安装 xmake(package 缓存)、build-essential
115-
- 通过 [Xlings](https://d2learn.org/xlings-install.sh) 安装 GCC 15.1(C++23 模块)
116-
- `xmake -y` 构建(含 gtest)
117-
- `xmake run cmdline_test` 跑测试
118-
- 跑示例 `with_dispatch` 做冒烟检查
119-
120-
## API 概要
121-
122-
| 类型/方法 | 说明 |
123-
|-----------|------|
124-
| `App` | 根或子命令;`.version()`, `.author()`, `.about()`, `.arg()`, `.opt()`, `.subcommand(name\|App)`, `.on_run()`, `.parse()`, `.parse_from()`, `.dispatch()`, `.print_help()` |
125-
| `Arg` | 位置参数;`.help()`, `.required()`, `.default_value()` |
126-
| `Opt` | 选项;`.short_name()`, `.long_opt()`, `.help()`, `.takes_value()`, `.value_name()`, `.multiple()`, `.global()` |
127-
| `ParsedArgs` | 解析结果;`.get_flag()`, `.get_one()`, `.opt()`, `.opt_or_empty()`, `.arg()`, `.arg_count()`, `.has_subcommand()`, `.subcommand_name()`, `.subcommand()` |
128-
| `OptionValue` | 单个选项取值;`.value()`, `.value_or()`, `.is_set()` |
129-
130-
解析返回 `std::expected<ParsedArgs, std::string>``-h`/`--help``--version` 时 error 为 `"help requested"` / `"version requested"`,可据此退出 0。
11+
详见 [docs/api.md](docs/api.md)

docs/api.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# API 与示例
2+
3+
## 类型一览
4+
5+
| 类型 | 说明 |
6+
|------|------|
7+
| `App` | 根或子命令 |
8+
| `Arg` | 位置参数 |
9+
| `Option` | 选项(短/长、带值、多值、全局) |
10+
| `ParsedArgs` | 解析结果 |
11+
| `OptionValue` | 单个选项取值(flag 计数 + values) |
12+
13+
解析返回 `std::expected<ParsedArgs, std::string>``-h`/`--help``--version` 时 error 为 `"help requested"` / `"version requested"`
14+
15+
---
16+
17+
## App
18+
19+
| 方法 | 说明 |
20+
|------|------|
21+
| `.version(s)` `.author(s)` `.description(s)` | 元信息 |
22+
| `.arg(Arg)` `.arg("name")` | 添加位置参数(字符串即链式) |
23+
| `.option(Option)` `.option("name")` | 添加选项 |
24+
| `.subcommand(App)` `.subcommand("name")` | 添加子命令 |
25+
| `.action(fn)` | 根/子命令执行函数 `void(const ParsedArgs&)` |
26+
| `.parse(argc, argv)` `.parse_from(span)` `.parse_from(string_view)` | 解析 |
27+
| `.run(parsed)` | 根据解析结果执行对应 action |
28+
| `.print_help(program_name)` | 打印帮助 |
29+
30+
---
31+
32+
## Arg / Option
33+
34+
**Arg**`.help(s)` `.required(bool)` `.default_value(s)`
35+
36+
**Option**`.short_name(c)` `.long_opt(s)` `.help(s)` `.takes_value(bool)` `.value_name(s)` `.multiple(bool)` `.global(bool)`
37+
38+
---
39+
40+
## ParsedArgs
41+
42+
| 方法 | 说明 |
43+
|------|------|
44+
| `.is_flag_set(name)` | 选项是否被设置 |
45+
| `.value(name)` | 选项或同名位置参数的一个值 `optional<string>` |
46+
| `.option(name)` | 选项的 `OptionValue`(optional) |
47+
| `.option_or_empty(name)` | 选项值,未设置则空 |
48+
| `.positional(i)` `.positional_or(i, default)` `.positional_count()` | 按索引取位置参数 |
49+
| `.has_subcommand()` `.subcommand_name()` `.subcommand()` | 子命令信息 |
50+
51+
**OptionValue**`.value()` `.value_or(default)` `.is_set()`
52+
53+
---
54+
55+
## 示例
56+
57+
### 最简:位置参数 + 选项
58+
59+
```cpp
60+
import std;
61+
import cmdline;
62+
63+
int main(int argc, char* argv[]) {
64+
using namespace cmdline;
65+
66+
App app("myapp");
67+
app.version("1.0.0")
68+
.description("My CLI")
69+
.arg("input").required().help("Input file")
70+
.option("verbose").short_name('v').help("Verbose")
71+
.option("config").short_name('c').takes_value().value_name("FILE").help("Config file");
72+
73+
auto result = app.parse(argc, argv);
74+
if (!result) {
75+
if (result.error() == "help requested" || result.error() == "version requested") return 0;
76+
std::println("Error: {}", result.error());
77+
return 1;
78+
}
79+
80+
const ParsedArgs& p = *result;
81+
if (p.is_flag_set("verbose")) std::println("Verbose on");
82+
if (auto c = p.value("config")) std::println("Config: {}", *c);
83+
std::println("Input: {}", p.positional(0));
84+
return 0;
85+
}
86+
```
87+
88+
### 多种输入
89+
90+
```cpp
91+
auto result = app.parse(argc, argv);
92+
auto result = app.parse_from(std::vector<std::string>{"myapp", "add", "x"});
93+
auto result = app.parse_from("myapp add x --yes");
94+
```
95+
96+
### 子命令 + action / run
97+
98+
```cpp
99+
App app("demo");
100+
app.version("0.1.0")
101+
.option("yes").global().help("Auto confirm")
102+
.subcommand("add")
103+
.description("Add a target")
104+
.arg("target").required()
105+
.arg("version").required()
106+
.action([](const ParsedArgs& a) {
107+
std::println("add: {}@{}", a.value("target").value_or(""), a.value("version").value_or(""));
108+
})
109+
.subcommand("remove")
110+
.description("Remove a target")
111+
.arg("target").required()
112+
.action([](const ParsedArgs& a) { std::println("remove: {}", a.positional(0)); });
113+
114+
auto result = app.parse(argc, argv);
115+
if (result) app.run(*result);
116+
```
117+
118+
### 自行分发子命令
119+
120+
```cpp
121+
auto result = app.parse(argc, argv);
122+
if (!result) return 1;
123+
const ParsedArgs& parsed = *result;
124+
if (parsed.has_subcommand()) {
125+
auto sub = parsed.subcommand();
126+
if (sub && parsed.subcommand_name() == "add") {
127+
const ParsedArgs& sub_args = sub->get();
128+
// 使用 sub_args.positional(0) 等
129+
}
130+
}
131+
```
132+
133+
### 传对象写法(与字符串链式等价)
134+
135+
```cpp
136+
app.option(Option("yes").long_opt("yes").global().help("Auto confirm"));
137+
app.arg(Arg("input").required().help("Input file"));
138+
app.subcommand(App("add").description("Add").arg(Arg("x").required()).action([](const ParsedArgs&) {}));
139+
```

examples/basic.cpp

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,27 @@ import cmdline;
44
int main(int argc, char* argv[]) {
55
using namespace cmdline;
66

7-
auto app = App("myapp")
8-
.version("1.0.0")
9-
.about("A demo CLI")
10-
.arg(Arg("input").help("Input file").required())
11-
.opt(Opt("verbose").short_name('v').long_opt("verbose").help("Verbose output"))
12-
.opt(Opt("config").short_name('c').long_opt("config").help("Config file").takes_value().value_name("FILE"));
7+
// 1) 定义:程序名、版本、位置参数、选项
8+
App app("myapp");
9+
app.version("1.0.0")
10+
.description("A demo CLI")
11+
.arg("input").required().help("Input file")
12+
.option("verbose").short_name('v').help("Verbose output")
13+
.option("config").short_name('c').takes_value().value_name("FILE").help("Config file");
1314

15+
// 2) 解析
1416
auto result = app.parse(argc, argv);
1517
if (!result) {
1618
if (result.error() == "help requested" || result.error() == "version requested")
1719
return 0;
1820
std::println("Error: {}", result.error());
1921
return 1;
2022
}
21-
const ParsedArgs& parsed = *result;
2223

23-
if (parsed.get_flag("verbose"))
24-
std::println("Verbose on");
25-
if (auto config = parsed.get_one("config"))
26-
std::println("Config: {}", *config);
27-
std::println("Input: {}", parsed.arg(0));
24+
// 3) 使用结果
25+
const ParsedArgs& p = *result;
26+
if (p.is_flag_set("verbose")) std::println("Verbose on");
27+
if (auto c = p.value("config")) std::println("Config: {}", *c);
28+
std::println("Input: {}", p.positional(0));
2829
return 0;
2930
}

examples/parse_from_string.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import std;
22
import cmdline;
33

4+
// 从字符串解析,适合测试或脚本内构造参数
45
int main() {
56
using namespace cmdline;
67

7-
auto app = App("tool")
8-
.version("1.0")
9-
.arg(Arg("cmd").required())
10-
.opt(Opt("verbose").long_opt("verbose").help("Verbose"));
8+
App app("tool");
9+
app.version("1.0")
10+
.arg("cmd").required()
11+
.option("verbose").help("Verbose");
1112

12-
// Parse from string (e.g. for tests)
1313
auto result = app.parse_from("tool add --verbose");
1414
if (!result) {
1515
std::println("Parse error: {}", result.error());
1616
return 1;
1717
}
18-
std::println("arg(0) = {}", result->arg(0));
19-
std::println("verbose = {}", result->get_flag("verbose"));
18+
19+
std::println("cmd = {}", result->positional(0));
20+
std::println("verbose = {}", result->is_flag_set("verbose"));
2021
return 0;
2122
}

examples/with_dispatch.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,33 @@ import cmdline;
44
int main(int argc, char* argv[]) {
55
using namespace cmdline;
66

7+
// 1) 定义:根选项 + 子命令(链式 .subcommand("name").arg/.action)
78
App app("demo");
89
app.version("0.1.0")
9-
.about("Demo: parse + dispatch with on_run")
10-
.opt("yes").global().help("Auto confirm")
10+
.description("Demo: parse + run with action")
11+
.option("yes").global().help("Auto confirm")
1112
.subcommand("add")
12-
.about("Add a target")
13+
.description("Add a target")
1314
.arg("target").required()
1415
.arg("version").required()
15-
.on_run([](const ParsedArgs& args) {
16-
auto target = args.get_one("target").value_or("");
17-
auto version = args.get_one("version").value_or("");
18-
std::println("add: {}@{}", target, version);
16+
.action([](const ParsedArgs& a) {
17+
std::println("add: {}@{}", a.value("target").value_or(""), a.value("version").value_or(""));
1918
})
2019
.subcommand("remove")
21-
.about("Remove a target")
20+
.description("Remove a target")
2221
.arg("target").required()
23-
.on_run([](const ParsedArgs& args) {
24-
std::println("remove: {}", args.arg(0));
25-
});
22+
.action([](const ParsedArgs& a) { std::println("remove: {}", a.positional(0)); });
2623

24+
// 2) 解析
2725
auto result = app.parse(argc, argv);
2826
if (!result) {
2927
if (result.error() == "help requested" || result.error() == "version requested")
3028
return 0;
3129
std::println("Error: {}", result.error());
3230
return 1;
3331
}
34-
app.dispatch(*result);
32+
33+
// 3) 根据解析结果执行对应动作
34+
app.run(*result);
3535
return 0;
3636
}

0 commit comments

Comments
 (0)