1 | ␊ |
2 | #include "base.hh"␊ |
3 | #include "file_io.hh"␊ |
4 | #include "option.hh"␊ |
5 | #include "sanity.hh"␊ |
6 | #include "ui.hh"␊ |
7 | ␊ |
8 | using std::map;␊ |
9 | using std::pair;␊ |
10 | using std::set;␊ |
11 | using std::string;␊ |
12 | using std::vector;␊ |
13 | ␊ |
14 | namespace option {␊ |
15 | ␊ |
16 | option_error::option_error(std::string const & str)␊ |
17 | : std::invalid_argument((F("option error: %s") % str).str())␊ |
18 | {}␊ |
19 | ␊ |
20 | unknown_option::unknown_option(std::string const & opt)␊ |
21 | : option_error((F("unknown option '%s'") % opt).str())␊ |
22 | {}␊ |
23 | ␊ |
24 | missing_arg::missing_arg(std::string const & opt)␊ |
25 | : option_error((F("missing argument to option '%s'") % opt).str())␊ |
26 | {}␊ |
27 | ␊ |
28 | extra_arg::extra_arg(std::string const & opt)␊ |
29 | : option_error((F("option '%s' does not take an argument") % opt).str())␊ |
30 | {}␊ |
31 | ␊ |
32 | bad_arg::bad_arg(std::string const & opt, arg_type const & arg)␊ |
33 | : option_error((F("bad argument '%s' to option '%s'") % arg() % opt).str())␊ |
34 | {}␊ |
35 | ␊ |
36 | bad_arg::bad_arg(std::string const & opt,␊ |
37 | arg_type const & arg,␊ |
38 | std::string const & reason)␊ |
39 | : option_error((F("bad argument '%s' to option '%s': %s")␊ |
40 | % arg() % opt % reason).str())␊ |
41 | {}␊ |
42 | ␊ |
43 | bad_arg_internal::bad_arg_internal(string const & str)␊ |
44 | : reason(str)␊ |
45 | {}␊ |
46 | ␊ |
47 | ␊ |
48 | ␊ |
49 | void splitname(string const & from, string & name, string & n)␊ |
50 | {␊ |
51 | // from looks like "foo" or "foo,f"␊ |
52 | string::size_type comma = from.find(',');␊ |
53 | name = from.substr(0, comma);␊ |
54 | if (comma != string::npos)␊ |
55 | n = from.substr(comma+1, 1);␊ |
56 | else␊ |
57 | n = "";␊ |
58 | ␊ |
59 | // "o" is equivalent to ",o"; it gives an option␊ |
60 | // with only a short name␊ |
61 | if (name.size() == 1)␊ |
62 | {␊ |
63 | I(n.empty());␊ |
64 | n = name;␊ |
65 | name = "";␊ |
66 | }␊ |
67 | }␊ |
68 | ␊ |
69 | ␊ |
70 | concrete_option::concrete_option()␊ |
71 | : has_arg(false)␊ |
72 | {}␊ |
73 | ␊ |
74 | concrete_option::concrete_option(std::string const & names,␊ |
75 | std::string const & desc,␊ |
76 | bool arg,␊ |
77 | boost::function<void (std::string)> set,␊ |
78 | boost::function<void ()> reset)␊ |
79 | {␊ |
80 | description = desc;␊ |
81 | splitname(names, longname, shortname);␊ |
82 | I(!description.empty() || !longname.empty() || !shortname.empty());␊ |
83 | // If an option has a name (ie, can be set), it must have a setter function␊ |
84 | I(set || (longname.empty() && shortname.empty()));␊ |
85 | has_arg = arg;␊ |
86 | setter = set;␊ |
87 | resetter = reset;␊ |
88 | }␊ |
89 | ␊ |
90 | bool concrete_option::operator<(concrete_option const & other) const␊ |
91 | {␊ |
92 | if (longname != other.longname)␊ |
93 | return longname < other.longname;␊ |
94 | if (shortname != other.shortname)␊ |
95 | return shortname < other.shortname;␊ |
96 | return description < other.description;␊ |
97 | }␊ |
98 | ␊ |
99 | concrete_option_set␊ |
100 | operator | (concrete_option const & a, concrete_option const & b)␊ |
101 | {␊ |
102 | return concrete_option_set(a) | b;␊ |
103 | }␊ |
104 | ␊ |
105 | concrete_option_set::concrete_option_set()␊ |
106 | {}␊ |
107 | ␊ |
108 | concrete_option_set::concrete_option_set(std::set<concrete_option> const & other)␊ |
109 | : options(other)␊ |
110 | {}␊ |
111 | ␊ |
112 | concrete_option_set::concrete_option_set(concrete_option const & opt)␊ |
113 | {␊ |
114 | options.insert(opt);␊ |
115 | }␊ |
116 | ␊ |
117 | // essentially the opposite of std::bind1st␊ |
118 | class discard_argument␊ |
119 | {␊ |
120 | boost::function<void()> functor;␊ |
121 | public:␊ |
122 | discard_argument(boost::function<void()> const & from)␊ |
123 | : functor(from)␊ |
124 | {}␊ |
125 | void operator()(std::string const &)␊ |
126 | { return functor(); }␊ |
127 | };␊ |
128 | ␊ |
129 | concrete_option_set &␊ |
130 | concrete_option_set::operator()(string const & names,␊ |
131 | string const & desc,␊ |
132 | boost::function<void ()> set,␊ |
133 | boost::function<void ()> reset)␊ |
134 | {␊ |
135 | options.insert(concrete_option(names, desc, false, discard_argument(set), reset));␊ |
136 | return *this;␊ |
137 | }␊ |
138 | ␊ |
139 | concrete_option_set &␊ |
140 | concrete_option_set::operator()(string const & names,␊ |
141 | string const & desc,␊ |
142 | boost::function<void (string)> set,␊ |
143 | boost::function<void ()> reset)␊ |
144 | {␊ |
145 | options.insert(concrete_option(names, desc, true, set, reset));␊ |
146 | return *this;␊ |
147 | }␊ |
148 | ␊ |
149 | concrete_option_set␊ |
150 | concrete_option_set::operator | (concrete_option_set const & other) const␊ |
151 | {␊ |
152 | concrete_option_set combined;␊ |
153 | std::set_union(options.begin(), options.end(),␊ |
154 | other.options.begin(), other.options.end(),␊ |
155 | std::inserter(combined.options, combined.options.begin()));␊ |
156 | return combined;␊ |
157 | }␊ |
158 | ␊ |
159 | void concrete_option_set::reset() const␊ |
160 | {␊ |
161 | for (std::set<concrete_option>::const_iterator i = options.begin();␊ |
162 | i != options.end(); ++i)␊ |
163 | {␊ |
164 | if (i->resetter)␊ |
165 | i->resetter();␊ |
166 | }␊ |
167 | }␊ |
168 | ␊ |
169 | static void␊ |
170 | tokenize_for_command_line(string const & from, args_vector & to)␊ |
171 | {␊ |
172 | // Unfortunately, the tokenizer in basic_io is too format-specific␊ |
173 | to.clear();␊ |
174 | enum quote_type {none, one, two};␊ |
175 | string cur;␊ |
176 | quote_type type = none;␊ |
177 | bool have_tok(false);␊ |
178 | ␊ |
179 | for (string::const_iterator i = from.begin(); i != from.end(); ++i)␊ |
180 | {␊ |
181 | if (*i == '\'')␊ |
182 | {␊ |
183 | if (type == none)␊ |
184 | type = one;␊ |
185 | else if (type == one)␊ |
186 | type = none;␊ |
187 | else␊ |
188 | {␊ |
189 | cur += *i;␊ |
190 | have_tok = true;␊ |
191 | }␊ |
192 | }␊ |
193 | else if (*i == '"')␊ |
194 | {␊ |
195 | if (type == none)␊ |
196 | type = two;␊ |
197 | else if (type == two)␊ |
198 | type = none;␊ |
199 | else␊ |
200 | {␊ |
201 | cur += *i;␊ |
202 | have_tok = true;␊ |
203 | }␊ |
204 | }␊ |
205 | else if (*i == '\\')␊ |
206 | {␊ |
207 | if (type != one)␊ |
208 | ++i;␊ |
209 | N(i != from.end(), F("Invalid escape in --xargs file"));␊ |
210 | cur += *i;␊ |
211 | have_tok = true;␊ |
212 | }␊ |
213 | else if (string(" \n\t").find(*i) != string::npos)␊ |
214 | {␊ |
215 | if (type == none)␊ |
216 | {␊ |
217 | if (have_tok)␊ |
218 | to.push_back(arg_type(cur));␊ |
219 | cur.clear();␊ |
220 | have_tok = false;␊ |
221 | }␊ |
222 | else␊ |
223 | {␊ |
224 | cur += *i;␊ |
225 | have_tok = true;␊ |
226 | }␊ |
227 | }␊ |
228 | else␊ |
229 | {␊ |
230 | cur += *i;␊ |
231 | have_tok = true;␊ |
232 | }␊ |
233 | }␊ |
234 | if (have_tok)␊ |
235 | to.push_back(arg_type(cur));␊ |
236 | }␊ |
237 | ␊ |
238 | void concrete_option_set::from_command_line(int argc, char const * const * argv)␊ |
239 | {␊ |
240 | args_vector arguments;␊ |
241 | for (int i = 1; i < argc; ++i)␊ |
242 | arguments.push_back(arg_type(argv[i]));␊ |
243 | from_command_line(arguments, true);␊ |
244 | }␊ |
245 | ␊ |
246 | static concrete_option const &␊ |
247 | getopt(map<string, concrete_option> const & by_name, string const & name)␊ |
248 | {␊ |
249 | map<string, concrete_option>::const_iterator i = by_name.find(name);␊ |
250 | if (i != by_name.end())␊ |
251 | return i->second;␊ |
252 | else␊ |
253 | throw unknown_option(name);␊ |
254 | }␊ |
255 | ␊ |
256 | static map<string, concrete_option>␊ |
257 | get_by_name(std::set<concrete_option> const & options)␊ |
258 | {␊ |
259 | map<string, concrete_option> by_name;␊ |
260 | for (std::set<concrete_option>::const_iterator i = options.begin();␊ |
261 | i != options.end(); ++i)␊ |
262 | {␊ |
263 | if (!i->longname.empty())␊ |
264 | by_name.insert(make_pair(i->longname, *i));␊ |
265 | if (!i->shortname.empty())␊ |
266 | by_name.insert(make_pair(i->shortname, *i));␊ |
267 | }␊ |
268 | return by_name;␊ |
269 | }␊ |
270 | ␊ |
271 | void concrete_option_set::from_command_line(args_vector & args,␊ |
272 | bool allow_xargs)␊ |
273 | {␊ |
274 | map<string, concrete_option> by_name = get_by_name(options);␊ |
275 | ␊ |
276 | bool seen_dashdash = false;␊ |
277 | for (args_vector::size_type i = 0; i < args.size(); ++i)␊ |
278 | {␊ |
279 | concrete_option o;␊ |
280 | string name;␊ |
281 | arg_type arg;␊ |
282 | bool separate_arg(false);␊ |
283 | if (idx(args,i)() == "--" || seen_dashdash)␊ |
284 | {␊ |
285 | if (!seen_dashdash)␊ |
286 | {␊ |
287 | seen_dashdash = true;␊ |
288 | allow_xargs = false;␊ |
289 | continue;␊ |
290 | }␊ |
291 | name = "--";␊ |
292 | o = getopt(by_name, name);␊ |
293 | arg = idx(args,i);␊ |
294 | }␊ |
295 | else if (idx(args,i)().substr(0,2) == "--")␊ |
296 | {␊ |
297 | string::size_type equals = idx(args,i)().find('=');␊ |
298 | if (equals == string::npos)␊ |
299 | name = idx(args,i)().substr(2);␊ |
300 | else␊ |
301 | name = idx(args,i)().substr(2, equals-2);␊ |
302 | ␊ |
303 | o = getopt(by_name, name);␊ |
304 | if (!o.has_arg && equals != string::npos)␊ |
305 | throw extra_arg(name);␊ |
306 | ␊ |
307 | if (o.has_arg)␊ |
308 | {␊ |
309 | if (equals == string::npos)␊ |
310 | {␊ |
311 | separate_arg = true;␊ |
312 | if (i+1 == args.size())␊ |
313 | throw missing_arg(name);␊ |
314 | arg = idx(args,i+1);␊ |
315 | }␊ |
316 | else␊ |
317 | arg = arg_type(idx(args,i)().substr(equals+1));␊ |
318 | }␊ |
319 | }␊ |
320 | else if (idx(args,i)().substr(0,1) == "-")␊ |
321 | {␊ |
322 | name = idx(args,i)().substr(1,1);␊ |
323 | ␊ |
324 | o = getopt(by_name, name);␊ |
325 | if (!o.has_arg && idx(args,i)().size() != 2)␊ |
326 | throw extra_arg(name);␊ |
327 | ␊ |
328 | if (o.has_arg)␊ |
329 | {␊ |
330 | if (idx(args,i)().size() == 2)␊ |
331 | {␊ |
332 | separate_arg = true;␊ |
333 | if (i+1 == args.size())␊ |
334 | throw missing_arg(name);␊ |
335 | arg = idx(args,i+1);␊ |
336 | }␊ |
337 | else␊ |
338 | arg = arg_type(idx(args,i)().substr(2));␊ |
339 | }␊ |
340 | }␊ |
341 | else␊ |
342 | {␊ |
343 | name = "--";␊ |
344 | o = getopt(by_name, name);␊ |
345 | arg = idx(args,i);␊ |
346 | }␊ |
347 | ␊ |
348 | if (allow_xargs && (name == "xargs" || name == "@"))␊ |
349 | {␊ |
350 | // expand the --xargs in place␊ |
351 | data dat;␊ |
352 | read_data_for_command_line(arg, dat);␊ |
353 | args_vector fargs;␊ |
354 | tokenize_for_command_line(dat(), fargs);␊ |
355 | ␊ |
356 | args.erase(args.begin() + i);␊ |
357 | if (separate_arg)␊ |
358 | args.erase(args.begin() + i);␊ |
359 | args.insert(args.begin()+i, fargs.begin(), fargs.end());␊ |
360 | --i;␊ |
361 | }␊ |
362 | else␊ |
363 | {␊ |
364 | if (separate_arg)␊ |
365 | ++i;␊ |
366 | try␊ |
367 | {␊ |
368 | if (o.setter)␊ |
369 | o.setter(arg());␊ |
370 | }␊ |
371 | catch (boost::bad_lexical_cast)␊ |
372 | {␊ |
373 | throw bad_arg(o.longname, arg);␊ |
374 | }␊ |
375 | catch (bad_arg_internal & e)␊ |
376 | {␊ |
377 | if (e.reason == "")␊ |
378 | throw bad_arg(o.longname, arg);␊ |
379 | else␊ |
380 | throw bad_arg(o.longname, arg, e.reason);␊ |
381 | }␊ |
382 | }␊ |
383 | }␊ |
384 | }␊ |
385 | ␊ |
386 | void concrete_option_set::from_key_value_pairs(vector<pair<string, string> > const & keyvals)␊ |
387 | {␊ |
388 | map<string, concrete_option> by_name = get_by_name(options);␊ |
389 | ␊ |
390 | for (vector<pair<string, string> >::const_iterator i = keyvals.begin();␊ |
391 | i != keyvals.end(); ++i)␊ |
392 | {␊ |
393 | string const & key(i->first);␊ |
394 | arg_type const & value(arg_type(i->second));␊ |
395 | ␊ |
396 | concrete_option o = getopt(by_name, key);␊ |
397 | ␊ |
398 | try␊ |
399 | {␊ |
400 | if (o.setter)␊ |
401 | o.setter(value());␊ |
402 | }␊ |
403 | catch (boost::bad_lexical_cast)␊ |
404 | {␊ |
405 | throw bad_arg(o.longname, value);␊ |
406 | }␊ |
407 | catch (bad_arg_internal & e)␊ |
408 | {␊ |
409 | if (e.reason == "")␊ |
410 | throw bad_arg(o.longname, value);␊ |
411 | else␊ |
412 | throw bad_arg(o.longname, value, e.reason);␊ |
413 | }␊ |
414 | }␊ |
415 | }␊ |
416 | ␊ |
417 | // Get the non-description part of the usage string,␊ |
418 | // looks like "--long [ -s ] <arg>".␊ |
419 | static string usagestr(concrete_option const & opt)␊ |
420 | {␊ |
421 | string out;␊ |
422 | if (opt.longname == "--")␊ |
423 | return "";␊ |
424 | if (!opt.longname.empty() && !opt.shortname.empty())␊ |
425 | out = "--" + opt.longname + " [ -" + opt.shortname + " ]";␊ |
426 | else if (!opt.longname.empty())␊ |
427 | out = "--" + opt.longname;␊ |
428 | else if (!opt.shortname.empty())␊ |
429 | out = "-" + opt.shortname;␊ |
430 | else␊ |
431 | return "";␊ |
432 | if (opt.has_arg)␊ |
433 | return out + " <arg>";␊ |
434 | else␊ |
435 | return out;␊ |
436 | }␊ |
437 | ␊ |
438 | std::string concrete_option_set::get_usage_str() const␊ |
439 | {␊ |
440 | unsigned int namelen = 0; // the longest option name string␊ |
441 | for (std::set<concrete_option>::const_iterator i = options.begin();␊ |
442 | i != options.end(); ++i)␊ |
443 | {␊ |
444 | string names = usagestr(*i);␊ |
445 | if (names.size() > namelen)␊ |
446 | namelen = names.size();␊ |
447 | }␊ |
448 | ␊ |
449 | // " --long [ -s ] <arg> description goes here"␊ |
450 | // ^ ^^ ^^ ^^ ^␊ |
451 | // | | \ namelen / | | \ descwidth /| <- edge of screen␊ |
452 | // ^^^^ ^^^^␊ |
453 | // pre_indent space␊ |
454 | string result;␊ |
455 | int pre_indent = 2; // empty space on the left␊ |
456 | int space = 2; // space after the longest option, before the description␊ |
457 | int termwidth = guess_terminal_width();␊ |
458 | int descindent = pre_indent + namelen + space;␊ |
459 | int descwidth = termwidth - descindent;␊ |
460 | for (std::set<concrete_option>::const_iterator i = options.begin();␊ |
461 | i != options.end(); ++i)␊ |
462 | {␊ |
463 | string names = usagestr(*i);␊ |
464 | if (names.empty())␊ |
465 | continue;␊ |
466 | ␊ |
467 | result += string(pre_indent, ' ')␊ |
468 | + names + string(namelen - names.size(), ' ');␊ |
469 | ␊ |
470 | if (!i->description.empty())␊ |
471 | {␊ |
472 | result += string(space, ' ');␊ |
473 | result += format_text(i->description, descindent, descindent);␊ |
474 | }␊ |
475 | ␊ |
476 | result += '\n';␊ |
477 | }␊ |
478 | return result;␊ |
479 | }␊ |
480 | ␊ |
481 | } // namespace option␊ |
482 | ␊ |
483 | ␊ |
484 | #ifdef BUILD_UNIT_TESTS␊ |
485 | #include "unit_tests.hh"␊ |
486 | ␊ |
487 | UNIT_TEST(option, concrete_options)␊ |
488 | {␊ |
489 | bool b = false;␊ |
490 | string s;␊ |
491 | int i = -1;␊ |
492 | vector<string> v;␊ |
493 | ␊ |
494 | option::concrete_option_set os;␊ |
495 | os("--", "", option::setter(v), option::resetter(v))␊ |
496 | ("bool,b", "", option::setter(b), option::resetter(b, false))␊ |
497 | ("s", "", option::setter(s))␊ |
498 | ("int", "", option::setter(i));␊ |
499 | ␊ |
500 | {␊ |
501 | char const * cmdline[] = {"progname", "pos", "-s", "str ing", "--int", "10",␊ |
502 | "--int", "45", "--", "--bad", "foo", "-b"};␊ |
503 | os.from_command_line(12, cmdline);␊ |
504 | }␊ |
505 | UNIT_TEST_CHECK(!b);␊ |
506 | UNIT_TEST_CHECK(i == 45);␊ |
507 | UNIT_TEST_CHECK(s == "str ing");␊ |
508 | UNIT_TEST_CHECK(v.size() == 4);// pos --bad foo -b␊ |
509 | os.reset();␊ |
510 | UNIT_TEST_CHECK(v.empty());␊ |
511 | ␊ |
512 | {␊ |
513 | args_vector cmdline;␊ |
514 | cmdline.push_back(arg_type("--bool"));␊ |
515 | cmdline.push_back(arg_type("-s"));␊ |
516 | cmdline.push_back(arg_type("-s"));␊ |
517 | cmdline.push_back(arg_type("foo"));␊ |
518 | os.from_command_line(cmdline);␊ |
519 | }␊ |
520 | UNIT_TEST_CHECK(b);␊ |
521 | UNIT_TEST_CHECK(s == "-s");␊ |
522 | UNIT_TEST_CHECK(v.size() == 1);␊ |
523 | UNIT_TEST_CHECK(v[0] == "foo");␊ |
524 | os.reset();␊ |
525 | UNIT_TEST_CHECK(!b);␊ |
526 | ␊ |
527 | {␊ |
528 | char const * cmdline[] = {"progname", "--bad_arg", "x"};␊ |
529 | UNIT_TEST_CHECK_THROW(os.from_command_line(3, cmdline), option::unknown_option);␊ |
530 | }␊ |
531 | ␊ |
532 | {␊ |
533 | char const * cmdline[] = {"progname", "--bool=x"};␊ |
534 | UNIT_TEST_CHECK_THROW(os.from_command_line(2, cmdline), option::extra_arg);␊ |
535 | }␊ |
536 | ␊ |
537 | {␊ |
538 | char const * cmdline[] = {"progname", "-bx"};␊ |
539 | UNIT_TEST_CHECK_THROW(os.from_command_line(2, cmdline), option::extra_arg);␊ |
540 | }␊ |
541 | ␊ |
542 | {␊ |
543 | char const * cmdline[] = {"progname", "-s"};␊ |
544 | UNIT_TEST_CHECK_THROW(os.from_command_line(2, cmdline), option::missing_arg);␊ |
545 | }␊ |
546 | ␊ |
547 | {␊ |
548 | char const * cmdline[] = {"progname", "--int=x"};␊ |
549 | UNIT_TEST_CHECK_THROW(os.from_command_line(2, cmdline), option::bad_arg);␊ |
550 | }␊ |
551 | }␊ |
552 | ␊ |
553 | #endif // BUILD_UNIT_TESTS␊ |
554 | ␊ |
555 | // Local Variables:␊ |
556 | // mode: C++␊ |
557 | // fill-column: 76␊ |
558 | // c-file-style: "gnu"␊ |
559 | // indent-tabs-mode: nil␊ |
560 | // End:␊ |
561 | // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:␊ |
562 | ␊ |