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