advgetopt 2.0.50
Parse complex command line arguments and configuration files in C++.
advgetopt_options.cpp
Go to the documentation of this file.
1// Copyright (c) 2006-2025 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
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/glob_to_list.h>
50#include <snapdev/join_strings.h>
51#include <snapdev/tokenize_string.h>
52
53
54// C++
55//
56#include <list>
57
58
59// last include
60//
61#include <snapdev/poison.h>
62
63
64
65
66namespace advgetopt
67{
68
69
70
71
72
73
74
84{
85 for(auto & opt : f_options_by_name)
86 {
87 opt.second->reset();
88 }
89}
90
91
100void getopt::parse_options_info(option const * opts, bool ignore_duplicates)
101{
102 if(opts == nullptr)
103 {
104 return;
105 }
106
107 for(
108 ; (opts->f_flags & GETOPT_FLAG_END) == 0
109 ; ++opts)
110 {
111 if(opts->f_name == nullptr
112 || opts->f_name[0] == '\0')
113 {
114 throw getopt_logic_error("option long name missing or empty.");
115 }
116 short_name_t const one_char(string_to_short_name(opts->f_name));
117 if(one_char != NO_SHORT_NAME)
118 {
119 throw getopt_logic_error("a long name option must be at least 2 characters.");
120 }
121
122 short_name_t const short_name(opts->f_short_name);
123
124 char const * const n(strrchr(opts->f_name, ':'));
125 if((opts->f_flags & GETOPT_FLAG_REMOVE_NAMESPACE) != 0
126 && n != nullptr)
127 {
128 // TODO: this is probably not a well thought feature... when adding
129 // dynamic options within a library we want to include the
130 // name of that library to the option, something like:
131 //
132 // fluid-settings::fluid-settings-timeout
133 //
134 // and it is important to keep the namespace in this case,
135 // as we do above; however, for the command line, we don't
136 // want to have enter the namespace, so here we remove it
137 // for that option (and above we hid the one with the namespace)
138 //
139
140 // the official option uses `basename`
141 //
142 std::string const basename(n + 1);
143 option_info::pointer_t o(std::make_shared<option_info>(
144 basename
145 , short_name));
146 o->set_variables(f_variables);
147
148 o->set_environment_variable_name(opts->f_environment_variable_name);
149 o->add_flag(opts->f_flags);
150 o->set_default(opts->f_default);
151 o->set_help(opts->f_help);
152 o->set_multiple_separators(opts->f_multiple_separators);
153
154 if(opts->f_validator != nullptr)
155 {
156 o->set_validator(opts->f_validator);
157 }
158
159 add_option(o, ignore_duplicates);
160
161 // the full named option (with namespaces) uses an alias
162 //
163 option_info::pointer_t alias(std::make_shared<option_info>(
164 opts->f_name
165 , short_name));
166 alias->set_variables(f_variables);
167
168 alias->set_environment_variable_name(opts->f_environment_variable_name);
169 alias->add_flag(opts->f_flags | GETOPT_FLAG_ALIAS);
170 alias->set_default(opts->f_default);
171 alias->set_help(basename);
172 alias->set_multiple_separators(opts->f_multiple_separators);
173
174 if(opts->f_validator != nullptr)
175 {
176 alias->set_validator(opts->f_validator);
177 }
178
179 add_option(alias, ignore_duplicates);
180 }
181 else
182 {
183 option_info::pointer_t o(std::make_shared<option_info>(
184 opts->f_name
185 , short_name));
186 o->set_variables(f_variables);
187
188 o->set_environment_variable_name(opts->f_environment_variable_name);
189 o->add_flag(opts->f_flags);
190 o->set_default(opts->f_default);
191 o->set_help(opts->f_help);
192 o->set_multiple_separators(opts->f_multiple_separators);
193
194 if(opts->f_validator != nullptr)
195 {
196 o->set_validator(opts->f_validator);
197 }
198
199 add_option(o, ignore_duplicates);
200 }
201 }
202}
203
204
221void getopt::add_option(option_info::pointer_t opt, bool ignore_duplicates)
222{
223 if(get_option(opt->get_name(), true) != nullptr)
224 {
225 if(ignore_duplicates)
226 {
227 return;
228 }
229 throw getopt_defined_twice(
230 std::string("option named \"")
231 + opt->get_name()
232 + "\" found twice.");
233 }
234
235 short_name_t short_name(opt->get_short_name());
236 if(get_option(short_name, true) != nullptr)
237 {
238 if(ignore_duplicates)
239 {
240 short_name = NO_SHORT_NAME;
241 opt->set_short_name(NO_SHORT_NAME);
242 }
243 else
244 {
245 throw getopt_defined_twice(
246 "option with short name \""
247 + short_name_to_string(short_name)
248 + "\" found twice.");
249 }
250 }
251
252 if(opt->is_default_option())
253 {
254 if(f_default_option != nullptr)
255 {
256 throw getopt_logic_error("two default options found.");
257 }
258 if(opt->has_flag(GETOPT_FLAG_FLAG))
259 {
260 throw getopt_logic_error("a default option must accept parameters, it can't be a GETOPT_FLAG_FLAG.");
261 }
262
263 f_default_option = opt;
264 }
265
266 f_options_by_name[opt->get_name()] = opt;
267
268 if(short_name != NO_SHORT_NAME)
269 {
270 f_options_by_short_name[short_name] = opt;
271 }
272}
273
274
276{
277 std::string path;
278
279 char const * const options_files_directory(getenv("ADVGETOPT_OPTIONS_FILES_DIRECTORY"));
280 if(options_files_directory != nullptr
281 && *options_files_directory != '\0')
282 {
283 // environment variable has priority
284 //
285 path = options_files_directory;
286 }
289 {
290 // next the tool option environment has priority
291 //
293 }
294 else
295 {
296 // finally, use a default
297 //
298 path = "/usr/share/advgetopt/options/";
299 }
300
301 if(path.back() != '/')
302 {
303 path += '/';
304 }
305
306 return path;
307}
308
309
361{
362 string_list_t result;
363
364 std::string const filename(get_group_or_project_name());
365 if(filename.empty())
366 {
367 return result;
368 }
369
370 std::string const path(get_path_to_option_files());
371
372 // add the plain filename
373 //
374 result.push_back(path + filename + ".ini");
375
376 // look for additional filenames
377 //
378 std::string pattern(path + filename + "-*.ini");
379 snapdev::glob_to_list<std::list<std::string>> list;
380 if(list.read_path<snapdev::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS>(pattern))
381 {
382 for(auto const & l : list)
383 {
384 result.push_back(l);
385 }
386 }
387
388 return result;
389}
390
391
417{
419 for(auto const & l : list)
420 {
422 }
423}
424
425
476 std::string const & filename
477 , int const min_sections
478 , int const max_sections
479 , bool ignore_duplicates
480 , bool keep_all_sections)
481{
482 if(filename.empty())
483 {
484 return;
485 }
486
488 if(min_sections == 1
489 && max_sections == 1)
490 {
491 operators |= SECTION_OPERATOR_ONE_SECTION;
492 }
493
494 conf_file_setup conf_setup(
495 filename
499 , operators);
500 if(!conf_setup.is_valid())
501 {
502 return; // LCOV_EXCL_LINE
503 }
504
505 // if the file includes a section named after the group or project
506 // we can remove it completely (this helps with sharing fluid settings)
507 //
508 // the format of an option file is:
509 //
510 // [<option-name>]
511 // help=option description
512 //
513 // For fluid-settings to work, we need to include the name of service
514 // or tool as in:
515 //
516 // [<service>::<option-name>]
517 // help=option description
518 //
519 // so we want to remove the "<service>::" part to avoid the namespace
520 // in the --<option-name> command line options.
521 //
522 std::string const section_to_ignore(get_group_or_project_name());
523 conf_setup.set_section_to_ignore(section_to_ignore);
524
526 conf_file::sections_t const & sections(conf->get_sections());
527 for(auto & section_names : sections)
528 {
529 string_list_t names;
530 split_string(section_names, names, {"::"});
531 std::string option_name;
532 if(keep_all_sections
533 && names.size() > 1
534 && *names.begin() == section_to_ignore)
535 {
536 names.erase(names.begin());
537 option_name = snapdev::join_strings(names, "::");
538 }
539 else
540 {
541 option_name = section_names;
542 }
543
544 if(names.size() < static_cast<std::size_t>(min_sections)
545 || names.size() > static_cast<std::size_t>(max_sections))
546 {
547 if(min_sections == 1
548 && max_sections == 1) // LCOV_EXCL_LINE
549 {
550 // right now this case cannot happen because we set the
551 // SECTION_OPERATOR_ONE_SECTION flag so errors are caught
552 // directly inside the conf_file::get_conf_file() call
553 //
554 cppthread::log << cppthread::log_level_t::error // LCOV_EXCL_LINE
555 << filename // LCOV_EXCL_LINE
556 << ": the name of a settings definition must include one namespace; \"" // LCOV_EXCL_LINE
557 << section_names // LCOV_EXCL_LINE
558 << "\" is not considered valid." // LCOV_EXCL_LINE
559 << cppthread::end; // LCOV_EXCL_LINE
560 }
561 else
562 {
563 cppthread::log << cppthread::log_level_t::error
564 << filename
565 << ": the name of a settings definition must include between "
566 << min_sections
567 << " and "
568 << max_sections
569 << " namespaces; \""
570 << section_names
571 << "\" is not considered valid."
572 << cppthread::end;
573 }
574 continue;
575 }
576
577 std::string const parameter_name(option_name);
578 std::string const short_name(unquote(conf->get_parameter(parameter_name + "::shortname")));
579 short_name_t const sn(string_to_short_name(short_name));
580 if(sn == NO_SHORT_NAME
581 && !short_name.empty())
582 {
583 throw getopt_logic_error(
584 "option \""
585 + section_names
586 + "\" has an invalid short name \""
587 + short_name
588 + "\" in \""
589 + filename
590 + "\", it can't be more than one character.");
591 }
592
593 option_info::pointer_t opt(std::make_shared<option_info>(parameter_name, sn));
594 opt->set_variables(f_variables);
595
596 std::string const environment_variable_name(parameter_name + "::environment_variable_name");
597 if(conf->has_parameter(environment_variable_name))
598 {
599 opt->set_environment_variable_name(unquote(conf->get_parameter(environment_variable_name)));
600 }
601
602 std::string const default_name(parameter_name + "::default");
603 if(conf->has_parameter(default_name))
604 {
605 opt->set_default(unquote(conf->get_parameter(default_name)));
606 }
607
608 opt->set_help(unquote(conf->get_parameter(parameter_name + "::help")));
609
610 std::string const validator_name_and_params(conf->get_parameter(parameter_name + "::validator"));
611 opt->set_validator(validator_name_and_params);
612
613 std::string const alias_name(parameter_name + "::alias");
614 if(conf->has_parameter(alias_name))
615 {
616 if(!opt->get_help().empty())
617 {
618 throw getopt_logic_error(
619 "option \""
620 + section_names
621 + "\" is an alias and as such it can't include a help=... parameter in \""
622 + filename
623 + "\".");
624 }
625 opt->set_help(unquote(conf->get_parameter(alias_name)));
626 opt->add_flag(GETOPT_FLAG_ALIAS);
627 }
628
629 std::string const allowed_name(parameter_name + "::allowed");
630 if(conf->has_parameter(allowed_name))
631 {
632 std::string const allowed_list(conf->get_parameter(allowed_name));
633 string_list_t allowed;
634 split_string(allowed_list, allowed, {","});
635 for(auto const & a : allowed)
636 {
637 if(a == "command-line")
638 {
639 opt->add_flag(GETOPT_FLAG_COMMAND_LINE);
640 }
641 else if(a == "environment-variable")
642 {
644 }
645 else if(a == "configuration-file")
646 {
647 opt->add_flag(GETOPT_FLAG_CONFIGURATION_FILE);
648 }
649 else if(a == "dynamic-configuration")
650 {
652 }
653 }
654 }
655
656 std::string const group_name(parameter_name + "::group");
657 if(conf->has_parameter(group_name))
658 {
659 std::string const group(conf->get_parameter(group_name));
660 if(group == "commands")
661 {
662 opt->add_flag(GETOPT_FLAG_GROUP_COMMANDS);
663 }
664 else if(group == "options")
665 {
666 opt->add_flag(GETOPT_FLAG_GROUP_OPTIONS);
667 }
668 else if(group == "three")
669 {
670 opt->add_flag(GETOPT_FLAG_GROUP_THREE);
671 }
672 else if(group == "four")
673 {
674 opt->add_flag(GETOPT_FLAG_GROUP_FOUR);
675 }
676 else if(group == "five")
677 {
678 opt->add_flag(GETOPT_FLAG_GROUP_FIVE);
679 }
680 else if(group == "six")
681 {
682 opt->add_flag(GETOPT_FLAG_GROUP_SIX);
683 }
684 else if(group == "seven")
685 {
686 opt->add_flag(GETOPT_FLAG_GROUP_SEVEN);
687 }
688 }
689
690 if(conf->has_parameter(parameter_name + "::show-usage-on-error"))
691 {
692 opt->add_flag(GETOPT_FLAG_SHOW_USAGE_ON_ERROR);
693 }
694
695 if(conf->has_parameter(parameter_name + "::no-arguments"))
696 {
697 opt->add_flag(GETOPT_FLAG_FLAG);
698 }
699
700 if(conf->has_parameter(parameter_name + "::multiple"))
701 {
702 opt->add_flag(GETOPT_FLAG_MULTIPLE);
703 }
704
705 if(conf->has_parameter(parameter_name + "::required"))
706 {
707 opt->add_flag(GETOPT_FLAG_REQUIRED);
708 }
709
710 add_option(opt, ignore_duplicates);
711 }
712}
713
714
724{
725 for(auto & c : f_options_by_name)
726 {
727 if(c.second->has_flag(GETOPT_FLAG_ALIAS))
728 {
729 std::string const & alias_name(c.second->get_help());
730 if(alias_name.empty())
731 {
732 throw getopt_logic_error(
733 "the default value of your alias cannot be an empty string for \""
734 + c.first
735 + "\".");
736 }
737
738 // we have to use the `true` flag in this get_option() because
739 // aliases may not yet be defined
740 //
741 option_info::pointer_t alias(get_option(alias_name, true));
742 if(alias == nullptr)
743 {
744 throw getopt_logic_error(
745 "no option named \""
746 + alias_name
747 + "\" to satisfy the alias of \""
748 + c.first
749 + "\".");
750 }
751
752 flag_t const expected_flags(c.second->get_flags() & ~GETOPT_FLAG_ALIAS);
753 if(alias->get_flags() != expected_flags)
754 {
755 std::stringstream ss;
756 ss << std::hex
757 << "the flags of alias \""
758 << c.first
759 << "\" (0x"
760 << expected_flags
761 << ") are different than the flags of \""
762 << alias_name
763 << "\" (0x"
764 << alias->get_flags()
765 << ").";
766 throw getopt_logic_error(ss.str());
767 }
768
769 c.second->set_alias_destination(alias);
770 }
771 }
772}
773
774
806void getopt::set_short_name(std::string const & name, short_name_t short_name)
807{
808 auto opt(f_options_by_name.find(name));
809 if(opt == f_options_by_name.end())
810 {
811 throw getopt_logic_error(
812 "option with name \""
813 + name
814 + "\" not found.");
815 }
816
817 if(short_name != NO_SHORT_NAME)
818 {
819 auto it(f_options_by_short_name.find(short_name));
820 if(it != f_options_by_short_name.end())
821 {
822 if(it->second == opt->second)
823 {
824 // same option, already named 'short_name'
825 //
826 return;
827 }
828
829 throw getopt_logic_error(
830 "found another option (\""
831 + it->second->get_name()
832 + "\") with short name '"
833 + short_name_to_string(short_name)
834 + "'.");
835 }
836 }
837
838 short_name_t const old_short_name(opt->second->get_short_name());
839 if(old_short_name != NO_SHORT_NAME)
840 {
841 auto it(f_options_by_short_name.find(old_short_name));
842 if(it != f_options_by_short_name.end())
843 {
844 f_options_by_short_name.erase(it);
845 }
846 }
847
848 opt->second->set_short_name(short_name);
849
850 if(short_name != NO_SHORT_NAME)
851 {
852 f_options_by_short_name[short_name] = opt->second;
853 }
854}
855
856
868void getopt::show_option_sources(std::basic_ostream<char> & out)
869{
870 int idx(1);
871 out << "Option Sources:\n";
872 for(auto const & opt : f_options_by_name)
873 {
874 out << " " << idx << ". option \"" << opt.second->get_name() << "\"";
875 string_list_t sources(opt.second->trace_sources());
876 if(sources.empty())
877 {
878 out << " (undefined)\n";
879 }
880 else
881 {
882 out << "\n";
883 for(auto const & src : sources)
884 {
885 out << " " << src << "\n";
886 }
887 }
888 out << "\n";
889
890 ++idx;
891 }
892 out << std::flush;
893}
894
895
896
897} // namespace advgetopt
898// vim: ts=4 sw=4 et
Definitions of the advanced getopt class.
bool is_valid() const
Check whether the setup is considered valid.
void set_section_to_ignore(std::string const &section_name)
Set a section name to ignore.
std::shared_ptr< conf_file > pointer_t
Definition conf_file.h:186
string_set_t sections_t
Definition conf_file.h:187
static pointer_t get_conf_file(conf_file_setup const &setup)
Create and read a conf_file.
std::string get_group_or_project_name() const
Retrieve the group or project name.
options_environment f_options_environment
Definition advgetopt.h:226
option_info::map_by_name_t f_options_by_name
Definition advgetopt.h:227
option_info::pointer_t get_option(std::string const &name, bool exact_option=false) const
Retrieve an option by name.
void add_option(option_info::pointer_t opt, bool ignore_duplicates=false)
Add one option to the advgetopt object.
option_info::map_by_short_name_t f_options_by_short_name
Definition advgetopt.h:228
std::string get_path_to_option_files() const
void parse_options_info(option const *opts, bool ignore_duplicates=false)
Parse the options to option_info objects.
string_list_t get_filenames_of_option_definitions() const
Get the path and filename to options.
void parse_options_from_file()
Check for a file with option definitions.
void reset()
Reset all the options.
void link_aliases()
Link options marked as a GETOPT_FLAG_ALIAS.
void show_option_sources(std::basic_ostream< char > &out)
Output the source of each option.
variables::pointer_t f_variables
Definition advgetopt.h:231
option_info::pointer_t f_default_option
Definition advgetopt.h:229
void set_short_name(std::string const &name, short_name_t short_name)
Assign a short name to an option.
std::shared_ptr< option_info > pointer_t
Definition option_info.h:78
Declaration of the conf_file class used to read a configuration file.
Definitions of the advanced getopt exceptions.
The advgetopt environment to parse command line options.
Definition version.h:37
static constexpr flag_t GETOPT_FLAG_GROUP_SEVEN
Definition flags.h:78
void split_string(std::string const &str, string_list_t &result, string_list_t const &separators)
Split a string in sub-strings separated by separators.
Definition utils.cpp:347
constexpr section_operator_t SECTION_OPERATOR_INI_FILE
Definition conf_file.h:97
static constexpr flag_t GETOPT_FLAG_REMOVE_NAMESPACE
Definition flags.h:80
static constexpr flag_t GETOPT_FLAG_GROUP_OPTIONS
Definition flags.h:73
std::uint_fast16_t section_operator_t
Definition conf_file.h:91
static constexpr flag_t GETOPT_FLAG_SHOW_USAGE_ON_ERROR
Definition flags.h:61
static constexpr flag_t GETOPT_FLAG_END
Definition flags.h:84
constexpr section_operator_t SECTION_OPERATOR_ONE_SECTION
Definition conf_file.h:99
constexpr comment_t COMMENT_SHELL
Definition conf_file.h:83
static constexpr flag_t GETOPT_FLAG_DYNAMIC_CONFIGURATION
Definition flags.h:49
short_name_t string_to_short_name(std::string const &name)
Transform a string to a short name.
static constexpr flag_t GETOPT_FLAG_COMMAND_LINE
Definition flags.h:46
static constexpr flag_t GETOPT_FLAG_GROUP_FOUR
Definition flags.h:75
static constexpr flag_t GETOPT_FLAG_GROUP_SIX
Definition flags.h:77
constexpr assignment_operator_t ASSIGNMENT_OPERATOR_EQUAL
Definition conf_file.h:71
constexpr short_name_t NO_SHORT_NAME
Definition option_info.h:51
std::uint32_t flag_t
Definition flags.h:42
static constexpr flag_t GETOPT_FLAG_CONFIGURATION_FILE
Definition flags.h:48
char32_t short_name_t
Definition option_info.h:49
static constexpr flag_t GETOPT_FLAG_FLAG
Definition flags.h:52
static constexpr flag_t GETOPT_FLAG_GROUP_COMMANDS
Definition flags.h:72
static constexpr flag_t GETOPT_FLAG_GROUP_FIVE
Definition flags.h:76
std::string short_name_to_string(short_name_t short_name)
Convert a short name to a UTF-8 string.
constexpr comment_t COMMENT_INI
Definition conf_file.h:82
static constexpr flag_t GETOPT_FLAG_MULTIPLE
Definition flags.h:54
static constexpr flag_t GETOPT_FLAG_GROUP_THREE
Definition flags.h:74
std::string unquote(std::string const &s, std::string const &pairs)
Remove single (') or double (") quotes from a string.
Definition utils.cpp:168
static constexpr flag_t GETOPT_FLAG_REQUIRED
Definition flags.h:53
static constexpr flag_t GETOPT_FLAG_ENVIRONMENT_VARIABLE
Definition flags.h:47
std::vector< std::string > string_list_t
Definition utils.h:41
static constexpr flag_t GETOPT_FLAG_ALIAS
Definition flags.h:51
Structure representing an option.
Definition options.h:70
char const *const * f_multiple_separators
Definition options.h:78
short_name_t f_short_name
Definition options.h:71
char const * f_help
Definition options.h:76
char const * f_validator
Definition options.h:77
char const * f_environment_variable_name
Definition options.h:74
char const * f_name
Definition options.h:73
char const * f_default
Definition options.h:75
char const * f_options_files_directory
Definition options.h:445

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.