Line data Source code
1 : // Copyright (c) 2006-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/advgetopt
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Advanced getopt data access implementation.
22 : *
23 : * The advgetopt class has many function used to access the data in the
24 : * class. These functions are gathered here.
25 : *
26 : * This file is covered by the following tests:
27 : *
28 : * \li options_parser
29 : * \li invalid_options_parser
30 : * \li valid_options_files
31 : * \li invalid_options_files
32 : */
33 :
34 : // self
35 : //
36 : #include "advgetopt/advgetopt.h"
37 :
38 : #include "advgetopt/conf_file.h"
39 : #include "advgetopt/exception.h"
40 :
41 :
42 : // cppthread
43 : //
44 : #include <cppthread/log.h>
45 :
46 :
47 : // snapdev
48 : //
49 : #include <snapdev/tokenize_string.h>
50 :
51 :
52 : // C++
53 : //
54 : #include <list>
55 :
56 :
57 : // last include
58 : //
59 : #include <snapdev/poison.h>
60 :
61 :
62 :
63 :
64 : namespace advgetopt
65 : {
66 :
67 :
68 :
69 :
70 :
71 :
72 :
73 : /** \brief Reset all the options.
74 : *
75 : * This function goes through the list of options and mark them all as
76 : * undefined. This is useful if you want to reuse a getopt object.
77 : *
78 : * The effect is that all calls to is_defined() made afterward return false
79 : * until new arguments get parsed.
80 : */
81 2 : void getopt::reset()
82 : {
83 18 : for(auto & opt : f_options_by_name)
84 : {
85 16 : opt.second->reset();
86 : }
87 2 : }
88 :
89 :
90 : /** \brief Parse the options to option_info objects.
91 : *
92 : * This function transforms an array of options in a vector of option_info
93 : * objects.
94 : *
95 : * \param[in] opts An array of options to be parsed.
96 : * \param[in] ignore_duplicates Whether to ignore potential duplicates.
97 : */
98 482 : void getopt::parse_options_info(option const * opts, bool ignore_duplicates)
99 : {
100 482 : if(opts == nullptr)
101 : {
102 66 : return;
103 : }
104 :
105 1643 : for(
106 2059 : ; (opts->f_flags & GETOPT_FLAG_END) == 0
107 : ; ++opts)
108 : {
109 1651 : if(opts->f_name == nullptr
110 1650 : || opts->f_name[0] == '\0')
111 : {
112 2 : throw getopt_logic_error("option long name missing or empty.");
113 : }
114 1649 : short_name_t const one_char(string_to_short_name(opts->f_name));
115 1649 : if(one_char != NO_SHORT_NAME)
116 : {
117 1 : throw getopt_logic_error("a long name option must be at least 2 characters.");
118 : }
119 :
120 1648 : short_name_t short_name(opts->f_short_name);
121 :
122 1648 : option_info::pointer_t o(std::make_shared<option_info>(
123 : opts->f_name
124 3295 : , short_name));
125 1647 : o->set_variables(f_variables);
126 :
127 1647 : o->set_environment_variable_name(opts->f_environment_variable_name);
128 1647 : o->add_flag(opts->f_flags);
129 1647 : o->set_default(opts->f_default);
130 1647 : o->set_help(opts->f_help);
131 1647 : o->set_multiple_separators(opts->f_multiple_separators);
132 :
133 1647 : if(opts->f_validator != nullptr)
134 : {
135 6 : o->set_validator(opts->f_validator);
136 : }
137 :
138 1651 : add_option(o, ignore_duplicates);
139 : }
140 : }
141 :
142 :
143 : /** \brief Add one option to the advgetopt object.
144 : *
145 : * This function is used to dynamically add one option to the advgetopt
146 : * object.
147 : *
148 : * This is often used in a library which wants to dynamically add support
149 : * for library specific parameters to the command line.
150 : *
151 : * \note
152 : * The \p ignore_duplicates option still gets the option added if only
153 : * the short-name is a duplicate. In that case, we set the option's
154 : * short-name to NO_SHORT_NAME before adding the option to the tables.
155 : *
156 : * \param[in] opt The option to be added.
157 : * \param[in] ignore_duplicate If option is a duplicate, do not add it.
158 : */
159 1680 : void getopt::add_option(option_info::pointer_t opt, bool ignore_duplicates)
160 : {
161 1680 : if(get_option(opt->get_name(), true) != nullptr)
162 : {
163 2 : if(ignore_duplicates)
164 : {
165 1 : return;
166 : }
167 : throw getopt_defined_twice(
168 2 : std::string("option named \"")
169 3 : + opt->get_name()
170 3 : + "\" found twice.");
171 : }
172 :
173 1678 : short_name_t short_name(opt->get_short_name());
174 1678 : if(get_option(short_name, true) != nullptr)
175 : {
176 3 : if(ignore_duplicates)
177 : {
178 2 : short_name = NO_SHORT_NAME;
179 2 : opt->set_short_name(NO_SHORT_NAME);
180 : }
181 : else
182 : {
183 : throw getopt_defined_twice(
184 : "option with short name \""
185 2 : + short_name_to_string(short_name)
186 3 : + "\" found twice.");
187 : }
188 : }
189 :
190 1677 : if(opt->is_default_option())
191 : {
192 48 : if(f_default_option != nullptr)
193 : {
194 1 : throw getopt_logic_error("two default options found.");
195 : }
196 47 : if(opt->has_flag(GETOPT_FLAG_FLAG))
197 : {
198 1 : throw getopt_logic_error("a default option must accept parameters, it can't be a GETOPT_FLAG_FLAG.");
199 : }
200 :
201 46 : f_default_option = opt;
202 : }
203 :
204 1675 : f_options_by_name[opt->get_name()] = opt;
205 :
206 1675 : if(short_name != NO_SHORT_NAME)
207 : {
208 729 : f_options_by_short_name[short_name] = opt;
209 : }
210 : }
211 :
212 :
213 : /** \brief Check for a file with option definitions.
214 : *
215 : * This function tries to read the default option file for this process.
216 : * This filename is generated using the the option environment files
217 : * directory and the project name.
218 : *
219 : * If the directory is not defined, the function uses this default path:
220 : * `"/usr/share/advgetopt/options/"`. See the other
221 : * parse_options_from_file(std::string const & filename, int min_sections, int max_sections)
222 : * function for additional details.
223 : *
224 : * \sa parse_options_from_file(std::string const & filename, int min_sections, int max_sections)
225 : */
226 357 : void getopt::parse_options_from_file()
227 : {
228 706 : std::string filename;
229 :
230 357 : if(f_options_environment.f_project_name == nullptr
231 353 : || f_options_environment.f_project_name[0] == '\0')
232 : {
233 6 : return;
234 : }
235 :
236 351 : if(f_options_environment.f_options_files_directory == nullptr
237 75 : || f_options_environment.f_options_files_directory[0] == '\0')
238 : {
239 278 : filename = "/usr/share/advgetopt/options/";
240 : }
241 : else
242 : {
243 73 : filename = f_options_environment.f_options_files_directory;
244 73 : if(filename.back() != '/')
245 : {
246 73 : filename += '/';
247 : }
248 : }
249 351 : filename += f_options_environment.f_project_name;
250 351 : filename += ".ini";
251 :
252 351 : parse_options_from_file(filename, 1, 1);
253 : }
254 :
255 :
256 : /** \brief Check for a file with option definitions.
257 : *
258 : * This function tries to read the specified file for command line options
259 : * for this application. These are similar to the option structure, only it
260 : * is defined in a file.
261 : *
262 : * The format of the file is like so:
263 : *
264 : * \li Option names are defined on a line by themselves between square brackets.
265 : * \li Parameters of that option are defined below as a `name=<value>`.
266 : *
267 : * Example:
268 : *
269 : * \code
270 : * [<command-name>]
271 : * short_name=<character>
272 : * default=<default value>
273 : * help=<help sentence>
274 : * validator=<validator name>[(<param>[,<param>...])]|/<regex>/<flags>
275 : * alias=<name of aliased option>
276 : * allowed=command-line,environment-variable,configuration-file
277 : * show-usage-on-error
278 : * no-arguments|multiple
279 : * required
280 : * \endcode
281 : *
282 : * The number of namespaces in `<command-name>` can be limited using the
283 : * \p min_sections and \p max_sections parameters.
284 : *
285 : * The function can be called multiple times. The first time, it verifies
286 : * that there are not duplicated settings. On following loads, that test
287 : * is ignored.
288 : *
289 : * \todo
290 : * Test that options get 100% updated on a reload.
291 : *
292 : * \note
293 : * By default, this function is called with one specific filename based
294 : * on the f_project_name field and the f_options_files_directory as
295 : * defined in the options environment.
296 : *
297 : * \param[in] filename The filename to load.
298 : * \param[in] min_sections The minimum number of namespaces.
299 : * \param[in] max_sections The maximum number of namespaces.
300 : * \param[in] ignore_duplicates Whether duplicates are okay or not.
301 : *
302 : * \sa parse_options_from_file()
303 : */
304 353 : void getopt::parse_options_from_file(
305 : std::string const & filename
306 : , int min_sections
307 : , int max_sections
308 : , bool ignore_duplicates)
309 : {
310 353 : section_operator_t operators(SECTION_OPERATOR_INI_FILE);
311 353 : if(min_sections == 1
312 351 : && max_sections == 1)
313 : {
314 351 : operators |= SECTION_OPERATOR_ONE_SECTION;
315 : }
316 :
317 353 : conf_file_setup conf_setup(filename
318 : , line_continuation_t::line_continuation_unix
319 : , ASSIGNMENT_OPERATOR_EQUAL
320 : , COMMENT_INI | COMMENT_SHELL
321 706 : , operators);
322 353 : if(!conf_setup.is_valid())
323 : {
324 : return; // LCOV_EXCL_LINE
325 : }
326 :
327 706 : conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
328 706 : conf_file::sections_t const & sections(conf->get_sections());
329 388 : for(auto & section_names : sections)
330 : {
331 72 : std::list<std::string> names;
332 37 : snapdev::tokenize_string(
333 : names
334 : , section_names
335 : , "::");
336 76 : if(names.size() < static_cast<std::size_t>(min_sections)
337 37 : || names.size() > static_cast<std::size_t>(max_sections))
338 : {
339 2 : if(min_sections == 1
340 : && max_sections == 1) // LCOV_EXCL_LINE
341 : {
342 : // right now this case cannot happen because we set the
343 : // SECTION_OPERATOR_ONE_SECTION flag so errors are caught
344 : // directly inside the conf_file::get_conf_file() call
345 : //
346 : cppthread::log << cppthread::log_level_t::error // LCOV_EXCL_LINE
347 : << "the name of a settings definition must include one namespace; \"" // LCOV_EXCL_LINE
348 : << section_names // LCOV_EXCL_LINE
349 : << "\" is not considered valid." // LCOV_EXCL_LINE
350 : << cppthread::end; // LCOV_EXCL_LINE
351 : }
352 : else
353 : {
354 4 : cppthread::log << cppthread::log_level_t::error
355 2 : << "the name of a settings definition must include between "
356 2 : << min_sections
357 2 : << " and "
358 2 : << max_sections
359 2 : << " namespaces; \""
360 2 : << section_names
361 2 : << "\" is not considered valid."
362 4 : << cppthread::end;
363 : }
364 2 : continue;
365 : }
366 :
367 70 : std::string const parameter_name(section_names);
368 70 : std::string const short_name(unquote(conf->get_parameter(parameter_name + "::shortname")));
369 35 : if(short_name.length() > 1)
370 : {
371 : throw getopt_logic_error(
372 : "option \""
373 2 : + section_names
374 3 : + "\" has an invalid short name in \""
375 3 : + filename
376 3 : + "\", it can't be more than one character.");
377 : }
378 34 : short_name_t const sn(short_name.length() == 1
379 34 : ? short_name[0]
380 34 : : NO_SHORT_NAME);
381 :
382 68 : option_info::pointer_t opt(std::make_shared<option_info>(parameter_name, sn));
383 34 : opt->set_variables(f_variables);
384 :
385 68 : std::string const environment_variable_name(parameter_name + "::environment_variable_name");
386 34 : if(conf->has_parameter(environment_variable_name))
387 : {
388 8 : opt->set_environment_variable_name(unquote(conf->get_parameter(environment_variable_name)));
389 : }
390 :
391 68 : std::string const default_name(parameter_name + "::default");
392 34 : if(conf->has_parameter(default_name))
393 : {
394 17 : opt->set_default(unquote(conf->get_parameter(default_name)));
395 : }
396 :
397 34 : opt->set_help(unquote(conf->get_parameter(parameter_name + "::help")));
398 :
399 68 : std::string const validator_name_and_params(conf->get_parameter(parameter_name + "::validator"));
400 34 : opt->set_validator(validator_name_and_params);
401 :
402 68 : std::string const alias_name(parameter_name + "::alias");
403 34 : if(conf->has_parameter(alias_name))
404 : {
405 8 : if(!opt->get_help().empty())
406 : {
407 : throw getopt_logic_error(
408 : "option \""
409 2 : + section_names
410 3 : + "\" is an alias and as such it can't include a help=... parameter in \""
411 3 : + filename
412 3 : + "\".");
413 : }
414 7 : opt->set_help(unquote(conf->get_parameter(alias_name)));
415 7 : opt->add_flag(GETOPT_FLAG_ALIAS);
416 : }
417 :
418 66 : std::string const allowed_name(parameter_name + "::allowed");
419 33 : if(conf->has_parameter(allowed_name))
420 : {
421 66 : std::string const allowed_list(conf->get_parameter(allowed_name));
422 66 : string_list_t allowed;
423 33 : split_string(allowed_list, allowed, {","});
424 92 : for(auto const & a : allowed)
425 : {
426 59 : if(a == "command-line")
427 : {
428 24 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE);
429 : }
430 35 : else if(a == "environment-variable")
431 : {
432 22 : opt->add_flag(GETOPT_FLAG_ENVIRONMENT_VARIABLE);
433 : }
434 13 : else if(a == "configuration-file")
435 : {
436 13 : opt->add_flag(GETOPT_FLAG_CONFIGURATION_FILE);
437 : }
438 : }
439 : }
440 :
441 33 : if(conf->has_parameter(parameter_name + "::show-usage-on-error"))
442 : {
443 4 : opt->add_flag(GETOPT_FLAG_SHOW_USAGE_ON_ERROR);
444 : }
445 :
446 33 : if(conf->has_parameter(parameter_name + "::no-arguments"))
447 : {
448 8 : opt->add_flag(GETOPT_FLAG_FLAG);
449 : }
450 :
451 33 : if(conf->has_parameter(parameter_name + "::multiple"))
452 : {
453 6 : opt->add_flag(GETOPT_FLAG_MULTIPLE);
454 : }
455 :
456 33 : if(conf->has_parameter(parameter_name + "::required"))
457 : {
458 17 : opt->add_flag(GETOPT_FLAG_REQUIRED);
459 : }
460 :
461 33 : add_option(opt, ignore_duplicates);
462 : }
463 : }
464 :
465 :
466 : /** \brief Link options marked as a GETOPT_FLAG_ALIAS.
467 : *
468 : * After we defined all the options, go through the list again to find
469 : * aliases and link them with their corresponding alias option.
470 : *
471 : * \exception getopt_exception_invalid
472 : * All aliases must exist or this exception is raised.
473 : */
474 275 : void getopt::link_aliases()
475 : {
476 1485 : for(auto & c : f_options_by_name)
477 : {
478 1214 : if(c.second->has_flag(GETOPT_FLAG_ALIAS))
479 : {
480 23 : std::string const & alias_name(c.second->get_help());
481 23 : if(alias_name.empty())
482 : {
483 : throw getopt_logic_error(
484 : "the default value of your alias cannot be an empty string for \""
485 4 : + c.first
486 6 : + "\".");
487 : }
488 :
489 : // we have to use the `true` flag in this get_option() because
490 : // aliases may not yet be defined
491 : //
492 42 : option_info::pointer_t alias(get_option(alias_name, true));
493 21 : if(alias == nullptr)
494 : {
495 : throw getopt_logic_error(
496 : "no option named \""
497 2 : + alias_name
498 3 : + "\" to satisfy the alias of \""
499 3 : + c.first
500 3 : + "\".");
501 : }
502 :
503 20 : flag_t const expected_flags(c.second->get_flags() & ~GETOPT_FLAG_ALIAS);
504 20 : if(alias->get_flags() != expected_flags)
505 : {
506 2 : std::stringstream ss;
507 1 : ss << std::hex
508 : << "the flags of alias \""
509 : << c.first
510 1 : << "\" (0x"
511 1 : << expected_flags
512 : << ") are different than the flags of \""
513 : << alias_name
514 1 : << "\" (0x"
515 1 : << alias->get_flags()
516 1 : << ").";
517 1 : throw getopt_logic_error(ss.str());
518 : }
519 :
520 19 : c.second->set_alias_destination(alias);
521 : }
522 : }
523 271 : }
524 :
525 :
526 : /** \brief Assign a short name to an option.
527 : *
528 : * This function allows for dynamically assigning a short name to an option.
529 : * This is useful for cases where a certain number of options may be added
530 : * dynamically and may share the same short name or similar situation.
531 : *
532 : * On our end we like to add `-c` as the short name of the `--config-dir`
533 : * command line or environment variable option. However, some of our tools
534 : * use `-c` for other reason (i.e. our `cxpath` tool uses `-c` for its
535 : * `--compile` option.) So we do not want to have it as a default in
536 : * `--config-dir`. Instead we assign it afterward if possible.
537 : *
538 : * **IMPORTANT:** It is possible to change the short-name at any time.
539 : * However, note that you can't have duplicates. It is also possible
540 : * to remove a short-name by setting it to the advgetopt::NO_SHORT_NAME
541 : * special value.
542 : *
543 : * \note
544 : * This function requires you to make use of the constructor without the
545 : * `argc` and `argv` parameters, add the short name, then run all the
546 : * parsing.
547 : *
548 : * \exception getopt_exception_logic
549 : * The same short name cannot be used more than once. This exception is
550 : * raised if it is discovered that another option already makes use of
551 : * this short name. This exception is also raised if \p name does not
552 : * reference an existing option.
553 : *
554 : * \param[in] name The name of the option which is to receive a short name.
555 : * \param[in] short_name The short name to assigned to the \p name option.
556 : */
557 9 : void getopt::set_short_name(std::string const & name, short_name_t short_name)
558 : {
559 9 : auto opt(f_options_by_name.find(name));
560 9 : if(opt == f_options_by_name.end())
561 : {
562 : throw getopt_logic_error(
563 : "option with name \""
564 2 : + name
565 3 : + "\" not found.");
566 : }
567 :
568 8 : if(short_name != NO_SHORT_NAME)
569 : {
570 6 : auto it(f_options_by_short_name.find(short_name));
571 6 : if(it != f_options_by_short_name.end())
572 : {
573 2 : if(it->second == opt->second)
574 : {
575 : // same option, already named 'short_name'
576 : //
577 1 : return;
578 : }
579 :
580 : throw getopt_logic_error(
581 : "found another option (\""
582 2 : + it->second->get_name()
583 3 : + "\") with short name '"
584 4 : + short_name_to_string(short_name)
585 3 : + "'.");
586 : }
587 : }
588 :
589 6 : short_name_t const old_short_name(opt->second->get_short_name());
590 6 : if(old_short_name != NO_SHORT_NAME)
591 : {
592 2 : auto it(f_options_by_short_name.find(old_short_name));
593 2 : if(it != f_options_by_short_name.end())
594 : {
595 2 : f_options_by_short_name.erase(it);
596 : }
597 : }
598 :
599 6 : opt->second->set_short_name(short_name);
600 :
601 6 : if(short_name != NO_SHORT_NAME)
602 : {
603 4 : f_options_by_short_name[short_name] = opt->second;
604 : }
605 : }
606 :
607 :
608 : /** \brief Output the source of each option.
609 : *
610 : * This function goes through the list of options by name ("alphabetically")
611 : * and prints out the sources or "(undefined)" if not defined anywhere.
612 : *
613 : * This function gets called when using the `--show-option-sources`
614 : * system command line option at the time the process_system_options()
615 : * function gets called.
616 : *
617 : * \param[in] out The output streaming where the info is written.
618 : */
619 3 : void getopt::show_option_sources(std::basic_ostream<char> & out)
620 : {
621 3 : int idx(1);
622 3 : out << "Option Sources:\n";
623 64 : for(auto const & opt : f_options_by_name)
624 : {
625 61 : out << " " << idx << ". option \"" << opt.second->get_name() << "\"";
626 122 : string_list_t sources(opt.second->trace_sources());
627 61 : if(sources.empty())
628 : {
629 29 : out << " (undefined)\n";
630 : }
631 : else
632 : {
633 32 : out << "\n";
634 84 : for(auto const & src : sources)
635 : {
636 52 : out << " " << src << "\n";
637 : }
638 : }
639 61 : out << "\n";
640 :
641 61 : ++idx;
642 : }
643 3 : out << std::flush;
644 3 : }
645 :
646 :
647 :
648 6 : } // namespace advgetopt
649 : // vim: ts=4 sw=4 et
|