C++ Klasse zum einfachen Parsen der Kommandozeilenargumente
C++ class template for parsing command line arguments
(Die Dokumentation lass ich mal in Englisch stehen. Source code ganz unten.)
*NIX/Linux style command line argument handling class template. Parses
options and positional arguments similar to ::getopt()
, except that
defining accepted options and retrieving their values is simpler.
Additionally ...
You can define what data types the argument must have, and can directly retrieve the converted values with the type conversion operators (bool), (double) and (std::string).
You can specify if the argument is
- required (error if it is omitted or no value is given),
- optional (default value if it is omitted, error if no value given),
- flag (can be omitted or set, but does not accept a value),
- optinoal/val(default if omitted, other default if no value is given).
It can implicitly handle errors (unknown options, missing options, type mismatch).
It can generate a complete program help with synopsis, program description, detailed argument help etc (*NIX/Linux style help/man text). Implicit soft wrapping of text.
It can allow you to ignore unknown option errors and retrieve the value of these options.
It can return the list of positional arguments.
You can retrieve the positions of all parsed arguments (e.g. in case that an option must occur before or after a positional argument or another options, etc).
Options can occur multiple times (e.g.
-i file1 -i file2 -i file3
).You can easily retrieve numeric option values / positional argument values using the argument class typecast operators
(double)
,(bool)
, also(string)
, e.g.cout << (bool)cli_args["verbose"] << endl;
.Allows value assignments with "=" for short options as well, e.g.
-f=file1
.Accepts commonly known separators like
--
for "end of options" or the values-
for "use stdin/stdout";-0
..-9
can be defined as short options and will be differed from negative numbers.The class works without exceptions (except exceptions that can can come from the STL containers).
Datei
File
Examples how arguments can be specified from the command line:
$my_tar -x -v -z -f ~/file.tar.gz
$my_tar -xvzf ~/file.tar.gz
$my_tar -xvzf~/file.tar.gz
$my_tar -xvzf=~/file.tar.gz
$my_tar --extract -vz --file ~/file.tar.gz
$my_tar --extract -vz --file=~/file.tar.gz
$my_prog --pi=3.1415 'positional 1' -- --no-option --neither-option
$my_prog -9 # Short opt if it is defined, a positional negative number
$my_prog -i - -o - # Single "-" often also known as "use STDIN/STDOUT".
Template and object
namespace sw {
// Template
namespace templates {
template <typename str_t> class basic_cli_args;
}
// Standard specialisation (std::string) : (class) sw::cli_args_t
typedef templates::basic_cli_args<std::string> cli_args_t;
// Used default global object
cli_args_t cli_args = cli_args_t();
}
As normally only one instance is required, latter is initialised as global
object sw::cli_args
with the template specialisation std::string (typedef'ed
sw::cli_args_t
). Around this class there are some helpers for argument
definitions and contents and iostream operations. They are all accessible
as typedefs in sw::cli_args_t
and allow smart auto-completion in IDEs.
Essentially the usage is:
Include this file, then you have the object
sw::cli_args
.In
main()
, passargc
andargv
to the object using the operator ():int main(int argc, char** argv) { cli_args(argc, argv); [...] }
In whatever function/method you like, define the arguments you accept, as well using the
operator()
(this overload takes only string arguments).cli_args( SHORT_OPT, // e.g. "i" LONG_OPT, // e.g. "input-file" DATA_TYPE, // e.g. "s" for string DEFAULT_IF_ARGUMENT_IS_COMPLETELY_OMMITTED, // e.g. "" DEFAULT_IF_ARGUMENT_HAS_NO_VALUE, // e.g. "" HELP_TEXT_FOR_THIS_ARGUMENT, // e.g. "Input file to ..." ); // You can chain call this operator cli_args("i","in", "s")("o","out", "s")("v","verbose", "b", "", "1");`
Call
cli_args.parse();
Now you can get arguments and other information. Use the operator[] to query arguments. If an argument is not found an empty argument (
narg
) is returned. You can usecli_args["string"]
to get options, andcli_args[UINT]
to get arguments by their position.Get option values by short/long name:
string ifile = (string) cli_args["i"]; string ofile = (string) cli_args["out"]; bool verbose = (bool) cli_args["v"];
Get argument values by position:
string arg = (string) cli_args[0];
Get information about options/arguments:
if(cli_args["i"].is_narg()) { Option -i is missing } if(cli_args["i"].is_ok()) { All ok with this option } if(cli_args["i"].is_bool()) { Its a boolean per definition } if(cli_args["i"].is_float()) { Its a floating point number } if(cli_args[0].is_positional()) { First argument is positional } if(cli_args[0].value()=="") { value() returns the string value }
Getting errors:
if(cli_args.has_errors()) { Something missing, wrong data type etc. } cli_args_t::str_vect_t errors = cli_args.errors(); // Errors as string vector // Or simpler: Let the class print errors to stderr and exit (code 1) cli_args.handle_help_and_errors();
Getting other information:
string name = cli_args.program_name(); // Program (base)name cli_args_t::args_t positionals = cli_args.positional_arguments();
Stream operations / object dump:
cerr << cli_args << endl; // Dump object cerr << cli_args["i"] << endl; // Dump single argument object cerr << cli_args.positional_arguments() << endl; // Dump positionals cli_args.help(); // Print defined options help
Data type definitions (all given as string values)
"s"
: string"b"
: bool, nonzero numbers=true, literals like yes,no,off,ja,oui accepted."n"
: number, means floating point number"i"
: number, integer"f"
: file, practically a string with some restrictions
"Kind of option" definition: This is done with the two default values.
Depending on weather a default is an empty string or not, the meaning changes:
Empty "option omitted", empty "value omitted": This menas the option and its value is required, e.g. for an input file:
E.g.
cli_args("i", "inp", "s", "", "")
.Needs one of:
-i file
,-i=file
,-ifile
,--inp=file
,--inp file
.Empty "option omitted", set "value omitted": Means that this options is a flag. It does not accept a value, except the value is given with "=" and corresponds to the default value.
E.g.
("x", "extract", "b", "", "1")
.Valid would be:
-x
,-x=yes
,--extract
,--extract=true
,-x=enabled
.Set "option omitted", empty "value omitted": That means this option optional and can be omitted, but if it is set it must have a value as well.
E.g.
("b", "bit-rate", "i", "115200", "")
.If
-b
or--bit-rate
is both missing, 115200 will be retrieved. Otherwise the argument must look like:-b9600
,-b 9600
,-b=9600
,--bit-rate=9600
,--bit-rate 9600
.Set "option omitted", set "value omitted": Means the option can be omitted and it can be set like a flag without a value. Verbosity is an example for this:
E.g.
("v", "verbose", "i", "0", "1")
.Now, if no
-v
or--verbose
is set 0 is assumed. If the user says-v
or--verbose
, the missing value defaults to 1. If the user says-v2
or--verbose=2
you will read 2.
Things to be aware of ...
Using optional arguments with optional values you can run into conditions where the parser cannot be sure if an argument is positional or the optional value of an option. E.g.
("v", "verbose", "i", "0", "1")
. Saying-v 2
it could be "verbose level 2" or "verbose level 1 and positional 2". A similar problem occurrs when inlining options, liketar -xvzf
. Ifv
would a string data type it is not clear if the default string should be used or if the optional value is like-v=zf
. For this reason the class follows some rules to differ:--opt VALUE is NOT allowed like this. VALUE will be positional. --opt=VALUE is ok -o VALUE again, VALUE will be positional. -o=VALUE is ok -opq (joined short opts): If `p` is a known flag or on data type missmatch it will be like `-o -pq`, if not it is like `-o=pq`.
In short: The parser tries to get what the user means, but simply prefer required arguments, flags and "optinoal with value" to avoid ambiguous argument interpretations.
When defining numeric short options like
-0
to-9
be aware that your program should not deal with numbers that can be negative.Positions in the argument list are not the equivalent to the
argv[]
indices thatmain()
gets, because option value is not counted as position. As well, position 0 is not the program path, it is the first argument or option. The program name is retrieved withprogram_name()
. E.g.$./myprog -f FILE POS1 POS2
will get:
- cli_args.program_name() = "myprog" - cli_args.arguments()[0] = { pos:0, key: "-f", val: "FILE", def:"(its defin)"} - cli_args.arguments()[1] = { pos:1, key: "", val: "POS1", def:"(positional)" } - cli_args.arguments()[2] = { pos:2, key: "", val: "POS2", def:"(positional)" } - cli_args.arguments()[>2]= (NARG)
This might be confusing at the first look, however, it saves practically a lot of work, as the positions are the logical positions of the arguments that the program gets, index starting at
0
as vectors do.Don't define single character long options if you define a short option with the same name. E.g.
-v
and--v
. Class can't differ them and will return the first that it sees in the argument list.If an argument is defined multiple times, the
operator[string key]
returns the first of them. Iterate througharguments()
or useoperator[](int index)
to get all of them. E.g.$prog -i file1 -i file2 -i file3
-->cli_args["i"]=="file1"
;
Beispiele
Examples
#include <cli_args.hh>
#include <iostream>
using namespace sw;
using namespace std;
int main(int argc, char** argv)
{
// The cli_args object of type class cli_args_t
cli_args(argc, argv) // Assign argc and argv
.allow_unknown_options(true) // We allow options not in the list above
.parse() // Parse, make argument list, check errors
.handle_help_and_errors( // Quick way to handle -h/--help and errors.
"Program to calculate something.", // Help description, default: ""
"v1.0, stfwi, 2010", // Version, author, etc, default: ""
"[c1 [c2 [...]]]", // Positionals synopsis help, default: ""
" 0: successful\n>0: error", // Return values help, default: ""
0, // Exit code (if it exits), default: 1
cerr // ostream to use, default: std::cerr
);
cout << cli_args << endl;
return 0;
}
Microtest:
#include <cli_args.hh>
#include "test.hh"
void test1()
{
const char* argv[] = {
"cli_args"
, "--help"
, "-i", "FILE1"
, "--output-file=FILE2"
, "--verbose", "2" // that will NOT be --verbose==2; 2 is treated positional
, "-b=2" // same decl as --verbose, but b will be 2 because it is known here that 2 belongs to b.
, "-r=.2"
, "-f", "rrr"
, "-a=2"
, "--flag1"
, "--flag2"
, "--flag3"
, "--"
, "--"
, "POS"
, NULL
};
{
std::stringstream ss;
ss << "Test args: ";
for(const char** p=argv; *p!=NULL; ++p) ss << " '" << *p << "'";
test_comment(ss.str());
}
using sw::cli_args;
int argc = (sizeof(argv)/sizeof(char*))-1;
cli_args(argc, argv)
("i", "input-file" , "f", "" , "" , "The input file to use.")
("o", "output-file", "f", "" , "" , "The output file to save to.")
("v", "verbose", "i", "0" , "1", "Verbosity level.")
("r", "noise-ratio", "n", "0.1" , "" , "Expected noise ratio.")
("n", "normalize", "b", "" , "1", "Normalise output.")
("f", "filter", "s", "rms" , "" , "Filter to apply.")
// --pi: optional number with value, default: 3.14....
("" , "pi" , "n", "3.1415926")
// -i/--int: optional (i)nt, if omitted -> 0, if no value set -> 1000
("b", "int", "i", "1", "0") // -b,--bool: optional, if omitted:true
("", "flag1", "s", "", "doit") // flag1, string
("", "flag2", "i", "", "10") // flag2, int
("", "flag3", "n", "", "1.1") // flag3, float
("", "flag4", "n", "", "5") // flag4, float
.allow_unknown_options(true)
.parse()
.handle_help_and_errors(
"Microtest 1.\n\n"
"- This text will be wrapped. This text will be wrapped This text "
"will be wrapped. This text will be wrapped.",
" VERSION_COPY_STATEMENT",
" SYNOPSIS_STATEMENT",
" RETURNCODE_STATEMENT",
0,
cerr
);
// Fetch arguments
// test_comment( "Positional arguments:" << cli_args.positional_arguments() );
// test_comment( "Args dump: " << cli_args );
// flag4 is a flag with a numeric value.
test_expect( !cli_args["flag4"].is_undefined_option() );
test_expect( cli_args["flag4"].is_narg() );
test_expect( cli_args["flag4"].is_ok() );
test_expect( !cli_args["flag4"].is_bool() );
test_expect( !cli_args["flag4"].is_file() );
test_expect( cli_args["flag4"].definition().is_float() );
test_expect( !cli_args["flag4"].is_int() );
test_expect( !cli_args["flag4"].is_positional() );
test_expect( !cli_args["flag4"].is_string() );
test_expect( (string) cli_args["flag4"] == "" );
test_expect( (double) cli_args["flag4"] == 0);
test_expect( (bool) cli_args["flag4"] == false);
// --help used in argument list, but defined in the expected arguments, hence:
test_expect( cli_args["help"].is_undefined_option() );
test_expect( !cli_args["help"].is_narg() ); // it is in the argument list --> not narg
test_expect( !cli_args["help"].is_ok() ); // not ok because not expected
test_expect( !cli_args["help"].is_bool() ); // everything below must return false ...
test_expect( !cli_args["help"].is_file() ); // ... because unexpected arg
test_expect( !cli_args["help"].is_float() );
test_expect( !cli_args["help"].is_int() );
test_expect( !cli_args["help"].is_positional() );
test_expect( !cli_args["help"].is_string() );
test_expect( (bool) cli_args["help"] == true ); // bool === not 0 or not empty
test_expect( (string) cli_args["help"] == "" );
test_expect( sw::utest::isnan((double) cli_args["help"]));
// -i FILE1 used in argument list, i expects an argument "f"===file===string
test_expect( !cli_args["i"].is_undefined_option() ); // is registered in cli args
test_expect( !cli_args["i"].is_narg() ); // given argument, not narg
test_expect( cli_args["i"].is_ok() ); // is file --> ok
test_expect( !cli_args["i"].is_bool() ); // defined as file, not bool
test_expect( cli_args["i"].is_file() ); // defined as file
test_expect( !cli_args["i"].is_float() ); // etc .
test_expect( !cli_args["i"].is_int() );
test_expect( !cli_args["i"].is_positional() );
test_expect( cli_args["i"].is_string() ); // File is implicitly string
test_expect( (string) cli_args["i"] == "FILE1" );
test_expect( (bool) cli_args["i"] == true ); // bool === not 0 or not empty
test_expect( sw::utest::isnan((double) cli_args["i"]));
// --output-file=FILE2 used in argument list, --output-file is mapped together with -o
// and expects an argument "f"===file===string --- same as -i:
test_expect( !cli_args["o"].is_undefined_option() );
test_expect( !cli_args["o"].is_narg() );
test_expect( cli_args["o"].is_ok() );
test_expect( !cli_args["o"].is_bool() );
test_expect( cli_args["o"].is_file() );
test_expect( !cli_args["o"].is_float() );
test_expect( !cli_args["o"].is_int() );
test_expect( !cli_args["o"].is_positional() );
test_expect( cli_args["o"].is_string() );
test_expect( (string) cli_args["o"] == "FILE2" );
test_expect( (bool) cli_args["o"] == true ); // bool === not 0 or not empty
test_expect( sw::utest::isnan((double) cli_args["o"]));
// --verbose is optional with optional arg. if --verbose=2 had been specified,
// the values would be 2, but on --verbose 2 the value assignment is ambiguous,
// and in doubt the the argument is positional. Hence, --verbose counts as specified
// but without given value, default is then --verbose===1:
test_expect( !cli_args["verbose"].is_undefined_option() );
test_expect( !cli_args["verbose"].is_narg() );
test_expect( cli_args["verbose"].is_ok() );
test_expect( !cli_args["verbose"].is_bool() );
test_expect( !cli_args["verbose"].is_file() );
test_expect( !cli_args["verbose"].is_float() );
test_expect( cli_args["verbose"].is_int() );
test_expect( !cli_args["verbose"].is_positional() );
test_expect( !cli_args["verbose"].is_string() );
test_expect( (double) cli_args["verbose"] == 1);
test_expect( (int) cli_args["verbose"] == 1 );
test_expect( (bool) cli_args["verbose"] == true );
// -b is optional with optional arg, and given as -b=2, so it's not ambiguous.
test_expect( !cli_args["b"].is_undefined_option() );
test_expect( !cli_args["b"].is_narg() );
test_expect( cli_args["b"].is_ok() );
test_expect( !cli_args["b"].is_bool() );
test_expect( !cli_args["b"].is_file() );
test_expect( !cli_args["b"].is_float() );
test_expect( cli_args["b"].is_int() );
test_expect( !cli_args["b"].is_positional() );
test_expect( !cli_args["b"].is_string() );
test_expect( (double) cli_args["b"] == 2);
test_expect( (int) cli_args["b"] == 2 );
test_expect( (bool) cli_args["b"] == true );
// -r or --noise-ratio, defined as numeric, default 0.1, given as 0.2.
test_expect( !cli_args["r"].is_undefined_option() );
test_expect( !cli_args["r"].is_narg() );
test_expect( cli_args["r"].is_ok() );
test_expect( !cli_args["r"].is_bool() );
test_expect( !cli_args["r"].is_file() );
test_expect( cli_args["r"].is_float() );
test_expect( !cli_args["r"].is_int() );
test_expect( !cli_args["r"].is_positional() );
test_expect( !cli_args["r"].is_string() );
test_expect( (double) cli_args["r"] == 0.2 );
test_expect( (int) cli_args["r"] == 0 );
test_expect( (bool) cli_args["r"] == true ); // bool === not 0 or not empty
// -n or --normalize, optional and not specified
test_expect( !cli_args["n"].is_undefined_option() );
test_expect( cli_args["n"].is_narg() );
test_expect( cli_args["n"].is_ok() );
test_expect( cli_args["n"].is_bool() );
test_expect( !cli_args["n"].is_file() );
test_expect( !cli_args["n"].is_float() );
test_expect( !cli_args["n"].is_int() );
test_expect( !cli_args["n"].is_positional() );
test_expect( !cli_args["n"].is_string() );
test_expect( (double) cli_args["n"] == 0);
test_expect( (int) cli_args["n"] == 0);
test_expect( (bool) cli_args["n"] == false );
// -f is expected as string and a given argument:
test_expect( !cli_args["f"].is_undefined_option() );
test_expect( !cli_args["f"].is_narg() );
test_expect( cli_args["f"].is_ok() );
test_expect( !cli_args["f"].is_bool() );
test_expect( !cli_args["f"].is_file() );
test_expect( !cli_args["f"].is_float() );
test_expect( !cli_args["f"].is_int() );
test_expect( !cli_args["f"].is_positional() );
test_expect( cli_args["f"].is_string() );
test_expect( (string) cli_args["f"] == "rrr" );
test_expect( (int) cli_args["f"] == (int) cli_args.narg );
test_expect( sw::utest::isnan((double) cli_args["f"]) );
test_expect( (bool) cli_args["f"] == true ); // not empty --> true
// -a is not registered as expected or optional argument, however, allow_unknown_options(true)
// does not cause exiting, and we can fetch the value, too. The following expectation apply:
test_expect( cli_args["a"].is_undefined_option() );
test_expect( !cli_args["a"].is_narg() );
test_expect( !cli_args["a"].is_ok() ); // Not defined, hence, no definition, hence
test_expect( !cli_args["a"].is_bool() ); // all type queries return false:
test_expect( !cli_args["a"].is_file() );
test_expect( !cli_args["a"].is_float() );
test_expect( !cli_args["a"].is_int() );
test_expect( !cli_args["a"].is_positional() );
test_expect( !cli_args["a"].is_string() );
test_expect( (string) cli_args["a"] == "2" );
test_expect( (bool) cli_args["a"] == true); // not 0, not empty --> true
test_expect( (double) cli_args["a"] == 2);
test_expect( (int) cli_args["a"] == 2 );
// flag1 is a flag, with a string value if given in the argument list. If the flag is
// specified, the value must be exactly the expected value, or the argument will be not ok.
test_expect( !cli_args["flag1"].is_undefined_option() );
test_expect( !cli_args["flag1"].is_narg() );
test_expect( cli_args["flag1"].is_ok() );
test_expect( !cli_args["flag1"].is_bool() );
test_expect( !cli_args["flag1"].is_file() );
test_expect( !cli_args["flag1"].is_float() );
test_expect( !cli_args["flag1"].is_int() );
test_expect( !cli_args["flag1"].is_positional() );
test_expect( cli_args["flag1"].is_string() );
test_expect( (string) cli_args["flag1"] == "doit" );
test_expect( (bool) cli_args["flag1"] == true); // not 0, not empty --> true
// flag2 is a flag, with a int value if given in the argument list.
test_expect( !cli_args["flag2"].is_undefined_option() );
test_expect( !cli_args["flag2"].is_narg() );
test_expect( cli_args["flag2"].is_ok() );
test_expect( !cli_args["flag2"].is_bool() );
test_expect( !cli_args["flag2"].is_file() );
test_expect( !cli_args["flag2"].is_float() );
test_expect( cli_args["flag2"].is_int() );
test_expect( !cli_args["flag2"].is_positional() );
test_expect( !cli_args["flag2"].is_string() );
test_expect( (int) cli_args["flag2"] == 10 );
test_expect( (bool) cli_args["flag2"] == true);
// flag3 is a flag with a numeric value.
test_expect( !cli_args["flag3"].is_undefined_option() );
test_expect( !cli_args["flag3"].is_narg() );
test_expect( cli_args["flag3"].is_ok() );
test_expect( !cli_args["flag3"].is_bool() );
test_expect( !cli_args["flag3"].is_file() );
test_expect( cli_args["flag3"].is_float() );
test_expect( !cli_args["flag3"].is_int() );
test_expect( !cli_args["flag3"].is_positional() );
test_expect( !cli_args["flag3"].is_string() );
test_expect( (string) cli_args["flag3"] == "1.1" );
test_expect( (double) cli_args["flag3"] == 1.1 );
test_expect( (int) cli_args["flag3"] == 1 );
test_expect( (bool) cli_args["flag3"] == true); // not 0, not empty --> true
// Positionals:
// cli_args --help .... --verbose 2 -b=2 ... -- -- POS
// ______________________^__________^^_^^_^^^
// ________________________________separator_||
if(test_expect_cond(cli_args.positional_arguments().size() == 3)) {
const char* vals[3] = { "2", "--", "POS" };
for(int i=0; i<3; ++i) {
test_expect( !cli_args.positional_arguments()[i].is_undefined_option() );
test_expect( !cli_args.positional_arguments()[i].is_narg() );
test_expect( cli_args.positional_arguments()[i].is_ok() ); // always ok, but the type is
test_expect( !cli_args.positional_arguments()[i].is_bool() ); // never known ...
test_expect( !cli_args.positional_arguments()[i].is_float() );
test_expect( !cli_args.positional_arguments()[i].is_int() );
test_expect( !cli_args.positional_arguments()[i].is_string() );
test_expect( (string) cli_args.positional_arguments()[i] == vals[i] );
test_expect( (bool) cli_args.positional_arguments()[i] == true );
}
}
}
void test()
{
test1();
}
Quelltext
Source code
/**
* @package de.atwillys.cc.swl.util
* @license BSD (simplified)
* @author Stefan Wilhelm (stfwi)
*
* @file cli_args.hh
* @ccflags
* @ldflags
* @platform linux, bsd, windows
* @standard >= c++98
*
* -----------------------------------------------------------------------------
*
* *NIX/Linux style command line argument handling class template. Parses
* options and positional arguments similar to `::getopt()`, except that
* defining accepted options and retrieving their values is simpler.
* Additionally ...
*
* - You can define what data types the argument must have, and can directly
* retrieve the converted values with the type conversion operators (bool),
* (double) and (std::string).
*
* - You can specify if the argument is
* - required (error if it is omitted or no value is given),
* - optional (default value if it is omitted, error if no value given),
* - flag (can be omitted or set, but does not accept a value),
* - optinoal/val(default if omitted, other default if no value is given).
*
* - It can implicitly handle errors (unknown options, missing options, type
* mismatch).
*
* - It can generate a complete program help with synopsis, program description,
* detailed argument help etc (*NIX/Linux style help/man text). Implicit
* soft wrapping of text.
*
* - It can allow you to ignore unknown option errors and retrieve the value
* of these options.
*
* - It can return the list of positional arguments.
*
* - You can retrieve the positions of all parsed arguments (e.g. in case
* that an option must occur before or after a positional argument or another
* options, etc).
*
* - Options can occur multiple times (e.g. `-i file1 -i file2 -i file3`).
*
* - You can easily retrieve numeric option values / positional argument
* values using the argument class typecast operators `(double)`, `(bool)`,
* also `(string)`, e.g. `cout << (bool)cli_args["verbose"] << endl;`.
*
* - Allows value assignments with "=" for short options as well,
* e.g. `-f=file1`.
*
* - Accepts commonly known separators like `--` for "end of options" or
* the values `-` for "use stdin/stdout"; `-0` .. `-9` can be defined as
* short options and will be differed from negative numbers.
*
* The class works without exceptions (except exceptions that can can come from
* the STL containers).
*
* Examples how arguments can be specified from the command line:
*
* - $my_tar -x -v -z -f ~/file.tar.gz
* - $my_tar -xvzf ~/file.tar.gz
* - $my_tar -xvzf~/file.tar.gz
* - $my_tar -xvzf=~/file.tar.gz
* - $my_tar --extract -vz --file ~/file.tar.gz
* - $my_tar --extract -vz --file=~/file.tar.gz
*
* - $my_prog --pi=3.1415 'positional 1' -- --no-option --neither-option
* ^^
* ``-- END OF OPTIONS SEPARATOR
*
* - $my_prog -9 # Short opt if it is defined, a positional negative number
*
* - $my_prog -i - -o - # Single "-" often also known as "use STDIN/STDOUT".
*
* -----------------------------------------------------------------------------
*
* Template:
*
* - template <typename str_t> class basic_cli_args;
*
* As normally only one instance is required, latter is initialised as global
* object `sw::cli_args` with the template specialisation std::string (typedef'ed
* `sw::cli_args_t`). Around this class there are some helpers for argument
* definitions and contents and iostream operations. They are all accessible
* as typedefs in `sw::cli_args_t` and allow smart auto-completion in IDEs.
*
* --------------------------------------------------------------------------
*
* Essentially the usage is:
*
* - Include this file, then you have the object sw::cli_args.
*
* - In main(), pass `argc` and `argv` to the object using the operator ():
*
* int main(int argc, char** argv) { cli_args(argc, argv); [...] }
*
* - In whatever function/method you like, define the arguments you accept,
* as well using the `operator()` (this overload takes only string arguments).
*
* cli_args(
* SHORT_OPT, // e.g. "i"
* LONG_OPT, // e.g. "input-file"
* DATA_TYPE, // e.g. "s" for string
* DEFAULT_IF_ARGUMENT_IS_COMPLETELY_OMMITTED, // e.g. ""
* DEFAULT_IF_ARGUMENT_HAS_NO_VALUE, // e.g. ""
* HELP_TEXT_FOR_THIS_ARGUMENT, // e.g. "Input file to ..."
* );
*
* // You can chain call this operator
* cli_args("i","in", "s")("o","out", "s")("v","verbose", "b", "", "1");
*
* - Call `cli_args.parse();`
*
* - Now you can get arguments and other information. Use the operator[] to
* query arguments. If an argument is not found an empty argument (`narg`)
* is returned. You can use `cli_args["string"]` to get options, and
* `cli_args[UINT]` to get arguments by their position.
*
* - Get option values by short/long name:
*
* string ifile = (string) cli_args["i"];
* string ofile = (string) cli_args["out"];
* bool verbose = (bool) cli_args["v"];
*
* - Get argument values by position:
*
* string arg = (string) cli_args[0];
*
* - Get information about options/arguments:
*
* if(cli_args["i"].is_narg()) { Option -i is missing }
* if(cli_args["i"].is_ok()) { All ok with this option }
* if(cli_args["i"].is_bool()) { Its a boolean per definition }
* if(cli_args["i"].is_float()) { Its a floating point number }
* if(cli_args[0].is_positional()) { First argument is positional }
* if(cli_args[0].value()=="") { value() returns the string value }
*
* - Getting errors:
*
* if(cli_args.has_errors()) { Something missing, wrong data type etc. }
* cli_args_t::str_vect_t errors = cli_args.errors(); // Errors as string vector
*
* - Getting other information:
*
* string name = cli_args.program_name(); // Program (base)name
* cli_args_t::args_t positionals = cli_args.positional_arguments();
*
* - Stream operations / object dump:
*
* cerr << cli_args << endl; // Dump object
* cerr << cli_args["i"] << endl; // Dump single argument object
* cerr << cli_args.positional_arguments() << endl; // Dump positionals
*
* cli_args.help(); // Print defined options help
*
* --------------------------------------------------------------------------
*
* - Data type definitions (all given as string values)
*
* - "s" : string
* - "b" : bool, nonzero numbers=true, literals like yes,no,off,ja,oui accepted.
* - "n" : number, means floating point number
* - "i" : number, integer
* - "f" : file
*
* - "Kind of option" definition: This is done with the two default values.
* Depending on weather a default is an empty string or not, the meaning
* changes:
*
* - Empty "option omitted", empty "value omitted":
* This menas the option and its value is required, e.g. for an input file:
*
* E.g. `cli_args("i", "inp", "s", "", "")`.
*
* Needs one of: `-i file`, `-i=file`, `-ifile` ,`--inp=file`, `--inp file`.
*
* - Empty "option omitted", set "value omitted":
* Means that this options is a flag. It does not accept a value, except
* the value is given with "=" and corresponds to the default value.
*
* E.g. `("x", "extract", "b", "", "1")`.
*
* Valid would be: `-x`, `-x=yes`, `--extract`, `--extract=true`, `-x=enabled`.
*
* - Set "option omitted", empty "value omitted":
* That means this option optional and can be omitted, but if it is set
* it must have a value as well.
*
* E.g. `("b", "bit-rate", "i", "115200", "")`.
*
* If `-b` or `--bit-rate` is both missing, 115200 will be retrieved. Otherwise
* the argument must look like: `-b9600`, `-b 9600`, `-b=9600`, `--bit-rate=9600`,
* `--bit-rate 9600`.
*
* - Set "option omitted", set "value omitted":
* Means the option can be omitted and it can be set like a flag without a
* value. Verbosity is an example for this:
*
* E.g. `("v", "verbose", "i", "0", "1")`.
*
* Now, if no `-v` or `--verbose` is set 0 is assumed. If the user says
* `-v` or `--verbose`, the missing value defaults to 1. If the user says
* `-v2` or `--verbose=2` you will read 2.
*
* -----------------------------------------------------------------------------
*
* - Things to keep in mind ...
*
* - With optional arguments with optional values you can run into conditions
* where the parser cannot be sure if an argument is positional or the
* optional value of an option.E.g. `("v", "verbose", "i", "0", "1")`.
* Saying `-v 2` it could be "verbose level 2" or "verbose level 1 and
* positional 2". A similar problem occurrs when inlining options, like
* `tar -xvzf`. If `v` would a string data type it is not clear if the
* default string should be used or if the optional value is like `-v=zf`.
* For this reason it follows somwe rules to differ.
*
* - `--opt VALUE` is NOT allowed like this. VALUE will be positional.
* - `--opt=VALUE` is ok
* - `-o VALUE` again, VALUE will be positional.
* - `-o=VALUE` is ok
* - `-opq` (joined short opts): If `p` is a known flag or on data
* type missmatch it will be like `-o -pq`, if not it is
* like `-o=pq`.
*
* In short: The parser tries to get what the user means, but simply prefer
* required arguments, flags and "optinoal with value" to avoid ambiguous
* argument interpretations.
*
* - When defining numeric short options like `-0` to `-9` be aware that your
* program should not deal with numbers that can be negative.
*
* - Positions in the argument list are not the equivalent to the argv[] indices
* that main() gets, because option value is not counted as position. As well,
* position 0 is not the program path, it is the first argument or option. The
* program name is retrieved with `program_name()`. E.g.
*
* `$./myprog -f FILE POS1 POS2` will get:
* - program_name() = "myprog"
* - arguments()[0] = { pos:0, key: "-f", val: "FILE", def:"(its defin)"}
* - arguments()[1] = { pos:1, key: "", val: "POS1", def:"(positional)" }
* - arguments()[2] = { pos:2, key: "", val: "POS2", def:"(positional)" }
* - arguments()[>2]= (NARG)
*
* - Don't define single character long options if you define a short option
* with the same name. E.g. '-v' and '--v'. Class can't differ them and
* will return the first that it sees in the argument list.
*
* - If an argument is defined multiple times, the `operator[]` returns the
* first of them. Iterate through `arguments()` to get all of them.
* E.g. `$prog -i file1 -i file2 -i file3` --> `cli_args["i"]=="file1"`;
*
* -----------------------------------------------------------------------------
*
* Example source code:
*
* int mmain(int argc, char** argv)
* {
* // The cli_args object of type class cli_args_t
* cli_args(argc, argv) // Assign argc and argv
* // Definitions
* ("i", "input-file" , "f", "" , "" , "The input file to use.")
* ("o", "output-file", "f", "" , "" , "The output file to save to.")
* ("v", "verbose", "i", "0" , "1", "Verbosity level.")
* ("r", "noise-ratio", "n", "0.1" , "" , "Expected noise ratio.")
* ("n", "normalize", "b", "" , "1", "Normalise output.")
* ("f", "filter", "s", "s-T1", "" , "Filter to apply.")
* .allow_unknown_options(true) // We allow options not in the list above
* .parse() // Parse, make argument list, check errors
* .handle_help_and_errors( // Quick way to handle -h/--help and errors
* "Program to calculate something.\n\n" // Help description, default: ""
* "- This text will be wrapped. This text will be wrapped This text "
* "will be wrapped. This text will be wrapped.",
* "v1.0, stfwi, 2010", // Version, author, etc, default: ""
* "[c1 [c2 [...]]]", // Positionals synopsis help, default: ""
* " 0: successful\n>0: error", // Return values help, default: ""
* 0, // Exit code (if it exits), default: 1
* cerr // ostream to use, default: std::cerr
* );
*
* // Fetch arguments
* string ifile = (string) cli_args["i"];
* string ofile = (string) cli_args["o"];
* int loglevel = (int) cli_args["v"];
* double s2n = (double) cli_args["r"];
* bool norm = (bool) cli_args["n"];
* string filt = (string) cli_args["f"];
* string unknown_a = (string) cli_args["a"]; // Retrieve unknown option -a
*
* // Dump them
* cout << "input : " << ifile << endl
* << "output : " << ofile << endl
* << "log level : " << loglevel << endl
* << "signal2noise: " << s2n << endl
* << "do normalise: " << norm << endl
* << "filter funct: " << filt << endl
* << "unknown -a : " << unknown_a << endl
* << endl
* << "Positional arguments :" << endl
* << cli_args.positional_arguments() << endl
* ;
*
* // Dump the whole object, this shows a good overview what is defined,
* // what arguments have been parsed and what errors we have (debugging
* // feature).
* cout << cli_args << endl;
*
* return 0;
* }
*
*
* -----------------------------------------------------------------------------
* +++ BSD license header +++
* Copyright (c) 2008-2014, Stefan Wilhelm (stfwi, <cerbero s@atwilly s.de>)
* All rights reserved.
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met: (1) Redistributions
* of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer. (2) Redistributions in binary form must reproduce
* the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
* (3) Neither the name of atwillys.de nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* --------------------------------------------------------------------------
*/
#ifndef SW_CLI_ARGS_HH
#define SW_CLI_ARGS_HH
// <editor-fold desc="preprocessor" defaultstate="collapsed">
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <vector>
#include <cmath>
#include <cctype>
#include <limits>
#include <algorithm>
#ifdef __MSC_VER
namespace { namespace std { template <typename T> bool isnan(T d) { return !!_isnan(d); } } }
#endif
#define NaN (std::numeric_limits<double>::quiet_NaN())
#ifdef DEBUG
#include <cassert>
#define dbg_assert(X) assert(X)
#define dbg(X) std::cerr << X << std::endl;
#else
#define dbg(X)
#define dbg_assert(X)
#endif
// </editor-fold>
// <editor-fold desc="aux functions" defaultstate="collapsed">
namespace sw { namespace detail {
template <typename str_t=std::string> class basic_cli_args;
/**
* Auxiliary strict string-to-double conversion. Returns NaN on any inconsistancies.
* @param const str_t &s
* @return double
*/
template <typename str_t>
double basic_cli_args_s2d(const str_t &s)
{
if(s.length()<1 || s.length() > 62) return NaN;
double d;
char cs[64];
for(unsigned i=0; i<s.length(); ++i) cs[i] = (char)s[i];
cs[s.length()] = '\0';
char *cp[1] = {cs};
d = ::strtod(cs, cp);
if((*cp) != cs + s.length()) d = NaN;
return d;
}
/**
* String soft wrap auxiliary function
* @param const str_t &s, unsigned width=80
* @param const str_t &wrap_after_chars=".,:;/?!@\\-+=*%^"
* @param const str_t &indent_ctl_chars="-*"
* @return str_t
*/
template <typename str_t>
str_t basic_cli_args_softwrap(
const str_t &s, unsigned width=80,
const str_t &wrap_after_chars=".,;?!@\\",
const str_t &indent_ctl_chars="-*")
{
if(width < 8 || s.empty()) return s;
str_t o;
typename str_t::size_type sz=0, indent=0, i, j, end = s.length();
const typename str_t::value_type nl = '\n';
o.reserve(s.length() + s.length()/width + 1);
// Indent of first line
for(indent=0, i=0; i < end; ++i) {
if(!::isspace(s[i]) && indent_ctl_chars.find_first_of(s[i]) == str_t::npos) break;
++indent;
}
indent %= width;
for(i=0; i < end; ++i) {
if(s[i]=='\r') continue;
if(s[i]=='\n') {
// Remove teiling spaces in the output line
while(o.length() > 0 && std::isspace(o[o.length()-1]) &&
o[o.length()-1] != nl) o.erase(o.length()-1);
o.push_back(nl);
// Indent for next line
for(indent= 0, j= i+1; j < end; ++j) {
if(!std::isspace(s[j]) && indent_ctl_chars.find_first_of(s[j])==str_t::npos) {
break;
}
indent++;
}
indent %= width;
sz = indent;
} else {
o.push_back(s[i]);
if(++sz >= width) {
// Find last space decrementing it and cropping o.
int p = o.length()-1;
int n = width;
j = i;
while(n >= 0 && p >= 0 && j>0 && !std::isspace(o[p]) &&
wrap_after_chars.find_first_of(o[p]) == str_t::npos) {
--j; --p; --n;
}
if(wrap_after_chars.find_first_of(o[p]) != str_t::npos) {
++p; ++j; ++n;
};
if(n > 0) { i = j; o.erase(p, str_t::npos); } // Soft wrap
sz = indent;
o.push_back(nl);
o += str_t(indent, ' ');
}
}
}
while(o.length()>0 && std::isspace(o[o.length()-1])) {
// Remove tailing whitesoaces
o.erase(o.length()-1, str_t::npos);
}
return o;
}
// Will be undefined at end of file
#define ostream_t std::basic_ostream<typename str_t::value_type>
}}
// </editor-fold>
// <editor-fold desc="basic_cli_arg_def" defaultstate="collapsed">
namespace sw { namespace detail {
/**
* Argument definition
*/
template <typename str_t>
class basic_cli_arg_def
{
public:
typedef typename str_t::value_type char_t;
/**
* Standard constructor. Initialises the object so that it is equivalent to
* ndef.
*/
inline basic_cli_arg_def() : t_('!'), s_(), l_(), vm_(), vs_(), h_()
{ ; }
/**
* Detailed assignment constructor
* @param const str_t & short_opt
* @param const str_t & long_opt=""
* @param str_t argument_type=""
* @param const str_t & default_missing=""
* @param const str_t & default_no_value=""
* @param const str_t help=""
*/
inline basic_cli_arg_def(
const str_t & short_opt,
const str_t & long_opt="",
str_t argument_type="",
const str_t & default_missing="",
const str_t & default_no_value="",
const str_t help=""
) : t_('b'), s_(short_opt), l_(long_opt), vm_(default_missing),
vs_(default_no_value), h_(help)
{
if(argument_type.empty()) {
t_ = 'b';
} else {
argument_type = " " + argument_type + " ";
if(str_t(" b bool flag ").find(argument_type) != str_t::npos) {
t_ = 'b';
} else if(str_t(" s string ").find(argument_type) != str_t::npos) {
t_ = 's';
} else if(str_t(" f file ").find(argument_type) != str_t::npos) {
t_ = 'f';
} else if(str_t(" n number float double ").find(argument_type) != str_t::npos) {
t_ = 'n';
} else if(str_t(" i int integer ").find(argument_type) != str_t::npos) {
t_ = 'i';
} else {
t_ = '!';
}
}
}
virtual ~basic_cli_arg_def()
{ ; }
/**
* Returns the argument type specification character, one of
* 's','n','i','b','f','!'.
* @return char_t
*/
inline char_t type() const throw()
{ return t_; }
/**
* Returns the short option of the definition, "" if not set.
* @return const str_t &
*/
inline const str_t & short_opt() const
{ return s_; }
/**
* Returns the long option of the definition, "" if not set.
* @return const str_t &
*/
inline const str_t & long_opt() const
{ return l_; }
/**
* Returns the value that shall be used if the option is omitted, "" if not
* set.
* @return const str_t &
*/
inline const str_t & value_if_omitted() const
{ return vm_; }
/**
* Returns the value that shall be used if the option set but has no value,
* "" if not set.
* @return const str_t &
*/
inline const str_t & value_if_no_value() const
{ return vs_; }
/**
* Returns the help text for this option.
* @return const str_t &
*/
inline const str_t & help() const
{ return h_; }
/**
* Returns a string representation for the option type.
* @return str_t
*/
inline str_t type_name() const
{ return (
t_=='b' ? "boolean" : (
t_=='s' ? "string" : (
t_=='f' ? "file" : (
t_=='n' ? "number" : (
t_=='i' ? "integer" : (
"(INVALID TYPE SPECIFIED)"
))))));
}
/**
* True if this option is undefined/unknown
* @return bool
*/
inline bool is_ndef() const
{ return s_.empty() && l_.empty(); }
/**
* True if the option cannot be omitted and requires a value.
* [ empty "omitted" default, empty "set" default ]
* @return bool
*/
inline bool is_required() const
{ return vm_.empty() && vs_.empty(); }
/**
* True if this option can be omitted, but if it is set there must be a given
* value as well.
* [ existing "omitted" default, empty "set" default ]
* @return bool
*/
inline bool is_value_required() const
{ return !vm_.empty() && vs_.empty(); }
/**
* True if this option can be omitted, and the value is optional with a
* defined default.
* [ existing "omitted" default, existing "set" default ]
* @return bool
*/
inline bool is_optional() const
{ return !vm_.empty() && !vs_.empty(); }
/**
* True if the option can be omitted and does not accept accept a value.
* [ empty "omitted" default, existing "set" default ]
* @return bool
*/
inline bool is_flag() const
{ return vm_.empty() && !vs_.empty(); }
/**
* Returns true if the definition of type string or file, which is implicitly string, too.
* @return bool
*/
inline bool is_string() const
{ return t_ == 's' || t_ == 'f'; }
/**
* Returns true if the definition is of type file.
* @return bool
*/
inline bool is_file() const
{ return t_ == 'f'; }
/**
* Returns true if the definition of type number.
* @return bool
*/
inline bool is_float() const
{ return t_ == 'n'; }
/**
* Returns true if the definition of type integer.
* @return bool
*/
inline bool is_int() const
{ return t_ == 'i'; }
/**
* Returns true if the definition is boolean.
* @return bool
*/
inline bool is_bool() const
{ return t_ == 'b'; }
/**
* True if short opt matches OR long opt matches
* @param def
* @return bool
*/
inline bool operator == (const basic_cli_arg_def& d) const
{ return d.s_==s_ || d.l_==l_; }
/**
* False if short opt matches OR long opt matches
* @param def
* @return bool
*/
inline bool operator != (const basic_cli_arg_def& d) const
{ return d.s_!=s_ && d.l_!=l_; }
public:
/**
* ostream / dump settings
*/
static unsigned line_width, help_indent, typehint_indent, line_indent;
/**
* "Undefined option" object.
* @var static const basic_cli_arg_def
*/
static const basic_cli_arg_def ndef;
protected:
char_t t_; // type
str_t s_, l_; // short, long,
str_t vm_, vs_; // value (if missing), value (if set without value)
str_t h_; // help
};
/**
* Static variables
*/
template <typename str_t>
unsigned basic_cli_arg_def<str_t>::line_indent = 2;
template <typename str_t>
unsigned basic_cli_arg_def<str_t>::line_width = 80;
template <typename str_t>
unsigned basic_cli_arg_def<str_t>::help_indent = 3;
template <typename str_t>
unsigned basic_cli_arg_def<str_t>::typehint_indent = 4;
template <typename str_t>
const basic_cli_arg_def<str_t> basic_cli_arg_def<str_t>::ndef = basic_cli_arg_def<str_t>();
/**
* ostream for basic_cli_arg_def< >
* @param ostream& os
* @param basic_cli_arg_def<str_t> a
* @return ostream& os
*/
template <typename str_t>
inline ostream_t& operator <<
(ostream_t& os, basic_cli_arg_def<str_t> a)
{
if(a.is_ndef()) { os << "(NDEF)"; return os; }
if(a.short_opt().empty() && a.long_opt().empty()) return os;
str_t s;
unsigned line_indent = basic_cli_arg_def<str_t>::line_indent;
unsigned hint_indent = basic_cli_arg_def<str_t>::typehint_indent;
unsigned help_indent = basic_cli_arg_def<str_t>::help_indent;
unsigned line_width = basic_cli_arg_def<str_t>::line_width;
s = str_t(line_indent, ' ');
if(!a.short_opt().empty()) {
s += str_t("-") + a.short_opt() + (a.long_opt().empty() ? "" : ", ");
}
if(!a.long_opt().empty()) s += str_t("--") + a.long_opt();
if(s.length() < hint_indent) s += str_t(hint_indent-s.length(), ' ');
os << s << " (";
if(a.is_required()) {
os << a.type_name() << ", required";
} else if(a.is_flag()) {
os << "flag";
} else if(a.is_value_required()) {
os << "optional " << a.type_name();
} else {
os << "optional " << a.type_name() << ", default: "
<< ((a.type()=='s' || a.type()=='f') ? "'":"")
<< a.value_if_omitted()
<< ((a.type()=='s' || a.type()=='f') ? "'":"");
}
os << ")";
if(!a.help().empty()) {
os << std::endl << std::endl;
std::vector<str_t> lines;
typename str_t::size_type p;
str_t hlp = a.help();
if((p=hlp.find_last_not_of("\r\t\n ")) != str_t::npos) hlp = hlp.substr(0, p+1);
while(!hlp.empty()) {
if((p=hlp.find_first_of("\n")) == str_t::npos) {
lines.push_back(hlp);
hlp = "";
break;
} else {
str_t s = hlp.substr(0, p);
hlp = hlp.substr(p+1); // cannot be last char, see above
if((p=s.find_last_not_of("\r\t\n ")) != str_t::npos) s = s.substr(0, p+1);
lines.push_back(s);
}
}
for(unsigned k=0; k<lines.size(); k++) {
os << basic_cli_args_softwrap(str_t(line_indent+help_indent, ' ') +
lines[k],line_width) << std::endl;
}
}
return os;
}
}}
// </editor-fold>
// <editor-fold desc="basic_cli_arg_defs" defaultstate="collapsed">
namespace sw { namespace detail {
/**
* List class for definitions
*/
template <typename str_t>
class basic_cli_arg_defs : public std::vector<basic_cli_arg_def<str_t> >
{
public:
typedef basic_cli_arg_def<str_t> def_t;
typedef basic_cli_arg_defs my_t;
typedef typename std::vector<basic_cli_arg_def<str_t> > base_t;
typedef typename base_t::iterator iterator;
typedef typename base_t::const_iterator const_iterator;
typedef typename base_t::size_type size_type;
public:
basic_cli_arg_defs() : base_t()
{ ; }
explicit basic_cli_arg_defs(const base_t &v) : base_t(v)
{ ; }
basic_cli_arg_defs(const my_t &v) : base_t(v)
{ ; }
virtual ~basic_cli_arg_defs()
{ ; }
public:
/**
* Finds a short or long option definition by its key. No leading "-" / "--".
* @param const str_t& key
* @return const def_t&
*/
inline const def_t& operator [] (const str_t& key) const
{ return find(key); }
/**
* Returns definition by index or ndef if index exceeds size.
* @param unsigned i
* @return const def_t&
*/
inline const def_t& operator [] (unsigned i) const
{ return i>=base_t::size() ? def_t::ndef : base_t::operator[](i); }
/**
* Finds a short or long option definition by its key. No leading "-" / "--".
* @param const str_t& key
* @return const def_t&
*/
inline const def_t& find(const str_t& key) const
{
for(const_iterator it=this->begin(); it!=this->end(); ++it)
if((*it).short_opt() == key || (*it).long_opt() == key) return *it;
return def_t::ndef;
}
/**
* Finds a short option definition by its key. No leading "-".
* @param const str_t& key
* @return const def_t&
*/
inline const def_t& find_short_opt(const str_t& key) const
{
for(const_iterator it=this->begin(); it!=this->end(); ++it)
if((*it).short_opt() == key) return *it;
return def_t::ndef;
}
/**
* Finds a long option definition by its key. No leading "--".
* @param const str_t& key
* @return const def_t&
*/
inline const def_t& find_long_opt(const str_t& key) const
{
for(const_iterator it=this->begin(); it!=this->end(); ++it)
if((*it).long_opt() == key) return *it;
return def_t::ndef;
}
};
template <typename str_t>
ostream_t& operator << (ostream_t& os, const basic_cli_arg_defs<str_t>& v)
{
typename basic_cli_arg_defs<str_t>::const_iterator it=v.begin();
while(it != v.end()) {
os << " " << *it;
if(++it!=v.end()) os << std::endl;
}
return os;
}
}}
// </editor-fold>
// <editor-fold desc="basic_cli_arg" defaultstate="collapsed">
namespace sw { namespace detail {
/**
* Argument
*/
template <typename str_t>
class basic_cli_arg
{
public:
typedef basic_cli_arg_def<str_t> def_t;
public:
/**
* Standard constructor. Creates the object so that it is equivalent to
* narg.
*/
inline basic_cli_arg() : def_(0), p_(npos), k_(), v_(), nv_(NaN), ok_(false)
{ ; }
/**
* Detailed assignment with existing definition
* @param int p
* @param str_t key
* @param str_t value
* @param const def_t &def
* @param bool ok=false
*/
inline basic_cli_arg(int p, str_t key, str_t value, const def_t &def) :
def_(&def), p_(p), k_(key), v_(value), nv_(NaN), ok_(false)
{ assign(value); }
/**
* Detailed assignment without existing definition (for unknown options)
* @param int p
* @param str_t key
* @param str_t value
*/
inline basic_cli_arg(int p, str_t key, str_t value) :
def_(0), p_(p), k_(key), v_(value), nv_(NaN), ok_(false)
{ assign(value); }
virtual ~basic_cli_arg()
{ ; }
/**
* Assignment, identical copy
* @param const basic_cli_arg& a
*/
inline basic_cli_arg& operator= (const basic_cli_arg& a)
{ def_=a.def_; p_=a.p_; k_=a.k_; v_=a.v_; ok_=a.ok_; nv_=a.nv_; return *this; }
public:
/**
* Returns the position in the program argument list where this option and
* its value has been retrieved from.
* @return unsigned
*/
inline int position() const
{ return p_; }
/**
* Returns the key, t.m. the exact option name string from the command line
* without leading "-". This key is empty for positional arguments.
* @return const str_t &
*/
inline const str_t & key() const
{ return k_; }
/**
* Returns the string value of this option or positional argument.
* @return const str_t &
*/
inline const str_t & value() const
{ return v_; }
/**
* Returns the definition that was determined while parsing this argument or
* def_t::ndef constant if no definition was found ("Unknown option").
* @return const def_t &
*/
inline const def_t & definition() const
{ return (def_!=0) ? (*def_) : def_t::ndef; }
/**
* True if this argument is positional
* @return bool
*/
inline bool is_positional() const
{ return (!def_ || def_->is_ndef()) && k_.empty() && p_>=0; }
/**
* True if this argument does not belong to a defined option definition.
* ("Unknown option").
* @return bool
*/
inline bool is_undefined_option() const
{ return (!def_ || def_->is_ndef()) && !k_.empty() && p_>=0; }
/**
* True if this argument is narg, means not found in the given program
* argument list.
* @return bool
*/
inline bool is_narg() const
{ return p_<0 && v_.empty(); } // && (!def_||def_->is_ndef()) && k_.empty() &&
/**
* True if everything seems to be ok with this option: Definition exists, and
* the value that was parsed meets the requirements for this definition.
* @return bool
*/
inline bool is_ok() const
{ return ok_; }
/**
* Returns true if the definition says this argument is of type string OR if
* there is no definition for this key/option. Type file is implicitly type
* string as well.
* @return bool
*/
inline bool is_string() const
{ return def_ && (def_->type() == 's' || def_->type() == 'f'); }
/**
* Returns true if the definition says this argument is of type file
* @return bool
*/
inline bool is_file() const
{ return def_ && def_->type() == 'f'; }
/**
* Returns true if the definition exists and says this argument is a number.
* @return bool
*/
inline bool is_float() const
{ return def_ && def_->type() == 'n'; }
/**
* Returns true if the definition exists and says this argument is an integer.
* @return bool
*/
inline bool is_int() const
{ return def_ && def_->type() == 'i'; }
/**
* Returns true if the definition exists and says this argument boolean.
* @return bool
*/
inline bool is_bool() const
{ return def_ && def_->type() == 'b'; }
public:
/**
* Returns the numeric value (effectively int if type is int, {0,1} if bool,
* or a floating point number if the definition type is "number".
* @return double
*/
inline operator double () const
{ return nv_; }
/**
* Returns the string value if the argument. Independent of the type, this
* always reads the unmodified text how it appears in the given argument list.
* @return str_t
*/
inline operator str_t() const
{ return v_; }
/**
* Boolean comparison, return: NaN and 0 := false, other values := true.
* @param bool b
*/
inline bool operator == (bool b) const
{ return (!std::isnan(nv_) && nv_!=0) == b; }
/**
* Boolean comparison, return: NaN and 0 := true, other values := false.
* @param bool b
*/
inline bool operator != (bool b) const
{ return !(operator==(b)); }
/**
* Negation
* @return bool
*/
inline bool operator ! () const
{ return (operator==(false)); }
public:
/**
* Assigns the value and derives the numeric/boolean value depending on the
* type of (the existing) definition.
* @param const str_t &val
* @return void
*/
void assign(const str_t &val)
{
nv_ = basic_cli_args_s2d(v_);
ok_ = false;
if(p_<0) p_ = npos;
if(!def_) def_ = &def_t::ndef;
if(p_<0 && k_.empty()) {
// Either narg or negative positional --> not ok, set to narg
v_ = narg.value();
} else if(k_.empty()) {
// Positional
v_ = val;
ok_ = true;
} else if(def_->is_ndef() || def_->type() == 's' || def_->type() == 'f') {
// String, applies as well to unknown options
v_ = val;
ok_ = !def_->is_ndef();
} else {
if(val.empty() && def_->is_flag()) {
// The default for unset flags is 0 and ""
v_ = "";
nv_ = 0;
}
ok_ = !std::isnan(nv_);
switch(def_->type()) {
case 'b':
if(!ok_) {
str_t s = val;
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
if(s.empty()) {
} else if(str_t(",true,yes,on,enabled,an,ja,oui,si,tak,")
.find(str_t(",")+s+",") != str_t::npos) {
nv_ = 1; ok_ = true;
} else if(str_t(",false,no,off,disabled,aus,nein,non,nie,geen,")
.find(str_t(",")+s+",") != str_t::npos) {
nv_ = 0; ok_ = true;
}
if(!ok_ && def_->is_flag()) {
nv_ = 0; ok_ = true;
}
}
break;
case 'n':
break;
case 'i':
if(ok_) {
ok_ = std::abs(nv_-((long)nv_)) <= std::numeric_limits<double>::epsilon();
} else {
nv_ = (int) narg; // Precaution for type conversion problems with NaN
}
break;
default:
// Invalid type
ok_ = false;
nv_ = NaN;
}
}
}
public:
/**
* Represents a nonexisting argument.
* @return const basic_cli_arg&
*/
static const basic_cli_arg narg;
/**
* Position that represents a nonexisting argument.
* @return const basic_cli_arg&
*/
static const int npos;
protected:
const def_t *def_; // Definition
int p_; // Position
str_t k_; // Option seen (short, long or other if not in def list)
str_t v_; // Value
double nv_; // Numeric value (inclusively bool)
bool ok_; // Argument valid?
};
/**
* No argument
*/
template <typename str_t>
const basic_cli_arg<str_t> basic_cli_arg<str_t>::narg = basic_cli_arg<str_t>();
template <typename str_t>
const int basic_cli_arg<str_t>::npos = -1;
/**
* ostream <<
* @param ostream&
* @param const basic_cli_arg<str_t>& a
* @return ostream&
*/
template <typename str_t>
inline ostream_t& operator << (ostream_t& os, const basic_cli_arg<str_t>& a)
{
if(a.is_narg()) {
os << "(NARG)";
} else {
os << "{ pos: \"" << a.position() << "\""
<< ", key: \"" << a.key() << "\""
<< ", val: \"" << a.value() << "\""
<< ", def: \"";
if(a.is_positional()) {
os << "(positional)";
} else if(a.is_undefined_option()) {
os << "(undefined option)";
} else if(a.definition().is_ndef()) {
os << "(ndef)";
} else {
if(!a.definition().short_opt().empty()) {
os << "-" << a.definition().short_opt();
}
if(!a.definition().short_opt().empty() && !a.definition().long_opt().empty()) {
os << "/";
}
if(!a.definition().long_opt().empty()) {
os << "--" << a.definition().long_opt();
}
}
os << "\", ok: " << (a.is_ok() ? "true" : "false");
os << " }";
}
return os;
}
}}
// </editor-fold>
// <editor-fold desc="basic_cli_arg_vector" defaultstate="collapsed">
namespace sw { namespace detail {
/**
* List class for parsed arguments
*/
template <typename str_t>
class basic_cli_arg_vector : public std::vector<basic_cli_arg<str_t> >
{
public:
typedef typename std::vector<basic_cli_arg<str_t> > base_t;
typedef basic_cli_arg_vector my_t;
typedef basic_cli_arg_def<str_t> def_t;
typedef basic_cli_arg<str_t> arg_t;
typedef typename base_t::iterator iterator;
typedef typename base_t::const_iterator const_iterator;
typedef typename base_t::size_type size_type;
public:
basic_cli_arg_vector() : base_t()
{ ; }
explicit basic_cli_arg_vector(const base_t &v) : base_t(v)
{ ; }
basic_cli_arg_vector(const my_t &v) : base_t(v)
{ ; }
virtual ~basic_cli_arg_vector()
{ ; }
public:
/**
* Returns the first option matching the given `key` or arg_t::narg if
* no argument was found.
* @param const str_t & key
* @return arg_t
*/
const arg_t& find(const str_t& key) const
{
if(key.empty()) return arg_t::narg;
for(const_iterator it=this->begin(); it!=this->end(); ++it) {
if((*it).definition().short_opt() == key) return *it; // Defined short
if((*it).definition().long_opt() == key) return *it; // Defined long
if((*it).key() == key) return (*it); // No definition but specified in argv
}
return arg_t::narg;
}
/**
* Returns the first option matching the given `key` or arg_t::narg if
* no argument was found.
* @param const str_t & sopt
* @param const str_t & lopt
* @return arg_t
*/
const arg_t& find(const str_t& sopt, const str_t& lopt) const
{
if(sopt.empty() && lopt.empty()) return arg_t::narg;
for(const_iterator it=this->begin(); it!=this->end(); ++it) {
if(it->definition().short_opt() == sopt && !sopt.empty()) return *it; // Defined short
if(it->definition().long_opt() == lopt && !lopt.empty()) return *it; // Defined long
if((!it->key().empty()) && (it->key()==sopt || it->key()==lopt)) {
return (*it); // No definition but specified in argv
}
}
return arg_t::narg;
}
/**
* Returns the first option matching the given `key` or arg_t::narg if
* no argument was found.
* @param const str_t & key
* @return arg_t
*/
const arg_t& operator [] (const str_t& key) const
{ return find(key); }
/**
* Returns option with the index `index` or arg_t::narg if not existing.
* @param unsigned index
* @return arg_t
*/
const arg_t& operator [] (unsigned index) const
{
if(index >= base_t::size()) return arg_t::narg;
const arg_t& r = base_t::at(index);
// position() < 0 === added default value
return r.position() >= 0 ? r : arg_t::narg;
}
};
/**
* ostream <<
* @param ostream& os
* @param const basic_cli_arg_vector<str_t>& v
* @return ostream&
*/
template <typename str_t>
inline ostream_t& operator << (ostream_t& os, const basic_cli_arg_vector<str_t>& v)
{
os << "[" << std::endl;
typename basic_cli_arg_vector<str_t>::const_iterator it=v.begin();
while(it != v.end()) {
os << " " << *it;
os << (++it==v.end() ? "" : ",") << std::endl;
}
os << "]" << std::endl;
return os;
}
}}
// </editor-fold>
// <editor-fold desc="basic_cli_args" defaultstate="collapsed">
namespace sw { namespace detail {
/**
* CLI Args Main Class
*/
template <typename str_t>
class basic_cli_args
{
public:
/**
* Text character type
*/
typedef typename str_t::value_type char_t;
/**
* String vector type
*/
typedef typename std::vector<str_t> str_vect_t;
/**
* Argument definition class type
*/
typedef basic_cli_arg_def<str_t> def_t;
/**
* Argument definition list class type
*/
typedef basic_cli_arg_defs<str_t> defs_t;
/**
* Argument class type
*/
typedef basic_cli_arg<str_t> arg_t;
/**
* Argument list class type
*/
typedef basic_cli_arg_vector<str_t> args_t;
public:
/**
* Reference to the definition object representing "definition not
* found", "nonexisting definition" etc.
* @return const def_t&
*/
static const def_t& ndef;
/**
* Reference to the argument object representing "this is no argument",
* "argument not found" etc.
* @return arg_t&
*/
static const arg_t& narg;
/**
* The argument position that represents "no position"
* @return int
*/
static const int npos;
public:
/**
* Standard constructor
*/
inline basic_cli_args() : allow_unknown_opts_(false)
{ ; }
/**
* Constructor / assign argc/argv
* @param int argc
* @param char** argv
*/
inline basic_cli_args(int argc, char** argv) : allow_unknown_opts_(false)
{ assign(argc, (const char**)argv); }
/**
* Constructor / assign argc/argv
* @param int argc
* @param const char** argv
*/
inline basic_cli_args(int argc, const char** argv) : allow_unknown_opts_(false)
{ assign(argc, argv); }
virtual ~basic_cli_args()
{ ; }
public:
/**
* Returns the definitions
* @return const defs_t&
*/
inline const defs_t& definitions() const
{ return defs_; }
/**
* Returns the basename of the program.
* @return const str_t&
*/
inline const str_t& program_name() const
{ return app_; }
/**
* Returns the parsed arguments including internally added optional options
* that were omitted.
* @return const args_t&
*/
inline const args_t& arguments() const
{ return args_; }
/**
* Returns the list of errors
* @return str_vect_t
*/
inline const str_vect_t errors() const
{ return errs_; }
/**
* (Getter) True of unknown options don't raise errors.
* @return bool
*/
inline bool allow_unknown_options() const throw()
{ return allow_unknown_opts_; }
/**
* (Setter) Set to true of unknown options shall not raise errors. These
* options can also be fetched using operator[](str_t) and contain set values,
* but `is_ok()` will return false as type the definition is unclear.
* THIS METHOD MUST BE CALLED BEFORE `parse()`.
* @param bool allow
* @return basic_cli_args&
*/
inline basic_cli_args& allow_unknown_options(bool allow) throw()
{ allow_unknown_opts_ = allow; return *this; }
/**
* Returns true if there are errors.
* @return bool
*/
inline bool has_errors() const
{ return errs_.size() > 0; }
/**
* Returns the list of given positional arguments.
* @return args_t
*/
inline args_t positional_arguments() const
{
args_t r;
for(typename args_t::const_iterator it=args_.begin(); it!=args_.end(); ++it) {
if(it->is_positional()) r.push_back(*it);
}
return r;
}
public:
/**
* Resets the object
* @return basic_cli_args&
*/
inline basic_cli_args& clear()
{ app_=""; argv_.clear(); defs_.clear(); args_.clear(); errs_.clear(); }
public:
/**
* Transfers the main() arguments into the corresponding instance variables.
* @param int argc
* @param const char** argv
* @return basic_cli_args&
*/
basic_cli_args& assign(int argc, const char** argv)
{
argv_.clear(); app_.clear();
if(argc<0 || !argv || !argv[0]) return *this;
app_ = argv[0];
while(!app_.empty() && ((app_[app_.length()-1]=='/') || (app_[app_.length()-1]=='\\'))) {
app_.resize(app_.length()-1);
}
typename str_t::size_type p =app_.find_last_of("\\/");
if(p != str_t::npos) app_ = app_.substr(p+1);
for(int i=1; i<argc && argv[i]; ++i) argv_.push_back(argv[i]);
return *this;
}
/**
* Transfers the main() arguments into the corresponding instance variables.
* @param int argc
* @param const char** argv
* @return basic_cli_args&
*/
inline basic_cli_args& operator () (int argc, char** argv)
{ return assign(argc, (const char**)argv); }
/**
* Transfers the main() arguments into the corresponding instance variables.
* @param int argc
* @param const char** argv
* @return basic_cli_args&
*/
inline basic_cli_args& operator () (int argc, const char** argv)
{ return assign(argc, argv); }
/**
* Returns a parsed argument or def_t::ndef if not found.
* @param const str_t & key
* @return const arg_t&
*/
inline const arg_t& operator [] (const str_t& key) const
{ return args_.find(key); }
/**
* Returns the argument with the index `index` or narg if exceeded.
* @param unsigned index
* @return const arg_t&
*/
inline const arg_t& operator [] (unsigned index) const
{ return index>=args_.size() ? arg_t::narg : args_[index]; }
/**
* Add an argument definition
* @param const str_t& short_opt
* @param const str_t& long_opt = ""
* @param const str_t& argument_type=""
* @param const str_t & default_if_missing="",
* @param const str_t & default_if_no_value="",
* @param str_t help = ""
* @return basic_cli_args&
*/
inline basic_cli_args& operator () (
const str_t& short_opt,
const str_t& long_opt="",
const str_t& argument_type="",
const str_t & default_if_missing="",
const str_t & default_if_no_value="",
const str_t& help=""
)
{
defs_.push_back(def_t(short_opt, long_opt, argument_type, default_if_missing,
default_if_no_value, help)); return *this;
}
/**
* Parses the given arguments using the specified definitions
* @return basic_cli_args&
*/
basic_cli_args& parse() {
// (1) Parse to args_
typename str_vect_t::const_iterator it = argv_.begin();
typename str_t::size_type p;
int pos = 0;
str_t key, val;
while(it != argv_.end()) {
str_t arg = *it; ++it;
key = val = "";
const def_t* def = &def_t::ndef;
if(arg.empty() || arg.find('-') != 0 || arg.find("---") == 0 || arg=="-"
|| arg=="--=" || arg=="-=") {
// Means: empty ('' / "") input given or something that cannot be an option
// ("-" etc).
// This is positional then.
args_.push_back(arg_t(pos++, "", arg));
continue;
} else if(arg == "--") {
// No more options, please
while(it != argv_.end()) {
args_.push_back(arg_t(pos++, "", *it)); ++it;
}
break;
} else if(arg.find("--") == 0) {
// Long option, non empty key
arg = arg.substr(2);
if(((p=arg.find('=')) != str_t::npos)) {
// --key='val', --key=
key = arg.substr(0, p);
val = p<arg.length()-1 ? arg.substr(p+1) : "";
} else {
// --key OR --key 'val'
key = arg;
val = (it!=argv_.end()) ? (*it) : "";
}
if((def=&defs_.find_long_opt(key))->is_ndef()) {
// No definition for this, but it really looks like an option, we can't
// take it as positional argument.
args_.push_back(arg_t(pos++, key, "", def_t::ndef));
} else if(def->is_value_required() || def->is_required()) {
// No "default if missing" defined --> required, the argv[i+1] must
// be the value if no "=" contained in the actual arg. (Even if this
// argv[i+1] starts with an "-", or "--").
args_.push_back(arg_t(pos++, key, val, *def));
if(p==str_t::npos && it!=argv_.end()) ++it; // Forward for --key 'val'
} else if(p!=str_t::npos) {
// Defined option, explicitly set with "=", if the string after "=" is
// empty it is assumed it should be empty and no default value is used.
// Flag checked later, optional value covered here.
args_.push_back(arg_t(pos++, key, val, *def));
} else {
// Last non ambiguous case left: No "=" and a "default if no value"
// defined.
args_.push_back(arg_t(pos++, key, def->value_if_no_value(), *def)); // --key
}
} else if(std::isdigit(arg[1])) {
// Numbers are positional arguments, except there is a short flag option
// like -9 or -0
if(arg.length()==2 && (def=&defs_.find_short_opt(str_t(1,arg[1])))->is_flag()) {
args_.push_back(arg_t(pos++, str_t(1, arg[1]), def->value_if_no_value(), *def));
} else {
// Positional, double conversion will be done during argument object
// creation
args_.push_back(arg_t(pos++, "", arg));
}
} else {
// Short option, non empty key, means:
// Multi character options without explicit "=" value (at least not
// yet):
// E.g. -abck, -k val, -kVAL, -abckVAL, , -abck=VAL
// Where abc are flags
// Rules:
// - The first option with accepts a value uses the remainder of the
// argument as value.
// - All other options must be flags (missing and no value default
// defined).
// - If a value is required and the option is the last option, the
// next argv value is used as value.
// - All characters after "=" are used as value
arg = arg.substr(1);
while(!arg.empty()) {
str_t key = arg.substr(0,1);
arg = arg.length()==1 ? "" : arg.substr(1);
if(key == "=") { arg.clear(); break; } // Catch potential trouble
def = &defs_.find_short_opt(key);
if(arg.length()>0 && arg[0] == '=') {
// -k=value, all chars after "=" belong to the value, '-k=' is
// interpreted as given empty value.
// Note: Flag checks are done later
val = arg.length()<2 ? "" : arg.substr(1);
arg.clear(); // next arg
args_.push_back(arg_t(pos, key, val, *def)); // Can be NDEF as well
} else if(arg.length()==0) {
// Single character option (or character left) without explicit
// "=" value: E.g. -k, -k val
val = (it!=argv_.end()) ? (*it) : ""; // (potential value)
arg.clear(); // next arg
if(def->is_ndef()) {
// Unknown option. Next argv is NOT a value.
args_.push_back(arg_t(pos, key, "", *def));
} else if(def->is_value_required() || def->is_required()) {
// Single option character, next argv must be a value
// E.g. -k 'VAL'
args_.push_back(arg_t(pos, key, val, *def));
if(it!=argv_.end()) ++it;
} else {
// Value is optional/flag, means here it must be interpreted flag-like.
// (Next argv value could not be differed from a positional parameter).
args_.push_back(arg_t(pos, key, def->value_if_no_value(), *def));
}
} else if(def->is_ndef()) {
// Undefined option
args_.push_back(arg_t(pos, key, arg, *def));
arg.clear();
} else if(def->is_required() || def->is_value_required()) {
// More chars left in arg, actual arg requires a value.
// The rest of the string is the value
args_.push_back(arg_t(pos, key, arg, *def));
arg.clear();
} else if(def->is_flag()) {
// Flag set: use "no value" default. If omitted, the flag would
// read an empty value.
args_.push_back(arg_t(pos, key, def->value_if_no_value(), *def));
} else {
// Optional option with optional value. "Smart detection check"
// needed, more simple: If the next char is not a known flag, use the
// rest as value, otherwise use the default.
dbg_assert(def->is_optional());
const def_t& next_arg_def = defs_.find_short_opt(arg.substr(1, 1));
if(next_arg_def.is_ndef()) {
args_.push_back(arg_t(pos, key, arg, *def));
arg.clear();
} else {
args_.push_back(arg_t(pos, key, def->value_if_no_value(), *def));
}
}
}
pos++;
}
}
// Checks
for(typename defs_t::const_iterator it=defs_.begin(); it != defs_.end(); ++it) {
const def_t& def = *it;
const arg_t& arg = args_.find(def.short_opt(), def.long_opt());
if(arg.is_narg()) {
str_t k = def.short_opt().empty() ? def.long_opt() : def.short_opt();
if(def.is_required()) {
errs_.push_back(str_t("Missing option ")
+ (def.short_opt().empty() ? "" : (str_t("-") + def.short_opt()))
+ (def.short_opt().empty() || def.long_opt().empty() ? "" : "/")
+ (def.long_opt().empty() ? "" : (str_t("--") + def.long_opt()))
);
} else if(!(def.value_if_omitted().empty())) {
str_t v = def.value_if_omitted();
args_.push_back(arg_t(arg_t::npos, k,v, def));
} else if(def.is_flag()) {
args_.push_back(arg_t(arg_t::npos, k, "", def));
}
}
}
for(typename args_t::const_iterator it=args_.begin(); it != args_.end(); ++it) {
if(!it->is_ok()) {
if(it->is_undefined_option()) {
if(!allow_unknown_opts_) {
errs_.push_back(str_t("Unknown option '")
+ (it->key().length() > 1 ? "--" : "-") + it->key() + "'"
);
}
} else {
errs_.push_back(
str_t("Invalid option value '")
+ str_t(it->value())
+ str_t("' for ")
+ (it->definition().short_opt().empty() ? "" : (str_t("-") + it->definition().short_opt()))
+ (it->definition().short_opt().empty() || it->definition().long_opt().empty() ? "" : "/")
+ (it->definition().long_opt().empty() ? "" : (str_t("--") + it->definition().long_opt()))
);
}
}
}
return *this;
}
/**
* Checks the definitions and adds faults to the list of `errors()`.
* @return basic_cli_args&
*/
basic_cli_args& check_definitions()
{
#define defe(X) errs_.push_back(str_t("Option definition error (-") \
+ it->short_opt() + "/--" + it->long_opt() + "): " + X);
for(typename defs_t::const_iterator it=defs_.begin(); it!=defs_.end(); ++it) {
if(it->is_ndef()) {
defe("Option without short and long opt.");
continue;
}
if(it->short_opt().length()>1) {
defe("Short option with more than one character.");
}
if(it->long_opt().find_first_of("\t ") != str_t::npos) {
defe("Long option with whitespace.");
}
if(it->long_opt().length()>0 && it->long_opt()[0]=='-') {
defe("Don't use -- in definitions.");
}
double dm, ds;
switch(it->type()) {
case 's':
case 'f':
break;
case 'n':
case 'i':
case 'b':
dm = it->value_if_omitted().empty() ? 0 : basic_cli_args_s2d(it->value_if_omitted());
ds = it->value_if_no_value().empty() ? 0 : basic_cli_args_s2d(it->value_if_no_value());
switch(it->type()) {
case 'b':
if((dm!=0 && dm!=1) || (ds!=0 && ds!=1)) {
defe("Default value for bool is not '0' or '1'.");
}
break;
case 'n':
if(std::isnan(dm) || std::isnan(ds)) {
defe("Default value is no number.");
}
break;
case 'i':
if(std::isnan(dm) || std::isnan(ds) || (long)dm != dm || (long)ds != ds) {
defe("Default value is no integer.");
}
break;
}
break;
default:
defe("Unknown type specification character.");
}
}
return *this;
#undef defe
}
/**
* Prints errors, if any there.
* @param ostream &os = std::cerr
* @return basic_cli_args&
*/
basic_cli_args& print_errors(ostream_t& os=std::cerr)
{
if(errs_.empty()) return *this;
if(errs_.size()==1) {
os << "Error: " << errs_.front() << std::endl;
} else {
os << "Errors: - " << errs_.front();
typename str_vect_t::const_iterator it=errs_.begin();
while(++it!=errs_.end()) os << std::endl << " - " << *it;
os << std::endl << std::endl;
}
return *this;
}
/**
* Prints arguments help to the specified stream.
* @param ostream& where_to=std::cerr
* @return basic_cli_args&
*/
inline basic_cli_args& help_arguments(ostream_t& where_to=std::cerr)
{
for(size_t i=0; i<defs_.size(); ++i) where_to << defs_[i] << std::endl;
return *this;
}
/**
* Prints synopsis in the same order as they are defined.
* @param ostream& where_to=std::cerr
* @return basic_cli_args&
*/
basic_cli_args& help_synopsis(const str_t & positionals_help="",
ostream_t& where_to=std::cerr, bool wrap_text=true)
{
std::basic_stringstream<typename str_t::value_type> ss;
for(unsigned i=0; i<definitions().size(); ++i) {
const def_t& def = definitions()[i];
if(!def.is_required()) ss << "["; // [-a]/[--aa] for optionals and flags
if(def.short_opt().empty()) {
ss << "--" << def.long_opt();
} else {
ss << "-" << def.short_opt();
}
if(!def.is_flag()) {
if(!def.is_value_required() && !def.is_required()) ss << "[";
ss << "=";
ss << "<" << def.type_name() << ">";
if(!def.is_value_required() && !def.is_required()) ss << "]";
}
if(!def.is_required()) ss << "]";
ss << " ";
}
ss << positionals_help;
std::string s = ss.str();
if(wrap_text) {
unsigned w = program_name().length() + def_t::line_indent;
s = basic_cli_args_softwrap(str_t(w, ' ')+s, def_t::line_width);
s = s.substr(w);
}
where_to << str_t(def_t::line_indent, ' ') << program_name() << " " << s;
return *this;
}
/**
* Prints complete help
* @param const str_t& help_introduction=""
* @param const str_t version_author_copyright=""
* @param const str_t& positionals_help=""
* @param const str_t& return_values_help=""
* @param ostream& where_to=std::cerr
* @return basic_cli_args&
*/
basic_cli_args& help(
const str_t& help_introduction="",
const str_t version_author_copyright="",
const str_t& positionals_help="",
const str_t& return_values_help="",
ostream_t& where_to=std::cerr)
{
const typename str_t::value_type nl = '\n';
ostream_t& os = where_to;
unsigned l_in = def_t::line_indent;
unsigned l_wi = def_t::line_width;
os << "NAME" << nl << nl
<< str_t(l_in, ' ') << program_name() << nl << nl
<< "SYNOPSIS" << nl << nl;
help_synopsis(positionals_help, os);
if(!help_introduction.empty()) {
std::string txt(help_introduction);
if(def_t::line_indent > 0) {
str_t ind = str_t("\n") + str_t(l_in, ' ');
typename str_t::size_type p = 0;
typename std::string::size_type nnl=0;
for(typename std::string::const_iterator it=txt.begin(); it!=txt.end(); ++it) {
if(*it=='\n') nnl++;
}
txt.reserve((l_in+1) * nnl);
while((p=txt.find('\n', p+1)) != str_t::npos) { // start from 1 is ok
txt.replace(p, 1, ind);
p += l_in-1;
}
txt.insert(0, ind);
}
os << nl << nl << "DESCRIPTION";
os << nl << basic_cli_args_softwrap(txt, l_wi);
}
if(!arguments().empty()) {
os << nl << nl << "ARGUMENTS" << nl << nl;
help_arguments(os);
}
if(!return_values_help.empty()) {
str_t txt = return_values_help;
if(def_t::line_indent > 0) {
str_t ind = str_t("\n") + str_t(l_in, ' ');
typename str_t::size_type p = 0;
typename std::string::size_type nnl=0;
for(typename std::string::const_iterator it=txt.begin(); it!=txt.end(); ++it) {
if(*it=='\n') nnl++;
}
txt.reserve((l_in+1) * nnl);
while((p=txt.find('\n', p+1)) != str_t::npos) { // start from 1 is ok
txt.replace(p, 1, ind);
p += l_in-1;
}
txt.insert(0, ind);
}
os << "RETURN VALUES" << nl;
os << basic_cli_args_softwrap(txt, l_wi) << nl << nl;
}
if(!version_author_copyright.empty()) {
os << program_name() << " " << version_author_copyright << nl;
}
return *this;
}
/**
* Exits on error or -h/--help, printing correspondent information.
* @param const str_t& help_introduction=""
* @param const str_t version_author_copyright=""
* @param const str_t& positionals_help=""
* @param const str_t& return_values_help=""
* @param int exit_code=1
* @param ostream &os=std::cerr
* @return basic_cli_args&
*/
basic_cli_args& handle_help_and_errors(
const str_t& help_introduction="",
const str_t version_author_copyright="",
const str_t& positionals_help="",
const str_t & return_values_help="",
int exit_code=1,
ostream_t& os=std::cerr)
{
if((argv_.size() >= 1 && argv_[0] == "--help") || (argv_.size() == 1 && argv_[0] == "-h")
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
|| (argv_.size() == 1 && argv_[0] == "/?")
#endif
) {
help(help_introduction, version_author_copyright, positionals_help, return_values_help, os);
::exit(exit_code);
} else if(has_errors()) {
print_errors(os);
::exit(exit_code);
}
return *this;
}
protected:
bool allow_unknown_opts_;
str_t app_;
str_vect_t argv_, errs_;
defs_t defs_;
args_t args_;
};
/**
* ndef constant
*/
template <typename str_t>
const typename basic_cli_args<str_t>::def_t& basic_cli_args<str_t>::ndef
= basic_cli_args<str_t>::def_t::ndef;
/**
* narg constant
*/
template <typename str_t>
const typename basic_cli_args<str_t>::arg_t& basic_cli_args<str_t>::narg
= basic_cli_args<str_t>::arg_t::narg;
/**
* npos constant
*/
template <typename str_t>
const int basic_cli_args<str_t>::npos
= basic_cli_args<str_t>::arg_t::npos;
template <typename str_t>
ostream_t& operator << (ostream_t& os, basic_cli_args<str_t>& a)
{
os << "cli_args dump: {" << std::endl;
if(a.errors().empty()) a.check_definitions();
os << " program: \"" << a.program_name() << "\"," << std::endl;
os << " all_ok: " << (a.has_errors() ? "false," : "true,") << std::endl;
os << " allow unknown options: "
<< (a.allow_unknown_options() ? "true," : "false,") << std::endl;
os << " definitions:" << " [";
if(a.definitions().empty()) {
os << "]," << std::endl;
} else {
int li = basic_cli_arg_def<str_t>::line_indent;
basic_cli_arg_def<str_t>::line_indent = 4;
for(typename basic_cli_args<str_t>::defs_t::const_iterator it=a.definitions().begin();
it!=a.definitions().end(); ++it) {
os << std::endl << *it;
}
os << std::endl << " ]," << std::endl;
basic_cli_arg_def<str_t>::line_indent = li;
}
os << " arguments: [";
if(a.arguments().empty()) {
os << "]," << std::endl;
} else {
for(typename basic_cli_args<str_t>::args_t::const_iterator it=
a.arguments().begin(); it != a.arguments().end(); ++it) {
os << std::endl << " " << *it;
}
os << std::endl << " ]," << std::endl;
}
os << " errors: [";
if(a.errors().empty()) {
os << "]" << std::endl;
} else {
unsigned i;
for(i=0; i< a.errors().size()-1; ++i) {
os << std::endl << " \"" << a.errors()[i] << "\",";
}
os << std::endl << " \"" << a.errors()[i] << "\"";
os << std::endl << " ]" << std::endl;
}
os << "}" << std::endl;
return os;
}
}}
// </editor-fold>
// <editor-fold desc="specialisation" defaultstate="collapsed">
namespace sw {
/**
* Standard specialisation with std::string
* THAT'S THE ONE USUALLY USED
*/
typedef detail::basic_cli_args<std::string> cli_args_t;
cli_args_t cli_args;
}
// </editor-fold>
// <editor-fold desc="undefs" defaultstate="collapsed">
#undef ostream_t
#undef NaN
// </editor-fold>
#endif