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 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 :
129 1386 : o->add_flag(opts->f_flags);
130 1386 : o->set_default(opts->f_default);
131 1386 : o->set_help(opts->f_help);
132 1386 : o->set_multiple_separators(opts->f_multiple_separators);
133 :
134 1386 : if(opts->f_validator != nullptr)
135 : {
136 6 : o->set_validator(opts->f_validator);
137 : }
138 :
139 1390 : 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 1399 : void getopt::add_option(option_info::pointer_t opt, bool ignore_duplicates)
161 : {
162 1399 : 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 1397 : short_name_t short_name(opt->get_short_name());
175 1397 : 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 1396 : 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 1394 : f_options_by_name[opt->get_name()] = opt;
206 :
207 1394 : if(short_name != NO_SHORT_NAME)
208 : {
209 650 : 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, int min_sections, int max_sections)
223 : * function for additional details.
224 : *
225 : * \sa parse_options_from_file(std::string const & filename, int min_sections, int max_sections)
226 : */
227 334 : void getopt::parse_options_from_file()
228 : {
229 659 : std::string filename;
230 :
231 334 : if(f_options_environment.f_project_name == nullptr
232 330 : || f_options_environment.f_project_name[0] == '\0')
233 : {
234 6 : return;
235 : }
236 :
237 328 : if(f_options_environment.f_options_files_directory == nullptr
238 73 : || f_options_environment.f_options_files_directory[0] == '\0')
239 : {
240 257 : 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 328 : filename += f_options_environment.f_project_name;
251 328 : filename += ".ini";
252 :
253 328 : 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 328 : void getopt::parse_options_from_file(
293 : std::string const & filename
294 : , int min_sections
295 : , int max_sections)
296 : {
297 328 : section_operator_t operators(SECTION_OPERATOR_INI_FILE);
298 328 : if(min_sections == 1
299 328 : && max_sections == 1)
300 : {
301 328 : operators |= SECTION_OPERATOR_ONE_SECTION;
302 : }
303 328 : conf_file_setup conf_setup(filename
304 : , line_continuation_t::line_continuation_unix
305 : , ASSIGNMENT_OPERATOR_EQUAL
306 : , COMMENT_INI | COMMENT_SHELL
307 656 : , operators);
308 328 : if(!conf_setup.is_valid())
309 : {
310 : return; // LCOV_EXCL_LINE
311 : }
312 :
313 656 : conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
314 656 : conf_file::sections_t const & sections(conf->get_sections());
315 341 : 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 30 : option_info::pointer_t opt(std::make_shared<option_info>(parameter_name, sn));
365 :
366 30 : std::string const default_name(parameter_name + "::default");
367 15 : if(conf->has_parameter(default_name))
368 : {
369 9 : opt->set_default(unquote(conf->get_parameter(default_name)));
370 : }
371 :
372 15 : opt->set_help(unquote(conf->get_parameter(parameter_name + "::help")));
373 :
374 30 : std::string const validator_name_and_params(conf->get_parameter(parameter_name + "::validator"));
375 15 : opt->set_validator(validator_name_and_params);
376 :
377 28 : std::string const alias_name(parameter_name + "::alias");
378 14 : if(conf->has_parameter(alias_name))
379 : {
380 6 : if(!opt->get_help().empty())
381 : {
382 : throw getopt_logic_error(
383 : "option \""
384 2 : + section_names
385 3 : + "\" is an alias and as such it can't include a help=... parameter in \""
386 3 : + filename
387 3 : + "\".");
388 : }
389 5 : opt->set_help(unquote(conf->get_parameter(alias_name)));
390 5 : opt->add_flag(GETOPT_FLAG_ALIAS);
391 : }
392 :
393 26 : std::string const allowed_name(parameter_name + "::allowed");
394 13 : if(conf->has_parameter(allowed_name))
395 : {
396 26 : std::string const allowed_list(conf->get_parameter(allowed_name));
397 26 : string_list_t allowed;
398 13 : split_string(allowed_list, allowed, {","});
399 32 : for(auto const & a : allowed)
400 : {
401 19 : if(a == "command-line")
402 : {
403 10 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE);
404 : }
405 9 : else if(a == "environment-variable")
406 : {
407 6 : opt->add_flag(GETOPT_FLAG_ENVIRONMENT_VARIABLE);
408 : }
409 3 : else if(a == "configuration-file")
410 : {
411 3 : opt->add_flag(GETOPT_FLAG_CONFIGURATION_FILE);
412 : }
413 : }
414 : }
415 :
416 13 : if(conf->has_parameter(parameter_name + "::show-usage-on-error"))
417 : {
418 1 : opt->add_flag(GETOPT_FLAG_SHOW_USAGE_ON_ERROR);
419 : }
420 :
421 13 : if(conf->has_parameter(parameter_name + "::no-arguments"))
422 : {
423 4 : opt->add_flag(GETOPT_FLAG_FLAG);
424 : }
425 :
426 13 : if(conf->has_parameter(parameter_name + "::multiple"))
427 : {
428 1 : opt->add_flag(GETOPT_FLAG_MULTIPLE);
429 : }
430 :
431 13 : if(conf->has_parameter(parameter_name + "::required"))
432 : {
433 5 : opt->add_flag(GETOPT_FLAG_REQUIRED);
434 : }
435 :
436 13 : add_option(opt);
437 : }
438 : }
439 :
440 :
441 : /** \brief Link options marked as a GETOPT_FLAG_ALIAS.
442 : *
443 : * After we defined all the options, go through the list again to find
444 : * aliases and link them with their corresponding alias option.
445 : *
446 : * \exception getopt_exception_invalid
447 : * All aliases must exist or this exception is raised.
448 : */
449 255 : void getopt::link_aliases()
450 : {
451 1255 : for(auto & c : f_options_by_name)
452 : {
453 1004 : if(c.second->has_flag(GETOPT_FLAG_ALIAS))
454 : {
455 22 : std::string const & alias_name(c.second->get_help());
456 22 : if(alias_name.empty())
457 : {
458 : throw getopt_logic_error(
459 : "the default value of your alias cannot be an empty string for \""
460 4 : + c.first
461 6 : + "\".");
462 : }
463 :
464 : // we have to use the `true` flag in this get_option() because
465 : // aliases may not yet be defined
466 : //
467 40 : option_info::pointer_t alias(get_option(alias_name, true));
468 20 : if(alias == nullptr)
469 : {
470 : throw getopt_logic_error(
471 : "no option named \""
472 2 : + alias_name
473 3 : + "\" to satisfy the alias of \""
474 3 : + c.first
475 3 : + "\".");
476 : }
477 :
478 19 : flag_t const expected_flags(c.second->get_flags() & ~GETOPT_FLAG_ALIAS);
479 19 : if(alias->get_flags() != expected_flags)
480 : {
481 2 : std::stringstream ss;
482 1 : ss << std::hex
483 : << "the flags of alias \""
484 : << c.first
485 1 : << "\" (0x"
486 1 : << expected_flags
487 : << ") are different than the flags of \""
488 : << alias_name
489 1 : << "\" (0x"
490 1 : << alias->get_flags()
491 1 : << ").";
492 1 : throw getopt_logic_error(ss.str());
493 : }
494 :
495 18 : c.second->set_alias_destination(alias);
496 : }
497 : }
498 251 : }
499 :
500 :
501 : /** \brief Assign a short name to an option.
502 : *
503 : * This function allows for dynamically assigning a short name to an option.
504 : * This is useful for cases where a certain number of options may be added
505 : * dynamically and may share the same short name or similar situation.
506 : *
507 : * On our end we like to add `-c` as the short name of the `--config-dir`
508 : * command line or environment variable option. However, some of our tools
509 : * use `-c` for other reason (i.e. our `cxpath` tool uses `-c` for its
510 : * `--compile` option.) So we do not want to have it as a default in
511 : * `--config-dir`. Instead we assign it afterward if possible.
512 : *
513 : * **IMPORTANT:** It is possible to change the short-name at any time.
514 : * However, note that you can't have duplicates. It is also possible
515 : * to remove a short-name by setting it to the advgetopt::NO_SHORT_NAME
516 : * special value.
517 : *
518 : * \note
519 : * This function requires you to make use of the constructor without the
520 : * `argc` and `argv` parameters, add the short name, then run all the
521 : * parsing.
522 : *
523 : * \exception getopt_exception_logic
524 : * The same short name cannot be used more than once. This exception is
525 : * raised if it is discovered that another option already makes use of
526 : * this short name. This exception is also raised if \p name does not
527 : * reference an existing option.
528 : *
529 : * \param[in] name The name of the option which is to receive a short name.
530 : * \param[in] short_name The short name to assigned to the \p name option.
531 : */
532 9 : void getopt::set_short_name(std::string const & name, short_name_t short_name)
533 : {
534 9 : auto opt(f_options_by_name.find(name));
535 9 : if(opt == f_options_by_name.end())
536 : {
537 : throw getopt_logic_error(
538 : "option with name \""
539 2 : + name
540 3 : + "\" not found.");
541 : }
542 :
543 8 : if(short_name != NO_SHORT_NAME)
544 : {
545 6 : auto it(f_options_by_short_name.find(short_name));
546 6 : if(it != f_options_by_short_name.end())
547 : {
548 2 : if(it->second == opt->second)
549 : {
550 : // same option, already named 'short_name'
551 : //
552 1 : return;
553 : }
554 :
555 : throw getopt_logic_error(
556 : "found another option (\""
557 2 : + it->second->get_name()
558 3 : + "\") with short name '"
559 4 : + short_name_to_string(short_name)
560 3 : + "'.");
561 : }
562 : }
563 :
564 6 : short_name_t const old_short_name(opt->second->get_short_name());
565 6 : if(old_short_name != NO_SHORT_NAME)
566 : {
567 2 : auto it(f_options_by_short_name.find(old_short_name));
568 2 : if(it != f_options_by_short_name.end())
569 : {
570 2 : f_options_by_short_name.erase(it);
571 : }
572 : }
573 :
574 6 : opt->second->set_short_name(short_name);
575 :
576 6 : if(short_name != NO_SHORT_NAME)
577 : {
578 4 : f_options_by_short_name[short_name] = opt->second;
579 : }
580 : }
581 :
582 :
583 : /** \brief Output the source of each option.
584 : *
585 : * This function goes through the list of options by name ("alphabetically")
586 : * and prints out the sources or "(undefined)" if not defined anywhere.
587 : *
588 : * This function gets called when using the `--show-option-sources`
589 : * system command line option at the time the process_system_options()
590 : * function gets called.
591 : *
592 : * \param[in] out The output streaming where the info is written.
593 : */
594 3 : void getopt::show_option_sources(std::basic_ostream<char> & out)
595 : {
596 3 : int idx(1);
597 3 : out << "Option Sources:\n";
598 61 : for(auto const & opt : f_options_by_name)
599 : {
600 58 : out << " " << idx << ". option \"" << opt.second->get_name() << "\"";
601 116 : string_list_t sources(opt.second->trace_sources());
602 58 : if(sources.empty())
603 : {
604 26 : out << " (undefined)\n";
605 : }
606 : else
607 : {
608 32 : out << "\n";
609 84 : for(auto const & src : sources)
610 : {
611 52 : out << " " << src << "\n";
612 : }
613 : }
614 58 : out << "\n";
615 :
616 58 : ++idx;
617 : }
618 3 : out << std::flush;
619 3 : }
620 :
621 :
622 :
623 6 : } // namespace advgetopt
624 : // vim: ts=4 sw=4 et
|