monotone

monotone Mtn Source Tree

Root/option.cc

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

Archive Download this file

Branches

Tags

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