clips
是一个基于C++11
的命令行解析器,可方便的集成到源码工程中。
https://github.com/esdk-net/clips
特点
提供少量的、简单的接口;
方便创建基于子命令和嵌套命令的应用程序;
可在单独的文件中定义命令处理函数和绑定命令,使用 CLIPS_INIT()
;
仅有头文件;
没有外部依赖;
直接绑定变量(&varname
),或者使用'cast<typename>'
函数获取flag
值;
支持枚举值;
支持简单的自定义类型;
清晰的帮助信息;
友好的错误信息;
支持在Windows
、Linux
和macOS
平台下使用;
命令行 示例:
1 2 3 4 5 $ ./appname $ ./appname pull --all $ ./appname clone -v https::clips.repo.git clips $ ./appname remote rename old_name new_name $ ./appname remote set-url --delete dev https::clips.repo.git
帮助 可以查看子命令的详细帮助,参考下面:
1 2 3 4 5 6 7 8 9 10 $ ./appname -h $ ./appname --help $ ./appname [cmds...] -h $ ./appname [cmds...] --help $ ./appname a b c -h
帮助信息,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ ./appname -h desc usage: appname [cmds...] [args...] [--flags...] cmds: sub sub brief flags: -h, --help <bool> :(false) help --eee <unsigned int> (1) eee desc -f, --fff <int> (2) fff desc {0,1,2} example: appname sub arg0 arg1 --name=value for more information about a cmd: appname [cmds...] -h appname [cmds...] --help
flags
列定义:
1 2 flags: -n, --name <type> :extend(default) desc {enums}
注::(false)
中的:
表示,这个命令是extend
的,可能是从上级命令中继承过来的。
工程 目录结构示例:
1 2 3 4 5 6 + <project_path> + src - main.cpp - cmds_first.cpp - cmds_second.cpp + <others...>
包含目录路径:
在代码中包含文件头:
1 #include "clips/clips.hpp"
使用 主函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "clips/clips.hpp" int main (int argc, char * argv[]) { clips::desc("desc" ); auto ret = clips::exec(argc, argv); if (clips::ok != ret) { std ::cout << ret << std ::endl ; return 1 ; } return 0 ; }
根函数 根函数实际上是内部一个预置的最顶级的根命令,这个命令不具名。
定义根函数:
1 2 3 4 5 6 uint32_t g_ddd = 0 ;clips::error_t root (const clips::pcmd_t & pcmd, const clips::args_t & args) { std ::cout << "exec root handler. ddd=" << ddd << std ::endl ; return clips::ok; }
添加flag
, 和绑定处理函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 auto ret = clips::flag<uint32_t >("aaa" , "a" , 2 , "aaa desc" );if (ret != clips::ok){ return ret; } ret = clips::pflag<uint32_t >(&g_ddd, "ddd" , "" , 5 , "ddd desc" , true ); if (ret != clips::ok){ return ret; } ret = clips::bind(root); if (ret != clips::ok){ return ret; }
执行命令:
1 $ ./appname [args...] [--flags...]
注:根函数不是必须的,可以为空(默认)。
子命令 实际上就是嵌套命令,内部的根函数对应的根命令的嵌套命令。只是因为提供了顶级接口,就和下面的嵌套命令做了区分。
定义和绑定子命令:
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 auto sub = clips::make_cmd("sub" );sub->brief("sub 简洁描述" ); sub->desc("sub 详细描述" ); int fff = 0 ;sub->pflag<int >(&fff, "fff" , "f" , 2 , { 0 , 1 , 2 }, "fff desc" ); sub->flag<uint32_t >("eee" , "" , 1u , "eee desc" , true ); sub->example("sub --eee 0 -f 0" ); sub->bind([](const clips::pcmd_t & pcmd, const clips::args_t & args) -> clips::error_t { std ::cout << "exec sub." << std ::endl ; return clips::ok; } ); auto ret = clips::bind(sub);if (ret != clips::ok){ return ret; }
执行命令:
1 $ ./appname sub [args...] [--flags...]
嵌套命令 嵌套命令是指命令中包含命令。
当需要在 sub
命令下包含子命令 nested
, 则需要如下方法实现:
1 2 3 4 5 6 7 8 9 auto nested = clips::make_cmd("nested" );nested->brief("nested 简洁描述" ); nested->desc("nested 详细描述" ); auto ret = sub->bind(nested);if (ret != clips::ok){ return ret; }
执行命令:
1 $ ./appname sub nested [args...] [--flags...]
在单独文件中定义和绑定命令 当包含子命令和flag
很多时,在 main.cpp
实现所有交互逻辑,会显得臃肿不清晰。将各命令在单独的逻辑中实现,既条理清晰,又增加了程序的可测试性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 auto your_func(const clips::pcmd_t& pcmd, const clips::args_t& args) -> clips::error_t { return clips::ok; } CLIPS_INIT() { auto pcmd = clips::make_cmd("name" ); pcmd->bind(your_func); return clips::bind(pcmd); }
CLIPS_INIT()
定义的逻辑会在 clips::exec()
的一开始执行。当在多个文件中定义了 CLIPS_INIT()
时,其执行顺序和编译器有关,通常是文件名称的字典序。其中,根函数的绑定并不受该顺序的影响,可在任何位置定义,而其他命令因为是具名的,所以会受先后绑定的影响,后绑定的同名命令会导致函数返回重复定义的错误信息。
应用信息 应用名称 如果指定了应用名称,则在clips::exec()
中解析时不会被重写;如果不指定应用名称,则在clips::exec()
中解析时会使用argv[0]
代替;
1 2 clips::name("name" ); auto name = clips::name();
应用描述 1 2 clips::desc("desc" ); auto desc = clips::desc();
原始命令参数 1 auto & argv = clips::argv();
Flag
flag
一般只能通过命令接口添加。
添加 1 2 3 4 5 6 7 8 9 auto err = pcmd->flag<int >("name" , "n" , 0 , "desc" );auto value = flags["--name" ]->cast<int >();auto value = pcmd->cast<int >("--name" );int varname = 0 ; auto err = pcmd->pflag<int >(&varname, "name" , "n" , 0 , "desc" );
名称 flag
的名称为 "name"
时,对应的命令行是 --name
。名称不能为空。
快捷名称 flag
的快捷名称为 "n"
时,对应的命令行是 -n
。快捷名称可以为空或一个字符。
类型名称 1 auto type_name = flags["--name" ]->type_name();
继承 当设置 flag
可继承时,其分支之后的嵌套命令都可以访问。
flag
或 pflag
函数的最后一个参数为 true
时,表示可继承,默认是不继承(不给出对应参数时),如下:
1 auto err = pcmd->flag<int >("name" , "n" , 0 , "desc" , true );
内部解析flag
的时候,其查找顺序是先在当前命令中查找这个flag
,找不到时,再从根命令自顶向下查找,排除非extend
的flag
,直到找到或到当前命令为止。
可选项(枚举值) 当提供可选项(枚举值)时,会检查输入参数是否为枚举值之一。如果不合法,则clips::exec()
会返回错误。
1 2 auto err = pcmd->flag<int >("name" , "n" , 0 , {0 , 1 , 2 }, "desc" );auto err = pcmd->pflag<int >(&varname, "name" , "n" , 0 , {0 , 1 , 2 }, "desc" );
转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 if (flags["--name" ]->castable<std ::string >()){ } auto value = flags["--name" ]->cast<std ::string >();auto value = pcmd->cast<std ::string >("--name" );try { auto value = flags["--name" ]->cast<std ::string >(); auto value = pcmd->cast<std ::string >("--abc" ); } catch (std ::exception& e){ std ::cout << e.what() << std ::endl ; }
使用 error_t
:
1 2 3 4 5 6 error_t err;auto value = pcmd->cast<std ::string >("--abc" , &err);if (err != clips::ok){ return err; }
堆栈信息 您可能需要堆栈信息(也可从当前命令中获取),以在出错时帮助定位。
1 auto stack = flags["--name" ]->stack ();
默认值字符串 可查看默认值对应的字符串。
1 auto stack = flags["--name" ]->default_value();
输入的字符串值 在clips::exec()
中的执行解析逻辑后,才可以得到用户输入的字符串值,否则为空。
1 auto text = flags["--name" ]->text();
输入形式 1 2 3 4 5 6 7 8 9 10 11 --name -n --name true -n false --name 1 -n 0 --name value -n value --name=value -n=value -n '-1'
自定义类型 需要实现流处理操作符 <<
和 >>
的重载.
定义:
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 class custom_t { public : custom_t () { } ~custom_t () { } custom_t (const custom_t & cpy) : num_(cpy.num_) , msg_(cpy.msg_) { } custom_t (custom_t && mv) noexcept : num_(mv.num_) , msg_(std ::move(mv.msg_)) { } custom_t & operator =(const custom_t & rhs) { num_ = rhs.num_; msg_ = rhs.msg_; return *this ; } int num_{ 0 }; std ::string msg_; }; std ::ostream& operator <<(std ::ostream& os, const custom_t & obj){ os << "{" << obj.num_ << "," << obj.msg_ << "}" ; return os; } std ::istream& operator >>(std ::istream& is, custom_t & obj){ unsigned char c; std ::string tmp; is >> obj.num_ >> c >> obj.msg_; return is; }
Flag:
1 err = pcmd->flag<custom_t >("custom" , "" , {}, "custom_t type" );
转换:
1 std ::cout << pcmd->cast<custom_t >("--custom" ) << std ::endl ;
执行:
1 $ ./appname sub --custom=1,msg
命令 创建命令 建议的三种创建方式:
1 2 3 4 5 auto pcmd = clips::make_cmd();auto pcmd = clips::make_cmd("name" );
基本信息 1 2 3 4 5 6 7 8 9 10 11 pcmd->name("name" ); auto name = pcmd->name();pcmd->brief("brief" ); auto brief = pcmd->brief();pcmd->desc("desc" ); auto desc = pcmd->desc();
提供示例 示例只是一段文本,通常需要写如何执行这个命令。
1 2 pcmd->example("example" ); auto example = pcmd->example();
添加flag
1 2 3 4 5 6 7 8 auto err = pcmd->flag<int >("name" , "n" , 0 , "desc" );auto value = flags["--name" ]->cast<int >();auto value = pcmd->cast<int >("--name" );int varname = 0 ; auto err = pcmd->pflag<int >(&varname, "name" , "n" , 0 , "desc" );
注意变量的生命周期。
绑定函数 1 2 3 4 5 6 auto your_func(const clips::pcmd_t& cmd, const clips::args_t& args) -> clips::error_t { return clips::ok; } auto err = pcmd->bind(your_func);
注:命令函数不是必须的,可以为空(默认)。
嵌套命令 1 2 3 auto psub = clips::make_cmd("sub" );auto err = pcmd->bind(psub);
命令参数 定义如下:
1 $ ./appname sub nested [args...] [--flags...]
示例如下:
1 2 3 4 $ ./appname sub adfa $ ./appname sub $ ./appname sub nested adfa $ ./appname sub nested
错误信息 创建 1 2 3 auto err = clips::make_error();auto err = clips::make_error("msg" );auto err = clips::make_error("msg" , "stack" );
接口 错误信息:
1 2 err.msg("parse failed." ); auto msg = err.msg();
堆栈信息:
1 2 err.stack ("appname sub" ); auto stack = err.stack ();
流式打印:
1 std ::cout << err << std ::endl ;
比较 只关注错误信息是否相同,不关心堆栈信息。
err == clips::ok
成功;
err != clips::ok
失败;
不支持
TODO
帮助信息 help
文本定制
多语言国际化 i18n --lang {default, en-us, zh-cn}
类型安全 type safety
兼容 POSIX flags
智能推荐
测试 使用 Catch2
框架对 clips
进行单元测试, 推荐前往下面的网址,了解这个优秀的单元测试框架:
https://github.com/catchorg/Catch2