• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #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;
11 
12 #include <iostream>
13 #include <sstream>
14 #include <vector>
15 #include <cassert>
16 using namespace std;
17 
18 #include "minitest.hpp"
19 
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.
22 */
23 
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;
34 
translate_syntax_error_kind(invalid_command_line_syntax::kind_t k)35 int translate_syntax_error_kind(invalid_command_line_syntax::kind_t k)
36 {
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
45     };
46     invalid_command_line_syntax::kind_t *b, *e, *i;
47     b = table;
48     e = table + sizeof(table)/sizeof(table[0]);
49     i = std::find(b, e, k);
50     assert(i != e);
51     return std::distance(b, i) + 3;
52 }
53 
54 struct test_case {
55     const char* input;
56     int expected_status;
57     const char* expected_result;
58 };
59 
60 
61 /* Parses the syntax description in 'syntax' and initialized
62    'cmd' accordingly'
63    The "boost::program_options" in parameter type is needed because CW9
64    has std::detail and it causes an ambiguity.
65 */
apply_syntax(options_description & desc,const char * syntax)66 void apply_syntax(options_description& desc,
67                   const char* syntax)
68 {
69 
70     string s;
71     stringstream ss;
72     ss << syntax;
73     while(ss >> s) {
74         value_semantic* v = 0;
75 
76         if (*(s.end()-1) == '=') {
77             v = value<string>();
78             s.resize(s.size()-1);
79         } else if (*(s.end()-1) == '?') {
80             v = value<string>()->implicit_value("default");
81             s.resize(s.size()-1);
82         } else if (*(s.end()-1) == '*') {
83             v = value<vector<string> >()->multitoken();
84             s.resize(s.size()-1);
85         } else if (*(s.end()-1) == '+') {
86             v = value<vector<string> >()->multitoken();
87             s.resize(s.size()-1);
88         }
89         if (v) {
90             desc.add_options()
91                 (s.c_str(), v, "");
92         } else {
93             desc.add_options()
94                 (s.c_str(), "");
95         }
96     }
97 }
98 
test_cmdline(const char * syntax,command_line_style::style_t style,const test_case * cases)99 void test_cmdline(const char* syntax,
100                   command_line_style::style_t style,
101                   const test_case* cases)
102 {
103     for (int i = 0; cases[i].input; ++i) {
104         // Parse input
105         vector<string> xinput;
106         {
107             string s;
108             stringstream ss;
109             ss << cases[i].input;
110             while (ss >> s) {
111                 xinput.push_back(s);
112             }
113         }
114         options_description desc;
115         apply_syntax(desc, syntax);
116 
117         cmdline cmd(xinput);
118         cmd.style(style);
119         cmd.set_options_description(desc);
120 
121 
122         string result;
123         int status = 0;
124 
125         try {
126             vector<option> options = cmd.run();
127 
128             for(unsigned j = 0; j < options.size(); ++j)
129             {
130                 option opt = options[j];
131 
132                 if (opt.position_key != -1) {
133                     if (!result.empty())
134                         result += " ";
135                     result += opt.value[0];
136                 } else {
137                     if (!result.empty())
138                         result += " ";
139                     result += opt.string_key + ":";
140                     for (size_t k = 0; k < opt.value.size(); ++k) {
141                         if (k != 0)
142                             result += "-";
143                         result += opt.value[k];
144                     }
145                 }
146             }
147         }
148         catch(unknown_option&) {
149             status = s_unknown_option;
150         }
151         catch(ambiguous_option&) {
152             status = s_ambiguous_option;
153         }
154         catch(invalid_command_line_syntax& e) {
155             status = translate_syntax_error_kind(e.kind());
156         }
157         BOOST_CHECK_EQUAL(status, cases[i].expected_status);
158         BOOST_CHECK_EQUAL(result, cases[i].expected_result);
159     }
160 }
161 
test_long_options()162 void test_long_options()
163 {
164     using namespace command_line_style;
165     cmdline::style_t style = cmdline::style_t(
166         allow_long | long_allow_adjacent);
167 
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"},
172 
173         // Unknown option
174         {"--unk", s_unknown_option, ""},
175 
176         // Test that abbreviated names do not work
177         {"--fo", s_unknown_option, ""},
178 
179         // Test for disallowed parameter
180         {"--foo=13", s_extra_parameter, ""},
181 
182         // Test option with required parameter
183         {"--bar=", s_empty_adjacent_parameter, ""},
184         {"--bar", s_missing_parameter, ""},
185 
186         {"--bar=123", s_success, "bar:123"},
187         {0, 0, 0}
188     };
189     test_cmdline("foo bar=", style, test_cases1);
190 
191 
192     style = cmdline::style_t(
193         allow_long | long_allow_next);
194 
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
200         // an option.
201         {"--bar --foo", s_success, "bar:--foo"},
202         {0, 0, 0}
203     };
204     test_cmdline("foo bar=", style, test_cases2);
205     style = cmdline::style_t(
206         allow_long | long_allow_adjacent
207         | long_allow_next);
208 
209     test_case test_cases3[] = {
210         {"--bar=10", s_success, "bar:10"},
211         {"--bar 11", s_success, "bar:11"},
212         {0, 0, 0}
213     };
214     test_cmdline("foo bar=", style, test_cases3);
215 
216     style = cmdline::style_t(
217         allow_long | long_allow_adjacent
218         | long_allow_next | case_insensitive);
219 
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:"},
228         {0, 0, 0}
229     };
230     test_cmdline("foo bar= baz? Giz", style, test_cases4);
231 }
232 
test_short_options()233 void test_short_options()
234 {
235     using namespace command_line_style;
236     cmdline::style_t style;
237 
238     style = cmdline::style_t(
239         allow_short | allow_dash_for_short
240         | short_allow_adjacent);
241 
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, ""},
250         {0, 0, 0}
251     };
252     test_cmdline(",d ,f= ,g", style, test_cases1);
253 
254     style = cmdline::style_t(
255         allow_short | allow_dash_for_short
256         | short_allow_next);
257 
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, ""},
264         {0, 0, 0}
265     };
266     test_cmdline(",d ,f=", style, test_cases2);
267 
268     style = cmdline::style_t(
269         allow_short | short_allow_next
270         | allow_dash_for_short | short_allow_adjacent);
271 
272     test_case test_cases3[] = {
273         {"-f10", s_success, "-f:10"},
274         {"-f 10", s_success, "-f:10"},
275         {"-f -d", s_missing_parameter, ""},
276         {0, 0, 0}
277     };
278     test_cmdline(",d ,f=", style, test_cases3);
279 
280     style = cmdline::style_t(
281         allow_short | short_allow_next
282         | allow_dash_for_short
283         | short_allow_adjacent | allow_sticky);
284 
285     test_case test_cases4[] = {
286         {"-de", s_success, "-d: -e:"},
287         {"-df10", s_success, "-d: -f:10"},
288         // FIXME: review
289         //{"-d12", s_extra_parameter, ""},
290         {"-f12", s_success, "-f:12"},
291         {"-fe", s_success, "-f:e"},
292         {0, 0, 0}
293     };
294     test_cmdline(",d ,f= ,e", style, test_cases4);
295 
296 }
297 
298 
test_dos_options()299 void test_dos_options()
300 {
301     using namespace command_line_style;
302     cmdline::style_t style;
303 
304     style = cmdline::style_t(
305         allow_short
306         | allow_slash_for_short | short_allow_adjacent);
307 
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, ""},
314         {0, 0, 0}
315     };
316     test_cmdline(",d ,f=", style, test_cases1);
317 
318     style = cmdline::style_t(
319         allow_short
320         | allow_slash_for_short | short_allow_next
321         | short_allow_adjacent | allow_sticky);
322 
323     test_case test_cases2[] = {
324         {"/de", s_extra_parameter, ""},
325         {"/fe", s_success, "-f:e"},
326         {0, 0, 0}
327     };
328     test_cmdline(",d ,f= ,e", style, test_cases2);
329 
330 }
331 
332 
test_disguised_long()333 void test_disguised_long()
334 {
335     using namespace command_line_style;
336     cmdline::style_t style;
337 
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);
343 
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"},
348         {0, 0, 0}
349     };
350     test_cmdline("foo,f goo,g= bee,b?", style, test_cases1);
351 
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"},
356         {0, 0, 0}
357     };
358     test_cmdline("foo,f goo,g= bee,b?", style, test_cases2);
359 }
360 
test_guessing()361 void test_guessing()
362 {
363     using namespace command_line_style;
364     cmdline::style_t style;
365 
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);
371 
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"},
377         {0, 0, 0}
378     };
379     test_cmdline("opt123 opt56 foo,f=", style, test_cases1);
380 
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, ""},
386         {0, 0, 0}
387     };
388     test_cmdline("fname fname2", style, test_cases2);
389 }
390 
test_arguments()391 void test_arguments()
392 {
393     using namespace command_line_style;
394     cmdline::style_t style;
395 
396     style = cmdline::style_t(
397         allow_short | allow_long
398         | allow_dash_for_short
399         | short_allow_adjacent | long_allow_adjacent);
400 
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"},
404         {0, 0, 0}
405     };
406     test_cmdline(",f ,g= ,e", style, test_cases1);
407 
408     // "--" should stop options regardless of whether long options are
409     // allowed or not.
410 
411     style = cmdline::style_t(
412         allow_short | short_allow_adjacent
413         | allow_dash_for_short);
414 
415     test_case test_cases2[] = {
416         {"-f - -gx - -- -e", s_success, "-f: - -g:x - -e"},
417         {0, 0, 0}
418     };
419     test_cmdline(",f ,g= ,e", style, test_cases2);
420 }
421 
test_prefix()422 void test_prefix()
423 {
424     using namespace command_line_style;
425     cmdline::style_t style;
426 
427     style = cmdline::style_t(
428         allow_short | allow_long
429         | allow_dash_for_short
430         | short_allow_adjacent | long_allow_adjacent
431         );
432 
433     test_case test_cases1[] = {
434         {"--foo.bar=12", s_success, "foo.bar:12"},
435         {0, 0, 0}
436     };
437 
438     test_cmdline("foo*=", style, test_cases1);
439 }
440 
441 
at_option_parser(string const & s)442 pair<string, string> at_option_parser(string const&s)
443 {
444     if ('@' == s[0])
445         return std::make_pair(string("response-file"), s.substr(1));
446     else
447         return pair<string, string>();
448 }
449 
at_option_parser_broken(string const & s)450 pair<string, string> at_option_parser_broken(string const&s)
451 {
452     if ('@' == s[0])
453         return std::make_pair(string("some garbage"), s.substr(1));
454     else
455         return pair<string, string>();
456 }
457 
458 
459 
test_additional_parser()460 void test_additional_parser()
461 {
462     options_description desc;
463     desc.add_options()
464         ("response-file", value<string>(), "response file")
465         ("foo", value<int>(), "foo")
466         ("bar,baz", value<int>(), "bar")
467         ;
468 
469     vector<string> input;
470     input.push_back("@config");
471     input.push_back("--foo=1");
472     input.push_back("--baz=11");
473 
474     cmdline cmd(input);
475     cmd.set_options_description(desc);
476     cmd.set_additional_parser(at_option_parser);
477 
478     vector<option> result = cmd.run();
479 
480     BOOST_REQUIRE(result.size() == 3);
481     BOOST_CHECK_EQUAL(result[0].string_key, "response-file");
482     BOOST_CHECK_EQUAL(result[0].value[0], "config");
483     BOOST_CHECK_EQUAL(result[1].string_key, "foo");
484     BOOST_CHECK_EQUAL(result[1].value[0], "1");
485     BOOST_CHECK_EQUAL(result[2].string_key, "bar");
486     BOOST_CHECK_EQUAL(result[2].value[0], "11");
487 
488     // Test that invalid options returned by additional style
489     // parser are detected.
490     cmdline cmd2(input);
491     cmd2.set_options_description(desc);
492     cmd2.set_additional_parser(at_option_parser_broken);
493 
494     BOOST_CHECK_THROW(cmd2.run(), unknown_option);
495 
496 }
497 
at_option_parser2(vector<string> & args)498 vector<option> at_option_parser2(vector<string>& args)
499 {
500     vector<option> result;
501     if ('@' == args[0][0]) {
502         // Simulate reading the response file.
503         result.push_back(option("foo", vector<string>(1, "1")));
504         result.push_back(option("bar", vector<string>(1, "1")));
505         args.erase(args.begin());
506     }
507     return result;
508 }
509 
510 
test_style_parser()511 void test_style_parser()
512 {
513     options_description desc;
514     desc.add_options()
515         ("foo", value<int>(), "foo")
516         ("bar", value<int>(), "bar")
517         ;
518 
519     vector<string> input;
520     input.push_back("@config");
521 
522     cmdline cmd(input);
523     cmd.set_options_description(desc);
524     cmd.extra_style_parser(at_option_parser2);
525 
526     vector<option> result = cmd.run();
527 
528     BOOST_REQUIRE(result.size() == 2);
529     BOOST_CHECK_EQUAL(result[0].string_key, "foo");
530     BOOST_CHECK_EQUAL(result[0].value[0], "1");
531     BOOST_CHECK_EQUAL(result[1].string_key, "bar");
532     BOOST_CHECK_EQUAL(result[1].value[0], "1");
533 }
534 
test_unregistered()535 void test_unregistered()
536 {
537     // Check unregisted option when no options are registed at all.
538     options_description desc;
539 
540     vector<string> input;
541     input.push_back("--foo=1");
542     input.push_back("--bar");
543     input.push_back("1");
544     input.push_back("-b");
545     input.push_back("-biz");
546 
547     cmdline cmd(input);
548     cmd.set_options_description(desc);
549     cmd.allow_unregistered();
550 
551     vector<option> result = cmd.run();
552     BOOST_REQUIRE(result.size() == 5);
553     // --foo=1
554     BOOST_CHECK_EQUAL(result[0].string_key, "foo");
555     BOOST_CHECK_EQUAL(result[0].unregistered, true);
556     BOOST_CHECK_EQUAL(result[0].value[0], "1");
557     // --bar
558     BOOST_CHECK_EQUAL(result[1].string_key, "bar");
559     BOOST_CHECK_EQUAL(result[1].unregistered, true);
560     BOOST_CHECK(result[1].value.empty());
561     // '1' is considered a positional option, not a value to
562     // --bar
563     BOOST_CHECK(result[2].string_key.empty());
564     BOOST_CHECK(result[2].position_key == 0);
565     BOOST_CHECK_EQUAL(result[2].unregistered, false);
566     BOOST_CHECK_EQUAL(result[2].value[0], "1");
567     // -b
568     BOOST_CHECK_EQUAL(result[3].string_key, "-b");
569     BOOST_CHECK_EQUAL(result[3].unregistered, true);
570     BOOST_CHECK(result[3].value.empty());
571     // -biz
572     BOOST_CHECK_EQUAL(result[4].string_key, "-b");
573     BOOST_CHECK_EQUAL(result[4].unregistered, true);
574     BOOST_CHECK_EQUAL(result[4].value[0], "iz");
575 
576     // Check sticky short options together with unregisted options.
577 
578     desc.add_options()
579         ("help,h", "")
580         ("magic,m", value<string>(), "")
581         ;
582 
583     input.clear();
584     input.push_back("-hc");
585     input.push_back("-mc");
586 
587 
588     cmdline cmd2(input);
589     cmd2.set_options_description(desc);
590     cmd2.allow_unregistered();
591 
592     result = cmd2.run();
593 
594     BOOST_REQUIRE(result.size() == 3);
595     BOOST_CHECK_EQUAL(result[0].string_key, "help");
596     BOOST_CHECK_EQUAL(result[0].unregistered, false);
597     BOOST_CHECK(result[0].value.empty());
598     BOOST_CHECK_EQUAL(result[1].string_key, "-c");
599     BOOST_CHECK_EQUAL(result[1].unregistered, true);
600     BOOST_CHECK(result[1].value.empty());
601     BOOST_CHECK_EQUAL(result[2].string_key, "magic");
602     BOOST_CHECK_EQUAL(result[2].unregistered, false);
603     BOOST_CHECK_EQUAL(result[2].value[0], "c");
604 
605     // CONSIDER:
606     // There's a corner case:
607     //   -foo
608     // when 'allow_long_disguise' is set. Should this be considered
609     // disguised long option 'foo' or short option '-f' with value 'oo'?
610     // It's not clear yet, so I'm leaving the decision till later.
611 }
612 
test_implicit_value()613 void test_implicit_value()
614 {
615     using namespace command_line_style;
616     cmdline::style_t style;
617 
618     style = cmdline::style_t(
619         allow_long | long_allow_adjacent
620         );
621 
622     test_case test_cases1[] = {
623         // 'bar' does not even look like option, so is consumed
624         {"--foo bar", s_success, "foo:bar"},
625         // '--bar' looks like option, and such option exists, so we don't consume this token
626         {"--foo --bar", s_success, "foo: bar:"},
627         // '--biz' looks like option, but does not match any existing one.
628         // Presently this results in parse error, since
629         // (1) in cmdline.cpp:finish_option, we only consume following tokens if they are
630         // requires
631         // (2) in cmdline.cpp:run, we let options consume following positional options
632         // For --biz, an exception is thrown between 1 and 2.
633         // We might want to fix that in future.
634         {"--foo --biz", s_unknown_option, ""},
635         {0, 0, 0}
636     };
637 
638     test_cmdline("foo? bar?", style, test_cases1);
639 }
640 
main(int,char **)641 int main(int /*ac*/, char** /*av*/)
642 {
643     test_long_options();
644     test_short_options();
645     test_dos_options();
646     test_disguised_long();
647     test_guessing();
648     test_arguments();
649     test_prefix();
650     test_additional_parser();
651     test_style_parser();
652     test_unregistered();
653     test_implicit_value();
654 
655     return 0;
656 }
657