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)
6 #include <boost/program_options/cmdline.hpp>
7 #include <boost/program_options/options_description.hpp>
8 #include <boost/program_options/detail/cmdline.hpp>
9 using namespace boost::program_options
;
10 using boost::program_options::detail::cmdline
;
18 #include "minitest.hpp"
20 /* To facilitate testing, declare a number of error codes. Otherwise,
21 we'd have to specify the type of exception that should be thrown.
24 const int s_success
= 0;
25 const int s_unknown_option
= 1;
26 const int s_ambiguous_option
= 2;
27 const int s_long_not_allowed
= 3;
28 const int s_long_adjacent_not_allowed
= 4;
29 const int s_short_adjacent_not_allowed
= 5;
30 const int s_empty_adjacent_parameter
= 6;
31 const int s_missing_parameter
= 7;
32 const int s_extra_parameter
= 8;
33 const int s_unrecognized_line
= 9;
35 int translate_syntax_error_kind(invalid_command_line_syntax::kind_t k
)
37 invalid_command_line_syntax::kind_t table
[] = {
38 invalid_command_line_syntax::long_not_allowed
,
39 invalid_command_line_syntax::long_adjacent_not_allowed
,
40 invalid_command_line_syntax::short_adjacent_not_allowed
,
41 invalid_command_line_syntax::empty_adjacent_parameter
,
42 invalid_command_line_syntax::missing_parameter
,
43 invalid_command_line_syntax::extra_parameter
,
44 invalid_command_line_syntax::unrecognized_line
46 invalid_command_line_syntax::kind_t
*b
, *e
, *i
;
48 e
= table
+ sizeof(table
)/sizeof(table
[0]);
49 i
= std::find(b
, e
, k
);
51 return std::distance(b
, i
) + 3;
57 const char* expected_result
;
61 /* Parses the syntax description in 'syntax' and initialized
63 The "boost::program_options" in parameter type is needed because CW9
64 has std::detail and it causes an ambiguity.
66 void apply_syntax(options_description
& desc
,
74 value_semantic
* v
= 0;
76 if (*(s
.end()-1) == '=') {
79 } else if (*(s
.end()-1) == '?') {
80 v
= value
<string
>()->implicit_value("default");
82 } else if (*(s
.end()-1) == '*') {
83 v
= value
<vector
<string
> >()->multitoken();
85 } else if (*(s
.end()-1) == '+') {
86 v
= value
<vector
<string
> >()->multitoken();
99 void test_cmdline(const char* syntax
,
100 command_line_style::style_t style
,
101 const test_case
* cases
)
103 for (int i
= 0; cases
[i
].input
; ++i
) {
105 vector
<string
> xinput
;
109 ss
<< cases
[i
].input
;
114 options_description desc
;
115 apply_syntax(desc
, syntax
);
119 cmd
.set_options_description(desc
);
126 vector
<option
> options
= cmd
.run();
128 for(unsigned j
= 0; j
< options
.size(); ++j
)
130 option opt
= options
[j
];
132 if (opt
.position_key
!= -1) {
135 result
+= opt
.value
[0];
139 result
+= opt
.string_key
+ ":";
140 for (size_t k
= 0; k
< opt
.value
.size(); ++k
) {
143 result
+= opt
.value
[k
];
148 catch(unknown_option
&) {
149 status
= s_unknown_option
;
151 catch(ambiguous_option
&) {
152 status
= s_ambiguous_option
;
154 catch(invalid_command_line_syntax
& e
) {
155 status
= translate_syntax_error_kind(e
.kind());
157 BOOST_CHECK_EQUAL(status
, cases
[i
].expected_status
);
158 BOOST_CHECK_EQUAL(result
, cases
[i
].expected_result
);
162 void test_long_options()
164 using namespace command_line_style
;
165 cmdline::style_t style
= cmdline::style_t(
166 allow_long
| long_allow_adjacent
);
168 test_case test_cases1
[] = {
169 // Test that long options are recognized and everything else
170 // is treated like arguments
171 {"--foo foo -123 /asd", s_success
, "foo: foo -123 /asd"},
174 {"--unk", s_unknown_option
, ""},
176 // Test that abbreviated names do not work
177 {"--fo", s_unknown_option
, ""},
179 // Test for disallowed parameter
180 {"--foo=13", s_extra_parameter
, ""},
182 // Test option with required parameter
183 {"--bar=", s_empty_adjacent_parameter
, ""},
184 {"--bar", s_missing_parameter
, ""},
186 {"--bar=123", s_success
, "bar:123"},
189 test_cmdline("foo bar=", style
, test_cases1
);
192 style
= cmdline::style_t(
193 allow_long
| long_allow_next
);
195 test_case test_cases2
[] = {
196 {"--bar 10", s_success
, "bar:10"},
197 {"--bar", s_missing_parameter
, ""},
198 // Since --bar accepts a parameter, --foo is
199 // considered a value, even though it looks like
201 {"--bar --foo", s_success
, "bar:--foo"},
204 test_cmdline("foo bar=", style
, test_cases2
);
205 style
= cmdline::style_t(
206 allow_long
| long_allow_adjacent
209 test_case test_cases3
[] = {
210 {"--bar=10", s_success
, "bar:10"},
211 {"--bar 11", s_success
, "bar:11"},
214 test_cmdline("foo bar=", style
, test_cases3
);
216 style
= cmdline::style_t(
217 allow_long
| long_allow_adjacent
218 | long_allow_next
| case_insensitive
);
220 // Test case insensitive style.
221 // Note that option names are normalized to lower case.
222 test_case test_cases4
[] = {
223 {"--foo", s_success
, "foo:"},
224 {"--Foo", s_success
, "foo:"},
225 {"--bar=Ab", s_success
, "bar:Ab"},
226 {"--Bar=ab", s_success
, "bar:ab"},
227 {"--giz", s_success
, "Giz:"},
230 test_cmdline("foo bar= baz? Giz", style
, test_cases4
);
233 void test_short_options()
235 using namespace command_line_style
;
236 cmdline::style_t style
;
238 style
= cmdline::style_t(
239 allow_short
| allow_dash_for_short
240 | short_allow_adjacent
);
242 test_case test_cases1
[] = {
243 {"-d d /bar", s_success
, "-d: d /bar"},
244 // This is treated as error when long options are disabled
245 {"--foo", s_success
, "--foo"},
246 {"-d13", s_extra_parameter
, ""},
247 {"-f14", s_success
, "-f:14"},
248 {"-g -f1", s_success
, "-g: -f:1"},
249 {"-f", s_missing_parameter
, ""},
252 test_cmdline(",d ,f= ,g", style
, test_cases1
);
254 style
= cmdline::style_t(
255 allow_short
| allow_dash_for_short
258 test_case test_cases2
[] = {
259 {"-f 13", s_success
, "-f:13"},
260 {"-f -13", s_success
, "-f:-13"},
261 {"-f", s_missing_parameter
, ""},
262 {"-f /foo", s_success
, "-f:/foo"},
263 {"-f -d", s_missing_parameter
, ""},
266 test_cmdline(",d ,f=", style
, test_cases2
);
268 style
= cmdline::style_t(
269 allow_short
| short_allow_next
270 | allow_dash_for_short
| short_allow_adjacent
);
272 test_case test_cases3
[] = {
273 {"-f10", s_success
, "-f:10"},
274 {"-f 10", s_success
, "-f:10"},
275 {"-f -d", s_missing_parameter
, ""},
278 test_cmdline(",d ,f=", style
, test_cases3
);
280 style
= cmdline::style_t(
281 allow_short
| short_allow_next
282 | allow_dash_for_short
283 | short_allow_adjacent
| allow_sticky
);
285 test_case test_cases4
[] = {
286 {"-de", s_success
, "-d: -e:"},
287 {"-df10", s_success
, "-d: -f:10"},
289 //{"-d12", s_extra_parameter, ""},
290 {"-f12", s_success
, "-f:12"},
291 {"-fe", s_success
, "-f:e"},
294 test_cmdline(",d ,f= ,e", style
, test_cases4
);
299 void test_dos_options()
301 using namespace command_line_style
;
302 cmdline::style_t style
;
304 style
= cmdline::style_t(
306 | allow_slash_for_short
| short_allow_adjacent
);
308 test_case test_cases1
[] = {
309 {"/d d -bar", s_success
, "-d: d -bar"},
310 {"--foo", s_success
, "--foo"},
311 {"/d13", s_extra_parameter
, ""},
312 {"/f14", s_success
, "-f:14"},
313 {"/f", s_missing_parameter
, ""},
316 test_cmdline(",d ,f=", style
, test_cases1
);
318 style
= cmdline::style_t(
320 | allow_slash_for_short
| short_allow_next
321 | short_allow_adjacent
| allow_sticky
);
323 test_case test_cases2
[] = {
324 {"/de", s_extra_parameter
, ""},
325 {"/fe", s_success
, "-f:e"},
328 test_cmdline(",d ,f= ,e", style
, test_cases2
);
333 void test_disguised_long()
335 using namespace command_line_style
;
336 cmdline::style_t style
;
338 style
= cmdline::style_t(
339 allow_short
| short_allow_adjacent
340 | allow_dash_for_short
341 | short_allow_next
| allow_long_disguise
342 | long_allow_adjacent
);
344 test_case test_cases1
[] = {
345 {"-foo -f", s_success
, "foo: foo:"},
346 {"-goo=x -gy", s_success
, "goo:x goo:y"},
347 {"-bee=x -by", s_success
, "bee:x bee:y"},
350 test_cmdline("foo,f goo,g= bee,b?", style
, test_cases1
);
352 style
= cmdline::style_t(style
| allow_slash_for_short
);
353 test_case test_cases2
[] = {
354 {"/foo -f", s_success
, "foo: foo:"},
355 {"/goo=x", s_success
, "goo:x"},
358 test_cmdline("foo,f goo,g= bee,b?", style
, test_cases2
);
363 using namespace command_line_style
;
364 cmdline::style_t style
;
366 style
= cmdline::style_t(
367 allow_short
| short_allow_adjacent
368 | allow_dash_for_short
369 | allow_long
| long_allow_adjacent
370 | allow_guessing
| allow_long_disguise
);
372 test_case test_cases1
[] = {
373 {"--opt1", s_success
, "opt123:"},
374 {"--opt", s_ambiguous_option
, ""},
375 {"--f=1", s_success
, "foo:1"},
376 {"-far", s_success
, "foo:ar"},
379 test_cmdline("opt123 opt56 foo,f=", style
, test_cases1
);
381 test_case test_cases2
[] = {
382 {"--fname file --fname2 file2", s_success
, "fname: file fname2: file2"},
383 {"--fnam file --fnam file2", s_ambiguous_option
, ""},
384 {"--fnam file --fname2 file2", s_ambiguous_option
, ""},
385 {"--fname2 file2 --fnam file", s_ambiguous_option
, ""},
388 test_cmdline("fname fname2", style
, test_cases2
);
391 void test_arguments()
393 using namespace command_line_style
;
394 cmdline::style_t style
;
396 style
= cmdline::style_t(
397 allow_short
| allow_long
398 | allow_dash_for_short
399 | short_allow_adjacent
| long_allow_adjacent
);
401 test_case test_cases1
[] = {
402 {"-f file -gx file2", s_success
, "-f: file -g:x file2"},
403 {"-f - -gx - -- -e", s_success
, "-f: - -g:x - -e"},
406 test_cmdline(",f ,g= ,e", style
, test_cases1
);
408 // "--" should stop options regardless of whether long options are
411 style
= cmdline::style_t(
412 allow_short
| short_allow_adjacent
413 | allow_dash_for_short
);
415 test_case test_cases2
[] = {
416 {"-f - -gx - -- -e", s_success
, "-f: - -g:x - -e"},
419 test_cmdline(",f ,g= ,e", style
, test_cases2
);
424 using namespace command_line_style
;
425 cmdline::style_t style
;
427 style
= cmdline::style_t(
428 allow_short
| allow_long
429 | allow_dash_for_short
430 | short_allow_adjacent
| long_allow_adjacent
433 test_case test_cases1
[] = {
434 {"--foo.bar=12", s_success
, "foo.bar:12"},
438 test_cmdline("foo*=", style
, test_cases1
);
442 pair
<string
, string
> at_option_parser(string
const&s
)
445 return std::make_pair(string("response-file"), s
.substr(1));
447 return pair
<string
, string
>();
450 pair
<string
, string
> at_option_parser_broken(string
const&s
)
453 return std::make_pair(string("some garbage"), s
.substr(1));
455 return pair
<string
, string
>();
460 void test_additional_parser()
462 options_description desc
;
464 ("response-file", value
<string
>(), "response file")
465 ("foo", value
<int>(), "foo")
468 vector
<string
> input
;
469 input
.push_back("@config");
470 input
.push_back("--foo=1");
473 cmd
.set_options_description(desc
);
474 cmd
.set_additional_parser(at_option_parser
);
476 vector
<option
> result
= cmd
.run();
478 BOOST_REQUIRE(result
.size() == 2);
479 BOOST_CHECK_EQUAL(result
[0].string_key
, "response-file");
480 BOOST_CHECK_EQUAL(result
[0].value
[0], "config");
481 BOOST_CHECK_EQUAL(result
[1].string_key
, "foo");
482 BOOST_CHECK_EQUAL(result
[1].value
[0], "1");
484 // Test that invalid options returned by additional style
485 // parser are detected.
487 cmd2
.set_options_description(desc
);
488 cmd2
.set_additional_parser(at_option_parser_broken
);
490 BOOST_CHECK_THROW(cmd2
.run(), unknown_option
);
494 vector
<option
> at_option_parser2(vector
<string
>& args
)
496 vector
<option
> result
;
497 if ('@' == args
[0][0]) {
498 // Simulate reading the response file.
499 result
.push_back(option("foo", vector
<string
>(1, "1")));
500 result
.push_back(option("bar", vector
<string
>(1, "1")));
501 args
.erase(args
.begin());
507 void test_style_parser()
509 options_description desc
;
511 ("foo", value
<int>(), "foo")
512 ("bar", value
<int>(), "bar")
515 vector
<string
> input
;
516 input
.push_back("@config");
519 cmd
.set_options_description(desc
);
520 cmd
.extra_style_parser(at_option_parser2
);
522 vector
<option
> result
= cmd
.run();
524 BOOST_REQUIRE(result
.size() == 2);
525 BOOST_CHECK_EQUAL(result
[0].string_key
, "foo");
526 BOOST_CHECK_EQUAL(result
[0].value
[0], "1");
527 BOOST_CHECK_EQUAL(result
[1].string_key
, "bar");
528 BOOST_CHECK_EQUAL(result
[1].value
[0], "1");
531 void test_unregistered()
533 // Check unregisted option when no options are registed at all.
534 options_description desc
;
536 vector
<string
> input
;
537 input
.push_back("--foo=1");
538 input
.push_back("--bar");
539 input
.push_back("1");
540 input
.push_back("-b");
541 input
.push_back("-biz");
544 cmd
.set_options_description(desc
);
545 cmd
.allow_unregistered();
547 vector
<option
> result
= cmd
.run();
548 BOOST_REQUIRE(result
.size() == 5);
550 BOOST_CHECK_EQUAL(result
[0].string_key
, "foo");
551 BOOST_CHECK_EQUAL(result
[0].unregistered
, true);
552 BOOST_CHECK_EQUAL(result
[0].value
[0], "1");
554 BOOST_CHECK_EQUAL(result
[1].string_key
, "bar");
555 BOOST_CHECK_EQUAL(result
[1].unregistered
, true);
556 BOOST_CHECK(result
[1].value
.empty());
557 // '1' is considered a positional option, not a value to
559 BOOST_CHECK(result
[2].string_key
.empty());
560 BOOST_CHECK(result
[2].position_key
== 0);
561 BOOST_CHECK_EQUAL(result
[2].unregistered
, false);
562 BOOST_CHECK_EQUAL(result
[2].value
[0], "1");
564 BOOST_CHECK_EQUAL(result
[3].string_key
, "-b");
565 BOOST_CHECK_EQUAL(result
[3].unregistered
, true);
566 BOOST_CHECK(result
[3].value
.empty());
568 BOOST_CHECK_EQUAL(result
[4].string_key
, "-b");
569 BOOST_CHECK_EQUAL(result
[4].unregistered
, true);
570 BOOST_CHECK_EQUAL(result
[4].value
[0], "iz");
572 // Check sticky short options together with unregisted options.
576 ("magic,m", value
<string
>(), "")
580 input
.push_back("-hc");
581 input
.push_back("-mc");
585 cmd2
.set_options_description(desc
);
586 cmd2
.allow_unregistered();
590 BOOST_REQUIRE(result
.size() == 3);
591 BOOST_CHECK_EQUAL(result
[0].string_key
, "help");
592 BOOST_CHECK_EQUAL(result
[0].unregistered
, false);
593 BOOST_CHECK(result
[0].value
.empty());
594 BOOST_CHECK_EQUAL(result
[1].string_key
, "-c");
595 BOOST_CHECK_EQUAL(result
[1].unregistered
, true);
596 BOOST_CHECK(result
[1].value
.empty());
597 BOOST_CHECK_EQUAL(result
[2].string_key
, "magic");
598 BOOST_CHECK_EQUAL(result
[2].unregistered
, false);
599 BOOST_CHECK_EQUAL(result
[2].value
[0], "c");
602 // There's a corner case:
604 // when 'allow_long_disguise' is set. Should this be considered
605 // disguised long option 'foo' or short option '-f' with value 'oo'?
606 // It's not clear yet, so I'm leaving the decision till later.
609 void test_implicit_value()
611 using namespace command_line_style
;
612 cmdline::style_t style
;
614 style
= cmdline::style_t(
615 allow_long
| long_allow_adjacent
618 test_case test_cases1
[] = {
619 // 'bar' does not even look like option, so is consumed
620 {"--foo bar", s_success
, "foo:bar"},
621 // '--bar' looks like option, and such option exists, so we don't consume this token
622 {"--foo --bar", s_success
, "foo: bar:"},
623 // '--biz' looks like option, but does not match any existing one.
624 // Presently this results in parse error, since
625 // (1) in cmdline.cpp:finish_option, we only consume following tokens if they are
627 // (2) in cmdline.cpp:run, we let options consume following positional options
628 // For --biz, an exception is thrown between 1 and 2.
629 // We might want to fix that in future.
630 {"--foo --biz", s_unknown_option
, ""},
634 test_cmdline("foo? bar?", style
, test_cases1
);
637 int main(int /*ac*/, char** /*av*/)
640 test_short_options();
642 test_disguised_long();
646 test_additional_parser();
649 test_implicit_value();