/* Copyright (C) 2004 Xavier Decoret <Xavier.Decoret@imag.fr> * * argsteam is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Foobar is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Foobar; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef ARGSTREAM_H #define ARGSTREAM_H #include <string> #include <list> #include <deque> #include <map> #include <stdexcept> #include <sstream> #include <iostream> namespace { class argstream; template<class T> class ValueHolder; template <typename T> argstream& operator>> (argstream&, const ValueHolder<T>&); //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Interface of ValueHolder<T> //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ template<class T> class ValueHolder { public: ValueHolder(char s, const char* l, T& b, const char* desc, bool mandatory); ValueHolder(const char* l, T& b, const char* desc, bool mandatory); ValueHolder(char s, T& b, const char* desc, bool mandatory); friend argstream& operator>><>(argstream& s,const ValueHolder<T>& v); std::string name() const; std::string description() const; private: std::string shortName_; std::string longName_; T* value_; T initialValue_; std::string description_; bool mandatory_; }; template <class T> inline ValueHolder<T> parameter(char s, const char* l, T& b, const char* desc="", bool mandatory = true) { return ValueHolder<T>(s,l,b,desc,mandatory); } template <class T> inline ValueHolder<T> parameter(char s, T& b, const char* desc="", bool mandatory = true) { return ValueHolder<T>(s,b,desc,mandatory); } template <class T> inline ValueHolder<T> parameter(const char* l, T& b, const char* desc="", bool mandatory = true) { return ValueHolder<T>(l,b,desc,mandatory); } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Interface of OptionHolder //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ class OptionHolder { public: inline OptionHolder(char s, const char* l, bool& b, const char* desc); inline OptionHolder(const char* l, bool& b, const char* desc); inline OptionHolder(char s, bool& b, const char* desc); friend argstream& operator>>(argstream& s,const OptionHolder& v); inline std::string name() const; inline std::string description() const; protected: inline OptionHolder(char s, const char* l, const char* desc); friend OptionHolder help(char s='h', const char* l="help", const char* desc="Display this help"); private: std::string shortName_; std::string longName_; bool* value_; std::string description_; }; inline OptionHolder option(char s, const char* l, bool& b, const char* desc="") { return OptionHolder(s,l,b,desc); } inline OptionHolder option(char s, bool& b, const char* desc="") { return OptionHolder(s,b,desc); } inline OptionHolder option(const char* l, bool& b, const char* desc="") { return OptionHolder(l,b,desc); } inline OptionHolder help(char s, const char* l, const char* desc) { return OptionHolder(s,l,desc); } template <class T, class O> class ValuesHolder; template <typename T, typename O> argstream& operator>> (argstream&, const ValuesHolder<T, O>&); //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Interface of ValuesHolder //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ template<class T,class O> class ValuesHolder { public: ValuesHolder(const O& o, const char* desc, int len); friend argstream& operator>><>(argstream& s,const ValuesHolder<T,O>& v); std::string name() const; std::string description() const; typedef T value_type; private: mutable O value_; std::string description_; int len_; char letter_; }; template<class T,class O> inline ValuesHolder<T,O> values(const O& o, const char* desc="", int len=-1) { return ValuesHolder<T,O>(o,desc,len); } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Interface of ValueParser //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ template <class T> class ValueParser { public: inline T operator()(const std::string& s) const { std::istringstream is(s); T t; is>>t; return t; } }; // We need to specialize for string otherwise parsing of a value that // contains space (for example a string with space passed in quotes on the // command line) would parse only the first element of the value!!! template <> class ValueParser<std::string> { public: inline std::string operator()(const std::string& s) const { return s; } }; //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Interface of argstream //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ class argstream { public: inline argstream(int argc,char** argv); //inline argstream(const char* c); template<class T> friend argstream& operator>>(argstream& s,const ValueHolder<T>& v); friend inline argstream& operator>>(argstream& s,const OptionHolder& v); template<class T,class O> friend argstream& operator>>(argstream& s,const ValuesHolder<T,O>& v); inline bool helpRequested() const; inline bool isOk() const; inline std::string errorLog() const; inline std::string usage() const; inline void defaultErrorHandling(bool ignoreUnused=false) const; static inline char uniqueLetter(); protected: void parse(int argc,char** argv); private: typedef std::list<std::string>::iterator value_iterator; typedef std::pair<std::string,std::string> help_entry; std::string progName_; std::map<std::string,value_iterator> options_; std::list<std::string> values_; bool minusActive_; bool isOk_; std::deque<help_entry> argHelps_; std::string cmdLine_; std::deque<std::string> errors_; bool helpRequested_; }; //************************************************************ // Implementation of ValueHolder<T> //************************************************************ template<class T> ValueHolder<T>::ValueHolder(char s, const char* l, T& v, const char* desc, bool mandatory) : shortName_(1,s), longName_(l), value_(&v), initialValue_(v), description_(desc), mandatory_(mandatory) { } template<class T> ValueHolder<T>::ValueHolder(const char* l, T& v, const char* desc, bool mandatory) : longName_(l), value_(&v), initialValue_(v), description_(desc), mandatory_(mandatory) { } template<class T> ValueHolder<T>::ValueHolder(char s, T& v, const char* desc, bool mandatory) : shortName_(1,s), value_(&v), initialValue_(v), description_(desc), mandatory_(mandatory) { } template<class T> std::string ValueHolder<T>::name() const { std::ostringstream os; if (!shortName_.empty()) os<<'-'<<shortName_; if (!longName_.empty()) { if (!shortName_.empty()) os<<'/'; os<<"--"<<longName_; } return os.str(); } template<class T> std::string ValueHolder<T>::description() const { std::ostringstream os; os<<description_; if (mandatory_) { os<<"(mandatory)"; } else { os<<"(default="<<initialValue_<<")"; } return os.str(); } //************************************************************ // Implementation of OptionHolder //************************************************************ inline OptionHolder::OptionHolder(char s, const char* l, bool& b, const char* desc) : shortName_(1,s), longName_(l), value_(&b), description_(desc) { } inline OptionHolder::OptionHolder(const char* l, bool& b, const char* desc) : longName_(l), value_(&b), description_(desc) { } inline OptionHolder::OptionHolder(char s, bool& b, const char* desc) : shortName_(1,s), value_(&b), description_(desc) { } inline OptionHolder::OptionHolder(char s, const char* l, const char* desc) : shortName_(1,s), longName_(l), value_(NULL), description_(desc) { } inline std::string OptionHolder::name() const { std::ostringstream os; if (!shortName_.empty()) os<<'-'<<shortName_; if (!longName_.empty()) { if (!shortName_.empty()) os<<'/'; os<<"--"<<longName_; } return os.str(); } inline std::string OptionHolder::description() const { return description_; } //************************************************************ // Implementation of ValuesHolder<T,O> //************************************************************ template<class T,class O> ValuesHolder<T,O>::ValuesHolder(const O& o, const char* desc, int len) : value_(o), description_(desc), len_(len) { letter_ = argstream::uniqueLetter(); } template <class T,class O> std::string ValuesHolder<T,O>::name() const { std::ostringstream os; os<<letter_<<"i"; return os.str(); } template <class T,class O> std::string ValuesHolder<T,O>::description() const { return description_; } //************************************************************ // Implementation of argstream //************************************************************ inline argstream::argstream(int argc,char** argv) : progName_(argv[0]), minusActive_(true), isOk_(true) { parse(argc,argv); } //inline // argstream::argstream(const char* c) // : progName_(""), // minusActive_(true), // isOk_(true) //{ // std::string s(c); // // Build argc, argv from s. We must add a dummy first element for // // progName because parse() expects it!! // std::deque<std::string> args; // args.push_back(""); // std::istringstream is(s); // while (is.good()) // { // std::string t; // is>>t; // args.push_back(t); // } // char* pargs[args.size()]; // char** p = pargs; // for (std::deque<std::string>::const_iterator // iter = args.begin(); // iter != args.end();++iter) // { // *p++ = const_cast<char*>(iter->c_str()); // } // parse(args.size(),pargs); //} inline void argstream::parse(int argc,char** argv) { // Run thru all arguments. // * it has -- in front : it is a long name option, if remainder is empty, // it is an error // * it has - in front : it is a sequence of short name options, if // remainder is empty, deactivates option (- will // now be considered a char). // * if any other char, or if option was deactivated // : it is a value. Values are split in parameters // (immediately follow an option) and pure values. // Each time a value is parsed, if the previously parsed argument was an // option, then the option is linked to the value in case of it is a // option with parameter. The subtle point is that when several options // are given with short names (ex: -abc equivalent to -a -b -c), the last // parsed option is -c). // Since we use map for option, any successive call overides the previous // one: foo -a -b -a hello is equivalent to foo -b -a hello // For values it is not true since we might have several times the same // value. value_iterator* lastOption = NULL; for (char** a = argv,**astop=a+argc;++a!=astop;) { std::string s(*a); if (minusActive_ && s[0] == '-') { if (s.size() > 1 && s[1] == '-') { if (s.size() == 2) { minusActive_ = false; continue; } lastOption = &(options_[s.substr(2)] = values_.end()); } else { if (s.size() > 1) { // Parse all chars, if it is a minus we have an error for (std::string::const_iterator cter = s.begin(); ++cter != s.end();) { if (*cter == '-') { isOk_ = false; std::ostringstream os; os<<"- in the middle of a switch "<<a; errors_.push_back(os.str()); break; } lastOption = &(options_[std::string(1,*cter)] = values_.end()); } } else { isOk_ = false; errors_.push_back("Invalid argument -"); break; } } } else { values_.push_back(s); if (lastOption != NULL) { *lastOption = --values_.end(); } lastOption = NULL; } } #ifdef ARGSTREAM_DEBUG for (std::map<std::string,value_iterator>::const_iterator iter = options_.begin();iter != options_.end();++iter) { std::cout<<"DEBUG: option "<<iter->first; if (iter->second != values_.end()) { std::cout<<" -> "<<*(iter->second); } std::cout<<std::endl; } for (std::list<std::string>::const_iterator iter = values_.begin();iter != values_.end();++iter) { std::cout<<"DEBUG: value "<<*iter<<std::endl; } #endif // ARGSTREAM_DEBUG } inline bool argstream::isOk() const { return isOk_; } inline bool argstream::helpRequested() const { return helpRequested_; } inline std::string argstream::usage() const { std::ostringstream os; os<<"usage: "<<progName_<<cmdLine_<<'\n'; unsigned int lmax = 0; for (std::deque<help_entry>::const_iterator iter = argHelps_.begin();iter != argHelps_.end();++iter) { if (lmax<iter->first.size()) lmax = iter->first.size(); } for (std::deque<help_entry>::const_iterator iter = argHelps_.begin();iter != argHelps_.end();++iter) { os<<'\t'<<iter->first<<std::string(lmax-iter->first.size(),' ') <<" : "<<iter->second<<'\n'; } return os.str(); } inline std::string argstream::errorLog() const { std::string s; for(std::deque<std::string>::const_iterator iter = errors_.begin(); iter != errors_.end();++iter) { s += *iter; s += '\n'; } return s; } inline char argstream::uniqueLetter() { static unsigned int c = 'a'; return c++; } template<class T> argstream& operator>>(argstream& s,const ValueHolder<T>& v) { // Search in the options if there is any such option defined either with a // short name or a long name. If both are found, only the last one is // used. #ifdef ARGSTREAM_DEBUG std::cout<<"DEBUG: searching "<<v.shortName_<<" "<<v.longName_<<std::endl; #endif s.argHelps_.push_back(argstream::help_entry(v.name(),v.description())); if (v.mandatory_) { if (!v.shortName_.empty()) { s.cmdLine_ += " -"; s.cmdLine_ += v.shortName_; } else { s.cmdLine_ += " --"; s.cmdLine_ += v.longName_; } s.cmdLine_ += " value"; } else { if (!v.shortName_.empty()) { s.cmdLine_ += " [-"; s.cmdLine_ += v.shortName_; } else { s.cmdLine_ += " [--"; s.cmdLine_ += v.longName_; } s.cmdLine_ += " value]"; } std::map<std::string,argstream::value_iterator>::iterator iter = s.options_.find(v.shortName_); if (iter == s.options_.end()) { iter = s.options_.find(v.longName_); } if (iter != s.options_.end()) { // If we find counterpart for value holder on command line, either it // has an associated value in which case we assign it, or it has not, in // which case we have an error. if (iter->second != s.values_.end()) { #ifdef ARGSTREAM_DEBUG std::cout<<"DEBUG: found value "<<*(iter->second)<<std::endl; #endif ValueParser<T> p; *(v.value_) = p(*(iter->second)); // The option and its associated value are removed, the subtle thing // is that someother options might have this associated value too, // which we must invalidate. s.values_.erase(iter->second); // FIXME this loop seems to crash if a std::string is used as the value //for (std::map<std::string,argstream::value_iterator>::iterator // jter = s.options_.begin();jter != s.options_.end();++jter) //{ // if (jter->second == iter->second) // { // jter->second = s.values_.end(); // } //} s.options_.erase(iter); } else { s.isOk_ = false; std::ostringstream os; os<<"No value following switch "<<iter->first <<" on command line"; s.errors_.push_back(os.str()); } } else { if (v.mandatory_) { s.isOk_ = false; std::ostringstream os; os<<"Mandatory parameter "; if (!v.shortName_.empty()) os<<'-'<<v.shortName_; if (!v.longName_.empty()) { if (!v.shortName_.empty()) os<<'/'; os<<"--"<<v.longName_; } os<<" missing"; s.errors_.push_back(os.str()); } } return s; } inline argstream& operator>>(argstream& s,const OptionHolder& v) { // Search in the options if there is any such option defined either with a // short name or a long name. If both are found, only the last one is // used. #ifdef ARGSTREAM_DEBUG std::cout<<"DEBUG: searching "<<v.shortName_<<" "<<v.longName_<<std::endl; #endif s.argHelps_.push_back(argstream::help_entry(v.name(),v.description())); { std::string c; if (!v.shortName_.empty()) { c += " [-"; c += v.shortName_; } else { c += " [--"; c += v.longName_; } c += "]"; s.cmdLine_ = c+s.cmdLine_; } std::map<std::string,argstream::value_iterator>::iterator iter = s.options_.find(v.shortName_); if (iter == s.options_.end()) { iter = s.options_.find(v.longName_); } if (iter != s.options_.end()) { // If we find counterpart for value holder on command line then the // option is true and if an associated value was found, it is ignored if (v.value_ != NULL) { *(v.value_) = true; } else { s.helpRequested_ = true; } // The option only is removed s.options_.erase(iter); } else { if (v.value_ != NULL) { *(v.value_) = false; } else { s.helpRequested_ = false; } } return s; } template<class T,class O> argstream& operator>>(argstream& s,const ValuesHolder<T,O>& v) { s.argHelps_.push_back(argstream::help_entry(v.name(),v.description())); { std::ostringstream os; os<<' '<<v.letter_<<'1'; switch (v.len_) { case -1: os<<"..."; break; case 1: break; default: os<<"..."<<v.letter_<<v.len_; break; } s.cmdLine_ += os.str(); } std::list<std::string>::iterator first = s.values_.begin(); // We add to the iterator as much values as we can, limited to the length // specified (if different of -1) int n = v.len_ != -1?v.len_:s.values_.size(); while (first != s.values_.end() && n-->0) { // Read the value from the string *first ValueParser<T> p; *(v.value_++) = p(*first ); s.argHelps_.push_back(argstream::help_entry(v.name(),v.description())); // The value we just removed was maybe "remembered" by an option so we // remove it now. for (std::map<std::string,argstream::value_iterator>::iterator jter = s.options_.begin();jter != s.options_.end();++jter) { if (jter->second == first) { jter->second = s.values_.end(); } } ++first; } // Check if we have enough values if (n != 0) { s.isOk_ = false; std::ostringstream os; os<<"Expecting "<<v.len_<<" values"; s.errors_.push_back(os.str()); } // Erase the values parsed s.values_.erase(s.values_.begin(),first); return s; } inline void argstream::defaultErrorHandling(bool ignoreUnused) const { if (helpRequested_) { std::cout<<usage(); exit(1); } if (!isOk_) { std::cerr<<errorLog(); exit(1); } if (!ignoreUnused && (!values_.empty() || !options_.empty())) { std::cerr<<"Unused arguments"<<std::endl; exit(1); } } }; #endif // ARGSTREAM_H