clips
is a command line parser for modern C++, easy inclusion in project.
- 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<typename>'
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.
Examples:
$ ./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
Run like this:
# help
$ ./appname -h
$ ./appname --help
# nested command help
$ ./appname [cmds...] -h
$ ./appname [cmds...] --help
# example
$ ./appname a b c -h
Help message, example:
$ ./appname -h
desc
usage:
appname [cmds...] [args...] [--flags...]
cmds:
sub sub brief
flags:
-h, --help <bool> :(false) help
--eee <unsigned int> (1) eee desc
-f, --fff <int> (2) fff desc {0,1,2}
example:
appname sub arg0 arg1 --name=value
for more information about a cmd:
appname [cmds...] -h
appname [cmds...] --help
Flags column definition:
flags:
-n, --name <type> :extend(default) desc {enums}
Note: in :(false)
, :
represents extend, may be extend from top command.
Directory structure example:
+ <project_path>
+ src
- main.cpp
- cmds_first.cpp
- cmds_second.cpp
+ <others...>
Include path in your project:
clips/include
And include header in your code:
#include "clips/clips.hpp"
#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;
}
In fact, the root function is an internal preset top-level root command that is not named.
Define your root function:
uint32_t g_ddd = 0;
clips::error_t root(const clips::pcmd_t& pcmd, const clips::args_t& args)
{
std::cout << "exec root handler. ddd=" << ddd << std::endl;
return clips::ok;
}
Add flag, and bind handler:
auto err = clips::flag<uint32_t>("aaa", "a", 2, "aaa desc");
if (err != clips::ok)
{
return err;
}
err = clips::pflag<uint32_t>(&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:
$ ./appname [args...] [--flags...]
Note: the root handler is not required and can be null (default).
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:
// create
auto sub = clips::make_cmd("sub");
sub->brief("sub brief");
sub->desc("sub desc");
// flags
int fff = 0;
sub->pflag<int>(&fff, "fff", "f", 2, { 0, 1, 2 }, "fff desc");
sub->flag<uint32_t>("eee", "", 1u, "eee desc", true);
// example
sub->example("sub --eee 0 -f 0");
// bind handler
sub->bind([](const clips::pcmd_t& pcmd,
const clips::args_t& args) -> 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:
$ ./appname sub [args...] [--flags...]
A nested command is one that contains commands.
Defined:
auto nested = clips::make_cmd("nested");
nested->brief("nested 简洁描述");
nested->desc("nested 详细描述");
// ...
auto ret = sub->bind(nested);
if (ret != clips::ok)
{
return ret;
}
Run like this:
$ ./appname sub nested [args...] [--flags...]
You don't need to implement all the commands in main.cpp, you can implement them separately. This is both clear and testable.
auto your_func(const clips::pcmd_t& pcmd, const clips::args_t& args) -> 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.
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().
clips::name("name");
auto name = clips::name();
clips::desc("desc");
auto desc = clips::desc();
It's a std::vectorstd::string from argv[].
auto& argv = clips::argv();
You can only add flag by command interfaces.
// need cast()
auto err = pcmd->flag<int>("name", "n", 0, "desc");
auto value = flags["--name"]->cast<int>();
// or
auto value = pcmd->cast<int>("--name");
// bind variables
int varname = 0; // also using cast()
auto err = pcmd->pflag<int>(&varname, "name", "n", 0, "desc");
When the name of flag
is "name"
, the corresponding command line is --name
. The name cannot be empty.
When the fast name of flag
is "n"
, the corresponding command line is -n
. The fast name can be empty or one character.
auto type_name = flags["--name"]->type_name();
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:
auto err = pcmd->flag<int>("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.
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.
auto err = pcmd->flag<int>("name", "n", 0, {0, 1, 2}, "desc");
auto err = pcmd->pflag<int>(&varname, "name", "n", 0, {0, 1, 2}, "desc");
Whether it can be converted to the target type:
if (flags["--name"]->castable<int>())
{
// can be converted
}
Converted:
auto value = flags["--name"]->cast<std::string>();
// or
auto value = pcmd->cast<std::string>("--name");
An exception is thrown when the target type cannot be converted:
try
{
auto value = flags["--name"]->cast<std::string>();
// or
auto value = pcmd->cast<std::string>("--abc");
}
catch (std::exception& e)
{
std::cout << e.what() << std::endl;
}
Using error_t:
error_t err;
auto value = pcmd->cast<std::string>("--abc", &err);
if (err != clips::ok)
{
return err;
}
You may need stack information (also available from the current command) to help locate an error.
auto stack = flags["--name"]->stack();
auto stack = flags["--name"]->default_value();
This is available only after the parsing logic in 'clips::exec()' is executed.
auto text = flags["--name"]->text();
--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
Need to implement stream operator <<
and >>
overloading.
Defined:
class custom_t
{
public:
custom_t()
{
}
~custom_t()
{
}
custom_t(const custom_t& cpy)
: num_(cpy.num_)
, msg_(cpy.msg_)
{
}
custom_t(custom_t&& mv) noexcept
: num_(mv.num_)
, msg_(std::move(mv.msg_))
{
}
custom_t& operator=(const custom_t& rhs)
{
num_ = rhs.num_;
msg_ = rhs.msg_;
return *this;
}
int num_{ 0 };
std::string msg_;
};
std::ostream& operator<<(std::ostream& os, const custom_t& obj)
{
os << "{" << obj.num_ << "," << obj.msg_ << "}";
return os;
}
std::istream& operator>>(std::istream& is, custom_t& obj)
{
unsigned char c;
std::string tmp;
is >> obj.num_ >> c >> obj.msg_;
return is;
}
Flag:
err = pcmd->flag<custom_t>("custom", "", {}, "custom_t type");
Cast:
std::cout << pcmd->cast<custom_t>("--custom") << std::endl;
Run like this:
$ ./appname sub --custom=1,msg
Three suggested ways to create:
// without arguments
auto pcmd = clips::make_cmd();
// given name
auto pcmd = clips::make_cmd("name");
pcmd->name("name");
auto name = pcmd->name();
pcmd->brief("brief");
auto brief = pcmd->brief();
pcmd->desc("desc");
auto desc = pcmd->desc();
The example is just a piece of text, and you usually need to write how to execute this command.
pcmd->example("example");
auto example = pcmd->example();
// need cast()
auto err = pcmd->flag<int>("name", "n", 0, "desc");
auto value = flags["--name"]->cast<int>();
auto value = pcmd->cast<int>("--name");
// bind variables
int varname = 0; // also using cast()
auto err = pcmd->pflag<int>(&varname, "name", "n", 0, "desc");
Notice the life cycle of the variable.
auto your_func(const clips::pcmd_t& cmd, const clips::args_t& args) -> clips::error_t
{
// ...
return clips::ok;
}
auto err = pcmd->bind(your_func); // also using lambda
Note: the command handler is not required and can be null (default).
auto psub = clips::make_cmd("sub");
// ...
auto err = pcmd->bind(psub);
Defined:
$ ./appname sub nested [args...] [--flags...]
As follows:
$ ./appname sub adfa # ok, adfa is args[0]
$ ./appname sub # ok, args size is 0
$ ./appname sub nested adfa # ok, args[0] is adfa
$ ./appname sub nested # ok, args size is 0
auto err = clips::make_error();
auto err = clips::make_error("msg");
auto err = clips::make_error("msg", "stack");
Msg:
err.msg("parse failed.");
auto msg = err.msg();
Stack:
err.stack("appname sub");
auto stack = err.stack();
Prints:
std::cout << err << std::endl;
The comparison only focuses on whether the error message is the same, not the stack message.
err == clips::ok
successful;err != clips::ok
failed;
- non-standard flag, like -long or --h;
- help
- custom text;
- i18n --lang { default, en-us, zh-cn};
- type safety
- type checking;
- compliant POSIX flags
- combination flag, like -abc;
- intelligent suggestions
Test clips with Catch2
, recommend going to the following url to learn about this excellent unit testing framework: