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
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
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
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
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