CmdParser 是一个模仿 cmdline 的轻量级的 C++ 命令行参数解析工具,支持解析标志(flags)和选项(options),并能够处理回调函数、类型转换以及剩余参数。

特点

  1. 标志解析

    • 支持定义简单的布尔标志,例如 --help--verbose
    • 标志是可选的,在解析过程中会对标记出现的次数计数
    • 可以通过回调函数来定义标志存在时的触发事件
  2. 选项解析

    • 支持带值的选项,例如 --scale=2.0--scale 2.0
    • 选项可以设置为可选或必选,在解析过程中会记录每次出现的选项的值
    • 支持的数据类型包括:intdoubleboolstringcharsize_t
    • 支持四种添加方式
      1. 提供默认值,提供回调函数检查
      2. 提供回调函数检查
      3. 提供默认值
      4. 都不提供
  3. 补充

    • 标志和选项的名称要求:
      • 必须提供一个--开头的,加上若干字母、数字、下划线组成的完整名称,区分大小写,例如--len;
      • 可以额外提供一个-开头的,加上单个字母或数字组成的缩写名称,例如-v;
      • 所有名称互异。
    • 标志和选项都需要提供说明字符串,可以使用空字符串

使用方法

创建 CmdParser 实例

1
auto parser = CmdParser{};

添加flag

例如

1
2
3
4
5
6
7
8
parser.add_flag({"--gzip", "-g"}, "use gzip");

parser.add_flag({"-v", "--verbose"}, "");

parser.add_flag({"--help","-h"}, "show help message", [&parser]() {
parser.print_usage();
exit(0);
});

在解析开始之前,如果没有设置名称为--help-h的flag或option,会自动按照上述形式进行添加。

添加option

下面是四种方式的示例

1
2
3
4
5
6
7
8
9
10
11
parser.add_option<int>({"-l", "--len"},
"option with default value and checker", false, 10,
[](int arg) { return arg >= 0; });

parser.add_option({"-n", "--num"}, "option with default value", false, 0);

parser.add_option<double>("--scale", "option with checker", true,
[](double arg) { return arg >= 0; });

parser.add_option<int>({"-w", "--weight"},
"option without default value and checker", false);

解析命令行参数

提供parse方法进行解析,返回布尔值

1
bool ok = parser.parse(argc, argv);

可以包装一下,解析错误时直接打印帮助信息并退出

1
2
3
4
if (!parser.parse(argc, argv)) {
parser.print_usage();
return 1;
}

默认提供的parse_check方法实现了类似的效果,可以直接使用

1
2
3
4
5
6
7
8
9
10
void parse_check(int argc, char *argv[]) {
// 如果解析失败,则打印帮助信息并退出程序
if (!parse(argc, argv)) {
print_usage();

// 如果完全没有提供参数,返回0,否则返回1
if (argc == 1) { exit(0); }
exit(1);
}
}

获取参数

对于flag,可以获取解析过程中出现的次数(通过完整名称或缩写均可)

1
2
3
if (auto n = parser.get_count("--gzip")) {
std::cout << "gzip count: " << n.value() << '\n';
}

对于option,可以获取对应的值,但是需要提供类型信息,并且这个类型需要和解析之前提供的一致

1
2
3
std::cout << "Scale: " << parser.get_option<double>("--scale").value()
<< '\n';
std::cout << "Len: " << parser.get_option<int>("--len").value() << '\n';

如果option被多次设置,那么get_option只会获取最后一次的值,可以可以通过get_option_all获取设置的所有值,返回对应的vector

1
2
3
4
5
if (auto weights = parser.get_option_all<int>("-w")) {
std::cout << "Weight: ";
for (const auto &w : *weights) { std::cout << w << ' '; }
std::cout << '\n';
}

某些情况下可能出现option没有值,此时get_option会返回一个std::nullopt,但是get_option_all会返回一个空的vector

收集多余参数

get_rest方法会返回未被解析的参数列表,保持原有的顺序。

1
2
3
4
auto rest = parser.get_rest();
for (const auto &s : rest) {
std::cout << s << '\n';
}

辅助方法

设置程序名称(否则在解析时自动获取argv[0]),用于在帮助信息中提供使用示例

1
parser.set_program_name("demo");

打印帮助信息

1
parser.print_usage();

帮助信息例如

