Skip to content

Latest commit

 

History

History

argparse

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

argparse

一个解析命令行参数的纯头文件库,仅需要c++11支持。

一个简单的例子

int main(int argc, char const *argv[])
{
    auto args = util::argparser("A quantum physics calculation program.");
    args.set_program_name("qcalc")
        .add_help_option()
        .use_color_error()
        .add_sc_option("-v", "--version", "show version info",
                       []() { std::cout << "qcalc version " << VERSION << std::endl; })
        .add_option("-o", "--openmp", "use openmp or not")
        .add_option("-m", "--mpi", "use mpi or not")
        .add_option<int>("-t", "--threads", "if openmp is set,\nuse how many threads,\ndefault is 16", 16)
        .add_option<int>("-p", "--processes", "if mpi is set,\nuse how many processes,\ndefault is 4", 4)
        .add_option<std::string>("", "--chemical", "chamical formula", "H2O")
        .add_option<util::StepRange>("-r", "--range", "range", util::range(0, 10, 2))
        .add_named_argument<std::string>("input", "initialize file")
        .add_named_argument<std::string>("output", "output file")
        .parse(argc, argv);
    if (args.has_option("--openmp"))
    {
        std::cout << "openmp is used, and we use " << args.get_option_int("--threads") << " threads" << std::endl;
    }
    if (args.has_option("--mpi"))
    {
        std::cout << "mpi is used, and we use " << args.get_option_int("--processes") << " processes" << std::endl;
    }
    std::cout << "the chemical formula is " << args.get_option_string("--chemical") << std::endl;
    std::cout << "calculate range is ";
    for (auto i : args.get_option<util::StepRange>("--range"))
    {
        std::cout << i << ',';
    }
    std::cout << std::endl;
    std::cout << "the input file is " << args.get_argument<std::string>("input") << std::endl;
    std::cout << "the output file is " << args.get_argument<std::string>("output") << std::endl;
    return 0;
}

将其编译为可执行文件qcalc,运行效果如下

> ./qcalc -?
usage: qcalc [options] [=input] [=output]

A quantum physics calculation program.

Options:
  -?, --help               show this help message
  -v, --version            show version info
  -o, --openmp             use openmp or not
  -m, --mpi                use mpi or not
  -t, --threads            (int) if openmp is set,
                           use how many threads,
                           default is 16
  -p, --processes          (int) if mpi is set,
                           use how many processes,
                           default is 4
  --chemical               (string) chamical formula
  -r, --range              (StepRange) range

Named arguments:
  input                    (string) initialize file
  output                   (string) output file
> ./qcalc -mo input=input.ini -p 2 -r 0:3:20 output=output.bin --chemical C6H6
openmp is used, and we use 16 threads
mpi is used, and we use 2 processes
the chemical formula is C6H6
calculate range is 0,3,6,9,12,15,18,
the input file is input.ini
the output file is output.bin

解析规则

在我的库里面,命令行参数分为两大类(可选的选项,和必选的参数),每类又分为两种,即共四种命令行参数。

option(选项)

选项,从语义上来说是可选的。它分成两种:一般选项和短路选项。

一般选项

用如下方式添加一般选项:

args.add_option<int>("-t", "--threads", "threads number, default is 16", 16);

其中第一个是短选项名,是可以为空字符串的,如果非空,则必须是一个'-'后接单个字符;第二个是长选项名,不能为空字符串,必须以"--"开头。第三个是帮助信息,最后一个是该选项的默认值。

一般选项默认支持:bool, int, int64_t, double, std::string这五种类型。本来不想加入int的,但是考虑到多数人还是习惯默认用int,所以还是加上吧。

除了bool型的option,其余的option添加时都要给定默认的值。 对于bool型,不需要默认值,检查到命令行参数有这个选项,就是true否则为false。例如

ls -l

此时-l选项为true。而其他类型选项需要在其后面加上参数,比如

greet --name Alice --age 24

于是--name选项的值为"Alice"--age选项的值为24

短路选项

短路选项(short circuit option)按照如下方式添加

args.add_sc_option("-v", "--version", "show version info", []() {
            std::cout << "version " << VERSION << std::endl;
        });

