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
,
67 positional_options_description
& m_positional
,
75 value_semantic
* v
= 0;
77 if (*(s
.end()-1) == '=') {
80 } else if (*(s
.end()-1) == '?') {
81 v
= value
<string
>()->implicit_value("bar");
82 m_positional
.add("positional", -1);
84 } else if (*(s
.end()-1) == '*') {
85 v
= value
<vector
<string
> >()->multitoken();
87 } else if (*(s
.end()-1) == '+') {
88 v
= value
<vector
<string
> >()->multitoken();
101 void test_cmdline(const char* syntax
,
102 command_line_style::style_t style
,
103 const test_case
* cases
)
105 for (int i
= 0; cases
[i
].input
; ++i
) {
107 vector
<string
> xinput
;
111 ss
<< cases
[i
].input
;
116 options_description desc
;
117 positional_options_description m_positional
;
118 apply_syntax(desc
, m_positional
, syntax
);
122 cmd
.set_options_description(desc
);
123 if(m_positional
.max_total_count())
124 cmd
.set_positional_options(m_positional
);
130 vector
<option
> options
= cmd
.run();
132 for(unsigned j
= 0; j
< options
.size(); ++j
)
134 option opt
= options
[j
];
136 if (opt
.position_key
!= -1
137 && (m_positional
.max_total_count() == 0 || (size_t)opt
.position_key
>= m_positional
.max_total_count()
138 || m_positional
.name_for_position(opt
.position_key
) != "positional")) {
141 result
+= opt
.value
[0];
145 result
+= opt
.string_key
+ ":";
146 for (size_t k
= 0; k
< opt
.value
.size(); ++k
) {
149 result
+= opt
.value
[k
];
154 catch(unknown_option
&) {
155 status
= s_unknown_option
;
157 catch(ambiguous_option
&) {
158 status
= s_ambiguous_option
;
160 catch(invalid_command_line_syntax
& e
) {
161 status
= translate_syntax_error_kind(e
.kind());
163 BOOST_CHECK_EQUAL(status
, cases
[i
].expected_status
);
164 BOOST_CHECK_EQUAL(result
, cases
[i
].expected_result
);
168 void test_long_options()
170 using namespace command_line_style
;
171 cmdline::style_t style
= cmdline::style_t(
172 allow_long
| long_allow_adjacent
);
174 test_case test_cases1
[] = {
175 // Test that long options are recognized and everything else
176 // is treated like arguments
177 {"--foo foo -123 /asd", s_success
, "foo: foo -123 /asd"},
180 {"--unk", s_unknown_option
, ""},
182 // Test that abbreviated names do not work
183 {"--fo", s_unknown_option
, ""},
185 // Test for disallowed parameter
186 {"--foo=13", s_extra_parameter
, ""},
188 // Test option with required parameter
189 {"--bar=", s_empty_adjacent_parameter
, ""},
190 {"--bar", s_missing_parameter
, ""},
192 {"--bar=123", s_success
, "bar:123"},
195 test_cmdline("foo bar=", style
, test_cases1
);
198 style
= cmdline::style_t(
199 allow_long
| long_allow_next
);
201 test_case test_cases2
[] = {
202 {"--bar 10", s_success
, "bar:10"},
203 {"--bar", s_missing_parameter
, ""},
204 // Since --bar accepts a parameter, --foo is
205 // considered a value, even though it looks like
207 {"--bar --foo", s_success
, "bar:--foo"},
210 test_cmdline("foo bar=", style
, test_cases2
);
211 style
= cmdline::style_t(
212 allow_long
| long_allow_adjacent
215 test_case test_cases3
[] = {
216 {"--bar=10", s_success
, "bar:10"},
217 {"--bar 11", s_success
, "bar:11"},
220 test_cmdline("foo bar=", style
, test_cases3
);
222 style
= cmdline::style_t(
223 allow_long
| long_allow_adjacent
224 | long_allow_next
| case_insensitive
);
226 // Test case insensitive style.
227 // Note that option names are normalized to lower case.
228 test_case test_cases4
[] = {
229 {"--foo", s_success
, "foo:"},
230 {"--Foo", s_success
, "foo:"},
231 {"--bar=Ab", s_success
, "bar:Ab"},
232 {"--Bar=ab", s_success
, "bar:ab"},
233 {"--giz", s_success
, "Giz:"},
236 test_cmdline("foo bar= Giz", style
, test_cases4
);
239 void test_short_options()
241 using namespace command_line_style
;
242 cmdline::style_t style
;
244 style
= cmdline::style_t(
245 allow_short
| allow_dash_for_short
246 | short_allow_adjacent
);
248 test_case test_cases1
[] = {
249 {"-d d /bar", s_success
, "-d: d /bar"},
250 // This is treated as error when long options are disabled
251 {"--foo", s_success
, "--foo"},
252 {"-d13", s_extra_parameter
, ""},
253 {"-f14", s_success
, "-f:14"},
254 {"-g -f1", s_success
, "-g: -f:1"},
255 {"-f", s_missing_parameter
, ""},
258 test_cmdline(",d ,f= ,g", style
, test_cases1
);
260 style
= cmdline::style_t(
261 allow_short
| allow_dash_for_short
264 test_case test_cases2
[] = {
265 {"-f 13", s_success
, "-f:13"},
266 {"-f -13", s_success
, "-f:-13"},
267 {"-f", s_missing_parameter
, ""},
268 {"-f /foo", s_success
, "-f:/foo"},
269 {"-f -d", s_missing_parameter
, ""},
272 test_cmdline(",d ,f=", style
, test_cases2
);
274 style
= cmdline::style_t(
275 allow_short
| short_allow_next
276 | allow_dash_for_short
| short_allow_adjacent
);
278 test_case test_cases3
[] = {
279 {"-f10", s_success
, "-f:10"},
280 {"-f 10", s_success
, "-f:10"},
281 {"-f -d", s_missing_parameter
, ""},
284 test_cmdline(",d ,f=", style
, test_cases3
);
286 style
= cmdline::style_t(
287 allow_short
| short_allow_next
288 | allow_dash_for_short
289 | short_allow_adjacent
| allow_sticky
);
291 test_case test_cases4
[] = {
292 {"-de", s_success
, "-d: -e:"},
293 {"-df10", s_success
, "-d: -f:10"},
295 //{"-d12", s_extra_parameter, ""},
296 {"-f12", s_success
, "-f:12"},
297 {"-fe", s_success
, "-f:e"},
300 test_cmdline(",d ,f= ,e", style
, test_cases4
);
305 void test_dos_options()
307 using namespace command_line_style
;
308 cmdline::style_t style
;
310 style
= cmdline::style_t(
312 | allow_slash_for_short
| short_allow_adjacent
);
314 test_case test_cases1
[] = {
315 {"/d d -bar", s_success
, "-d: d -bar"},
316 {"--foo", s_success
, "--foo"},
317 {"/d13", s_extra_parameter
, ""},
318 {"/f14", s_success
, "-f:14"},
319 {"/f", s_missing_parameter
, ""},
322 test_cmdline(",d ,f=", style
, test_cases1
);
324 style
= cmdline::style_t(
326 | allow_slash_for_short
| short_allow_next
327 | short_allow_adjacent
| allow_sticky
);
329 test_case test_cases2
[] = {
330 {"/de", s_extra_parameter
, ""},
331 {"/fe", s_success
, "-f:e"},
334 test_cmdline(",d ,f= ,e", style
, test_cases2
);
339 void test_disguised_long()
341 using namespace command_line_style
;
342 cmdline::style_t style
;
344 style
= cmdline::style_t(
345 allow_short
| short_allow_adjacent
346 | allow_dash_for_short
347 | short_allow_next
| allow_long_disguise
348 | long_allow_adjacent
);
350 test_case test_cases1
[] = {
351 {"-foo -f", s_success
, "foo: foo:"},
352 {"-goo=x -gy", s_success
, "goo:x goo:y"},
353 {"-bee=x -by", s_success
, "bee:x bee:y"},
356 test_cmdline("foo,f goo,g= bee,b=", style
, test_cases1
);
358 style
= cmdline::style_t(style
| allow_slash_for_short
);
359 test_case test_cases2
[] = {
360 {"/foo -f", s_success
, "foo: foo:"},
361 {"/goo=x", s_success
, "goo:x"},
364 test_cmdline("foo,f goo,g=", style
, test_cases2
);
369 using namespace command_line_style
;
370 cmdline::style_t style
;
372 style
= cmdline::style_t(
373 allow_short
| short_allow_adjacent
374 | allow_dash_for_short
375 | allow_long
| long_allow_adjacent
376 | allow_guessing
| allow_long_disguise
);
378 test_case test_cases1
[] = {
379 {"--opt1", s_success
, "opt123:"},
380 {"--opt", s_ambiguous_option
, ""},
381 {"--f=1", s_success
, "foo:1"},
382 {"-far", s_success
, "foo:ar"},
385 test_cmdline("opt123 opt56 foo,f=", style
, test_cases1
);
387 test_case test_cases2
[] = {
388 {"--fname file --fname2 file2", s_success
, "fname: file fname2: file2"},
389 {"--fnam file --fnam file2", s_ambiguous_option
, ""},
390 {"--fnam file --fname2 file2", s_ambiguous_option
, ""},
391 {"--fname2 file2 --fnam file", s_ambiguous_option
, ""},
394 test_cmdline("fname fname2", style
, test_cases2
);
397 void test_arguments()
399 using namespace command_line_style
;
400 cmdline::style_t style
;
402 style
= cmdline::style_t(
403 allow_short
| allow_long
404 | allow_dash_for_short
405 | short_allow_adjacent
| long_allow_adjacent
);
407 test_case test_cases1
[] = {
408 {"-f file -gx file2", s_success
, "-f: file -g:x file2"},
409 {"-f - -gx - -- -e", s_success
, "-f: - -g:x - -e"},
412 test_cmdline(",f ,g= ,e", style
, test_cases1
);
414 // "--" should stop options regardless of whether long options are
417 style
= cmdline::style_t(
418 allow_short
| short_allow_adjacent
419 | allow_dash_for_short
);
421 test_case test_cases2
[] = {
422 {"-f - -gx - -- -e", s_success
, "-f: - -g:x - -e"},
425 test_cmdline(",f ,g= ,e", style
, test_cases2
);
430 using namespace command_line_style
;
431 cmdline::style_t style
;
433 style
= cmdline::style_t(
434 allow_short
| allow_long
435 | allow_dash_for_short
436 | short_allow_adjacent
| long_allow_adjacent
439 test_case test_cases1
[] = {
440 {"--foo.bar=12", s_success
, "foo.bar:12"},
444 test_cmdline("foo*=", style
, test_cases1
);
448 pair
<string
, string
> at_option_parser(string
const&s
)
451 return std::make_pair(string("response-file"), s
.substr(1));
453 return pair
<string
, string
>();
456 pair
<string
, string
> at_option_parser_broken(string
const&s
)
459 return std::make_pair(string("some garbage"), s
.substr(1));
461 return pair
<string
, string
>();
466 void test_additional_parser()
468 options_description desc
;
470 ("response-file", value
<string
>(), "response file")
471 ("foo", value
<int>(), "foo")
474 vector
<string
> input
;
475 input
.push_back("@config");
476 input
.push_back("--foo=1");
479 cmd
.set_options_description(desc
);
480 cmd
.set_additional_parser(at_option_parser
);
482 vector
<option
> result
= cmd
.run();
484 BOOST_REQUIRE(result
.size() == 2);
485 BOOST_CHECK_EQUAL(result
[0].string_key
, "response-file");
486 BOOST_CHECK_EQUAL(result
[0].value
[0], "config");
487 BOOST_CHECK_EQUAL(result
[1].string_key
, "foo");
488 BOOST_CHECK_EQUAL(result
[1].value
[0], "1");
490 // Test that invalid options returned by additional style
491 // parser are detected.
493 cmd2
.set_options_description(desc
);
494 cmd2
.set_additional_parser(at_option_parser_broken
);
496 BOOST_CHECK_THROW(cmd2
.run(), unknown_option
);
500 vector
<option
> at_option_parser2(vector
<string
>& args
)
502 vector
<option
> result
;
503 if ('@' == args
[0][0]) {
504 // Simulate reading the response file.
505 result
.push_back(option("foo", vector
<string
>(1, "1")));
506 result
.push_back(option("bar", vector
<string
>(1, "1")));
507 args
.erase(args
.begin());
513 void test_style_parser()
515 options_description desc
;
517 ("foo", value
<int>(), "foo")
518 ("bar", value
<int>(), "bar")
521 vector
<string
> input
;
522 input
.push_back("@config");
525 cmd
.set_options_description(desc
);
526 cmd
.extra_style_parser(at_option_parser2
);
528 vector
<option
> result
= cmd
.run();
530 BOOST_REQUIRE(result
.size() == 2);
531 BOOST_CHECK_EQUAL(result
[0].string_key
, "foo");
532 BOOST_CHECK_EQUAL(result
[0].value
[0], "1");
533 BOOST_CHECK_EQUAL(result
[1].string_key
, "bar");
534 BOOST_CHECK_EQUAL(result
[1].value
[0], "1");
537 void test_unregistered()
539 // Check unregisted option when no options are registed at all.
540 options_description desc
;
542 vector
<string
> input
;
543 input
.push_back("--foo=1");
544 input
.push_back("--bar");
545 input
.push_back("1");
546 input
.push_back("-b");
547 input
.push_back("-biz");
550 cmd
.set_options_description(desc
);
551 cmd
.allow_unregistered();
553 vector
<option
> result
= cmd
.run();
554 BOOST_REQUIRE(result
.size() == 5);
556 BOOST_CHECK_EQUAL(result
[0].string_key
, "foo");
557 BOOST_CHECK_EQUAL(result
[0].unregistered
, true);
558 BOOST_CHECK_EQUAL(result
[0].value
[0], "1");
560 BOOST_CHECK_EQUAL(result
[1].string_key
, "bar");
561 BOOST_CHECK_EQUAL(result
[1].unregistered
, true);
562 BOOST_CHECK(result
[1].value
.empty());
563 // '1' is considered a positional option, not a value to
565 BOOST_CHECK(result
[2].string_key
.empty());
566 BOOST_CHECK(result
[2].position_key
== 0);
567 BOOST_CHECK_EQUAL(result
[2].unregistered
, false);
568 BOOST_CHECK_EQUAL(result
[2].value
[0], "1");
570 BOOST_CHECK_EQUAL(result
[3].string_key
, "-b");
571 BOOST_CHECK_EQUAL(result
[3].unregistered
, true);
572 BOOST_CHECK(result
[3].value
.empty());
574 BOOST_CHECK_EQUAL(result
[4].string_key
, "-b");
575 BOOST_CHECK_EQUAL(result
[4].unregistered
, true);
576 BOOST_CHECK_EQUAL(result
[4].value
[0], "iz");
578 // Check sticky short options together with unregisted options.
582 ("magic,m", value
<string
>(), "")
586 input
.push_back("-hc");
587 input
.push_back("-mc");
591 cmd2
.set_options_description(desc
);
592 cmd2
.allow_unregistered();
596 BOOST_REQUIRE(result
.size() == 3);
597 BOOST_CHECK_EQUAL(result
[0].string_key
, "help");
598 BOOST_CHECK_EQUAL(result
[0].unregistered
, false);
599 BOOST_CHECK(result
[0].value
.empty());
600 BOOST_CHECK_EQUAL(result
[1].string_key
, "-c");
601 BOOST_CHECK_EQUAL(result
[1].unregistered
, true);
602 BOOST_CHECK(result
[1].value
.empty());
603 BOOST_CHECK_EQUAL(result
[2].string_key
, "magic");
604 BOOST_CHECK_EQUAL(result
[2].unregistered
, false);
605 BOOST_CHECK_EQUAL(result
[2].value
[0], "c");
608 // There's a corner case:
610 // when 'allow_long_disguise' is set. Should this be considered
611 // disguised long option 'foo' or short option '-f' with value 'oo'?
612 // It's not clear yet, so I'm leaving the decision till later.
615 void test_implicit_value()
617 using namespace command_line_style
;
618 cmdline::style_t style
;
620 style
= cmdline::style_t(
621 allow_long
| long_allow_adjacent
624 test_case test_cases1
[] = {
625 {"--foo bar", s_success
, "foo: positional:bar"},
626 {"--foo=bar foobar", s_success
, "foo:bar positional:foobar"},
630 test_cmdline("positional= foo?", style
, test_cases1
);
632 style
= cmdline::style_t(
633 allow_short
| allow_dash_for_short
634 | short_allow_adjacent
);
636 test_case test_cases2
[] = {
637 {"-f bar", s_success
, "-f: positional:bar"},
638 {"-fbar foobar", s_success
, "-f:bar positional:foobar"},
641 test_cmdline("positional= ,f?", style
, test_cases2
);
644 int main(int /*ac*/, char** /*av*/)
647 test_short_options();
649 test_disguised_long();
653 test_additional_parser();
656 test_implicit_value();