commandline.cc
Go to the documentation of this file.
00001 #include "utilmm/configfile/commandline.hh"
00002 
00003 #include "utilmm/configfile/configset.hh"
00004 #include "utilmm/stringtools.hh"
00005 #include <boost/regex.hpp>
00006 
00007 using namespace boost;
00008 using namespace std;
00009 
00010 namespace {
00011 
00012     char advance(std::string const& source, std::string const& allowed, size_t& current, std::string& text)
00013     {
00014         size_t next = source.find_first_of(allowed, current);
00015         text = string(source, current, next - current);
00016         current = next;
00017 
00018         return (next == string::npos) ? 0 : source[current];
00019     }
00020     
00021     using namespace utilmm;
00022 
00023 }
00024 
00025 namespace utilmm
00026 {
00031     cmdline_option::cmdline_option(const std::string& description)
00032         : m_multiple(false), m_required(false), m_argument_flags(None)
00033     {
00034         static regex rx_valid_identifier("[\\w-]+");
00035 
00036         size_t current = 0;
00037         if (description[current] == '!')
00038         {
00039             ++current;
00040             m_required = true;
00041         }
00042         if (description[current] == '*')
00043         {
00044             ++current;
00045             m_multiple = true;
00046         }
00047 
00048         if (!advance(description, ":", current, m_config))
00049             throw bad_syntax(description, "expected ':'");
00050 
00051         char delim = advance(description, ",=?:", ++current, m_long);
00052         if (! regex_match(m_long, rx_valid_identifier))
00053             throw bad_syntax(description, "invalid identifier");
00054         if (!delim) return;
00055 
00056         std::string text;
00057 
00058         if (delim == ',')
00059         {
00060             delim = advance(description, "=?:", ++current, text);
00061 
00062             if (text.size() != 1)
00063                 throw bad_syntax(description, "short option string must be only one character");
00064             m_short = text;
00065             if (! regex_match(m_short, rx_valid_identifier))
00066                 throw bad_syntax(description, "invalid short option character");
00067 
00068             if (! delim) return;
00069         }
00070 
00071         if (delim == '=' || delim == '?')
00072         {
00073             if (delim == '?')
00074                 m_argument_flags |= Optional;
00075 
00076             delim = advance(description, ",:", ++current, text);
00077 
00078             if (text == "int")
00079                 m_argument_flags |= IntArgument;
00080             else if (text == "string")
00081                 m_argument_flags |= StringArgument;
00082             else if (text == "bool")
00083                 m_argument_flags |= BoolArgument;
00084             else
00085                 throw bad_syntax(description, "invalid option type");
00086 
00087             if (delim == ',')
00088             {
00089                 delim = advance(description, ":", ++current, text);
00090                 if (isRequired() && !isArgumentOptional())
00091                     throw bad_syntax(description, "it is meaningless to have a default value for a required argument of a require option");
00092 
00093                 if (!checkArgument(text))
00094                     throw bad_syntax(description, "default value for " + getLong() + " is not a valid value");
00095 
00096                 m_argument_flags |= DefaultValue;
00097                 m_default = text;
00098             }
00099             else if (m_argument_flags & Optional)
00100                 throw bad_syntax(description, "options with optional arguments should have a default value");
00101 
00102             if (!delim) return;
00103         }
00104 
00105         m_help = string(description, current + 1);
00106     }
00107 
00108     cmdline_option::~cmdline_option() { }
00109 
00110     bool        cmdline_option::isMultiple() const            { return m_multiple; }
00111     std::string cmdline_option::getConfigKey() const          
00112     { 
00113         if (!m_config.empty())
00114             return m_config;
00115         return getLong(); 
00116     }
00117     std::string cmdline_option::getLong()   const             { return m_long; }
00118     std::string cmdline_option::getShort()  const             { return m_short; }
00119     std::string cmdline_option::getHelp()   const             { return m_help; }
00120 
00121     bool cmdline_option::isRequired() const                   { return m_required; }
00122 
00123     int  cmdline_option::getArgumentFlags() const             { return m_argument_flags; }
00124     bool cmdline_option::hasArgument() const                  { return m_argument_flags; }
00125     bool cmdline_option::isArgumentOptional() const           { return m_argument_flags & Optional; }
00126 
00127     bool cmdline_option::hasDefaultValue() const                  { return m_argument_flags & DefaultValue; }
00128     std::string cmdline_option::getDefaultValue() const   
00129     { 
00130         if (hasDefaultValue())
00131             return m_default;
00132         return "true";
00133     }
00134 
00138     bool cmdline_option::checkArgument(const std::string& value) const
00139     {
00140         static const regex 
00141             rx_int("[0-9]+"),
00142             rx_bool("1|0|false|true");
00143 
00144 
00145         if (m_argument_flags & IntArgument)
00146             return regex_match(value, rx_int);
00147         else if (m_argument_flags & BoolArgument)
00148             return regex_match(value, rx_bool);
00149         else return true;
00150     }
00151 
00152     command_line::command_line(const char* options[])
00153     {
00154         for(char const** opt = options; *opt; ++opt)
00155             m_options.push_back(cmdline_option(*opt));
00156     }
00157 
00158     command_line::command_line(const std::list<std::string>& description)
00159     {
00160         for(list<string>::const_iterator it = description.begin(); it != description.end(); ++it)
00161             m_options.push_back(cmdline_option(*it));
00162     }
00163             
00164     command_line::~command_line() { }
00165 
00166     void command_line::add_argument(config_set& config, cmdline_option const& optdesc, std::string const& value)
00167     { 
00168         if (! optdesc.checkArgument(value))
00169             throw commandline_error("invalid value for --" + optdesc.getLong());
00170 
00171         if (optdesc.isMultiple())
00172             config.insert(optdesc.getConfigKey(), value);
00173         else
00174             config.set(optdesc.getConfigKey(), value);
00175     }
00176 
00177     int command_line::option_match(config_set& config, cmdline_option const& opt, int argc, char const* const* argv, int i)
00178     {
00179         if (argv[i] == "-" + opt.getShort())
00180         {
00181             // check if it looks like we have an argument
00182             bool has_argument = (i + 1 < argc && argv[i + 1][0] != '-');
00183 
00184             if (!opt.hasArgument() || (opt.isArgumentOptional() && !has_argument))
00185                 add_argument(config, opt, opt.getDefaultValue());
00186             else if (has_argument)
00187             {
00188                 add_argument(config, opt, argv[i + 1]);
00189                 return i + 2;
00190             }
00191             else
00192                 throw commandline_error("missing argument to -" + opt.getShort());
00193         }
00194         else if (argv[i] == "--" + opt.getLong())
00195         {
00196             if (opt.hasArgument() && !opt.isArgumentOptional())
00197                 throw commandline_error("missing argument to --" + opt.getLong());
00198 
00199             add_argument(config, opt, opt.getDefaultValue());
00200         }
00201         else if (starts_with(argv[i], "--" + opt.getLong() + "="))
00202         {
00203             if (!opt.hasArgument())
00204                 throw commandline_error("argument provided to --" + opt.getLong());
00205 
00206             int seed_size = opt.getLong().length() + 3;
00207             add_argument(config, opt, std::string(argv[i]).substr(seed_size));
00208         }
00209         else return i;
00210         return i + 1;
00211     }
00212 
00213     void command_line::parse(int argc, char const* const argv[], config_set& config)
00214     {
00215         std::list<string> remains;
00216         
00217         // Parse options from the command line
00218         for (int i = 0; i < argc; )
00219         {
00220             if (argv[i][0] != '-')
00221             {
00222                 remains.push_back(argv[i]);
00223                 ++i;
00224                 continue;
00225             }
00226 
00227             // Get the option description object (if this is an option)
00228             Options::iterator opt;
00229             for (opt = m_options.begin(); opt != m_options.end(); ++opt)
00230             {
00231                 int new_index = option_match(config, *opt, argc, argv, i);
00232                 if (new_index != i) 
00233                 {
00234                     i = new_index;
00235                     break;
00236                 }
00237             }
00238             if (opt == m_options.end())
00239                 throw commandline_error("unknown argument " + string(argv[i]));
00240         }
00241 
00242         // Set default values for options that have one
00243         for (Options::iterator opt = m_options.begin(); opt != m_options.end(); ++opt)
00244         {
00245             if (config.exists(opt->getConfigKey()))
00246                 continue;
00247 
00248             if (opt->isRequired())
00249                 throw commandline_error("required option --" + opt->getLong() + " is missing");
00250             else if (opt->hasDefaultValue())
00251                 config.set(opt->getConfigKey(), opt->getDefaultValue());
00252         }
00253 
00254         m_remaining = remains;
00255     }
00256 
00257     list<string> command_line::remaining() const { return m_remaining; }
00258     void command_line::setBanner(std::string const& str) { m_banner = str; }
00259     void command_line::usage(std::ostream& out) const
00260     {
00261         if (! m_banner.empty())
00262             out << m_banner << "\n";
00263 
00264         for(Options::const_iterator it = m_options.begin(); it != m_options.end(); ++it)
00265         {
00266             string longopt = it->getLong(), shortopt = it->getShort();
00267             bool hasarg = it->hasArgument();
00268             bool optarg = it->isArgumentOptional();
00269             string helpstring = it->getHelp();
00270 
00271             out << "  ";
00272             if (!shortopt.empty())
00273                 out << "-" << shortopt << "\t";
00274             out << "--" << longopt;
00275             if (hasarg)
00276             {
00277                 if (optarg) out << "[=VALUE]";
00278                 else out << "=VALUE";
00279             }
00280             out << "\t" << helpstring << "\n";
00281         }
00282 
00283     }
00284 }
00285 
00286 


utilmm
Author(s): Sylvain Joyeux/sylvain.joyeux@m4x.org
autogenerated on Thu Jan 2 2014 11:38:31