1 | // Copyright Vladimir Prus 2002-2004. |
2 | // Distributed under the Boost Software License, Version 1.0. |
3 | // (See accompanying file LICENSE_1_0.txt |
4 | // or copy at http://www.boost.org/LICENSE_1_0.txt) |
5 | |
6 | #define BOOST_PROGRAM_OPTIONS_SOURCE |
7 | #include <boost/program_options/config.hpp> |
8 | |
9 | #include <boost/config.hpp> |
10 | |
11 | #include <boost/program_options/detail/cmdline.hpp> |
12 | #include <boost/program_options/errors.hpp> |
13 | #include <boost/program_options/value_semantic.hpp> |
14 | #include <boost/program_options/options_description.hpp> |
15 | #include <boost/program_options/positional_options.hpp> |
16 | #include <boost/throw_exception.hpp> |
17 | |
18 | #include <boost/bind.hpp> |
19 | |
20 | #include <string> |
21 | #include <utility> |
22 | #include <vector> |
23 | #include <cassert> |
24 | #include <cstring> |
25 | #include <cctype> |
26 | #include <climits> |
27 | |
28 | #include <cstdio> |
29 | |
30 | #include <iostream> |
31 | |
32 | namespace boost { namespace program_options { |
33 | |
34 | using namespace std; |
35 | using namespace boost::program_options::command_line_style; |
36 | |
37 | |
38 | string |
39 | invalid_syntax::get_template(kind_t kind) |
40 | { |
41 | // Initially, store the message in 'const char*' variable, |
42 | // to avoid conversion to string in all cases. |
43 | const char* msg; |
44 | switch(kind) |
45 | { |
46 | case empty_adjacent_parameter: |
47 | msg = "the argument for option '%canonical_option%' should follow immediately after the equal sign" ; |
48 | break; |
49 | case missing_parameter: |
50 | msg = "the required argument for option '%canonical_option%' is missing" ; |
51 | break; |
52 | case unrecognized_line: |
53 | msg = "the options configuration file contains an invalid line '%invalid_line%'" ; |
54 | break; |
55 | // none of the following are currently used: |
56 | case long_not_allowed: |
57 | msg = "the unabbreviated option '%canonical_option%' is not valid" ; |
58 | break; |
59 | case long_adjacent_not_allowed: |
60 | msg = "the unabbreviated option '%canonical_option%' does not take any arguments" ; |
61 | break; |
62 | case short_adjacent_not_allowed: |
63 | msg = "the abbreviated option '%canonical_option%' does not take any arguments" ; |
64 | break; |
65 | case extra_parameter: |
66 | msg = "option '%canonical_option%' does not take any arguments" ; |
67 | break; |
68 | default: |
69 | msg = "unknown command line syntax error for '%s'" ; |
70 | } |
71 | return msg; |
72 | } |
73 | |
74 | |
75 | }} |
76 | |
77 | |
78 | namespace boost { namespace program_options { namespace detail { |
79 | |
80 | // vc6 needs this, but borland chokes when this is added. |
81 | #if BOOST_WORKAROUND(_MSC_VER, < 1300) |
82 | using namespace std; |
83 | using namespace program_options; |
84 | #endif |
85 | |
86 | |
87 | cmdline::cmdline(const vector<string>& args) |
88 | { |
89 | init(args); |
90 | } |
91 | |
92 | cmdline::cmdline(int argc, const char*const * argv) |
93 | { |
94 | #if defined(BOOST_NO_TEMPLATED_ITERATOR_CONSTRUCTORS) |
95 | vector<string> args; |
96 | copy(argv+1, argv+argc+!argc, inserter(args, args.end())); |
97 | init(args); |
98 | #else |
99 | init(vector<string>(argv+1, argv+argc+!argc)); |
100 | #endif |
101 | } |
102 | |
103 | void |
104 | cmdline::init(const vector<string>& args) |
105 | { |
106 | this->m_args = args; |
107 | m_style = command_line_style::default_style; |
108 | m_desc = 0; |
109 | m_positional = 0; |
110 | m_allow_unregistered = false; |
111 | } |
112 | |
113 | void |
114 | cmdline::style(int style) |
115 | { |
116 | if (style == 0) |
117 | style = default_style; |
118 | |
119 | check_style(style); |
120 | this->m_style = style_t(style); |
121 | } |
122 | |
123 | void |
124 | cmdline::allow_unregistered() |
125 | { |
126 | this->m_allow_unregistered = true; |
127 | } |
128 | |
129 | void |
130 | cmdline::check_style(int style) const |
131 | { |
132 | bool allow_some_long = |
133 | (style & allow_long) || (style & allow_long_disguise); |
134 | |
135 | const char* error = 0; |
136 | if (allow_some_long && |
137 | !(style & long_allow_adjacent) && !(style & long_allow_next)) |
138 | error = "boost::program_options misconfiguration: " |
139 | "choose one or other of 'command_line_style::long_allow_next' " |
140 | "(whitespace separated arguments) or " |
141 | "'command_line_style::long_allow_adjacent' ('=' separated arguments) for " |
142 | "long options." ; |
143 | |
144 | if (!error && (style & allow_short) && |
145 | !(style & short_allow_adjacent) && !(style & short_allow_next)) |
146 | error = "boost::program_options misconfiguration: " |
147 | "choose one or other of 'command_line_style::short_allow_next' " |
148 | "(whitespace separated arguments) or " |
149 | "'command_line_style::short_allow_adjacent' ('=' separated arguments) for " |
150 | "short options." ; |
151 | |
152 | if (!error && (style & allow_short) && |
153 | !(style & allow_dash_for_short) && !(style & allow_slash_for_short)) |
154 | error = "boost::program_options misconfiguration: " |
155 | "choose one or other of 'command_line_style::allow_slash_for_short' " |
156 | "(slashes) or 'command_line_style::allow_dash_for_short' (dashes) for " |
157 | "short options." ; |
158 | |
159 | if (error) |
160 | boost::throw_exception(invalid_command_line_style(error)); |
161 | |
162 | // Need to check that if guessing and long disguise are enabled |
163 | // -f will mean the same as -foo |
164 | } |
165 | |
166 | bool |
167 | cmdline::is_style_active(style_t style) const |
168 | { |
169 | return ((m_style & style) ? true : false); |
170 | } |
171 | |
172 | void |
173 | cmdline::set_options_description(const options_description& desc) |
174 | { |
175 | m_desc = &desc; |
176 | } |
177 | |
178 | void |
179 | cmdline::set_positional_options( |
180 | const positional_options_description& positional) |
181 | { |
182 | m_positional = &positional; |
183 | } |
184 | |
185 | int |
186 | cmdline::get_canonical_option_prefix() |
187 | { |
188 | if (m_style & allow_long) |
189 | return allow_long; |
190 | |
191 | if (m_style & allow_long_disguise) |
192 | return allow_long_disguise; |
193 | |
194 | if ((m_style & allow_short) && (m_style & allow_dash_for_short)) |
195 | return allow_dash_for_short; |
196 | |
197 | if ((m_style & allow_short) && (m_style & allow_slash_for_short)) |
198 | return allow_slash_for_short; |
199 | |
200 | return 0; |
201 | } |
202 | |
203 | vector<option> |
204 | cmdline::run() |
205 | { |
206 | // The parsing is done by having a set of 'style parsers' |
207 | // and trying then in order. Each parser is passed a vector |
208 | // of unparsed tokens and can consume some of them (by |
209 | // removing elements on front) and return a vector of options. |
210 | // |
211 | // We try each style parser in turn, untill some input |
212 | // is consumed. The returned vector of option may contain the |
213 | // result of just syntactic parsing of token, say --foo will |
214 | // be parsed as option with name 'foo', and the style parser |
215 | // is not required to care if that option is defined, and how |
216 | // many tokens the value may take. |
217 | // So, after vector is returned, we validate them. |
218 | assert(m_desc); |
219 | |
220 | vector<style_parser> style_parsers; |
221 | |
222 | if (m_style_parser) |
223 | style_parsers.push_back(m_style_parser); |
224 | |
225 | if (m_additional_parser) |
226 | style_parsers.push_back( |
227 | boost::bind(&cmdline::handle_additional_parser, this, _1)); |
228 | |
229 | if (m_style & allow_long) |
230 | style_parsers.push_back( |
231 | boost::bind(&cmdline::parse_long_option, this, _1)); |
232 | |
233 | if ((m_style & allow_long_disguise)) |
234 | style_parsers.push_back( |
235 | boost::bind(&cmdline::parse_disguised_long_option, this, _1)); |
236 | |
237 | if ((m_style & allow_short) && (m_style & allow_dash_for_short)) |
238 | style_parsers.push_back( |
239 | boost::bind(&cmdline::parse_short_option, this, _1)); |
240 | |
241 | if ((m_style & allow_short) && (m_style & allow_slash_for_short)) |
242 | style_parsers.push_back(boost::bind(&cmdline::parse_dos_option, this, _1)); |
243 | |
244 | style_parsers.push_back(boost::bind(&cmdline::parse_terminator, this, _1)); |
245 | |
246 | vector<option> result; |
247 | vector<string>& args = m_args; |
248 | while(!args.empty()) |
249 | { |
250 | bool ok = false; |
251 | for(unsigned i = 0; i < style_parsers.size(); ++i) |
252 | { |
253 | unsigned current_size = static_cast<unsigned>(args.size()); |
254 | vector<option> next = style_parsers[i](args); |
255 | |
256 | // Check that option names |
257 | // are valid, and that all values are in place. |
258 | if (!next.empty()) |
259 | { |
260 | vector<string> e; |
261 | for(unsigned k = 0; k < next.size()-1; ++k) { |
262 | finish_option(next[k], e, style_parsers); |
263 | } |
264 | // For the last option, pass the unparsed tokens |
265 | // so that they can be added to next.back()'s values |
266 | // if appropriate. |
267 | finish_option(next.back(), args, style_parsers); |
268 | for (unsigned j = 0; j < next.size(); ++j) |
269 | result.push_back(next[j]); |
270 | } |
271 | |
272 | if (args.size() != current_size) { |
273 | ok = true; |
274 | break; |
275 | } |
276 | } |
277 | |
278 | if (!ok) { |
279 | option opt; |
280 | opt.value.push_back(args[0]); |
281 | opt.original_tokens.push_back(args[0]); |
282 | result.push_back(opt); |
283 | args.erase(args.begin()); |
284 | } |
285 | } |
286 | |
287 | /* If an key option is followed by a positional option, |
288 | can can consume more tokens (e.g. it's multitoken option), |
289 | give those tokens to it. */ |
290 | vector<option> result2; |
291 | for (unsigned i = 0; i < result.size(); ++i) |
292 | { |
293 | result2.push_back(result[i]); |
294 | option& opt = result2.back(); |
295 | |
296 | if (opt.string_key.empty()) |
297 | continue; |
298 | |
299 | const option_description* xd; |
300 | try |
301 | { |
302 | xd = m_desc->find_nothrow(opt.string_key, |
303 | is_style_active(allow_guessing), |
304 | is_style_active(long_case_insensitive), |
305 | is_style_active(short_case_insensitive)); |
306 | } |
307 | catch(error_with_option_name& e) |
308 | { |
309 | // add context and rethrow |
310 | e.add_context(opt.string_key, opt.original_tokens[0], get_canonical_option_prefix()); |
311 | throw; |
312 | } |
313 | |
314 | if (!xd) |
315 | continue; |
316 | |
317 | unsigned min_tokens = xd->semantic()->min_tokens(); |
318 | unsigned max_tokens = xd->semantic()->max_tokens(); |
319 | if (min_tokens < max_tokens && opt.value.size() < max_tokens) |
320 | { |
321 | // This option may grab some more tokens. |
322 | // We only allow to grab tokens that are not already |
323 | // recognized as key options. |
324 | |
325 | int can_take_more = max_tokens - static_cast<int>(opt.value.size()); |
326 | unsigned j = i+1; |
327 | for (; can_take_more && j < result.size(); --can_take_more, ++j) |
328 | { |
329 | option& opt2 = result[j]; |
330 | if (!opt2.string_key.empty()) |
331 | break; |
332 | |
333 | if (opt2.position_key == INT_MAX) |
334 | { |
335 | // We use INT_MAX to mark positional options that |
336 | // were found after the '--' terminator and therefore |
337 | // should stay positional forever. |
338 | break; |
339 | } |
340 | |
341 | assert(opt2.value.size() == 1); |
342 | |
343 | opt.value.push_back(opt2.value[0]); |
344 | |
345 | assert(opt2.original_tokens.size() == 1); |
346 | |
347 | opt.original_tokens.push_back(opt2.original_tokens[0]); |
348 | } |
349 | i = j-1; |
350 | } |
351 | } |
352 | result.swap(result2); |
353 | |
354 | |
355 | // Assign position keys to positional options. |
356 | int position_key = 0; |
357 | for(unsigned i = 0; i < result.size(); ++i) { |
358 | if (result[i].string_key.empty()) |
359 | result[i].position_key = position_key++; |
360 | } |
361 | |
362 | if (m_positional) |
363 | { |
364 | unsigned position = 0; |
365 | for (unsigned i = 0; i < result.size(); ++i) { |
366 | option& opt = result[i]; |
367 | if (opt.position_key != -1) { |
368 | if (position >= m_positional->max_total_count()) |
369 | { |
370 | boost::throw_exception(too_many_positional_options_error()); |
371 | } |
372 | opt.string_key = m_positional->name_for_position(position); |
373 | ++position; |
374 | } |
375 | } |
376 | } |
377 | |
378 | // set case sensitive flag |
379 | for (unsigned i = 0; i < result.size(); ++i) { |
380 | if (result[i].string_key.size() > 2 || |
381 | (result[i].string_key.size() > 1 && result[i].string_key[0] != '-')) |
382 | { |
383 | // it is a long option |
384 | result[i].case_insensitive = is_style_active(long_case_insensitive); |
385 | } |
386 | else |
387 | { |
388 | // it is a short option |
389 | result[i].case_insensitive = is_style_active(short_case_insensitive); |
390 | } |
391 | } |
392 | |
393 | return result; |
394 | } |
395 | |
396 | void |
397 | cmdline::finish_option(option& opt, |
398 | vector<string>& other_tokens, |
399 | const vector<style_parser>& style_parsers) |
400 | { |
401 | if (opt.string_key.empty()) |
402 | return; |
403 | |
404 | // |
405 | // Be defensive: |
406 | // will have no original token if option created by handle_additional_parser() |
407 | std::string original_token_for_exceptions = opt.string_key; |
408 | if (opt.original_tokens.size()) |
409 | original_token_for_exceptions = opt.original_tokens[0]; |
410 | |
411 | try |
412 | { |
413 | // First check that the option is valid, and get its description. |
414 | const option_description* xd = m_desc->find_nothrow(opt.string_key, |
415 | is_style_active(allow_guessing), |
416 | is_style_active(long_case_insensitive), |
417 | is_style_active(short_case_insensitive)); |
418 | |
419 | if (!xd) |
420 | { |
421 | if (m_allow_unregistered) { |
422 | opt.unregistered = true; |
423 | return; |
424 | } else { |
425 | boost::throw_exception(unknown_option()); |
426 | } |
427 | } |
428 | const option_description& d = *xd; |
429 | |
430 | // Canonize the name |
431 | opt.string_key = d.key(opt.string_key); |
432 | |
433 | // We check that the min/max number of tokens for the option |
434 | // agrees with the number of tokens we have. The 'adjacent_value' |
435 | // (the value in --foo=1) counts as a separate token, and if present |
436 | // must be consumed. The following tokens on the command line may be |
437 | // left unconsumed. |
438 | unsigned min_tokens = d.semantic()->min_tokens(); |
439 | unsigned max_tokens = d.semantic()->max_tokens(); |
440 | |
441 | unsigned present_tokens = static_cast<unsigned>(opt.value.size() + other_tokens.size()); |
442 | |
443 | if (present_tokens >= min_tokens) |
444 | { |
445 | if (!opt.value.empty() && max_tokens == 0) |
446 | { |
447 | boost::throw_exception( |
448 | invalid_command_line_syntax(invalid_command_line_syntax::extra_parameter)); |
449 | } |
450 | |
451 | // Grab min_tokens values from other_tokens, but only if those tokens |
452 | // are not recognized as options themselves. |
453 | if (opt.value.size() <= min_tokens) |
454 | { |
455 | min_tokens -= static_cast<unsigned>(opt.value.size()); |
456 | } |
457 | else |
458 | { |
459 | min_tokens = 0; |
460 | } |
461 | |
462 | // Everything's OK, move the values to the result. |
463 | for(;!other_tokens.empty() && min_tokens--; ) |
464 | { |
465 | // check if extra parameter looks like a known option |
466 | // we use style parsers to check if it is syntactically an option, |
467 | // additionally we check if an option_description exists |
468 | vector<option> followed_option; |
469 | vector<string> next_token(1, other_tokens[0]); |
470 | for (unsigned i = 0; followed_option.empty() && i < style_parsers.size(); ++i) |
471 | { |
472 | followed_option = style_parsers[i](next_token); |
473 | } |
474 | if (!followed_option.empty()) |
475 | { |
476 | original_token_for_exceptions = other_tokens[0]; |
477 | const option_description* od = m_desc->find_nothrow(other_tokens[0], |
478 | is_style_active(allow_guessing), |
479 | is_style_active(long_case_insensitive), |
480 | is_style_active(short_case_insensitive)); |
481 | if (od) |
482 | boost::throw_exception( |
483 | invalid_command_line_syntax(invalid_command_line_syntax::missing_parameter)); |
484 | } |
485 | opt.value.push_back(other_tokens[0]); |
486 | opt.original_tokens.push_back(other_tokens[0]); |
487 | other_tokens.erase(other_tokens.begin()); |
488 | } |
489 | } |
490 | else |
491 | { |
492 | boost::throw_exception( |
493 | invalid_command_line_syntax(invalid_command_line_syntax::missing_parameter)); |
494 | |
495 | } |
496 | } |
497 | // use only original token for unknown_option / ambiguous_option since by definition |
498 | // they are unrecognised / unparsable |
499 | catch(error_with_option_name& e) |
500 | { |
501 | // add context and rethrow |
502 | e.add_context(opt.string_key, original_token_for_exceptions, get_canonical_option_prefix()); |
503 | throw; |
504 | } |
505 | |
506 | } |
507 | |
508 | vector<option> |
509 | cmdline::parse_long_option(vector<string>& args) |
510 | { |
511 | vector<option> result; |
512 | const string& tok = args[0]; |
513 | if (tok.size() >= 3 && tok[0] == '-' && tok[1] == '-') |
514 | { |
515 | string name, adjacent; |
516 | |
517 | string::size_type p = tok.find('='); |
518 | if (p != tok.npos) |
519 | { |
520 | name = tok.substr(2, p-2); |
521 | adjacent = tok.substr(p+1); |
522 | if (adjacent.empty()) |
523 | boost::throw_exception( invalid_command_line_syntax( |
524 | invalid_command_line_syntax::empty_adjacent_parameter, |
525 | name, |
526 | name, |
527 | get_canonical_option_prefix()) ); |
528 | } |
529 | else |
530 | { |
531 | name = tok.substr(2); |
532 | } |
533 | option opt; |
534 | opt.string_key = name; |
535 | if (!adjacent.empty()) |
536 | opt.value.push_back(adjacent); |
537 | opt.original_tokens.push_back(tok); |
538 | result.push_back(opt); |
539 | args.erase(args.begin()); |
540 | } |
541 | return result; |
542 | } |
543 | |
544 | |
545 | vector<option> |
546 | cmdline::parse_short_option(vector<string>& args) |
547 | { |
548 | const string& tok = args[0]; |
549 | if (tok.size() >= 2 && tok[0] == '-' && tok[1] != '-') |
550 | { |
551 | vector<option> result; |
552 | |
553 | string name = tok.substr(0,2); |
554 | string adjacent = tok.substr(2); |
555 | |
556 | // Short options can be 'grouped', so that |
557 | // "-d -a" becomes "-da". Loop, processing one |
558 | // option at a time. We exit the loop when either |
559 | // we've processed all the token, or when the remainder |
560 | // of token is considered to be value, not further grouped |
561 | // option. |
562 | for(;;) { |
563 | const option_description* d; |
564 | try |
565 | { |
566 | |
567 | d = m_desc->find_nothrow(name, false, false, |
568 | is_style_active(short_case_insensitive)); |
569 | } |
570 | catch(error_with_option_name& e) |
571 | { |
572 | // add context and rethrow |
573 | e.add_context(name, name, get_canonical_option_prefix()); |
574 | throw; |
575 | } |
576 | |
577 | |
578 | // FIXME: check for 'allow_sticky'. |
579 | if (d && (m_style & allow_sticky) && |
580 | d->semantic()->max_tokens() == 0 && !adjacent.empty()) { |
581 | // 'adjacent' is in fact further option. |
582 | option opt; |
583 | opt.string_key = name; |
584 | result.push_back(opt); |
585 | |
586 | if (adjacent.empty()) |
587 | { |
588 | args.erase(args.begin()); |
589 | break; |
590 | } |
591 | |
592 | name = string("-" ) + adjacent[0]; |
593 | adjacent.erase(adjacent.begin()); |
594 | } else { |
595 | |
596 | option opt; |
597 | opt.string_key = name; |
598 | opt.original_tokens.push_back(tok); |
599 | if (!adjacent.empty()) |
600 | opt.value.push_back(adjacent); |
601 | result.push_back(opt); |
602 | args.erase(args.begin()); |
603 | break; |
604 | } |
605 | } |
606 | return result; |
607 | } |
608 | return vector<option>(); |
609 | } |
610 | |
611 | vector<option> |
612 | cmdline::parse_dos_option(vector<string>& args) |
613 | { |
614 | vector<option> result; |
615 | const string& tok = args[0]; |
616 | if (tok.size() >= 2 && tok[0] == '/') |
617 | { |
618 | string name = "-" + tok.substr(1,1); |
619 | string adjacent = tok.substr(2); |
620 | |
621 | option opt; |
622 | opt.string_key = name; |
623 | if (!adjacent.empty()) |
624 | opt.value.push_back(adjacent); |
625 | opt.original_tokens.push_back(tok); |
626 | result.push_back(opt); |
627 | args.erase(args.begin()); |
628 | } |
629 | return result; |
630 | } |
631 | |
632 | vector<option> |
633 | cmdline::parse_disguised_long_option(vector<string>& args) |
634 | { |
635 | const string& tok = args[0]; |
636 | if (tok.size() >= 2 && |
637 | ((tok[0] == '-' && tok[1] != '-') || |
638 | ((m_style & allow_slash_for_short) && tok[0] == '/'))) |
639 | { |
640 | try |
641 | { |
642 | if (m_desc->find_nothrow(tok.substr(1, tok.find('=')-1), |
643 | is_style_active(allow_guessing), |
644 | is_style_active(long_case_insensitive), |
645 | is_style_active(short_case_insensitive))) |
646 | { |
647 | args[0].insert(0, "-" ); |
648 | if (args[0][1] == '/') |
649 | args[0][1] = '-'; |
650 | return parse_long_option(args); |
651 | } |
652 | } |
653 | catch(error_with_option_name& e) |
654 | { |
655 | // add context and rethrow |
656 | e.add_context(tok, tok, get_canonical_option_prefix()); |
657 | throw; |
658 | } |
659 | } |
660 | return vector<option>(); |
661 | } |
662 | |
663 | vector<option> |
664 | cmdline::parse_terminator(vector<string>& args) |
665 | { |
666 | vector<option> result; |
667 | const string& tok = args[0]; |
668 | if (tok == "--" ) |
669 | { |
670 | for(unsigned i = 1; i < args.size(); ++i) |
671 | { |
672 | option opt; |
673 | opt.value.push_back(args[i]); |
674 | opt.original_tokens.push_back(args[i]); |
675 | opt.position_key = INT_MAX; |
676 | result.push_back(opt); |
677 | } |
678 | args.clear(); |
679 | } |
680 | return result; |
681 | } |
682 | |
683 | vector<option> |
684 | cmdline::handle_additional_parser(vector<string>& args) |
685 | { |
686 | vector<option> result; |
687 | pair<string, string> r = m_additional_parser(args[0]); |
688 | if (!r.first.empty()) { |
689 | option next; |
690 | next.string_key = r.first; |
691 | if (!r.second.empty()) |
692 | next.value.push_back(r.second); |
693 | result.push_back(next); |
694 | args.erase(args.begin()); |
695 | } |
696 | return result; |
697 | } |
698 | |
699 | void |
700 | cmdline::set_additional_parser(additional_parser p) |
701 | { |
702 | m_additional_parser = p; |
703 | } |
704 | |
705 | void |
706 | cmdline::(style_parser s) |
707 | { |
708 | m_style_parser = s; |
709 | } |
710 | |
711 | |
712 | |
713 | }}} |
714 | |