123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- // Copyright Vladimir Prus 2002-2004.
- // Distributed under the Boost Software License, Version 1.0.
- // (See accompanying file LICENSE_1_0.txt
- // or copy at http://www.boost.org/LICENSE_1_0.txt)
- #include <boost/program_options/cmdline.hpp>
- #include <boost/program_options/options_description.hpp>
- #include <boost/program_options/detail/cmdline.hpp>
- using namespace boost::program_options;
- using boost::program_options::detail::cmdline;
- #include <iostream>
- #include <sstream>
- #include <vector>
- #include <cassert>
- using namespace std;
- #include "minitest.hpp"
- /* To facilitate testing, declare a number of error codes. Otherwise,
- we'd have to specify the type of exception that should be thrown.
- */
- const int s_success = 0;
- const int s_unknown_option = 1;
- const int s_ambiguous_option = 2;
- const int s_long_not_allowed = 3;
- const int s_long_adjacent_not_allowed = 4;
- const int s_short_adjacent_not_allowed = 5;
- const int s_empty_adjacent_parameter = 6;
- const int s_missing_parameter = 7;
- const int s_extra_parameter = 8;
- const int s_unrecognized_line = 9;
- int translate_syntax_error_kind(invalid_command_line_syntax::kind_t k)
- {
- invalid_command_line_syntax::kind_t table[] = {
- invalid_command_line_syntax::long_not_allowed,
- invalid_command_line_syntax::long_adjacent_not_allowed,
- invalid_command_line_syntax::short_adjacent_not_allowed,
- invalid_command_line_syntax::empty_adjacent_parameter,
- invalid_command_line_syntax::missing_parameter,
- invalid_command_line_syntax::extra_parameter,
- invalid_command_line_syntax::unrecognized_line
- };
- invalid_command_line_syntax::kind_t *b, *e, *i;
- b = table;
- e = table + sizeof(table)/sizeof(table[0]);
- i = std::find(b, e, k);
- assert(i != e);
- return std::distance(b, i) + 3;
- }
- struct test_case {
- const char* input;
- int expected_status;
- const char* expected_result;
- };
- /* Parses the syntax description in 'syntax' and initialized
- 'cmd' accordingly'
- The "boost::program_options" in parameter type is needed because CW9
- has std::detail and it causes an ambiguity.
- */
- void apply_syntax(options_description& desc,
- const char* syntax)
- {
-
- string s;
- stringstream ss;
- ss << syntax;
- while(ss >> s) {
- value_semantic* v = 0;
-
- if (*(s.end()-1) == '=') {
- v = value<string>();
- s.resize(s.size()-1);
- } else if (*(s.end()-1) == '?') {
- v = value<string>()->implicit_value("default");
- s.resize(s.size()-1);
- } else if (*(s.end()-1) == '*') {
- v = value<vector<string> >()->multitoken();
- s.resize(s.size()-1);
- } else if (*(s.end()-1) == '+') {
- v = value<vector<string> >()->multitoken();
- s.resize(s.size()-1);
- }
- if (v) {
- desc.add_options()
- (s.c_str(), v, "");
- } else {
- desc.add_options()
- (s.c_str(), "");
- }
- }
- }
- void test_cmdline(const char* syntax,
- command_line_style::style_t style,
- const test_case* cases)
- {
- for (int i = 0; cases[i].input; ++i) {
- // Parse input
- vector<string> xinput;
- {
- string s;
- stringstream ss;
- ss << cases[i].input;
- while (ss >> s) {
- xinput.push_back(s);
- }
- }
- options_description desc;
- apply_syntax(desc, syntax);
- cmdline cmd(xinput);
- cmd.style(style);
- cmd.set_options_description(desc);
- string result;
- int status = 0;
- try {
- vector<option> options = cmd.run();
- for(unsigned j = 0; j < options.size(); ++j)
- {
- option opt = options[j];
- if (opt.position_key != -1) {
- if (!result.empty())
- result += " ";
- result += opt.value[0];
- } else {
- if (!result.empty())
- result += " ";
- result += opt.string_key + ":";
- for (size_t k = 0; k < opt.value.size(); ++k) {
- if (k != 0)
- result += "-";
- result += opt.value[k];
- }
- }
- }
- }
- catch(unknown_option&) {
- status = s_unknown_option;
- }
- catch(ambiguous_option&) {
- status = s_ambiguous_option;
- }
- catch(invalid_command_line_syntax& e) {
- status = translate_syntax_error_kind(e.kind());
- }
- BOOST_CHECK_EQUAL(status, cases[i].expected_status);
- BOOST_CHECK_EQUAL(result, cases[i].expected_result);
- }
- }
- void test_long_options()
- {
- using namespace command_line_style;
- cmdline::style_t style = cmdline::style_t(
- allow_long | long_allow_adjacent);
- test_case test_cases1[] = {
- // Test that long options are recognized and everything else
- // is treated like arguments
- {"--foo foo -123 /asd", s_success, "foo: foo -123 /asd"},
- // Unknown option
- {"--unk", s_unknown_option, ""},
- // Test that abbreviated names do not work
- {"--fo", s_unknown_option, ""},
- // Test for disallowed parameter
- {"--foo=13", s_extra_parameter, ""},
- // Test option with required parameter
- {"--bar=", s_empty_adjacent_parameter, ""},
- {"--bar", s_missing_parameter, ""},
- {"--bar=123", s_success, "bar:123"},
- {0, 0, 0}
- };
- test_cmdline("foo bar=", style, test_cases1);
- style = cmdline::style_t(
- allow_long | long_allow_next);
- test_case test_cases2[] = {
- {"--bar 10", s_success, "bar:10"},
- {"--bar", s_missing_parameter, ""},
- // Since --bar accepts a parameter, --foo is
- // considered a value, even though it looks like
- // an option.
- {"--bar --foo", s_success, "bar:--foo"},
- {0, 0, 0}
- };
- test_cmdline("foo bar=", style, test_cases2);
- style = cmdline::style_t(
- allow_long | long_allow_adjacent
- | long_allow_next);
- test_case test_cases3[] = {
- {"--bar=10", s_success, "bar:10"},
- {"--bar 11", s_success, "bar:11"},
- {0, 0, 0}
- };
- test_cmdline("foo bar=", style, test_cases3);
- style = cmdline::style_t(
- allow_long | long_allow_adjacent
- | long_allow_next | case_insensitive);
- // Test case insensitive style.
- // Note that option names are normalized to lower case.
- test_case test_cases4[] = {
- {"--foo", s_success, "foo:"},
- {"--Foo", s_success, "foo:"},
- {"--bar=Ab", s_success, "bar:Ab"},
- {"--Bar=ab", s_success, "bar:ab"},
- {"--giz", s_success, "Giz:"},
- {0, 0, 0}
- };
- test_cmdline("foo bar= baz? Giz", style, test_cases4);
- }
- void test_short_options()
- {
- using namespace command_line_style;
- cmdline::style_t style;
- style = cmdline::style_t(
- allow_short | allow_dash_for_short
- | short_allow_adjacent);
- test_case test_cases1[] = {
- {"-d d /bar", s_success, "-d: d /bar"},
- // This is treated as error when long options are disabled
- {"--foo", s_success, "--foo"},
- {"-d13", s_extra_parameter, ""},
- {"-f14", s_success, "-f:14"},
- {"-g -f1", s_success, "-g: -f:1"},
- {"-f", s_missing_parameter, ""},
- {0, 0, 0}
- };
- test_cmdline(",d ,f= ,g", style, test_cases1);
- style = cmdline::style_t(
- allow_short | allow_dash_for_short
- | short_allow_next);
- test_case test_cases2[] = {
- {"-f 13", s_success, "-f:13"},
- {"-f -13", s_success, "-f:-13"},
- {"-f", s_missing_parameter, ""},
- {"-f /foo", s_success, "-f:/foo"},
- {"-f -d", s_missing_parameter, ""},
- {0, 0, 0}
- };
- test_cmdline(",d ,f=", style, test_cases2);
- style = cmdline::style_t(
- allow_short | short_allow_next
- | allow_dash_for_short | short_allow_adjacent);
- test_case test_cases3[] = {
- {"-f10", s_success, "-f:10"},
- {"-f 10", s_success, "-f:10"},
- {"-f -d", s_missing_parameter, ""},
- {0, 0, 0}
- };
- test_cmdline(",d ,f=", style, test_cases3);
- style = cmdline::style_t(
- allow_short | short_allow_next
- | allow_dash_for_short
- | short_allow_adjacent | allow_sticky);
- test_case test_cases4[] = {
- {"-de", s_success, "-d: -e:"},
- {"-df10", s_success, "-d: -f:10"},
- // FIXME: review
- //{"-d12", s_extra_parameter, ""},
- {"-f12", s_success, "-f:12"},
- {"-fe", s_success, "-f:e"},
- {0, 0, 0}
- };
- test_cmdline(",d ,f= ,e", style, test_cases4);
- }
- void test_dos_options()
- {
- using namespace command_line_style;
- cmdline::style_t style;
- style = cmdline::style_t(
- allow_short
- | allow_slash_for_short | short_allow_adjacent);
- test_case test_cases1[] = {
- {"/d d -bar", s_success, "-d: d -bar"},
- {"--foo", s_success, "--foo"},
- {"/d13", s_extra_parameter, ""},
- {"/f14", s_success, "-f:14"},
- {"/f", s_missing_parameter, ""},
- {0, 0, 0}
- };
- test_cmdline(",d ,f=", style, test_cases1);
- style = cmdline::style_t(
- allow_short
- | allow_slash_for_short | short_allow_next
- | short_allow_adjacent | allow_sticky);
- test_case test_cases2[] = {
- {"/de", s_extra_parameter, ""},
- {"/fe", s_success, "-f:e"},
- {0, 0, 0}
- };
- test_cmdline(",d ,f= ,e", style, test_cases2);
- }
- void test_disguised_long()
- {
- using namespace command_line_style;
- cmdline::style_t style;
- style = cmdline::style_t(
- allow_short | short_allow_adjacent
- | allow_dash_for_short
- | short_allow_next | allow_long_disguise
- | long_allow_adjacent);
- test_case test_cases1[] = {
- {"-foo -f", s_success, "foo: foo:"},
- {"-goo=x -gy", s_success, "goo:x goo:y"},
- {"-bee=x -by", s_success, "bee:x bee:y"},
- {0, 0, 0}
- };
- test_cmdline("foo,f goo,g= bee,b?", style, test_cases1);
- style = cmdline::style_t(style | allow_slash_for_short);
- test_case test_cases2[] = {
- {"/foo -f", s_success, "foo: foo:"},
- {"/goo=x", s_success, "goo:x"},
- {0, 0, 0}
- };
- test_cmdline("foo,f goo,g= bee,b?", style, test_cases2);
- }
- void test_guessing()
- {
- using namespace command_line_style;
- cmdline::style_t style;
- style = cmdline::style_t(
- allow_short | short_allow_adjacent
- | allow_dash_for_short
- | allow_long | long_allow_adjacent
- | allow_guessing | allow_long_disguise);
- test_case test_cases1[] = {
- {"--opt1", s_success, "opt123:"},
- {"--opt", s_ambiguous_option, ""},
- {"--f=1", s_success, "foo:1"},
- {"-far", s_success, "foo:ar"},
- {0, 0, 0}
- };
- test_cmdline("opt123 opt56 foo,f=", style, test_cases1);
- test_case test_cases2[] = {
- {"--fname file --fname2 file2", s_success, "fname: file fname2: file2"},
- {"--fnam file --fnam file2", s_ambiguous_option, ""},
- {"--fnam file --fname2 file2", s_ambiguous_option, ""},
- {"--fname2 file2 --fnam file", s_ambiguous_option, ""},
- {0, 0, 0}
- };
- test_cmdline("fname fname2", style, test_cases2);
- }
- void test_arguments()
- {
- using namespace command_line_style;
- cmdline::style_t style;
- style = cmdline::style_t(
- allow_short | allow_long
- | allow_dash_for_short
- | short_allow_adjacent | long_allow_adjacent);
- test_case test_cases1[] = {
- {"-f file -gx file2", s_success, "-f: file -g:x file2"},
- {"-f - -gx - -- -e", s_success, "-f: - -g:x - -e"},
- {0, 0, 0}
- };
- test_cmdline(",f ,g= ,e", style, test_cases1);
- // "--" should stop options regardless of whether long options are
- // allowed or not.
- style = cmdline::style_t(
- allow_short | short_allow_adjacent
- | allow_dash_for_short);
- test_case test_cases2[] = {
- {"-f - -gx - -- -e", s_success, "-f: - -g:x - -e"},
- {0, 0, 0}
- };
- test_cmdline(",f ,g= ,e", style, test_cases2);
- }
- void test_prefix()
- {
- using namespace command_line_style;
- cmdline::style_t style;
- style = cmdline::style_t(
- allow_short | allow_long
- | allow_dash_for_short
- | short_allow_adjacent | long_allow_adjacent
- );
- test_case test_cases1[] = {
- {"--foo.bar=12", s_success, "foo.bar:12"},
- {0, 0, 0}
- };
- test_cmdline("foo*=", style, test_cases1);
- }
- pair<string, string> at_option_parser(string const&s)
- {
- if ('@' == s[0])
- return std::make_pair(string("response-file"), s.substr(1));
- else
- return pair<string, string>();
- }
- pair<string, string> at_option_parser_broken(string const&s)
- {
- if ('@' == s[0])
- return std::make_pair(string("some garbage"), s.substr(1));
- else
- return pair<string, string>();
- }
- void test_additional_parser()
- {
- options_description desc;
- desc.add_options()
- ("response-file", value<string>(), "response file")
- ("foo", value<int>(), "foo")
- ("bar,baz", value<int>(), "bar")
- ;
- vector<string> input;
- input.push_back("@config");
- input.push_back("--foo=1");
- input.push_back("--baz=11");
- cmdline cmd(input);
- cmd.set_options_description(desc);
- cmd.set_additional_parser(at_option_parser);
- vector<option> result = cmd.run();
- BOOST_REQUIRE(result.size() == 3);
- BOOST_CHECK_EQUAL(result[0].string_key, "response-file");
- BOOST_CHECK_EQUAL(result[0].value[0], "config");
- BOOST_CHECK_EQUAL(result[1].string_key, "foo");
- BOOST_CHECK_EQUAL(result[1].value[0], "1");
- BOOST_CHECK_EQUAL(result[2].string_key, "bar");
- BOOST_CHECK_EQUAL(result[2].value[0], "11");
- // Test that invalid options returned by additional style
- // parser are detected.
- cmdline cmd2(input);
- cmd2.set_options_description(desc);
- cmd2.set_additional_parser(at_option_parser_broken);
- BOOST_CHECK_THROW(cmd2.run(), unknown_option);
- }
- vector<option> at_option_parser2(vector<string>& args)
- {
- vector<option> result;
- if ('@' == args[0][0]) {
- // Simulate reading the response file.
- result.push_back(option("foo", vector<string>(1, "1")));
- result.push_back(option("bar", vector<string>(1, "1")));
- args.erase(args.begin());
- }
- return result;
- }
- void test_style_parser()
- {
- options_description desc;
- desc.add_options()
- ("foo", value<int>(), "foo")
- ("bar", value<int>(), "bar")
- ;
- vector<string> input;
- input.push_back("@config");
- cmdline cmd(input);
- cmd.set_options_description(desc);
- cmd.extra_style_parser(at_option_parser2);
- vector<option> result = cmd.run();
- BOOST_REQUIRE(result.size() == 2);
- BOOST_CHECK_EQUAL(result[0].string_key, "foo");
- BOOST_CHECK_EQUAL(result[0].value[0], "1");
- BOOST_CHECK_EQUAL(result[1].string_key, "bar");
- BOOST_CHECK_EQUAL(result[1].value[0], "1");
- }
- void test_unregistered()
- {
- // Check unregisted option when no options are registed at all.
- options_description desc;
- vector<string> input;
- input.push_back("--foo=1");
- input.push_back("--bar");
- input.push_back("1");
- input.push_back("-b");
- input.push_back("-biz");
- cmdline cmd(input);
- cmd.set_options_description(desc);
- cmd.allow_unregistered();
-
- vector<option> result = cmd.run();
- BOOST_REQUIRE(result.size() == 5);
- // --foo=1
- BOOST_CHECK_EQUAL(result[0].string_key, "foo");
- BOOST_CHECK_EQUAL(result[0].unregistered, true);
- BOOST_CHECK_EQUAL(result[0].value[0], "1");
- // --bar
- BOOST_CHECK_EQUAL(result[1].string_key, "bar");
- BOOST_CHECK_EQUAL(result[1].unregistered, true);
- BOOST_CHECK(result[1].value.empty());
- // '1' is considered a positional option, not a value to
- // --bar
- BOOST_CHECK(result[2].string_key.empty());
- BOOST_CHECK(result[2].position_key == 0);
- BOOST_CHECK_EQUAL(result[2].unregistered, false);
- BOOST_CHECK_EQUAL(result[2].value[0], "1");
- // -b
- BOOST_CHECK_EQUAL(result[3].string_key, "-b");
- BOOST_CHECK_EQUAL(result[3].unregistered, true);
- BOOST_CHECK(result[3].value.empty());
- // -biz
- BOOST_CHECK_EQUAL(result[4].string_key, "-b");
- BOOST_CHECK_EQUAL(result[4].unregistered, true);
- BOOST_CHECK_EQUAL(result[4].value[0], "iz");
- // Check sticky short options together with unregisted options.
-
- desc.add_options()
- ("help,h", "")
- ("magic,m", value<string>(), "")
- ;
- input.clear();
- input.push_back("-hc");
- input.push_back("-mc");
- cmdline cmd2(input);
- cmd2.set_options_description(desc);
- cmd2.allow_unregistered();
-
- result = cmd2.run();
- BOOST_REQUIRE(result.size() == 3);
- BOOST_CHECK_EQUAL(result[0].string_key, "help");
- BOOST_CHECK_EQUAL(result[0].unregistered, false);
- BOOST_CHECK(result[0].value.empty());
- BOOST_CHECK_EQUAL(result[1].string_key, "-c");
- BOOST_CHECK_EQUAL(result[1].unregistered, true);
- BOOST_CHECK(result[1].value.empty());
- BOOST_CHECK_EQUAL(result[2].string_key, "magic");
- BOOST_CHECK_EQUAL(result[2].unregistered, false);
- BOOST_CHECK_EQUAL(result[2].value[0], "c");
- // CONSIDER:
- // There's a corner case:
- // -foo
- // when 'allow_long_disguise' is set. Should this be considered
- // disguised long option 'foo' or short option '-f' with value 'oo'?
- // It's not clear yet, so I'm leaving the decision till later.
- }
- void test_implicit_value()
- {
- using namespace command_line_style;
- cmdline::style_t style;
- style = cmdline::style_t(
- allow_long | long_allow_adjacent
- );
- test_case test_cases1[] = {
- // 'bar' does not even look like option, so is consumed
- {"--foo bar", s_success, "foo:bar"},
- // '--bar' looks like option, and such option exists, so we don't consume this token
- {"--foo --bar", s_success, "foo: bar:"},
- // '--biz' looks like option, but does not match any existing one.
- // Presently this results in parse error, since
- // (1) in cmdline.cpp:finish_option, we only consume following tokens if they are
- // requires
- // (2) in cmdline.cpp:run, we let options consume following positional options
- // For --biz, an exception is thrown between 1 and 2.
- // We might want to fix that in future.
- {"--foo --biz", s_unknown_option, ""},
- {0, 0, 0}
- };
- test_cmdline("foo? bar?", style, test_cases1);
- }
- int main(int /*ac*/, char** /*av*/)
- {
- test_long_options();
- test_short_options();
- test_dos_options();
- test_disguised_long();
- test_guessing();
- test_arguments();
- test_prefix();
- test_additional_parser();
- test_style_parser();
- test_unregistered();
- test_implicit_value();
- return 0;
- }
|