一个解析命令行参数的纯头文件库,仅需要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
在我的库里面,命令行参数分为两大类(可选的选项,和必选的参数),每类又分为两种,即共四种命令行参数。
选项,从语义上来说是可选的。它分成两种:一般选项和短路选项。
用如下方式添加一般选项:
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
只要命令行参数包含了这类参数,则调用回调函数,并立即(正常)退出程序。
如果有多个短路选项,按照添加的顺序搜索,仅调用第一个找到的短路选项的回调函数。
参数,和选项相反是必须提供的。如果某个参数没有提供,则程序会报错并退出。参数分为位置参数和命名参数两种
按照位置获取的参数,例如
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_error
;parse
中的错误称为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
的一些思路。随着用于我自己的工作中,逐渐修改成了现在的版本。