new library for arguments; refs #6
parent
c27a11b2ce
commit
904740c518
4 changed files with 448 additions and 8 deletions
@ -0,0 +1,438 @@ |
||||
/*! @file |
||||
|
||||
@id $Id$ |
||||
*/ |
||||
// 1 2 3 4 5 6 7 8 |
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890 |
||||
#ifndef __ARGS__ |
||||
#define __ARGS__ |
||||
|
||||
#include <vector> |
||||
#include <map> |
||||
#include <memory> |
||||
#include <iostream> |
||||
#include <iomanip> |
||||
#include <sstream> |
||||
#include <stdexcept> |
||||
#include <numeric> |
||||
#include <cstdlib> |
||||
|
||||
// check if code is compiled with a new C++11 compiler |
||||
// otherwise there is a fallback wich makes everything much more compliacted |
||||
#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 |
||||
|
||||
#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 |
||||
|
||||
/** @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. |
||||
*/ |
||||
|
||||
/// 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::init(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 { |
||||
#ifndef ARGS__CPLUSPLUS11 |
||||
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) { |
||||
#ifndef ARGS__CPLUSPLUS11 |
||||
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)); |
||||
#ifndef ARGS__CPLUSPLUS11 |
||||
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 init(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 values description for later use in help |
||||
arg_list() = l; // store options and parameters for later use in help |
||||
// setup the argument mapping table |
||||
#ifndef ARGS__CPLUSPLUS11 |
||||
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 |
||||
#ifndef ARGS__CPLUSPLUS11 |
||||
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 |
||||
#ifndef ARGS__CPLUSPLUS11 |
||||
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); |
||||
} |
||||
} |
||||
} |
||||
//! IO-Manipulator to split long lines into several shorter lines. |
||||
template <class _CharT = char, class _Traits = std::char_traits<_CharT> > |
||||
class basic_split /*: public std::basic_ostream<_CharT, _Traits>*/ { |
||||
public: |
||||
basic_split(int width=80, int indent=0, char fill=' '): |
||||
_width(width), _indent(indent), _fill(fill), _os(0) { |
||||
if (_width<_indent) |
||||
#ifndef ARGS__CPLUSPLUS11 |
||||
throw std::runtime_error(((std::stringstream&) |
||||
(std::stringstream() |
||||
<<"wrong use of split: width is "<<_width |
||||
<<" but should be larger than indent," |
||||
<<" which is "<<_indent)).str()); |
||||
#else |
||||
throw std::runtime_error("wrong use of split: width is "+ |
||||
std::to_string(_width)+ |
||||
" but should be larger than indent," |
||||
" which is "+std::to_string(_indent)); |
||||
#endif |
||||
} |
||||
virtual ~basic_split() { |
||||
flush(); |
||||
} |
||||
friend basic_split& operator<<(std::basic_ostream<_CharT, _Traits>& os, |
||||
const basic_split& s) { |
||||
if (s._os!=&os) { |
||||
const_cast<basic_split&>(s).flush(); |
||||
const_cast<basic_split&>(s)._os = &os; |
||||
} |
||||
return const_cast<basic_split&>(s); |
||||
} |
||||
template <typename T> basic_split& operator<<(T t) { |
||||
if (!_os) |
||||
throw std::runtime_error("wrong use of split, it is an io manipulator" |
||||
" and must be used within a stream"); |
||||
_buffer<<t; |
||||
while (_buffer.str().size()>=_width-_indent) { |
||||
if (_indent) *_os<<std::setw(_indent)<<std::setfill(_fill)<<_fill; |
||||
std::string::size_type pos |
||||
(_buffer.str().find_last_of(' ', _width-_indent)); |
||||
std::string::size_type next(pos+1); |
||||
if (pos==std::string::npos) { |
||||
pos=_width-_indent; |
||||
next=pos; |
||||
} |
||||
*_os<<_buffer.str().substr(0, pos)<<std::endl; |
||||
_buffer.str(_buffer.str().substr(next)); |
||||
} |
||||
return *this; |
||||
} |
||||
private: |
||||
void flush() { |
||||
if (_os) { |
||||
if (_indent) *_os<<std::setw(_indent)<<std::setfill(_fill)<<_fill; |
||||
*_os<<_buffer.str(); |
||||
_buffer.clear(); |
||||
} |
||||
} |
||||
private: |
||||
int _width; |
||||
int _indent; |
||||
char _fill; |
||||
std::stringstream _buffer; |
||||
std::basic_ostream<_CharT, _Traits>* _os; |
||||
}; |
||||
typedef basic_split<char> split; |
||||
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; |
||||
#ifndef ARGS__CPLUSPLUS11 |
||||
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:"")); |
||||
#ifndef ARGS__CPLUSPLUS11 |
||||
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 |
Loading…
Reference in new issue