advgetopt 2.0.47
Parse complex command line arguments and configuration files in C++.
advgetopt_config.cpp
Go to the documentation of this file.
1// Copyright (c) 2006-2024 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
86 bool exists
87 , bool writable
88 , int argc
89 , char * argv[]) const
90{
91 string_list_t result;
92
93 get_managed_configuration_filenames(result, writable, argc, argv);
94 get_direct_configuration_filenames(result, writable);
95
96 if(!exists)
97 {
98 return result;
99 }
100
101 string_list_t existing_files;
102 int const mode(R_OK | (writable ? W_OK : 0));
103 for(auto r : result)
104 {
105 if(access(r.c_str(), mode) == 0)
106 {
107 existing_files.push_back(r);
108 }
109 }
110
111 return existing_files;
112}
113
114
129void getopt::add_configuration_filename(string_list_t & names, std::string const & add)
130{
131 if(std::find(names.begin(), names.end(), add) == names.end())
132 {
133 names.push_back(add);
134 }
135}
136
137
158 string_list_t & names
159 , bool writable
160 , int argc
161 , char * argv[]) const
162{
165 {
166 return;
167 }
168
169 string_list_t directories;
171 {
172 if(f_parsed)
173 {
174 // WARNING: at this point the command line and environment
175 // variable may not be parsed in full if at all
176 //
177 //if(has_flag(SYSTEM_OPTION_CONFIGURATION_FILENAMES))
178 if(is_defined("config-dir"))
179 {
180 std::size_t const max(size("config-dir"));
181 directories.reserve(max);
182 for(std::size_t idx(0); idx < max; ++idx)
183 {
184 directories.push_back(get_string("config-dir", idx));
185 }
186 }
187 }
188 else
189 {
190 // we've got to do some manual parsing (argh!)
191 //
192 directories = find_config_dir(argc, argv);
193 if(directories.empty())
194 {
196
197 std::vector<char *> sub_argv;
198 sub_argv.resize(args.size() + 2);
199 sub_argv[0] = const_cast<char *>(f_program_fullname.c_str());
200 for(std::size_t idx(0); idx < args.size(); ++idx)
201 {
202 sub_argv[idx + 1] = const_cast<char *>(args[idx].c_str());
203 }
204 sub_argv[args.size() + 1] = nullptr;
205
206 directories = find_config_dir(sub_argv.size() - 1, sub_argv.data());
207 }
208 }
209 }
210
212 {
213 for(char const * const * configuration_directories(f_options_environment.f_configuration_directories);
214 *configuration_directories != nullptr;
215 ++configuration_directories)
216 {
217 directories.push_back(*configuration_directories);
218 }
219 }
220
221 if(directories.empty())
222 {
223 std::string const name(get_group_or_project_name());
224
225 if(!name.empty())
226 {
227 std::string directory_name("/usr/share/advgetopt/options/");
228 directory_name += name;
229 directories.push_back(directory_name);
230
231 directory_name = "/usr/share/";
232 directory_name += name;
233 directory_name += "/options";
234 directories.push_back(directory_name);
235
236 directory_name = "/etc/";
237 directory_name += name;
238 directories.push_back(directory_name);
239 }
240 }
241
242 std::string const filename(f_options_environment.f_configuration_filename);
243
244 for(auto directory : directories)
245 {
246 if(!directory.empty())
247 {
248 std::string const full_filename(directory + ("/" + filename));
249 std::string const user_filename(handle_user_directory(full_filename));
250 if(user_filename == full_filename)
251 {
252 if(!writable)
253 {
254 add_configuration_filename(names, user_filename);
255 }
256
257 string_list_t const with_project_name(insert_group_name(
258 user_filename
261 if(!with_project_name.empty())
262 {
263 for(auto const & n : with_project_name)
264 {
266 }
267 }
268 }
269 else
270 {
271 add_configuration_filename(names, user_filename);
272 }
273 }
274 }
275}
276
277
294 string_list_t & names
295 , bool writable) const
296{
298 {
299 return;
300 }
301
302 // load options from configuration files specified as is by the programmer
303 //
304 for(char const * const * configuration_files(f_options_environment.f_configuration_files);
305 *configuration_files != nullptr;
306 ++configuration_files)
307 {
308 char const * filename(*configuration_files);
309 if(*filename == '\0')
310 {
311 continue;
312 }
313
314 std::string const user_filename(handle_user_directory(filename));
315 if(user_filename == filename)
316 {
317 if(!writable)
318 {
319 add_configuration_filename(names, user_filename);
320 }
321
322 string_list_t const with_project_name(insert_group_name(
323 user_filename
326 if(!with_project_name.empty())
327 {
328 for(auto const & n : with_project_name)
329 {
331 }
332 }
333 }
334 else
335 {
336 add_configuration_filename(names, user_filename);
337 }
338 }
339}
340
341
372{
374 {
375 // check the programmer defined paths as is
376 //
377 char const * found(nullptr);
378 for(char const * const * configuration_files(f_options_environment.f_configuration_files);
379 *configuration_files != nullptr;
380 ++configuration_files)
381 {
382 char const * filename(*configuration_files);
383 if(*filename == '\0')
384 {
385 // ignore empty filenames
386 //
387 continue;
388 }
389
390 if(filename[0] == '~'
391 && (filename[1] == '/' || filename[1] == '\0'))
392 {
393 // ignore user directory entries
394 //
395 continue;
396 }
397
398 // we want the last one, so we are not done once we found one...
399 //
400 found = filename;
401 }
402
403 if(found != nullptr)
404 {
405 return default_group_name(
406 found
409 }
410 }
411
414 {
415 // check the directories either defined by the programmer or if
416 // none defined by the programmer, as defined by advgetopt which
417 // in this case simply means "/etc/"; we ignore the possible
418 // use of the --config-dir because in that case the administrator
419 // knows where to save his file
420 //
421 std::string const name(get_group_or_project_name());
422
423 std::string directory;
425 {
426 for(char const * const * configuration_directories(f_options_environment.f_configuration_directories);
427 *configuration_directories != nullptr;
428 ++configuration_directories)
429 {
430 char const * dir(*configuration_directories);
431 if(dir[0] == '\0')
432 {
433 continue;
434 }
435
436 if(dir[0] == '~'
437 && (dir[1] == '/' || dir[1] == '\0'))
438 {
439 continue;
440 }
441
442 // we want to keep the last entry unless it starts with ~/...
443 //
444 directory = dir;
445 }
446 }
447
448 if(directory.empty())
449 {
450 // no programmer defined directory, use a system defined one
451 // instead
452 //
453 directory = "/etc/" + name;
454 }
455
456 if(directory.back() != '/')
457 {
458 directory += '/';
459 }
460
461 std::string const filename(directory + f_options_environment.f_configuration_filename);
462
463 return default_group_name(
464 filename
467 }
468
469 // the programmer did not define anything, it is likely that no files
470 // will be loaded but we still generate a default name
471 //
472 std::string filename("/etc/");
475 {
477 }
478 else if(f_options_environment.f_project_name != nullptr
480 {
482 }
483 else
484 {
485 // really nothing can be done in this case... we have no name
486 // to generate a valid path/configuration filename
487 //
488 return std::string();
489 }
490
491 filename += '/';
492
495 {
497 }
498 else if(f_options_environment.f_group_name != nullptr
500 {
502 }
503 else
504 {
505 throw getopt_logic_error("we just checked both of those names and at least one was valid."); // LCOV_EXCL_LINE
506 }
507
508 filename += ".conf";
509
510 return filename;
511}
512
513
528 int argc
529 , char * argv[])
530{
531 if(argv == nullptr)
532 {
533 return string_list_t();
534 }
535
536 string_list_t result;
537 for(int idx(1); idx < argc; ++idx)
538 {
539 if(strcmp(argv[idx], "--config-dir") == 0)
540 {
541 for(++idx; idx < argc; ++idx)
542 {
543 if(argv[idx][0] == '-')
544 {
545 --idx;
546 break;
547 }
548 result.push_back(argv[idx]);
549 }
550 }
551 else if(strncmp(argv[idx], "--config-dir=", 13) == 0)
552 {
553 result.push_back(argv[idx] + 13);
554 }
555 }
556
557 return result;
558}
559
560
605void getopt::parse_configuration_files(int argc, char * argv[])
606{
607 string_list_t const filenames(get_configuration_filenames(false, false, argc, argv));
608
609 for(auto f : filenames)
610 {
612 f_parsed = false;
613 }
614
615 f_parsed = true;
616}
617
618
641void getopt::process_configuration_file(std::string const & filename)
642{
644
647 {
648 conf_setup = std::make_shared<conf_file_setup>(filename);
649 }
650 else
651 {
652 conf_setup = std::make_shared<conf_file_setup>(
653 filename
655 }
656 if(!conf_setup->is_valid())
657 {
658 // a non-existant file is considered valid now so this should never
659 // happen; later we may use the flag if we find errors in the file
660 //
661 return; // LCOV_EXCL_LINE
662 }
664
665 // is there a variable section?
666 //
668 {
669 conf->section_to_variables(
671 , f_variables);
672 }
673
674 conf_file::sections_t sections(conf->get_sections());
675 if(!sections.empty())
676 {
677 std::string const name(CONFIGURATION_SECTIONS);
678 option_info::pointer_t configuration_sections(get_option(name));
679 if(configuration_sections == nullptr)
680 {
681 configuration_sections = std::make_shared<option_info>(name);
682 configuration_sections->add_flag(
685 f_options_by_name[configuration_sections->get_name()] = configuration_sections;
686 }
687 else if(!configuration_sections->has_flag(GETOPT_FLAG_MULTIPLE))
688 {
689 cppthread::log << cppthread::log_level_t::error
690 << "option \""
691 << name
692 << "\" must have GETOPT_FLAG_MULTIPLE set."
693 << cppthread::end;
694 return;
695 }
696 for(auto s : sections)
697 {
698 if(!configuration_sections->has_value(s))
699 {
700 configuration_sections->add_value(
701 s
702 , string_list_t()
704 }
705 }
706 }
707
708 for(auto const & param : conf->get_parameters())
709 {
710 // in configuration files we only allow long arguments
711 //
712 option_info::pointer_t opt(get_option(param.first));
713 if(opt == nullptr)
714 {
716 || param.first.length() == 1)
717 {
718 cppthread::log << cppthread::log_level_t::error
719 << "unknown option \""
720 << option_with_underscores(param.first)
721 << "\" found in configuration file \""
722 << filename
723 << "\" on line "
724 << param.second.get_line()
725 << "."
726 << cppthread::end;
727 continue;
728 }
729 else
730 {
731 // add a new parameter dynamically
732 //
733 opt = std::make_shared<option_info>(param.first);
734 opt->set_variables(f_variables);
735
737
738 // consider the first definition as the default
739 // (which is likely in our environment)
740 //
741 opt->set_default(param.second);
742
743 f_options_by_name[opt->get_name()] = opt;
744 }
745 }
746 else
747 {
748 if(!opt->has_flag(GETOPT_FLAG_CONFIGURATION_FILE))
749 {
750 // in configuration files we are expected to use '_' so
751 // print an error with such
752 //
753 cppthread::log << cppthread::log_level_t::error
754 << "option \""
755 << option_with_underscores(param.first)
756 << "\" is not supported in configuration files (found in \""
757 << filename
758 << "\")."
759 << cppthread::end;
760 continue;
761 }
762 }
763
764 std::string value(param.second.get_value());
765 switch(param.second.get_assignment_operator())
766 {
769 // nothing special in this case, just overwrite if already defined
770 //
771 break;
772
774 if(opt->is_defined())
775 {
776 // already set, do not overwrite
777 //
778 continue;
779 }
780 break;
781
783 if(opt->is_defined()
784 && !opt->has_flag(GETOPT_FLAG_MULTIPLE))
785 {
786 // append the new value
787 //
788 value = opt->get_value() + value;
789 }
790 break;
791
793 if(opt->is_defined())
794 {
795 // prevent re-assignment
796 //
797 cppthread::log << cppthread::log_level_t::error
798 << "option \""
799 << option_with_underscores(param.first)
800 << "\" found in configuration file \""
801 << filename
802 << "\" on line "
803 << param.second.get_line()
804 << " uses the := operator but the value is already defined."
805 << cppthread::end;
806 continue;
807 }
808 break;
809
810 }
811
813 opt
814 , value
815 , filename
816 , string_list_t()
818 }
819
820 f_parsed = true;
821}
822
823
824
825
826
827
828} // namespace advgetopt
829// 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.
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.