monotone

monotone Mtn Source Tree

Root/option.cc

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

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status