CLIUtils/CLI11
GitHub: CLIUtils/CLI11
CLI11是一个轻量级的C++命令行解析器,通过简洁的接口提供丰富的参数处理功能,解决复杂工具的命令行构建需求。
Stars: 4266 | Forks: 442
# CLI11:用于 C++11 的命令行解析器

[![Build Status Azure][azure-badge]][azure]
[![Actions Status][actions-badge]][actions-link]
[![Code Coverage][codecov-badge]][codecov]
[![Codacy Badge][codacy-badge]][codacy-link]
[](./LICENSE) [][doi-link]
[][gitter]
[][github releases]
[][repology]
[][conan-link]
[][conda-link]
[][wandbox-link]
[新功能](./CHANGELOG.md) • [文档][gitbook] • [API
参考][api-docs]
CLI11 是一个用于 C++11 及更高版本的命令行解析器,它提供了丰富的功能集和简单直观的接口。
## 目录
- [CLI11:用于 C++11 的命令行解析器](#cli11-command-line-parser-for-c11)
- [目录](#table-of-contents)
- [背景](#background)
- [简介](#introduction)
- [为什么还要写一个 CLI 解析器?](#why-write-another-cli-parser)
- [其他解析器](#other-parsers)
- [本库不支持的特性](#features-not-supported-by-this-library)
- [安装](#install)
- [使用](#usage)
- [添加选项](#adding-options)
- [选项类型](#option-types)
- [示例](#example)
- [选项设置](#option-options)
- [验证器](#validators)
- [默认验证器](#default-validators)
- [可能被禁用的验证器 🆕](#validators-that-may-be-disabled-)
- [额外验证器 🆕](#extra-validators-)
- [验证器用法](#validator-usage)
- [转换验证器](#transforming-validators)
- [验证器操作](#validator-operations)
- [自定义验证器](#custom-validators)
- [查询验证器](#querying-validators)
- [获取结果](#getting-results)
- [子命令](#subcommands)
- [子命令选项](#subcommand-options)
- [回调](#callbacks)
- [选项组](#option-groups)
- [配置文件](#configuration-file)
- [继承默认值](#inheriting-defaults)
- [格式化](#formatting)
- [子类化](#subclassing)
- [工作原理](#how-it-works)
- [Unicode 支持](#unicode-support)
- [关于使用 Unicode 路径的说明](#note-on-using-unicode-paths)
- [工具](#utilities)
- [其他库](#other-libraries)
- [API](#api)
- [示例](#examples)
- [贡献](#contribute)
- [许可证](#license)
在上一个小版本发布中添加的特性会标有"🆕"。仅在 main 分支中可用的特性会标有"🚧"。
## 背景
### 简介
CLI11 提供了你在强大的命令行解析器中所期望的所有功能,拥有优美、极简的语法,且除了 C++11 之外没有其他依赖。它是 header-only 的,并提供单文件形式,便于在项目中包含。它易于在小型项目中使用,但也足够强大以应对复杂的命令行项目,并且可以为框架进行定制。它已在 [Azure][] 和 [GitHub Actions][actions-link] 上经过测试,最初被 [GooFit GPU 拟合框架][goofit] 使用。它的设计灵感来源于 Python 的 [`plumbum.cli`][plumbum]。CLI11 在此 README 中提供了一个友好的简介,在 [GitBook][] 上有更深入的教程,以及由 Travis 生成的 [API 文档][api-docs]。有关当前和过去版本的详细信息,请参阅[更新日志](./CHANGELOG.md)或 [GitHub Releases][]。你也可以查看 [Version 1.0 post][]、[Version 1.3 post][]、[Version 1.6 post][] 或 [Version 2.0 post][] 了解更多信息。
你可以在 RSS 阅读器(如 Feedly)上订阅
,或者使用 GitHub 的 watching 工具的 releases 模式,以便在新版本发布时收到通知。
### 为什么还要写一个 CLI 解析器?
一个可接受的 CLI 解析器库应该具备以下所有特点:
- 易于包含(即 header-only,如果可能的话最好是单文件,**无外部依赖**)。
- 简短、简单的语法:这是使用 CLI 解析器的主要原因之一,它应该让从命令行获取变量变得几乎与定义任何其他变量一样容易。如果你的大部分程序都隐藏在 CLI 解析中,这对于可读性来说是个问题。
- C++11 或更高版本:应支持 GCC 4.8+(CentOS/RHEL 7 默认)、Clang 3.4+、AppleClang 7+、NVCC 7.0+ 或 MSVC 2015+。
- 支持 Linux、macOS 和 Windows。
- 在所有常见平台和编译器上都经过良好测试。"良好"定义为具有由 [CodeCov][] 衡量的高覆盖率。
- 清晰的帮助信息打印。
- 友好的错误提示。
- 自然地支持标准 shell 惯用法,如组合标志、位置分隔符等。
- 易于执行,帮助、解析错误等提供正确的退出代码和详细信息。
- 易于作为向用户提供“应用程序”的框架的一部分进行扩展。
- 可用的子命令语法,支持多个子命令、嵌套子命令、选项组和可选的回退机制(稍后解释)。
- 能够添加配置文件(`TOML`、`INI` 或自定义格式),并且也能生成配置文件。
- 生成可以在代码中直接使用的真实值,而不是你需要花费计算时间去查找的东西,适用于 HPC 应用程序。
- 支持常见类型、简单的自定义类型,并可扩展至特殊类型。
- 宽松的许可证。
### 其他解析器
C++ 的主要 CLI 解析器包括,带有我个人偏见的看法:(点击展开)
| 库 | 我的个人偏见 |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Boost Program Options][] | 如果你已经依赖于 Boost,这是一个很棒的库,但它的 C++11 之前的语法确实很奇怪,而且关于如何在 main 函数中设置正确的调用,其文档记录很少(而且几乎需要一整页代码)。最初开发了一个简单的 Boost 库包装器,但随着 CLI11 变得越来越强大,它被废弃了。捕获值并对其进行设置的想法起源于 Boost PO。[查看此比较。][cli11-po-compare] |
| [The Lean Mean C++ Option Parser][] | 一个头文件很棒,但在我看来,它的语法太糟糕了。包装这种语法或在复杂的项目中使用它非常不切实际。它似乎能很好地处理标准解析。 |
| [TCLAP][] | 不太标准的命令行解析导致常见的快捷方式失败。它似乎也缺乏支持,只接受最少的错误修复。仅包含头文件,但文件数量不少。至今仍未获得足够的支持以迁移到 GitHub。没有子命令。生成包装后的值。 |
| [Cxxopts][] | C++11,单文件,出色的 CMake 支持,但需要 regex,因此 GCC 4.8(CentOS 7 默认)无法工作。语法紧密基于 Boost PO,所以不够理想但很熟悉。 |
| [DocOpt][] | 在 C++11 中处理程序选项的完全不同的方法,你编写文档,接口会自动生成。过于脆弱和专业化。 |
在我写了这些之后,我还发现了以下库:
| 库 | 我的个人偏见 |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [GFlags][] | Google 命令行标志库。大量使用宏,范围有限,缺少子命令之类的东西。它提供了简单的语法并支持配置文件/环境变量。 |
| [GetOpt][] | 非常有限的 C 语言解决方案,冗长且复杂的语法。不支持很多东西,比如生成帮助信息。不过,在 UNIX 上总是可用的(但有不同的版本)。 |
| [ProgramOptions.hxx][] | 有趣的库,功能较弱且没有子命令。不错的回调系统。 |
| [Args][] | 也很感兴趣,并支持子命令。我喜欢类似 optional 的设计,但 CLI11 更简洁,提供直接的值访问,并且不那么冗长。 |
| [Argument Aggregator][] | 我是 [fmt][] 库的超级粉丝,try-catch 语句看起来很熟悉。:thumbsup: 似乎不支持子命令。 |
| [Clara][] | 为出色的 [Catch][] 测试框架构建的简单库。语法独特,范围有限。 |
| [Argh!][] | 非常简约的 C++11 解析器,单头文件。没有很多功能。竟然没有帮助信息生成?!?!至少它是无异常的。 |
| [CLI][] | 自定义语言和解析器。庞大的构建系统大材小用,收益甚微。最后一次发布在 2009 年,但偶尔仍有活动。 |
| [argparse][] | C++17 单文件参数解析器。设计在某些方面似乎与 CLI11 类似。作者还有其他几个有趣的项目。 |
| [lyra][] | 一个简单的带有可组合选项的仅头文件解析器。可能适用于简单的标准化解析 |
查看 [Awesome C++][] 获取较少偏见的解析器列表。你还可以在 [Single file libs][] 找到其他单文件库。
这些库中没有一个能满足上述所有要求,或者真的甚至接近这些要求。正如你可能已经猜到的那样,CLI11 做到了。因此,这个库被设计为提供出色的语法、良好的编译器兼容性以及最小的安装麻烦。
### 本库不支持的特性
有一些其他的可能“特性”是本库故意不支持的:
- 补全部分选项,例如 Python 的 `argparse` 为不完整的参数提供的功能。最好不去猜测。大多数 Python 的第三方命令行解析器实际上重新实现了命令行解析,而不是使用 argparse,正是因为这个被认为是设计缺陷的地方(最近的版本确实提供了一个禁用它的选项)。最近发布的 CLI11 确实包含了针对选项前缀的部分选项匹配 🆕。这通过 `.allow_subcommand_prefix_matching()` 启用,并附带一个生成建议近似匹配的示例。
- 自动补全:这最终可能会被添加到 Plumbum 和 CLI11 中,但目前尚不支持。
- 虽然不推荐,CLI11 现在确实支持非标准选项名称,例如 `-option`。这通过在 app 上使用 `allow_non_standard_option_names()` 修饰符来启用。
## 安装
使用时,这里描述了最常用的方法,额外的方法和详细信息可在[安装说明][installation]获取:
- 多合一本地头文件:从[最新发布版][github releases]复制 `CLI11.hpp` 到你的 include 目录中即可。这是每个发布版由源文件组合而成的。它包含整个命令解析器库,但不包括独立的实用程序(如 `Timer`、`AutoTimer`)。这些实用程序是完全独立的,可以单独复制。
- 多合一全局头文件:与上面类似,但将文件复制到共享文件夹位置,如 `/opt/CLI11`。然后,必须扩展 C++ include 路径以指向此文件夹。使用 CMake 3.10+ 时,使用 `include_directories(/opt/CLI11)`
- 对于其他方法,包括使用 CMake、conan 或 vcpkg 以及一些针对 GCC 8 或 WASI 的特定说明,请参见[安装说明][installation]。
## 使用
### 添加选项
要进行设置、添加选项并运行,你的 main 函数看起来会像这样:
```
int main(int argc, char** argv) {
CLI::App app{"App description"};
argv = app.ensure_utf8(argv);
std::string filename = "default";
app.add_option("-f,--file", filename, "A help string");
CLI11_PARSE(app, argc, argv);
return 0;
}
```
添加选项时,名称不应相互冲突,如果添加了一个选项,或者修改了会导致命名冲突的修饰符,在 `add_option` 方法中会抛出运行时错误。这包括帮助 `-h, --help` 的默认选项。有关 `ensure_utf8` 的更多信息,请参阅下面关于 [Unicode 支持](#unicode-support) 的部分。
注意:如果你不喜欢宏,这是该宏展开后的样子:(点击展开)
```
try {
app.parse(argc, argv);
} catch (const CLI::ParseError &e) {
return app.exit(e);
}
```
try/catch 块确保 `-h,--help` 或解析错误将以正确的返回码(从 `CLI::ExitCodes` 中选择)退出。(此处的 return 应该在 `main` 内部)。你不应该假设选项值已在 catch 块中设置;例如,帮助标志有意地短路所有其他处理以提高速度,并确保必需的选项等不会干扰。
初始化只需一行,添加选项每个只需两行。解析宏只需一行(或宏的内容需要5 行)。在 app 运行后,如果传递了文件名,它将被设置为正确的值,否则将被设置为默认值。你可以使用 `app.count("--file")` 检查它是否是在命令行中传递的。
#### 选项类型
虽然所有选项在内部都是相同的类型,但根据你的需要,有几种添加选项的方法。支持的值有:
```
// Add options
app.add_option(option_name, help_str="")
app.add_option(option_name,
variable_to_bind_to, // bool, char(see note), int, float, vector, enum, std::atomic, or string-like, or anything with a defined conversion from a string or that takes an int, double, or string in a constructor. Also allowed are tuples, std::array or std::pair. Also supported are complex numbers, wrapper types, and containers besides vectors of any other supported type.
help_string="")
app.add_option_function(option_name,
function , // type can be any type supported by add_option
help_string="")
// char as an option type is supported before 2.0 but in 2.0 it defaulted to allowing single non numerical characters in addition to the numeric values.
// There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value. For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type
app.add_option(option_name,
T &output, // output must be assignable or constructible from a value of type XC
help_string="")
// Add flags
app.add_flag(option_name,
help_string="")
app.add_flag(option_name,
variable_to_bind_to, // bool, int, float, complex, containers, enum, std::atomic, or string-like, or any singular object with a defined conversion from a string like add_option
help_string="")
app.add_flag_function(option_name,
function ,
help_string="")
app.add_flag_callback(option_name,function,help_string="")
// Add subcommands
App* subcom = app.add_subcommand(name, description);
Option_group *app.add_option_group(name,description);
```
选项名称可以以除 ('-', ' ', '\n' 和 '!') 之外的任何字符开头。对于长选项,在第一个字符之后,除 ('=',':','{',' ', '\n') 之外的所有字符都是允许的。对于 `add_flag*` 函数,'{' 和 '!' 具有特殊含义,这就是为什么不允许使用它们的原因。名称以逗号分隔的字符串形式给出,带有一个或多个破折号。一个选项或标志可以有任意多个名称,之后,在使用 `count` 时,你可以使用任何名称,并根据需要使用破折号来计算选项。其中一个名称可以在没有前置破折号的情况下给出;如果存在,该选项就是一个位置选项,并且该名称将用于帮助行中其位置形式。字符串 `++` 也不允许作为选项名称,因为它在配置文件中用作数组分隔符和标记。
`add_option_function(...` 函数通常需要给出模板参数,除非传递了完全匹配的 `std::function` 对象。该类型可以是 `add_option` 函数支持的任何类型。如果值无效,函数应抛出错误(可能是 `CLI::ConversionError` 或 `CLI::ValidationError`)。
双参数模板重载可用于你想限制输入的情况,例如
```
double val
app.add_option("-v",val);
```
这将首先验证输入可转换为 `unsigned int`,然后再赋值。或者使用某种变体类型
```
using vtype=std::variant;
vtype v1;
app.add_option("--vs",v1);
app.add_option("--vi",v1);
app.add_option("--vf",v1);
```
否则输出将默认为字符串。`add_option` 可用于任何整型或浮点类型、枚举或字符串。或者任何在赋值运算符或构造函数中接受 int、double 或 std::string 的类型。如果一个对象可以接受这些的多种形式,std::string 优先,然后是 double,最后是 int。为了更好地控制使用哪一种,或使用另一种类型进行底层转换,请使用双参数模板直接指定转换类型。
诸如 (std 或 boost) `optional`、`optional` 和 `optional` 以及任何其他包装类型直接受支持。出于 CLI11 的目的,包装类型是那些具有 `value_type` 定义的类型。有关如何为其他类型添加自定义转换器的信息,请参阅 [CLI11 Advanced Topics/Custom Converters][]。
向量类型也可用于双参数模板重载
```
std::vector v1;
app.add_option,int>("--vs",v1);
```
这将加载一个 double 向量,但确保所有值都可以表示为整数。
使用 `default_str(...)` 或 `default_val(...)` 设置选项或标志的默认字符串或值。使用 `->default_function(std::string())` 直接自定义默认捕获函数。然后通过调用 `->capture_default_str()` 捕获默认值。
通过 `add_flag*` 函数指定的标志选项允许一种针对选项名称的语法,以将特定选项默认为 false 值,或者如果传递了某些标志则为任何其他值。例如:
```
app.add_flag("--flag,!--no-flag",result,"help for flag");
```
指定如果在命令行中传递了 `--flag`,结果将为 true 或包含值 1。如果传递了 `--no-flag`,如果 `result` 是有符号整数类型,`result` 将包含 false 或 -1,如果是无符号类型则为 0。该语法的另一种形式更加明确:`"--flag,--no-flag{false}"`;这与前面的例子等效。这也适用于短格式选项 `"-f,!-n"` 或 `"-f,-n{false}"`。如果 `variable_to_bind_to` 不是整数值,默认行为是采用给出的最后一个值,而如果 `variable_to_bind_to` 是整数类型,行为将是对所有给定的参数求和并返回结果。如果需要,可以通过更改每个标志上的 `multi_option_policy` 来修改此行为(这是不继承的)。默认值可以是任何值。例如,如果你想定义一个数字标志:
```
app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag")
```
在命令行上使用这些标志中的任何一个都将导致输出中出现指定的数字。对于字符串值和枚举也可以做类似的事情,只要默认值可以转换为给定的类型即可。
在 C++14 编译器上,你可以直接将回调函数传递给 `.add_flag`,而在 C++11 模式下,如果你想要回调函数,则需要使用 `.add_flag_function`。该函数将接收该标志被传递的次数。你可以抛出相关的 `CLI::ParseError` 来表示失败。
#### 示例
- `"one,-o,--one"`:只要不是标志就有效,将创建一个可以按位置指定的选项,或者使用 `-o` 或 `--one`
- `"this"` 只能按位置传递
- `"-a,-b,-c"`:非位置选项名称的数量没有限制
add 命令返回指向内部存储的 `Option` 的指针。该选项可以直接用于在解析后检查计数 (`->count()`),以避免基于字符串的查找。
#### 选项设置
在解析之前,你可以设置以下选项:
- `->required()`:如果此选项不存在,程序将退出。这在 Plumbum 中是 `mandatory`,但必填选项似乎是一个更标准的术语。为了兼容性,`->mandatory()` 也有效。
- `->expected(N)`:接受 `N` 个值而不是尽可能多的值,仅适用于向量参数。如果为负数,则至少需要 `-N`;以 `--` 或另一个已识别的选项或子命令结束。
- `->expected(MIN,MAX)`:设置伴随选项的期望值范围。`expected(0,1)` 等同于制作一个标志。
- `->type_name(typename)`:设置选项类型的名称(`type_name_fn` 允许使用函数代替)
- `->type_size(N)`:设置选项值的固有大小。如果为负数,解析器将要求此数字的倍数。大多数情况下这是自动检测的,尽管可以针对特定用例进行修改。
- `->type_size(MIN,MAX)`:将选项的固有大小设置为一个范围。
- `->needs(opt)`:此选项需要另一个选项也存在,opt 是一个 `Option` 指针。可以使用 `remove_needs(opt)` 从 `needs` 中移除选项。该选项也可以使用包含选项名称的字符串来指定
- `->excludes(opt)`:不能在 `opt` 存在的情况下给出此选项,opt 是一个 `Option` 指针。也可以作为包含选项名称的字符串给出。可以使用 `->remove_excludes(opt)` 从 excludes 列表中移除选项
- `->envname(name)`:如果存在且未在命令行中传递,则从环境中获取值。该值还必须通过任何验证器才能被使用。
- `->group(name)`:将选项放入的帮助组。对位置选项无效。默认为 `"Options"`。给定空字符串的选项将不会显示在帮助打印中(隐藏)。
- `->ignore_case()`:忽略命令行中的大小写(也适用于子命令,不影响参数)。
- `->ignore_underscore()`:忽略选项名称中的任何下划线(也适用于子命令,不影响参数)。例如 "option_one" 将匹配 "optionone"。这不适用于短格式选项,因为它们只有一个字符
- `->disable_flag_override()`:在命令行中,长格式标志选项可以使用 `=` 表示法 `--flag=value` 在命令行上赋值。如果不希望此行为,`disable_flag_override()` 会禁用它,如果在命令行中这样做,将生成异常。`=` 不适用于短格式标志选项。
- `->allow_extra_args(true/false)`:如果设置为 true,选项将接受无限数量的参数,如向量;如果设置为 false,它将参数数量限制为选项中使用的类型大小。默认值取决于使用类型的性质,容器默认为 true,其他默认为 false。
- `->delimiter(char)`:允许指定自定义分隔符,用于将单个参数分隔为向量参数,例如在一个选项上指定 `->delimiter(',')` 将导致 `--opt=1,2,3` 生成向量的 3 个元素,并且相当于 --opt 1 2 3(假设 opt 是一个向量值)。
- `->description(str)`:设置/更改描述。
- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`:设置多选项策略。可用的快捷方式:`->take_last()`、`->take_first()`、`->take_all()` 和 `->join()`。这只会影响期望 1 个参数的选项或布尔标志(它们不继承其默认值,而是始终以特定策略开始)。`->join(delim)` 也可用于使用特定分隔符进行连接。这等效于调用 `->delimiter(delim)` 和 `->join()`。有效值为 `CLI::MultiOptionPolicy::Throw`、`CLI::MultiOptionPolicy::Throw`、`CLI::MultiOptionPolicy::TakeLast`、`CLI::MultiOptionPolicy::TakeFirst`、`CLI::MultiOptionPolicy::Join`、`CLI::MultiOptionPolicy::TakeAll`、`CLI::MultiOptionPolicy::Sum` 和 `CLI::MultiOptionPolicy::Reverse`。
- `->check(std::string(const std::string &), validator_name="",validator_description="")`:定义检查函数。如果检查失败,该函数应返回包含错误消息的非空字符串
- `->check(Validator)`:使用 Validator 对象进行检查,有关可用验证器以及如何创建新验证器的说明,请参阅[验证器](#validators)。
- `->transform(std::string(std::string &), validator_name="",validator_description=")`:将输入字符串转换为输出字符串,在已解析的选项中就地转换。
- `->transform(Validator)`:使用 Validator 对象进行转换,有关可用验证器以及如何创建新验证器的说明,请参阅[验证器](#validators)。
- `->each(void(const std::string &)>`:在接收到每个值时对其运行此函数。如果遇到错误,它应该抛出 `ValidationError`。
- `->configurable(false)`:禁止此选项出现在配置文件中。
- `->capture_default_str()`:存储附加的当前值并将其显示在帮助字符串中。这应该适用于 `add_option` 可以接受的几乎任何类型。
- `->default_function(std::string())`:高级:更改 `capture_default_str()` 使用的函数。
- `->always_capture_default()`:在创建新选项时始终运行 `capture_default_str()`。仅在 App 的 `option_defaults` 上有用。
- `->default_str(string)`:直接设置默认字符串(无验证或回调)。如果未传递参数且请求了该值,此字符串也将用作默认值。
- `->default_val(value)`:从值生成默认字符串并验证该值是否也有效。对于直接赋值给值类型的选项,该类型中的值也会更新。值必须可转换为字符串(已知类型之一或具有流运算符)。如果设置了 `run_callback_for_default`,则可能会触发回调。
- `->run_callback_for_default()`:这将强制在设置 `default_val` 时执行选项回调或设置变量。
- `->option_text(string)`:设置选项名称和描述之间的文本。
- `->force_callback()`:即使在解析中不存在该选项,也导致选项回调或值设置被触发。
- `->trigger_on_parse()`:如果设置,则在解析选项值时导致回调以及与该选项相关的所有验证检查被执行,而不是在所有解析结束时。这可能导致回调被执行多次。也适用于位置选项。
- `->callback_priority(CallbackPriority priority)`:🆕 更改执行选项回调的顺序。有四个主要的回调调用点可用。`CallbackPriority::First` 在处理的最开始执行,在读取配置文件和解释环境变量之前。`CallbackPriority::PreRequirementsCheck` 在配置和环境处理之后但在需求检查之前执行。`CallbackPriority::Normal` 在需求检查之后但在重新抛出任何先前可能引发的异常之前执行。`CallbackPriority::Last` 在异常处理完成后执行。对于每个位置,都会调用普通选项回调和帮助回调。它们之间的相对顺序可以使用相应的 `PreHelp` 变体来控制。`CallbackPriority::FirstPreHelp` 在处理的最开始在帮助回调之前执行普通选项回调。`CallbackPriority::PreRequirementsCheckPreHelp` 在配置和环境处理之后但在需求检查之前,在帮助回调之前执行普通选项回调。`CallbackPriority::NormalPreHelp` 在需求检查之后但在异常重新抛出之前,在帮助回调之前执行普通选项回调。`CallbackPriority::LastPreHelp` 在异常处理完成后,在帮助回调之前执行普通选项回调。使用标准优先级(`CallbackPriority::First`、`CallbackPriority::PreRequirementsCheck`、`CallbackPriority::Normal`、`CallbackPriority::Last`)时,帮助回调在普通选项回调之前执行。默认情况下,帮助回调使用 `CallbackPriority::First`,普通选项回调使用 `CallbackPriority::Normal`。此机制提供了对何时设置选项值以及何时进行帮助或需求检查的细粒度控制,从而能够精确自定义处理顺序。
这些选项返回 `Option` 指针,因此你可以将它们链接起来,甚至完全跳过存储指针。`each` 函数接受任何具有 `void(const std::string&)` 签名的函数;当验证失败时它应该抛出 `ValidationError`。帮助消息将在前面加上父选项的名称。由于each`、`check` 和 `transform` 使用相同的底层机制,你可以根据需要链接任意多个,它们将按顺序执行。通过 `transform` 添加的操作首先以与添加顺序相反的顺序执行,然后 `check` 和 `each` 按照添加顺序在 transform 函数之后运行。如果你只想查看未转换的值,请使用 `.results()` 获取结果的 `std::vector`。
在命令行中,选项可以按以下方式给出:
- `-a`(标志)
- `-abc`(标志可以组合)
- `-f filename`(选项)
- `-ffilename`(不需要空格)
- `-abcf filename`(标志和选项可以组合)
- `--long`(长标志)
- `--long_flag=true`(带有等号的长标志 —— 用于覆盖默认值)
- `--file filename`(空格)
- `--file=filename`(等号)
如果在应用程序或子命令中指定了 `allow_windows_style_options()`,选项也可以按以下方式给出:
- `/a`(标志)
- `/f filename`(选项)
- `/long`(长标志)
- `/file filename`(空格)
- `/file:filename`(冒号)
- `/long_flag:false`(带有 : 的长标志,用于覆盖默认值)
- Windows 样式的选项不允许组合短选项,也不允许像 `-` 选项那样使用未与短选项分隔的值
长标志选项可以使用 `=` 给出,以允许为标志指定 false 值或其他某个值。有关支持的值的详细信息,请参阅[配置文件](#configuration-file)。注意:只有 `=` 或 `:`(对于 windows 样式选项)可用于此目的,使用空格将导致参数被解释为位置参数。此语法可以覆盖默认值,并且可以通过使用 `disable_flag_override()` 来禁用。
额外的位置参数将导致程序退出,因此如果你想允许无关参数,建议至少使用一个带有向量的位置选项。如果你在主 `App` 上设置了 `.allow_extras()`,你将不会收到错误。你可以使用 `remaining` 访问缺失的选项(如果你有子命令,`app.remaining(true)` 将获取所有剩余的选项,包括子命令)。如果剩余的参数将由另一个 `App` 处理,那么可以使用 `remaining_for_passthrough()` 函数以相反的顺序获取剩余的参数,这样 `app.parse(vector)` 就可以直接工作,甚至可以在子命令回调中使用。
你可以使用 `parse_order()` 访问按原始顺序指向已解析选项的指针向量。如果命令行中存在 `--` 并且没有结束无限选项,那么之后的所有内容都仅作为位置参数。
#### 验证器
验证器是用于检查或修改输入的结构,可用于验证输入是否满足特定条件或将其转换为另一个值。它们通过 `check` 或 `transform` 函数添加。这两个函数之间的区别在于,检查不会修改输入,而转换可以,并且在通过 `check` 添加的任何验证器之前执行。
CLI11 内置了几个执行一些常见检查的验证器。默认情况下,最常用的验证器是可用的。🆕 如果不需要某些验证器,可以通过使用以下方式禁用它们
```
#define CLI11_DISABLE_EXTRA_VALIDATORS 1
```
#### 默认验证器
无论定义如何,这些验证器始终可用。它们在内部使用或非常常用,因此无论标志如何都将始终可用。
- `CLI::ExistingFile`:要求如果给出了该文件,则该文件必须存在。
- `CLI::ExistingDirectory`:要求该目录存在。
- `CLI::ExistingPath`:要求路径(文件或目录)存在。
- `CLI::NonexistentPath`:要求路径不存在。
- `CLI::FileOnDefaultPath`:最好用作 transform,将检查文件是直接存在还是在默认路径中存在,并相应地更新路径。有关更多详细信息,请参阅[转换验证器](#transforming-validators)
- `CLI::Range(min,max)`:要求选项介于 min 和 max 之间(确保在需要时使用浮点数)。Min 默认为 0。
- `CLI::PositiveNumber`:要求数字大于 0
- `CLI::NonNegativeNumber`:要求数字大于或等于 0
- `CLI::Number`:要求输入是数字。
#### 可能被禁用的验证器 🆕
可以通过将 `CLI11_DISABLE_EXTRA_VALIDATORS` 设置为 1 来禁用,或将 `CLI11_ENABLE_EXTRA_VALIDATORS` 设置为 1 来启用的验证器。默认情况下它们是启用的。在 3.0 版本中,这些可能会移至默认禁用,并完全由 `CLI11_ENABLE_EXTRA_VALIDATORS` 选项控制。这些验证器较少使用,或者是模板繁重且需要额外计算时间的,这对于某些用例可能没有价值。
- `CLI::IsMember(...)`:要求选项是给定集合的成员。有关更多详细信息,请参阅[转换验证器](#transforming-validators)。
- `CLI::Transformer(...)`:使用 map 修改输入。有关更多详细信息,请参阅[转换验证器](#transforming-validators)。
- `CLI::CheckedTransformer(...)`:使用 map 修改输入,并要求输入要么在集合中,要么已经是集合的输出之一。有关更多详细信息,请参阅[转换验证器](#transforming-validators)。
- `CLI::AsNumberWithUnit(...)`:通过匹配单位并将数字乘以相应的因子来修改 ` ` 对。它可以用作转换器的基础,以接受诸如大小值(`1 KB`)或持续时间(`0.33 ms`)之类的东西。
- `CLI::AsSizeValue(...)`:将 `100b`、`42 KB`、`101 Mb`、`11 Mib` 等输入转换为绝对值。`KB` 可以配置为解释为 10^3 或 2^10。
- `CLI::Bounded(min,max)`:修改输入,使其始终介于 min 和 max 之间(确保在需要时使用浮点数)。Min 默认为 0。如果无法转换,将产生错误。
- `CLI::ValidIPV4`:要求选项是有效的 IPv4 字符串,例如 `'255.255.255.255'`、`'10.1.1.7'`。
- `CLI::TypeValidator`:要求选项可转换为指定类型,例如 `CLI::TypeValidator()` 将要求输入可转换为 `unsigned int`,而不管最终转换如何。
#### 额外验证器 🆕
新的验证器将放入必须通过将 `CLI11_ENABLE_EXTRA_VALIDATORS` 设置为 1 来显式启用的代码段中
- `CLI::ReadPermission`:要求给定的文件或文件夹存在并具有读取权限。需要 C++17。
- `CLI::WritePermission`:要求给定的文件或文件夹存在并具有写入权限。需要 C++17。
- `CLI::ExecPermission`:要求给定的文件存在并具有执行权限。需要 C++17。
#### 验证器用法
这些验证器一旦启用,只需将其名称传递给选项上的 `check` 或 `transform` 方法即可使用
```
->check(CLI::ExistingFile);
->check(CLI::Range(0,10));
```
验证器可以使用 `&` 和 `|` 进行合并,并使用 `!` 进行取反。例如:
```
->check(CLI::Range(0,10)|CLI::Range(20,30));
```
将生成一个检查,确保值在 0 到 10 或 20 到 30 之间。
```
->check(!CLI::PositiveNumber);
```
将生成一个检查小于或等于 0 的数字。
##### 转换验证器
有一些内置的验证器,如果与 `transform` 函数一起使用,可以让你转换值。如果它们也执行一些检查,那么它们可以用于 `check`,但在这种情况下有些可能什么也不做。
- `CLI::Bounded(min,max)` 会将值限制在 min 和 max 之间,超出该范围的值将被限制为 min 或 max,如果无法转换该值,它将失败并产生一个 `ValidationError`
- `IsMember` 验证器允许你指定一组预定义的选项。你可以将任何容器或可复制的指针(包括 `std::shared_ptr`)传递给此验证器的容器;容器只需要是可迭代的并且具有 `::value_type`。键类型应可从字符串转换,如果你愿意,可以直接使用初始化列表。如果你需要稍后修改集合,指针形式可以让你做到这一点;类型消息和检查将正确引用该集合的当前版本。传入的容器可以是集合、向量或类似 map 的结构。如果在 `transform` 方法中使用,输出值将是匹配的键,因为它可能已被过滤器修改。在指定了一组选项之后,你还可以指定形式为 `T(T)` 的“过滤器”函数,其中 `T` 是值的类型。最常见的选择可能是 `CLI::ignore_case`、`CLI::ignore_underscore` 和 `CLI::ignore_space`。这些都适用于字符串,但可以定义适用于其他类型的函数。以下是 `IsMember` 的一些示例:
- `CLI::IsMember({"choice1", "choice2"})`:从与选项的完全匹配中进行选择。
- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`:匹配诸如 `Choice_1` 之类的内容。
- `CLI::IsMember(std::set({2,3,4}))`:大多数容器和类型都有效;你只需要 `std::begin`、`std::end` 和 `::value_type`。
- `CLI::IsMember(std::map({{"one", 1}, {"two", 2}}))`:你可以使用 map;在 `->transform()` 中,这些将匹配的值替换为匹配的键。map 的值成员不在 `IsMember` 中使用,因此它可以是任何类型。
- `auto p = std::make_shared>(std::initializer_list("one", "two")); CLI::IsMember(p)`:你可以稍后修改 `p`。
- `Transformer` 和 `CheckedTransformer` 验证器将一个值转换为另一个值。任何容器或可复制的指针(包括 `std::shared_ptr`)指向生成值对的容器都可以传递给这些 `Validator`;容器只需要是可迭代的并且具有由对组成的 `::value_type`。键类型应可从字符串转换,值类型应可转换为字符串。如果你愿意,可以直接使用初始化列表。如果你需要稍后修改 map,指针形式可以让你做到这一点;描述消息将正确引用该 map 的当前版本。`Transformer` 不进行任何检查,因此忽略不在 map 中的值。`CheckedTransformer` 需要额外的一个步骤,即验证该值要么是 map 键值之一(在这种情况下它被转换),要么是预期的输出值之一,如果不是,将生成 `ValidationError`。使用 `check` 放置的 Transformer 将不执行任何操作。在指定了选项 map 之后,你还可以像在 `CLI::IsMember` 中一样指定“过滤器”。以下是一些示例(`Transformer` 和 `CheckedTransformer` 在示例中可互换):
- `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`:从键值中选择并生成 map 值。
- `CLI::Transformer(std::map({"two",2},{"three",3},{"four",4}}))`:大多数类似 map 的容器都有效,`::value_type` 需要生成某种对。
- `CLI::CheckedTransformer(std::map({{"one", 1}, {"two", 2}}))`:你可以使用 map;在 `->transform()` 中,这些将匹配的键替换为该值。`CheckedTransformer` 还要求该值要么匹配键之一,要么匹配已知输出之一。
- `auto p = std::make_shared>(std::initializer_list>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`:你可以稍后修改 `p`。`TransformPairs` 是 `std::vector>` 的别名
注意:如果在 `IsMember`、`Transformer` 或 `CheckedTransformer` 中使用的容器具有像 `std::unordered_map` 或 `std::map` 这样的 `find` 函数,则该函数用于执行搜索。如果它没有 `find` 函数,则执行线性搜索。如果存在过滤器,则首先执行快速搜索,如果失败,则对键值使用过滤器执行线性搜索。
- `CLI::FileOnDefaultPath(default_path)`:可用于检查默认路径中的文件。如果用作 transform,它将首先检查文件是否存在,如果存在则不执行任何其他操作,如果不存在则尝试将默认路径添加到文件中并再次在那里搜索。如果文件不存在,通常会返回错误,但这可以使用 `CLI::FileOnDefaultPath(default_path, false)` 来禁用。这允许使用多个 transform 调用链接多个路径。
- `CLI::EscapedString`:可用于处理转义字符串。其处理等效于 TOML 配置文件使用的处理方式,请参阅 [TOML 字符串](https://toml.io/en/v1.0.0#string)。有两个显著的例外。
\` 也可以用作字面字符串表示法,并且它还允许二进制字符串表示法,请参阅[二进制字符串](https://cliutils.github.io/CLI11/book/chapters/config.html)。
转义字符串处理将移除存在的外部引号,`"` 将指示具有潜在转义的字符串,`'` 和 \` 将指示字面字符串并移除引号,但不会处理转义序列。这与配置文件中使用的转义处理相同。
##### 验证器操作
验证器是可复制的,并且可以对它们执行一些操作以更改设置。大多数内置验证器都有显示在帮助中的默认描述。这可以通过 `.description(validator_description)` 更改。验证器的名称,对于以后从 `Option` 的 `get_validator(name)` 方法引用很有用,可以通过 `.name(validator_name)` 设置。验证器的操作函数可以通过 `.operation(std::function)` 设置。`.active()` 函数可以激活或停用验证器的操作。可以将验证器设置为仅应用于输出的特定元素。例如,在 `std::pair` 这样的对选项中,第一个元素可能需要是正整数,而第二个元素可能需要是有效文件。`.application_index(int)` 函数可以指定这一点。它是从零开始的,负索引应用于所有值。
```
opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
opt->check(CLI::Validator(CLI::ExistingFile).application_index(1));
```
所有的验证器操作函数都返回一个验证器引用,允许它们被链接。例如
```
opt->check(CLI::Range(10,20).description("range is limited to sensible values").active(false).name("range"));
```
将指定对具有名称 "range" 的选项的检查,但目前将其停用。稍后可以通过以下方式激活该检查
```
opt->get_validator("range")->active();
```
##### 自定义验证器
带有自定义函数的验证器对象可以通过以下方式创建
```
CLI::Validator(std::function,validator_description,validator_name="");
```
或者如果稍后设置操作函数,它们可以通过以下方式创建
```
CLI::Validator(validator_description);
```
也可以创建 `CLI::Validator` 的子类,在这种情况下,它还可以设置自定义描述函数和操作函数。其中一个示例在[自定义验证器示例](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_validator.cpp)中。如果你希望在多个位置重用验证器,或者它是变化的并且检查依赖于其他操作或者是可变的,`check` 和 `transform` 操作也可以接受指向验证器的 shared_ptr 🆕。请注意,在这种情况下,不建议将同一对象同时用于 check 和 transform 操作,检查会修改对象上的一些内部标志,因此它将无法用于 transform 操作。
##### 查询验证器
一旦加载到 Option 中,就可以通过以下方式检索指向命名验证器的指针
```
auto *validator = opt->get_validator(name);
```
这将检索具有给定名称的 Validator,如果未找到则抛出 `CLI::OptionNotFound` 错误。如果没有给出名称或名称为空,将返回第一个未命名的 Validator,或者如果只有一个 Validator,则返回第一个 Validator。
或者
```
auto *validator = opt->get_validator(index);
```
这将按应用时的索引返回一个验证器,这不一定是定义时的顺序。如果给定了无效索引,该指针可能是 `nullptr`。验证器有几个函数用于查询当前值:
- `get_description()`:将返回描述字符串
- `get_name()`:将返回验证器名称
- `get_active()`:将返回当前的活动状态,如果验证器处于活动状态则为 true。
- `get_application_index()`:将返回当前的应用程序索引。
- `get_modifying()`:如果允许验证器修改输入则返回 true,这可以通过 `non_modifying()` 方法控制,尽管建议在需要时让 `check` 和 `transform` 选项方法来操作它。
#### 获取结果
在大多数情况下,最快和最简单的方法是通过在其中一个 `add_*` 函数中指定的回调或变量返回结果。但在某些情况下,这是不可能或不希望的。对于这些情况,可以通过以下函数之一获取结果。请注意,这些函数将在调用期间进行任何类型转换和处理,因此不应在性能关键代码中使用:
- `->results()`:检索包含所有结果的字符串向量,按给出结果的顺序排列。
- `->results(variable_to_bind_to)`:根据 MultiOptionPolicy 获取结果,并像带有变量的 `add_option_function` 一样转换它们。
- `Value=opt->as()`:如果可能,直接以指定类型返回结果或默认值,可以是返回所有结果的向量,以及根据当前 MultiOptionPolicy 获取结果的非向量。如果预期结果将作为向量需要,建议在选项上使用 `->expected(CLI::details::expected_max_vector_size)` 或 `allow_extra_args()`,以通知 CLI11 预期并允许向量参数。
### 子命令
子命令是调用一组新选项和功能的关键字。例如,`git` 命令有一长串子命令,如 `add` 和 `commit`。每个都可以有自己的选项和实现。CLI11 支持子命令,并且可以无限嵌套。要添加子命令,请使用名称和可选描述调用 `add_subcommand` 方法。这提供了一个指向 `App` 的指针,其行为与主 app 相同,并且可以接受选项或进一步的子命令。将 `->ignore_case()` 添加到子命令以允许任何大写字母变体也被接受。`->ignore_underscore()` 类似,但针对的是下划线。子级从父级继承当前设置。你不能在同一级别添加多个匹配的子命令名称(包括 `ignore_case` 和 `ignore_underscore`)。
如果你想要求至少给出一个子命令,请在父 app 上使用 `.require_subcommand()`。你也可以选择性地给出需要子命令的确切数量。如果你给出两个参数,那将设置允许的最小和最大数量。允许的最大数量为 0 将允许无限数量的子命令。作为一个方便的快捷方式,单个负值 N 将设置“最多 N”个值。限制最大数量可以让你防止匹配先前子命令名称的参数被匹配。
如果 `App`(主 app 或子命令)已在命令行上被解析,`->parsed` 将为 true(或直接转换为 bool)。所有 `App` 都有一个 `get_subcommands()` 方法,它返回指向在命令行上传递的子命令的指针列表。还提供了一个 `got_subcommand(App_or_name)` 方法,用于检查在命令行上是否收集了 `App` 指针或字符串名称。
然而,对于许多情况,使用 app 的回调功能可能更容易。每个 app 都有一组可以在解析的各个阶段执行的回调;一个 `C++` lambda 函数(带有捕获以获取已解析的值)可以用作回调定义函数的输入。如果你抛出 `CLI::Success` 或 `CLI::RuntimeError(return_value)`,你甚至可以通过回调退出程序。
允许多个子命令,以允许像 [`Click`][click] 一样的命令系列(顺序被保留)。相同的子命令可以被触发多次,但所有位置参数将优先于子命令的第二次和未来的调用。子命令上的 `->count()` 将返回调用子命令的次数。除非设置了 `.immediate_callback()` 标志或者通过 `parse_complete_callback()` 函数指定回调,子命令回调将只触发一次。`final_callback()` 只触发一次。在这种情况下,回调在子命令参数完成时执行,但在该子命令的参数已被解析之后,并且可以被触发多次。请注意,`parse_complete_callback()` 在处理任何配置文件之前执行。`final_callback()` 在配置文件处理之后执行。
子命令也可以具有空名称,通过使用空字符串作为名称调用 `add_subcommand` 或不带参数调用。无名称子命令的功能类似于主 `App` 中的组。请参阅[选项组](#option-groups)了解其工作原理。如果选项未在主 App 中定义,则也会检查所有无名称子命令。这允许在可组合的组中定义选项。`add_subcommand` 函数具有重载以添加 `shared_ptr`,因此子命令可以在不同的组件中定义并合并到主 `App` 或多个 `Apps` 中。允许多个无名称子命令。仅当解析了子命令中的任何选项时,才会触发无名称子命令的回调。通过 `add_subcommand` 方法给出的子命令名称具有与选项名称相同的限制。
子命令中的选项或标志可以使用点表示法直接指定
- `--subcommand.long=val`(长子命令选项)
- `--subcommand.long val`(长子命令选项)
- `--subcommand.f=val`(短格式子命令选项)
- `--subcommand.f val`(短格式子命令选项)
- `--subcommand.f`(短格式子命令标志)
- `--subcommand1.subsub.f val`(短格式嵌套子命令选项)
以这种形式使用点表示法等效于 `--subcommand.long ` => `subcommand --long ++`。嵌套子命令也有效,`sub1.subsub` 将触发 `sub1` 中的 subsub 子命令。这等效于 "sub1 subsub"。允许在子命令名称周围加上引号,遵循 TOML 规范进行此类指定。这包括允许转义序列。例如 `"subcommand".'f'` 或 `"subcommand.with.dots".arg1 = value`。
#### 子命令选项
主 app 和子命令以及 option_groups 支持几个选项。它们是:
- `.ignore_case()`:忽略此子命令的大小写。由添加的子命令继承,因此通常在主 `App` 上使用。
- `.ignore_underscore()`:忽略子命令名称中的任何下划线。由添加的子命令继承,因此通常在主 `App` 上使用。
- `.allow_windows_style_options()`:允许以 `/s /long /file:file_name.ext` 形式解析命令行选项。此选项不改变在 `add_option` 调用中指定选项的方式,也不影响以 `-s --long --file=file_name.ext` 形式处理选项的能力。
- `.allow_non_standard_option_names()`:允许指定单个 `-` 的长格式选项名称。不建议这样做,但可用于重新改造现有接口。如果在 app 或子命令上启用了此修饰符,则可以像往常一样指定选项或标志,并且不是抛出异常,而是允许长格式单破折号选项名称。不允许有以与单破折号长格式名称相同字符开头的单字符短选项;例如,在同一个应用程序中不允许同时存在 `-s` 和 `-single`。
- `.allow_subcommand_prefix_matching()`:🆕 如果启用此修饰符,子命令的无歧义前缀部分将匹配。例如,只要没有其他子命令也匹配,`upgrade_package` 将匹配 `upgrade_`、`upg`、`u`。它还不允许作为另一个子命令完整前缀的子命令名称。
- `.fallthrough()`:允许额外未匹配的选项和位置参数“回退”并在父选项上匹配。默认情况下,子命令被允许“回退”,因为它们将首先尝试在当前子命令上匹配,如果失败,将逐步检查父项以匹配子命令。这可以通过 `subcommand_fallthrough(false)` 禁用。
- `.subcommand_fallthrough()`:允许子命令“回退”并在父选项上匹配。禁用此选项可防止在同一级别匹配额外的子命令。在子命令和位置参数之间可能存在歧义的某些情况下,它可能很有用。默认值为 true。
- `.configurable()`:允许从配置文件中触发子命令。默认情况下,配置文件中的子命令选项不会触发子命令,而只会更新默认值。
- `.disable()`:指定子命令被禁用,如果给定一个 bool 值,它将启用或禁用子命令或选项组。
- `.disabled_by_default()`:指定在解析开始时,子命令/option_group 应该被禁用。这对于允许某些子命令触发其他子命令很有用。
- `.enabled_by_default()`:指定在每次解析开始时,子命令/option_group 应该被启用。这对于允许某些子命令禁用其他子命令很有用。
- `.silent()`:指定子命令是静默的,这意味着如果使用它,它不会显示在子命令列表中。这允许将子命令用作修饰符
- `.validate_positionals()`:指定位置参数在匹配之前应通过验证。验证是通过选项的 `transform`、`check` 和 `each` 指定的。如果参数未通过验证,这不是错误,并且匹配将继续进行到下一个可用的位置或额外参数。
- `.validate_optional_arguments()`:指定可选参数在分配给选项之前应通过验证。验证是通过选项的 `transform`、`check` 和 `each` 指定的。如果参数未通过验证,这不是错误,并且匹配将继续进行到下一个可用的位置子命令或额外参数。
- `.excludes(option_or_subcommand)`:如果给定一个选项指针或指向另一个子命令的指针,则这些子命令不能同时给出。对于选项,如果传递了选项,则子命令不能使用并将生成错误。
- `.needs(option_or_subcommand)`:如果给定一个选项指针或指向另一个子命令的指针,子命令将要求在验证此子命令之前已给出给定的选项,验证发生在执行任何回调之前或在解析完成之后。
- `.require_option()`:要求使用 1 个或多个选项或选项组。
- `.require_option(N)`:要求 `N` 个选项或选项组,如果 `N>0`,或者如果 `N<0` 则最多 `N` 个。`N=0` 重置为默认的 0 个或多个。
- `.require_option(min, max)`:显式设置允许的最小和最大选项或选项组。将 `max` 设置为 0 意味着无限选项。
- `.require_subcommand()`:要求 1 个或多个子命令。
- `.require_subcommand(N)`:如果 `N>0`,则要求 `N` 个子命令,或者如果 `N<0`,则最多 `N` 个。`N=0` 重置为默认的 0 个多个。
- `.require_subcommand(min, max)`:显式设置允许的最小和最大子命令。将 `max` 设置为 0 表示无限制。
- `.add_subcommand(name="", description="")`:添加一个子命令,返回指向内部存储的子命令的指针。
- `.add_subcommand(shared_ptr)`:通过 shared_ptr 添加子命令,返回指向内部存储的子命令的指针。
- `.remove_subcommand(App)`:从 app 或子命令中移除子命令。
- `.got_subcommand(App_or_name)`:检查在命令行中是否接收到了子命令。
- `.get_subcommands(filter)`:匹配特定过滤函数的子命令列表。
- `.add_option_group(name="", description="")`:向 App 添加[选项组](#option-groups),选项组是专用子命令,旨在包含选项组或其他组以控制选项交互方式。
- `.get_parent()`:获取父 App,如果在主 App 上调用则为 `nullptr`。
- `.get_option(name)`:通过选项名称获取选项指针,如果指定选项不可用将抛出异常,对于带有回退的子命令,也会搜索无名称子命令及其父级。
- `.get_option_no_throw(name)`:通过选项名称获取选项指针。如果选项不可用,此函数将返回 `nullptr` 而不是抛出异常。无论回退状态如何,此方法都不会搜索 option_options 或无名称子命令的父级 🚧,此行为与 `get_option` 略有不同。
- `.get_options(filter)`:获取所有已定义选项指针的列表(用于处理 app 以生成自定义输出格式)。如果在子命令上使用,并且子命令具有回退(并且不是无名称的 🚧),则还将获取父 app 中的选项。
- `.parse_order()`:按解析顺序获取选项指针列表(包括重复项)。
- `.formatter(std::shared_ptr fmt)`:为帮助设置自定义格式化器。
- `.formatter_fn(fmt)`,签名为 `std::string(const App*, std::string, AppFormatMode)`。有关更多详细信息,请参阅[格式化][formatting]。
- `.config_formatter(std::shared_ptr fmt)`:设置用于生成配置文件的自定义配置格式化器,更多详细信息请参见[配置文件][config]
- `.description(str)`:设置/更改描述。
- `.get_description()`:访问描述。
- `.alias(str)`:为子命令设置别名,这允许通过多个名称调用子命令。
- `.parsed()`:如果在命令行上给出了此子命令,则为 True。
- `.count()`:返回调用子命令的次数。
- `.count(option_name)`:返回特定选项被调用的次数。
- `.count_all()`:返回特定子命令处理的总参数数,在主 App 上它返回已处理命令的总数。
- `.name(name)`:添加或更改名称。
- `.callback(void() function)`:为 app 设置回调。根据 `immediate_callback` 的值,设置 `pre_parse_callback` 或 `final_callback`。有关一些额外详细信息,请参阅[子命令回调](#callbacks)。
- `.parse_complete_callback(void() function)`:设置在解析完成时运行的回调。对于子命令,这在单个子命令完成时执行,并且可以执行多次。有关一些额外详细信息,请参阅[子命令回调](#callbacks)。
- `.final_callback(void() function)`:设置在所有处理结束时运行的回调。这是返回前执行的最后一步。有关一些额外详细信息,请参阅[子命令回调](#callbacks)。
- `.immediate_callback()`:指定子命令的回调应作为 `parse_complete_callback`(true)还是 `final_callback`(false)运行。在主 app 上使用时,如果子命令没有设置 `immediate_callback` 标志,它将在子命令的回调之前执行主 app 回调。最好直接使用 `parse_complete_callback` 或 `final_callback` 而不是 `callback` 和 `immediate_callback`,如果你希望控制回调的顺序和时机。尽管如果需要,可以使用 `immediate_callback` 来交换它们。
- `.pre_parse_callback(void(std::size_t) function)`:设置在处理应用程序的第一个参数后执行的回调。有关一些额外详细信息,请参阅[子命令回调](#callbacks)。
- `.allow_extras()`:如果留下额外参数,不要抛出错误。
- `.allow_extras(CLI::ExtrasMode)`:🆕 指定处理无法识别的参数的方法。
- `CLI::ExtrasMode::Error`:对无法识别的参数生成错误。与 `.allow_extras(false)` 相同。
- `CLI::ExtrasMode::ErrorImmediately`:在解析无法识别的选项时立即生成错误。
- `CLI::ExtrasMode::Ignore`:忽略任何无法识别的参数,不生成错误。
- `CLI::ExtrasMode::AssumeSingleArgument`:在无法识别的标志或选项参数之后,如果后面的参数不是标志或选项参数,则假定它是一个参数,并将其也视为无法识别的,即使它原本会进入位置参数。
- `CLI::ExtrasMode::AssumeMultipleArguments`:在无法识别的标志或选项参数之后,如果后面的参数不是标志或选项参数,则假定它们是参数,并将它们也视为无法识别的,即使它们原本会进入位置参数。
- `CLI::ExtrasMode::Capture`:捕获所有无法识别的参数,与 `.allow_extras` 的 `true` 相同:
- `.positionals_at_end()`:指定位置参数作为最后的参数出现,如果遇到意外的位置参数则抛出错误。
- `.prefix_command()`:类似于 `allow_extras`,但在遇到第一个无法识别的项目时立即停止处理。所有后续的参数都放在 remaining_arg 列表中。它非常适合允许你的 app 或子命令成为调用另一个 app 的“前缀”。
- `.prefix_command(bool)`:启用或禁用前缀命令模式。`prefix_command(true)` 等效于 `prefix_command(CLI::PrefixCommandMode::On)`,而 `prefix_command(false)` 等效于 `prefix_command(CLI::PrefixCommandMode::Off)`。
- `.prefix_command(CLI::PrefixCommandMode)`:直接指定前缀命令模式。`PrefixCommandMode::On` 和 `PrefixCommandMode::Off` 与 `prefix_command(true)` 和 `prefix_command(false)` 相同。使用 `PrefixCommandMode::SeparatorOnly` 调用只会在带有子命令分隔符 `--` 时触发前缀命令模式;其他无法识别的参数被认为是错误,除非启用了 `allow_extras`。
- `.usage(message)`:替换出现在描述之后的帮助字符串开头的文本。
- `.usage(std::string())`:设置一个回调以生成将出现在描述之后的帮助字符串开头的字符串。
- `.footer(message)`:设置出现在帮助字符串底部的文本。
- `.footer(std::string())`:设置一个回调以生成将出现在帮助字符串末尾的字符串。
- `.set_help_flag(name, message)`:设置帮助标志名称和消息,返回指向创建的选项的指针。
- `.set_version_flag(name, versionString or callback, help_message)`:设置版本标志名称和版本字符串或回调以及可选的帮助消息,返回指向创建的选项的指针。
- `.set_help_all_flag(name, message)`:设置帮助所有标志的名称和消息,返回指向创建的选项的指针。展开子命令。
- `.failure_message(func)`:设置失败消息函数。提供了两个:`CLI::FailureMessage::help` 和 `CLI::FailureMessage::simple`(默认)。
- `.group(name)`:设置组名,默认为 `"Subcommands"`。为名称设置空字符串将隐藏子命令。
- `[option_name]`:检索由 `option_name` 给出的选项的 const 指针,例如 `app["--flag1"]` 将获取 "--flag1" 值的选项指针,`app["--flag1"]->as()` 将获取标志的命令行结果。如果选项名称无效,该操作将抛出异常。
#### 回调
子命令有三个可选回调,在处理的不同阶段执行。`preparse_callback` 在处理子命令或应用程序的第一个参数后执行一次,并给出要处理的剩余参数数量的参数。对于主 app,第一个参数被认为是程序名称,对于子命令,第一个参数是子命令名称。对于 Option 组和无名称子命令,第一个参数是在处理该组的第一个参数或子命令之后。第二个回调在解析后执行。这被称为 `parse_complete_callback`。对于子命令,这在解析后立即执行,如果多次调用子命令则可以执行多次。在主 app 上,此回调在执行了子命令的所有 `parse_complete_callback` 之后但在子命令或选项组中的任何 `final_callback` 调用之前执行。如果主 app 或子命令具有配置文件,则配置文件中的数据将不会反映在命名子命令的 `parse_complete_callback` 中。对于 `option_group`,`parse_complete_callback` 在主 app 的 `parse_complete_callback` 之前执行,但在加载 `config_file`(如果指定)之后执行。`final_callback` 在所有处理完成后执行。在主 app 上执行 `parse_complete_callback` 后,将执行已使用的子命令 `final_callback`,随后是 option 的“最终回调”
组。最后执行的是 `main_app` 的 `final_callback`。
例如,假设一个应用程序的设置如下
```
app.parse_complete_callback(ac1);
app.final_callback(ac2);
auto sub1=app.add_subcommand("sub1")->parse_complete_callback(c1)->preparse_callback(pc1);
auto sub2=app.add_subcommand("sub2")->final_callback(c2)->preparse_callback(pc2);
app.preparse_callback( pa);
... A bunch of other options
```
然后在命令行中输入
```
program --opt1 opt1_val sub1 --sub1opt --sub1optb val sub2 --sub2opt sub1 --sub1opt2 sub2 --sub2opt2 val
```
- `pa` 将在任何值被解析之前被调用,参数为 13。
- `pc1` 将在处理完 `sub1` 命令后立即被调用,值为 10。
- `c1` 将在遇到 `sub2` 命令时被调用。
- `pc2` 将在遇到 `sub2` 命令后被调用,值为 6。
- `c1` 将在遇到第二个 `sub2` 命令后再次被调用。
- `ac1` 将在处理完所有参数后被调用
- `c2` 将在处理完所有参数后被调用一次。
- `ac2` 将在所有较低层级的回调执行完毕后,最后被调用。
当满足以下条件之一时,子命令被视为终止:
1. 没有更多参数需要处理
2. 遇到了另一个子命令,且该子命令不适合放入当前子命令的可选位置参数槽位中
3. 遇到了 `positional_mark` (`--`),且当前子命令中没有可用的位置参数槽位。
4. 遇到了 `subcommand_terminator` 标记 (`++`)
在执行 `parse_complete_callback` 之前,所有包含的选项都会被处理完,然后才会触发回调。如果带有 `parse_complete_callback` 的子命令再次被调用,那么其包含的选项将被重置,并且可以再次触发。
#### 选项组
子命令方法
```
.add_option_group(name,description)
```
将创建一个选项组,并返回一个指向它的指针。`description` 参数是可选的,可以省略。选项组允许创建一个选项集合,类似于选项上的 groups 功能,但具有额外的控制和要求。它们允许将特定的选项集作为一个整体进行组合和控制。示例请参见 [range 示例](https://github.com/CLIUtils/CLI11/blob/main/examples/ranges.cpp)。
选项组是 App 的一个特化版本,因此所有适用于 App 或子命令的[函数](#subcommand-options)同样适用于选项组。可以使用 add 函数像子命令一样将选项创建为选项组的一部分,或者通过以下方式添加先前创建的选项。选项组中给定的名称不得包含换行符或空字符。
```
ogroup->add_option(option_pointer);
ogroup->add_options(option_pointer);
ogroup->add_options(option1,option2,option3,...);
```
此函数中使用的选项指针必须是在选项组的父应用程序中定义的选项,否则将产生错误。
也可以通过以下方式添加子命令
```
ogroup->add_subcommand(subcom_pointer);
```
这会导致子命令从其父级移动到选项组中。
系统在主应用中的所有选项之后,才会搜索选项组中的选项以进行命令行匹配,因此主应用中的任何位置参数都会首先被匹配。因此,在使用位置参数和选项组时,必须注意顺序。选项组与 `excludes` 和 `require_options` 方法配合良好,因为应用程序在计数和要求目的上会将选项组视为单个选项,并且如果选项组中包含的任何选项或子命令被使用,该选项组就会被视为已使用。选项组允许指定要求,例如在一个组中要求 3 个选项中的 1 个,在另一个组中要求 3 个选项中的 1 个。选项组也可以包含其他组。禁用选项组将关闭该组内的所有选项。
`CLI::TriggerOn` 和 `CLI::TriggerOff` 方法是辅助函数,允许使用一个组中的选项/子命令来触发另一个组的开启或关闭。
```
CLI::TriggerOn(group1_pointer, triggered_group);
CLI::TriggerOff(group2_pointer, disabled_group);
```
这些函数使用了 `preparse_callback`、`enabled_by_default()` 和 `disabled_by_default`。被触发的组可以是组指针的向量。这些方法每个组只应使用一次,并且将覆盖底层函数的任何先前使用。可以使用具有类似方法的自定义 `preparse_callback` 函数来完成更复杂的安排,该函数可以实现更多功能。
额外的辅助函数 `deprecate_option` 和 `retire_option` 可用于弃用或停用选项
```
CLI::deprecate_option(option *, replacement_name="");
CLI::deprecate_option(App,option_name,replacement_name="");
```
将指定该选项已被弃用,这将在帮助信息中显示一条消息,并在首次使用时发出警告。被弃用的选项功能正常,但会在帮助信息中添加一条消息并在首次使用时显示警告。
```
CLI::retire_option(App,option *);
CLI::retire_option(App,option_name);
```
将创建一个默认情况下什么都不做的选项,并在首次使用时显示警告,说明该选项已停用且无效。如果该选项存在,它将被一个接受相同参数的虚拟选项替换。
如果为选项组名称传递一个空字符串,则整个组将在帮助结果中被隐藏。例如。
```
auto hidden_group=app.add_option_group("");
```
将创建一个组,使得该组中的任何选项都不会显示在帮助字符串中。出于帮助显示的目的,如果选项组名称以 '+' 开头,则在进行帮助和 get_options 操作时,它将被视为不在组中。例如:
```
auto added_group=app.add_option_group("+sub");
```
在这种情况下,帮助输出将不会引用该选项组,并且其中的选项在大多数情况下将被视为其父级的一部分。
### 配置文件
```
app.set_config(option_name="",
default_file_name="",
help_string="Read an ini file",
required=false)
```
如果在没有参数的情况下调用此方法,它将移除配置文件选项(类似于 `set_help_flag`)。设置配置选项是很特殊的。如果它存在,它将与普通的命令行参数一起被读取。如果文件存在将被读取,并且除非 `required` 为 `true`,否则不会抛出错误。配置文件默认为 [TOML][] 格式,尽管默认读取器也可以接受 INI 格式的文件。配置读取器可以读取 TOML 文件的大部分内容,包括字符串(字面量和可能带有转义序列的字符串)、数字分隔符和多行字符串,并通过 CLI11 解析器运行它们。熟练的用户可以添加其他格式,通过默认格式化程序中的自定义点可以使用一些变体。
TOML 文件的示例:
```
# 注释使用 # 支持
# 默认 section 是 [default],不区分大小写
value = 1
value2 = 123_456 # a string with separators
str = "A string"
str2 = "A string\nwith new lines"
str3 = 'A literal "string"'
vector = [1,2,3]
str_vector = ["one","two","and three"]
# Section 映射到 subcommand
[subcommand]
in_subcommand = Wow
sub.subcommand = true
"sub"."subcommand2" = "string_value"
```
或者等效的 INI 格式
```
; Comments are supported, using a ;
; The default section is [default], case-insensitive
value = 1
str = "A string"
vector = 1 2 3
str_vector = "one" "two" "and three"
; Sections map to subcommands
[subcommand]
in_subcommand = Wow
sub.subcommand = true
```
名称和参数前后的空格将被忽略。多个参数由空格分隔。会移除一层引号以保留空格(与命令行的工作方式相同)。布尔选项可以是 `true`、`on`、`1`、`yes`、`enable`;或者是 `false`、`off`、`0`、`no`、`disable`(不区分大小写)。
节(以及由 `.` 分隔的名称)被视为子命令(注意:这并不一定意味着该子命令被传递了,它只是设置了“默认值”)。您不能设置仅限位置的参数。如果在子命令上设置了 `configurable` 标志,则可以从配置文件中触发子命令。然后,使用 `[subcommand]` 表示法将触发一个子命令,并使其表现得像在命令行中一样。
要从传递的参数打印配置文件,请使用以下重载之一:
- `.config_to_str()`:仅打印活动值。
- `.config_to_str(bool default_also, bool write_description = false)`:打印活动值,或者如果 `default_also` 为 `true`,则包含具有默认值的参数。此重载可能会在未来的版本中被弃用,以支持 `CLI::ConfigOutputMode` 重载。
- `.config_to_str(CLI::ConfigOutputMode, bool write_description = false)`:🆕 指定应如何生成配置输出。
- `CLI::ConfigOutputMode::Active`:仅打印活动值。与 `.config_to_str()` 相同。
- `CLI::ConfigOutputMode::AllDefaults`:包含应用程序和所有子命令的默认参数。与 `.config_to_str(true, ...)` 相同。
- `CLI::ConfigOutputMode::ActiveSubcommandDefaults`:包含应用程序和活动子命令的默认参数,同时省略非活动子命令的默认值。
`write_description` 参数将包含应用程序和选项的描述。
有关一些额外的详细信息和自定义点,请参见 [配置文件](https://cliutils.github.io/CLI11/book/chapters/config.html)。
如果希望允许多个配置文件,请使用
```
app.set_config("--config")->expected(1, X);
```
其中 X 是某个正数,这将允许通过单独的 `--config` 参数指定最多 `X` 个配置文件。包含引号字符的值字符串将使用单引号打印。所有其他参数将使用双引号。空字符串将使用带双引号的参数。数字或布尔值不加引号。
对于允许传递 0 个参数的选项或标志,如果在配置文件中使用空字符串、`{}` 或 `[]`,则会将结果转换为通过选项上的 `default_str` 或 `default_val` 指定的默认值。如果未指定用户默认值,则结果为空字符串或空字符串的转换值。
注意:转换和检查可以与从 set_config 返回的选项指针一起使用,就像任何其他选项一样,以便在需要时验证输入。它还可以与内置的转换 `CLI::FileOnDefaultPath` 一起使用,以在当前路径以及默认路径中查找。例如
```
app.set_config("--config")->transform(CLI::FileOnDefaultPath("/to/default/path/"));
```
有关此验证器的其他详细信息,请参见 [转换验证器](#transforming-validators)。可以通过多次调用或对转换使用 `|` 操作来使用多个转换或验证器。
### 继承默认值
子命令甚至选项的许多默认值都是从它们的创建者那里继承的。子命令继承的默认值包括 `allow_extras`、`prefix_command`、`ignore_case`、`ignore_underscore`、`fallthrough`、`group`、`usage`、`footer`、`immediate_callback` 以及所需子命令的最大数量。帮助标志的存在、名称和描述也会被继承。
选项具有 `group`、`required`、`multi_option_policy`、`ignore_case`、`ignore_underscore`、`delimiter` 和 `disable_flag_override` 的默认值。要设置这些默认值,您应该设置 `option_defaults()` 对象,例如:
```
app.option_defaults()->required();
// All future options will be required
```
选项的默认设置也会继承给子命令。
### 格式化
格式化帮助输出的工作被委托给一个格式化器对象。您可以自由地通过在 `App` 上调用 `formatter(fmt)` 来用自定义格式化器替换默认格式化器。CLI11 附带了一个默认的 App 格式化器 `Formatter`。您可以通过 `.get_formatter()` 检索格式化器,这将返回一个指向当前 `Formatter` 的指针。它是可自定义的;您可以设置 `label(key, value)` 来替换默认标签,如 `REQUIRED` 和 `OPTIONS`。您还可以设置 `column_width(n)` 以在将功能添加到 app 或 option 之前设置列的宽度。`Formatter` 中还提供了其他几个配置选项。您还可以在任一格式化器的子类中覆盖格式化过程的几乎任何阶段。如果您想从头开始创建一个新的格式化器,也可以这样做;您只需要实现正确的签名。有关更多详细信息,请参见 [格式化][formatting]。
### 子类化
App 类的设计允许工具包对其进行子类化,以提供预设的默认选项(见上文)以及设置/拆卸代码。子命令仍然是一个未被子类化的 `App`,因为预计它们不需要设置和拆卸。默认的 `App` 只添加了一个帮助标志 `-h,--help`,可以通过 `.set_help_flag(name, help_string)` 将其移除/替换。您还可以使用 `.set_help_all_flag(name, help_string)` 设置一个帮助全部标志;这将展开子命令(仅限一级)。如果您有指向选项的指针,可以使用 `.remove_option(opt)` 删除它们。您可以添加 `pre_callback` 覆盖来自定义解析后但在运行前的行为,同时仍给予用户在主应用上使用 `callback` 的自由。
最重要的解析函数是 `parse(std::vector)`,它接受一个反转的参数列表(以便 `pop_back` 以正确的顺序处理参数)。`get_help_ptr` 和 `get_config_ptr` 让您可以访问帮助/配置选项指针。标准的 `parse` 从第一个参数手动设置名称,因此它不应出现在此向量中。您还可以使用 `parse(string, bool)` 来拆分并解析单个字符串;如果您在字符串中包含程序名称,可选的布尔值应设置为 true,否则为 false。如果程序名称是一个现有文件,它可以包含空格,否则可以用引号(单引号、双引号或反引号)括起来。嵌入的引号字符可以用 `\` 转义。
此外,在相关说明中,您获得指针的 `App` 存储在父 `App` 的 `shared_ptr` 中(类似于 `Option`),并在主 `App` 超出作用域时被删除,除非该对象有另一个所有者。
### 工作原理
到目前为止,您看到的每个 `add_` 选项都依赖于一个接受 lambda 函数的方法。这些方法中的每一个只是使用捕获来创建一个不同的 lambda 函数以填充选项。该函数可以完全访问字符串向量,因此它知道一个选项被传递了多少次或接收了多少个参数。如果它可以验证选项字符串,则 lambda 返回 `true`,如果失败则返回 `false`。
只要其他值支持 `operator>>`(如果它们支持 `operator<<` 则可以打印默认值),就可以添加它们。例如,要添加新类型,请提供一个带有 `istream` 的自定义 `operator>>`(如果您不想干扰现有的 `operator>>`,在 CLI 命名空间内即可)。
如果您想扩展它以支持一个全新的类型,可以使用 lambda 或者为您需要转换的类型所在的命名空间中的 `lexical_cast` 函数添加重载。在[其中一个测试](./tests/NewParseTest.cpp)中提供了一些支持标准 `add_options` 调用所有特性的 `complex` 新解析器示例。下面展示了一个简单的示例:
```
app.add_option("--fancy-count", [](std::vector val){
std::cout << "This option was given " << val.size() << " times." << std::endl;
return true;
});
```
### Unicode 支持
CLI11 支持 [UTF-8 Everywhere](http://utf8everywhere.org/) 宣言中定义的 Unicode 和宽字符串。特别是:
- 该库可以在 Windows 上解析宽版本的命令行参数,这些参数在内部转换为 UTF-8(下文将详细介绍);
- 您可以将选项值存储在 `std::wstring` 中,在这种情况下,它们将被转换为系统上正确的宽字符串编码(Windows 上为 UTF-16,大多数其他系统上为 UTF-32);
- 建议不要存储宽字符串,而是使用提供的 `widen` 和 `narrow` 函数在实际必要时(例如调用 Windows API 时)在宽字符串之间进行转换。
在 Windows 上使用带有 unicode 参数的命令行时,您的 `main` 函数可能已经接收到损坏的 Unicode。此时解析 `argv` 将无法为您提供正确的字符串。要解决此问题,您有三个选项;第一个选项建议用于跨平台支持:
1\. 在解析任何参数之前,用 `app.ensure_utf8(argv)` 替换 `argv`。`ensure_utf8` 在 `argv` 已经是 UTF-8 的系统(如 Linux 或 macOS)上不执行任何操作,并原样返回 `argv`。在 Windows 上,它将丢弃 `argv`,并用从 win32 API 正确解码的参数数组替换它。
```
int main(int argc, char** argv) {
CLI::App app;
argv = app.ensure_utf8(argv); // new argv memory is held by app
// ...
CLI11_PARSE(app, argc, argv);
}
```
请确保在调用此函数之前不要修改 `argv`,因为正确的值将使用 Windows API 重新构建并由此次调用生成。它对其他平台没有影响,只是直接传递 `argv`。
其他选项(点击展开)
2\. 使用仅限 Windows 的非标准 `wmain` 函数,它接受 `wchar_t *argv[]` 而不是 `char* argv[]`。解析它将允许 CLI 将宽字符串转换为 UTF-8 而不会丢失信息。
```
int wmain(int argc, wchar_t *argv[]) {
CLI::App app;
// ...
CLI11_PARSE(app, argc, argv);
}
```
3\. 自己使用 Windows API(例如 [CommandLineToArgvW](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw))检索参数,并将其传递给 CLI。这就是库在 `ensure_utf8` 内部所做的工作。
该库提供了在 UTF-8 和宽字符串之间进行转换的函数:
```
namespace CLI {
std::string narrow(const std::wstring &str);
std::string narrow(const wchar_t *str);
std::string narrow(const wchar_t *str, std::size_t size);
std::string narrow(std::wstring_view str); // C++17
std::wstring widen(const std::string &str);
std::wstring widen(const char *str);
std::wstring widen(const char *str, std::size_t size);
std::wstring widen(std::string_view str); // C++17
}
```
#### 使用 Unicode 路径的注意事项
在 Windows 上从 UTF-8 路径创建 `filesystem::path` 时,您需要先将其转换为宽字符串。CLI11 提供了一个独立于平台的 `to_path` 函数,它将以正确的方式将 UTF-8 字符串转换为路径:
```
std::string utf8_name = "Hello Halló Привет 你好 👩🚀❤️.txt";
std::filesystem::path p = CLI::to_path(utf8_name);
std::ifstream stream(CLI::to_path(utf8_name));
// etc.
```
### 实用工具
在 CLI 编程中还有其他一些通常有用的实用工具。它们位于单独的头文件中,不会出现在 `CLI11.hpp` 中,但它们是完全独立的,可以根据需要使用。`Timer`/`AutoTimer` 类允许您轻松地为代码块计时,并提供自定义的打印输出。
```
{
CLI::AutoTimer timer {"My Long Process", CLI::Timer::Big};
some_long_running_process();
}
```
这将创建一个带有标题(默认:`Timer`)的计时器,并将使用预定义的 `Big` 输出(默认:`Simple`)自定义输出。因为它是一个 `AutoTimer`,所以当计时器在块末尾被销毁时,它将打印出经过的时间。如果您改用 `Timer`,则可以使用 `to_string` 或 `std::cout << timer << std::endl;` 来打印时间。打印函数可以是任何接受两个字符串(标题和时间)并返回格式化字符串以供打印的函数。
### 其他库
如果您使用优秀的 [Rang][] 库以一种安全、跨平台的方式为您的终端添加颜色,您可以将其与 CLI11 很好地结合使用:
```
std::atexit([](){std::cout << rang::style::reset;});
try {
app.parse(argc, argv);
} catch (const CLI::ParseError &e) {
std::cout << (e.get_exit_code()==0 ? rang::fg::blue : rang::fg::red);
return app.exit(e);
}
```
这将以蓝色打印帮助信息,以红色打印错误,并在将终端交还给用户之前重置。
如果您在类似 Unix 的系统上,并且想要处理 control-c 和颜色,您可以添加:
```
#include
void signal_handler(int s) {
std::cout << std::endl << rang::style::reset << rang::fg::red << rang::fg::bold;
std::cout << "Control-C detected, exiting..." << rang::style::reset << std::endl;
std::exit(1); // will call the correct exit func, no unwinding of the stack though
}
```
并且,在您的主函数中:
```
// Nice Control-C
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = signal_handler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, nullptr);
```
## API
API [在此处记录][api-docs]。另请参阅 [CLI11 教程 GitBook][gitbook]。
## 示例
存储库中包含了展示不同特性的几个简短示例。这里包含了对每个示例的简要描述
- [arg_capture](https://github.com/CLIUtils/CLI11/blob/main/examples/arg_capture.cpp):使用带有别名的子命令和 prefix_command() 捕获特定选项之后所有剩余参数的示例。
- [callback_passthrough](https://github.com/CLIUtils/CLI11/blob/main/examples/callback_passthrough.cpp):将剩余参数直接传递给基于现有参数生成 CLI11 应用程序的回调函数的示例。
- [custom_parse](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_parse.cpp):基于 [Issue #566](https://github.com/CLIUtils/CLI11/issues/566) 的自定义解析器示例
- [digit_args](https://github.com/CLIUtils/CLI11/blob/main/examples/digit_args.cpp):基于 [Issue #123](https://github.com/CLIUtils/CLI11/issues/123) 的使用数字标志传递值的示例
- [enum](https://github.com/CLIUtils/CLI11/blob/main/examples/enum.cpp):在选项中使用枚举,以及 [CheckedTransformer](#transforming-validators) 的使用
- [enum_ostream](https://github.com/CLIUtils/CLI11/blob/main/examples/enum_ostream.cpp):除了 example enum.cpp 的内容外,此示例还展示了自定义 ostream 运算符如何覆盖 CLI11 的枚举流输出。
- [formatter](https://github.com/CLIUtils/CLI11/blob/main/examples/formatter.cpp):说明自定义格式化器用法的示例
- [groups](https://github.com/CLIUtils/CLI11/blob/main/examples/groups.cpp):使用选项组进行帮助分组以及计时器辅助类的示例
- [inter_argument_order](https://github.com/CLIUtils/CLI11/blob/main/examples/inter_argument_order.cpp):一个用于练习混合无限参数,但仍能恢复原始顺序的应用程序。
- [json](https://github.com/CLIUtils/CLI11/blob/main/examples/json.cpp):使用 JSON 作为配置文件解析器
- [modhelp](https://github.com/CLIUtils/CLI11/blob/main/examples/modhelp.cpp):如何修改帮助标志以执行非默认操作
- [nested](https://github.com/CLIUtils/CLI11/blob/main/examples/nested.cpp):嵌套子命令
- [option_groups](https://github.com/CLIUtils/CLI11/blob/main/examples/option_groups.cpp):演示选项组使用和所需选项数量的示例。基于 [Issue #88](https://github.com/CLIUtils/CLI11/issues/88) 设置交互式选项组
- [positional_arity](https://github.com/CLIUtils/CLI11/blob/main/examples/positional_arity.cpp):演示使用 `preparse_callback` 处理参数数量决定解析内容的情况,基于 [Issue #166](https://github.com/CLIUtils/CLI11/issues/166)
- [positional_validation](https://github.com/CLIUtils/CLI11/blob/main/examples/positional_validation.cpp):演示如何使用 `validate_positional` 标志验证位置参数的示例,同样基于 [Issue #166](https://github.com/CLIUtils/CLI11/issues/166)
- [prefix_command](https://github.com/CLIUtils/CLI11/blob/main/examples/prefix_command.cpp):演示使用 `prefix_command` 标志的示例。
- [ranges](https://github.com/CLIUtils/CLI11/blob/main/examples/ranges.cpp):演示基于 [Issue #88](https://github.com/CLIUtils/CLI11/issues/88) 的排他性选项组的应用程序
- [shapes](https://github.com/CLIUtils/CLI11/blob/main/examples/shapes.cpp):演示如何基于 [gitter 讨论](https://gitter.im/CLI11gitter/Lobby?at=5c7af6b965ffa019ea788cd5) 设置重复的子命令
- [simple](https://github.com/CLIUtils/CLI11/blob/main/examples/simple.cpp):一个关于如何设置带有不同标志和选项的 CLI11 应用程序的简单示例
- [subcom_help](https://github.com/CLIUtils/CLI11/blob/main/examples/subcom_help.cpp):为子命令配置帮助
- [subcom_partitioned](https://github.com/CLIUtils/CLI11/blob/main/examples/subcom_partitioned.cpp):带有计时器以及单独生成随后添加到主应用程序的子命令的示例。
- [subcommands](https://github.com/CLIUtils/CLI11/blob/main/examples/subcommands.cpp):子命令的简短示例
- [validators](https://github.com/CLIUtils/CLI11/blob/main/examples/validators.cpp):演示验证器用法的示例
- [custom validators](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_validator.cpp):演示验证器用法的示例
- [date validators](https://github.com/CLIUtils/CLI11/blob/main/examples/date_validator.cpp):演示验证器用法的示例
## 贡献
要进行贡献,请在 GitHub 上提出 [issue][github issues] 或 [pull request][github pull requests],或者在 [gitter][] 上提问。这里还有一封[给贡献者的简短说明](./.github/CONTRIBUTING.md)。本自述文件大致遵循 [Standard Readme Style][],并提到了该库的几乎每一个特性。更复杂的特性在 [CLI11 教程 GitBook][gitbook] 中有更详细的记录。
该项目由 [Henry Schreiner](https://github.com/henryiii) 创建,主要功能由 [Philip Top](https://github.com/phlptp) 添加。特别感谢所有的贡献者
([表情符号键](https://allcontributors.org/docs/en/emoji-key)):
该项目遵循 [all-contributors](https://github.com/all-contributors/all-contributors) 规范。欢迎任何形式的贡献!
## 许可证
自 1.0 版本起,该库在 3-Clause BSD 许可证下可用。有关详细信息,请参见 [LICENSE](./LICENSE) 文件。
CLI11 是在 [辛辛那提大学][University of Cincinnati] 开发的,旨在支持 [NSF Award 1414736][] 资助下的 [GooFit][] 库。0.9 版本曾在 CERN 的 [DIANA/HEP][] 会议上亮相([参见幻灯片][diana slides])。请尝试使用它!我们始终欢迎您的反馈。
标签:C++, C++11, C++14, C++17, CLI, Conan, Conda, Header-only, HTTP头分析, WiFi技术, 参数解析, 参数验证, 命令行解析器, 子命令, 开源库, 恶意代码分析, 搜索引擎爬虫, 数据擦除, 编程工具, 软件开发, 远程代码执行, 配置文件