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