monotone

monotone Mtn Source Tree

Root/option.cc

1
2#include "base.hh"
3#include "file_io.hh"
4#include "option.hh"
5#include "sanity.hh"
6#include "ui.hh"
7
8using std::map;
9using std::pair;
10using std::set;
11using std::string;
12using std::vector;
13
14namespace option {
15
16option_error::option_error(std::string const & str)
17 : std::invalid_argument((F("option error: %s") % str).str())
18{}
19
20unknown_option::unknown_option(std::string const & opt)
21 : option_error((F("unknown option '%s'") % opt).str())
22{}
23
24missing_arg::missing_arg(std::string const & opt)
25 : option_error((F("missing argument to option '%s'") % opt).str())
26{}
27
28extra_arg::extra_arg(std::string const & opt)
29 : option_error((F("option '%s' does not take an argument") % opt).str())
30{}
31
32bad_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
36bad_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
43bad_arg_internal::bad_arg_internal(string const & str)
44 : reason(str)
45{}
46
47
48
49void 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
70concrete_option::concrete_option()
71 : has_arg(false)
72{}
73
74concrete_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
90bool 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
99concrete_option_set
100operator | (concrete_option const & a, concrete_option const & b)
101{
102 return concrete_option_set(a) | b;
103}
104
105concrete_option_set::concrete_option_set()
106{}
107
108concrete_option_set::concrete_option_set(std::set<concrete_option> const & other)
109 : options(other)
110{}
111
112concrete_option_set::concrete_option_set(concrete_option const & opt)
113{
114 options.insert(opt);
115}
116
117// essentially the opposite of std::bind1st
118class 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
129concrete_option_set &
130concrete_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
139concrete_option_set &
140concrete_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
149concrete_option_set
150concrete_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
159void 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
169static void
170tokenize_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
238void 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
246static concrete_option const &
247getopt(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
256static map<string, concrete_option>
257get_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
271void 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
386void 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>".
419static 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
438std::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
487UNIT_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

Archive Download this file

Branches

Tags

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