Skip to content

Commit cf5e43d

Browse files
committed
update
1 parent 6af23e9 commit cf5e43d

File tree

8 files changed

+229
-80
lines changed

8 files changed

+229
-80
lines changed

README.md

Lines changed: 112 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,117 @@
11
# cmdline
22

3-
C++23 命令行解析库,API 见名知意,单 import
3+
> 基于 C++23 的命令行解析库,API 见名知意,单 import
44
5-
```bash
6-
cd cmdline && xmake -P .
7-
xmake run basic
8-
xmake -y run cmdline_test # 测试(-y 自动安装 gtest)
5+
[English](README.md) - 简体中文
6+
7+
[文档](docs/api.md) - [API 参考](docs/api.md#类型一览) - [示例](examples/)
8+
9+
使用 C++23 模块的类型安全命令行解析库。流式链式接口,零样板代码。支持位置参数、短/长选项、子命令、全局标志,以及基于 `std::expected` 的错误处理。
10+
11+
## ✨ 特性
12+
13+
- **C++23 模块**`import cmdline`
14+
15+
- **流式接口** — 链式构建方法,所见即所得的 API
16+
17+
- **子命令** — 支持 `action` 回调或手动分发的嵌套命令
18+
19+
- **全局选项** — 标志透明地传播到所有子命令
20+
21+
- **类型安全结果**`std::expected<ParsedArgs, std::string>`
22+
23+
- **多种解析模式** — 支持 `argc/argv``cmdline::Args`(即 `vector<string>`)或原始 `string_view`
24+
25+
## 快速开始
26+
27+
```cpp
28+
import std;
29+
import cmdline;
30+
31+
using namespace mcpplibs;
32+
33+
int main(int argc, char* argv[]) {
34+
auto app = cmdline::App("myapp")
35+
.version("1.0.0")
36+
.description("A demo CLI")
37+
.arg("input").required().help("输入文件")
38+
.option("verbose").short_name('v').help("详细输出")
39+
.option("config").short_name('c').takes_value().value_name("FILE").help("配置文件")
40+
.action([](const cmdline::ParsedArgs& p) {
41+
if (p.is_flag_set("verbose")) std::println("详细模式已开启");
42+
if (auto c = p.value("config")) std::println("配置文件: {}", *c);
43+
std::println("输入文件: {}", p.positional(0));
44+
});
45+
46+
auto result = app.parse(argc, argv);
47+
if (!result) {
48+
if (result.error() == "help requested" || result.error() == "version requested")
49+
return 0;
50+
std::println("Error: {}", result.error());
51+
return 1;
52+
}
53+
app.run(*result);
54+
return 0;
55+
}
56+
```
57+
58+
### 子命令 + action / run
59+
60+
```cpp
61+
auto app = cmdline::App("demo")
62+
.version("0.1.0")
63+
.description("Demo: subcommands with action dispatch")
64+
.option("yes").short_name('y').global().help("自动确认")
65+
.subcommand("add")
66+
.description("添加目标")
67+
.arg("target").required()
68+
.arg("version").required()
69+
.action([](const cmdline::ParsedArgs& a) {
70+
std::println("add: {}@{}", a.value("target").value_or(""),
71+
a.value("version").value_or(""));
72+
})
73+
.subcommand("remove")
74+
.description("移除目标")
75+
.arg("target").required()
76+
.action([](const cmdline::ParsedArgs& a) { std::println("remove: {}", a.positional(0)); });
77+
78+
auto result = app.parse(argc, argv);
79+
if (result) app.run(*result);
980
```
1081

11-
详见 [docs/api.md](docs/api.md)
82+
### 多种解析模式
83+
84+
```cpp
85+
auto r1 = app.parse(argc, argv);
86+
auto r2 = app.parse_from(cmdline::Args{"myapp", "add", "x", "1.0"});
87+
auto r3 = app.parse_from("myapp remove x --yes");
88+
```
89+
90+
## 构建
91+
92+
```shell
93+
xmake # 构建库
94+
xmake run basic # 运行基础示例
95+
xmake -y run cmdline_test # 运行测试(自动安装 gtest)
96+
```
97+
98+
## 集成到构建工具
99+
100+
### xmake
101+
102+
```lua
103+
add_requires("cmdline")
104+
105+
target("mytool")
106+
add_packages("cmdline")
107+
```
108+
109+
### cmake
110+
111+
```
112+
todo...
113+
```
114+
115+
## 📄 许可证
116+
117+
Apache-2.0

examples/basic.cpp

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
11
import std;
22
import cmdline;
33

4-
int main(int argc, char* argv[]) {
5-
using namespace cmdline;
4+
using namespace mcpplibs;
65

7-
// 1) 定义:程序名、版本、位置参数、选项
8-
App app("myapp");
9-
app.version("1.0.0")
6+
int main(int argc, char* argv[]) {
7+
auto app = cmdline::App("myapp")
8+
.version("1.0.0")
109
.description("A demo CLI")
1110
.arg("input").required().help("Input file")
1211
.option("verbose").short_name('v').help("Verbose output")
13-
.option("config").short_name('c').takes_value().value_name("FILE").help("Config file");
12+
.option("config").short_name('c').takes_value().value_name("FILE").help("Config file")
13+
.action([](const cmdline::ParsedArgs& p) {
14+
if (p.is_flag_set("verbose")) std::println("Verbose on");
15+
if (auto c = p.value("config")) std::println("Config: {}", *c);
16+
std::println("Input: {}", p.positional(0));
17+
});
1418

15-
// 2) 解析
1619
auto result = app.parse(argc, argv);
1720
if (!result) {
1821
if (result.error() == "help requested" || result.error() == "version requested")
1922
return 0;
2023
std::println("Error: {}", result.error());
2124
return 1;
2225
}
23-
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));
26+
app.run(*result);
2927
return 0;
3028
}

examples/parse_from_string.cpp

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

4+
using namespace mcpplibs;
5+
46
// 从字符串解析,适合测试或脚本内构造参数
57
int main() {
6-
using namespace cmdline;
7-
8-
App app("tool");
9-
app.version("1.0")
8+
auto app = cmdline::App("tool")
9+
.version("1.0")
1010
.arg("cmd").required()
11-
.option("verbose").help("Verbose");
11+
.option("verbose").help("Verbose")
12+
.action([](const cmdline::ParsedArgs& p) {
13+
std::println("cmd = {}", p.positional(0));
14+
std::println("verbose = {}", p.is_flag_set("verbose"));
15+
});
1216

1317
auto result = app.parse_from("tool add --verbose");
1418
if (!result) {
1519
std::println("Parse error: {}", result.error());
1620
return 1;
1721
}
18-
19-
std::println("cmd = {}", result->positional(0));
20-
std::println("verbose = {}", result->is_flag_set("verbose"));
22+
app.run(*result);
2123
return 0;
2224
}

examples/with_dispatch.cpp

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
import std;
22
import cmdline;
33

4-
int main(int argc, char* argv[]) {
5-
using namespace cmdline;
4+
using namespace mcpplibs;
65

7-
// 1) 定义:根选项 + 子命令(链式 .subcommand("name").arg/.action)
8-
App app("demo");
9-
app.version("0.1.0")
6+
int main(int argc, char* argv[]) {
7+
auto app = cmdline::App("demo")
8+
.version("0.1.0")
109
.description("Demo: parse + run with action")
1110
.option("yes").short_name('y').global(true).help("Auto confirm")
1211
.subcommand("add")
1312
.description("Add a target")
1413
.arg("target").required()
1514
.arg("version").required()
16-
.action([](const ParsedArgs& args) {
15+
.action([](const cmdline::ParsedArgs& args) {
1716
std::println("add: {}@{}", args.value("target").value_or(""), args.value("version").value_or(""));
1817
})
1918
.subcommand("remove")
2019
.description("Remove a target")
2120
.arg("target").required()
22-
.action([](const ParsedArgs& args) { std::println("remove: {}", args.positional(0)); });
21+
.action([](const cmdline::ParsedArgs& args) { std::println("remove: {}", args.positional(0)); });
2322

24-
// 2) 解析
2523
auto result = app.parse(argc, argv);
2624
if (!result) {
2725
if (result.error() == "help requested" || result.error() == "version requested")
@@ -30,7 +28,6 @@ int main(int argc, char* argv[]) {
3028
return 1;
3129
}
3230

33-
// 3) 根据解析结果执行对应动作
3431
app.run(*result);
3532
return 0;
3633
}

src/cmdline.cppm

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import std;
55
export import :options;
66
export import :parse;
77

8-
namespace cmdline {
8+
namespace mcpplibs::cmdline {
99

1010
class SubcommandBuilder;
11+
class SubcommandArgBuilder;
1112
class OptBuilder;
1213
class ArgBuilder;
1314

15+
export using Args = std::vector<std::string>;
1416
export using detail::Arg;
1517
export using detail::Option;
1618
export using detail::OptionValue;
@@ -414,6 +416,7 @@ private:
414416

415417
// 链式子命令构建器:.subcommand("add").description("...").action(...) 或 .subcommand("remove")...
416418
class SubcommandBuilder {
419+
friend class SubcommandArgBuilder;
417420
public:
418421
SubcommandBuilder(App* parent, App sub_app) : parent_(parent), sub_(std::move(sub_app)) {}
419422
SubcommandBuilder(SubcommandBuilder&& other) noexcept
@@ -438,11 +441,18 @@ public:
438441
SubcommandBuilder& author(std::string_view author_str) { (void)sub_.author(author_str); return *this; }
439442
SubcommandBuilder& description(std::string_view desc) { (void)sub_.description(desc); return *this; }
440443
SubcommandBuilder& arg(Arg argument) { (void)sub_.arg(std::move(argument)); return *this; }
441-
ArgBuilder arg(std::string_view name);
444+
SubcommandArgBuilder arg(std::string_view name);
442445
SubcommandBuilder& option(Option opt) { (void)sub_.option(std::move(opt)); return *this; }
443446
OptBuilder option(std::string_view name);
447+
448+
// Commits this subcommand to the parent App and returns App& to enable further chaining.
444449
template <typename Fn>
445-
SubcommandBuilder& action(Fn&& fn) { (void)sub_.action(std::forward<Fn>(fn)); return *this; }
450+
App& action(Fn&& fn) {
451+
(void)sub_.action(std::forward<Fn>(fn));
452+
App* p = parent_;
453+
commit();
454+
return *p;
455+
}
446456

447457
SubcommandBuilder& subcommand(std::string_view name) {
448458
commit();
@@ -469,6 +479,42 @@ private:
469479
bool committed_ = false;
470480
};
471481

482+
// 子命令内参数链式构建器:保持在 SubcommandBuilder 上下文中,使整条链能正确返回根 App&。
483+
class SubcommandArgBuilder {
484+
public:
485+
SubcommandArgBuilder(SubcommandArgBuilder&& other) noexcept
486+
: sb_(other.sb_), arg_(std::move(other.arg_)), committed_(other.committed_) {
487+
other.sb_ = nullptr;
488+
}
489+
SubcommandArgBuilder& operator=(SubcommandArgBuilder&&) = delete;
490+
SubcommandArgBuilder(const SubcommandArgBuilder&) = delete;
491+
SubcommandArgBuilder& operator=(const SubcommandArgBuilder&) = delete;
492+
~SubcommandArgBuilder() { commit(); }
493+
494+
SubcommandArgBuilder& required(bool r = true) { (void)arg_.required(r); return *this; }
495+
SubcommandArgBuilder& help(std::string_view h) { (void)arg_.help(h); return *this; }
496+
SubcommandArgBuilder& default_value(std::string_view v) { (void)arg_.default_value(v); return *this; }
497+
498+
SubcommandArgBuilder arg(std::string_view name) { commit(); return sb_->arg(name); }
499+
500+
template <typename Fn>
501+
App& action(Fn&& fn) { commit(); return sb_->action(std::forward<Fn>(fn)); }
502+
503+
private:
504+
SubcommandArgBuilder(SubcommandBuilder* sb, Arg a) : sb_(sb), arg_(std::move(a)) {}
505+
void commit() {
506+
if (sb_ && !committed_) {
507+
(void)sb_->sub_.arg(std::move(arg_));
508+
committed_ = true;
509+
}
510+
}
511+
SubcommandBuilder* sb_ = nullptr;
512+
Arg arg_;
513+
bool committed_ = false;
514+
515+
friend class SubcommandBuilder;
516+
};
517+
472518
inline SubcommandBuilder App::subcommand(std::string_view name) {
473519
return SubcommandBuilder(this, App(name));
474520
}
@@ -493,8 +539,8 @@ inline SubcommandBuilder ArgBuilder::subcommand(std::string_view name) {
493539
inline OptBuilder SubcommandBuilder::option(std::string_view name) {
494540
return OptBuilder(&sub_, Option(name));
495541
}
496-
inline ArgBuilder SubcommandBuilder::arg(std::string_view name) {
497-
return ArgBuilder(&sub_, Arg(name));
542+
inline SubcommandArgBuilder SubcommandBuilder::arg(std::string_view name) {
543+
return SubcommandArgBuilder(this, Arg(name));
498544
}
499545

500-
} // namespace cmdline
546+
} // namespace mcpplibs::cmdline

src/options.cppm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export module cmdline:options;
44

55
import std;
66

7-
namespace cmdline::detail {
7+
namespace mcpplibs::cmdline::detail {
88

99
export struct Arg {
1010
std::string name;
@@ -80,4 +80,4 @@ export struct Option {
8080
}
8181
};
8282

83-
} // namespace cmdline::detail
83+
} // namespace mcpplibs::cmdline::detail

src/parse.cppm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export module cmdline:parse;
44

55
import std;
66

7-
namespace cmdline::detail {
7+
namespace mcpplibs::cmdline::detail {
88

99
/// 选项解析结果:同时表示 flag(用 count)和带值/多值选项(用 values)。
1010
/// 不用 std::optional<std::string> 的原因:flag 无值且需出现次数;multiple 需多个值。
@@ -62,4 +62,4 @@ export struct ParsedArgs {
6262
}
6363
};
6464

65-
} // namespace cmdline::detail
65+
} // namespace mcpplibs::cmdline::detail

0 commit comments

Comments
 (0)