1 //////////////////////////////////////////////////////////////////////////////// 2 3 // Author: Andy Rushton 4 // Copyright: (c) Southampton University 1999-2004 5 // (c) Andy Rushton 2004 onwards 6 // License: BSD License, see ../docs/license.html 7 8 //////////////////////////////////////////////////////////////////////////////// 9 #include "cli_parser.hpp" 10 #include "file_system.hpp" 11 #include "dprintf.hpp" 12 //////////////////////////////////////////////////////////////////////////////// 13 14 namespace stlplus 15 { 16 17 //////////////////////////////////////////////////////////////////////////////// 18 // cli_definition internals 19 name(void) const20 const std::string& stlplus::cli_definition::name(void) const 21 { 22 return m_name; 23 } 24 kind(void) const25 stlplus::cli_kind_t stlplus::cli_definition::kind(void) const 26 { 27 return m_kind; 28 } 29 mode(void) const30 stlplus::cli_mode_t stlplus::cli_definition::mode(void) const 31 { 32 return m_mode; 33 } 34 message(void) const35 const std::string& stlplus::cli_definition::message(void) const 36 { 37 return m_message; 38 } 39 default_value(void) const40 const std::string& stlplus::cli_definition::default_value(void) const 41 { 42 return m_default; 43 } 44 45 //////////////////////////////////////////////////////////////////////////////// 46 // internal data structures 47 48 class cli_value 49 { 50 public: 51 unsigned m_definition; 52 std::string m_value; 53 unsigned m_level; 54 std::string m_source; 55 cli_value(unsigned definition,const std::string & value,unsigned level,const std::string & source)56 cli_value(unsigned definition, const std::string& value, unsigned level, const std::string& source) : 57 m_definition(definition), m_value(value), m_level(level), m_source(source) 58 { 59 } 60 }; 61 62 //////////////////////////////////////////////////////////////////////////////// 63 64 class cli_parser_data 65 { 66 public: 67 message_handler& m_messages; 68 std::string m_executable; 69 cli_parser::definitions m_definitions; 70 std::vector<cli_value> m_values; 71 unsigned m_level; 72 bool m_valid; 73 std::vector<std::string> m_ini_files; 74 75 public: 76 cli_parser_data(message_handler & messages)77 cli_parser_data(message_handler& messages) : 78 m_messages(messages), m_level(1), m_valid(true) 79 { 80 // ensure that the CLI's messages are in the message handler - these 81 // can be overridden from a file later - see message_handler 82 if (!m_messages.message_present("CLI_VALUE_MISSING")) 83 m_messages.add_message("CLI_VALUE_MISSING", "option @0 requires a value - end of line was reached instead"); 84 if (!m_messages.message_present("CLI_UNRECOGNISED_OPTION")) 85 m_messages.add_message("CLI_UNRECOGNISED_OPTION", "@0 is not a recognised option for this command"); 86 if (!m_messages.message_present("CLI_NO_VALUES")) 87 m_messages.add_message("CLI_NO_VALUES", "argument @0 is invalid - this program doesn't take command-line arguments"); 88 if (!m_messages.message_present("CLI_USAGE_PROGRAM")) 89 m_messages.add_message("CLI_USAGE_PROGRAM", "usage:\n\t@0 { arguments }"); 90 if (!m_messages.message_present("CLI_USAGE_DEFINITIONS")) 91 m_messages.add_message("CLI_USAGE_DEFINITIONS", "arguments:"); 92 if (!m_messages.message_present("CLI_USAGE_VALUES")) 93 m_messages.add_message("CLI_USAGE_VALUES", "values:"); 94 if (!m_messages.message_present("CLI_USAGE_VALUE_RESULT")) 95 m_messages.add_message("CLI_USAGE_VALUE_RESULT", "\t@0 : from @1"); 96 if (!m_messages.message_present("CLI_USAGE_SWITCH_RESULT")) 97 m_messages.add_message("CLI_USAGE_SWITCH_RESULT", "\t-@0 : from @1"); 98 if (!m_messages.message_present("CLI_USAGE_OPTION_RESULT")) 99 m_messages.add_message("CLI_USAGE_OPTION_RESULT", "\t-@0 @1 : from @2"); 100 if (!m_messages.message_present("CLI_INI_HEADER")) 101 m_messages.add_message("CLI_INI_HEADER", "configuration files:"); 102 if (!m_messages.message_present("CLI_INI_FILE_PRESENT")) 103 m_messages.add_message("CLI_INI_FILE_PRESENT", "\t@0"); 104 if (!m_messages.message_present("CLI_INI_FILE_ABSENT")) 105 m_messages.add_message("CLI_INI_FILE_ABSENT", "\t@0 (not found)"); 106 if (!m_messages.message_present("CLI_INI_VARIABLE")) 107 m_messages.add_message("CLI_INI_VARIABLE", "unknown variable \"@0\" found in Ini file"); 108 } 109 add_definition(const cli_parser::definition & definition)110 unsigned add_definition(const cli_parser::definition& definition) 111 { 112 m_definitions.push_back(definition); 113 return static_cast<unsigned>(m_definitions.size())-1; 114 } 115 name(unsigned i) const116 std::string name(unsigned i) const 117 { 118 if (i >= m_values.size()) throw cli_index_error("Index " + dformat("%u",i) + " out of range"); 119 return m_definitions[m_values[i].m_definition].name(); 120 } 121 id(unsigned i) const122 unsigned id(unsigned i) const 123 { 124 if (i >= m_values.size()) throw cli_index_error("Index " + dformat("%u",i) + " out of range"); 125 return m_values[i].m_definition; 126 } 127 kind(unsigned i) const128 cli_parser::kind_t kind(unsigned i) const 129 { 130 if (i >= m_values.size()) throw cli_index_error("Index " + dformat("%u",i) + " out of range"); 131 return m_definitions[m_values[i].m_definition].kind(); 132 } 133 mode(unsigned i) const134 cli_parser::mode_t mode(unsigned i) const 135 { 136 if (i >= m_values.size()) throw cli_index_error("Index " + dformat("%u",i) + " out of range"); 137 return m_definitions[m_values[i].m_definition].mode(); 138 } 139 140 // unsigned add_definition(const std::string& name, 141 // cli_parser::kind_t kind, 142 // cli_parser::mode_t mode, 143 // const std::string& message) 144 // { 145 // return add_definition(cli_parser::definition(name, kind, mode, message)); 146 // } 147 find_definition(const std::string & name)148 unsigned find_definition(const std::string& name) 149 { 150 // this does substring matching on the definitions and returns the first 151 // match - however, it requires at least one character in the substring so 152 // that the "" convention for command line arguments doesn't match with 153 // anything. It returns size() if it fails 154 for (unsigned i = 0; i < m_definitions.size(); i++) 155 { 156 std::string candidate = m_definitions[i].name(); 157 if ((candidate.empty() && name.empty()) || 158 (!name.empty() && candidate.size() >= name.size() && candidate.substr(0,name.size()) == name)) 159 return i; 160 } 161 return static_cast<unsigned>(m_definitions.size()); 162 } 163 clear_definitions(void)164 void clear_definitions(void) 165 { 166 m_definitions.clear(); 167 m_values.clear(); 168 reset_level(); 169 set_valid(); 170 } 171 reset_level(void)172 void reset_level(void) 173 { 174 // the starting level is 1 so that later functions can call clear_level with 175 // a value of m_level-1 without causing underflow 176 m_level = 1; 177 } 178 increase_level(void)179 void increase_level(void) 180 { 181 m_level++; 182 } 183 clear_level(unsigned definition,unsigned level)184 void clear_level(unsigned definition, unsigned level) 185 { 186 // clears all values with this definition at the specified level or below 187 for (std::vector<cli_value>::iterator i = m_values.begin(); i != m_values.end(); ) 188 { 189 if (i->m_definition == definition && i->m_level <= level) 190 i = m_values.erase(i); 191 else 192 i++; 193 } 194 } 195 set_valid(void)196 void set_valid(void) 197 { 198 m_valid = true; 199 } 200 set_invalid(void)201 void set_invalid(void) 202 { 203 m_valid = false; 204 } 205 valid(void) const206 bool valid(void) const 207 { 208 return m_valid; 209 } 210 add_value(unsigned definition,const std::string & value,const std::string & source)211 unsigned add_value(unsigned definition, const std::string& value, const std::string& source) 212 { 213 // behaviour depends on mode: 214 // - single: erase all previous values 215 // - multiple: erase values at a lower level than current 216 // - cumulative: erase no previous values 217 switch (m_definitions[definition].mode()) 218 { 219 case cli_single_mode: 220 clear_level(definition, m_level); 221 break; 222 case cli_multiple_mode: 223 clear_level(definition, m_level-1); 224 break; 225 case cli_cumulative_mode: 226 break; 227 } 228 m_values.push_back(cli_value(definition,value,m_level,source)); 229 return static_cast<unsigned>(m_values.size())-1; 230 } 231 add_switch(unsigned definition,bool value,const std::string & source)232 unsigned add_switch(unsigned definition, bool value, const std::string& source) 233 { 234 return add_value(definition, value ? "on" : "off", source); 235 } 236 erase_value(unsigned definition)237 void erase_value(unsigned definition) 238 { 239 // this simply erases all previous values 240 clear_level(definition, m_level); 241 } 242 add_ini_file(const std::string & file)243 void add_ini_file(const std::string& file) 244 { 245 m_ini_files.push_back(file); 246 } 247 ini_file_size(void) const248 unsigned ini_file_size(void) const 249 { 250 return static_cast<unsigned>(m_ini_files.size()); 251 } 252 ini_file(unsigned i) const253 const std::string& ini_file(unsigned i) const 254 { 255 return m_ini_files[i]; 256 } 257 add_checked_definition(const cli_parser::definition & definition)258 unsigned add_checked_definition(const cli_parser::definition& definition) 259 { 260 // check for stupid combinations 261 // at this stage the only really stupid one is to declare command line arguments to be switch mode 262 if (definition.name().empty() && definition.kind() == cli_switch_kind) 263 { 264 set_invalid(); 265 throw cli_mode_error("CLI arguments cannot be switch kind"); 266 } 267 // add the definition to the set of all definitions 268 unsigned i = add_definition(definition); 269 // also add it to the list of values, but only if it has a default value 270 if (!definition.default_value().empty()) 271 add_value(i, definition.default_value(), "builtin default"); 272 return i; 273 } 274 switch_value(unsigned i) const275 bool switch_value(unsigned i) const 276 { 277 if (i >= m_values.size()) throw cli_index_error("Index " + dformat("%u",i) + " out of range"); 278 if (kind(i) != cli_switch_kind) throw cli_mode_error(name(i) + " is not a switch kind"); 279 std::string value = m_values[i].m_value; 280 return value == "on" || value == "true" || value == "1"; 281 } 282 string_value(unsigned i) const283 std::string string_value(unsigned i) const 284 { 285 if (i >= m_values.size()) throw cli_index_error("Index " + dformat("%u",i) + " out of range"); 286 if (kind(i) != cli_value_kind) throw cli_mode_error(name(i) + " is not a value kind"); 287 return m_values[i].m_value; 288 } 289 set_defaults(const ini_manager & defaults,const std::string & ini_section)290 void set_defaults(const ini_manager& defaults, const std::string& ini_section) 291 { 292 // import default values from the Ini Manager 293 increase_level(); 294 // get the set of all names from the Ini manager so that illegal names generate meaningful error messages 295 std::vector<std::string> names = defaults.variable_names(ini_section); 296 for (unsigned i = 0; i < names.size(); i++) 297 { 298 std::string name = names[i]; 299 unsigned definition = find_definition(name); 300 if (definition == (unsigned)-1) 301 { 302 // not found - give an error report 303 message_position position(defaults.variable_filename(ini_section,name), 304 defaults.variable_linenumber(ini_section,name), 305 0); 306 m_messages.error(position,"CLI_INI_VARIABLE", name); 307 } 308 else 309 { 310 // found - so add the value 311 // multi-valued variables are entered as a comma-separated list and this is then turned into a vector 312 // the vector is empty if the value was empty 313 std::vector<std::string> values = defaults.variable_values(ini_section, name); 314 // an empty string is used to negate the value 315 if (values.empty()) 316 erase_value(definition); 317 else 318 { 319 std::string source = filespec_to_relative_path(defaults.variable_filename(ini_section, name)); 320 for (unsigned j = 0; j < values.size(); j++) 321 add_value(definition, values[j], source); 322 } 323 } 324 } 325 // add the set of ini files to the list for usage reports 326 for (unsigned j = 0; j < defaults.size(); j++) 327 add_ini_file(defaults.filename(j)); 328 } 329 parse(char * argv[])330 bool parse(char* argv[]) 331 { 332 bool result = true; 333 if (!argv) throw cli_argument_error("Argument vector cannot be null"); 334 increase_level(); 335 if (argv[0]) 336 m_executable = argv[0]; 337 for (unsigned i = 1; argv[i]; i++) 338 { 339 std::string name = argv[i]; 340 if (!name.empty() && name[0] == '-') 341 { 342 // we have a command line option 343 // Patch from Evgeny Pokhilko: Treat -- as - to support double dash command switches 344 // this is a simple work around that erases first -. Now it is -parameter as the parser needs 345 // TODO - implement idea of short and long parameters 346 if(name.length() > 1 && name[1] == '-') 347 name.erase(0, 1); 348 unsigned found = find_definition(name.substr(1, name.size()-1)); 349 if (found < m_definitions.size()) 350 { 351 // found it in its positive form 352 switch (m_definitions[found].kind()) 353 { 354 case cli_switch_kind: 355 add_switch(found, true, "command line"); 356 break; 357 case cli_value_kind: 358 // get the next argument in argv as the value of this option 359 // first check that there is a next value 360 if (!argv[i+1]) 361 result &= m_messages.error("CLI_VALUE_MISSING", name); 362 else 363 add_value(found, argv[++i], "command line"); 364 break; 365 } 366 } 367 else if (name.size() > 3 && name.substr(1,2) == "no") 368 { 369 found = find_definition(name.substr(3, name.size()-3)); 370 if (found < m_definitions.size()) 371 { 372 // found it in its negated form 373 switch (m_definitions[found].kind()) 374 { 375 case cli_switch_kind: 376 add_switch(found, false, "command line"); 377 break; 378 case cli_value_kind: 379 erase_value(found); 380 break; 381 } 382 } 383 else 384 { 385 // could not find this option in either its true or negated form 386 result &= m_messages.error("CLI_UNRECOGNISED_OPTION", name); 387 } 388 } 389 else 390 { 391 // could not find this option and it could not be negated 392 result &= m_messages.error("CLI_UNRECOGNISED_OPTION", name); 393 } 394 } 395 else 396 { 397 // we have a command-line value which is represented internally as an option with an empty string as its name 398 // some very obscure commands do not have values - only options, so allow for that case too 399 unsigned found = find_definition(""); 400 if (found < m_definitions.size()) 401 add_value(found, name, "command line"); 402 else 403 result &= m_messages.error("CLI_NO_VALUES", name); 404 } 405 } 406 if (!result) set_invalid(); 407 return result; 408 } 409 usage(void) const410 void usage(void) const 411 { 412 m_messages.information("CLI_USAGE_PROGRAM", m_executable); 413 m_messages.information("CLI_USAGE_DEFINITIONS"); 414 for (unsigned d = 0; d < m_definitions.size(); d++) 415 m_messages.information(m_definitions[d].message()); 416 if (m_values.size() != 0) 417 { 418 m_messages.information("CLI_USAGE_VALUES"); 419 for (unsigned v = 0; v < m_values.size(); v++) 420 { 421 std::string source = m_values[v].m_source; 422 std::string key = name(v); 423 if (key.empty()) 424 { 425 // command-line values 426 m_messages.information("CLI_USAGE_VALUE_RESULT", string_value(v), source); 427 } 428 else if (kind(v) == cli_switch_kind) 429 { 430 // a switch 431 m_messages.information("CLI_USAGE_SWITCH_RESULT", (switch_value(v) ? name(v) : "no" + name(v)), source); 432 } 433 else 434 { 435 // other values 436 std::vector<std::string> args; 437 args.push_back(name(v)); 438 args.push_back(string_value(v)); 439 args.push_back(source); 440 m_messages.information("CLI_USAGE_OPTION_RESULT", args); 441 } 442 } 443 } 444 if (ini_file_size() > 0) 445 { 446 m_messages.information("CLI_INI_HEADER"); 447 for (unsigned i = 0; i < ini_file_size(); i++) 448 { 449 if (file_exists(ini_file(i))) 450 m_messages.information("CLI_INI_FILE_PRESENT", filespec_to_relative_path(ini_file(i))); 451 else 452 m_messages.information("CLI_INI_FILE_ABSENT", filespec_to_relative_path(ini_file(i))); 453 } 454 } 455 } 456 457 private: 458 // make this class uncopyable 459 cli_parser_data(const cli_parser_data&); 460 cli_parser_data& operator = (const cli_parser_data&); 461 }; 462 463 //////////////////////////////////////////////////////////////////////////////// 464 cli_parser(message_handler & messages)465 cli_parser::cli_parser(message_handler& messages) : 466 m_data(new cli_parser_data(messages)) 467 { 468 } 469 cli_parser(cli_parser::definitions_t definitions,message_handler & messages)470 cli_parser::cli_parser(cli_parser::definitions_t definitions, message_handler& messages) : 471 m_data(new cli_parser_data(messages)) 472 { 473 add_definitions(definitions); 474 } 475 cli_parser(cli_parser::definitions_t definitions,const ini_manager & defaults,const std::string & ini_section,message_handler & messages)476 cli_parser::cli_parser(cli_parser::definitions_t definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& messages) : 477 m_data(new cli_parser_data(messages)) 478 { 479 add_definitions(definitions); 480 set_defaults(defaults, ini_section); 481 } 482 cli_parser(char * argv[],cli_parser::definitions_t definitions,message_handler & messages)483 cli_parser::cli_parser(char* argv[], cli_parser::definitions_t definitions, message_handler& messages) : 484 m_data(new cli_parser_data(messages)) 485 { 486 add_definitions(definitions); 487 parse(argv); 488 } 489 cli_parser(char * argv[],cli_parser::definitions_t definitions,const ini_manager & defaults,const std::string & ini_section,message_handler & messages)490 cli_parser::cli_parser(char* argv[], cli_parser::definitions_t definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& messages) : 491 m_data(new cli_parser_data(messages)) 492 { 493 add_definitions(definitions); 494 set_defaults(defaults, ini_section); 495 parse(argv); 496 } 497 cli_parser(cli_parser::definitions definitions,message_handler & messages)498 cli_parser::cli_parser(cli_parser::definitions definitions, message_handler& messages) : 499 m_data(new cli_parser_data(messages)) 500 { 501 add_definitions(definitions); 502 } 503 cli_parser(cli_parser::definitions definitions,const ini_manager & defaults,const std::string & ini_section,message_handler & messages)504 cli_parser::cli_parser(cli_parser::definitions definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& messages) : 505 m_data(new cli_parser_data(messages)) 506 { 507 add_definitions(definitions); 508 set_defaults(defaults, ini_section); 509 } 510 cli_parser(char * argv[],cli_parser::definitions definitions,message_handler & messages)511 cli_parser::cli_parser(char* argv[], cli_parser::definitions definitions, message_handler& messages) : 512 m_data(new cli_parser_data(messages)) 513 { 514 add_definitions(definitions); 515 parse(argv); 516 } 517 cli_parser(char * argv[],cli_parser::definitions definitions,const ini_manager & defaults,const std::string & ini_section,message_handler & messages)518 cli_parser::cli_parser(char* argv[], cli_parser::definitions definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& messages) : 519 m_data(new cli_parser_data(messages)) 520 { 521 add_definitions(definitions); 522 set_defaults(defaults, ini_section); 523 parse(argv); 524 } 525 ~cli_parser(void)526 cli_parser::~cli_parser(void) 527 { 528 } 529 add_definitions(cli_parser::definitions_t definitions)530 void cli_parser::add_definitions(cli_parser::definitions_t definitions) 531 { 532 m_data->clear_definitions(); 533 // the definitions array is terminated by a definition with a null name pointer 534 for (unsigned i = 0; definitions[i].m_name; i++) 535 add_definition(definitions[i]); 536 } 537 add_definition(const cli_parser::definition_t & definition)538 unsigned cli_parser::add_definition(const cli_parser::definition_t& definition) 539 { 540 std::string name = definition.m_name ? definition.m_name : ""; 541 std::string message = definition.m_message ? definition.m_message : ""; 542 std::string value = definition.m_default ? definition.m_default : ""; 543 return add_definition(cli_parser::definition(name, definition.m_kind, definition.m_mode, message, value)); 544 } 545 add_definitions(cli_parser::definitions definitions)546 void cli_parser::add_definitions(cli_parser::definitions definitions) 547 { 548 m_data->clear_definitions(); 549 for (unsigned i = 0; i < definitions.size(); i++) 550 add_definition(definitions[i]); 551 } 552 add_definition(const cli_parser::definition & definition)553 unsigned cli_parser::add_definition(const cli_parser::definition& definition) 554 { 555 return m_data->add_checked_definition(definition); 556 } 557 set_defaults(const ini_manager & defaults,const std::string & ini_section)558 void cli_parser::set_defaults(const ini_manager& defaults, const std::string& ini_section) 559 { 560 m_data->set_defaults(defaults, ini_section); 561 } 562 parse(char * argv[])563 bool cli_parser::parse(char* argv[]) 564 { 565 return m_data->parse(argv); 566 } 567 valid(void)568 bool cli_parser::valid(void) 569 { 570 return m_data->valid(); 571 } 572 size(void) const573 unsigned cli_parser::size(void) const 574 { 575 return static_cast<unsigned>(m_data->m_values.size()); 576 } 577 name(unsigned i) const578 std::string cli_parser::name(unsigned i) const 579 { 580 return m_data->name(i); 581 } 582 id(unsigned i) const583 unsigned cli_parser::id(unsigned i) const 584 { 585 return m_data->id(i); 586 } 587 kind(unsigned i) const588 cli_parser::kind_t cli_parser::kind(unsigned i) const 589 { 590 return m_data->kind(i); 591 } 592 switch_kind(unsigned i) const593 bool cli_parser::switch_kind(unsigned i) const 594 { 595 return kind(i) == cli_switch_kind; 596 } 597 value_kind(unsigned i) const598 bool cli_parser::value_kind(unsigned i) const 599 { 600 return kind(i) == cli_value_kind; 601 } 602 mode(unsigned i) const603 cli_parser::mode_t cli_parser::mode(unsigned i) const 604 { 605 return m_data->mode(i); 606 } 607 single_mode(unsigned i) const608 bool cli_parser::single_mode(unsigned i) const 609 { 610 return mode(i) == cli_single_mode; 611 } 612 multiple_mode(unsigned i) const613 bool cli_parser::multiple_mode(unsigned i) const 614 { 615 return mode(i) == cli_multiple_mode; 616 } 617 cumulative_mode(unsigned i) const618 bool cli_parser::cumulative_mode(unsigned i) const 619 { 620 return mode(i) == cli_cumulative_mode; 621 } 622 switch_value(unsigned i) const623 bool cli_parser::switch_value(unsigned i) const 624 { 625 return m_data->switch_value(i); 626 } 627 string_value(unsigned i) const628 std::string cli_parser::string_value(unsigned i) const 629 { 630 return m_data->string_value(i); 631 } 632 633 //////////////////////////////////////////////////////////////////////////////// 634 usage(void) const635 void cli_parser::usage(void) const 636 { 637 m_data->usage(); 638 } 639 640 //////////////////////////////////////////////////////////////////////////////// 641 642 } // end namespace stlplus 643