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