1
2
3
4
5
6
7
8
9
10
11
usage: demo --scale=double ...
@required options:
--scale option with checker (double)
@options:
-l, --len option with default value and checker (int [=10])
-n, --num option with default value (int [=0])
-q, --quiet option without default value and checker (int)
@flags:
-g, --gzip use gzip
-v, --verbose
-h, --help print help message

将参数解析规则设置为严格或宽松,默认情况下使用宽松模式

1
2
parser.enable_strict_mode();
parser.disable_strict_mode();

严格和宽松模式的区别如下:

  • 在严格模式下:
    • flag只有一种方式设置:--verbose-v
    • option的键值对只有两种方式设置:
      • 分离:--len 1-l 1
      • 使用等号:--len=1-l=1
  • 在宽松模式下,额外支持如下写法:
    • flag的缩写形式支持合并,例如-abc会被拆分为-a -b -c,如果这些都是合法的flag缩写名称;
    • option支持键值合并写法,例如-l2会被拆分为-l 2--len3会被拆分为--len 3,如果拆分前只能在option范围内匹配到唯一的全称或缩写名称作为前缀,否则因为存在歧义,不会进行拆分。

源码

源码文件如下(其中使用了 concept,因此至少需要 C++20)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
#pragma once

#include <algorithm>
#include <functional>
#include <iomanip>
#include <iostream>
#include <optional>
#include <regex>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

namespace cmd_parser_detail {

// 选项支持的类型
template <typename T>
concept Supported =
std::disjunction_v<std::is_same<T, std::string>, std::is_same<T, bool>,
std::is_same<T, char>, std::is_same<T, int>,
std::is_same<T, double>, std::is_same<T, size_t>>;

template <Supported T>
static constexpr const char *get_type_id() {
if constexpr (std::is_same_v<T, std::string>) { return "string"; }
else if constexpr (std::is_same_v<T, bool>) { return "bool"; }
else if constexpr (std::is_same_v<T, char>) { return "char"; }
else if constexpr (std::is_same_v<T, int>) { return "int"; }
else if constexpr (std::is_same_v<T, double>) { return "double"; }
else if constexpr (std::is_same_v<T, std::size_t>) { return "size_t"; }
else { return "???"; }
}

template <Supported T>
static std::string get_default_value_str(const T &var) {
std::ostringstream ss;
ss << var;
return ss.str();
}

template <Supported T>
static std::string get_usage_str(const std::string &name) {
std::ostringstream ss;
ss << name << "=" << get_type_id<T>();
return ss.str();
}

// 转换值
template <typename T>
static std::optional<T> case_string_to(std::string value) {
if constexpr (std::is_same_v<T, std::string>) { return value; }
else if constexpr (std::is_same_v<T, bool>) {
std::ranges::transform(value, value.begin(), ::tolower);
return (value == "true" || value == "yes" || value == "ok"
|| value == "on" || value == "1");
}
else {
T result;
std::stringstream ss(value);

// bad cast
if (!(ss >> result && ss.eof())) { return std::nullopt; }

return result;
}
}

// 合法长名称
inline bool is_valid_full_name(const std::string &name) {
static const std::regex full_name_pattern(R"(^--[A-Za-z0-9_]{1,10}$)");
return std::regex_match(name, full_name_pattern);
}

// 合法短名称,允许空
inline bool is_valid_short_name(const std::string &name) {
static const std::regex short_name_pattern(R"(^-[A-Za-z0-9]?$)");
return name.empty() || std::regex_match(name, short_name_pattern);
}

// 但是长短名称不允许全空

} // namespace cmd_parser_detail

