// SPDX-License-Identifier: GPL-3.0-or-later /** * @file cmdLine.h * @brief Command line option parsing * * (C) 2012-2016, 2019, Pascal Monasse */ #ifndef CMDLINE_H #define CMDLINE_H #include #include #include #include #include #include #include /// Base class for option/switch class Option { public: char c; ///< Option letter (eg 's' for -s), 0 if you want only a long name bool used; ///< Does the command line use that option? std::string longName; ///< Optional long name (eg "switch" for --switch) std::string desc; ///< Description /// Constructor with short name/long name Option(char d, std::string name) : c(d), used(false), longName(name) {} virtual ~Option() {} const std::string& doc() const { return desc; } virtual void print(std::ostream& str) const { if(c) str << '-' << c << (longName.empty()? "": ", "); else str << " "; // For alignment if(! longName.empty()) str << "--" << longName; } Option& doc(const std::string& description) { desc=description; return *this;} virtual bool check(int& argc, char* argv[])=0; ///< Option found at argv[0]? virtual Option* clone() const=0; ///< Copy /// Print value of option virtual std::string printValue() const { return std::string(); } }; /// Option on/off is called a switch class OptionSwitch : public Option { public: /// Constructor with short name/long name (optional) OptionSwitch(char c, std::string name="") : Option(c,name) {} /// Find switch in argv[0] bool check(int& argc, char* argv[]) { if(std::string("-")+c==argv[0] || (!longName.empty() && std::string("--")+longName==argv[0])) { used = true; std::rotate(argv, argv+1, argv+argc); argc -= 1; return true; } else if(std::string(argv[0]).find(std::string("-")+c)==0) { used = true; // Handle multiple switches in single option std::rotate(argv[0]+1, argv[0]+2, argv[0]+std::string(argv[0]).size()+1); return true; } return false; } /// Copy Option* clone() const { return new OptionSwitch(*this); } }; /// Option with an argument of type T, which must be readable by operator>> template class OptionField : public Option { public: /// Constructor. The result with be stored in variable @field. OptionField(char c, T& field, std::string name="") : Option(c,name), _field(field) {} /// Find option in argv[0] and argument in argv[1]. Throw an exception /// (type std::string) if the argument cannot be read. bool check(int& argc, char* argv[]) { std::string param; int arg=0; if(std::string("-")+c==argv[0] || (!longName.empty() && std::string("--")+longName==argv[0])) { if(argc<=1) throw std::string("Option ") +argv[0]+" requires argument"; param=argv[1]; arg=2; } else if(std::string(argv[0]).find(std::string("-")+c)==0) { param=argv[0]+2; arg=1; } else if(!longName.empty() && std::string(argv[0]).find(std::string("--")+longName+'=')==0){ size_t size=(std::string("--")+longName+'=').size(); param=std::string(argv[0]).substr(size); arg=1; } if(arg>0) { if(! read_param(param)) throw std::string("Unable to interpret ") +param+" as argument of "+argv[0]; used = true; std::rotate(argv, argv+arg, argv+argc); argc -= arg; return true; } return false; } /// Decode the string as template type T bool read_param(const std::string& param) { std::stringstream str(param); char unused; return !((str >> _field).fail() || !(str>>unused).fail()); } /// Indicate that an argument is required void print(std::ostream& str) const { Option::print(str); str << (longName.empty()? ' ': '=') << "ARG"; } std::string printValue() const { std::stringstream s; s << _field; return s.str(); } /// Copy Option* clone() const { return new OptionField(*this); } private: T& _field; ///< Reference to variable where to store the value }; /// Template specialization to declare a switch like an option, storing result /// in the variable. template <> inline bool OptionField::check(int& argc, char* argv[]) { bool res = OptionSwitch(c,longName).check(argc, argv); if(res) _field = true; return res; } /// Specialisation for a switch, no argument to print. template<> void OptionField::print(std::ostream& str) const { Option::print(str); } /// Specialisation for a switch, no argument to print. template<> std::string OptionField::printValue() const { return Option::printValue(); } /// Template specialization to be able to take parameter including space. /// Generic method would do >>_field (stops at space) and signal unused chars. template <> inline bool OptionField::read_param(const std::string& param) { _field = param; return true; } /// New switch option OptionSwitch make_switch(char c, std::string name="") { return OptionSwitch(c, name); } /// New option with argument. template OptionField make_option(char c, T& field, std::string name="") { return OptionField(c, field, name); } /// Command line parsing class CmdLine { std::vector opts; public: std::string prefixDoc; ///< For example, a tabulation for each line of doc int alignDoc; ///< Column where option description starts bool showDefaults; ///< Show default values of options /// Constructor CmdLine(): alignDoc(0), showDefaults(true) {} /// Destructor ~CmdLine() { std::vector::iterator it=opts.begin(); for(; it != opts.end(); ++it) delete *it; } /// Add an option void add(const Option& opt) { opts.push_back( opt.clone() ); } /// Parse of command line acting as a filter. All options are virtually /// removed from the command line. void process(int& argc, char* argv[]) { std::vector::iterator it=opts.begin(); for(; it != opts.end(); ++it) (*it)->used = false; for(int i=1; icheck(n, argv+i); if(found) { argc = n+i; break; } } if(! found) { // A negative number is not an option if(std::string(argv[i]).size()>1 && argv[i][0] == '-') { std::istringstream str(argv[i]); float v; if(! (str>>v).eof()) throw std::string("Unrecognized option ")+argv[i]; } ++i; } } } /// Output options. void print(std::ostream& str) const { std::vector::const_iterator it=opts.begin(); for(; it != opts.end(); ++it) { std::stringstream ss; ss << prefixDoc; (*it)->print(ss); ss << ' '; std::string d = ss.str(); str << d; if((int)d.size() < alignDoc) std::fill_n(std::ostream_iterator(str), alignDoc-d.size(), ' '); str << (*it)->desc; if(showDefaults && !(*it)->printValue().empty()) str << " (" << (*it)->printValue() << ')'; str << std::endl; } } /// Was the option used in last parsing? bool used(char c) const { std::vector::const_iterator it=opts.begin(); for(; it != opts.end(); ++it) if((*it)->c == c) return (*it)->used; assert(false); // Called with non-existent option, probably a bug return false; } }; /// Output possible options. std::ostream& operator<<(std::ostream& str, const CmdLine& cmd) { cmd.print(str); return str; } #endif