Line data Source code
1 : /*
2 : * License:
3 : * Copyright (c) 2006-2019 Made to Order Software Corp. All Rights Reserved
4 : *
5 : * https://snapwebsites.org/
6 : * contact@m2osw.com
7 : *
8 : * This program is free software; you can redistribute it and/or modify
9 : * it under the terms of the GNU General Public License as published by
10 : * the Free Software Foundation; either version 2 of the License, or
11 : * (at your option) any later version.
12 : *
13 : * This program is distributed in the hope that it will be useful,
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : * GNU General Public License for more details.
17 : *
18 : * You should have received a copy of the GNU General Public License along
19 : * with this program; if not, write to the Free Software Foundation, Inc.,
20 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 : *
22 : * Authors:
23 : * Alexis Wilke alexis@m2osw.com
24 : * Doug Barbieri doug@m2osw.com
25 : */
26 :
27 : /** \file
28 : * \brief Advanced getopt data access implementation.
29 : *
30 : * The advgetopt class has many function used to access the data in the
31 : * class. These functions are gathered here.
32 : *
33 : * This file is covered by the following tests:
34 : *
35 : * \li options_parser
36 : * \li invalid_options_parser
37 : * \li valid_options_files
38 : * \li invalid_options_files
39 : */
40 :
41 : // self
42 : //
43 : #include "advgetopt/advgetopt.h"
44 :
45 :
46 : // advgetopt lib
47 : //
48 : #include "advgetopt/conf_file.h"
49 : #include "advgetopt/exception.h"
50 :
51 :
52 : // last include
53 : //
54 : #include <snapdev/poison.h>
55 :
56 :
57 :
58 :
59 : namespace advgetopt
60 : {
61 :
62 :
63 :
64 :
65 :
66 :
67 :
68 : /** \brief Reset all the options.
69 : *
70 : * This function goes through the list of options and mark them all as
71 : * undefined. This is useful if you want to reuse a getopt object.
72 : *
73 : * The effect is that all calls to is_defined() made afterward return false
74 : * until new arguments get parsed.
75 : */
76 2 : void getopt::reset()
77 : {
78 18 : for(auto & opt : f_options_by_name)
79 : {
80 16 : opt.second->reset();
81 : }
82 2 : }
83 :
84 :
85 : /** \brief Parse the options to option_info objects.
86 : *
87 : * This function transforms an array of options in a vector of option_info
88 : * objects.
89 : *
90 : * \param[in] opts An array of options to be parsed.
91 : * \param[in] ignore_duplicates Whether to ignore potential duplicates.
92 : */
93 425 : void getopt::parse_options_info(option const * opts, bool ignore_duplicates)
94 : {
95 425 : if(opts == nullptr)
96 : {
97 64 : return;
98 : }
99 :
100 1012 : for(
101 1373 : ; (opts->f_flags & GETOPT_FLAG_END) == 0
102 : ; ++opts)
103 : {
104 1020 : if(opts->f_name == nullptr
105 1019 : || opts->f_name[0] == '\0')
106 : {
107 2 : throw getopt_logic_error("option long name missing or empty.");
108 : }
109 1018 : short_name_t const one_char(string_to_short_name(opts->f_name));
110 1018 : if(one_char != NO_SHORT_NAME)
111 : {
112 1 : throw getopt_logic_error("a long name option must be at least 2 characters.");
113 : }
114 :
115 1017 : if(get_option(opts->f_name, true) != nullptr)
116 : {
117 2 : if(ignore_duplicates)
118 : {
119 1 : continue;
120 : }
121 : throw getopt_logic_error(
122 2 : std::string("option named \"")
123 3 : + opts->f_name
124 3 : + "\" found twice.");
125 : }
126 1015 : short_name_t short_name(opts->f_short_name);
127 1015 : if(get_option(short_name, true) != nullptr)
128 : {
129 2 : if(ignore_duplicates)
130 : {
131 1 : short_name = U'\0';
132 : }
133 : else
134 : {
135 : throw getopt_logic_error(
136 : "option with short name \""
137 2 : + short_name_to_string(opts->f_short_name)
138 3 : + "\" found twice.");
139 : }
140 : }
141 :
142 : option_info::pointer_t o(std::make_shared<option_info>(
143 : opts->f_name
144 2027 : , short_name));
145 :
146 1013 : o->add_flag(opts->f_flags);
147 1013 : o->set_default(opts->f_default);
148 1013 : o->set_help(opts->f_help);
149 1013 : o->set_multiple_separators(opts->f_multiple_separators);
150 :
151 1013 : if(opts->f_validator != nullptr)
152 : {
153 6 : o->set_validator(opts->f_validator);
154 : }
155 :
156 1013 : if(o->is_default_option())
157 : {
158 42 : if(f_default_option != nullptr)
159 : {
160 1 : throw getopt_logic_error("two default options found after check of long names duplication.");
161 : }
162 41 : if(o->has_flag(GETOPT_FLAG_FLAG))
163 : {
164 1 : throw getopt_logic_error("a default option must accept parameters, it can't be a GETOPT_FLAG_FLAG.");
165 : }
166 :
167 40 : f_default_option = o;
168 : }
169 :
170 1011 : f_options_by_name[o->get_name()] = o;
171 1011 : if(short_name != NO_SHORT_NAME)
172 : {
173 568 : f_options_by_short_name[short_name] = o;
174 : }
175 : }
176 : }
177 :
178 :
179 : /** \brief Check for a file with option definitions.
180 : *
181 : * This function tries to read a file of options for this application.
182 : * These are similar to the option structure, only it is defined in a
183 : * file.
184 : *
185 : * The format of the file is like so:
186 : *
187 : * \li Option names are defined on a line by themselves between square brackets.
188 : * \li Parameters of that option are defined below as a `name=<value>`.
189 : *
190 : * Example:
191 : *
192 : * \code
193 : * [<command-name>]
194 : * short_name=<character>
195 : * default=<default value>
196 : * help=<help sentence>
197 : * validator=<validator name>[(<param>)]|/<regex>/<flags>
198 : * alias=<name of aliased option>
199 : * allowed=command-line,environment-variable,configuration-file
200 : * show-usage-on-error
201 : * no-arguments|multiple
202 : * required
203 : * \endcode
204 : */
205 325 : void getopt::parse_options_from_file()
206 : {
207 644 : std::string filename;
208 :
209 325 : if(f_options_environment.f_project_name == nullptr
210 321 : || f_options_environment.f_project_name[0] == '\0')
211 : {
212 6 : return;
213 : }
214 :
215 319 : if(f_options_environment.f_options_files_directory == nullptr
216 73 : || f_options_environment.f_options_files_directory[0] == '\0')
217 : {
218 248 : filename = "/usr/share/advgetopt/options/";
219 : }
220 : else
221 : {
222 71 : filename = f_options_environment.f_options_files_directory;
223 71 : if(filename.back() != '/')
224 : {
225 71 : filename += '/';
226 : }
227 : }
228 319 : filename += f_options_environment.f_project_name;
229 319 : filename += ".ini";
230 :
231 : conf_file_setup conf_setup(filename
232 : , line_continuation_t::line_continuation_unix
233 : , ASSIGNMENT_OPERATOR_EQUAL
234 : , COMMENT_INI | COMMENT_SHELL
235 638 : , SECTION_OPERATOR_INI_FILE | SECTION_OPERATOR_ONE_SECTION);
236 319 : if(!conf_setup.is_valid())
237 : {
238 : return; // LCOV_EXCL_LINE
239 : }
240 :
241 638 : conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
242 638 : conf_file::sections_t const & sections(conf->get_sections());
243 332 : for(auto & section_name : sections)
244 : {
245 16 : std::string::size_type pos(section_name.find("::"));
246 16 : if(pos != std::string::npos)
247 : {
248 : // this should never happen since we use the
249 : // SECTION_OPERATOR_ONE_SECTION flag
250 : //
251 : throw getopt_logic_error( // LCOV_EXCL_LINE
252 : "section \"" // LCOV_EXCL_LINE
253 : + section_name // LCOV_EXCL_LINE
254 : + "\" includes a section separator (::) in \"" // LCOV_EXCL_LINE
255 : + filename // LCOV_EXCL_LINE
256 : + "\". We only support one level."); // LCOV_EXCL_LINE
257 : }
258 :
259 32 : std::string const parameter_name(section_name);
260 32 : std::string const short_name(unquote(conf->get_parameter(parameter_name + "::shortname")));
261 16 : if(short_name.length() > 1)
262 : {
263 : throw getopt_logic_error(
264 : "option \""
265 2 : + section_name
266 2 : + "\" has an invalid short name in \""
267 2 : + filename
268 3 : + "\", it can't be more than one character.");
269 : }
270 15 : short_name_t sn('\0');
271 15 : if(short_name.length() == 1)
272 : {
273 13 : sn = short_name[0];
274 : }
275 :
276 30 : option_info::pointer_t opt(std::make_shared<option_info>(parameter_name, sn));
277 :
278 30 : std::string const default_name(parameter_name + "::default");
279 15 : if(conf->has_parameter(default_name))
280 : {
281 9 : opt->set_default(unquote(conf->get_parameter(default_name)));
282 : }
283 :
284 15 : opt->set_help(unquote(conf->get_parameter(parameter_name + "::help")));
285 :
286 30 : std::string const validator_name_and_params(conf->get_parameter(parameter_name + "::validator"));
287 15 : opt->set_validator(validator_name_and_params);
288 :
289 28 : std::string const alias_name(parameter_name + "::alias");
290 14 : if(conf->has_parameter(alias_name))
291 : {
292 6 : if(!opt->get_help().empty())
293 : {
294 : throw getopt_logic_error(
295 : "option \""
296 2 : + section_name
297 2 : + "\" is an alias and as such it can't include a help=... parameter in \""
298 2 : + filename
299 3 : + "\".");
300 : }
301 5 : opt->set_help(unquote(conf->get_parameter(alias_name)));
302 5 : opt->add_flag(GETOPT_FLAG_ALIAS);
303 : }
304 :
305 26 : std::string const allowed_name(parameter_name + "::allowed");
306 13 : if(conf->has_parameter(allowed_name))
307 : {
308 26 : std::string const allowed_list(conf->get_parameter(allowed_name));
309 26 : string_list_t allowed;
310 13 : split_string(allowed_list, allowed, {","});
311 32 : for(auto const & a : allowed)
312 : {
313 19 : if(a == "command-line")
314 : {
315 10 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE);
316 : }
317 9 : else if(a == "environment-variable")
318 : {
319 6 : opt->add_flag(GETOPT_FLAG_ENVIRONMENT_VARIABLE);
320 : }
321 3 : else if(a == "configuration-file")
322 : {
323 3 : opt->add_flag(GETOPT_FLAG_CONFIGURATION_FILE);
324 : }
325 : }
326 : }
327 :
328 13 : if(conf->has_parameter(parameter_name + "::show-usage-on-error"))
329 : {
330 1 : opt->add_flag(GETOPT_FLAG_SHOW_USAGE_ON_ERROR);
331 : }
332 :
333 13 : if(conf->has_parameter(parameter_name + "::no-arguments"))
334 : {
335 4 : opt->add_flag(GETOPT_FLAG_FLAG);
336 : }
337 :
338 13 : if(conf->has_parameter(parameter_name + "::multiple"))
339 : {
340 1 : opt->add_flag(GETOPT_FLAG_MULTIPLE);
341 : }
342 :
343 13 : if(conf->has_parameter(parameter_name + "::required"))
344 : {
345 5 : opt->add_flag(GETOPT_FLAG_REQUIRED);
346 : }
347 :
348 13 : f_options_by_name[parameter_name] = opt;
349 13 : if(sn != NO_SHORT_NAME)
350 : {
351 11 : f_options_by_short_name[sn] = opt;
352 : }
353 : }
354 : }
355 :
356 :
357 : /** \brief Link options marked as a GETOPT_FLAG_ALIAS.
358 : *
359 : * After we defined all the options, go through the list again to find
360 : * aliases and link them with their corresponding alias option.
361 : *
362 : * \exception getopt_exception_invalid
363 : * All aliases must exist or this exception is raised.
364 : */
365 245 : void getopt::link_aliases()
366 : {
367 975 : for(auto & c : f_options_by_name)
368 : {
369 734 : if(c.second->has_flag(GETOPT_FLAG_ALIAS))
370 : {
371 22 : std::string const & alias_name(c.second->get_help());
372 22 : if(alias_name.empty())
373 : {
374 : throw getopt_logic_error(
375 : "the default value of your alias cannot be an empty string for \""
376 4 : + c.first
377 6 : + "\".");
378 : }
379 :
380 : // we have to use the `true` flag in this get_option() because
381 : // aliases may not yet be defined
382 : //
383 40 : option_info::pointer_t alias(get_option(alias_name, true));
384 20 : if(alias == nullptr)
385 : {
386 : throw getopt_logic_error(
387 : "no option named \""
388 2 : + alias_name
389 2 : + "\" to satisfy the alias of \""
390 3 : + c.first
391 3 : + "\".");
392 : }
393 :
394 19 : flag_t const expected_flags(c.second->get_flags() & ~GETOPT_FLAG_ALIAS);
395 19 : if(alias->get_flags() != expected_flags)
396 : {
397 2 : std::stringstream ss;
398 1 : ss << std::hex
399 : << "the flags of alias \""
400 : << c.first
401 1 : << "\" (0x"
402 1 : << expected_flags
403 : << ") are different than the flags of \""
404 : << alias_name
405 1 : << "\" (0x"
406 1 : << alias->get_flags()
407 1 : << ").";
408 1 : throw getopt_logic_error(ss.str());
409 : }
410 :
411 18 : c.second->set_alias_destination(alias);
412 : }
413 : }
414 241 : }
415 :
416 :
417 : /** \brief Assign a short name to an option.
418 : *
419 : * This function allows for dynamically assigning a short name to an option.
420 : * This is useful for cases where a certain number of options may be added
421 : * dynamically and may share the same short name or similar situation.
422 : *
423 : * On our end we like to add `-c` as the short name of the `--config-dir`
424 : * command line or environment variable option. However, some of our tools
425 : * use `-c` for other reason (i.e. our `cxpath` tool uses `-c` for its
426 : * `--compile` option.) So we do not want to have it has a default in
427 : * that option. Instead we assign it afterward.
428 : *
429 : * **IMPORTANT:** To make this call useful, make sure to make it before
430 : * you call the parse functions. Setting the short name after the parsing
431 : * was done is going to be useless.
432 : *
433 : * \note
434 : * This function requires you to make use of the constructor without the
435 : * `argc` and `argv` parameters, add the short name, then run all the
436 : * parsing.
437 : *
438 : * \note
439 : * The function calls the option_info::set_short_name() function which
440 : * may raise an exception if the option already has a short name (or if
441 : * you inadvertendly passed NO_SHORT_NAME.)
442 : *
443 : * \exception getopt_exception_logic
444 : * The same short name cannot be used more than once. This exception is
445 : * raised if it is discovered that another option already makes use of
446 : * this short name. This exception is also raised if the \p name
447 : * parameter does not reference an existing option.
448 : *
449 : * \param[in] name The name of the option which is to receive a short name.
450 : * \param[in] short_name The short name to assigned to the \p name option.
451 : */
452 7 : void getopt::set_short_name(std::string const & name, short_name_t short_name)
453 : {
454 7 : auto it(f_options_by_short_name.find(short_name));
455 7 : if(it != f_options_by_short_name.end())
456 : {
457 : throw getopt_logic_error(
458 : "found another option (\""
459 2 : + it->second->get_name()
460 2 : + "\") with short name '"
461 4 : + short_name_to_string(short_name)
462 3 : + "'.");
463 : }
464 :
465 6 : auto opt(f_options_by_name.find(name));
466 6 : if(opt == f_options_by_name.end())
467 : {
468 : throw getopt_logic_error(
469 : "option with name \""
470 2 : + name
471 3 : + "\" not found.");
472 : }
473 :
474 5 : opt->second->set_short_name(short_name);
475 :
476 3 : f_options_by_short_name[short_name] = opt->second;
477 3 : }
478 :
479 :
480 :
481 :
482 : } // namespace advgetopt
483 : // vim: ts=4 sw=4 et
|