1 // Copyright (C) 2006 Timothy Brownawell <tbrownaw@gmail.com>
2 //
3 // This program is made available under the GNU GPL version 2.0 or
4 // greater. See the accompanying file COPYING for details.
5 //
6 // This program is distributed WITHOUT ANY WARRANTY; without even the
7 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8 // PURPOSE.
9
10 #include "base.hh"
11 #include "file_io.hh"
12 #include "option.hh"
13 #include "sanity.hh"
14
15 using std::map;
16 using std::pair;
17 using std::set;
18 using std::string;
19 using std::vector;
20
21 namespace option {
22
option_error(std::string const & str)23 option_error::option_error(std::string const & str)
24 : std::invalid_argument((F("option error: %s") % str).str())
25 {}
26
unknown_option(std::string const & opt)27 unknown_option::unknown_option(std::string const & opt)
28 : option_error((F("unknown option '%s'") % opt).str())
29 {}
30
missing_arg(std::string const & opt)31 missing_arg::missing_arg(std::string const & opt)
32 : option_error((F("missing argument to option '%s'") % opt).str())
33 {}
34
extra_arg(std::string const & opt)35 extra_arg::extra_arg(std::string const & opt)
36 : option_error((F("option '%s' does not take an argument") % opt).str())
37 {}
38
bad_arg(std::string const & opt,arg_type const & arg)39 bad_arg::bad_arg(std::string const & opt, arg_type const & arg)
40 : option_error((F("bad argument '%s' to option '%s'") % arg() % opt).str())
41 {}
42
bad_arg(std::string const & opt,arg_type const & arg,std::string const & reason)43 bad_arg::bad_arg(std::string const & opt,
44 arg_type const & arg,
45 std::string const & reason)
46 : option_error((F("bad argument '%s' to option '%s': %s")
47 % arg() % opt % reason).str())
48 {}
49
bad_arg_internal(string const & str)50 bad_arg_internal::bad_arg_internal(string const & str)
51 : reason(str)
52 {}
53
54
55
splitname(char const * f,string & name,string & n,string & cancel)56 void splitname(char const * f, string & name, string & n, string & cancel)
57 {
58 string from(f);
59 if (from.find("/") != string::npos)
60 {
61 string::size_type slash = from.find("/");
62 cancel = from.substr(slash+1);
63 from.erase(slash);
64 }
65 // from looks like "foo" or "foo,f"
66 string::size_type comma = from.find(',');
67 name = from.substr(0, comma);
68 if (comma != string::npos)
69 n = from.substr(comma+1, 1);
70 else
71 n = "";
72
73 // "o" is equivalent to ",o"; it gives an option
74 // with only a short name
75 if (name.size() == 1)
76 {
77 I(n.empty());
78 n = name;
79 name = "";
80 }
81 }
82
83
concrete_option()84 concrete_option::concrete_option()
85 : has_arg(false)
86 {}
87
concrete_option(char const * names,char const * desc,bool arg,boost::function<void (std::string)> set,boost::function<void ()> reset,bool hide,char const * deprecate)88 concrete_option::concrete_option(char const * names,
89 char const * desc,
90 bool arg,
91 boost::function<void (std::string)> set,
92 boost::function<void ()> reset,
93 bool hide,
94 char const * deprecate)
95 {
96 description = desc;
97 splitname(names, longname, shortname, cancelname);
98 I((desc && desc[0]) || !longname.empty() || !shortname.empty());
99 // not sure how to display if it can only be reset (and what would that mean?)
100 I((!longname.empty() || !shortname.empty()) || cancelname.empty());
101 // If an option has a name (ie, can be set), it must have a setter function
102 I(set || (longname.empty() && shortname.empty()));
103 // If an option can be canceled, it must have a resetter function
104 I(reset || cancelname.empty());
105 has_arg = arg;
106 setter = set;
107 resetter = reset;
108 hidden = hide;
109 deprecated = deprecate;
110 }
111
operator <(concrete_option const & other) const112 bool concrete_option::operator<(concrete_option const & other) const
113 {
114 if (longname != other.longname)
115 return longname < other.longname;
116 if (shortname != other.shortname)
117 return shortname < other.shortname;
118 if (cancelname != other.cancelname)
119 return cancelname < other.cancelname;
120 return description < other.description;
121 }
122
123 concrete_option_set
operator |(concrete_option const & a,concrete_option const & b)124 operator | (concrete_option const & a, concrete_option const & b)
125 {
126 return concrete_option_set(a) | b;
127 }
128
concrete_option_set()129 concrete_option_set::concrete_option_set()
130 {}
131
concrete_option_set(std::set<concrete_option> const & other)132 concrete_option_set::concrete_option_set(std::set<concrete_option> const & other)
133 : options(other)
134 {}
135
concrete_option_set(concrete_option const & opt)136 concrete_option_set::concrete_option_set(concrete_option const & opt)
137 {
138 options.insert(opt);
139 }
140
141 // essentially the opposite of std::bind1st
142 class discard_argument
143 {
144 boost::function<void()> functor;
145 public:
discard_argument(boost::function<void ()> const & from)146 discard_argument(boost::function<void()> const & from)
147 : functor(from)
148 {}
operator ()(std::string const &)149 void operator()(std::string const &)
150 { return functor(); }
151 };
152
153 concrete_option_set &
operator ()(char const * names,char const * desc,boost::function<void ()> set,boost::function<void ()> reset,bool hide,char const * deprecate)154 concrete_option_set::operator()(char const * names,
155 char const * desc,
156 boost::function<void ()> set,
157 boost::function<void ()> reset,
158 bool hide,
159 char const * deprecate)
160 {
161 options.insert(concrete_option(names, desc, false, discard_argument(set),
162 reset, hide, deprecate));
163 return *this;
164 }
165
166 concrete_option_set &
operator ()(char const * names,char const * desc,boost::function<void (string)> set,boost::function<void ()> reset,bool hide,char const * deprecate)167 concrete_option_set::operator()(char const * names,
168 char const * desc,
169 boost::function<void (string)> set,
170 boost::function<void ()> reset,
171 bool hide,
172 char const * deprecate)
173 {
174 options.insert(concrete_option(names, desc, true, set, reset, hide, deprecate));
175 return *this;
176 }
177
178 concrete_option_set
operator |(concrete_option_set const & other) const179 concrete_option_set::operator | (concrete_option_set const & other) const
180 {
181 concrete_option_set combined;
182 std::set_union(options.begin(), options.end(),
183 other.options.begin(), other.options.end(),
184 std::inserter(combined.options, combined.options.begin()));
185 return combined;
186 }
187
reset() const188 void concrete_option_set::reset() const
189 {
190 for (std::set<concrete_option>::const_iterator i = options.begin();
191 i != options.end(); ++i)
192 {
193 if (i->resetter)
194 i->resetter();
195 }
196 }
197
198 static void
tokenize_for_command_line(string const & from,args_vector & to)199 tokenize_for_command_line(string const & from, args_vector & to)
200 {
201 // Unfortunately, the tokenizer in basic_io is too format-specific
202 to.clear();
203 enum quote_type {none, one, two};
204 string cur;
205 quote_type type = none;
206 bool have_tok(false);
207
208 for (string::const_iterator i = from.begin(); i != from.end(); ++i)
209 {
210 if (*i == '\'')
211 {
212 if (type == none)
213 type = one;
214 else if (type == one)
215 type = none;
216 else
217 {
218 cur += *i;
219 have_tok = true;
220 }
221 }
222 else if (*i == '"')
223 {
224 if (type == none)
225 type = two;
226 else if (type == two)
227 type = none;
228 else
229 {
230 cur += *i;
231 have_tok = true;
232 }
233 }
234 else if (*i == '\\')
235 {
236 if (type != one)
237 ++i;
238 E(i != from.end(), origin::user, F("invalid escape in '--xargs' file"));
239 cur += *i;
240 have_tok = true;
241 }
242 else if (string(" \n\t").find(*i) != string::npos)
243 {
244 if (type == none)
245 {
246 if (have_tok)
247 to.push_back(arg_type(cur, origin::user));
248 cur.clear();
249 have_tok = false;
250 }
251 else
252 {
253 cur += *i;
254 have_tok = true;
255 }
256 }
257 else
258 {
259 cur += *i;
260 have_tok = true;
261 }
262 }
263 if (have_tok)
264 to.push_back(arg_type(cur, origin::user));
265 }
266
from_command_line(int argc,char const * const * argv)267 void concrete_option_set::from_command_line(int argc,
268 char const * const * argv)
269 {
270 args_vector arguments;
271 for (int i = 1; i < argc; ++i)
272 arguments.push_back(arg_type(argv[i], origin::user));
273 from_command_line(arguments);
274 }
275
276 // checks a multi-word option like 'no-builtin-rcfile' against a
277 // possible abbreviated given option 'nbr' which is only compiled
278 // of the first character of each word
279 static bool
abbrev_match(string const & option,string const & part)280 abbrev_match(string const & option, string const & part)
281 {
282 if (option.find('-') == 0)
283 return false;
284
285 string::const_iterator it = option.begin();
286 string opt_part(1, *it);
287 for (; it != option.end(); ++it)
288 {
289 if (*it == '-' && it != option.end())
290 opt_part += *(it+1);
291 }
292
293 return part == opt_part;
294 }
295
296 static concrete_option const &
getopt(map<string,concrete_option> const & by_name,string & name)297 getopt(map<string, concrete_option> const & by_name, string & name)
298 {
299 // try to match the option name as a whole first, so if the user
300 // specified "--foo" and we have "--foo" and "--foo-bar", don't
301 // display both choices
302 map<string, concrete_option>::const_iterator i = by_name.find(name);
303 if (i != by_name.end())
304 return i->second;
305
306 if (name.size() == 0)
307 throw unknown_option(name);
308
309 // try to find the option by partial name
310 set<string> candidates;
311 for (i = by_name.begin(); i != by_name.end(); ++i)
312 {
313 if (i->first.find(name) == 0)
314 candidates.insert(i->first);
315 if (abbrev_match(i->first, name))
316 candidates.insert(i->first);
317 }
318
319 if (candidates.size() == 0)
320 throw unknown_option(name);
321
322 if (candidates.size() == 1)
323 {
324 string expanded_name = *candidates.begin();
325 i = by_name.find(expanded_name);
326 I(i != by_name.end());
327 L(FL("expanding option '%s' to '%s'") % name % expanded_name);
328 name = expanded_name;
329 return i->second;
330 }
331
332 string err = (F("option '%s' has multiple ambiguous expansions:")
333 % name).str();
334
335 for (set<string>::const_iterator j = candidates.begin();
336 j != candidates.end(); ++j)
337 {
338 i = by_name.find(*j);
339 I(i != by_name.end());
340
341 if (*j == "--")
342 continue;
343
344 if (*j == i->second.shortname)
345 err += "\n'-" + *j + "' (" + i->second.description + ")";
346 else if (*j == i->second.cancelname)
347 err += "\n'--" + *j + "' (" + (F("negation of '--%s'") % i->second.longname).str() + ")";
348 else
349 err += "\n'--" + *j + "' (" + i->second.description + ")";
350 }
351
352 E(false, origin::user, i18n_format(err));
353 }
354
355 // helper for get_by_name
356 // Make sure that either:
357 // * There are no duplicate options, or
358 // * If we're only parsing options (and not applying them), any duplicates
359 // are consistent WRT whether they take an option
360 typedef pair<map<string, concrete_option>::iterator, bool> by_name_res_type;
check_by_name_insertion(by_name_res_type const & res,concrete_option const & opt,concrete_option_set::preparse_flag pf)361 static void check_by_name_insertion(by_name_res_type const & res,
362 concrete_option const & opt,
363 concrete_option_set::preparse_flag pf)
364 {
365 switch (pf)
366 {
367 case concrete_option_set::preparse:
368 if (!res.second)
369 {
370 string const & name = res.first->first;
371 concrete_option const & them = res.first->second;
372 bool const i_have_arg = (name != opt.cancelname && opt.has_arg);
373 bool const they_have_arg = (name != them.cancelname && them.has_arg);
374 I(i_have_arg == they_have_arg);
375 }
376 break;
377 case concrete_option_set::no_preparse:
378 I(res.second);
379 break;
380 }
381 }
382
383 // generate an index that lets us look options up by name
384 static map<string, concrete_option>
get_by_name(std::set<concrete_option> const & options,concrete_option_set::preparse_flag pf)385 get_by_name(std::set<concrete_option> const & options,
386 concrete_option_set::preparse_flag pf)
387 {
388 map<string, concrete_option> by_name;
389 for (std::set<concrete_option>::const_iterator i = options.begin();
390 i != options.end(); ++i)
391 {
392 if (!i->longname.empty())
393 check_by_name_insertion(by_name.insert(make_pair(i->longname, *i)),
394 *i, pf);
395 if (!i->shortname.empty())
396 check_by_name_insertion(by_name.insert(make_pair(i->shortname, *i)),
397 *i, pf);
398 if (!i->cancelname.empty())
399 check_by_name_insertion(by_name.insert(make_pair(i->cancelname, *i)),
400 *i, pf);
401 }
402 return by_name;
403 }
404
from_command_line(args_vector & args,preparse_flag pf)405 void concrete_option_set::from_command_line(args_vector & args,
406 preparse_flag pf)
407 {
408 map<string, concrete_option> by_name = get_by_name(options, pf);
409
410 bool seen_dashdash = false;
411 for (args_vector::size_type i = 0; i < args.size(); ++i)
412 {
413 concrete_option o;
414 string name;
415 arg_type arg;
416 bool is_cancel;
417 bool separate_arg(false);
418 if (idx(args,i)() == "--" || seen_dashdash)
419 {
420 if (!seen_dashdash)
421 {
422 seen_dashdash = true;
423 continue;
424 }
425 name = "--";
426 o = getopt(by_name, name);
427 arg = idx(args,i);
428 is_cancel = false;
429 }
430 else if (idx(args,i)().substr(0,2) == "--")
431 {
432 string::size_type equals = idx(args,i)().find('=');
433 if (equals == string::npos)
434 name = idx(args,i)().substr(2);
435 else
436 name = idx(args,i)().substr(2, equals-2);
437
438 o = getopt(by_name, name);
439 is_cancel = (name == o.cancelname);
440 if ((!o.has_arg || is_cancel) && equals != string::npos)
441 throw extra_arg(name);
442
443 if (o.has_arg && !is_cancel)
444 {
445 if (equals == string::npos)
446 {
447 separate_arg = true;
448 if (i+1 == args.size())
449 throw missing_arg(name);
450 arg = idx(args,i+1);
451 }
452 else
453 arg = arg_type(idx(args,i)().substr(equals+1), origin::user);
454 }
455 }
456 else if (idx(args,i)().substr(0,1) == "-")
457 {
458 name = idx(args,i)().substr(1,1);
459
460 map<string, concrete_option>::const_iterator j = by_name.find(name);
461 if (j == by_name.end())
462 throw unknown_option(name);
463 o = j->second;
464
465 is_cancel = (name == o.cancelname);
466 I(!is_cancel);
467 if (!o.has_arg && idx(args,i)().size() != 2)
468 throw extra_arg(name);
469
470 if (o.has_arg)
471 {
472 if (idx(args,i)().size() == 2)
473 {
474 separate_arg = true;
475 if (i+1 == args.size())
476 throw missing_arg(name);
477 arg = idx(args,i+1);
478 }
479 else
480 arg = arg_type(idx(args,i)().substr(2), origin::user);
481 }
482 }
483 else
484 {
485 name = "--";
486 o = getopt(by_name, name);
487 arg = idx(args,i);
488 is_cancel = false;
489 }
490
491 if (name == "xargs" || name == "@")
492 {
493 // expand the --xargs in place
494 data dat;
495 read_data_for_command_line(arg, dat);
496 args_vector fargs;
497 tokenize_for_command_line(dat(), fargs);
498
499 args.erase(args.begin() + i);
500 if (separate_arg)
501 args.erase(args.begin() + i);
502 args.insert(args.begin()+i, fargs.begin(), fargs.end());
503 --i;
504 }
505 else
506 {
507 if (separate_arg)
508 ++i;
509 try
510 {
511 if (o.deprecated)
512 W(F("deprecated option '%s' used: %s")
513 % o.longname % gettext(o.deprecated));
514 if (!is_cancel)
515 {
516 if (o.setter)
517 o.setter(arg());
518 }
519 else
520 {
521 if (o.resetter)
522 o.resetter();
523 }
524 }
525 catch (boost::bad_lexical_cast)
526 {
527 throw bad_arg(o.longname, arg);
528 }
529 catch (bad_arg_internal & e)
530 {
531 if (e.reason == "")
532 throw bad_arg(o.longname, arg);
533 else
534 throw bad_arg(o.longname, arg, e.reason);
535 }
536 }
537 }
538 }
539
from_key_value_pairs(vector<pair<string,string>> const & keyvals)540 void concrete_option_set::from_key_value_pairs(vector<pair<string, string> > const & keyvals)
541 {
542 map<string, concrete_option> by_name = get_by_name(options, no_preparse);
543
544 for (vector<pair<string, string> >::const_iterator i = keyvals.begin();
545 i != keyvals.end(); ++i)
546 {
547 string key(i->first);
548 arg_type const & value(arg_type(i->second, origin::user));
549
550 concrete_option o = getopt(by_name, key);
551 bool const is_cancel = (key == o.cancelname);
552
553 try
554 {
555 if (o.deprecated)
556 W(F("deprecated option '%s' used: %s")
557 % o.longname % gettext(o.deprecated));
558
559 if (!is_cancel)
560 {
561 if (o.setter)
562 o.setter(value());
563 }
564 else
565 {
566 if (o.resetter)
567 o.resetter();
568 }
569 }
570 catch (boost::bad_lexical_cast)
571 {
572 throw bad_arg(o.longname, value);
573 }
574 catch (bad_arg_internal & e)
575 {
576 if (e.reason == "")
577 throw bad_arg(o.longname, value);
578 else
579 throw bad_arg(o.longname, value, e.reason);
580 }
581 }
582 }
583
584 // Get the non-description part of the usage string,
585 // looks like "--long [ -s ] <arg> / --cancel".
usagestr(concrete_option const & opt)586 static string usagestr(concrete_option const & opt)
587 {
588 string out;
589 if (opt.longname == "--")
590 return "";
591 if (!opt.longname.empty() && !opt.shortname.empty())
592 out = "--" + opt.longname + " [ -" + opt.shortname + " ]";
593 else if (!opt.longname.empty())
594 out = "--" + opt.longname;
595 else if (!opt.shortname.empty())
596 out = "-" + opt.shortname;
597
598 if (out.empty())
599 return out;
600
601 if (opt.has_arg)
602 out += " <arg>";
603
604 if (!opt.cancelname.empty())
605 {
606 if (!out.empty())
607 out += " / ";
608 out += "--" + opt.cancelname;
609 }
610
611 return out;
612 }
613
614 void
get_usage_strings(vector<string> & names,vector<string> & descriptions,unsigned int & maxnamelen,bool show_hidden) const615 concrete_option_set::get_usage_strings(vector<string> & names,
616 vector<string> & descriptions,
617 unsigned int & maxnamelen,
618 bool show_hidden) const
619 {
620 unsigned int namelen = 0; // the longest option name string
621 names.clear();
622 descriptions.clear();
623 for (std::set<concrete_option>::const_iterator i = options.begin();
624 i != options.end(); ++i)
625 {
626 if (i->hidden && !show_hidden)
627 continue;
628 if (i->deprecated)
629 continue;
630 string name = usagestr(*i);
631 if (name.size() > namelen)
632 namelen = name.size();
633 names.push_back(name);
634 descriptions.push_back(gettext(i->description));
635 }
636 maxnamelen = namelen;
637 }
638
639 } // namespace option
640
641
642
643 // Local Variables:
644 // mode: C++
645 // fill-column: 76
646 // c-file-style: "gnu"
647 // indent-tabs-mode: nil
648 // End:
649 // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
650