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

/*! @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