Files
mrw-cxx/src/mrw/args.hxx

402 lines
15 KiB
C++
Raw Normal View History

2013-09-26 13:58:05 +00:00
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
2013-10-21 08:58:17 +00:00
#ifndef __MRW_ARGS__
#define __MRW_ARGS__
2013-09-26 13:58:05 +00:00
#include <mrw/checkcxx11.hxx>
#include <mrw/iomanip.hxx>
2013-09-26 13:58:05 +00:00
#include <vector>
#include <map>
#include <memory>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <numeric>
#include <cstdlib> // exit()
2013-09-26 13:58:05 +00:00
/** @page oldcompiler Workaround for old non C++11 compilers
... to be documented
@note Old compilers are automatically detected and the flag
@ref MRW__OLD_PRE11_COMPILER is set.
*/
2013-09-26 13:58:05 +00:00
2013-10-21 08:58:17 +00:00
// Add version information for @c what and @c ident
const std::string MRW_IDENT("$Id: " PACKAGE_NAME "-" PACKAGEPACKAGE_VERSION " $");
const std::string MRW_WHAT("#(@)" PACKAGE_NAME "-" PACKAGEPACKAGE_VERSION);
2013-10-21 08:58:17 +00:00
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
2013-09-26 13:58:05 +00:00
// bind and evaluate options
args::parse(argc, argv, "This is an example for argument processing.",
{
{"h", "help", "show this help", {args::help(), args::exit()}},
2013-10-21 08:58:17 +00:00
{"v", "version", "show version", {args::version(), 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)}},
});
2013-09-26 13:58:05 +00:00
// [...] now the variables are setup according to the user's choice
2013-09-26 13:58:05 +00:00
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)();
};
typedef std::shared_ptr<abstract_parameter> param_ptr;
template<typename T>
param_ptr param(T& t, const std::string& s = std::string()) {
return param_ptr(new args::parameter<T>(t, s));
2013-09-26 13:58:05 +00:00
}
struct decl {
typedef std::vector<param_ptr> param_list;
decl(std::string p1, std::string p2, std::string p3, param_list p4):
short_arg(p1), long_arg(p2), desc(p3), params(p4) {
}
std::string short_arg;
std::string long_arg;
std::string desc;
param_list params;
};
typedef std::map<std::string, decl::param_list> arg_map;
static arg_map& args() {
static arg_map a;
return a;
}
typedef std::vector<decl> list;
static list& arg_list() {
static list a;
return a;
}
void match(const std::string& arg, char**& a, char** max) {
#ifdef MRW__OLD_PRE11_COMPILER
arg_map::iterator it(args().find(arg));
2013-09-26 13:58:05 +00:00
#else
auto it(args().find(arg));
2013-09-26 13:58:05 +00:00
#endif
if (it==args().end())
throw std::runtime_error("unknown argument: "+std::string(*a));
#ifdef MRW__OLD_PRE11_COMPILER
for (decl::param_list::iterator it2(it->second.begin());
it2!=it->second.end(); ++it2)
2013-09-26 13:58:05 +00:00
#else
for (auto it2(it->second.begin()); it2!=it->second.end(); ++it2)
2013-09-26 13:58:05 +00:00
#endif
(*it2)->evaluate(a, max);
2013-09-26 13:58:05 +00:00
}
/// 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;
2013-09-26 13:58:05 +00:00
}
/// 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 desc a string describing what the program does,
used in @ref show_help
@param l list of options and parameters to be parsed
@param 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 MRW__OLD_PRE11_COMPILER
for (list::iterator it(l.begin()); it!=l.end(); ++it)
2013-09-26 13:58:05 +00:00
#else
for (auto it(l.begin()); it!=l.end(); ++it)
2013-09-26 13:58:05 +00:00
#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 MRW__OLD_PRE11_COMPILER
for (char** a(argv+1); a<argv+argc; ++a)
2013-09-26 13:58:05 +00:00
#else
for (auto a(argv+1); a<argv+argc; ++a)
2013-09-26 13:58:05 +00:00
#endif
{
std::string arg(*a);
if (arg.size()>1&&arg[0]=='-'&&arg[1]!='-') { // short argument
#ifdef MRW__OLD_PRE11_COMPILER
for (std::string::iterator it(arg.begin()+1); it!=arg.end(); ++it)
2013-09-26 13:58:05 +00:00
#else
for (auto it(arg.begin()+1); it!=arg.end(); ++it)
2013-09-26 13:58:05 +00:00
#endif
match(std::string(1, *it), a, argv+argc);
} else { // long argument or wrong argument
match(arg, a, argv+argc);
2013-09-26 13:58:05 +00:00
}
}
}
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",
unsigned int max_line=80, unsigned int indent=2,
unsigned int long_indent=4,
unsigned int option_len=16, unsigned 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
<<ssplit(max_line, indent)<<description(); //! @bug
std::cout<<std::endl<<std::endl
<<options_txt<<std::endl;
#ifdef MRW__OLD_PRE11_COMPILER
for (list::iterator arg(arg_list().begin()); arg!=arg_list().end(); ++arg)
2013-09-26 13:58:05 +00:00
#else
for (auto arg(arg_list().begin()); arg!=arg_list().end(); ++arg)
2013-09-26 13:58:05 +00:00
#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 MRW__OLD_PRE11_COMPILER
std::string a;
for (decl::param_list::iterator p(arg->params.begin());
p!=arg->params.end(); ++p) {
std::string def(**p);
a+=(*p)->type()+(def.size()?"="+def:"");
}
2013-09-26 13:58:05 +00:00
#else
std::string a(std::accumulate(arg->params.begin(), arg->params.end(),
std::string(),
[](const std::string& s,
param_ptr p)
-> std::string {
std::string def(*p);
return
s+p->type()+(def.size()?"="+def:"");
}));
2013-09-26 13:58:05 +00:00
#endif
std::cout<<std::endl<<" "
<<o<<std::setw(int(option_len-o.size()))<<' '
<<a;
if (arg->desc.size()>max_line-indent-option_len-param_len)
std::cout<<std::endl<<std::endl<<ssplit(max_line, long_indent)
<<arg->desc;
else
std::cout<<std::setw(int(param_len-a.size()))<<' '<<arg->desc;
std::cout<<std::endl;
}
if (returns().size()) {
std::cout<<std::endl<<returns_txt<<std::endl<<std::endl
<<ssplit(max_line, indent)<<returns();
std::cout<<std::endl;
}
2013-09-26 13:58:05 +00:00
}
2013-10-21 08:58:17 +00:00
/// @return version information
std::string version_text() {
# ifdef PACKAGEPACKAGE_VERSION
# ifdef PACKAGE_NAME
std::string v(": " PACKAGE_NAME "-" PACKAGEPACKAGE_VERSION);
2013-10-21 08:58:17 +00:00
# else
std::string v("-" PACKAGEPACKAGE_VERSION);
2013-10-21 08:58:17 +00:00
# endif
# else
# ifdef PACKAGE_NAME
std::string v(": " PACKAGE_NAME);
2013-10-21 08:58:17 +00:00
# else
std::string v;
# endif
# endif
return filename() + v;
}
void show_version() {
std::cout<<version_text()<<std::endl;
}
void help_no_arg() {
show_help();
}
void do_exit() {
exit(1);
}
param_ptr func(void(*fn)()) {
return param_ptr(new args::parameter<void(*)()>(fn));
}
param_ptr help() {
return func(args::help_no_arg);
}
2013-10-21 08:58:17 +00:00
param_ptr version() {
return func(args::show_version);
}
param_ptr exit() {
return func(args::do_exit);
2013-09-26 13:58:05 +00:00
}
2013-10-21 08:58:17 +00:00
/// Sets up an argument list containing help and version.
list defaults() {
#ifdef MRW__OLD_PRE11_COMPILER
list res;
decl::param_list h;
h.push_back(help());
h.push_back(exit());
decl::param_list v;
v.push_back(version());
v.push_back(exit());
res.push_back(decl("h", "help", "show help", h));
res.push_back(decl("v", "version", "show version", v));
return res;
#else // New C++ standard C++11 is great:
return list({
{"h", "help", "show help", {help(), exit()}},
{"v", "version", "show version", {version(), exit()}}
});
#endif
}
2013-09-26 13:58:05 +00:00
}
}
#endif