C++ Library containing a lot of needful things: Stack Trace, Command Line Parser, Resource Handling, Configuration Files, Unix Command Execution, Directories, Regular Expressions, Tokenizer, Function Trace, Standard Extensions.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
15 KiB
388 lines
15 KiB
/*! @file |
|
|
|
@id $Id$ |
|
*/ |
|
// 1 2 3 4 5 6 7 8 |
|
// 45678901234567890123456789012345678901234567890123456789012345678901234567890 |
|
#ifndef __ARGS__ |
|
#define __ARGS__ |
|
|
|
#include <mrw/iomanip.hxx> |
|
|
|
#include <vector> |
|
#include <map> |
|
#include <memory> |
|
#include <iostream> |
|
#include <iomanip> |
|
#include <sstream> |
|
#include <stdexcept> |
|
#include <numeric> |
|
#include <cstdlib> // exit() |
|
|
|
// check if code is compiled with a new C++11 compiler |
|
// otherwise there is a fallback wich makes everything much more compliacted |
|
#ifndef ARGS__OLD_PRE11_COMPILER |
|
#if __cplusplus < 201103L |
|
/// Code is compiled with an old non C++11 standard compliant compiler |
|
/** There are workarounds for old non C++11 compatible |
|
compilers. These workarounds are deprecated, but will remain until |
|
most compilers fully support C++11. So this workaround will be |
|
removed in future releases, when support for C++11 is more |
|
common. Only rely on this workaround, if you really have to. |
|
@see oldcompiler for details on usage */ |
|
#define ARGS__OLD_PRE11_COMPILER |
|
#warning You need a C++11 compliant compiler, on gcc use option -std=c++11 |
|
#warning emulating C++11 - this changes the way you use the library |
|
#warning this is deprecated and will be removed in future releases |
|
#warning refere to the library documentation for more details |
|
#endif |
|
#endif |
|
|
|
/** @page oldcompiler Workaround for old non C++11 compilers |
|
... to be documented |
|
@note Old compilers are automatically detected and the flag |
|
@refs ARGS__OLD_PRE11_COMPILER is set. |
|
*/ |
|
|
|
#ifdef ARGS__OLD_PRE11_COMPILER |
|
#include <boost/shared_ptr.hpp> |
|
namespace std { |
|
// there is no std::shared_ptr in pre C++11 compilers, so we use the |
|
// one from the boost library as a 1:1 replacement |
|
template <typename T> class shared_ptr: public boost::shared_ptr<T> { |
|
public: |
|
explicit shared_ptr(T* p): boost::shared_ptr<T>(p) {} |
|
}; |
|
}; |
|
#endif |
|
|
|
namespace mrw { |
|
/// Cool and easy evaluation of commandline arguments in C++11. |
|
/** Evaluating command line arguments is extremely easy with this library: |
|
@begincode |
|
void test_func(); // bound to option --test |
|
|
|
int main(int argc, char** argv) try { |
|
|
|
// option variables |
|
bool flag; // bound to option --flag |
|
int r(5); // bound to option --repeat |
|
std::string txt("Hello World"); // bound to option --text |
|
int x(0), y(0); // bound to option --coord |
|
|
|
// bind and evaluate options |
|
args::parse(argc, argv, "This is an example for argument processing.", |
|
{ |
|
{"h", "help", "show this help", {args::help(), args::exit()}}, |
|
{"r", "repeat", "number of repetitions", |
|
{args::param(r, "number")}}, |
|
{"f", "flag", "check a flag", {args::param(flag)}}, |
|
{"t", "text", "pass a text", {args::param(txt, "text")}}, |
|
{"c", "coord", "add some 2D coordinates", {args::param(x, "x"), |
|
args::param(y, "y")}}, |
|
{"", "test", "call test_func, no shortcut", |
|
{args::func(test_func)}}, |
|
}); |
|
|
|
// [...] now the variables are setup according to the user's choice |
|
|
|
return 0; |
|
} catch (const std::exception& x) { // error in commandline options |
|
std::cerr<<"**** ERROR: "<<x.what()<<std::endl; |
|
args::show_help(); // display help |
|
return 1; |
|
} |
|
@endcode |
|
@note This library requires C++11, but there is also a fallback |
|
for old compilers. But the calling syntax is much worse on |
|
non C++11 compliant compilers, so the use of a modern |
|
compiler is strongly recommended. On GNU GCC, you may have |
|
to add the commandline option @c -std=c++11 |
|
@note I suggest to take this library into the next C++ standard. */ |
|
namespace args { |
|
/// Parent for holding a reference to a parameter variable. |
|
/** This class ist used only as parent for instances of template |
|
parameters. It represents one parameter that is evaluated when |
|
a commandline option is given. The derived classes hold a |
|
reference to a variable that is set in method @ref evaluate when |
|
the specific commandline parameter is given. */ |
|
class abstract_parameter { |
|
public: |
|
/// Called when the user specifies the related commandline option. |
|
/** Method must be implemented according to the specific type in |
|
the child classes. */ |
|
virtual void evaluate(char**& a, char** max) = 0; |
|
/// Convert the actual value to a string. |
|
/** Defaults to empty string, can be overwritten in child |
|
classes. Used by the help function to show the default |
|
value. */ |
|
virtual operator std::string() { |
|
return std::string(); |
|
} |
|
virtual std::string type() { |
|
return std::string(); |
|
} |
|
}; |
|
// default implementation for all types |
|
template<typename T> class parameter: public abstract_parameter { |
|
public: |
|
parameter(T& ref, const std::string& s = std::string()): |
|
_ref(ref), _type(s) {} |
|
virtual void evaluate(char**& a, char** max) { |
|
if (max<a+1) |
|
throw std::runtime_error("missing parameter for: "+std::string(*a)); |
|
std::stringstream ss; |
|
ss<<*++a; |
|
ss>>_ref; |
|
} |
|
virtual operator std::string() { |
|
std::stringstream ss; |
|
ss<<_ref; |
|
return ss.str(); |
|
} |
|
virtual std::string type() { |
|
return _type.size()?" <"+_type+">":""; |
|
} |
|
private: |
|
T& _ref; |
|
std::string _type; |
|
}; |
|
// special case: boolean has no parameter |
|
template<> class parameter<bool>: public abstract_parameter { |
|
public: |
|
parameter(bool& ref, const std::string& = std::string()): |
|
_ref(ref=false) { |
|
} |
|
virtual void evaluate(char**&, char**) { |
|
_ref = true; |
|
} |
|
private: |
|
bool& _ref; |
|
}; |
|
// special case: string cannot easily be shifted |
|
template<typename T> class parameter<std::basic_string<T> >: |
|
public abstract_parameter { |
|
public: |
|
parameter(std::basic_string<T>& ref, |
|
const std::string& s = std::string()): |
|
_ref(ref), _type(s) {} |
|
virtual void evaluate(char**& a, char** max) { |
|
if (max<a+1) |
|
throw std::runtime_error("missing parameter for: "+std::string(*a)); |
|
std::stringstream ss; |
|
ss<<*++a; |
|
_ref = std::basic_string<T>(std::istreambuf_iterator<T>(ss), |
|
std::istreambuf_iterator<T>()); |
|
} |
|
virtual operator std::string() { |
|
std::stringstream ss; |
|
ss<<_ref; |
|
return ss.str(); |
|
} |
|
virtual std::string type() { |
|
return _type.size()?" <"+_type+">":""; |
|
} |
|
private: |
|
std::basic_string<T>& _ref; |
|
std::string _type; |
|
}; |
|
// special case: if a function is called |
|
template<> class parameter<void(*)()>: public abstract_parameter { |
|
public: |
|
parameter(void(*ref)(), const std::string& = std::string()): |
|
_ref(ref) { |
|
} |
|
virtual void evaluate(char**&, char**) { |
|
_ref(); |
|
} |
|
private: |
|
void(*_ref)(); |
|
}; |
|
template<typename T> |
|
std::shared_ptr<abstract_parameter> param |
|
(T& t, const std::string& s = std::string()) { |
|
return std::shared_ptr<abstract_parameter>(new args::parameter<T>(t, s)); |
|
} |
|
struct declaration { |
|
#ifdef ARGS__OLD_PRE11_COMPILER |
|
declaration(std::string p1, std::string p2, std::string p3, |
|
std::vector<std::shared_ptr<abstract_parameter> > p4): |
|
short_arg(p1), long_arg(p2), desc(p3), params(p4) { |
|
} |
|
#endif |
|
std::string short_arg; |
|
std::string long_arg; |
|
std::string desc; |
|
std::vector<std::shared_ptr<abstract_parameter> > params; |
|
}; |
|
typedef std::map<std::string, |
|
std::vector<std::shared_ptr<abstract_parameter> > > |
|
arg_map; |
|
static arg_map& args() { |
|
static arg_map a; |
|
return a; |
|
} |
|
typedef std::vector<declaration> list; |
|
static list& arg_list() { |
|
static list a; |
|
return a; |
|
} |
|
void match(const std::string& arg, char**& a, char** max) { |
|
#ifdef ARGS__OLD_PRE11_COMPILER |
|
arg_map::iterator it(args().find(arg)); |
|
#else |
|
auto it(args().find(arg)); |
|
#endif |
|
if (it==args().end()) |
|
throw std::runtime_error("unknown argument: "+std::string(*a)); |
|
#ifdef ARGS__OLD_PRE11_COMPILER |
|
for (std::vector<std::shared_ptr<abstract_parameter> >::iterator |
|
it2(it->second.begin()); it2!=it->second.end(); ++it2) |
|
#else |
|
for (auto it2(it->second.begin()); it2!=it->second.end(); ++it2) |
|
#endif |
|
(*it2)->evaluate(a, max); |
|
} |
|
/// Filename as passed in argv[0]. |
|
/** Used in the help display. |
|
@note This function emulates a global variable using parts of a |
|
singleton pattern. */ |
|
static std::string filename(const std::string& arg0 = std::string()) { |
|
static std::string file(arg0); |
|
return file; |
|
} |
|
/// Description of the program. |
|
/** Used in the help display. |
|
@note This function emulates a global variable using parts of a |
|
singleton pattern. */ |
|
static std::string description(const std::string& desc = std::string()) { |
|
static std::string d(desc); |
|
return d; |
|
} |
|
/// Return values of the program. |
|
/** Used in the help display. |
|
@note This function emulates a global variable using parts of a |
|
singleton pattern. */ |
|
static std::string returns(const std::string& ret = std::string()) { |
|
static std::string r(ret); |
|
return r; |
|
} |
|
/// Initialize all parameters according to the commandline options. |
|
/** Sets up the parser from the @ref list of parameters |
|
@param argc the argument count as given in C++ @c main function |
|
@param argv the array of arguments as given in C++ @c main function |
|
@param descr a string describing what the program does, |
|
used in @ref show_help |
|
@param l list of options and parameters to be parsed |
|
@raram ret documentation of the return values of the program */ |
|
static void parse(int argc, char** argv, const std::string& desc, list l, |
|
const std::string& ret = std::string()) { |
|
filename(argv[0]); // store filename for later use in help |
|
description(desc); // store program description for later use in help |
|
returns(ret); // store return documentation for later use in help |
|
arg_list() = l; // store options and parameters for later use in help |
|
// setup the argument mapping table |
|
#ifdef ARGS__OLD_PRE11_COMPILER |
|
for (list::iterator it(l.begin()); it!=l.end(); ++it) |
|
#else |
|
for (auto it(l.begin()); it!=l.end(); ++it) |
|
#endif |
|
{ |
|
if (it->short_arg.size()==1) args()[it->short_arg] = it->params; |
|
if (it->long_arg.size()) args()["--"+it->long_arg] = it->params; |
|
} |
|
// parse commandline and evaluate the arguments |
|
#ifdef ARGS__OLD_PRE11_COMPILER |
|
for (char** a(argv+1); a<argv+argc; ++a) |
|
#else |
|
for (auto a(argv+1); a<argv+argc; ++a) |
|
#endif |
|
{ |
|
std::string arg(*a); |
|
if (arg.size()>1&&arg[0]=='-'&&arg[1]!='-') { // short argument |
|
#ifdef ARGS__OLD_PRE11_COMPILER |
|
for (std::string::iterator it(arg.begin()+1); it!=arg.end(); ++it) |
|
#else |
|
for (auto it(arg.begin()+1); it!=arg.end(); ++it) |
|
#endif |
|
match(std::string(1, *it), a, argv+argc); |
|
} else { // long argument or wrong argument |
|
match(arg, a, argv+argc); |
|
} |
|
} |
|
} |
|
void show_help(const std::string& synopsis_txt="SYNOPSIS", |
|
const std::string& description_txt="DESCRIPTION", |
|
const std::string& options_txt="OPTIONS", |
|
const std::string& returns_txt="RETURNS", |
|
int max_line=80, int indent=2, int long_indent=4, |
|
int option_len=16, int param_len=21) { |
|
std::cout<<synopsis_txt<<std::endl<<std::endl |
|
<<" "<<filename()<<" ["<<options_txt<<"]"<<std::endl<<std::endl |
|
<<description_txt<<std::endl<<std::endl |
|
<<split(max_line, indent)<<description(); //! @bug |
|
std::cout<<std::endl<<std::endl |
|
<<options_txt<<std::endl; |
|
#ifdef ARGS__OLD_PRE11_COMPILER |
|
for (list::iterator arg(arg_list().begin()); arg!=arg_list().end(); ++arg) |
|
#else |
|
for (auto arg(arg_list().begin()); arg!=arg_list().end(); ++arg) |
|
#endif |
|
{ |
|
std::string o((arg->short_arg.size()?"-"+arg->short_arg:"")+ |
|
(arg->short_arg.size()&&arg->long_arg.size()?", ":"")+ |
|
(arg->long_arg.size()?"--"+arg->long_arg:"")); |
|
#ifdef ARGS__OLD_PRE11_COMPILER |
|
std::string a; |
|
for (std::vector<std::shared_ptr<abstract_parameter> >::iterator |
|
p(arg->params.begin()); |
|
p!=arg->params.end(); ++p) { |
|
std::string def(**p); |
|
a+=(*p)->type()+(def.size()?"="+def:""); |
|
} |
|
#else |
|
std::string a(std::accumulate(arg->params.begin(), arg->params.end(), |
|
std::string(), |
|
[](const std::string& s, |
|
std::shared_ptr<abstract_parameter> p) |
|
-> std::string { |
|
std::string def(**p); |
|
return s+p->type()+(def.size()?"="+def:""); |
|
})); |
|
#endif |
|
std::cout<<std::endl<<" " |
|
<<o<<std::setw(option_len-o.size())<<' ' |
|
<<a; |
|
if (arg->desc.size()>max_line-indent-option_len-param_len) |
|
std::cout<<std::endl<<std::endl<<split(max_line, long_indent) |
|
<<arg->desc; |
|
else |
|
std::cout<<std::setw(param_len-a.size())<<' '<<arg->desc; |
|
std::cout<<std::endl; |
|
} |
|
if (returns().size()) { |
|
std::cout<<std::endl<<returns_txt<<std::endl<<std::endl |
|
<<split(max_line, indent)<<returns(); |
|
std::cout<<std::endl; |
|
} |
|
} |
|
void help_no_arg() { |
|
show_help(); |
|
} |
|
void do_exit() { |
|
exit(1); |
|
} |
|
std::shared_ptr<args::abstract_parameter> func(void(*fn)()) { |
|
return |
|
std::shared_ptr<args::abstract_parameter> |
|
(new args::parameter<void(*)()>(fn)); |
|
} |
|
std::shared_ptr<args::abstract_parameter> help() { |
|
return func(args::help_no_arg); |
|
} |
|
std::shared_ptr<args::abstract_parameter> exit() { |
|
return func(args::do_exit); |
|
} |
|
} |
|
} |
|
#endif
|
|
|