CLI11:命令行参数解析库

CLI11 是一个支持 C++11 标准的命令行参数解析器。除此之外,比较流行的命令行参数解析库有 Boost Program Options 和 Cxxopts 等。CLI11 的 GitHub 库的 README 中对几种常见的库进行了简单的比较,再次不赘述。选择这个库的原因是其支持像 git 命令那样的子命令。

接下来用一个完整的 CMake 项目,以实现 git 命令的 init 子命令作为示例说明这个库的用法。项目代码可以在 GitHub 仓库中查看。

首先一定要包含这三个头文件:

<CLI/App.hpp>
<CLI/Config.hpp>
<CLI/Formatter.hpp>

否则编译时在链接阶段会报错。

CLI11 库使用 CLI::App 对象表示一个解析器。我们用一个 application 类来封装代码:

namespace demoapp {
class application {
public:
    application();
    ~application();
    int run(int argc, char **argv);

private:
    CLI::App app;
};

}; // namespace demoapp

application::run 方法中通过 CLI11_PARSE 宏对命令行参数进行解析:

int application::run(int argc, char **argv)
{
    CLI11_PARSE(app, argc, argv);
    return 0;
}

CLI11_PARSE 宏的定义为:

try {
    app.parse(argc, argv);
} catch (const CLI::ParseError &e) {
    return app.exit(e);
}

默认情况下 CLI::App 对象不允许非选项参数,如果指定了非选项参数会抛出异常。在需要非选项参数的时候可调用 CLI::Appallow_extras() 方法允许非选项参数。

可通过 CLI::Appremaining() 方法访问所有的非选项参数。remaining_size() 返回非选项参数的个数。

可通过 CLI::Appadd_flag()add_option() 方法添加选项参数。flag 表示不需要值的参数,或者说值类型为 bool 的参数, option 表示需要值的参数。二者通常区别不大。

两个方法支持传入变量,用来将参数值保存在变量中。但是像 git 这样有很多子命令和选项参数的命令,这种做法不是很好。因此可以通过 CLI::Appget_option() 方法获取指定选项参数的 CLI::Option 对象,然后使用 CLI::Option 对象的 results() 方法或者 as() 方法获取参数值。我们就是用这种方法获取 init 子命令的选项参数:

const CLI::Option *option = app->get_option("--initial-branch");
option->results(branch);

option = app->get_option("--quiet");
option->results(quiet);

可通过 CLI::Appadd_subcommand() 方法添加子命令。该方法返回一个 CLI:App 对象,因此可以用上面提到的方法为子命令添加选项参数。

可使用 require_subcommand() 方法要求命令行参数中必须至少使用几个子命令。

application::application()
{
    app.description("Demo git command to show the usage of CLI11 library");
    app.name("git");

    auto init_subcmd = app.add_subcommand("init", "Create an empty Git repository or reinitialize an existing one");
    init_subcmd->add_option("-b,--initial-branch",
                            "Use the specified name for the initial branch in the newly created repository");
    init_subcmd->add_flag("-q,--quiet", "Only print warning and error messages.");
    init_subcmd->allow_extras();

    app.require_subcommand(1);
}

然后为 init 子命令定义一个类:

class init {
public:
    init(const CLI::App *app) { this->app = app; }
    ~init() {}
    int run();

private:
    const CLI::App *app;
    bool quiet;
    std::string dir{"."};
    std::string branch;

    int check_args();
};

私有方法 check_args() 检查参数是否合规,并设置相关变量。具体的业务逻辑放在 run() 方法中。