| 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 = 8; |
| 33 | const int s_unrecognized_line = 9; |
| 34 | |
| 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(first: b, last: e, val: k); |
| 50 | assert(i != e); |
| 51 | return std::distance(first: b, last: 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 | */ |
| 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(n: s.size()-1); |
| 79 | } else if (*(s.end()-1) == '?') { |
| 80 | v = value<string>()->implicit_value(v: "default" ); |
| 81 | s.resize(n: s.size()-1); |
| 82 | } else if (*(s.end()-1) == '*') { |
| 83 | v = value<vector<string> >()->multitoken(); |
| 84 | s.resize(n: s.size()-1); |
| 85 | } else if (*(s.end()-1) == '+') { |
| 86 | v = value<vector<string> >()->multitoken(); |
| 87 | s.resize(n: 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 | |
| 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(x: 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(k: e.kind()); |
| 156 | } |
| 157 | BOOST_CHECK_EQUAL(status, cases[i].expected_status); |
| 158 | BOOST_CHECK_EQUAL(result, cases[i].expected_result); |
| 159 | } |
| 160 | } |
| 161 | |
| 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 | {.input: "--foo foo -123 /asd" , .expected_status: s_success, .expected_result: "foo: foo -123 /asd" }, |
| 172 | |
| 173 | // Unknown option |
| 174 | {.input: "--unk" , .expected_status: s_unknown_option, .expected_result: "" }, |
| 175 | |
| 176 | // Test that abbreviated names do not work |
| 177 | {.input: "--fo" , .expected_status: s_unknown_option, .expected_result: "" }, |
| 178 | |
| 179 | // Test for disallowed parameter |
| 180 | {.input: "--foo=13" , .expected_status: s_extra_parameter, .expected_result: "" }, |
| 181 | |
| 182 | // Test option with required parameter |
| 183 | {.input: "--bar=" , .expected_status: s_empty_adjacent_parameter, .expected_result: "" }, |
| 184 | {.input: "--bar" , .expected_status: s_missing_parameter, .expected_result: "" }, |
| 185 | |
| 186 | {.input: "--bar=123" , .expected_status: s_success, .expected_result: "bar:123" }, |
| 187 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 188 | }; |
| 189 | test_cmdline(syntax: "foo bar=" , style, cases: test_cases1); |
| 190 | |
| 191 | |
| 192 | style = cmdline::style_t( |
| 193 | allow_long | long_allow_next); |
| 194 | |
| 195 | test_case test_cases2[] = { |
| 196 | {.input: "--bar 10" , .expected_status: s_success, .expected_result: "bar:10" }, |
| 197 | {.input: "--bar" , .expected_status: s_missing_parameter, .expected_result: "" }, |
| 198 | // Since --bar accepts a parameter, --foo is |
| 199 | // considered a value, even though it looks like |
| 200 | // an option. |
| 201 | {.input: "--bar --foo" , .expected_status: s_success, .expected_result: "bar:--foo" }, |
| 202 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 203 | }; |
| 204 | test_cmdline(syntax: "foo bar=" , style, cases: test_cases2); |
| 205 | style = cmdline::style_t( |
| 206 | allow_long | long_allow_adjacent |
| 207 | | long_allow_next); |
| 208 | |
| 209 | test_case test_cases3[] = { |
| 210 | {.input: "--bar=10" , .expected_status: s_success, .expected_result: "bar:10" }, |
| 211 | {.input: "--bar 11" , .expected_status: s_success, .expected_result: "bar:11" }, |
| 212 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 213 | }; |
| 214 | test_cmdline(syntax: "foo bar=" , style, cases: 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 | {.input: "--foo" , .expected_status: s_success, .expected_result: "foo:" }, |
| 224 | {.input: "--Foo" , .expected_status: s_success, .expected_result: "foo:" }, |
| 225 | {.input: "--bar=Ab" , .expected_status: s_success, .expected_result: "bar:Ab" }, |
| 226 | {.input: "--Bar=ab" , .expected_status: s_success, .expected_result: "bar:ab" }, |
| 227 | {.input: "--giz" , .expected_status: s_success, .expected_result: "Giz:" }, |
| 228 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 229 | }; |
| 230 | test_cmdline(syntax: "foo bar= baz? Giz" , style, cases: test_cases4); |
| 231 | } |
| 232 | |
| 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 | {.input: "-d d /bar" , .expected_status: s_success, .expected_result: "-d: d /bar" }, |
| 244 | // This is treated as error when long options are disabled |
| 245 | {.input: "--foo" , .expected_status: s_success, .expected_result: "--foo" }, |
| 246 | {.input: "-d13" , .expected_status: s_extra_parameter, .expected_result: "" }, |
| 247 | {.input: "-f14" , .expected_status: s_success, .expected_result: "-f:14" }, |
| 248 | {.input: "-g -f1" , .expected_status: s_success, .expected_result: "-g: -f:1" }, |
| 249 | {.input: "-f" , .expected_status: s_missing_parameter, .expected_result: "" }, |
| 250 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 251 | }; |
| 252 | test_cmdline(syntax: ",d ,f= ,g" , style, cases: 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 | {.input: "-f 13" , .expected_status: s_success, .expected_result: "-f:13" }, |
| 260 | {.input: "-f -13" , .expected_status: s_success, .expected_result: "-f:-13" }, |
| 261 | {.input: "-f" , .expected_status: s_missing_parameter, .expected_result: "" }, |
| 262 | {.input: "-f /foo" , .expected_status: s_success, .expected_result: "-f:/foo" }, |
| 263 | {.input: "-f -d" , .expected_status: s_missing_parameter, .expected_result: "" }, |
| 264 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 265 | }; |
| 266 | test_cmdline(syntax: ",d ,f=" , style, cases: 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 | {.input: "-f10" , .expected_status: s_success, .expected_result: "-f:10" }, |
| 274 | {.input: "-f 10" , .expected_status: s_success, .expected_result: "-f:10" }, |
| 275 | {.input: "-f -d" , .expected_status: s_missing_parameter, .expected_result: "" }, |
| 276 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 277 | }; |
| 278 | test_cmdline(syntax: ",d ,f=" , style, cases: 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 | {.input: "-de" , .expected_status: s_success, .expected_result: "-d: -e:" }, |
| 287 | {.input: "-df10" , .expected_status: s_success, .expected_result: "-d: -f:10" }, |
| 288 | // FIXME: review |
| 289 | //{"-d12", s_extra_parameter, ""}, |
| 290 | {.input: "-f12" , .expected_status: s_success, .expected_result: "-f:12" }, |
| 291 | {.input: "-fe" , .expected_status: s_success, .expected_result: "-f:e" }, |
| 292 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 293 | }; |
| 294 | test_cmdline(syntax: ",d ,f= ,e" , style, cases: test_cases4); |
| 295 | |
| 296 | } |
| 297 | |
| 298 | |
| 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 | {.input: "/d d -bar" , .expected_status: s_success, .expected_result: "-d: d -bar" }, |
| 310 | {.input: "--foo" , .expected_status: s_success, .expected_result: "--foo" }, |
| 311 | {.input: "/d13" , .expected_status: s_extra_parameter, .expected_result: "" }, |
| 312 | {.input: "/f14" , .expected_status: s_success, .expected_result: "-f:14" }, |
| 313 | {.input: "/f" , .expected_status: s_missing_parameter, .expected_result: "" }, |
| 314 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 315 | }; |
| 316 | test_cmdline(syntax: ",d ,f=" , style, cases: 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 | {.input: "/de" , .expected_status: s_extra_parameter, .expected_result: "" }, |
| 325 | {.input: "/fe" , .expected_status: s_success, .expected_result: "-f:e" }, |
| 326 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 327 | }; |
| 328 | test_cmdline(syntax: ",d ,f= ,e" , style, cases: test_cases2); |
| 329 | |
| 330 | } |
| 331 | |
| 332 | |
| 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 | {.input: "-foo -f" , .expected_status: s_success, .expected_result: "foo: foo:" }, |
| 346 | {.input: "-goo=x -gy" , .expected_status: s_success, .expected_result: "goo:x goo:y" }, |
| 347 | {.input: "-bee=x -by" , .expected_status: s_success, .expected_result: "bee:x bee:y" }, |
| 348 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 349 | }; |
| 350 | test_cmdline(syntax: "foo,f goo,g= bee,b?" , style, cases: test_cases1); |
| 351 | |
| 352 | style = cmdline::style_t(style | allow_slash_for_short); |
| 353 | test_case test_cases2[] = { |
| 354 | {.input: "/foo -f" , .expected_status: s_success, .expected_result: "foo: foo:" }, |
| 355 | {.input: "/goo=x" , .expected_status: s_success, .expected_result: "goo:x" }, |
| 356 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 357 | }; |
| 358 | test_cmdline(syntax: "foo,f goo,g= bee,b?" , style, cases: test_cases2); |
| 359 | } |
| 360 | |
| 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 | {.input: "--opt1" , .expected_status: s_success, .expected_result: "opt123:" }, |
| 374 | {.input: "--opt" , .expected_status: s_ambiguous_option, .expected_result: "" }, |
| 375 | {.input: "--f=1" , .expected_status: s_success, .expected_result: "foo:1" }, |
| 376 | {.input: "-far" , .expected_status: s_success, .expected_result: "foo:ar" }, |
| 377 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 378 | }; |
| 379 | test_cmdline(syntax: "opt123 opt56 foo,f=" , style, cases: test_cases1); |
| 380 | |
| 381 | test_case test_cases2[] = { |
| 382 | {.input: "--fname file --fname2 file2" , .expected_status: s_success, .expected_result: "fname: file fname2: file2" }, |
| 383 | {.input: "--fnam file --fnam file2" , .expected_status: s_ambiguous_option, .expected_result: "" }, |
| 384 | {.input: "--fnam file --fname2 file2" , .expected_status: s_ambiguous_option, .expected_result: "" }, |
| 385 | {.input: "--fname2 file2 --fnam file" , .expected_status: s_ambiguous_option, .expected_result: "" }, |
| 386 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 387 | }; |
| 388 | test_cmdline(syntax: "fname fname2" , style, cases: test_cases2); |
| 389 | } |
| 390 | |
| 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 | {.input: "-f file -gx file2" , .expected_status: s_success, .expected_result: "-f: file -g:x file2" }, |
| 403 | {.input: "-f - -gx - -- -e" , .expected_status: s_success, .expected_result: "-f: - -g:x - -e" }, |
| 404 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 405 | }; |
| 406 | test_cmdline(syntax: ",f ,g= ,e" , style, cases: 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 | {.input: "-f - -gx - -- -e" , .expected_status: s_success, .expected_result: "-f: - -g:x - -e" }, |
| 417 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 418 | }; |
| 419 | test_cmdline(syntax: ",f ,g= ,e" , style, cases: test_cases2); |
| 420 | } |
| 421 | |
| 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 | {.input: "--foo.bar=12" , .expected_status: s_success, .expected_result: "foo.bar:12" }, |
| 435 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 436 | }; |
| 437 | |
| 438 | test_cmdline(syntax: "foo*=" , style, cases: test_cases1); |
| 439 | } |
| 440 | |
| 441 | |
| 442 | pair<string, string> at_option_parser(string const&s) |
| 443 | { |
| 444 | if ('@' == s[0]) |
| 445 | return std::make_pair(x: string("response-file" ), y: s.substr(pos: 1)); |
| 446 | else |
| 447 | return pair<string, string>(); |
| 448 | } |
| 449 | |
| 450 | pair<string, string> at_option_parser_broken(string const&s) |
| 451 | { |
| 452 | if ('@' == s[0]) |
| 453 | return std::make_pair(x: string("some garbage" ), y: s.substr(pos: 1)); |
| 454 | else |
| 455 | return pair<string, string>(); |
| 456 | } |
| 457 | |
| 458 | |
| 459 | |
| 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(x: "@config" ); |
| 471 | input.push_back(x: "--foo=1" ); |
| 472 | input.push_back(x: "--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 | |
| 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(x: option("foo" , vector<string>(1, "1" ))); |
| 504 | result.push_back(x: option("bar" , vector<string>(1, "1" ))); |
| 505 | args.erase(position: args.begin()); |
| 506 | } |
| 507 | return result; |
| 508 | } |
| 509 | |
| 510 | |
| 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(x: "@config" ); |
| 521 | |
| 522 | cmdline cmd(input); |
| 523 | cmd.set_options_description(desc); |
| 524 | cmd.extra_style_parser(s: 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 | |
| 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(x: "--foo=1" ); |
| 542 | input.push_back(x: "--bar" ); |
| 543 | input.push_back(x: "1" ); |
| 544 | input.push_back(x: "-b" ); |
| 545 | input.push_back(x: "-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(x: "-hc" ); |
| 585 | input.push_back(x: "-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 | |
| 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 | {.input: "--foo bar" , .expected_status: s_success, .expected_result: "foo:bar" }, |
| 625 | // '--bar' looks like option, and such option exists, so we don't consume this token |
| 626 | {.input: "--foo --bar" , .expected_status: s_success, .expected_result: "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 | {.input: "--foo --biz" , .expected_status: s_unknown_option, .expected_result: "" }, |
| 635 | {.input: 0, .expected_status: 0, .expected_result: 0} |
| 636 | }; |
| 637 | |
| 638 | test_cmdline(syntax: "foo? bar?" , style, cases: test_cases1); |
| 639 | } |
| 640 | |
| 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 | |