短路选项仅支持bool类型,添加该选项时需要给定一个回调函数。短路选项是最先搜索解析一种命令行参数。比如

git --help
gcc -v

只要命令行参数包含了这类参数,则调用回调函数,并立即(正常)退出程序。

如果有多个短路选项,按照添加的顺序搜索,仅调用第一个找到的短路选项的回调函数。

argument参数

参数,和选项相反是必须提供的。如果某个参数没有提供,则程序会报错并退出。参数分为位置参数和命名参数两种

位置参数

按照位置获取的参数,例如

dir /usr/bin

/usr/bin就是一个位置参数。一般而言位置参数不应该过多,不然这个命令行程序很难使用。

按照如下方式添加位置参数

args.add_argument<std::string>("input", "initialize file");

该函数的第一个参数是该参数的名字,用于获取该参数值时使用,第二个是帮助信息。参数不支持bool类型,默认支持int, int64_t, double, std::string四种类型。

命名参数

这是为了解决我个人工作中遇到的情况而定义的。它的使用方式例如

greet name=Alice age=24

使用如下函数添加命名参数

args.add_named_argument<std::string>("output", "output file");

显然这样使用参数非常繁琐,不应该作为轻量级的命令行程序使用。但是可以放在一个较重的工程中,并且运行的时候是用脚本调用程序而不是直接在命令行使用。这个时候,使用命名参数可以让你的脚本更具可读性。

解析命令行参数时,先解析命名参数,剩下的自动按照顺序赋值给位置参数。命名参数不必按照设置的顺序指定。

杂项

短选项名聚合指定

这是为了实现linux一些基本命令行工具类似的效果。比如

ls -lh

同时指定了-l-h选项。(这也是为什么选项的短名字仅允许一个字符。)

帮助选项

使用

args.add_help_option();

添加默认的帮助选项,它实际上相当于

args.add_sc_option("-?", "--help", "show this help message", [&args](){
            args.print_help();
        });

使用"-?"而不是"-h"是为了给其他选项留出空间,如果你不喜欢的话,可以用add_sc_option自行添加。

冲突

短路选项和一般选项的名字不允许冲突。命名参数和位置参数的名字也不允许冲突。

多行的帮助信息

有的时候我们的帮助信息很长,如果写在一行但是控制台的宽度不够造成换行会很难看。你可以在帮助信息里面加上换行符,它会根据的换行符在换行前加空格使得帮助信息看起来更好看。

获取结果

获取选项

短路选项通常用于一些程序非正常运行的情况,并且检测到就调用回调函数并立即退出。所以我们不需要获取短路选项的值。对于一般选项,你可以用模板函数

args.get_option<bool>("-o");

获取,也可以用我提供的一些别名,比如get_option_int。对于bool型选项,特别提供了has_option函数。

获取参数

命名参数和位置参数在设置和解析阶段有区别,但是在获取结果时没有区别,所以统一使用 get_argument<T>获取,同样提供一些类似get_argument_int的别名。

拓展

你可以拓展支持你想要的类型。只要你实现了如下两个模板特化(均在命名空间util下)

template <> inline std::string_view type_name<T>() {return "you type name"};
template <> inline std::optional<T> try_parse<T>(const std::string &str) {...}

然后使用add_option<T>等模板函数就可以了。我前面给出代码中,util::StepRange就是我自定义的一个类型。

其中try_parse默认实现是符号重载了std::istream>>,如果你有,并且有默认构造函数,那么可以不用特化。

错误处理

我将调用parse(argc, argv)之前称的错误称为build_errorparse中的错误称为parse_error,之后的错误称为get_error。 默认处理方式是打印错误并退出。你可以用use_color_error()给出一点红色,或使用use_exception_error()启用异常(throw std::runtime_error())。

你也可以自定义错误处理方法,使用reg_build_error(std::funtion<void(const std::string &)> error_handler)注册错误处理方法。

相似的项目

我的库最开始借鉴了cmdline的一些设计思路,也借鉴了python标准库的argparse的一些思路。随着用于我自己的工作中,逐渐修改成了现在的版本。