class CmdParser {
public:
class Item {
public:
Item(std::string first, std::string second)
: full_name(std::move(first)), short_name(std::move(second)) {
validate();
}

// NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
Item(std::string name) : full_name(std::move(name)) { validate(); }

// NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
Item(const char *name) : full_name(name) { validate(); }

std::string to_str() const {
return short_name.empty() ? full_name
: (short_name + ", " + full_name);
}

std::string full_name;
std::string short_name;

// 重载 operator==
bool operator==(const Item &other) const noexcept {
return full_name == other.full_name
&& short_name == other.short_name;
}

// 自定义哈希函数
struct Hash {
std::size_t operator()(const Item &item) const noexcept {
std::size_t h1 = std::hash<std::string>{}(item.full_name);
std::size_t h2 = std::hash<std::string>{}(item.short_name);
return h1 ^ (h2 << 1);
}
};

private:
void validate() {
if (!short_name.empty()
&& !cmd_parser_detail::is_valid_full_name(full_name)) {
// 如果存在第二个参数,并且第一个作为全称选项检查失败,可以尝试交换
std::swap(full_name, short_name);
}

if (!cmd_parser_detail::is_valid_full_name(full_name)) {
throw std::invalid_argument("Invalid full name: " + full_name);
}
if (!cmd_parser_detail::is_valid_short_name(short_name)) {
throw std::invalid_argument("Invalid short name: "
+ short_name);
}
}
};

// 执行回调函数
CmdParser &add_flag(const Item &flag, const std::string &desc,
std::function<void()> caller) {
check_and_update_names(flag);

m_flags.insert({flag, FlagInfo{.caller = caller,
.desc = desc,
.usage_str = flag.full_name}});
return *this;
}

// 不指定回调函数,只有计数功能
CmdParser &add_flag(const Item &flag, const std::string &desc) {
return add_flag(flag, desc, []() {});
}

// 添加选项,提供变量默认值,提供参数检查函数
template <cmd_parser_detail::Supported T>
CmdParser &add_option(const Item &option, const std::string &desc,
bool required, T default_value,
std::function<bool(T)> checker) {
check_and_update_names(option);

auto checker_wrapper = [checker](const std::string &value_str) {
auto parsed = cmd_parser_detail::case_string_to<T>(value_str);
if (!parsed.has_value()) { return false; } // 转换失败
return checker(parsed.value()); // 检查
};

std::string type_id = cmd_parser_detail::get_type_id<T>();
std::string usage_str =
cmd_parser_detail::get_usage_str<T>(option.full_name);
std::string default_value_str =
cmd_parser_detail::get_default_value_str<T>(default_value);

m_options.insert({option, OptionInfo{
.checker = checker_wrapper,
.desc = desc,
.usage_str = usage_str,
.type_id = type_id,
.default_value_str = default_value_str,
.required = required,
.value_list = {default_value_str},
}});

return *this;
}

// 添加选项,提供参数检查函数
template <cmd_parser_detail::Supported T>
CmdParser &add_option(const Item &option, const std::string &desc,
bool required, std::function<bool(T)> checker) {
check_and_update_names(option);

auto checker_wrapper = [checker](const std::string &value_str) {
auto parsed = cmd_parser_detail::case_string_to<T>(value_str);
if (!parsed.has_value()) { return false; } // 转换失败
return checker(parsed.value()); // 检查
};

std::string usage_str =
cmd_parser_detail::get_usage_str<T>(option.full_name);
std::string type_id = cmd_parser_detail::get_type_id<T>();

m_options.insert({option, OptionInfo{
.checker = checker_wrapper,
.desc = desc,
.usage_str = usage_str,
.type_id = type_id,
.default_value_str = "",
.required = required,
.value_list = {},
}});

return *this;
}

// 添加选项,提供变量默认值,自动生成参数检查函数
template <cmd_parser_detail::Supported T>
CmdParser &add_option(const Item &option, const std::string &desc,
bool required, T default_value) {
return add_option<T>(option, desc, required, default_value,
[](T) { return true; });
}

// 添加选项,自动生成参数检查函数
template <cmd_parser_detail::Supported T>
CmdParser &add_option(const Item &option, const std::string &desc,
bool required) {
return add_option<T>(option, desc, required, [](T) { return true; });
}

// 获取标志的计数,如果名称不存在,返回空
std::optional<size_t> get_count(const std::string &name) const {
if (const auto *p_flag = get_flag_via_name(name)) {
return m_flags.at(*p_flag).count;
}

return std::nullopt;
}

// 获取选项最后设置的值,如果选项名称不存在,或者类型不匹配,返回空
template <cmd_parser_detail::Supported T>
std::optional<T> get_option(const std::string &name) {
if (const auto *p_option = get_option_via_name(name)) { // 要求名称存在
std::string cur_type_id = cmd_parser_detail::get_type_id<T>();
if (m_options.at(*p_option).type_id
== cur_type_id) { // 要求类型一致
auto &value_list = m_options.at(*p_option).value_list;

// 异常情况下会为空
if (value_list.empty()) { return std::nullopt; }

// 获取最后一个值
auto last_value_str = value_list.back();
return cmd_parser_detail::case_string_to<T>(last_value_str);
}
}
return std::nullopt;
}

// 获取选项所有设置的值,如果选项名称不存在,或者类型不匹配,返回空
template <cmd_parser_detail::Supported T>
std::optional<std::vector<T>> get_option_all(const std::string &name) {
if (const auto *p_option = get_option_via_name(name)) { // 要求名称存在
std::string cur_type_id = cmd_parser_detail::get_type_id<T>();
if (m_options.at(*p_option).type_id
== cur_type_id) { // 要求类型一致
auto &value_list = m_options.at(*p_option).value_list;

// 异常情况下会为空,此时返回空列表
if (value_list.empty()) { return std::vector<T>{}; }

// 获取所有的值
std::vector<T> result;
for (auto &value_str : value_list) {
auto parsed = cmd_parser_detail::case_string_to<T>(value_str);
if (!parsed.has_value()) { return std::nullopt; }
result.push_back(parsed.value());
}

return result;
}
}
return std::nullopt;
}

bool parse(int argc, char *argv[]) {
// 如果解析之前没有设置程序名,则使用argv[0]作为程序名
if (m_program_name.empty()) { set_program_name(argv[0]); }

// 如果解析之前没有设置--help或-h,自动添加一个默认的--help选项
if (!(m_unique_names.contains("--help")
|| m_unique_names.contains("-h"))) {
add_flag({"--help", "-h"}, "print help message", [this]() {
print_usage();
exit(0);
});
}

std::vector<std::string> args = expand_args(argc, argv);
return parse_detail(args);
}

void parse_check(int argc, char *argv[]) {
// 如果解析失败,则打印帮助信息并退出程序
if (!parse(argc, argv)) {
print_usage();

// 如果完全没有提供参数,返回0,否则返回1
if (argc == 1) { exit(0); }
exit(1);
}
}

// 打印帮助信息
void print_usage() const {
std::cout << "usage: " << m_program_name << " ";
for (const auto &[option, info] : m_options) {
if (info.required) { std::cout << info.usage_str << " "; }
}
std::cout << "...\n";

std::vector<std::pair<std::string, std::string>> flag_descs;
std::vector<std::pair<std::string, std::string>> option_descs;
std::vector<std::pair<std::string, std::string>> required_option_descs;

size_t max_length = 0;

// 构造格式化字符串并计算最大长度用于对齐
for (const auto &[option, info] : m_options) {
std::string full_desc;
if (!info.desc.empty()) { // 有描述信息
// 如果必要,并且存在默认值
if (!(info.required || info.default_value_str.empty())) {
full_desc = info.desc + " (" + info.type_id
+ " [=" + info.default_value_str + "])";
}
else { full_desc = info.desc + " (" + info.type_id + ")"; }
}
else { full_desc = "(" + info.type_id + ")"; }

max_length = std::max(max_length, option.to_str().size());

if (info.required) {
required_option_descs.emplace_back(option.to_str(), full_desc);
}
else { option_descs.emplace_back(option.to_str(), full_desc); }
}
for (const auto &[flag, info] : m_flags) {
max_length = std::max(max_length, flag.to_str().size());

flag_descs.emplace_back(flag.to_str(), info.desc);
}

max_length += 4; // 空格对齐

// 输出选项和标志
if (!required_option_descs.empty()) {
std::cout << " @required options:\n";
}
for (const auto &[use_str, desc_str] : required_option_descs) {
std::cout << " " << std::left
<< std::setw(static_cast<int>(max_length)) << use_str
<< desc_str << '\n';
}

if (!option_descs.empty()) { std::cout << " @options:\n"; }
for (const auto &[use_str, desc_str] : option_descs) {
std::cout << " " << std::left
<< std::setw(static_cast<int>(max_length)) << use_str
<< desc_str << '\n';
}

if (!option_descs.empty()) { std::cout << " @flags:\n"; }
for (const auto &[use_str, desc_str] : flag_descs) {
std::cout << " " << std::left
<< std::setw(static_cast<int>(max_length)) << use_str
<< desc_str << '\n';
}
}

// 获取多余参数(保持原有顺序)
const std::vector<std::string> &get_rest() const { return m_rest; }

// 设置程序名称(如果没有设置,在parse阶段会自动尝试获取)
CmdParser &set_program_name(const std::string &program_name) {
m_program_name = program_name;
return *this;
}

// 解析时使用严格模式
CmdParser &enable_strict_mode() {
m_strict_mode = true;
return *this;
}

// 解析时关闭严格模式
CmdParser &disable_strict_mode() {
m_strict_mode = false;
return *this;
}

private:
struct OptionInfo {
const std::function<bool(const std::string &)> checker;

const std::string desc; // 描述信息(允许空)
const std::string usage_str; // 使用示例
const std::string type_id; // 类型信息
const std::string default_value_str; // 默认值
const bool required{false}; // 是否的必要参数

std::vector<std::string>
value_list; // 支持多次设置,每次的值都会被记录
};

struct FlagInfo {
const std::function<void()> caller;

const std::string desc; // 描述信息(允许空)
const std::string usage_str; // 使用示例

size_t count{0}; // 计数出现次数
};

std::unordered_map<Item, FlagInfo, Item::Hash> m_flags;
std::unordered_map<Item, OptionInfo, Item::Hash> m_options;
std::set<std::string> m_unique_names;
std::vector<std::string> m_rest;
std::string m_program_name;
bool m_strict_mode{false};

// 将参数替换为字符串,对于连续短选项尝试展开
std::vector<std::string> expand_args(int argc, char *argv[]) {
std::vector<std::string> result;
for (int i = 1; i < argc; ++i) { // 跳过第一个参数,即程序名
std::string arg = argv[i];

// 首先基于等号划分键值对
std::string name;
std::string value;
if (arg.find('=') != std::string::npos) {
size_t equal_pos = arg.find('=');
name = arg.substr(0, equal_pos);
value = arg.substr(equal_pos + 1);
}
else { name = arg; }

// 尝试进行拆分

// (1)
// 如果名称部分是一个合法的option名称(全称或缩写均可),有没有等号不影响
// 例如-a,--ab,值可能是下一项,不需要进行拆分
// 或者-a=1,--ab=1
if (get_option_via_name(name) != nullptr) {
result.push_back(name);
if (!value.empty()) { result.push_back(value); }

continue;
}

// (2)
// 如果名称部分是一个合法的flag名称(全称或缩写均可),并且不含有等号
// 例如-a,--ab,直接放进去即可,其实不需要拆分
if (get_flag_via_name(name) != nullptr && value.empty()) {
result.push_back(name);
continue;
}

// 下面的拆分仅在不含等号,非严格模式下进行
if (!m_strict_mode && value.empty()) {
// (s1)
// 如果名称部分是若干个合法的flag缩写名称的合并,将其分别拆开
// 例如-abc 拆为 -a -b -c
if (name.size() > 2 && name[0] == '-' && name[1] != '-') {
std::vector<std::string> expand_short_names;
bool expand_ok = true;
for (size_t j = 1; j < name.size(); ++j) {
auto short_name = "-" + std::string(1, name[j]);
if (get_flag_via_name(short_name) != nullptr) {
expand_short_names.push_back(short_name);
}
else { expand_ok = false; }
}

// 如果可以进行拆分
if (expand_ok) {
result.insert(result.end(), expand_short_names.begin(),
expand_short_names.end());
continue;
}
}

// (s2)
// 如果名称部分在option中存在唯一的前缀匹配,按照前缀匹配重新划分
// 例如-l2拆为-l2,--ab2 拆为 --ab 2
if ((get_unique_option_prefix(name)) != nullptr) {
const auto *p_matching_name =
get_unique_option_prefix(name);
value = name.substr(p_matching_name->size());

result.push_back(*p_matching_name);
result.push_back(value);
continue;
}
}

// 其他情况下保持原样
result.push_back(arg);
}

return result;
}

// 解析参数
bool parse_detail(std::vector<std::string> args) noexcept {
try {
size_t arg_size = args.size();
for (size_t i = 0; i < arg_size; ++i) {
const std::string& arg = args[i];

// 解析option
if (const auto *p_option = get_option_via_name(arg)) {
if (i + 1 >= arg_size)
throw std::runtime_error("Missing value for option: "
+ arg);

const std::string& value = args[++i]; // 获取选项的值

// 调用选项的检查函数
if (m_options.at(*p_option).checker(value)) {
m_options.at(*p_option).value_list.push_back(value);
}
else { // 转换或检查失败
std::string msg = "Failed to set option with value: "
+ p_option->to_str() + " = " + value;
throw std::runtime_error(msg);
}
}
// 解析flag
else if (const auto *p_flag = get_flag_via_name(arg)) {
m_flags.at(*p_flag).count++; // 计数器自增

// 调用标志的回调函数
m_flags.at(*p_flag).caller();
}
else { // 其它的多余参数,保持顺序地收集
m_rest.push_back(arg);
}
}

// 检查必需的选项是否提供
for (const auto &[option, info] : m_options) {
if (info.required && (info.value_list.empty())) {
throw std::runtime_error("Missing required option: "
+ option.to_str());
}
}

return true;
}
catch (const std::exception &e) {
std::cerr << "CmdParser error: " << e.what() << '\n';
return false;
}
}

const Item *get_flag_via_name(const std::string &name) const {
if (cmd_parser_detail::is_valid_full_name(name)) {
for (const auto &[flag, _] : m_flags) {
if (flag.full_name == name) { return &flag; }
}
}
else if (cmd_parser_detail::is_valid_short_name(name)) {
for (const auto &[flag, _] : m_flags) {
if (flag.short_name == name) { return &flag; }
}
}
return nullptr;
}

const Item *get_option_via_name(const std::string &name) const {
if (cmd_parser_detail::is_valid_full_name(name)) {
for (const auto &[option, _] : m_options) {
if (option.full_name == name) { return &option; }
}
}
else if (cmd_parser_detail::is_valid_short_name(name)) {
for (const auto &[option, _] : m_options) {
if (option.short_name == name) { return &option; }
}
}
return nullptr;
}

void check_and_update_names(const Item &item) {
if (m_unique_names.contains(item.full_name)) {
std::cerr << "CmdParser error: " << item.full_name
<< " already exists.";
exit(1);
}
else { m_unique_names.insert(item.full_name); }

if (!item.short_name.empty()) {
if (m_unique_names.contains(item.short_name)) {
std::cerr << "CmdParser error: " << item.short_name
<< " already exists.";
exit(1);
}
else { m_unique_names.insert(item.short_name); }
}
}

// 查找并返回option范围内唯一匹配的前缀指针
const std::string *get_unique_option_prefix(const std::string &str) const {
const std::string *match_prefix = nullptr;
size_t cnt = 0;
for (const auto &[option, _] : m_options) {
// 检查当前前缀是否是option的full_name的前缀
if (str.find(option.full_name) == 0) {
match_prefix = &(option.full_name);
cnt += 1;
}

if (option.short_name.empty()) continue;

// 检查当前前缀是否是option的short_name的前缀
if (str.find(option.short_name) == 0) {
match_prefix = &(option.short_name);
cnt += 1;
}
}

if (cnt == 1) { return match_prefix; }

return nullptr;
}
};

