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