1 ////////////////////////////////////////////////////////////////////////////////
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
8 ////////////////////////////////////////////////////////////////////////////////
9 #include "cli_parser.hpp"
10 #include "file_system.hpp"
11 #include "dprintf.hpp"
12 ////////////////////////////////////////////////////////////////////////////////
17 ////////////////////////////////////////////////////////////////////////////////
18 // cli_definition internals
20 const std::string
& stlplus::cli_definition::name(void) const
25 stlplus::cli_kind_t
stlplus::cli_definition::kind(void) const
30 stlplus::cli_mode_t
stlplus::cli_definition::mode(void) const
35 const std::string
& stlplus::cli_definition::message(void) const
40 const std::string
& stlplus::cli_definition::default_value(void) const
45 ////////////////////////////////////////////////////////////////////////////////
46 // internal data structures
51 unsigned m_definition
;
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
)
62 ////////////////////////////////////////////////////////////////////////////////
67 message_handler
& m_messages
;
68 std::string m_executable
;
69 cli_parser::definitions m_definitions
;
70 std::vector
<cli_value
> m_values
;
73 std::vector
<std::string
> m_ini_files
;
77 cli_parser_data(message_handler
& messages
) :
78 m_messages(messages
), m_level(1), m_valid(true)
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");
110 unsigned add_definition(const cli_parser::definition
& definition
)
112 m_definitions
.push_back(definition
);
113 return m_definitions
.size()-1;
116 std::string
name(unsigned i
) const throw(cli_index_error
)
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();
122 unsigned id(unsigned i
) const throw(cli_index_error
)
124 if (i
>= m_values
.size()) throw cli_index_error("Index " + dformat("%u",i
) + " out of range");
125 return m_values
[i
].m_definition
;
128 cli_parser::kind_t
kind(unsigned i
) const throw(cli_index_error
)
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();
134 cli_parser::mode_t
mode(unsigned i
) const throw(cli_index_error
)
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();
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)
145 // return add_definition(cli_parser::definition(name, kind, mode, message));
148 unsigned find_definition(const std::string
& name
)
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
++)
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
))
161 return m_definitions
.size();
164 void clear_definitions(void)
166 m_definitions
.clear();
172 void reset_level(void)
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
179 void increase_level(void)
184 void clear_level(unsigned definition
, unsigned level
)
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(); )
189 if (i
->m_definition
== definition
&& i
->m_level
<= level
)
190 i
= m_values
.erase(i
);
201 void set_invalid(void)
206 bool valid(void) const
211 unsigned add_value(unsigned definition
, const std::string
& value
, const std::string
& source
)
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())
219 case cli_single_mode
:
220 clear_level(definition
, m_level
);
222 case cli_multiple_mode
:
223 clear_level(definition
, m_level
-1);
225 case cli_cumulative_mode
:
228 m_values
.push_back(cli_value(definition
,value
,m_level
,source
));
229 return m_values
.size()-1;
232 unsigned add_switch(unsigned definition
, bool value
, const std::string
& source
)
234 return add_value(definition
, value
? "on" : "off", source
);
237 void erase_value(unsigned definition
)
239 // this simply erases all previous values
240 clear_level(definition
, m_level
);
243 void add_ini_file(const std::string
& file
)
245 m_ini_files
.push_back(file
);
248 unsigned ini_file_size(void) const
250 return m_ini_files
.size();
253 const std::string
& ini_file(unsigned i
) const
255 return m_ini_files
[i
];
258 unsigned add_checked_definition(const cli_parser::definition
& definition
) throw(cli_mode_error
)
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
)
265 throw cli_mode_error("CLI arguments cannot be switch kind");
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");
275 bool switch_value(unsigned i
) const throw(cli_mode_error
,cli_index_error
)
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";
283 std::string
string_value(unsigned i
) const throw(cli_mode_error
,cli_index_error
)
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
;
290 void set_defaults(const ini_manager
& defaults
, const std::string
& ini_section
) throw()
292 // import default values from the Ini Manager
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
++)
298 std::string name
= names
[i
];
299 unsigned definition
= find_definition(name
);
300 if (definition
== (unsigned)-1)
302 // not found - give an error report
303 message_position
position(defaults
.variable_filename(ini_section
,name
),
304 defaults
.variable_linenumber(ini_section
,name
),
306 m_messages
.error(position
,"CLI_INI_VARIABLE", name
);
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
316 erase_value(definition
);
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
);
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
));
330 bool parse(char* argv
[]) throw(cli_argument_error
,message_handler_id_error
,message_handler_format_error
)
333 if (!argv
) throw cli_argument_error("Argument vector cannot be null");
336 m_executable
= argv
[0];
337 for (unsigned i
= 1; argv
[i
]; i
++)
339 std::string name
= argv
[i
];
340 if (!name
.empty() && name
[0] == '-')
342 // we have a command line option
343 unsigned found
= find_definition(name
.substr(1, name
.size()-1));
344 if (found
< m_definitions
.size())
346 // found it in its positive form
347 switch (m_definitions
[found
].kind())
349 case cli_switch_kind
:
350 add_switch(found
, true, "command line");
353 // get the next argument in argv as the value of this option
354 // first check that there is a next value
356 result
&= m_messages
.error("CLI_VALUE_MISSING", name
);
358 add_value(found
, argv
[++i
], "command line");
362 else if (name
.size() > 3 && name
.substr(1,2) == "no")
364 found
= find_definition(name
.substr(3, name
.size()-3));
365 if (found
< m_definitions
.size())
367 // found it in its negated form
368 switch (m_definitions
[found
].kind())
370 case cli_switch_kind
:
371 add_switch(found
, false, "command line");
380 // could not find this option in either its true or negated form
381 result
&= m_messages
.error("CLI_UNRECOGNISED_OPTION", name
);
386 // could not find this option and it could not be negated
387 result
&= m_messages
.error("CLI_UNRECOGNISED_OPTION", name
);
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");
398 result
&= m_messages
.error("CLI_NO_VALUES", name
);
401 if (!result
) set_invalid();
405 void usage(void) const throw(std::runtime_error
)
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)
413 m_messages
.information("CLI_USAGE_VALUES");
414 for (unsigned v
= 0; v
< m_values
.size(); v
++)
416 std::string source
= m_values
[v
].m_source
;
417 std::string key
= name(v
);
420 // command-line values
421 m_messages
.information("CLI_USAGE_VALUE_RESULT", string_value(v
), source
);
423 else if (kind(v
) == cli_switch_kind
)
426 m_messages
.information("CLI_USAGE_SWITCH_RESULT", (switch_value(v
) ? name(v
) : "no" + name(v
)), source
);
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
);
439 if (ini_file_size() > 0)
441 m_messages
.information("CLI_INI_HEADER");
442 for (unsigned i
= 0; i
< ini_file_size(); i
++)
444 if (file_exists(ini_file(i
)))
445 m_messages
.information("CLI_INI_FILE_PRESENT", filespec_to_relative_path(ini_file(i
)));
447 m_messages
.information("CLI_INI_FILE_ABSENT", filespec_to_relative_path(ini_file(i
)));
453 // make this class uncopyable
454 cli_parser_data(const cli_parser_data
&);
455 cli_parser_data
& operator = (const cli_parser_data
&);
458 ////////////////////////////////////////////////////////////////////////////////
460 cli_parser::cli_parser(message_handler
& messages
) throw() :
461 m_data(new cli_parser_data(messages
))
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
))
468 add_definitions(definitions
);
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
))
474 add_definitions(definitions
);
475 set_defaults(defaults
, ini_section
);
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
))
481 add_definitions(definitions
);
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
))
488 add_definitions(definitions
);
489 set_defaults(defaults
, ini_section
);
493 cli_parser::cli_parser(cli_parser::definitions definitions
, message_handler
& messages
) throw(cli_mode_error
) :
494 m_data(new cli_parser_data(messages
))
496 add_definitions(definitions
);
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
))
502 add_definitions(definitions
);
503 set_defaults(defaults
, ini_section
);
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
))
509 add_definitions(definitions
);
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
))
516 add_definitions(definitions
);
517 set_defaults(defaults
, ini_section
);
521 cli_parser::~cli_parser(void) throw()
525 void cli_parser::add_definitions(cli_parser::definitions_t definitions
) throw(cli_mode_error
)
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
]);
533 unsigned cli_parser::add_definition(const cli_parser::definition_t
& definition
) throw(cli_mode_error
,cli_argument_error
)
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
));
541 void cli_parser::add_definitions(cli_parser::definitions definitions
) throw(cli_mode_error
)
543 m_data
->clear_definitions();
544 for (unsigned i
= 0; i
< definitions
.size(); i
++)
545 add_definition(definitions
[i
]);
548 unsigned cli_parser::add_definition(const cli_parser::definition
& definition
) throw(cli_mode_error
)
550 return m_data
->add_checked_definition(definition
);
553 void cli_parser::set_defaults(const ini_manager
& defaults
, const std::string
& ini_section
) throw()
555 m_data
->set_defaults(defaults
, ini_section
);
558 bool cli_parser::parse(char* argv
[]) throw(cli_argument_error
,message_handler_id_error
,message_handler_format_error
)
560 return m_data
->parse(argv
);
563 bool cli_parser::valid(void) throw()
565 return m_data
->valid();
568 unsigned cli_parser::size(void) const throw()
570 return m_data
->m_values
.size();
573 std::string
cli_parser::name(unsigned i
) const throw(cli_index_error
)
575 return m_data
->name(i
);
578 unsigned cli_parser::id(unsigned i
) const throw(cli_index_error
)
580 return m_data
->id(i
);
583 cli_parser::kind_t
cli_parser::kind(unsigned i
) const throw(cli_index_error
)
585 return m_data
->kind(i
);
588 bool cli_parser::switch_kind(unsigned i
) const throw(cli_index_error
)
590 return kind(i
) == cli_switch_kind
;
593 bool cli_parser::value_kind(unsigned i
) const throw(cli_index_error
)
595 return kind(i
) == cli_value_kind
;
598 cli_parser::mode_t
cli_parser::mode(unsigned i
) const throw(cli_index_error
)
600 return m_data
->mode(i
);
603 bool cli_parser::single_mode(unsigned i
) const throw(cli_index_error
)
605 return mode(i
) == cli_single_mode
;
608 bool cli_parser::multiple_mode(unsigned i
) const throw(cli_index_error
)
610 return mode(i
) == cli_multiple_mode
;
613 bool cli_parser::cumulative_mode(unsigned i
) const throw(cli_index_error
)
615 return mode(i
) == cli_cumulative_mode
;
618 bool cli_parser::switch_value(unsigned i
) const throw(cli_mode_error
,cli_index_error
)
620 return m_data
->switch_value(i
);
623 std::string
cli_parser::string_value(unsigned i
) const throw(cli_mode_error
,cli_index_error
)
625 return m_data
->string_value(i
);
628 ////////////////////////////////////////////////////////////////////////////////
630 void cli_parser::usage(void) const throw(std::runtime_error
)
635 ////////////////////////////////////////////////////////////////////////////////
637 } // end namespace stlplus