测试代码如下

test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "cmd_parser.hpp"

int main(int argc, char *argv[]) {
auto parser = CmdParser{};

parser.add_flag({"--gzip", "-g"}, "use gzip");
parser.add_flag({"-v", "--verbose"}, "");

parser.add_option<int>({"-l", "--len"},
"option with default value and checker", false, 10,
[](int arg) { return arg >= 0; });

parser.add_option({"-n", "--num"}, "option with default value", false, 0);

parser.add_option<double>("--scale", "option with checker", true,
[](double arg) { return arg >= 0; });

parser.add_option<int>({"-w", "--weight"},
"option without default value and checker", false);

parser.add_flag({"--help", "-h"}, "print help message", [&parser]() {
parser.print_usage();
exit(0);
});

// parser.enable_strict_mode();
parser.parse_check(argc, argv);

// if (!parser.parse(argc, argv)) {
// parser.print_usage();
// return 1;
// }

auto rest = parser.get_rest();
std::cout << "Rest:\n";
for (const auto &s : rest) { std::cout << s << '\n'; }

std::cout << "Scale: " << parser.get_option<double>("--scale").value()
<< '\n';
std::cout << "Len: " << parser.get_option<double>("--len").value_or(0)
<< '\n';

if (auto weights = parser.get_option_all<int>("-w")) {
std::cout << "Weight: ";
for (const auto &w : *weights) { std::cout << w << ' '; }
std::cout << '\n';
}

if (auto n = parser.get_count("--gzip")) {
std::cout << "gzip count: " << n.value() << '\n';
}

if (auto n = parser.get_count("--verbose")) {
std::cout << "verbose count: " << n.value() << '\n';
}

return 0;
}