]> Dogcows Code - chaz/yoink/blob - src/stlplus/subsystems/cli_parser.cpp
archiving support for releases
[chaz/yoink] / src / stlplus / subsystems / cli_parser.cpp
1 ////////////////////////////////////////////////////////////////////////////////
2
3 // Author: Andy Rushton
4 // Copyright: (c) Southampton University 1999-2004
5 // (c) Andy Rushton 2004-2009
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
20 const std::string& stlplus::cli_definition::name(void) const
21 {
22 return m_name;
23 }
24
25 stlplus::cli_kind_t stlplus::cli_definition::kind(void) const
26 {
27 return m_kind;
28 }
29
30 stlplus::cli_mode_t stlplus::cli_definition::mode(void) const
31 {
32 return m_mode;
33 }
34
35 const std::string& stlplus::cli_definition::message(void) const
36 {
37 return m_message;
38 }
39
40 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
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
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
110 unsigned add_definition(const cli_parser::definition& definition)
111 {
112 m_definitions.push_back(definition);
113 return m_definitions.size()-1;
114 }
115
116 std::string name(unsigned i) const throw(cli_index_error)
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
122 unsigned id(unsigned i) const throw(cli_index_error)
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
128 cli_parser::kind_t kind(unsigned i) const throw(cli_index_error)
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
134 cli_parser::mode_t mode(unsigned i) const throw(cli_index_error)
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
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 m_definitions.size();
162 }
163
164 void clear_definitions(void)
165 {
166 m_definitions.clear();
167 m_values.clear();
168 reset_level();
169 set_valid();
170 }
171
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
179 void increase_level(void)
180 {
181 m_level++;
182 }
183
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
196 void set_valid(void)
197 {
198 m_valid = true;
199 }
200
201 void set_invalid(void)
202 {
203 m_valid = false;
204 }
205
206 bool valid(void) const
207 {
208 return m_valid;
209 }
210
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 m_values.size()-1;
230 }
231
232 unsigned add_switch(unsigned definition, bool value, const std::string& source)
233 {
234 return add_value(definition, value ? "on" : "off", source);
235 }
236
237 void erase_value(unsigned definition)
238 {
239 // this simply erases all previous values
240 clear_level(definition, m_level);
241 }
242
243 void add_ini_file(const std::string& file)
244 {
245 m_ini_files.push_back(file);
246 }
247
248 unsigned ini_file_size(void) const
249 {
250 return m_ini_files.size();
251 }
252
253 const std::string& ini_file(unsigned i) const
254 {
255 return m_ini_files[i];
256 }
257
258 unsigned add_checked_definition(const cli_parser::definition& definition) throw(cli_mode_error)
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
275 bool switch_value(unsigned i) const throw(cli_mode_error,cli_index_error)
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
283 std::string string_value(unsigned i) const throw(cli_mode_error,cli_index_error)
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
290 void set_defaults(const ini_manager& defaults, const std::string& ini_section) throw()
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
330 bool parse(char* argv[]) throw(cli_argument_error,message_handler_id_error,message_handler_format_error)
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 unsigned found = find_definition(name.substr(1, name.size()-1));
344 if (found < m_definitions.size())
345 {
346 // found it in its positive form
347 switch (m_definitions[found].kind())
348 {
349 case cli_switch_kind:
350 add_switch(found, true, "command line");
351 break;
352 case cli_value_kind:
353 // get the next argument in argv as the value of this option
354 // first check that there is a next value
355 if (!argv[i+1])
356 result &= m_messages.error("CLI_VALUE_MISSING", name);
357 else
358 add_value(found, argv[++i], "command line");
359 break;
360 }
361 }
362 else if (name.size() > 3 && name.substr(1,2) == "no")
363 {
364 found = find_definition(name.substr(3, name.size()-3));
365 if (found < m_definitions.size())
366 {
367 // found it in its negated form
368 switch (m_definitions[found].kind())
369 {
370 case cli_switch_kind:
371 add_switch(found, false, "command line");
372 break;
373 case cli_value_kind:
374 erase_value(found);
375 break;
376 }
377 }
378 else
379 {
380 // could not find this option in either its true or negated form
381 result &= m_messages.error("CLI_UNRECOGNISED_OPTION", name);
382 }
383 }
384 else
385 {
386 // could not find this option and it could not be negated
387 result &= m_messages.error("CLI_UNRECOGNISED_OPTION", name);
388 }
389 }
390 else
391 {
392 // we have a command-line value which is represented internally as an option with an empty string as its name
393 // some very obscure commands do not have values - only options, so allow for that case too
394 unsigned found = find_definition("");
395 if (found < m_definitions.size())
396 add_value(found, name, "command line");
397 else
398 result &= m_messages.error("CLI_NO_VALUES", name);
399 }
400 }
401 if (!result) set_invalid();
402 return result;
403 }
404
405 void usage(void) const throw(std::runtime_error)
406 {
407 m_messages.information("CLI_USAGE_PROGRAM", m_executable);
408 m_messages.information("CLI_USAGE_DEFINITIONS");
409 for (unsigned d = 0; d < m_definitions.size(); d++)
410 m_messages.information(m_definitions[d].message());
411 if (m_values.size() != 0)
412 {
413 m_messages.information("CLI_USAGE_VALUES");
414 for (unsigned v = 0; v < m_values.size(); v++)
415 {
416 std::string source = m_values[v].m_source;
417 std::string key = name(v);
418 if (key.empty())
419 {
420 // command-line values
421 m_messages.information("CLI_USAGE_VALUE_RESULT", string_value(v), source);
422 }
423 else if (kind(v) == cli_switch_kind)
424 {
425 // a switch
426 m_messages.information("CLI_USAGE_SWITCH_RESULT", (switch_value(v) ? name(v) : "no" + name(v)), source);
427 }
428 else
429 {
430 // other values
431 std::vector<std::string> args;
432 args.push_back(name(v));
433 args.push_back(string_value(v));
434 args.push_back(source);
435 m_messages.information("CLI_USAGE_OPTION_RESULT", args);
436 }
437 }
438 }
439 if (ini_file_size() > 0)
440 {
441 m_messages.information("CLI_INI_HEADER");
442 for (unsigned i = 0; i < ini_file_size(); i++)
443 {
444 if (file_exists(ini_file(i)))
445 m_messages.information("CLI_INI_FILE_PRESENT", filespec_to_relative_path(ini_file(i)));
446 else
447 m_messages.information("CLI_INI_FILE_ABSENT", filespec_to_relative_path(ini_file(i)));
448 }
449 }
450 }
451
452 private:
453 // make this class uncopyable
454 cli_parser_data(const cli_parser_data&);
455 cli_parser_data& operator = (const cli_parser_data&);
456 };
457
458 ////////////////////////////////////////////////////////////////////////////////
459
460 cli_parser::cli_parser(message_handler& messages) throw() :
461 m_data(new cli_parser_data(messages))
462 {
463 }
464
465 cli_parser::cli_parser(cli_parser::definitions_t definitions, message_handler& messages) throw(cli_mode_error) :
466 m_data(new cli_parser_data(messages))
467 {
468 add_definitions(definitions);
469 }
470
471 cli_parser::cli_parser(cli_parser::definitions_t definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& messages) throw(cli_mode_error) :
472 m_data(new cli_parser_data(messages))
473 {
474 add_definitions(definitions);
475 set_defaults(defaults, ini_section);
476 }
477
478 cli_parser::cli_parser(char* argv[], cli_parser::definitions_t definitions, message_handler& messages) throw(cli_mode_error,message_handler_id_error,message_handler_format_error) :
479 m_data(new cli_parser_data(messages))
480 {
481 add_definitions(definitions);
482 parse(argv);
483 }
484
485 cli_parser::cli_parser(char* argv[], cli_parser::definitions_t definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& messages) throw(cli_mode_error,message_handler_id_error,message_handler_format_error) :
486 m_data(new cli_parser_data(messages))
487 {
488 add_definitions(definitions);
489 set_defaults(defaults, ini_section);
490 parse(argv);
491 }
492
493 cli_parser::cli_parser(cli_parser::definitions definitions, message_handler& messages) throw(cli_mode_error) :
494 m_data(new cli_parser_data(messages))
495 {
496 add_definitions(definitions);
497 }
498
499 cli_parser::cli_parser(cli_parser::definitions definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& messages) throw(cli_mode_error) :
500 m_data(new cli_parser_data(messages))
501 {
502 add_definitions(definitions);
503 set_defaults(defaults, ini_section);
504 }
505
506 cli_parser::cli_parser(char* argv[], cli_parser::definitions definitions, message_handler& messages) throw(cli_mode_error,message_handler_id_error,message_handler_format_error) :
507 m_data(new cli_parser_data(messages))
508 {
509 add_definitions(definitions);
510 parse(argv);
511 }
512
513 cli_parser::cli_parser(char* argv[], cli_parser::definitions definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& messages) throw(cli_mode_error,message_handler_id_error,message_handler_format_error) :
514 m_data(new cli_parser_data(messages))
515 {
516 add_definitions(definitions);
517 set_defaults(defaults, ini_section);
518 parse(argv);
519 }
520
521 cli_parser::~cli_parser(void) throw()
522 {
523 }
524
525 void cli_parser::add_definitions(cli_parser::definitions_t definitions) throw(cli_mode_error)
526 {
527 m_data->clear_definitions();
528 // the definitions array is terminated by a definition with a null name pointer
529 for (unsigned i = 0; definitions[i].m_name; i++)
530 add_definition(definitions[i]);
531 }
532
533 unsigned cli_parser::add_definition(const cli_parser::definition_t& definition) throw(cli_mode_error,cli_argument_error)
534 {
535 std::string name = definition.m_name ? definition.m_name : "";
536 std::string message = definition.m_message ? definition.m_message : "";
537 std::string value = definition.m_default ? definition.m_default : "";
538 return add_definition(cli_parser::definition(name, definition.m_kind, definition.m_mode, message, value));
539 }
540
541 void cli_parser::add_definitions(cli_parser::definitions definitions) throw(cli_mode_error)
542 {
543 m_data->clear_definitions();
544 for (unsigned i = 0; i < definitions.size(); i++)
545 add_definition(definitions[i]);
546 }
547
548 unsigned cli_parser::add_definition(const cli_parser::definition& definition) throw(cli_mode_error)
549 {
550 return m_data->add_checked_definition(definition);
551 }
552
553 void cli_parser::set_defaults(const ini_manager& defaults, const std::string& ini_section) throw()
554 {
555 m_data->set_defaults(defaults, ini_section);
556 }
557
558 bool cli_parser::parse(char* argv[]) throw(cli_argument_error,message_handler_id_error,message_handler_format_error)
559 {
560 return m_data->parse(argv);
561 }
562
563 bool cli_parser::valid(void) throw()
564 {
565 return m_data->valid();
566 }
567
568 unsigned cli_parser::size(void) const throw()
569 {
570 return m_data->m_values.size();
571 }
572
573 std::string cli_parser::name(unsigned i) const throw(cli_index_error)
574 {
575 return m_data->name(i);
576 }
577
578 unsigned cli_parser::id(unsigned i) const throw(cli_index_error)
579 {
580 return m_data->id(i);
581 }
582
583 cli_parser::kind_t cli_parser::kind(unsigned i) const throw(cli_index_error)
584 {
585 return m_data->kind(i);
586 }
587
588 bool cli_parser::switch_kind(unsigned i) const throw(cli_index_error)
589 {
590 return kind(i) == cli_switch_kind;
591 }
592
593 bool cli_parser::value_kind(unsigned i) const throw(cli_index_error)
594 {
595 return kind(i) == cli_value_kind;
596 }
597
598 cli_parser::mode_t cli_parser::mode(unsigned i) const throw(cli_index_error)
599 {
600 return m_data->mode(i);
601 }
602
603 bool cli_parser::single_mode(unsigned i) const throw(cli_index_error)
604 {
605 return mode(i) == cli_single_mode;
606 }
607
608 bool cli_parser::multiple_mode(unsigned i) const throw(cli_index_error)
609 {
610 return mode(i) == cli_multiple_mode;
611 }
612
613 bool cli_parser::cumulative_mode(unsigned i) const throw(cli_index_error)
614 {
615 return mode(i) == cli_cumulative_mode;
616 }
617
618 bool cli_parser::switch_value(unsigned i) const throw(cli_mode_error,cli_index_error)
619 {
620 return m_data->switch_value(i);
621 }
622
623 std::string cli_parser::string_value(unsigned i) const throw(cli_mode_error,cli_index_error)
624 {
625 return m_data->string_value(i);
626 }
627
628 ////////////////////////////////////////////////////////////////////////////////
629
630 void cli_parser::usage(void) const throw(std::runtime_error)
631 {
632 m_data->usage();
633 }
634
635 ////////////////////////////////////////////////////////////////////////////////
636
637 } // end namespace stlplus
This page took 0.067016 seconds and 4 git commands to generate.