1 /* 2 * Copyright (c) 2012-2014, Bruno Levy 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * * Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * * Neither the name of the ALICE Project-Team nor the names of its 14 * contributors may be used to endorse or promote products derived from this 15 * software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 * 29 * If you modify this software, you should include a notice giving the 30 * name of the person performing the modification, the date of modification, 31 * and the reason for such modification. 32 * 33 * Contact: Bruno Levy 34 * 35 * Bruno.Levy@inria.fr 36 * http://www.loria.fr/~levy 37 * 38 * ALICE Project 39 * LORIA, INRIA Lorraine, 40 * Campus Scientifique, BP 239 41 * 54506 VANDOEUVRE LES NANCY CEDEX 42 * FRANCE 43 * 44 */ 45 46 #include <geogram/basic/command_line.h> 47 #include <geogram/basic/command_line_args.h> 48 #include <geogram/basic/environment.h> 49 #include <geogram/basic/file_system.h> 50 #include <geogram/basic/logger.h> 51 #include <geogram/basic/process.h> 52 #include <geogram/bibliography/bibliography.h> 53 #include <geogram/NL/nl.h> 54 #include <iostream> 55 #include <iomanip> 56 57 #if defined(GEO_OS_LINUX) || defined(GEO_OS_DRAGONFLY) || defined(GEO_OS_APPLE) 58 #include <sys/ioctl.h> 59 #include <stdio.h> 60 #include <unistd.h> 61 #include <termios.h> 62 #endif 63 64 #ifdef GEO_OS_WINDOWS 65 #include <io.h> // for _isatty() 66 #endif 67 68 /** 69 * \brief Checks argument type 70 * \details Checks if argument type \p type is in allowed types \p 71 * allowed_types or is undefined. Multiple types can be specified in \p 72 * allowed_types by bit-OR'ing the values defined in enum 73 * GEO::CmdLine::ArgType. 74 * \param[in] type the type to be tested 75 * \param[in] allowed_types argument types allowed for \p type. 76 * \retval true if type is in \p allowed_types 77 * \retval false otherwise 78 * \see GEO::CmdLine::ArgType 79 */ 80 #define geo_assert_arg_type(type, allowed_types) \ 81 geo_assert(((type) & ~(allowed_types)) == 0) 82 83 namespace { 84 85 using namespace GEO; 86 using namespace CmdLine; 87 88 std::string config_file_name = "geogram.ini"; 89 bool auto_create_args = false; 90 bool loaded_config_file = false; 91 92 int geo_argc = 0; 93 char** geo_argv = nullptr; 94 95 // True if displaying help in a way that 96 // it will be easily processed by help2man 97 bool man_mode = false; 98 99 /** 100 * \brief Command line argument 101 * \details Arg stores information about command line arguments: 102 */ 103 struct Arg { 104 /** \brief The argument name */ 105 std::string name; 106 /** \brief The argument description */ 107 std::string desc; 108 /** \brief The argument type */ 109 ArgType type; 110 /** \brief The argument flags */ 111 ArgFlags flags; 112 }; 113 114 /** \brief Stores command line arguments by name */ 115 typedef std::map<std::string, Arg> Args; 116 117 /** 118 * \brief Stores the list of all arguments in a group 119 * \see ArgGroup 120 */ 121 typedef std::vector<std::string> GroupArgs; 122 123 /** 124 * \brief Command line argument group 125 * \details ArgGroup stores information about command line argument groups 126 */ 127 struct Group { 128 /** \brief The group name */ 129 std::string name; 130 /** \brief The group description */ 131 std::string desc; 132 /** \brief The group flags */ 133 ArgFlags flags; 134 /** \brief The list of arguments in the group */ 135 GroupArgs args; 136 }; 137 138 /** \brief Stores command line argument groups by name */ 139 typedef std::map<std::string, Group> Groups; 140 141 /** \brief List of group names ordered in declaration order */ 142 typedef std::vector<std::string> GroupNames; 143 144 /** 145 * \brief Command line private data 146 */ 147 struct CommandLineDesc { 148 /** \brief Program name */ 149 std::string argv0; 150 /** \brief Table of all declared arguments indexed by name */ 151 Args args; 152 /** \brief Table of all declared groups indexed by name */ 153 Groups groups; 154 /** \brief Oredered list of declared group names */ 155 GroupNames group_names; 156 }; 157 158 /** \brief Maximum length of a feature name */ 159 const unsigned int feature_max_length = 12; 160 161 /** \brief Pointer to command line private data */ 162 CommandLineDesc* desc_ = nullptr; 163 164 /** 165 * \brief Checks if an argument name matches a sub-strung 166 * \param[in] arg_in the sub-string to match 167 * \param[in] arg_name the argument name 168 * \retval true if \p arg_name contains \p arg_in 169 * \retval false otherwise 170 */ arg_matches(const std::string & arg_in,const std::string & arg_name)171 bool arg_matches( 172 const std::string& arg_in, const std::string& arg_name 173 ) { 174 return arg_name.find(arg_in) != std::string::npos; 175 } 176 177 /** 178 * \brief Prints bad argument value error message 179 * \param[in] name the argument name 180 * \param[in] s the bad value 181 * \param[in] type the argument type 182 * \return \c false 183 */ arg_value_error(const std::string & name,const std::string & s,const char * type)184 bool arg_value_error( 185 const std::string& name, 186 const std::string& s, const char* type 187 ) { 188 Logger::instance()->set_quiet(false); 189 Logger::err("CmdLine") 190 << "Argument " << name << " received a bad value: '" 191 << s << "' is not a " << type << " value" 192 << std::endl; 193 return false; 194 } 195 196 /** 197 * \brief Checks the value of an argument 198 * \details This function is used by parse_internal() to check the values 199 * passed to arguments on the command line. It verifies that the string \p 200 * s is convertible to the declared type of argument \p name (see 201 * declare_arg()). 202 * \param[in] name the argument name 203 * \param[in] s the string value of the argument 204 * \retval true if string \p s is convertible to the type of the argument 205 * \retval false otherwise 206 */ check_arg_value(const std::string & name,const std::string & s)207 bool check_arg_value( 208 const std::string& name, const std::string& s 209 ) { 210 ArgType type = get_arg_type(name); 211 if(type == ARG_UNDEFINED || type == ARG_STRING) { 212 return true; 213 } 214 215 if(type == ARG_INT) { 216 int value; 217 if(!String::from_string(s, value)) { 218 return arg_value_error(name, s, "integer"); 219 } else { 220 return true; 221 } 222 } 223 224 if(type == ARG_DOUBLE) { 225 double value; 226 if(!String::from_string(s, value)) { 227 return arg_value_error(name, s, "floating point"); 228 } else { 229 return true; 230 } 231 } 232 233 if(type == ARG_BOOL) { 234 bool value; 235 if(!String::from_string(s, value)) { 236 return arg_value_error(name, s, "boolean"); 237 } else { 238 return true; 239 } 240 } 241 242 if(type == ARG_PERCENT) { 243 std::string s2 = s; 244 if(s2.length() > 0 && s2[s2.length() - 1] == '%') { 245 s2.resize(s2.length() - 1); 246 } 247 double value; 248 if(!String::from_string(s2, value)) { 249 return arg_value_error(name, s, "percentage"); 250 } else { 251 return true; 252 } 253 } 254 255 return false; 256 } 257 258 259 /** 260 * \brief Parses the configuration file in the home directory. 261 * \details The configuration file "geogram.ini" in the home directory 262 * has name=value pairs for pre-initializing command line arguments. 263 * In addition it has sections indicated by square-breacketed names. 264 * Only the arguments in the section with the same name as the program 265 * are taken into account. Section [*] refers to all possible programs. 266 * \param[in] config_filename the name of the configuration file 267 * \param[in] program_name the name of the program 268 */ parse_config_file(const std::string & config_filename,const std::string & program_name)269 void parse_config_file( 270 const std::string& config_filename, const std::string& program_name 271 ) { 272 std::string section = "*"; 273 if(FileSystem::is_file(config_filename)) { 274 std::ifstream in(config_filename.c_str()); 275 std::string line; 276 while(std::getline(in,line)) { 277 if(line.length() >= 3 && line[0] == '[' && line[line.length()-1] == ']') { 278 section = String::to_uppercase(line.substr(1,line.length()-2)); 279 } else if(section == program_name || section == "*") { 280 size_t pos = line.find("="); 281 if(pos != std::string::npos) { 282 std::string argname = line.substr(0,pos); 283 std::string argval = line.substr(pos+1,line.length()-pos-1); 284 if(CmdLine::arg_is_declared(argname)) { 285 CmdLine::set_arg(argname, argval); 286 } else { 287 if(auto_create_args) { 288 CmdLine::declare_arg(argname, argval, "..."); 289 } else { 290 Logger::warn("config") << argname 291 << "=" << argval 292 << " ignored" 293 << std::endl; 294 } 295 } 296 } 297 } 298 } 299 loaded_config_file= true; 300 } 301 } 302 303 /** 304 * \brief Parses the configuration file in the home directory. 305 * \details The configuration file "geogram.ini" in the home directory 306 * has name=value pairs for pre-initializing command line arguments. 307 * In addition it has sections indicated by square-breacketed names. 308 * Only the arguments in the section with the same name as the program 309 * are taken into account. Section [*] refers to all possible programs. 310 * \param[in] argc number of arguments passed to main() 311 * \param[in] argv array of command line arguments passed to main() 312 */ parse_config_file(int argc,char ** argv)313 void parse_config_file(int argc, char** argv) { 314 geo_assert(argc >= 1); 315 std::string program_name = String::to_uppercase( 316 FileSystem::base_name(argv[0]) 317 ); 318 static bool init = false; 319 if(init) { 320 return; 321 } 322 init = true; 323 Logger::out("config") 324 << "Configuration file name:" << config_file_name 325 << std::endl; 326 Logger::out("config") 327 << "Home directory:" << FileSystem::home_directory() 328 << std::endl; 329 std::string config_filename = 330 FileSystem::home_directory() + "/" + config_file_name; 331 parse_config_file(config_filename, program_name); 332 } 333 334 /** 335 * \brief Parses the command line arguments 336 * \details This analyzes command line arguments passed to the main() 337 * program in \p argc and \p argv. Arguments not matching program 338 * options declared with declare_arg() are stored in output vector \p 339 * unparsed_args. 340 * \param[in] argc number of arguments passed to main() 341 * \param[in] argv array of command line arguments passed to main() 342 * \param[out] unparsed_args output vector of unparsed arguments 343 * \retval true if the command line arguments are successfully parsed 344 * \retval false otherwise 345 */ parse_internal(int argc,char ** argv,std::vector<std::string> & unparsed_args)346 bool parse_internal( 347 int argc, char** argv, std::vector<std::string>& unparsed_args 348 ) { 349 geo_argc = argc; 350 geo_argv = argv; 351 352 parse_config_file(argc, argv); 353 354 bool ok = true; 355 desc_->argv0 = argv[0]; 356 unparsed_args.clear(); 357 358 for(int i = 1; i < argc; i++) { 359 std::vector<std::string> parsed_arg; 360 String::split_string(argv[i], '=', parsed_arg); 361 if(parsed_arg.size() != 2) { 362 unparsed_args.push_back(argv[i]); 363 } else { 364 if( 365 String::string_starts_with(parsed_arg[0], "dbg:") || 366 desc_->args.find(parsed_arg[0]) != desc_->args.end() 367 ) { 368 if(!set_arg(parsed_arg[0], parsed_arg[1])) { 369 ok = false; 370 } 371 } else { 372 373 std::vector<std::string> matches; 374 for( auto& it : desc_->args) { 375 if(arg_matches(parsed_arg[0], it.first)) { 376 matches.push_back(it.first); 377 } 378 } 379 380 if(matches.size() == 1) { 381 if(!set_arg(matches[0], parsed_arg[1])) { 382 ok = false; 383 } 384 } else if(matches.size() >= 2) { 385 ok = false; 386 Logger::instance()->set_quiet(false); 387 Logger::err("CmdLine") 388 << "Argument is ambiguous: " << argv[i] 389 << std::endl 390 << "Possible matches: " 391 << String::join_strings(matches, ' ') 392 << std::endl; 393 } else { 394 ok = false; 395 Logger::instance()->set_quiet(false); 396 Logger::err("CmdLine") 397 << "Invalid argument: " << parsed_arg[0] 398 << std::endl; 399 } 400 } 401 } 402 } 403 return ok; 404 } 405 406 /** 407 * \brief Extracts the group name from an argument 408 * \details If the command line argument \p name has the form 409 * "group:arg", then "group" is returned, otherwise the argument is 410 * considered to be global and the "global" group is returned. 411 * \param[in] name the name of the argument 412 * \return the group name or "global" 413 */ arg_group(const std::string & name)414 std::string arg_group(const std::string& name) { 415 size_t pos = name.find(':'); 416 return pos == std::string::npos 417 ? std::string("global") 418 : name.substr(0, pos); 419 } 420 421 422 /** 423 * \brief Gets an argument as a string for display. 424 * \param[in] arg_name the name of the argument. 425 * \return a string with the value of the argument for 426 * display. 427 * \details It ignores least significant digits for floating 428 * point arguments. 429 */ get_display_arg(const std::string & arg_name)430 std::string get_display_arg(const std::string& arg_name) { 431 CmdLine::ArgType arg_type = get_arg_type(arg_name); 432 std::string result; 433 if(arg_type == CmdLine::ARG_DOUBLE) { 434 double x = CmdLine::get_arg_double(arg_name); 435 result = String::to_display_string(x); 436 } else if(arg_type == CmdLine::ARG_PERCENT) { 437 double x = CmdLine::get_arg_percent(arg_name, 100.0); 438 result = String::to_display_string(x) + "%"; 439 } else { 440 result = CmdLine::get_arg(arg_name); 441 if(result.length() > ui_terminal_width()/2) { 442 // TODO: fix display long lines in terminal 443 // (that trigger infinite loop for now) 444 result = "..."; 445 } 446 } 447 return result; 448 } 449 450 /** 451 * \brief Private data used for printing ArgGroup details 452 */ 453 struct Line { 454 std::string name; 455 std::string value; 456 std::string desc; 457 }; 458 459 /** 460 * \brief Prints arguments of a group 461 * \details This function is called by show_usage() to display help on a 462 * group of arguments. It displays a fancy box that summarizes all 463 * arguments of the group \p group. By default, only standard arguments 464 * are displayed. If parameter \p advanced is set to \c true, the function 465 * also displays advanced arguments. 466 * \param[in] group the group name 467 * \param[in] advanced boolean flag that controls the display of 468 * advanced arguments in the group. 469 * \see show_usage() 470 */ show_group(const std::string & group,bool advanced)471 void show_group(const std::string& group, bool advanced) { 472 473 auto it = desc_->groups.find(group); 474 if(it == desc_->groups.end()) { 475 return; 476 } 477 478 const Group& g = it->second; 479 bool advanced_group = g.flags & ARG_ADVANCED; 480 if(!advanced && advanced_group) { 481 return; 482 } 483 484 if(advanced_group) { 485 ui_separator(g.desc, "*" + g.name); 486 } else { 487 ui_separator(g.desc, g.name); 488 } 489 490 // Step 1: 491 // Build a vector of lines that contain the various elements to 492 // print for the argument, and compute the maximum width of the 493 // argument names followed by their default value. This will allow 494 // us to align the closing paren of the default value. 495 496 std::vector<Line> lines; 497 index_t max_left_width = 0; 498 499 for(size_t i = 0; i < g.args.size(); i++) { 500 auto ita = desc_->args.find(g.args[i]); 501 if(ita == desc_->args.end()) { 502 continue; 503 } 504 505 const Arg& arg = ita->second; 506 bool advanced_arg = arg.flags & ARG_ADVANCED; 507 if(!advanced && advanced_arg) { 508 continue; 509 } 510 511 const char* name_marker = (advanced_arg && !man_mode) ? "*" : " "; 512 513 Line line; 514 line.name = name_marker + arg.name; 515 line.value = " (=" + get_display_arg(arg.name) + ") : "; 516 line.desc = arg.desc; 517 lines.push_back(line); 518 519 max_left_width = std::max( 520 index_t(line.name.length() + line.value.length()), 521 max_left_width 522 ); 523 } 524 525 // Step 2: 526 // Print the lines constructed in step 1 with default values 527 // right aligned. 528 529 for(size_t i = 0; i < lines.size(); i++) { 530 const Line& line = lines[i]; 531 int value_width = int(max_left_width - line.name.length()); 532 std::ostringstream os; 533 os << line.name 534 << std::setw(value_width) << line.value 535 << line.desc 536 << std::endl; 537 ui_message(os.str(), max_left_width); 538 if(man_mode) { 539 ui_message("\n"); 540 } 541 } 542 } 543 544 } 545 546 /****************************************************************************/ 547 548 namespace GEO { 549 550 namespace CmdLine { 551 initialize()552 void initialize() { 553 desc_ = new CommandLineDesc; 554 declare_arg_group("global", ""); 555 } 556 terminate()557 void terminate() { 558 ui_close_separator(); 559 delete desc_; 560 desc_ = nullptr; 561 } 562 argc()563 int argc() { 564 return geo_argc; 565 } 566 argv()567 char** argv() { 568 return geo_argv; 569 } 570 set_config_file_name(const std::string & filename,bool auto_create)571 void set_config_file_name( 572 const std::string& filename, bool auto_create 573 ) { 574 config_file_name = filename; 575 auto_create_args = auto_create; 576 } 577 get_config_file_name()578 std::string get_config_file_name() { 579 return config_file_name; 580 } 581 load_config(const std::string & filename,const std::string & program_name)582 void load_config( 583 const std::string& filename, const std::string& program_name 584 ) { 585 parse_config_file(filename, program_name); 586 } 587 588 config_file_loaded()589 bool config_file_loaded() { 590 return loaded_config_file; 591 } 592 parse(int argc,char ** argv,std::vector<std::string> & unparsed_args,const std::string & additional_arg_specs)593 bool parse( 594 int argc, char** argv, std::vector<std::string>& unparsed_args, 595 const std::string& additional_arg_specs 596 ) { 597 if(!parse_internal(argc, argv, unparsed_args)) { 598 return false; 599 } 600 601 if(arg_is_declared("profile")) { 602 std::string profile = get_arg("profile"); 603 if(profile != "default") { 604 if(!set_profile(profile)) { 605 return false; 606 } 607 // Re-parse args to override values set by profiles 608 unparsed_args.clear(); 609 parse_internal(argc, argv, unparsed_args); 610 } 611 } 612 613 for(index_t i = 0; i < unparsed_args.size(); ++i) { 614 const std::string& arg = unparsed_args[i]; 615 if( 616 arg == "-h" || 617 arg == "-?" || 618 arg == "/h" || 619 arg == "/?" 620 ) { 621 show_usage(additional_arg_specs, true); 622 exit(0); 623 } 624 if(arg == "--help") { 625 CmdLine::set_arg("log:pretty",false); 626 man_mode = true; 627 show_usage(additional_arg_specs, true); 628 exit(0); 629 } 630 if(arg == "--version" || arg == "--v") { 631 std::cout << FileSystem::base_name(argv[0]) 632 << " " 633 << Environment::instance()->get_value("version") 634 << " (built " 635 << Environment::instance()->get_value( 636 "release_date") 637 << ")" 638 << std::endl 639 << "Copyright (C) 2006-2017" 640 << std::endl 641 << "The Geogram library used by this program is licensed" 642 << std::endl 643 << "under the 3-clauses BSD license." 644 << std::endl 645 << "Inria, the ALICE project" 646 << std::endl 647 << " <http://alice.loria.fr/software/geogram>" 648 << std::endl 649 << "Report Geogram bugs to the geogram mailing list, see: " 650 << std::endl 651 << " <https://gforge.inria.fr/mail/?group_id=5833>" 652 << std::endl; 653 exit(0); 654 } 655 } 656 657 index_t min_unparsed = 0; 658 index_t max_unparsed = 0; 659 std::vector<std::string> additional_args; 660 String::split_string(additional_arg_specs, ' ', additional_args); 661 for(index_t i = 0; i < additional_args.size(); ++i) { 662 const std::string& arg = additional_args[i]; 663 if(arg[0] == '<' && arg[arg.length() - 1] == '>') { 664 ++max_unparsed; 665 } else if( 666 arg[0] == '<' && 667 arg[arg.length() - 2] == '>' && 668 arg[arg.length() - 1] == '*' 669 ) { 670 min_unparsed=0; 671 max_unparsed=100000; 672 } else { 673 ++max_unparsed; 674 ++min_unparsed; 675 } 676 } 677 678 if( 679 unparsed_args.size() > max_unparsed || 680 unparsed_args.size() < min_unparsed 681 ) { 682 show_usage(additional_arg_specs); 683 return false; 684 } 685 686 #ifndef GEOGRAM_PSM 687 nlPrintfFuncs(geogram_printf, geogram_fprintf); 688 nlInitialize(argc, argv); 689 #endif 690 if( 691 CmdLine::arg_is_declared("nl:CUDA") && 692 CmdLine::get_arg_bool("nl:CUDA") 693 ) { 694 geo_cite("DBLP:journals/paapp/BuatoisCL09"); 695 } 696 697 return true; 698 } 699 parse(int argc,char ** argv)700 bool parse(int argc, char** argv) { 701 std::vector<std::string> unparsed_args; 702 return parse(argc, argv, unparsed_args, ""); 703 } 704 declare_arg_group(const std::string & name,const std::string & description,ArgFlags flags)705 void declare_arg_group( 706 const std::string& name, 707 const std::string& description, 708 ArgFlags flags 709 ) { 710 if(desc_->groups.find(name) != desc_->groups.end()) { 711 Logger::err("CmdLine") 712 << "Group is multiply defined: " << name 713 << std::endl; 714 return; 715 } 716 717 Group group; 718 group.name = name; 719 group.desc = description; 720 group.flags = flags; 721 desc_->groups[name] = group; 722 desc_->group_names.push_back(name); 723 } 724 declare_arg(const std::string & name,ArgType type,const std::string & default_value,const std::string & description,ArgFlags flags)725 void declare_arg( 726 const std::string& name, 727 ArgType type, 728 const std::string& default_value, 729 const std::string& description, 730 ArgFlags flags 731 ) { 732 if(desc_->args.find(name) != desc_->args.end()) { 733 Logger::err("CmdLine") 734 << "Argument is multiply defined: " << name 735 << std::endl; 736 return; 737 } 738 739 Arg arg; 740 arg.name = name; 741 arg.type = type; 742 arg.desc = description; 743 arg.flags = flags; 744 desc_->args[name] = arg; 745 746 Environment::instance()->set_value(name, default_value); 747 748 std::string group = arg_group(name); 749 auto it = desc_->groups.find(group); 750 if(it == desc_->groups.end()) { 751 Logger::err("CmdLine") 752 << "Argument group does not exist: " << name 753 << std::endl; 754 return; 755 } 756 757 it->second.args.push_back(name); 758 } 759 get_arg_type(const std::string & name)760 ArgType get_arg_type(const std::string& name) { 761 auto it = desc_->args.find(name); 762 return it == desc_->args.end() 763 ? ARG_UNDEFINED 764 : it->second.type; 765 } 766 get_arg(const std::string & name)767 std::string get_arg(const std::string& name) { 768 return Environment::instance()->get_value(name); 769 } 770 arg_is_declared(const std::string & name)771 bool arg_is_declared(const std::string& name) { 772 return get_arg_type(name) != ARG_UNDEFINED; 773 } 774 get_arg_int(const std::string & name)775 int get_arg_int(const std::string& name) { 776 ArgType type = get_arg_type(name); 777 geo_assert_arg_type(type, ARG_INT); 778 return String::to_int(get_arg(name)); 779 } 780 get_arg_uint(const std::string & name)781 unsigned int get_arg_uint(const std::string& name) { 782 ArgType type = get_arg_type(name); 783 geo_assert_arg_type(type, ARG_INT); 784 return String::to_uint(get_arg(name)); 785 } 786 get_arg_double(const std::string & name)787 double get_arg_double(const std::string& name) { 788 ArgType type = get_arg_type(name); 789 geo_assert_arg_type(type, ARG_DOUBLE); 790 return String::to_double(get_arg(name)); 791 } 792 get_arg_percent(const std::string & name,double reference)793 double get_arg_percent( 794 const std::string& name, double reference 795 ) { 796 ArgType type = get_arg_type(name); 797 geo_assert_arg_type(type, ARG_PERCENT); 798 double result; 799 std::string s = get_arg(name); 800 if(s.length() > 0 && s[s.length() - 1] == '%') { 801 s.resize(s.length() - 1); 802 result = String::to_double(s) * reference * 0.01; 803 Logger::out("CmdLine") 804 << "using " << name << "=" << result 805 << "(" << get_arg(name) << ")" 806 << std::endl; 807 } else { 808 result = String::to_double(s); 809 Logger::out("CmdLine") 810 << "using " << name << "=" << result 811 << std::endl; 812 } 813 return result; 814 } 815 get_arg_bool(const std::string & name)816 bool get_arg_bool(const std::string& name) { 817 ArgType type = get_arg_type(name); 818 geo_assert_arg_type(type, ARG_BOOL); 819 return Environment::instance()->has_value(name) && 820 String::to_bool(get_arg(name)); 821 } 822 set_arg(const std::string & name,const std::string & value)823 bool set_arg( 824 const std::string& name, const std::string& value 825 ) { 826 if(!check_arg_value(name, value)) { 827 return false; 828 } 829 Environment::instance()->set_value(name, value); 830 return true; 831 } 832 set_arg(const std::string & name,int value)833 void set_arg(const std::string& name, int value) { 834 ArgType type = get_arg_type(name); 835 geo_assert_arg_type( 836 type, ARG_INT | ARG_DOUBLE | ARG_PERCENT | ARG_STRING 837 ); 838 Environment::instance()->set_value(name, String::to_string(value)); 839 } 840 set_arg(const std::string & name,unsigned int value)841 void set_arg(const std::string& name, unsigned int value) { 842 ArgType type = get_arg_type(name); 843 geo_assert_arg_type( 844 type, ARG_INT | ARG_DOUBLE | ARG_PERCENT | ARG_STRING 845 ); 846 Environment::instance()->set_value(name, String::to_string(value)); 847 } 848 set_arg(const std::string & name,double value)849 void set_arg(const std::string& name, double value) { 850 ArgType type = get_arg_type(name); 851 geo_assert_arg_type(type, ARG_DOUBLE | ARG_PERCENT | ARG_STRING); 852 Environment::instance()->set_value(name, String::to_string(value)); 853 } 854 set_arg(const std::string & name,bool value)855 void set_arg(const std::string& name, bool value) { 856 ArgType type = get_arg_type(name); 857 geo_assert_arg_type(type, ARG_BOOL | ARG_STRING); 858 Environment::instance()->set_value(name, String::to_string(value)); 859 } 860 set_arg_percent(const std::string & name,double value)861 void set_arg_percent(const std::string& name, double value) { 862 ArgType type = get_arg_type(name); 863 geo_assert_arg_type(type, ARG_PERCENT | ARG_STRING); 864 Environment::instance()->set_value( 865 name, String::to_string(value) + "%" 866 ); 867 } 868 show_usage(const std::string & additional_args,bool advanced)869 void show_usage(const std::string& additional_args, bool advanced) { 870 std::string program_name = FileSystem::base_name(desc_->argv0); 871 Logger::instance()->set_quiet(false); 872 Logger::out("") 873 << "Usage: " << program_name << " " 874 << additional_args 875 << " <parameter=value>*" << std::endl; 876 if(!advanced) { 877 Logger::out("") 878 << "Showing basic parameters (use " << program_name 879 << " -h to see advanced parameters)" 880 << std::endl; 881 } 882 883 for(auto& it : desc_->group_names) { 884 show_group(it, advanced); 885 } 886 } 887 get_args(std::vector<std::string> & args)888 void get_args(std::vector<std::string>& args) { 889 args.clear(); 890 for(auto& it : desc_->args) { 891 std::string cur_arg = it.first + "=" + get_arg(it.first); 892 args.push_back(cur_arg); 893 } 894 } 895 } 896 } 897 898 // =================== Console logging utilies ===================== 899 900 namespace { 901 902 using namespace GEO; 903 using namespace CmdLine; 904 905 /** Keeps length of a feature name */ 906 bool ui_separator_opened = false; 907 908 /** Width of the current terminal */ 909 index_t ui_term_width = 79; 910 911 /** Terminal left margin */ 912 index_t ui_left_margin = 0; 913 914 /** Terminal right margin */ 915 index_t ui_right_margin = 0; 916 917 /** Characters of the progress wheel */ 918 const char working[] = {'|', '/', '-', '\\'}; 919 920 /** Current index in the progress wheel */ 921 index_t working_index = 0; 922 923 /** Characters of the progress wave */ 924 const char waves[] = {',', '.', 'o', 'O', '\'', 'O', 'o', '.', ','}; 925 926 /** 927 * \brief Gets the console output stream 928 * \return a reference to a std::ostream 929 */ ui_out()930 inline std::ostream& ui_out() { 931 return std::cout; 932 } 933 934 /** 935 * \brief Prints a sequence of identical chars 936 * \param[in] c the character to print 937 * \param[in] nb the number of characters to print 938 */ ui_pad(char c,size_t nb)939 inline void ui_pad(char c, size_t nb) { 940 for(index_t i = 0; i < nb; i++) { 941 std::cout << c; 942 } 943 } 944 945 /** 946 * \brief Checks if the standard output is redirected 947 * \details It is used by the various console formatting functions to 948 * determine if messages are printed in pretty mode or not. 949 * \retval true if the console is redirected to a file 950 * \retval false otherwise 951 */ is_redirected()952 bool is_redirected() { 953 static bool initialized = false; 954 static bool result; 955 if(!initialized) { 956 #ifdef GEO_OS_WINDOWS 957 result = !_isatty(1); 958 #else 959 result = !isatty(1); 960 #endif 961 initialized = true; 962 } 963 return result || !Logger::instance()->is_pretty(); 964 } 965 966 /** 967 * \brief Recomputes the width of the terminal 968 */ update_ui_term_width()969 void update_ui_term_width() { 970 #ifdef GEO_OS_EMSCRIPTEN 971 return; // ioctl not implemented under emscripten 972 #else 973 #ifndef GEO_OS_WINDOWS 974 if(is_redirected()) { 975 return; 976 } 977 struct winsize w; 978 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); 979 ui_term_width = w.ws_col; 980 if(ui_term_width < 20) { 981 ui_term_width = 79; 982 } 983 if(ui_term_width <= 82) { 984 ui_left_margin = 0; 985 ui_right_margin = 0; 986 } else if(ui_term_width < 90) { 987 ui_left_margin = 2; 988 ui_right_margin = 2; 989 } else { 990 ui_left_margin = 4; 991 ui_right_margin = 4; 992 } 993 #endif 994 #endif 995 } 996 997 /** 998 * \brief Safe unsigned subtraction 999 * \return a - b if a > b, 0 otherwise 1000 */ sub(size_t a,size_t b)1001 inline size_t sub(size_t a, size_t b) { 1002 return a > b ? a - b : 0; 1003 } 1004 } 1005 1006 namespace GEO { 1007 1008 namespace CmdLine { 1009 ui_terminal_width()1010 index_t ui_terminal_width() { 1011 index_t ui_term_width_bkp = ui_term_width; 1012 update_ui_term_width(); 1013 ui_term_width = std::min(ui_term_width, ui_term_width_bkp); 1014 return ui_term_width; 1015 } 1016 ui_separator()1017 void ui_separator() { 1018 if(Logger::instance()->is_quiet() || is_redirected()) { 1019 return; 1020 } 1021 1022 update_ui_term_width(); 1023 ui_separator_opened = true; 1024 1025 ui_out() << " "; 1026 ui_pad(' ', ui_left_margin); 1027 ui_pad( 1028 '_', 1029 sub(ui_terminal_width(), 2 + ui_left_margin + ui_right_margin) 1030 ); 1031 ui_out() << " " << std::endl; 1032 1033 // Force a blank line under the separator 1034 ui_message("\n"); 1035 } 1036 ui_separator(const std::string & title,const std::string & short_title)1037 void ui_separator( 1038 const std::string& title, 1039 const std::string& short_title 1040 ) { 1041 if(Logger::instance()->is_quiet()) { 1042 return; 1043 } 1044 1045 if(man_mode) { 1046 if(title == "") { 1047 return; 1048 } 1049 ui_out() << std::endl; 1050 std::string shortt = short_title; 1051 if(shortt.length() > 0 && shortt[0] == '*') { 1052 shortt = shortt.substr(1, shortt.length()-1); 1053 ui_out() << title << " (\"" << shortt << ":*\" options, advanced)" 1054 << std::endl; 1055 } else { 1056 ui_out() << title << " (\"" << shortt << ":*\" options)" 1057 << std::endl; 1058 } 1059 ui_out() << std::endl << std::endl; 1060 return; 1061 } 1062 1063 if(is_redirected()) { 1064 ui_out() << std::endl; 1065 if(short_title != "" && title != "") { 1066 ui_out() << "=[" << short_title << "]=[" 1067 << title << "]=" << std::endl; 1068 } else { 1069 std::string s = title + short_title; 1070 ui_out() << "=[" << s << "]=" << std::endl; 1071 } 1072 return; 1073 } 1074 1075 update_ui_term_width(); 1076 ui_separator_opened = true; 1077 1078 size_t L = title.length() + short_title.length(); 1079 1080 ui_out() << " "; 1081 ui_pad(' ', ui_left_margin); 1082 ui_pad('_', L + 14); 1083 ui_out() << std::endl; 1084 1085 ui_pad(' ', ui_left_margin); 1086 if(short_title != "" && title != "") { 1087 ui_out() << " _/ ==[" << short_title << "]====[" 1088 << title << "]== \\"; 1089 } else { 1090 std::string s = title + short_title; 1091 ui_out() << " _/ =====[" << s << "]===== \\"; 1092 } 1093 1094 ui_pad( 1095 '_', 1096 sub( 1097 ui_terminal_width(), 1098 19 + L + ui_left_margin + ui_right_margin 1099 ) 1100 ); 1101 ui_out() << std::endl; 1102 1103 // Force a blank line under the separator 1104 ui_message("\n"); 1105 } 1106 ui_message(const std::string & message)1107 void ui_message(const std::string& message) { 1108 // By default, wrap to the column that is right after the feature 1109 // name and its decorations. 1110 ui_message(message, feature_max_length + 5); 1111 } 1112 ui_message(const std::string & message,index_t wrap_margin)1113 void ui_message( 1114 const std::string& message, 1115 index_t wrap_margin 1116 ) { 1117 if(Logger::instance()->is_quiet()) { 1118 return; 1119 } 1120 1121 if(!ui_separator_opened) { 1122 ui_separator(); 1123 } 1124 1125 if(is_redirected()) { 1126 ui_out() << message; 1127 return; 1128 } 1129 1130 std::string cur = message; 1131 size_t maxL = 1132 sub(ui_terminal_width(), 4 + ui_left_margin + ui_right_margin); 1133 index_t wrap = 0; 1134 1135 for(;;) { 1136 std::size_t newline = cur.find('\n'); 1137 if(newline != std::string::npos && newline < maxL) { 1138 // Got a new line that occurs before the right border 1139 // We cut before the newline and pad with spaces 1140 ui_pad(' ', ui_left_margin); 1141 ui_out() << "| "; 1142 ui_pad(' ', wrap); 1143 ui_out() << cur.substr(0, newline); 1144 ui_pad(' ', sub(maxL,newline)); 1145 ui_out() << " |" << std::endl; 1146 cur = cur.substr(newline + 1); 1147 } else if(cur.length() > maxL) { 1148 // The line length runs past the right border 1149 // We cut the string just before the border 1150 ui_pad(' ', ui_left_margin); 1151 ui_out() << "| "; 1152 ui_pad(' ', wrap); 1153 ui_out() << cur.substr(0, maxL); 1154 ui_out() << " |" << std::endl; 1155 cur = cur.substr(maxL); 1156 } else if(cur.length() != 0) { 1157 // Print the remaining portion of the string 1158 // and pad with spaces 1159 ui_pad(' ', ui_left_margin); 1160 ui_out() << "| "; 1161 ui_pad(' ', wrap); 1162 ui_out() << cur; 1163 ui_pad(' ', sub(maxL,cur.length())); 1164 ui_out() << " |"; 1165 break; 1166 } else { 1167 // No more chars to print 1168 break; 1169 } 1170 1171 if(wrap == 0) { 1172 wrap = wrap_margin; 1173 maxL = sub(maxL,wrap_margin); 1174 } 1175 } 1176 } 1177 ui_clear_line()1178 void ui_clear_line() { 1179 if(Logger::instance()->is_quiet() || is_redirected()) { 1180 return; 1181 } 1182 1183 ui_pad('\b', ui_terminal_width()); 1184 ui_out() << std::flush; 1185 } 1186 ui_close_separator()1187 void ui_close_separator() { 1188 if(!ui_separator_opened) { 1189 return; 1190 } 1191 1192 if(Logger::instance()->is_quiet() || is_redirected()) { 1193 return; 1194 } 1195 1196 ui_pad(' ', ui_left_margin); 1197 ui_out() << '\\'; 1198 ui_pad( 1199 '_', 1200 sub(ui_terminal_width(), 2 + ui_left_margin + ui_right_margin) 1201 ); 1202 ui_out() << '/'; 1203 ui_out() << std::endl; 1204 1205 ui_separator_opened = false; 1206 } 1207 ui_progress(const std::string & task_name,index_t val,index_t percent,bool clear)1208 void ui_progress( 1209 const std::string& task_name, index_t val, index_t percent, 1210 bool clear 1211 ) { 1212 if(Logger::instance()->is_quiet() || is_redirected()) { 1213 return; 1214 } 1215 1216 working_index++; 1217 1218 std::ostringstream os; 1219 1220 if(percent != val) { 1221 os << ui_feature(task_name) 1222 << "(" 1223 << working[(working_index % sizeof(working))] 1224 << ")-[" 1225 << std::setw(3) << percent 1226 << "%]-[" 1227 << std::setw(3) << val 1228 << "]--["; 1229 } else { 1230 os << ui_feature(task_name) 1231 << "(" 1232 << working[(working_index % sizeof(working))] 1233 << ")-[" 1234 << std::setw(3) << percent 1235 << "%]--------["; 1236 } 1237 1238 size_t max_L = 1239 sub(ui_terminal_width(), 43 + ui_left_margin + ui_right_margin); 1240 1241 if(val > max_L) { 1242 // No space enough to expand the progress bar 1243 // Do some animation... 1244 for(index_t i = 0; i < max_L; i++) { 1245 os << waves[((val - i + working_index) % sizeof(waves))]; 1246 } 1247 } else { 1248 for(index_t i = 0; i < val; i++) { 1249 os << "o"; 1250 } 1251 } 1252 os << " ]"; 1253 1254 if(clear) { 1255 ui_clear_line(); 1256 } 1257 ui_message(os.str()); 1258 } 1259 ui_progress_time(const std::string & task_name,double elapsed,bool clear)1260 void ui_progress_time( 1261 const std::string& task_name, double elapsed, bool clear 1262 ) { 1263 if(Logger::instance()->is_quiet()) { 1264 return; 1265 } 1266 1267 std::ostringstream os; 1268 os << ui_feature(task_name) 1269 << "Elapsed time: " << elapsed 1270 << "s\n"; 1271 1272 if(clear) { 1273 ui_clear_line(); 1274 } 1275 ui_message(os.str()); 1276 } 1277 ui_progress_canceled(const std::string & task_name,double elapsed,index_t percent,bool clear)1278 void ui_progress_canceled( 1279 const std::string& task_name, 1280 double elapsed, index_t percent, bool clear 1281 ) { 1282 if(Logger::instance()->is_quiet()) { 1283 return; 1284 } 1285 1286 std::ostringstream os; 1287 os << ui_feature(task_name) 1288 << "Task canceled after " << elapsed 1289 << "s (" << percent << "%)\n"; 1290 1291 if(clear) { 1292 ui_clear_line(); 1293 } 1294 ui_message(os.str()); 1295 } 1296 ui_feature(const std::string & feat_in,bool show)1297 std::string ui_feature( 1298 const std::string& feat_in, bool show 1299 ) { 1300 if(feat_in.empty()) { 1301 return feat_in; 1302 } 1303 1304 if(!show) { 1305 return std::string(feature_max_length + 5, ' '); 1306 } 1307 1308 std::string result = feat_in; 1309 if(!is_redirected()) { 1310 result = result.substr(0, feature_max_length); 1311 } 1312 if(result.length() < feature_max_length) { 1313 result.append(feature_max_length - result.length(), ' '); 1314 } 1315 return "o-[" + result + "] "; 1316 } 1317 } 1318 } 1319 1320 #ifdef GEO_OS_ANDROID 1321 namespace { 1322 android_app* android_app_ = nullptr; 1323 } 1324 1325 namespace GEO { 1326 namespace CmdLine { set_android_app(android_app * app)1327 void set_android_app(android_app* app) { 1328 android_app_ = app; 1329 } 1330 get_android_app()1331 android_app* get_android_app() { 1332 return android_app_; 1333 } 1334 } 1335 } 1336 #endif 1337 1338