diff --git a/.gitignore b/.gitignore index 259148f..a5c6858 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,9 @@ *.exe *.out *.app + +# User +build +bin +ipch +.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8201448 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required (VERSION 2.6) + +set (MODULE_NAME clips) +project (${MODULE_NAME}) + +# def +if (NOT WIN32) +set (CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -g3 -ggdb") +set (CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3") +set (CMAKE_CXX_FLAGS "-fPIC -m64 -Wall -std=c++11 ${CMAKE_CXX_FLAGS}") +endif () +#add_definitions(-DEXPOERTS) + +#------------------------------------------------------------------------------ +# module + +ADD_SUBDIRECTORY(src) +ADD_SUBDIRECTORY(examples) + +#------------------------------------------------------------------------------ + +if (TEST) +ADD_SUBDIRECTORY(test) +endif () + +#------------------------------------------------------------------------------ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..00172ee --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ + 木兰宽松许可证, 第1版 + + 木兰宽松许可证, 第1版 + 2019年8月 http://license.coscl.org.cn/MulanPSL + + 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第1版(“本许可证”)的如下条款的约束: + + 0. 定义 + + “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 + + “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 + + “法人实体”是指提交贡献的机构及其“关联实体”。 + + “关联实体”是指,对“本许可证”下的一方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 + + “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 + + 1. 授予版权许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 + + 2. 授予专利许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括仅因您或他人修改“贡献”或其他结合而将必然会侵犯到的专利权利要求。如您或您的“关联实体”直接或间接地(包括通过代理、专利被许可人或受让人),就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 + + 3. 无商标许可 + + “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 + + 4. 分发限制 + + 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 + + 5. 免责声明与责任限制 + + “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 + + 条款结束。 + + 如何将木兰宽松许可证,第1版,应用到您的软件 + + 如果您希望将木兰宽松许可证,第1版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: + + 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; + + 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; + + 3, 请将如下声明文本放入每个源文件的头部注释中。 + + Copyright (c) [2019] [name of copyright holder] + [Software Name] is licensed under the Mulan PSL v1. + You can use this software according to the terms and conditions of the Mulan PSL v1. + You may obtain a copy of Mulan PSL v1 at: + http://license.coscl.org.cn/MulanPSL + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + PURPOSE. + See the Mulan PSL v1 for more details. + + + Mulan Permissive Software License,Version 1 + + Mulan Permissive Software License,Version 1 (Mulan PSL v1) + August 2019 http://license.coscl.org.cn/MulanPSL + + Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v1 (this License) with following terms and conditions: + + 0. Definition + + Software means the program and related documents which are comprised of those Contribution and licensed under this License. + + Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. + + Legal Entity means the entity making a Contribution and all its Affiliates. + + Affiliates means entities that control, or are controlled by, or are under common control with a party to this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. + + Contribution means the copyrightable work licensed by a particular Contributor under this License. + + 1. Grant of Copyright License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. + + 2. Grant of Patent License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed, excluding of any patent claims solely be infringed by your or others’ modification or other combinations. If you or your Affiliates directly or indirectly (including through an agent, patent licensee or assignee), institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. + + 3. No Trademark License + + No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4. + + 4. Distribution Restriction + + You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. + + 5. Disclaimer of Warranty and Limitation of Liability + + The Software and Contribution in it are provided without warranties of any kind, either express or implied. In no event shall any Contributor or copyright holder be liable to you for any damages, including, but not limited to any direct, or indirect, special or consequential damages arising from your use or inability to use the Software or the Contribution in it, no matter how it’s caused or based on which legal theory, even if advised of the possibility of such damages. + + End of the Terms and Conditions + + How to apply the Mulan Permissive Software License,Version 1 (Mulan PSL v1) to your software + + To apply the Mulan PSL v1 to your work, for easy identification by recipients, you are suggested to complete following three steps: + + i. Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; + ii. Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; + iii. Attach the statement to the appropriate annotated syntax at the beginning of each source file. + + Copyright (c) [2019] [name of copyright holder] + [Software Name] is licensed under the Mulan PSL v1. + You can use this software according to the terms and conditions of the Mulan PSL v1. + You may obtain a copy of Mulan PSL v1 at: + http://license.coscl.org.cn/MulanPSL + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + PURPOSE. + + See the Mulan PSL v1 for more details. \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..0942346 --- /dev/null +++ b/build.bat @@ -0,0 +1,19 @@ +@echo off + +rem build dir +if not exist "build" ( + md "build" +) +if not exist "build" ( + md "build" +) + +rem build +cd "build" +cmake -G "Visual Studio 15 Win64" -DCMAKE_BUILD_TYPE=Release .. +cmake --build . + +rem home +cd .. + +pause diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..3ed58ee --- /dev/null +++ b/build.sh @@ -0,0 +1,16 @@ + +if [ ! -d "build" ]; then + mkdir build +fi + +if [ -d "build" ]; then + rm -rf build +fi +mkdir build + +cd build +cmake -DCMAKE_BUILD_TYPE=Release .. +make -j8 +#cpack --config CPackConfig.cmake + +cd .. diff --git a/docs/readme-zh.md b/docs/readme-zh.md new file mode 100644 index 0000000..fe98f52 --- /dev/null +++ b/docs/readme-zh.md @@ -0,0 +1,655 @@ +# Clips + +[English Docs](../readme.md) | [中文文档](readme-zh.md) + +# 概述 + +`clips` 是一个基于`C++11`的命令行解析器,可方便的集成到源码工程中。 + +# 特点 + +- 提供少量的、简单的接口; +- 方便创建基于子命令和嵌套命令的应用程序; +- 可在单独的文件中定义命令处理函数和绑定命令,使用 `CLIPS_INIT()`; +- 仅有头文件; +- 没有外部依赖; +- 直接绑定变量(`&varname`),或者使用`'cast'`函数获取`flag`值; +- 支持枚举值; +- 支持简单的自定义类型; +- 清晰的帮助信息; +- 友好的错误信息; +- 支持在`Windows`、`Linux`和`macOS`平台下使用; + +# 命令行 + +示例: + +```yaml +$ ./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 +``` + +# 帮助 + +可以查看子命令的详细帮助,参考下面: + +```yaml +# 帮助 +$ ./appname -h +$ ./appname --help + +# 嵌套命令的帮助 +$ ./appname [cmds...] -h +$ ./appname [cmds...] --help + +# 示例 +$ ./appname a b c -h +``` + +帮助信息,示例: + +```yaml +$ ./appname -h + +desc + +usage: + appname [cmds...] [args...] [--flags...] + +cmds: + sub sub brief + +flags: + -h, --help :(false) help + --eee (1) eee desc + -f, --fff (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` 列定义: + +```yaml +flags: + -n, --name :extend(default) desc {enums} +``` + +注:`:(false)`中的`:`表示,这个命令是`extend`的,可能是从上级命令中继承过来的。 + +# 工程 + +目录结构示例: + +```yaml ++ + + src + - main.cpp + - cmd_first.cpp + - cmd_second.cpp + + +``` + +包含目录路径: + +```cpp +clips/include +``` + +在代码中包含文件头: + +```cpp +#include "clips/clips.hpp" +``` + +# 使用 + +## 主函数 + +```cpp +#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; +} +``` + +## 根函数 + +根函数实际上是内部一个预置的最顶级的根命令,这个命令不具名。 + +定义根函数: + +```cpp +uint32_t g_ddd = 0; +clips::error_t root(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec root handler. ddd=" << ddd << std::endl; + return clips::ok; +} +``` + +添加`flag`, 和绑定处理函数: + +```cpp + +auto ret = clips::flag("aaa", "a", 2, "aaa desc"); +if (ret != clips::ok) +{ + return ret; +} + +ret = clips::pflag(&g_ddd, "ddd", "", 5, "ddd desc", true); +if (ret != clips::ok) +{ + return ret; +} + +ret = clips::bind(root); +if (ret != clips::ok) +{ + return ret; +} +``` + +执行命令: + +```yaml +$ ./appname [args...] [--flags...] +``` + +注:根函数不是必须的,可以为空(默认)。 + +## 子命令 + +实际上就是嵌套命令,内部的根函数对应的根命令的嵌套命令。只是因为提供了顶级接口,就和下面的嵌套命令做了区分。 + +定义和绑定子命令: + +```cpp +// 创建 +auto sub = clips::make_cmd("sub", "sub brief", "sub desc"); + +// 添加 flags +int fff = 0; +sub->pflag(&fff, "fff", "f", 2, { 0, 1, 2 }, "fff desc"); +sub->flag("eee", "", 1u, "eee desc", true); + +// 示例 +sub->example("sub --eee 0 -f 0"); + +// 绑定函数 +sub->bind([](const clips::pcmd_t& cmd, + const clips::args_t& args, + const clips::flags_t& flags) -> clips::error_t + { + std::cout << "exec sub." << std::endl; + return clips::ok; + }); + +// 绑定命令 +auto ret = clips::bind(sub); +if (ret != clips::ok) +{ + return ret; +} +``` + +执行命令: + +```yaml +$ ./appname sub [args...] [--flags...] +``` + +## 嵌套命令 + +嵌套命令是指命令中包含命令。 + +当需要在 `sub` 命令下包含子命令 `nested`, 则需要如下方法实现: + +```cpp +auto nested = clips::make_cmd("nested", "nested brief", "nested desc"); +// ... +auto ret = sub->bind(nested); +if (ret != clips::ok) +{ + return ret; +} +``` + +执行命令: + +```yaml +$ ./appname sub nested [args...] [--flags...] +``` + +## 在单独文件中定义和绑定命令 + +当包含子命令和`flag`很多时,在 `main.cpp` 实现所有交互逻辑,会显得臃肿不清晰。将各命令在单独的逻辑中实现,既条理清晰,又增加了程序的可测试性。 + +```cpp +auto your_func(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) -> clips::error_t +{ + // ... + return clips::ok; +} + +CLIPS_INIT() +{ + // 这个函数将会在解析命令行参数之前被调用。 + + auto pcmd = clips::make_cmd("name"); + // ... + pcmd->bind(your_func); // 也可以使用lambda函数 + return clips::bind(pcmd); +} +``` + +`CLIPS_INIT()`定义的逻辑会在 `clips::exec()` 的一开始执行。当在多个文件中定义了 `CLIPS_INIT()` 时,其执行顺序和编译器有关,通常是文件名称的字典序。其中,根函数的绑定并不受该顺序的影响,可在任何位置定义,而其他命令因为是具名的,所以会受先后绑定的影响,后绑定的同名命令会导致函数返回重复定义的错误信息。 + +# 应用信息 + +## 应用名称 + +如果指定了应用名称,则在`clips::exec()`中解析时不会被重写;如果不指定应用名称,则在`clips::exec()`中解析时会使用`argv[0]`代替; + +```cpp +clips::name("name"); +auto name = clips::name(); +``` + +## 应用描述 + +```cpp +clips::desc("desc"); +auto desc = clips::desc(); +``` + +## 原始命令参数 + +```cpp +auto argv = clips::argv(); +``` + +# `Flag` + +`flag`一般只能通过命令接口添加。 + +## 添加 + +```cpp +// 需要用 cast() 转换 +auto err = pcmd->flag("name", "n", 0, "desc"); +auto value = flags["--name"]->cast(); +// 或 +auto value = clips::cast("--name"); + +// 绑定变量 +int varname = 0; // 也可用 cast() 转换 +auto err = pcmd->pflag(&varname, "name", "n", 0, "desc"); +``` + +## 名称 + +`flag` 的名称为 `"name"` 时,对应的命令行是 `--name`。名称不能为空。 + +## 快捷名称 + +`flag` 的快捷名称为 `"n"` 时,对应的命令行是 `-n`。快捷名称可以为空或一个字符。 + +## 类型名称 + +```cpp +auto type_name = flags["--name"]->type_name(); +``` + +## 继承 + +当设置 `flag` 可继承时,其分支之后的嵌套命令都可以访问。 + +`flag` 或 `pflag` 函数的最后一个参数为 `true` 时,表示可继承,默认是不继承(不给出对应参数时),如下: + +```cpp +auto err = pcmd->flag("name", "n", 0, "desc", true); +``` + +内部解析`flag`的时候,其查找顺序是先在当前命令中查找这个`flag`,找不到时,再从根命令自顶向下查找,排除非`extend`的`flag`,直到找到或到当前命令为止。 + +## 可选项(枚举值) + +当提供可选项(枚举值)时,会检查输入参数是否为枚举值之一。如果不合法,则`clips::exec()`会返回错误。 + +```cpp +auto err = pcmd->flag("name", "n", 0, {0, 1, 2}, "desc"); +auto err = pcmd->pflag(&varname, "name", "n", 0, {0, 1, 2}, "desc"); +``` + +## 转换 + +```cpp +// 是否可以转换为目标类型 +if (flags["--name"]->castable()) +{ + // 可以转换 +} + +// 转换为目标类型值 +auto value = flags["--name"]->cast(); +// 或 +auto value = clips::cast("--name"); + +// 不能转换为目标类型时,会抛出异常 +try +{ + auto value = flags["--name"]->cast(); + // 或 + auto value = clips::cast("--abc"); +} +catch (std::exception& e) +{ + std::cout << e.what() << std::endl; +} +``` + +使用 `error_t`: + +```cpp +error_t err; +auto value = clips::cast("--abc", &err); +if (err != clips::ok) +{ + return err; +} +``` + +## 堆栈信息 + +您可能需要堆栈信息(也可从当前命令中获取),以在出错时帮助定位。 + +```cpp +auto stack = flags["--name"]->stack(); +``` + +## 默认值字符串 + +可查看默认值对应的字符串。 + +```cpp +auto stack = flags["--name"]->default_value(); +``` + +## 输入的字符串值 + +在`clips::exec()`中的执行解析逻辑后,才可以得到用户输入的字符串值,否则为空。 + +```cpp +auto text = flags["--name"]->text(); +``` + +## 输入形式 + +```yaml +--name # bool 型 +-n # bool 型 +--name true # bool 型 +-n false # bool 型 +--name 1 # bool 型 +-n 0 # bool 型 +--name value # 空格区隔名称和值 +-n value # 空格区隔名称和值 +--name=value # 等号区隔名称和值 +-n=value # 等号区隔名称和值 +-n '-1' # 单引号防止命令行解析规则导致的转义 +``` + +## 自定义类型 + +需要实现流处理操作符 `<<` 和 `>>` 的重载. + +定义: + +```cpp +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) + : 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: + +```cpp +err = pcmd->flag("custom", "", {}, "custom_t type"); +``` + +转换: + +```cpp +std::cout << clips::cast("--custom") << std::endl; +``` + +执行: + +```yaml +$ ./appname sub --custom=1,msg +``` + +# 命令 + +## 创建命令 + +建议的三种创建方式: + +```cpp +// 不带参数 +auto pcmd = clips::make_cmd(); + +// 给定名称 +auto pcmd = clips::make_cmd("name"); + +// 给定基本信息 +auto pcmd = clips::make_cmd("name", "brief", "desc"); +``` + +## 基本信息 + +```cpp +/// name 名称 +pcmd->name("name"); +auto name = pcmd->name(); + +/// brief 简短描述 +pcmd->brief("brief"); +auto brief = pcmd->brief(); + +/// desc 完整描述 +pcmd->desc("desc"); +auto desc = pcmd->desc(); +``` + +## 提供示例 + +示例只是一段文本,通常需要写如何执行这个命令。 + +```cpp +pcmd->example("example"); +auto example = pcmd->example(); +``` + +## 添加`flag` + +```cpp +// 需要用 cast() 转换 +auto err = pcmd->flag("name", "n", 0, "desc"); +auto value = flags["--name"]->cast(); +auto value = clips::cast("--name"); + +// 绑定变量 +int varname = 0; // 也可用 cast() 转换 +auto err = pcmd->pflag(&varname, "name", "n", 0, "desc"); +``` + +注意变量的生命周期。 + +## 绑定函数 + +```cpp +auto your_func(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) -> clips::error_t +{ + // ... + return clips::ok; +} +auto err = pcmd->bind(your_func); // 或者使用lambda函数 +``` + +注:命令函数不是必须的,可以为空(默认)。 + +## 嵌套命令 + +```cpp +auto psub = clips::make_cmd("sub"); +// ... +auto err = pcmd->bind(psub); +``` + +## 命令参数 + +定义如下: + +```yaml +$ ./appname sub nested [args...] [--flags...] +``` + +如果命令包含嵌套命令,则输入的非`flag`可能会被识别为未定义的命令,此种情况只允许无参数方式执行,如下: + +```yaml +$ ./appname sub adfa # 会报错误,因为sub包含nested,adfa 被认为未定义的命令 +$ ./appname sub # ok, args 大小为 0 +``` + +如果命令不包含嵌套命令,则输入的非`flag`都会被识别为参数。如下: + +```yaml +$ ./appname sub nested adfa # ok, args[0] 为 adfa +$ ./appname sub nested # ok, args 大小为 0 +``` + +`Flags` 不受这个规则的限制。 + +# 错误信息 + +## 创建 + +```cpp +auto err = clips::make_error(); +auto err = clips::make_error("msg"); +auto err = clips::make_error("msg", "stack"); +``` + +## 接口 + +错误信息: + +```cpp +err.msg("parse failed."); +auto msg = err.msg(); +``` + +堆栈信息: + +```cpp +err.stack("appname sub"); +auto stack = err.stack(); +``` + +流式打印: + +```cpp +std::cout << err << std::endl; +``` + +## 比较 + +只关注错误信息是否相同,不关心堆栈信息。 + +- `err == clips::ok` 成功; +- `err != clips::ok` 失败; + +# 不支持 + +- 非标准`flag`, 如 `-long` 或 `--h`; + +# TODO + +- 帮助信息 `help` + - 文本定制 + - 多语言国际化 `i18n --lang {default, en-us, zh-cn}` +- 类型安全 `type safety` + - 更多类型检查 `type checking` +- 兼容 `POSIX flags` + - 组合`flag` 支持, 如 `-abc` +- 智能推荐 + +# 测试 + +使用 `Catch2` 框架对 `clips` 进行单元测试, 推荐前往下面的网址,了解这个优秀的单元测试框架: + +[https://github.com/catchorg/Catch2](https://github.com/catchorg/Catch2) \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..731392e --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required (VERSION 2.6) + +set (MODULE_NAME clips_examples) +project (${MODULE_NAME}) + +#------------------------------------------------------------------------------ +# examples + +# message +message ("${MODULE_NAME} for ${CMAKE_BUILD_TYPE}") + +# include +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include/) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../examples) + +# src +file(GLOB_RECURSE HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/../include/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../include/*.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/../examples/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../examples/*.hpp" +) +source_group("includes" FILES ${HEADERS}) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/../examples SRCS) + +# target +add_executable(${MODULE_NAME} ${SRCS} ${HEADERS}) + +#install +set (EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin) + +#------------------------------------------------------------------------------ diff --git a/examples/clips_error.cpp b/examples/clips_error.cpp new file mode 100644 index 0000000..90aa965 --- /dev/null +++ b/examples/clips_error.cpp @@ -0,0 +1,32 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// + +#include "clips/clips.hpp" + +clips::error_t error_handler(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec error_handler" << std::endl; + return clips::make_error("exec failed.", cmd->stack()); +} + +CLIPS_INIT() +{ + auto cerror = clips::make_cmd("error", "command handler return error", + "command handler return error, it will be show an error message."); + cerror->example("error"); + cerror->bind(error_handler); + + return clips::bind(cerror); +} \ No newline at end of file diff --git a/examples/clips_init.cpp b/examples/clips_init.cpp new file mode 100644 index 0000000..3b66d62 --- /dev/null +++ b/examples/clips_init.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// + +#include "clips/clips.hpp" + +CLIPS_INIT() +{ + // 它会导致exec()终止,并返回该错误信息 + //return clips::make_error("init failed.", __FILE__); + return clips::ok; +} \ No newline at end of file diff --git a/examples/clips_nonested.cpp b/examples/clips_nonested.cpp new file mode 100644 index 0000000..b1778c6 --- /dev/null +++ b/examples/clips_nonested.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// + +#include "clips/clips.hpp" + +CLIPS_INIT() +{ + auto pcmd = clips::make_cmd("nonested", "nonested subcommand", "subcommand and has no nested command"); + pcmd->example("nonested"); + pcmd->bind([](const clips::pcmd_t& cmd, + const clips::args_t& args, + const clips::flags_t& flags) -> clips::error_t + { + std::cout << "exec nonested handler" << std::endl; + + std::cout << " args{"; + for (auto& item : args) + { + std::cout << item << ", "; + } + std::cout << "}" << std::endl; + + return clips::ok; + }); + + return clips::bind(pcmd); +} \ No newline at end of file diff --git a/examples/clips_null.cpp b/examples/clips_null.cpp new file mode 100644 index 0000000..c9bd93e --- /dev/null +++ b/examples/clips_null.cpp @@ -0,0 +1,25 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// + +#include "clips/clips.hpp" + +CLIPS_INIT() +{ + auto cnull = clips::make_cmd("null", "command handler is null", + "command handler is null, it will be nothing to do"); + cnull->example("null"); + + return clips::bind(cnull); +} \ No newline at end of file diff --git a/examples/clips_root.cpp b/examples/clips_root.cpp new file mode 100644 index 0000000..efdcb21 --- /dev/null +++ b/examples/clips_root.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// + +#include "clips/clips.hpp" + +static uint32_t g_global = 0; + +clips::error_t root(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec root handler" << std::endl; + + std::cout << " args{"; + for (auto& item : args) + { + std::cout << item << ", "; + } + std::cout << "}" << std::endl; + + std::cout << " flags{ global=" << g_global + << ", extend=" << clips::cast("-e") + << "}" << std::endl; + + return clips::ok; +} + +CLIPS_INIT() +{ + clips::pflag(&g_global, "global", "g", 0, "bind a global var."); + clips::flag("extend", "e", 1, "extend flag from root", true); + + return clips::bind(root); +} \ No newline at end of file diff --git a/examples/clips_sub.cpp b/examples/clips_sub.cpp new file mode 100644 index 0000000..602466d --- /dev/null +++ b/examples/clips_sub.cpp @@ -0,0 +1,112 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// + +#include "clips/clips.hpp" + +clips::error_t sub_handler(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec sub handler" << std::endl; + + std::cout << " args{"; + for (auto& item : args) + { + std::cout << item << ", "; + } + std::cout << "}" << std::endl; + + std::cout << " flags{extend=" << clips::cast("-e") + << "}" << std::endl; + + return clips::ok; +} + +clips::error_t nested_handler(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec sub nested handler" << std::endl; + + std::cout << " args{"; + for (auto& item : args) + { + std::cout << item << ", "; + } + std::cout << "}" << std::endl; + + std::cout << " flags{extend=" << clips::cast("-e") + << "}" << std::endl; + + return clips::ok; +} + +clips::error_t leaf_handler(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec sub nested leaf handler" << std::endl; + + std::cout << " args{"; + for (auto& item : args) + { + std::cout << item << ", "; + } + std::cout << "}" << std::endl; + + std::cout << " flags{extend=" << clips::cast("-e") + << ", num=" << clips::cast("--num") + << ", enum=" << clips::cast("--enum") + << "}" << std::endl; + + return clips::ok; +} + +CLIPS_INIT() +{ + // sub + auto sub = clips::make_cmd("sub", "subcommand", "subcommand and have nested command"); + sub->example("sub"); + sub->bind(sub_handler); + + // nested + auto nested = clips::make_cmd("nested", "a nested command", "a nested command"); + nested->example("sub nested"); + nested->bind(nested_handler); + + // leaf + auto leaf = clips::make_cmd("leaf", "a leaf command", "a leaf command"); + + auto err = leaf->flag("num", "n", 1, "num desc"); + if (err != clips::ok) + { + return err; + } + err = leaf->flag("enum", "", 2, { 0, 1, 2 }, "enum desc", false); + if (err != clips::ok) + { + return err; + } + + leaf->example("sub nested leaf example"); + leaf->bind(leaf_handler); + + err = nested->bind(leaf); + if (err != clips::ok) + { + return err; + } + err = sub->bind(nested); + if (err != clips::ok) + { + return err; + } + + return clips::bind(sub); +} \ No newline at end of file diff --git a/examples/clips_test.cpp b/examples/clips_test.cpp new file mode 100644 index 0000000..4c98378 --- /dev/null +++ b/examples/clips_test.cpp @@ -0,0 +1,230 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// + +#include "clips/clips.hpp" + +clips::error_t test_handler(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec test handler" << std::endl; + + std::cout << " args{"; + for (auto& item : args) + { + std::cout << item << ", "; + } + std::cout << "}" << std::endl; + + std::cout << " flags{extend=" << clips::cast("--extend") + << "}" << std::endl; + + return clips::ok; +} + +clips::error_t base_handler(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec test base handler" << std::endl; + + std::cout << " args{"; + for (auto& item : args) + { + std::cout << item << ", "; + } + std::cout << "}" << std::endl; + + std::cout << " flags{" << std::endl + << " extend =" << clips::cast("--extend") << std::endl + << " bool =" << clips::cast("--bool") << std::endl + << " char =" << clips::cast("--char") << std::endl + << " int8 =" << clips::cast("--int8") << std::endl + << " uint8 =" << clips::cast("--uint8") << std::endl + << " int16 =" << clips::cast("--int16") << std::endl + << " uint16 =" << clips::cast("--uint16") << std::endl + << " int32 =" << clips::cast("--int32") << std::endl + << " uint32 =" << clips::cast("--uint32") << std::endl + << " int64 =" << clips::cast("--int64") << std::endl + << " uint64 =" << clips::cast("--uint64") << std::endl + << " float =" << clips::cast("--float") << std::endl + << " double =" << clips::cast("--double") << std::endl + << " string =" << clips::cast("--string") << std::endl + << "}" << std::endl; + + return clips::ok; +} + +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) + : 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; +} + +clips::error_t custom_handler(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec test custom handler" << std::endl; + + std::cout << " args{"; + for (auto& item : args) + { + std::cout << item << ", "; + } + std::cout << "}" << std::endl; + + std::cout << " flags{" << std::endl + << " extend=" << clips::cast("--extend") << std::endl + << " custom=" << clips::cast("--custom") << std::endl + << "}" << std::endl; + + return clips::ok; +} + +CLIPS_INIT() +{ + auto test = clips::make_cmd("test", "test clips", "test clips for some type flags."); + test->example("test --name=value"); + test->bind(test_handler); + + auto base = clips::make_cmd("base", "test base types", "test base types."); + base->example("test base --name=value"); + + auto err = base->flag("bool", "", false, "bool type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("char", "", 0u, "char type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("int8", "", 0, "int8_t type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("uint8", "", 0u, "uint8_t type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("int16", "", 0, "int16_t type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("uint16", "", 0u, "uint16_t type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("int32", "", 0, "int32_t type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("uint32", "", 0u, "uint32_t type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("int64", "", 0, "int64_t type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("uint64", "", 0u, "uint64_t type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("float", "", 0.0f, "float type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("double", "", 0.0, "double type"); + if (err != clips::ok) + { + return err; + } + err = base->flag("string", "", "", "string type"); + if (err != clips::ok) + { + return err; + } + base->bind(base_handler); + + auto custom = clips::make_cmd("custom", "test custom types", "test custom types."); + err = custom->flag("custom", "", {}, "custom_t type"); + if (err != clips::ok) + { + return err; + } + custom->example("test custom --name=value"); + custom->bind(custom_handler); + + err = test->bind(base); + if (err != clips::ok) + { + return err; + } + err = test->bind(custom); + if (err != clips::ok) + { + return err; + } + + return clips::bind(test); +} \ No newline at end of file diff --git a/examples/main.cpp b/examples/main.cpp new file mode 100644 index 0000000..66e317c --- /dev/null +++ b/examples/main.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// + +#include "clips/clips.hpp" + +int main(int argc, char* argv[]) +{ + // ӦõϢhelpʾ + clips::desc("desc"); + + // ִ + auto ret = clips::exec(argc, argv); + if (clips::ok != ret) + { + std::cout << ret << std::endl; + return 1; + } + return 0; +} \ No newline at end of file diff --git a/include/clips/clips.hpp b/include/clips/clips.hpp new file mode 100644 index 0000000..bfe9f6f --- /dev/null +++ b/include/clips/clips.hpp @@ -0,0 +1,2597 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// +// api list: +// clips::name() 应用名称 +// clips::desc() 应用描述 +// clips::make_error() 创建错误信息 +// clips::make_cmd() 创建命令 +// clips::flag() 添加flag +// clips::pflag() 添加flag,绑定外部变量 +// clips::bind() 绑定根函数,绑定子命令等 +// clips::exec() 解析,执行 +// clips::argv() 原始命令参数 +// +// clips_cmd.cpp: +// static uint32_t g_ccc = 0; +// clips::error_t your_func(const pcmd_t& cmd, const args_t& args, const flags_t& flags) +// { +// 处理 +// return clips::ok; +// } +// CLIPS_INIT() +// { +// auto sub = clips::make_cmd(); +// sub->name("name"); +// sub->brief("brief"); +// sub->desc("desc"); +// sub->flag("aaa", "a", 1, "desc"); +// sub->flag("bbb", "b", 2, { 0, 1, 2 }, "desc", true); +// sub->pflag(&g_ccc, "ccc", "", 5, "desc", true); +// sub->example("example"); +// sub->bind(your_func); +// return clips::bind(sub); +// } +// +// main.cpp: +// static uint32_t g_fff = 0; +// clips::error_t your_root(const pcmd_t& cmd, const args_t& args, const flags_t& flags) +// { +// 处理 +// return clips::ok; +// } +// int main(int argc, char* argv[]) +// { +// clips::name(argv[0]); +// clips::desc("desc"); +// clips::flag("ccc", "c", 2, "desc"); +// clips::flag("ddd", "", 3, "desc"); +// clips::flag("eee", "", 4, "desc", true); +// clips::pflag(&g_fff, "fff", "", 5, "desc", true); +// clips::bind(your_root); +// +// auto ret = clips::exec(argc, argv); +// if (clips::ok != ret) +// { +// std::cout << ret << std::endl; +// return 1; +// } +// return 0; +// } + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef WIN32 +#define strcasecmp _stricmp +#endif + +/// CLIPS_INIT 初始化函数 +#define CLIPS_INIT() INNER_CLIPS_INIT_IMPL(__FILE__, __LINE__) + +// INNER_CLIPS_INIT_IMPL 实现 +#define INNER_CLIPS_INIT_IMPL(file, line) \ + INNER_CLIPS_INIT_FUNC_IMPL(INNER_CLIPS_CLI_UNIQUE_NAME(__CLIPS__INIT__LINE__FUNC__, line), file, line) +#define INNER_CLIPS_INIT_FUNC_IMPL(func_name, file, line) \ + static clips::error_t func_name(); \ + namespace { \ + static const bool INNER_CLIPS_CLI_UNIQUE_NAME(__CLIPS__CMD__LINE__LOCAL__VAR__, line) \ + INNER_CLIPS_CLI_UNUSED { clips::_bind_init_func(func_name, file, line) }; \ + } \ + static clips::error_t func_name() + +// generate unique name +#define INNER_CLIPS_CLI_UNIQUE_NAME(name, line) name##line##__ + +// disable unused warning +#ifdef __GNUC__ +#define INNER_CLIPS_CLI_UNUSED __attribute__((unused)) +#else +#define INNER_CLIPS_CLI_UNUSED +#endif + +// version 版本 +constexpr char* CLIPS_VERSION_CODE = "0.1.0"; +constexpr uint32_t CLIPS_VERSION_NUMBER_FUNC(int major, int minor, int patch) +{ + return ((major) * 1000 * 1000 + (minor) * 1000 + (patch)); +} +constexpr uint32_t CLIPS_VERSION_NUMBER = CLIPS_VERSION_NUMBER_FUNC(0, 1, 0); + +namespace clips { +// ---------------------------------------------------------------------------- + +/// version 版本 +static const std::string& version() +{ + return CLIPS_VERSION_CODE; +} + +/// version 版本 +static uint32_t version_number() +{ + return CLIPS_VERSION_NUMBER; +} + +// ---------------------------------------------------------------------------- +// forward declaration + +static void name(const std::string& name); +static const std::string& name(); +static void desc(const std::string& desc); +static const std::string& desc(); + +class cmd; +class flag_t; + +// ---------------------------------------------------------------------------- +// error_t + +/// error_t 错误信息 +class error_t +{ +public: + error_t() + { + } + + explicit error_t(const std::string& msg) + : msg_(msg) + { + } + + explicit error_t(const std::string& msg, const std::string& stack) + : msg_(msg) + , stack_(stack) + { + } + + error_t(const error_t& cpy) + : msg_(cpy.msg_) + , stack_(cpy.stack_) + { + } + + error_t(error_t&& mv) + : msg_(std::move(mv.msg_)) + , stack_(std::move(mv.stack_)) + { + } + + error_t& operator=(const error_t& rhs) + { + msg_ = rhs.msg_; + stack_ = rhs.stack_; + return *this; + } + + // == 等于 + // 只判断是否是同一种错误信息,而不关心堆栈信息是否相同 + bool operator==(const error_t& rhs) const + { + return (msg_ == rhs.msg_); + } + + // != 不等于 + // 只判断是否是同一种错误信息,而不关心堆栈信息是否相同 + bool operator!=(const error_t& rhs) const + { + return (msg_ != rhs.msg_); + } + + // << 流式打印 + std::ostream& operator<<(std::ostream& ss) const + { + ss << msg_ << " " << stack_; + return ss; + } + + // to_string 错误和堆栈信息 + std::string to_string() const + { + return std::move(msg_ + " " + stack_); + } + + // msg 错误信息 + void msg(const std::string& msg) + { + msg_ = msg; + } + + // msg 错误信息 + const std::string& msg() const + { + return msg_; + } + + // statck 堆栈,可确定位置信息 + void stack(const std::string& stack) + { + stack_ = stack; + } + + // statck 堆栈,可确定位置信息 + const std::string& stack() const + { + return stack_; + } + +private: + std::string msg_; + std::string stack_; +}; + +// ok 成功 +static const error_t ok{}; + +/// make_error 创建错误 +static error_t make_error(const std::string& msg) +{ + return std::move(error_t(msg)); +} + +/// make_error 创建错误 +static error_t make_error(const std::string& msg, const std::string& stack) +{ + return std::move(error_t(msg, stack)); +} + +/// << 流式打印 +static std::ostream& operator<<(std::ostream& os, const error_t& err) +{ + os << err.msg() << " " << err.stack(); + return os; +} + +// ---------------------------------------------------------------------------- +// types + +/// argv_t 原始参数列表 +using argv_t = std::vector; + +/// args_t 命令参数列表 +using args_t = std::vector; + +/// pcmd_t 命令指针 +using pcmd_t = std::shared_ptr; + +/// cmds_t 命令列表 +using cmds_t = std::unordered_map; + +/// pflag_t 标记指针 +using pflag_t = std::shared_ptr; + +/// flags_t 标记列表 +using flags_t = std::unordered_map; + +/// func_t 命令函数 +using func_t = std::function; + +// _init_func_t 初始化函数 +using _init_func_t = std::function; + +// ---------------------------------------------------------------------------- +// utils + +// utils 工具类 +class utils +{ +public: + // starts_with C Style 判断是否以指定字符串起始 + // @param sz char* 字符串 + // @param prefix char* 前缀 + // @return bool true = 是,false = 否 + static bool starts_with(const char* sz, const char* prefix) + { + return strncmp(sz, prefix, strlen(prefix)) == 0; + } + + // ends_with C Style 判断字符串是否以指定字符串结尾 + // @param sz char* 字符串 + // @param suffix char* 后缀 + // @return bool true = 是,false = 否 + static bool ends_with(const char* sz, const char* suffix) + { + size_t sz_len = strlen(sz); + size_t suffix_len = strlen(suffix); + if (suffix_len > sz_len) + { + return false; + } + const char* substr = sz + (sz_len - suffix_len); + return strncmp(substr, suffix, suffix_len) == 0; + } + + // trim 去除头尾的空白字符或其他字符 + // @param str std::string 字符串 + // @param trim std::string 空白字符,默认" \t\r\n" + // @return std::string 处理完的字符串 + static std::string trim(const std::string& str, const std::string& trim = " \t\r\n") + { + std::string::size_type first = str.find_first_not_of(trim.c_str()); + std::string::size_type last = str.find_last_not_of(trim.c_str()); + + if (first == std::string::npos || last == std::string::npos) + { + return ""; + } + + return std::move(str.substr(first, last - first + 1)); + } + + // trim_left 去除头部的空白字符或其他字符 + // @param str std::string 字符串 + // @param trim std::string 空白字符,默认" \t\r\n" + // @return std::string 处理完的字符串 + static std::string trim_left(const std::string& str, const std::string& trim = " \t\r\n") + { + std::string::size_type first = str.find_first_not_of(trim.c_str()); + std::string::size_type last = str.size(); + + if (first == std::string::npos) + { + return str; + } + + return std::move(str.substr(first, last - first + 1)); + } + + // split 切分字符串 + // @brief 是否需要支持移动语义或修改函数定义来保证性能? + // @param str std::string 字符串 + // @param d std::string 分隔符,默认" " + // @return std::vector 切分结果 + static void split(std::vector& dst, const std::string& str, const std::string& d = " ") + { + dst.clear(); + + if ("" == str) + { + dst.emplace_back(""); + return; + } + + std::string::size_type pos1, pos2; + size_t len = str.length(); + pos2 = str.find(d); + pos1 = 0; + while (std::string::npos != pos2) + { + dst.emplace_back(str.substr(pos1, pos2 - pos1)); + pos1 = pos2 + d.size(); + pos2 = str.find(d, pos1); + } + + if (pos1 != len) + { + dst.emplace_back(str.substr(pos1)); + } + else + { + dst.emplace_back(""); + } + + return; + } + + // filename 文件名 + static std::string filename(const char* sz) + { + return std::move(filename(std::string(sz))); + } + + // filename 文件名 + static std::string filename(const std::string& path) + { + std::string::size_type pos = path.find_last_of("/\\"); + if (pos == std::string::npos) + { + return path; + } + return std::move(path.substr(pos + 1, path.length() - pos - 1)); + } + + // pading_left 使用 pad 填充字符串 + static std::string pading_left(const std::string& src, size_t max, const char* pad) + { + if (max <= src.length()) + { + return src; + } + if (nullptr == pad || strcmp("", pad) == 0) + { + return std::string(max - src.length(), ' ') + src; + } + return std::string(max - src.length(), pad[0]) + src; + } + + // pading_right 使用 pad 填充字符串 + static std::string pading_right(const std::string& src, size_t max, const char* pad) + { + if (max <= src.length()) + { + return src; + } + if (nullptr == pad || strcmp("", pad) == 0) + { + return src + std::string(max - src.length(), ' '); + } + return src + std::string(max - src.length(), pad[0]); + } + +private: + utils() {} +}; + +// ---------------------------------------------------------------------------- +// flag + +// flag_cast_exception cast exception +class flag_cast_exception : public std::exception +{ +public: + flag_cast_exception() + { + } + + virtual ~flag_cast_exception() + { + } + + explicit flag_cast_exception(const char* msg) + : msg_(msg) + { + } + + explicit flag_cast_exception(std::string& msg) + : msg_(msg) + { + } + + flag_cast_exception(const flag_cast_exception& cpy) + : msg_(cpy.msg_) + { + } + + flag_cast_exception(flag_cast_exception&& mv) + : msg_(std::move(mv.msg_)) + { + } + + flag_cast_exception& operator=(const flag_cast_exception& rhs) + { + msg_ = rhs.msg_; + return *this; + } + + void msg(const std::string& msg) + { + msg_ = msg; + } + + virtual const char* what() + { + return msg_.c_str(); + } + +private: + std::string msg_; +}; + +// flag_t +class flag_t +{ +public: + flag_t() + : type_index_(std::type_index(typeid(void))) + { + } + + flag_t(const flag_t& cpy) + : holder_(cpy.clone()) + , type_index_(cpy.type_index_) + , extend_(cpy.extend_) + , required_(cpy.required_) + , name_(cpy.name_) + , fast_(cpy.fast_) + , desc_(cpy.desc_) + , stack_(cpy.stack_) + , default_(cpy.default_) + , text_(cpy.text_) + , oneof_(cpy.oneof_) + { + } + + flag_t(flag_t&& mv) + : holder_(std::move(mv.holder_)) + , type_index_(std::move(mv.type_index_)) + , extend_(mv.extend_) + , required_(mv.required_) + , name_(std::move(mv.name_)) + , fast_(std::move(mv.fast_)) + , desc_(std::move(mv.desc_)) + , stack_(std::move(mv.stack_)) + , default_(std::move(mv.default_)) + , text_(std::move(mv.text_)) + , oneof_(std::move(mv.oneof_)) + { + } + + flag_t& operator=(const flag_t& rhs) + { + if (holder_ == rhs.holder_) + { + return *this; + } + holder_ = rhs.clone(); + type_index_ = rhs.type_index_; + extend_ = rhs.extend_; + required_ = rhs.required_; + name_ = rhs.name_; + fast_ = rhs.fast_; + desc_ = rhs.desc_; + stack_ = rhs.stack_; + default_ = rhs.default_; + text_ = rhs.text_; + oneof_ = rhs.oneof_; + return *this; + } + + /// is_null 是否为空 + bool is_null() + { + return !bool(holder_); + } + + /// type_name 类型名称 + std::string type_name() + { + if (type_index_ == typeid(std::string)) + { + return "std::string"; + } + return type_index_.name(); + } + + /// castable 是否可以转换为指定类型 + template + bool castable() + { + return type_index_ == std::type_index(typeid(T)); + } + + /// cast 转换,类型不一致时会抛出异常 + template + T& cast() + { + if (!castable()) + { + throw flag_cast_exception( + std::string("can not cast ") + typeid(T).name() + + " to " + type_index_.name() + ); + } + auto ptr = dynamic_cast*>(holder_.get()); + return ptr->value_; + } + + /// is_oneof 是否为合法枚举值 + bool is_oneof(const std::string& text) + { + if (oneof_.size() == 0) + { + return true; // 不是枚举类型,就都合法 + } + auto it = oneof_.begin(); + while (it != oneof_.end()) + { + if (*it == text) + { + break; + } + } + return (it != oneof_.end()); + } + + /// parse 解析 + error_t parse(const std::string& text) + { + if (oneof_.size() != 0) + { + auto it = oneof_.begin(); + for (; it != oneof_.end(); ++it) + { + if (*it == text) + { + break; + } + } + if (it == oneof_.end()) + { + return make_error("not one of flag options.", stack_); + } + } + text_ = text; + if (!holder_->parse(text)) + { + return make_error(std::string("parse failed. type must be ") + type_index_.name(), stack_); + } + return ok; + } + + /// extend 继承属性 + void extend(bool extend) + { + extend_ = extend; + } + + /// extend 继承属性 + bool extend() + { + return extend_; + } + + /// name 名称 + void name(const std::string& name) + { + name_ = name; + } + + /// name 名称 + std::string name() + { + return name_; + } + + /// fast 快捷名称 + void fast(const std::string& fast) + { + fast_ = fast; + } + + /// fast 快捷名称 + std::string fast() + { + return fast_; + } + + /// desc 描述 + void desc(const std::string& desc) + { + desc_ = desc; + } + + /// desc 描述 + std::string desc() + { + return desc_; + } + + // stack 堆栈,可确定命令分支的位置 + void stack(const std::string& stack) + { + stack_ = stack; + } + + // stack 堆栈,可确定命令分支的位置 + const std::string& stack() const + { + return stack_; + } + + /// default_value 默认值的字符串表示 + std::string default_value() + { + return default_; + } + + /// text 输入的字符串 + std::string text() + { + return text_; + } + + /// oneof 枚举值列表 + std::vector& oneof() + { + return oneof_; + } + + /// set 设置 + template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, bool>::value + && !std::is_same::type, std::string>::value + && !std::is_same::type, unsigned char>::value + && !std::is_same::type, char>::value + && !std::is_same::type, signed char>::value, T>::type> + error_t set(const std::string& name, const std::string& fast, T default_v, + const std::string& desc, bool extend = false) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + std::stringstream oss; + oss << "" << default_v; + default_ = oss.str(); + + return ok; + } + + /// set 设置 + template + error_t set(const std::string& name, const std::string& fast, + bool default_v, const std::string& desc, bool extend) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = default_v ? "true" : "false"; + + return ok; + } + + /// set 设置 + template + error_t set(const std::string& name, const std::string& fast, + char default_v, const std::string& desc, bool extend) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + return ok; + } + + /// set 设置 + template + error_t set(const std::string& name, const std::string& fast, + signed char default_v, const std::string& desc, bool extend) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + return ok; + } + + /// set 设置 + template + error_t set(const std::string& name, const std::string& fast, + unsigned char default_v, const std::string& desc, bool extend) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + return ok; + } + + /// set 设置 + template + error_t set(const std::string& name, const std::string& fast, + const std::string& default_v, const std::string& desc, bool extend) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = default_v; + + return ok; + } + + /// set 设置 + template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, double>::value + && !std::is_same::type, float>::value + && !std::is_same::type, bool>::value + && !std::is_same::type, std::string>::value + && !std::is_same::type, unsigned char>::value + && !std::is_same::type, char>::value + && !std::is_same::type, signed char>::value, T>::type> + error_t set(const std::string& name, const std::string& fast, T default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + std::stringstream oss; + oss << default_v; + default_ = oss.str(); + + oneof_.clear(); + for (auto& item : options) + { + std::stringstream oss; + oss << item; + oneof_.push_back(oss.str()); + } + + return ok; + } + + /// set 设置 + template + error_t set(const std::string& name, const std::string& fast, char default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + oneof_.clear(); + for (auto& item : options) + { + oneof_.push_back(std::to_string(item)); + } + + return ok; + } + + /// set 设置 + template + error_t set(const std::string& name, const std::string& fast, signed char default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + oneof_.clear(); + for (auto& item : options) + { + oneof_.push_back(std::to_string(item)); + } + + return ok; + } + + /// set 设置 + template + error_t set(const std::string& name, const std::string& fast, unsigned char default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + oneof_.clear(); + for (auto& item : options) + { + oneof_.push_back(std::to_string(item)); + } + + return ok; + } + + /// set 设置 + template + error_t set(const std::string& name, const std::string& fast, const std::string& default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = default_v; + + oneof_.clear(); + for (auto& item : options) + { + oneof_.push_back(item); + } + + return ok; + } + + /// set 设置 + template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, bool>::value + && !std::is_same::type, std::string>::value + && !std::is_same::type, unsigned char>::value + && !std::is_same::type, char>::value + && !std::is_same::type, signed char>::value, T>::type> + error_t set(T* ptr, const std::string& name, const std::string& fast, T default_v, + const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + std::stringstream oss; + oss << default_v; + default_ = oss.str(); + + return ok; + } + + /// set 设置 + template + error_t set(bool* ptr, const std::string& name, const std::string& fast, + bool default_v, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = default_v ? "true" : "false"; + + return ok; + } + + /// set 设置 + template + error_t set(char* ptr, const std::string& name, const std::string& fast, + char default_v, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + return ok; + } + + /// set 设置 + template + error_t set(signed char* ptr, const std::string& name, const std::string& fast, + signed char default_v, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + return ok; + } + + /// set 设置 + template + error_t set(unsigned char* ptr, const std::string& name, const std::string& fast, + unsigned char default_v, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + return ok; + } + + /// set 设置 + template + error_t set(std::string* ptr, const std::string& name, const std::string& fast, + const std::string& default_v, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = default_v; + + return ok; + } + + /// set 设置 + template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, double>::value + && !std::is_same::type, float>::value + && !std::is_same::type, bool>::value + && !std::is_same::type, std::string>::value + && !std::is_same::type, unsigned char>::value + && !std::is_same::type, char>::value + && !std::is_same::type, signed char>::value, T>::type> + error_t set(T* ptr, const std::string& name, const std::string& fast, T default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + std::stringstream oss; + oss << default_v; + default_ = oss.str(); + + oneof_.clear(); + for (auto& item : options) + { + std::stringstream oss; + oss << item; + oneof_.push_back(oss.str()); + } + + return ok; + } + + /// set 设置 + template + error_t set(char* ptr, const std::string& name, const std::string& fast, char default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + oneof_.clear(); + for (auto& item : options) + { + oneof_.push_back(std::to_string(item)); + } + + return ok; + } + + /// set 设置 + template + error_t set(signed char* ptr, const std::string& name, const std::string& fast, signed char default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + oneof_.clear(); + for (auto& item : options) + { + oneof_.push_back(std::to_string(item)); + } + + return ok; + } + + template + error_t set(unsigned char* ptr, const std::string& name, const std::string& fast, unsigned char default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = std::to_string(default_v); + + oneof_.clear(); + for (auto& item : options) + { + oneof_.push_back(std::to_string(item)); + } + + return ok; + } + + /// set 设置 + template + error_t set(std::string* ptr, const std::string& name, const std::string& fast, const std::string& default_v, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto err = set_value(ptr, name, fast, default_v, desc, extend); + if (err != ok) + { + return err; + } + + default_ = default_v; + + oneof_.clear(); + for (auto& item : options) + { + oneof_.push_back(item); + } + + return ok; + } + +private: + // ctor [禁用] 隐式构造 + template::type, flag_t>::value, T>::type> + flag_t(T&& v) + : holder_(new value_holder::type>(std::forward(v))) + , type_index_(typeid(std::decay::type)) + { + } + + // = [禁用] 隐式赋值 + template::type, flag_t>::value, T>::type> + flag_t& operator=(T& rhs) + { + holder_.reset(new value_holder::type>(std::forward(rhs))); + type_index_ = typeid(std::decay::type); + return *this; + } + + error_t check_name(const std::string& name, const std::string& fast) + { + if (name.empty()) + { + return make_error("flag name must be not null."); + } + if (name.length() <= 1) + { + return make_error("flag name length too short, len > 1."); + } + if (fast.length() > 1) + { + return make_error("flag fast length too long, len <= 1."); + } + return ok; + } + + /// set_value 设置 + /// todo: 标准名称的检查 + template ::value + && !std::is_const::value + && !std::is_reference::value, T>::type> + error_t set_value(const std::string& name, const std::string& fast, + T default_v, const std::string& desc, bool extend = false) + { + auto err = check_name(name, fast); + if (err != ok) + { + return err; + } + + name_ = name; + fast_ = fast; + desc_ = desc; + extend_ = extend; + + holder_.reset(new value_holder::type>(std::forward(default_v))); + type_index_ = typeid(std::decay::type); + + return ok; + } + + /// set_value 设置 + /// todo: 标准名称的检查 + template ::value + && !std::is_const::value + && !std::is_reference::value, T>::type> + error_t set_value(T* ptr, const std::string& name, const std::string& fast, + T default_v, const std::string& desc, bool extend = false) + { + auto err = check_name(name, fast); + if (err != ok) + { + return err; + } + + name_ = name; + fast_ = fast; + desc_ = desc; + extend_ = extend; + + holder_.reset(new value_holder::type>(ptr, std::forward(default_v))); + type_index_ = typeid(std::decay::type); + + return ok; + } + + struct holder; + using holder_ptr = std::unique_ptr; + + struct holder + { + virtual ~holder() {} + virtual holder_ptr clone() const = 0; + virtual bool parse(const std::string& text) = 0; + virtual const std::type_index type_index() = 0; + }; + + template + struct value_holder : public holder + { + using type = T; + + value_holder() + { + memset(&value_, 0, sizeof(value_)); + } + + value_holder(const value_holder& cpy) + : ptr_(cpy.ptr_) + , value_(cpy.value_) + { + } + + value_holder(value_holder&& cpy) + : ptr_(std::move(cpy.ptr_)) + , value_(std::move(cpy.value_)) + { + } + + value_holder& operator=(const value_holder& rhs) + { + ptr_ = rhs.ptr_; + value_ = rhs.value_; + } + + value_holder(const T& v) + : value_(v) + { + } + + value_holder(T* ptr, const T& v) + : ptr_(ptr) + , value_(v) + { + if (nullptr != ptr_) + { + *ptr_ = value_; + } + } + + virtual holder_ptr clone() const + { + return holder_ptr(new value_holder(ptr_, value_)); + } + + virtual bool parse(const std::string& text) + { + std::stringstream iss(text); + iss >> value_; + if (!iss) + { + return false; + } + if (nullptr != ptr_) + { + *ptr_ = value_; + } + return true; + } + + virtual const std::type_index type_index() + { + return std::type_index(typeid(value_)); + } + + T* ptr_{ nullptr }; + T value_; + }; + + holder_ptr clone() const + { + if (holder_) + { + return holder_->clone(); + } + return nullptr; + } + + // 数据 + holder_ptr holder_; + + // 数据类型 + std::type_index type_index_; + + /// extend_ 是否可继承 + bool extend_{ false }; + + // required_ 是否必须 + // todo: no implements + bool required_{ false }; + + /// name_ 名称 + std::string name_; + + /// fast_ 快捷名称 + std::string fast_; + + /// desc_ 描述 + std::string desc_; + + // stack_ 堆栈 + std::string stack_; + + /// default_ 默认值对应的字符串表示 + std::string default_; + + /// text_ 输入的字符串 + std::string text_; + + /// oneof 可选项,枚举值 + std::vector oneof_; +}; + +// ---------------------------------------------------------------------------- +// cmd + +// cmd 命令 +class cmd +{ +public: + cmd() + { + } + + ~cmd() + { + } + + explicit cmd(const char* name) + : name_(name) + { + } + + explicit cmd(const std::string& name) + : name_(name) + { + } + + cmd(const std::string& name, const std::string& brief, const std::string& desc) + : name_(name) + , brief_(brief) + , desc_(desc) + { + } + + cmd(const cmd& cpy) + : name_(cpy.name_) + , brief_(cpy.brief_) + , desc_(cpy.desc_) + , example_(cpy.example_) + , stack_(cpy.stack_) + , subs_(cpy.subs_) + , flags_(cpy.flags_) + , on_func_(cpy.on_func_) + , parent_ptr_(cpy.parent_ptr_) + { + } + + cmd(cmd&& mv) + : name_(std::move(mv.name_)) + , brief_(std::move(mv.brief_)) + , desc_(std::move(mv.desc_)) + , example_(std::move(mv.example_)) + , stack_(std::move(mv.stack_)) + , subs_(std::move(mv.subs_)) + , flags_(std::move(mv.flags_)) + , on_func_(std::move(mv.on_func_)) + , parent_ptr_(std::move(mv.parent_ptr_)) + { + } + + cmd& operator=(const cmd& rhs) + { + name_ = rhs.name_; + brief_ = rhs.brief_; + desc_ = rhs.desc_; + example_ = rhs.example_; + stack_ = rhs.stack_; + subs_ = rhs.subs_; + flags_ = rhs.flags_; + on_func_ = rhs.on_func_; + parent_ptr_ = rhs.parent_ptr_; + return *this; + } + + // name 命令名称 + void name(const std::string& name) + { + name_ = name; + } + + // name 命令名称 + const std::string& name() const + { + return name_; + } + + // brief 简短描述 + void brief(const std::string& brief) + { + brief_ = brief; + } + + // brief 简短描述 + const std::string& brief() const + { + return brief_; + } + + // desc 命令描述 + void desc(const std::string& desc) + { + desc_ = desc; + } + + // desc 命令描述 + const std::string& desc() const + { + return desc_; + } + + // example 示例 + void example(const std::string& example) + { + example_ = example; + } + + // example 示例 + const std::string& example() const + { + return example_; + } + + // stack 堆栈,可确定当前命令在分支中的位置 + // 堆栈并不是在绑定命令时确定的,而是在解析时确定的。 + // 因为pcmd_t可以复用,存在于多条命令分支中。 + void stack(const std::string& stack) + { + stack_ = stack; + } + + // stack 堆栈,可确定当前命令在分支中的位置 + // 堆栈并不是在绑定命令时确定的,而是在解析时确定的。 + // 因为pcmd_t可以复用,存在于多条命令分支中。 + const std::string& stack() const + { + return stack_; + } + + // subs 子命令列表 + cmds_t& subs() + { + return subs_; + } + + /// flags 标记列表 + flags_t& flags() + { + return flags_; + } + + // bind 绑定函数 + error_t bind(func_t func) + { + on_func_ = func; + return ok; + } + + // bind 绑定子命令 + // todo: 标准的命令名称的检查 + error_t bind(cmd* cmd_ptr) + { + if (nullptr == cmd_ptr) + { + return make_error("error: cmd ptr is null"); + } + if (cmd_ptr->name().empty()) + { + return make_error("error: cmd name is null"); + } + auto it = subs_.find(cmd_ptr->name()); + if (it != subs_.end()) + { + return make_error("error: double defined."); + } + subs_[cmd_ptr->name()].reset(cmd_ptr); + return ok; + } + + // bind 绑定子命令 + // todo: 标准的命令名称的检查 + error_t bind(pcmd_t& cmd_ptr) + { + if (nullptr == cmd_ptr) + { + return make_error("error: cmd ptr is null"); + } + if (cmd_ptr->name().empty()) + { + return make_error("error: cmd name is null"); + } + auto it = subs_.find(cmd_ptr->name()); + if (it != subs_.end()) + { + return make_error("error: double defined."); + } + subs_[cmd_ptr->name()] = cmd_ptr; + return ok; + } + + // parent 上级命令 + // 上级命令并不是在绑定命令时确定的,而是在解析时确定的。 + // 因为pcmd_t可以复用,存在于多条命令分支中。 + void parent(pcmd_t& cmd_ptr) + { + parent_ptr_ = cmd_ptr; + } + + // parent 上级命令 + // 上级命令并不是在绑定命令时确定的,而是在解析时确定的。 + // 因为pcmd_t可以复用,存在于多条命令分支中。 + pcmd_t& parent() + { + return parent_ptr_; + } + + // flag 标记 + template ::value + && !std::is_const::value + && !std::is_reference::value, T>::type> + error_t flag(const std::string& name, const std::string& fast, T default_value, + const std::string& desc, bool extend = false) + { + auto it = flags_.find("--" + name); + if (it != flags_.end()) + { + return make_error("error: double defined flag. name=" + name); + } + auto flag_ptr = std::make_shared(); + auto ret = flag_ptr->set(name, fast, default_value, desc, extend); + if (ok != ret) + { + return ret; + } + flags_["--" + name] = flag_ptr; + if (fast == "") + { + return ok; + } + auto fast_it = flags_.find("-" + fast); + if (fast_it != flags_.end()) + { + return make_error("error: double defined flag. fast=" + fast + " name=" + name); + } + flags_["-" + fast] = flag_ptr; + return ok; + } + + // flag + template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, double>::value + && !std::is_same::type, float>::value + && !std::is_same::type, bool>::value, T>::type> + error_t flag(const std::string& name, const std::string& fast, T default_value, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto it = flags_.find("--" + name); + if (it != flags_.end()) + { + return make_error("error: double defined flag. name=" + name); + } + auto flag_ptr = std::make_shared(); + auto ret = flag_ptr->set(name, fast, default_value, options, desc, extend); + if (ok != ret) + { + return ret; + } + flags_["--" + name] = flag_ptr; + if (fast == "") + { + return ok; + } + auto fast_it = flags_.find("-" + fast); + if (fast_it != flags_.end()) + { + return make_error("error: double defined flag. fast=" + fast + " name=" + name); + } + flags_["-" + fast] = flag_ptr; + return ok; + } + + // pflag 标记 + template ::value + && !std::is_const::value + && !std::is_reference::value, T>::type> + error_t pflag(T* ptr, const std::string& name, const std::string& fast, T default_value, + const std::string& desc, bool extend = false) + { + auto it = flags_.find("--" + name); + if (it != flags_.end()) + { + return make_error("error: double defined flag. name=" + name); + } + auto flag_ptr = std::make_shared(); + auto ret = flag_ptr->set(ptr, name, fast, default_value, desc, extend); + if (ok != ret) + { + return ret; + } + flags_["--" + name] = flag_ptr; + if (fast == "") + { + return ok; + } + auto fast_it = flags_.find("-" + fast); + if (fast_it != flags_.end()) + { + return make_error("error: double defined flag. fast=" + fast + " name=" + name); + } + flags_["-" + fast] = flag_ptr; + return ok; + } + + // pflag + template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, double>::value + && !std::is_same::type, float>::value + && !std::is_same::type, bool>::value, T>::type> + error_t pflag(T* ptr, const std::string& name, const std::string& fast, T default_value, + const std::vector& options, const std::string& desc, bool extend = false) + { + auto it = flags_.find("--" + name); + if (it != flags_.end()) + { + return make_error("error: double defined flag. name=" + name); + } + auto flag_ptr = std::make_shared(); + auto ret = flag_ptr->set(ptr, name, fast, default_value, options, desc, extend); + if (ok != ret) + { + return ret; + } + flags_["--" + name] = flag_ptr; + if (fast == "") + { + return ok; + } + auto fast_it = flags_.find("-" + fast); + if (fast_it != flags_.end()) + { + return make_error("error: double defined flag. fast=" + fast + " name=" + name); + } + flags_["-" + fast] = flag_ptr; + return ok; + } + + // on_exec 执行命令 + error_t on_exec(const pcmd_t& cmd, const args_t& args, const flags_t& flags) + { + auto fit = flags.find("--help"); + if (fit != flags.end() && fit->second->cast()) + { + return on_help(flags); + } + if (nullptr == on_func_) + { + return make_error(" warn: nothing to do"); + } + return on_func_(cmd, args, flags); + } + +private: + + // on_help 输出帮助信息 + error_t on_help(const flags_t& flags) + { + if (!name_.empty()) + { + std::cout << std::endl << desc_ << std::endl << std::endl; + } + else + { + std::cout << std::endl << clips::desc() << std::endl << std::endl; + } + + std::cout << "usage:" << std::endl + << " " << stack_; + if (subs_.size() != 0) + { + std::cout << " [cmds...]"; + } + std::cout << " [args...] [--flags...] " << std::endl << std::endl; + + if (subs_.size() != 0) + { + std::cout << "cmds:" << std::endl; + size_t cmd_max_len = 0; + for (auto& item : subs_) + { + if (cmd_max_len < item.second->name().length()) + { + cmd_max_len = item.second->name().length(); + } + } + for (auto& item : subs_) + { + std::cout << " " << utils::pading_right(item.second->name(), cmd_max_len, " ") + << " " << item.second->brief() << std::endl; + } + std::cout << std::endl; + } + + flags_t flags_tmp; + for (auto& item : flags) + { + flags_tmp["--" + item.second->name()] = item.second; + } + std::cout << "flags:" << std::endl; + size_t fast_max_len = 0; + size_t name_max_len = 0; + size_t type_max_len = 0; + size_t default_max_len = 0; + for (auto& item : flags_tmp) + { + if (fast_max_len < item.second->fast().length()) + { + fast_max_len = item.second->fast().length(); + } + if (name_max_len < item.second->name().length()) + { + name_max_len = item.second->name().length(); + } + if (type_max_len < item.second->type_name().length()) + { + type_max_len = item.second->type_name().length(); + } + if (default_max_len < item.second->default_value().length()) + { + default_max_len = item.second->default_value().length(); + } + } + for (auto& item : flags_tmp) + { + std::cout << " " + << utils::pading_right( + (item.second->fast() == "" ? "" : "-" + item.second->fast() + ","), + fast_max_len + 2, " ") + << " " + << utils::pading_right("--" + item.second->name(), name_max_len + 2, " ") + << " " + << utils::pading_right("<" + item.second->type_name() + ">", type_max_len + 2, " ") + << " "; + + if (item.second->extend()) + { + std::cout << ":"; + } + else + { + std::cout << " "; + } + std::cout << utils::pading_right("(" + item.second->default_value() + ")", default_max_len + 2, " ") + << " " + << item.second->desc(); + + auto& oneof = item.second->oneof(); + if (oneof.size() != 0) + { + std::cout << " {"; + for (auto& eit = oneof.begin(); eit != oneof.end(); ++eit) + { + if (eit != oneof.begin()) + { + std::cout << ","; + } + std::cout << *eit; + } + std::cout << "}"; + } + + std::cout << std::endl; + } + std::cout << std::endl; + + if (!example_.empty()) + { + std::cout << "example:" << std::endl + << " " << example_ << std::endl << std::endl; + } + + std::cout << "for more information about a cmd:" << std::endl + << " " << clips::name() << " [cmds...] -h" << std::endl + << " " << clips::name() << " [cmds...] --help" << std::endl; + + return ok; + } + + // name_ 命令名称,单行,合法的命令行标识符号 + std::string name_; + + // brief_ 简短描述 + std::string brief_; + + // desc_ 完整描述 + std::string desc_; + + // example 示例 + std::string example_; + + // stack_ 堆栈 + std::string stack_; + + // subs_ 子命令 + cmds_t subs_; + + // flags_ 局部标记列表, 会被子命令继承的标记列表 + flags_t flags_; + + // func_ 执行函数 + func_t on_func_{ nullptr }; + + // parent_ptr_ 父级命令 + pcmd_t parent_ptr_{ nullptr }; +}; + +/// make_cmd 创建指令 +static pcmd_t make_cmd() +{ + return std::move(std::make_shared()); +} + +/// make_cmd 创建指令 +static pcmd_t make_cmd(const std::string& name) +{ + return std::move(std::make_shared(name)); +} + +/// make_cmd 创建指令 +static pcmd_t make_cmd(const std::string& name, const std::string& brief, const std::string& desc) +{ + return std::move(std::make_shared(name, brief, desc)); +} + +// ---------------------------------------------------------------------------- +// inner + +// inner 内部使用 +class inner +{ +public: + // get 获取实例 + static inner& get() + { + static std::unique_ptr ins_{ nullptr }; + static std::once_flag flagone; + std::call_once(flagone, []() { + ins_.reset(new inner()); + }); + return *ins_; + } + + // name 应用名称 + void name(const char* name) + { + name_ = utils::filename(name); + } + + // name 应用名称 + void name(const std::string& name) + { + name_ = utils::filename(name); + } + + // name 应用名称 + const std::string& name() const + { + return name_; + } + + // desc 应用描述 + void desc(const char* desc) + { + desc_ = desc; + } + + // desc 应用描述 + void desc(const std::string& desc) + { + desc_ = desc; + } + + // desc 应用描述 + const std::string& desc() const + { + return desc_; + } + + // version 版本号 + void version(const char* version) + { + version_ = version; + } + + // version 版本号 + void version(const std::string& version) + { + version_ = version; + } + + // version 版本号 + const std::string& version() const + { + return version_; + } + + // argv 返回所有原始参数 + const argv_t& argv() + { + return argv_; + } + + // str 原始命令行 + std::string str() + { + return str_; + } + + // bind 根函数 + error_t bind(func_t func) + { + root_->bind(func); + return ok; + } + + // bind 绑定子命令 + error_t bind(cmd* ptr) + { + return root_->bind(ptr); + } + + // bind 绑定子指令 + error_t bind(pcmd_t& ptr) + { + return root_->bind(ptr); + } + + // _bind_init_func 绑定初始化函数 + bool _bind_init_func(_init_func_t func, const char* file, int line) + { + inits_.push_back(std::make_tuple(func, file, line)); + return false; // 返回值只是为了初始化一个全局静态变量,没有其他作用 + } + + // flag 标记 + template ::value + && !std::is_const::value + && !std::is_reference::value, T>::type> + error_t flag(const std::string& name, const std::string& fast, T& default_value, + const std::string& desc, bool extend = false) + { + return root_->flag(name, fast, default_value, desc, extend); + } + + // flag 标记 + template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, double>::value + && !std::is_same::type, float>::value + && !std::is_same::type, bool>::value, T>::type> + error_t flag(const std::string& name, const std::string& fast, T& default_value, + const std::vector& options, const std::string& desc, bool extend = false) + { + return root_->flag(name, fast, default_value, options, desc, extend); + } + + // pflag 标记 + template ::value && !std::is_const::value + && !std::is_reference::value, T>::type> + error_t pflag(T* ptr, const std::string& name, const std::string& fast, T& default_value, + const std::string& desc, bool extend = false) + { + return root_->pflag(ptr, name, fast, default_value, desc, extend); + } + + // pflag 标记 + template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, double>::value + && !std::is_same::type, float>::value + && !std::is_same::type, bool>::value, T>::type> + error_t pflag(T * ptr, const std::string& name, const std::string& fast, T& default_value, + const std::vector& options, const std::string& desc, bool extend = false) + { + return root_->pflag(ptr, name, fast, default_value, options, desc, extend); + } + + // cast 转换,不存在或类型不一致时会抛出异常 + template + T& cast(const std::string& name) + { + auto it = flags_.find(name); + if (it == flags_.end()) + { + throw flag_cast_exception("not found."); + } + return flags_[name]->cast(); + } + + // cast 转换 + template + T& cast(const std::string& name, error_t *eptr) + { + auto it = flags_.find(name); + if (it == flags_.end()) + { + *eptr = make_error("not found."); + if (std::is_class::value) + { + return std::move(T()); + } + else + { + T o; + memset(&o, 0, sizeof(T)); + return o; + } + } + + try + { + return flags_[name]->cast(); + } + catch (std::exception & e) + { + *eptr = make_error(e.what()); + if (std::is_class::value) + { + return std::move(T()); + } + else + { + T o; + memset(&o, 0, sizeof(T)); + return o; + } + } + } + + // exec 执行命令 + error_t exec(const std::string& argv) + { + str_ = argv; + + utils::split(argv_, argv, " "); + + auto ret = bind_init(); + if (ret != ok) + { + return ret; + } + + return parse(argv_, args_, flags_); + } + + // exec 执行命令 + error_t exec(int argc, char* argv[]) + { + module_ = argv[0]; + + if (name_.empty()) + { + name_ = utils::filename(argv[0]); + } + + argv_.clear(); + + std::stringstream oss; + for (int i = 1; i < argc; i++) + { + argv_.push_back(argv[i]); + if (i >= 1) + { + oss << " "; + } + oss << argv[i]; + } + str_ = oss.str(); + + auto ret = bind_init(); + if (ret != ok) + { + return ret; + } + + return parse(argv_, args_, flags_); + } + +private: + inner(const inner& cpy) = delete; + inner& operator=(const inner& rhs) = delete; + inner() + : root_(new cmd()) + { + root_->flag("help", "h", false, "help", true); + } + + // bind_init 初始化 + error_t bind_init() + { + for (auto& item : inits_) + { + auto ret = std::get<0>(item)(); + if (ret != ok) + { + ret.stack(std::get<1>(item) + ":" + std::to_string(std::get<2>(item))); + return ret; + } + } + return ok; + } + + // parse 解析 + error_t parse(const argv_t& argv, args_t& args, flags_t& flags) + { + std::stringstream full_stack; + std::stringstream cmd_stack; + full_stack << name_; + cmd_stack << name_; + + args.clear(); + flags.clear(); + + auto cmd = root_; + cmd->stack(cmd_stack.str()); + size_t i = 0; + while (i < argv.size()) + { + full_stack << " " << argv[i]; + + // 命令和参数处理 + if (!utils::starts_with(argv[i].c_str(), "-")) + { + if (args.size() == 0) + { + cmd_stack << " " << argv[i]; + + auto cit = cmd->subs().find(argv[i]); + if (cit != cmd->subs().end()) + { + for (auto& item : cmd->flags()) + { + if (item.second->extend() && flags.count(item.first) == 0) + { + item.second->stack(cmd_stack.str()); + flags[item.first] = item.second; + } + } + cit->second->parent(cmd); + cmd = cit->second; + cmd->stack(cmd_stack.str()); + } + else + { + if (cmd->subs().size() != 0) + { + return make_error("undefined cmd. ", cmd_stack.str()); + } + args.push_back(utils::trim(argv[i], "\'")); + } + } + else + { + args.push_back(utils::trim(argv[i], "\'")); + } + i++; + continue; + } + + // flag选项 + + auto flag_name = argv[i]; + std::string flag_value(""); + + auto flag_equal_pos = flag_name.find("="); + if (flag_equal_pos != std::string::npos) + { + flag_value = flag_name.substr(flag_equal_pos + 1, flag_name.length() - flag_equal_pos - 1); + flag_name = flag_name.substr(0, flag_equal_pos); + } + i++; + + auto fit = cmd->flags().find(flag_name); + if (fit == cmd->flags().end()) + { + auto pit = flags.find(flag_name); + if (pit == flags.end()) + { + return make_error("undefined flag.", full_stack.str()); + } + else if (!pit->second->extend()) + { + return make_error("not a extend flag.", full_stack.str()); + } + else + { + fit = pit; + } + } + + // flag转换 + + auto pflag = fit->second; + if (pflag->castable()) + { + if (flag_equal_pos == std::string::npos) + { + if (i < argv.size()) + { + flag_value = utils::trim(argv[i], "\'"); + if (_stricmp(flag_value.c_str(), "true") == 0 || _stricmp(flag_value.c_str(), "1") == 0) + { + pflag->parse("1"); + full_stack << " " << argv[i]; + i++; + } + else if (_stricmp(flag_value.c_str(), "false") == 0 || _stricmp(flag_value.c_str(), "0") == 0) + { + pflag->parse("0"); + full_stack << " " << argv[i]; + i++; + } + else + { + pflag->parse("1"); + } + } + else + { + pflag->parse("1"); + } + } + else + { + if (_stricmp(flag_value.c_str(), "true") == 0 || _stricmp(flag_value.c_str(), "1") == 0) + { + pflag->parse("1"); + } + else if (_stricmp(flag_value.c_str(), "false") == 0 || _stricmp(flag_value.c_str(), "0") == 0) + { + pflag->parse("0"); + } + else + { + return make_error("bool value error.", full_stack.str()); + } + } + } + else + { + if (flag_equal_pos == std::string::npos) + { + if (i < argv.size()) + { + flag_value = utils::trim(argv[i], "\'"); + if (utils::starts_with(flag_value.c_str(), "-")) + { + return make_error("no value of flag.", full_stack.str()); + } + full_stack << " " << argv[i]; + auto ret = pflag->parse(flag_value); + if (ret != ok) + { + return make_error(ret.msg(), full_stack.str()); + } + i++; + } + else + { + return make_error("no value of flag ", full_stack.str()); + } + } + else + { + auto ret = pflag->parse(flag_value); + if (ret != ok) + { + return make_error(ret.msg(), full_stack.str()); + } + } + } + } + + for (auto& item : cmd->flags()) + { + if (flags.count(item.first) == 0) + { + item.second->stack(cmd->stack()); + flags[item.first] = item.second; + } + } + + return cmd->on_exec(cmd, args, flags); + } + + // module_ 模块名称 + std::string module_; + + // name_ 应用名称 + std::string name_; + + // desc 应用描述 + std::string desc_; + + // version_ 版本 + std::string version_; + + // str_ 原始命令 + std::string str_; + + // argv_ 原始命令 + argv_t argv_; + + // args_ 命令参数 + args_t args_; + + // flags_ 选项列表 + flags_t flags_; + + // root_ 根命令 + pcmd_t root_; + + // inits_ 初始化函数 + std::vector> inits_; +}; + +// ---------------------------------------------------------------------------- +// global + +/// name 应用名称 +/// 如果指定了应用名称,则在exec解析时不会被重写; +/// 如果不指定应用名称,则在exec解析时会使用argv[0]代替; +static void name(const std::string& name) +{ + inner::get().name(); +} + +/// name 应用名称 +/// 如果指定了应用名称,则在exec解析时不会被重写; +/// 如果不指定应用名称,则在exec解析时会使用argv[0]代替; +static const std::string& name() +{ + return inner::get().name(); +} + +/// desc 应用描述 +static void desc(const std::string& desc) +{ + inner::get().desc(desc); +} + +/// desc 应用描述 +static const std::string& desc() +{ + return inner::get().desc(); +} + +/// argv 返回所有原始参数 +static const argv_t& argv() +{ + return inner::get().argv(); +} + +// str 原始命令行 +static std::string str() +{ + return inner::get().str(); +} + +/// bind 绑定根函数 +static error_t bind(func_t func) +{ + return inner::get().bind(func); +} + +/// bind 绑定指令 +static error_t bind(cmd* ptr) +{ + return inner::get().bind(ptr); +} + +/// bind 绑定指令 +static error_t bind(pcmd_t& ptr) +{ + return inner::get().bind(ptr); +} + +/// flag 标记 +template ::value + && !std::is_const::value + && !std::is_reference::value, T>::type> +static error_t flag(const std::string& name, const std::string& fast, + T default_value, const std::string& desc, bool extend = false) +{ + return inner::get().flag(name, fast, default_value, desc, extend); +} + +/// flag 标记 +template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, double>::value + && !std::is_same::type, float>::value + && !std::is_same::type, bool>::value, T>::type> +static error_t flag(const std::string& name, const std::string& fast, T default_value, + const std::vector& options, const std::string& desc, bool extend = false) +{ + return inner::get().flag(name, fast, default_value, options, desc, extend); +} + +/// pflag 标记 +template ::value + && !std::is_const::value + && !std::is_reference::value, T>::type> +static error_t pflag(T* ptr, const std::string& name, const std::string& fast, + T default_value, const std::string& desc, bool extend = false) +{ + return inner::get().pflag(ptr, name, fast, default_value, desc, extend); +} + +/// pflag 标记 +template ::value + && !std::is_const::value + && !std::is_reference::value + && !std::is_same::type, double>::value + && !std::is_same::type, float>::value + && !std::is_same::type, bool>::value, T>::type> +static error_t pflag(T* ptr, const std::string& name, const std::string& fast, T default_value, + const std::vector& options, const std::string& desc, bool extend = false) +{ + return inner::get().pflag(ptr, name, fast, default_value, options, desc, extend); +} + +// cast 转换,不存在或类型不一致时会抛出异常 +template +static T& cast(const std::string& name) +{ + return inner::get().cast(name); +} + +// cast 转换 +template +static T& cast(const std::string& name, error_t* eptr) +{ + return inner::get().cast(name, eptr); +} + +/// exec 执行命令 +static error_t exec(const std::string& argv) +{ + return inner::get().exec(argv); +} + +/// exec 执行命令 +static error_t exec(int argc, char* argv[]) +{ + return inner::get().exec(argc, argv); +} + +// _bind_init_func 绑定初始化函数 +// 内部使用 +static bool _bind_init_func(_init_func_t func, const char* file, int line) +{ + return inner::get()._bind_init_func(func, file, line); +} + +// ---------------------------------------------------------------------------- +} // namespace clips diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..47fb6a8 --- /dev/null +++ b/readme.md @@ -0,0 +1,659 @@ +# Clips + +[English Docs](readme.md) | [中文文档](docs/readme-zh.md) + +# Overview + +`clips` is a command line parser for modern C++, easy inclusion in project. + +# Features + +- provides a few and simple interfaces; +- esay to create subcommand-based and subcommand-nested CLIs application; +- command handlers can be defined and binded in separate files, using `CLIPS_INIT()`; +- header only; +- no external dependencies; +- directly to bind variables (`&varname`), or using `'cast'` to get result of flag; +- support options of flag; +- support simple custom type; +- clear help messages; +- friendly error messages; +- work on Windows, Linux, and macOS. + +# Command Line + +Examples: + +```yaml +$ ./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 +``` + +# Help + +Run like this: + +```yaml +# help +$ ./appname -h +$ ./appname --help + +# nested command help +$ ./appname [cmds...] -h +$ ./appname [cmds...] --help + +# example +$ ./appname a b c -h +``` + +Help message, example: + +```yaml +$ ./appname -h + +desc + +usage: + appname [cmds...] [args...] [--flags...] + +cmds: + sub sub brief + +flags: + -h, --help :(false) help + --eee (1) eee desc + -f, --fff (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 column definition: + +```yaml +flags: + -n, --name :extend(default) desc {enums} +``` + +Note: in `:(false)`, `:` represents extend, may be extend from top command. + +# Project + +Directory structure example: + +```yaml ++ + + src + - main.cpp + - cmd_first.cpp + - cmd_second.cpp + + +``` + +Include path in your project: + +```cpp +clips/include +``` + +And include header in your code: + +```cpp +#include "clips/clips.hpp" +``` + +# Using + +## main + +```cpp +#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; +} +``` + +## root + +In fact, the root function is an internal preset top-level root command that is not named. + +Define your root function: + +```cpp +uint32_t g_ddd = 0; +clips::error_t root(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) +{ + std::cout << "exec root handler. ddd=" << ddd << std::endl; + return clips::ok; +} +``` + +Add flag, and bind handler: + +```cpp + +auto err = clips::flag("aaa", "a", 2, "aaa desc"); +if (err != clips::ok) +{ + return err; +} + +err = clips::pflag(&g_ddd, "ddd", "", 5, "ddd desc", true); +if (err != clips::ok) +{ + return err; +} + +err = clips::bind(root); +if (err != clips::ok) +{ + return err; +} +``` + +Run like this: + +```yaml +$ ./appname [args...] [--flags...] +``` + +Note: the root handler is not required and can be null (default). + +## subcommand + +It's really just a nested command, a nested command of the internal root command, distinguished from the following because it provides a top-level interface. + +Defined: + +```cpp +// create +auto sub = clips::make_cmd("sub", "sub brief", "sub desc"); + +// flags +int fff = 0; +sub->pflag(&fff, "fff", "f", 2, { 0, 1, 2 }, "fff desc"); +sub->flag("eee", "", 1u, "eee desc", true); + +// example +sub->example("sub --eee 0 -f 0"); + +// bind handler +sub->bind([](const clips::pcmd_t& cmd, + const clips::args_t& args, + const clips::flags_t& flags) -> clips::error_t + { + std::cout << "exec sub" << std::endl; + return clips::ok; + }); + +// bind cmd +auto ret = clips::bind(sub); +if (ret != clips::ok) +{ + return ret; +} +``` + +Run like this: + +```yaml +$ ./appname sub [args...] [--flags...] +``` + +## nested subcommand + +A nested command is one that contains commands. + +Defined: + +```cpp +auto nested = clips::make_cmd("nested", "nested brief", "nested desc"); +// ... +auto ret = sub->bind(nested); +if (ret != clips::ok) +{ + return ret; +} +``` + +Run like this: + +```yaml +$ ./appname sub nested [args...] [--flags...] +``` + +## defined and binded command in separate file + +You don't need to implement all the commands in main.cpp, you can implement them separately. This is both clear and testable. + +```cpp +auto your_func(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) -> clips::error_t +{ + // ... + return clips::ok; +} + +CLIPS_INIT() +{ + // this function will be calling before parsing command line. + + auto pcmd = clips::make_cmd("name"); + // ... + pcmd->bind(your_func); // also using lambda + return clips::bind(pcmd); +} +``` + +'CLIPS_INIT()' is executed at the beginning of 'clips::exec()'. When 'CLIPS_INIT()' is defined in multiple files, the order of execution is compiler-dependent, usually in lexicographic order of the file name. The binding of the root function is not affected by the order and can be defined anywhere. Because the other commands are named, they are affected, and the postbound command with the same name causes the function to return an error message that is repeatedly defined. + +# Application Info + +## Name + +If the application name is specified, it will not be overridden when parsing in clips::exec(), otherwise it will be replaced by argv[0] when parsing in clips::exec(). + +```cpp +clips::name("name"); +auto name = clips::name(); +``` + +## Description + +```cpp +clips::desc("desc"); +auto desc = clips::desc(); +``` + +## Origin argv + +It's a std::vector from argv[]. + +```cpp +auto argv = clips::argv(); +``` + +# Flag + +You can only add flag by command interfaces. + +## Add + +```cpp +// need cast() +auto err = pcmd->flag("name", "n", 0, "desc"); +auto value = flags["--name"]->cast(); +// or +auto value = clips::cast("--name"); + +// bind variables +int varname = 0; // also using cast() +auto err = pcmd->pflag(&varname, "name", "n", 0, "desc"); +``` + +## Name + +When the name of `flag` is `"name"`, the corresponding command line is `--name`. The name cannot be empty. + +## Fast Name + +When the fast name of `flag` is `"n"`, the corresponding command line is `-n`. The fast name can be empty or one character. + +## Type Name + +```cpp +auto type_name = flags["--name"]->type_name(); +``` + +## Extend + +When `flag` is `extend`, all nested commands following its branches are accessible. + +When the last parameter of `flag()` or `pflag()` function is `true`, it means extend. The default is not extend (when the last parameter are not given), as follows: + +```cpp +auto err = pcmd->flag("name", "n", 0, "desc", true); +``` + +The search rule is to find the `flag` in the current command first, When it cannot be found, look down from the root command and exclude non-extend `flag`, until it is found before the current command. + +## Options + +When options (enumeration values) is provided, the input parameter is checked to see if it is one of the options (enumeration values). If not, the call `clips::exec()` returns an error. + +```cpp +auto err = pcmd->flag("name", "n", 0, {0, 1, 2}, "desc"); +auto err = pcmd->pflag(&varname, "name", "n", 0, {0, 1, 2}, "desc"); +``` + +## Cast + +Whether it can be converted to the target type: + +```cpp +if (flags["--name"]->castable()) +{ + // can be converted +} +``` + +Converted: + +```cpp +auto value = flags["--name"]->cast(); +// or +auto value = clips::cast("--name"); +``` + +An exception is thrown when the target type cannot be converted: + +```cpp +try +{ + auto value = flags["--name"]->cast(); + // or + auto value = clips::cast("--abc"); +} +catch (std::exception& e) +{ + std::cout << e.what() << std::endl; +} +``` + +Using error_t: + +```cpp +error_t err; +auto value = clips::cast("--abc", &err); +if (err != clips::ok) +{ + return err; +} +``` + +## Stack + +You may need stack information (also available from the current command) to help locate an error. + +```cpp +auto stack = flags["--name"]->stack(); +``` + +## Default Value + +```cpp +auto stack = flags["--name"]->default_value(); +``` + +## Input Text + +This is available only after the parsing logic in 'clips::exec()' is executed. + +```cpp +auto text = flags["--name"]->text(); +``` + +## Input Form + +```yaml +--name # bool +-n # bool +--name true # bool +-n false # bool +--name 1 # bool +-n 0 # bool +--name value # Space separates the name and value +-n value # Space separates the name and value +--name=value # The equals sign separates the name and the value +-n=value # The equals sign separates the name and the value +-n '-1' # Single quotes prevent escape from command line parsing rules +``` + +## custom type + +Need to implement stream operator `<<` and `>>` overloading. + +Defined: + +```cpp +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) + : 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: + +```cpp +err = pcmd->flag("custom", "", {}, "custom_t type"); +``` + +Cast: + +```cpp +std::cout << clips::cast("--custom") << std::endl; +``` + +Run like this: + +```yaml +$ ./appname sub --custom=1,msg +``` + +# Command + +## Create + +Three suggested ways to create: + +```cpp +// without arguments +auto pcmd = clips::make_cmd(); + +// given name +auto pcmd = clips::make_cmd("name"); + +// given base info +auto pcmd = clips::make_cmd("name", "brief", "desc"); +``` + +## Base Info + +```cpp +pcmd->name("name"); +auto name = pcmd->name(); + +pcmd->brief("brief"); +auto brief = pcmd->brief(); + +pcmd->desc("desc"); +auto desc = pcmd->desc(); +``` + +## Example + +The example is just a piece of text, and you usually need to write how to execute this command. + +```cpp +pcmd->example("example"); +auto example = pcmd->example(); +``` + +## Add Flag + +```cpp +// need cast() +auto err = pcmd->flag("name", "n", 0, "desc"); +auto value = flags["--name"]->cast(); +auto value = clips::cast("--name"); + +// bind variables +int varname = 0; // also using cast() +auto err = pcmd->pflag(&varname, "name", "n", 0, "desc"); +``` + +Notice the life cycle of the variable. + +## Bind Handler + +```cpp +auto your_func(const clips::pcmd_t& cmd, const clips::args_t& args, const clips::flags_t& flags) -> clips::error_t +{ + // ... + return clips::ok; +} +auto err = pcmd->bind(your_func); // also using lambda +``` + +Note: the command handler is not required and can be null (default). + +## Nested Command + +```cpp +auto psub = clips::make_cmd("sub"); +// ... +auto err = pcmd->bind(psub); +``` + +## Arguments + +Defined: + +```yaml +$ ./appname sub nested [args...] [--flags...] +``` + +If the command contains nested commands, the input non-flag may be recognized as an undefined command. Only no-parameter execution is allowed. As follows: + +```yaml +$ ./appname sub adfa # error occurs, because the sub contains the nested, adfa is considered undefined command +$ ./appname sub # ok, args size is 0 +``` + +If the command does not contain nested commands, any input that is not 'flag' is recognized as a parameter. As follows: + +```yaml +$ ./appname sub nested adfa # ok, args[0] is adfa +$ ./appname sub nested # ok, args size is 0 +``` + +Flags are not subject to this rule. + +# Error Message + +## Create + +```cpp +auto err = clips::make_error(); +auto err = clips::make_error("msg"); +auto err = clips::make_error("msg", "stack"); +``` + +## Interfaces + +Msg: + +```cpp +err.msg("parse failed."); +auto msg = err.msg(); +``` + +Stack: + +```cpp +err.stack("appname sub"); +auto stack = err.stack(); +``` + +Prints: + +```cpp +std::cout << err << std::endl; +``` + +## Comparison + +The comparison only focuses on whether the error message is the same, not the stack message. + +- `err == clips::ok` successful; +- `err != clips::ok` failed; + +# Nonsupport + +- non-standard flag, like -long or --h; + +# TODO + +- help + - custom text; + - i18n --lang { default, en-us, zh-cn}; +- type safety + - type checking; +- compliant POSIX flags + - combination flag, like -abc; +- intelligent suggestions + +# TEST + +Test clips with `Catch2`, recommend going to the following url to learn about this excellent unit testing framework: + +[https://github.com/catchorg/Catch2](https://github.com/catchorg/Catch2) \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..cfb4c64 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required (VERSION 2.6) + +set (MODULE_NAME clips) +project (${MODULE_NAME}) + +# message +message ("${MODULE_NAME} for ${CMAKE_BUILD_TYPE}") + +# def +if (NOT WIN32) +set (CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -g3 -ggdb") +set (CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3") +set (CMAKE_CXX_FLAGS "-fPIC -m64 -Wall -std=c++11 ${CMAKE_CXX_FLAGS}") +endif () +#add_definitions(-DEXPOERTS) + +#------------------------------------------------------------------------------ +# module + +# include +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src) + +# src +file(GLOB_RECURSE HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/../include/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../include/*.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/../src/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../src/*.hpp" +) +source_group("includes" FILES ${HEADERS}) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/../src SRCS) + +# target +add_library(${MODULE_NAME} STATIC ${SRCS} ${HEADERS}) + +#------------------------------------------------------------------------------ diff --git a/src/clips.cpp b/src/clips.cpp new file mode 100644 index 0000000..82582d9 --- /dev/null +++ b/src/clips.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2020, garry.hou , All rights reserved. +// +// Clips is licensed under the Mulan PSL v1. +// You can use this software according to the termsand conditions of the Mulan PSL v1. +// You may obtain a copy of Mulan PSL v1 at : +// http://license.coscl.org.cn/MulanPSL +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON - INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v1 for more details. +// ---------------------------------------------------------------------------- +// + +#include "clips/clips.hpp" + +namespace clips { +// ---------------------------------------------------------------------------- + + + + + +// ---------------------------------------------------------------------------- +} // namespace clips \ No newline at end of file diff --git a/test.bat b/test.bat new file mode 100644 index 0000000..3178d1b --- /dev/null +++ b/test.bat @@ -0,0 +1,19 @@ +@echo off + +rem build dir +if not exist "build" ( + md "build" +) +if not exist "build" ( + md "build" +) + +rem build +cd "build" +cmake -G "Visual Studio 15 Win64" -DCMAKE_BUILD_TYPE=Release -DTEST=1 .. +cmake --build . + +rem home +cd .. + +pause diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..3175edc --- /dev/null +++ b/test.sh @@ -0,0 +1,16 @@ + +if [ ! -d "build" ]; then + mkdir build +fi + +if [ -d "build" ]; then + rm -rf build +fi +mkdir build + +cd build +cmake -DCMAKE_BUILD_TYPE=Release -DTEST=1 .. +make -j8 +#cpack --config CPackConfig.cmake + +cd .. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..a7c6c53 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required (VERSION 2.6) + +set (MODULE_NAME clips_test) +project (${MODULE_NAME}) + +#------------------------------------------------------------------------------ +# testing + +# message +message ("${MODULE_NAME} for ${CMAKE_BUILD_TYPE}") + +# include +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include/) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../test) + +# src +file(GLOB_RECURSE TEST_HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/../include/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../include/*.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/../test/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../test/*.hpp" +) +source_group("includes" FILES ${TEST_HEADERS}) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/../test TEST_SRCS) + +# target +add_executable(${MODULE_NAME} ${TEST_SRCS} ${TEST_HEADERS}) + +#install +set (EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin) + +#------------------------------------------------------------------------------ diff --git a/test/catch.hpp b/test/catch.hpp new file mode 100644 index 0000000..303f664 --- /dev/null +++ b/test/catch.hpp @@ -0,0 +1,16865 @@ +/* + * Catch v2.9.1 + * Generated: 2019-06-17 11:59:24.363643 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2019 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 9 +#define CATCH_VERSION_PATCH 1 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#ifdef __clang__ + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if string_view is available and usable +// The check is split apart to work around v140 (VS2015) preprocessor issue... +#if defined(__has_include) +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if optional is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +//////////////////////////////////////////////////////////////////////////////// +// Check if variant is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 +# include +# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# define CATCH_CONFIG_NO_CPP17_VARIANT +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__clang__) && (__clang_major__ < 8) +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept; + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. c_str() must return a null terminated + /// string, however, and so the StringRef will internally take ownership + /// (taking a copy), if necessary. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + public: + using size_type = std::size_t; + + private: + friend struct StringRefTestAccess; + + char const* m_start; + size_type m_size; + + char* m_data = nullptr; + + void takeOwnership(); + + static constexpr char const* const s_empty = ""; + + public: // construction/ assignment + StringRef() noexcept + : StringRef( s_empty, 0 ) + {} + + StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef( char const* rawChars ) noexcept; + + StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + ~StringRef() noexcept { + delete[] m_data; + } + + auto operator = ( StringRef const &other ) noexcept -> StringRef& { + delete[] m_data; + m_data = nullptr; + m_start = other.m_start; + m_size = other.m_size; + return *this; + } + + operator std::string() const; + + void swap( StringRef& other ) noexcept; + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; + + auto operator[] ( size_type index ) const noexcept -> char; + + public: // named queries + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + + auto numberOfCharacters() const noexcept -> size_type; + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; + + // Returns the current start pointer. + // Note that the pointer can change when if the StringRef is a substring + auto currentData() const noexcept -> char const*; + + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + }; + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; + auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } + +} // namespace Catch + +inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_type_traits.hpp + + +#include + +namespace Catch{ + +#ifdef CATCH_CPP17_OR_GREATER + template + inline constexpr auto is_unique = std::true_type{}; + + template + inline constexpr auto is_unique = std::bool_constant< + (!std::is_same_v && ...) && is_unique + >{}; +#else + +template +struct is_unique : std::true_type{}; + +template +struct is_unique : std::integral_constant +::value + && is_unique::value + && is_unique::value +>{}; + +#endif +} + +// end catch_type_traits.hpp +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + \ + template class L1, typename...E1, template class L2, typename...E2> \ + constexpr auto append(L1, L2) noexcept -> L1 { return {}; }\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + constexpr auto append(L1, L2, Rest...) noexcept -> decltype(append(L1{}, Rest{}...)) { return {}; }\ + template< template class L1, typename...E1, typename...Rest>\ + constexpr auto append(L1, TypeList, Rest...) noexcept -> L1 { return {}; }\ + \ + template< template class Container, template class List, typename...elems>\ + constexpr auto rewrap(List) noexcept -> TypeList> { return {}; }\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + constexpr auto rewrap(List,Elements...) noexcept -> decltype(append(TypeList>{}, rewrap(Elements{}...))) { return {}; }\ + \ + template