advgetopt 2.0.49
Parse complex command line arguments and configuration files in C++.
advgetopt_config.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
27// self
28//
29#include "advgetopt/advgetopt.h"
30
31#include "advgetopt/conf_file.h"
32#include "advgetopt/exception.h"
33
34
35// cppthread
36//
37#include <cppthread/log.h>
38
39
40// C
41//
42#include <string.h>
43#include <unistd.h>
44
45
46// last include
47//
48#include <snapdev/poison.h>
49
50
51
52namespace advgetopt
53{
54
55
56
57
85 bool exists
86 , bool writable
87 , int argc
88 , char * argv[]) const
89{
90 string_list_t result;
91
92 get_managed_configuration_filenames(result, writable, argc, argv);
93 get_direct_configuration_filenames(result, writable);
94
95 if(!exists)
96 {
97 return result;
98 }
99
100 string_list_t existing_files;
101 int const mode(R_OK | (writable ? W_OK : 0));
102 for(auto r : result)
103 {
104 if(access(r.c_str(), mode) == 0)
105 {
106 existing_files.push_back(r);
107 }
108 }
109
110 return existing_files;
111}
112
113
128void getopt::add_configuration_filename(string_list_t & names, std::string const & add)
129{
130 if(std::find(names.begin(), names.end(), add) == names.end())
131 {
132 names.push_back(add);
133 }
134}
135
136
157 string_list_t & names
158 , bool writable
159 , int argc
160 , char * argv[]) const
161{
164 {
165 return;
166 }
167
168 string_list_t directories;
170 {
171 if(f_parsed)
172 {
173 // WARNING: at this point the command line and environment
174 // variable may not be parsed in full if at all
175 //
176 //if(has_flag(SYSTEM_OPTION_CONFIGURATION_FILENAMES))
177 if(is_defined("config-dir"))
178 {
179 std::size_t const max(size("config-dir"));
180 directories.reserve(max);
181 for(std::size_t idx(0); idx < max; ++idx)
182 {
183 directories.push_back(get_string("config-dir", idx));
184 }
185 }
186 }
187 else
188 {
189 // we've got to do some manual parsing (argh!)
190 //
191 directories = find_config_dir(argc, argv);
192 if(directories.empty())
193 {
195
196 std::vector<char *> sub_argv;
197 sub_argv.resize(args.size() + 2);
198 sub_argv[0] = const_cast<char *>(f_program_fullname.c_str());
199 for(std::size_t idx(0); idx < args.size(); ++idx)
200 {
201 sub_argv[idx + 1] = const_cast<char *>(args[idx].c_str());
202 }
203 sub_argv[args.size() + 1] = nullptr;
204
205 directories = find_config_dir(sub_argv.size() - 1, sub_argv.data());
206 }
207 }
208 }
209
211 {
212 for(char const * const * configuration_directories(f_options_environment.f_configuration_directories);
213 *configuration_directories != nullptr;
214 ++configuration_directories)
215 {
216 directories.push_back(*configuration_directories);
217 }
218 }
219
220 if(directories.empty())
221 {
222 std::string const name(get_group_or_project_name());
223
224 if(!name.empty())
225 {
226 std::string directory_name("/usr/share/advgetopt/options/");
227 directory_name += name;
228 directories.push_back(directory_name);
229
230 directory_name = "/usr/share/";
231 directory_name += name;
232 directory_name += "/options";
233 directories.push_back(directory_name);
234
235 directory_name = "/etc/";
236 directory_name += name;
237 directories.push_back(directory_name);
238 }
239 }
240
241 std::string const filename(f_options_environment.f_configuration_filename);
242
243 for(auto directory : directories)
244 {
245 if(!directory.empty())
246 {
247 std::string const full_filename(directory + ("/" + filename));
248 std::string const user_filename(handle_user_directory(full_filename));
249 if(user_filename == full_filename)
250 {
251 if(!writable)
252 {
253 add_configuration_filename(names, user_filename);
254 }
255
256 string_list_t const with_project_name(insert_group_name(
257 user_filename
260 if(!with_project_name.empty())
261 {
262 for(auto const & n : with_project_name)
263 {
265 }
266 }
267 }
268 else
269 {
270 add_configuration_filename(names, user_filename);
271 }
272 }
273 }
274}
275
276
293 string_list_t & names
294 , bool writable) const
295{
297 {
298 return;
299 }
300
301 // load options from configuration files specified as is by the programmer
302 //
303 for(char const * const * configuration_files(f_options_environment.f_configuration_files);
304 *configuration_files != nullptr;
305 ++configuration_files)
306 {
307 char const * filename(*configuration_files);
308 if(*filename == '\0')
309 {
310 continue;
311 }
312
313 std::string const user_filename(handle_user_directory(filename));
314 if(user_filename == filename)
315 {
316 if(!writable)
317 {
318 add_configuration_filename(names, user_filename);
319 }
320
321 string_list_t const with_project_name(insert_group_name(
322 user_filename
325 if(!with_project_name.empty())
326 {
327 for(auto const & n : with_project_name)
328 {
330 }
331 }
332 }
333 else
334 {
335 add_configuration_filename(names, user_filename);
336 }
337 }
338}
339
340
371{
373 {
374 // check the programmer defined paths as is
375 //
376 char const * found(nullptr);
377 for(char const * const * configuration_files(f_options_environment.f_configuration_files);
378 *configuration_files != nullptr;
379 ++configuration_files)
380 {
381 char const * filename(*configuration_files);
382 if(*filename == '\0')
383 {
384 // ignore empty filenames
385 //
386 continue;
387 }
388
389 if(filename[0] == '~'
390 && (filename[1] == '/' || filename[1] == '\0'))
391 {
392 // ignore user directory entries
393 //
394 continue;
395 }
396
397 // we want the last one, so we are not done once we found one...
398 //
399 found = filename;
400 }
401
402 if(found != nullptr)
403 {
404 return default_group_name(
405 found
408 }
409 }
410
413 {
414 // check the directories either defined by the programmer or if
415 // none defined by the programmer, as defined by advgetopt which
416 // in this case simply means "/etc/"; we ignore the possible
417 // use of the --config-dir because in that case the administrator
418 // knows where to save his file
419 //
420 std::string const name(get_group_or_project_name());
421
422 std::string directory;
424 {
425 for(char const * const * configuration_directories(f_options_environment.f_configuration_directories);
426 *configuration_directories != nullptr;
427 ++configuration_directories)
428 {
429 char const * dir(*configuration_directories);
430 if(dir[0] == '\0')
431 {
432 continue;
433 }
434
435 if(dir[0] == '~'
436 && (dir[1] == '/' || dir[1] == '\0'))
437 {
438 continue;
439 }
440
441 // we want to keep the last entry unless it starts with ~/...
442 //
443 directory = dir;
444 }
445 }
446
447 if(directory.empty())
448 {
449 // no programmer defined directory, use a system defined one
450 // instead
451 //
452 directory = "/etc/" + name;
453 }
454
455 if(directory.back() != '/')
456 {
457 directory += '/';
458 }
459
460 std::string const filename(directory + f_options_environment.f_configuration_filename);
461
462 return default_group_name(
463 filename
466 }
467
468 // the programmer did not define anything, it is likely that no files
469 // will be loaded but we still generate a default name
470 //
471 std::string filename("/etc/");
474 {
476 }
477 else if(f_options_environment.f_project_name != nullptr
479 {
481 }
482 else
483 {
484 // really nothing can be done in this case... we have no name
485 // to generate a valid path/configuration filename
486 //
487 return std::string();
488 }
489
490 filename += '/';
491
494 {
496 }
497 else if(f_options_environment.f_group_name != nullptr
499 {
501 }
502 else
503 {
504 throw getopt_logic_error("we just checked both of those names and at least one was valid."); // LCOV_EXCL_LINE
505 }
506
507 filename += ".conf";
508
509 return filename;
510}
511
512
527 int argc
528 , char * argv[])
529{
530 if(argv == nullptr)
531 {
532 return string_list_t();
533 }
534
535 string_list_t result;
536 for(int idx(1); idx < argc; ++idx)
537 {
538 if(strcmp(argv[idx], "--config-dir") == 0)
539 {
540 for(++idx; idx < argc; ++idx)
541 {
542 if(argv[idx][0] == '-')
543 {
544 --idx;
545 break;
546 }
547 result.push_back(argv[idx]);
548 }
549 }
550 else if(strncmp(argv[idx], "--config-dir=", 13) == 0)
551 {
552 result.push_back(argv[idx] + 13);
553 }
554 }
555
556 return result;
557}
558
559
604void getopt::parse_configuration_files(int argc, char * argv[])
605{
606 string_list_t const filenames(get_configuration_filenames(false, false, argc, argv));
607
608 for(auto f : filenames)
609 {
611 f_parsed = false;
612 }
613
614 f_parsed = true;
615}
616
617
640void getopt::process_configuration_file(std::string const & filename)
641{
643
646 {
647 conf_setup = std::make_shared<conf_file_setup>(filename);
648 }
649 else
650 {
651 conf_setup = std::make_shared<conf_file_setup>(
652 filename
654 }
655 if(!conf_setup->is_valid())
656 {
657 // a non-existant file is considered valid now so this should never
658 // happen; later we may use the flag if we find errors in the file
659 //
660 return; // LCOV_EXCL_LINE
661 }
663
664 // is there a variable section?
665 //
667 {
668 conf->section_to_variables(
670 , f_variables);
671 }
672
673 conf_file::sections_t sections(conf->get_sections());
674 if(!sections.empty())
675 {
676 std::string const name(CONFIGURATION_SECTIONS);
677 option_info::pointer_t configuration_sections(get_option(name));
678 if(configuration_sections == nullptr)
679 {
680 configuration_sections = std::make_shared<option_info>(name);
681 configuration_sections->add_flag(
684 f_options_by_name[configuration_sections->get_name()] = configuration_sections;
685 }
686 else if(!configuration_sections->has_flag(GETOPT_FLAG_MULTIPLE))
687 {
688 cppthread::log << cppthread::log_level_t::error
689 << "option \""
690 << name
691 << "\" must have GETOPT_FLAG_MULTIPLE set."
692 << cppthread::end;
693 return;
694 }
695 for(auto s : sections)
696 {
697 if(!configuration_sections->has_value(s))
698 {
699 configuration_sections->add_value(
700 s
701 , string_list_t()
703 }
704 }
705 }
706
707 for(auto const & param : conf->get_parameters())
708 {
709 // in configuration files we only allow long arguments
710 //
711 option_info::pointer_t opt(get_option(param.first));
712 if(opt == nullptr)
713 {
715 || param.first.length() == 1)
716 {
717 cppthread::log << cppthread::log_level_t::error
718 << "unknown option \""
719 << option_with_underscores(param.first)
720 << "\" found in configuration file \""
721 << filename
722 << "\" on line "
723 << param.second.get_line()
724 << "."
725 << cppthread::end;
726 continue;
727 }
728 else
729 {
730 // add a new parameter dynamically
731 //
732 opt = std::make_shared<option_info>(param.first);
733 opt->set_variables(f_variables);
734
736
737 // consider the first definition as the default
738 // (which is likely in our environment)
739 //
740 opt->set_default(param.second);
741
742 f_options_by_name[opt->get_name()] = opt;
743 }
744 }
745 else
746 {
747 if(!opt->has_flag(GETOPT_FLAG_CONFIGURATION_FILE))
748 {
749 // in configuration files we are expected to use '_' so
750 // print an error with such
751 //
752 cppthread::log << cppthread::log_level_t::error
753 << "option \""
754 << option_with_underscores(param.first)
755 << "\" is not supported in configuration files (found in \""
756 << filename
757 << "\")."
758 << cppthread::end;
759 continue;
760 }
761 }
762
763 std::string value(param.second.get_value());
764 switch(param.second.get_assignment_operator())
765 {
768 // nothing special in this case, just overwrite if already defined
769 //
770 break;
771
773 if(opt->is_defined())
774 {
775 // already set, do not overwrite
776 //
777 continue;
778 }
779 break;
780
782 if(opt->is_defined()
783 && !opt->has_flag(GETOPT_FLAG_MULTIPLE))
784 {
785 // append the new value
786 //
787 value = opt->get_value() + value;
788 }
789 break;
790
792 if(opt->is_defined())
793 {
794 // prevent re-assignment
795 //
796 cppthread::log << cppthread::log_level_t::error
797 << "option \""
798 << option_with_underscores(param.first)
799 << "\" found in configuration file \""
800 << filename
801 << "\" on line "
802 << param.second.get_line()
803 << " uses the := operator but the value is already defined."
804 << cppthread::end;
805 continue;
806 }
807 break;
808
809 }
810
812 opt
813 , value
814 , filename
815 , string_list_t()
817 }
818
819 f_parsed = true;
820}
821
822
823
824
825
826
827} // namespace advgetopt
828// vim: ts=4 sw=4 et
Definitions of the advanced getopt class.
std::shared_ptr< conf_file_setup > pointer_t
Definition conf_file.h:113
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_string(std::string const &name, int idx=0, bool raw=false) const
Get the content of an option as a string.
string_list_t get_configuration_filenames(bool exists, bool writable, int argc=0, char *argv[]=nullptr) const
Generate a list of configuration filenames.
std::string get_group_or_project_name() const
Retrieve the group or project name.
std::size_t size(std::string const &name) const
Retrieve the number of arguments.
options_environment f_options_environment
Definition advgetopt.h:224
void add_option_from_string(option_info::pointer_t opt, std::string const &value, std::string const &filename, string_list_t const &option_keys, option_source_t source=option_source_t::SOURCE_DIRECT)
Add an option with a value defined in a string.
void get_managed_configuration_filenames(string_list_t &names, bool writable, int argc, char *argv[]) const
Generate the list of managed configuration filenames.
void process_configuration_file(std::string const &filename)
Parse one specific configuration file and process the results.
void parse_configuration_files(int argc=0, char *argv[]=nullptr)
This function checks for arguments in configuration files.
void get_direct_configuration_filenames(string_list_t &names, bool writable) const
Define the list of direct configuration filenames.
option_info::map_by_name_t f_options_by_name
Definition advgetopt.h:225
bool is_defined(std::string const &name) const
Check whether a parameter is defined.
option_info::pointer_t get_option(std::string const &name, bool exact_option=false) const
Retrieve an option by name.
static string_list_t find_config_dir(int argc, char *argv[])
Search for the "--config-dir" option in a set of arguments.
std::string f_program_fullname
Definition advgetopt.h:221
static void add_configuration_filename(string_list_t &names, std::string const &add)
Add one configuration filename to our list.
std::string f_environment_variable
Definition advgetopt.h:228
bool has_flag(flag_t flag) const
Check whether an environment flag is set or not.
static string_list_t split_environment(std::string const &environment)
Transform a string in an array of arguments.
std::string get_output_filename() const
Determine the best suited file for updates.
variables::pointer_t f_variables
Definition advgetopt.h:229
std::shared_ptr< option_info > pointer_t
Definition option_info.h:78
static void set_configuration_filename(std::string const &filename)
Save the filename of the current configuration file.
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
string_list_t insert_group_name(std::string const &filename, char const *group_name, char const *project_name, bool add_default_on_empty)
Insert the group (or project) name in the filename.
Definition utils.cpp:466
constexpr flag_t GETOPT_ENVIRONMENT_FLAG_DYNAMIC_PARAMETERS
Definition options.h:432
static constexpr flag_t GETOPT_FLAG_CONFIGURATION_FILE
Definition flags.h:47
std::string option_with_underscores(std::string const &s)
Converts an option back to using underscores.
Definition utils.cpp:288
static constexpr flag_t GETOPT_FLAG_DYNAMIC
Definition flags.h:79
static constexpr flag_t GETOPT_FLAG_MULTIPLE
Definition flags.h:53
constexpr flag_t GETOPT_ENVIRONMENT_FLAG_SYSTEM_PARAMETERS
Definition options.h:434
std::vector< std::string > string_list_t
Definition utils.h:41
std::string handle_user_directory(std::string const &filename)
Replace a starting ~/... with the contents of the $HOME variable.
Definition utils.cpp:676
std::string default_group_name(std::string const &filename, char const *group_name, char const *project_name, int priority)
Generate the default filename (the ".../50-...")
Definition utils.cpp:585
constexpr char const CONFIGURATION_SECTIONS[]
Definition advgetopt.h:51
char const *const * f_configuration_directories
Definition options.h:451
conf_file_setup const * f_config_setup
Definition options.h:461
char const * f_section_variables_name
Definition options.h:448
char const * f_configuration_filename
Definition options.h:450
char const *const * f_configuration_files
Definition options.h:449

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.