cliargs (Command Line Interface Arguments Parser) This is a C++ program command line argument parser. Supports standard GNU command line argument syntax style; this library contains only one header file (header-only).
Functions similar to (tribute to): getopts (GNU C / Linux Shell), cxxpots (C++), argparse (Python)
Command line arguments can be specified as follows:
--long1
--long2=value
--long2 value
-a
-ab
-abc value
Among them, long2
and c
require parameter values, while long1
, a
and b
do not.
#include "cliargs.hpp"
int main(int argc, char *argv[]) {
// Create a 'cliargs::Parser' instance
cliargs::Parser parser("MyProgram", "One line description of MyProgram");
// Define arguments
parser.set_width(120).add_args()
('h', "help", "Print this message and exit") // a bool argument
('i', "int", "An interger", cliargs::value<int>()->default_value(-1))
('s', "string", "A string", cliargs::value<std::string>()->default_value("/dev/mem"))
('v', "vector", "An int vector", cliargs::value<std::vector<float>>())
;
//Parse
auto result = parser.parse(argc, argv);
if (parser.error() || result["help"].as<bool>()) {
parser.print_help();
return parser.error() ? -1 : 0;
}
//Use result
std::cout << " int: " << cliargs::to_string(result["int"].as<int>()) << std::endl;
std::cout << "string: " << cliargs::to_string(result["string"].as<std::string>()) << std::endl;
std::cout << "vector: " << cliargs::to_string(result["vector"].as<std::vector<float>>()) << std::endl;
return 0;
}
./simple -h
One line description of MyProgram
Usage: MyProgram ...
-h, --help Print this message and exit
-i, --int An interger
value: 'int'[optional]; default:-1
-s, --string A string
value: 'string'[optional]; default:"/dev/mem"
-v, --vector An int vector
value: 'vector<float>'[0~N]
./simple -i=32 -s hello -v 1.5 2.5 3.5
int: 32
string: "hello"
vector: [1.5, 2.5, 3.5]
layout | c++ type | usage |
---|---|---|
scalar | char , short , int , long , long long unsigned ... float , double , bool char * , const char * , std::string |
--arg_name arg_value --arg_name=arg_value --arg_name |
vector | std::vector<scalar> |
--arg_name v1 v2 v3 ... --arg_name v1 v2 --arg_name v3 ... |
matrix | std::vector<std::vector<scalar>> |
--arg_name v00 v01 ... --arg_name v10 v11 ... |
map | std::map<scalar, scalar> std::unordered_map<...> |
--arg_name key1 v1 --arg_name key2 v2 |
std::map<scalar, std::vector<scalar>> std::unordered_map<...> |
--arg_name key1 k1v1 k1v2 ... --arg_name key2 k2v1 k2v2 ... |
|
tuple | std::tuple<scalar...> |
--arg_name v1 v2 ... |
std::vector<std::tuple<scalar...>> |
--arg_name v00 v01 ... --arg_name v10 v11 ... |
|
std::map<scalar, std::tuple<scalar...>> std::unordered_map<...> |
--arg_name key1 k1v1 k1v2 ... --arg_name key2 k2v1 k2v2 ... |
2.2 Support for custom structure types and their derivative types after combining with STL containers
struct MyStruct {
std::string name;
float gain;
long size;
};
// overload oerator << for printing default-value in help
std::ostream &operator << (std::ostream &os, const MyStruct &obj) {
os << "{.name=" << obj.name << ", .gain=" << obj.gain << ", .size=" << obj.size << "}";
return os;
}
Choose one of the following functions. If both parsing functions exist, __parse_by_parser
will be called.
After overloading this function, the program user needs to use string array to assign values to the structure. Complete example
void __parse_by_parser(MyStruct &obj, cliargs::ArgParser &parser, const std::string &name) {
parser.domain_begin(name.empty() ? "MyStruct" : name); // tell ArgParser the struct's name
if (parser.assign(obj.name, "name")) { // MyStruct::name required a string value
parser.check(!obj.name.empty(), "invalid name: empty");
}
parser.assign(obj.gain, "gain"); // MyStruct::gain required an uint64 value
parser.set_optional(); // the followwing member is optional
parser.assign(obj.size, "size", (long)0); // specify a default value for optional member
parser.domain_end();
}
--my_struct data.bin 32 64
--my_struct data.bin 32
Note:
cliargs
will automatically infer the type name ofMyStruct
assert(cliargs::type_traits::name() == "{string, float[, long]}"); assert(cliargs::type_traits<std::vector>::name() == "vector<{string, float[, long]}>");
After overloading this function, the program user needs to use a single structured string to assign a value to the structure. Complete example
const char *__parse_by_format(MyStruct &obj, char *psz
, std::string name, std::list<std::string> &err_list
, void *context, char *parent) {
const char *type_name = "\"string,float[,long]\"";
if (!psz || !psz[0]) {
return type_name;
}
// try to split the string like "data.bin,32,64" or "data.bin,32"
...
// asign struct's member
// push messages into err_list if something is bad
...
return type_name; // return the type name
}
--my_struct "data.bin,32,64"
--my_struct "data.bin,32"
Note:
cliargs
will automatically deduce the type name ofMyStruct
assert(cliargs::type_traits::name() == "{"string,float[,long]"}"); assert(cliargs::type_traits<std::vector>::name() == "vector<{"string,float[,long]"}>");
layout | c++ type | __parse_by | usage |
---|---|---|---|
single | MyStruct |
parser |
--arg_name name gain [size] |
format |
--arg_name name,gain[,size] --arg_name=name,gain[,size] |
||
vector | std::vector<MyStruct> |
parser |
--arg_name name1 gain1 [size1] --arg_name name2 gain2 [size2] ... |
format |
--arg_name name,gain[,size] --arg_name=name,gain[,size] ... |
||
map | std::map<scalar, MyStruct> |
parser |
--arg_name key1 name1 gain1 [size1] --arg_name key2 name2 gain2 [size2] ... |
format |
--arg_name key1 name,gain[,size] --arg_name key 2 name,gain[,size] ... |
cliargs::Parser parser("MyApp");
parser.add_args()
('s', "string", "A string argument which only can be one of {'file', 'directory'})",
cliargs::value<std::string>()
->choices({"dump", "load"})
->regex("\\d+", "a integer")
// the value can only be one of {"dump", "load"} or an interger
)
('i', "interger", "An interger argument which only can be one of {1, 3, 5}",
cliargs::value<std::vector<int>>()
->choices({1, 3, 5})
->ranges({{10, 20}, {30, 50}}) ->examine([](int &v) ->bool { return v % 2; }, "an odd number")
// the value can only be one of {1, 3, 5} or in range [10, 20] or [30, 50] and be an odd number
);
The parser will judge whether the parameters specified by the user meet the requirements according to this order and formula:
(choices || ranges || regex) && examine
Applicable to all data types (including tuple
and struct
). Program developers can register custom callback functions through this interface to check (or modify) a single value. If the callback function returns false
, it is considered that the parameter value provided by the program user does not meet the requirements.
examine(std::function<bool(T &value)> func, std::string desc = "");
examine(std::function<bool(T &value, void *context)> func, std::string desc = "");
examine(std::function<bool(T &value, void *context, void *data)> func, std::string desc = "");
Only applicable to single-value types (integers, floating-point numbers, and strings) and their derived composite types. Other types do not have this interface. Program developers can use this interface to set a set of determined enumeration values.
choices(std::unordered_set<T> value_set, std::string desc = "");
Only applicable to numeric types (integers and floating-point numbers). Other types do not have this interface. Program developers can use this interface to set the value range of the value. This interface can be called multiple times to specify multiple ranges.
ranges(std::vector<std::pair<T, T>> pairs, std::string desc = "");
Only applicable to string type and its derived composite types. Other types do not have this interface. Program developers can use this interface to set a regular expression.
regex(std::string regex_string, std::string desc = "");
default_value(T &&data);
For command line parameters that provide default values, when the user does not set them, the default parameter value will be used.
implicit_value(typename T::value_type value);
For command line parameters that provide implicit values, if the number of parameter values provided by the user is less than the number of parameter values in the implicit value, the parser will automatically use the latter part of the implicit value to fill the command line parameters until the upper limit of the number of parameter values of the command line parameters is reached.
case | user action | result |
---|---|---|
cliargs::value<int>() ->default_value(1) ->implicit_value(5) |
not specified | 1 |
--arg_name |
5 | |
--arg_name 6 |
6 | |
cliargs::value<vector<int>>() ->data_count(1, 3) ->implicit_value({0, 1, 2, 3}) |
not specified | error |
--arg_name |
error | |
--arg_name 1 |
[1, 1, 2] |
|
--arg_name 1 2 |
[1, 2, 2] |
|
--arg_name 1 2 3 |
[1, 2, 3] |
|
--arg_name 1 2 3 4 |
error or 4 is accepted by positional argument |
cliargs::Parser parser("MyApp");
parser.add_args()
('v', "vector", "A size limited vector",
cliargs::value<std::vector<float>>()
->data_count(3, 5) // the vector can only accepts 3 ~ 5 datas
->implicit_value({0, 0, 0, 44, 55})
)
('m', "matrix", "A size limited matrix",
cliargs::value<std::vector<std::vector<int>>>()
->data_count(3, 5) // the matrix's height is 3 ~ 5
->line_width(2, 2) // the matrix's width is 2
)
('t', "tuple", "A tuple with default tail",
cliargs::value<std::tuple<std::string, int, float>>()
->line_width(2) // tuple[0] and tuple[1] is required and tuple[2] is optional
->implicit_value({"", 0, 1.0f})
);
The attribute interface includes "[common attribute interface](#common attribute interface)", "[metadata type specific attribute interface](#metadata type specific attribute interface)" and "[container type specific attribute interface](#container type specific attribute interface)". The final attribute interface of the combined type is the union of the three.
For detailed examples of combined usage, please refer to the unit test
attribue |
---|
required |
positional |
default_value |
examine |
meta type | attributes |
---|---|
numerical | choices, ranges |
string | choices, regex |
struct |
attribute | |
---|---|
scalar | implicit_value |
vector | implicit_value, data_count |
matrix | implicit_value, data_count, line_width |
map | implicit_value, line_width |
tuple | implicit_value, line_width |
default_value
The data type is the same as the data type of the command line parameter
The data type of implicit_value
is as follows:
argument | implicit_value |
---|---|
scalar |
scalar |
vector<scalar> |
vector<scalar> |
vector<vector<scalar>> |
vector<scalar> |
map<key, scalar> |
scalar |
map<key, vector<scalar>> |
vector<scalar> |
tuple<scalar...> |
tuple<scalar...> |
attribute |
---|
allow_unknow |
set_width |
concise_help |
cliargs::Parser parser("MyApp");
parser.add_args()
('h', "help", "Normal boolean arguemnt")
('r', "reverse_bool", "Reverse boolean argument",
cliargs::value()->implicit_value(false)
);
The attribute of a normal boolean parameter is: cliargs::value()->implicit_value(true)
cliargs::Parser parser("MyApp");
parser.add_args()
("ptr", "char *", cliargs::value<char *>())
("const_ptr", "const char *", cliargs::AsArgs<const char *>());
- If you declare your own data type as a pointer type (
char *
orconst char *
), or combine a pointer type with an STL container, or embed a pointer in atuple
, the pointer will directly point to the string passed to themain
function by the program user - If the pointer type is embedded in the structure and
__parse_by_parser
is used to assign values to the structure members, the pointer type will point to the string passed to themain
function by the program user - If the pointer type is embedded in the structure and
__parse_by_format
is used to assign values to the structure members, the program developer must ensure the validity of the pointer
template <typename T>
std::string cliargs::to_string(const T &value
, const std::string &delimiter = ",", const std::string &gap = " ");
Composite data types can be converted into strings.
- Example:
std::map<std::string, std::tuple<int, std::vector<float>>> data = {
{"key1": {1, {2.5, 3.5}}},
{"key2": {2, {4.5, 5.5}}},
};
std::cout << cliargs::to_string(data) << std::endl;
- Output:
{"key1": (1, [2.5, 3.5]), "key2": (2, [4.5, 5.5])}
template <typename T>
const std::string &cliargs::type_traits<T>::name();
You can get the string name of the composite data type. For custom structure types, you need to overload __parse_by_parser
or __parse_by_format
first.
- Example:
typedef std::map<std::string, std::tuple<int, std::vector<float>>> MyType;
std::cout << cliargs::type_traits<MyType>::name() << std::endl;
- Output:
map<string, tuple<int, vector<float>>>