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