LCOV - code coverage report
Current view: top level - advgetopt - advgetopt_config.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 142 142 100.0 %
Date: 2022-05-26 21:41:34 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2006-2022  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             : 
      27             : // self
      28             : //
      29             : #include    "advgetopt/advgetopt.h"
      30             : 
      31             : // advgetopt lib
      32             : //
      33             : #include    "advgetopt/conf_file.h"
      34             : 
      35             : 
      36             : // cppthread lib
      37             : //
      38             : #include    <cppthread/log.h>
      39             : 
      40             : // boost lib
      41             : //
      42             : #include    <boost/algorithm/string/join.hpp>
      43             : #include    <boost/algorithm/string/replace.hpp>
      44             : 
      45             : 
      46             : // last include
      47             : //
      48             : #include    <snapdev/poison.h>
      49             : 
      50             : 
      51             : namespace advgetopt
      52             : {
      53             : 
      54             : 
      55             : 
      56             : 
      57             : /** \brief Generate a list of configuration filenames.
      58             :  *
      59             :  * This function goes through the list of filenames and directories and
      60             :  * generates a complete list of all the configuration files that the
      61             :  * system will load when you call the parse_configuration_files()
      62             :  * function.
      63             :  *
      64             :  * Set the flag \p exists to true if you only want the name of files
      65             :  * that currently exists.
      66             :  *
      67             :  * The \p writable file means that we only want files under the
      68             :  * \<project-name>.d folder and the user configuration folder.
      69             :  *
      70             :  * \note
      71             :  * The argc/argv and environment variable parameters are used whenever the
      72             :  * function is called early and we can't call is_defined(). These are
      73             :  * ignored otherwise.
      74             :  *
      75             :  * \param[in] exists  Remove files that do not exist from the list.
      76             :  * \param[in] writable  Only return files we consider writable.
      77             :  * \param[in] argc  The number of arguments in argv.
      78             :  * \param[in] argv  The arguments passed to the finish_parsing() function or
      79             :  * nullptr.
      80             :  * \param[in] environment_variable  The environment variable or an empty string.
      81             :  *
      82             :  * \return The list of configuration filenames.
      83             :  */
      84         351 : string_list_t getopt::get_configuration_filenames(
      85             :           bool exists
      86             :         , bool writable
      87             :         , int argc
      88             :         , char * argv[]) const
      89             : {
      90         702 :     string_list_t result;
      91             : 
      92         351 :     if(f_options_environment.f_configuration_files != nullptr)
      93             :     {
      94             :         // load options from configuration files specified as is by caller
      95             :         //
      96         455 :         for(char const * const * configuration_files(f_options_environment.f_configuration_files)
      97         455 :           ; *configuration_files != nullptr
      98             :           ; ++configuration_files)
      99             :         {
     100         352 :             char const * filename(*configuration_files);
     101         352 :             if(*filename != '\0')
     102             :             {
     103         704 :                 std::string const user_filename(handle_user_directory(filename));
     104         352 :                 if(user_filename == filename)
     105             :                 {
     106         345 :                     if(!writable)
     107             :                     {
     108         203 :                         result.push_back(user_filename);
     109             :                     }
     110             : 
     111         345 :                     string_list_t const with_project_name(insert_group_name(
     112             :                                   user_filename
     113         345 :                                 , f_options_environment.f_group_name
     114        1035 :                                 , f_options_environment.f_project_name));
     115         345 :                     if(!with_project_name.empty())
     116             :                     {
     117         690 :                         result.insert(
     118         690 :                                   result.end()
     119             :                                 , with_project_name.begin()
     120        1380 :                                 , with_project_name.end());
     121             :                     }
     122             :                 }
     123             :                 else
     124             :                 {
     125           7 :                     result.push_back(user_filename);
     126             :                 }
     127             :             }
     128             :         }
     129             :     }
     130             : 
     131         351 :     if(f_options_environment.f_configuration_filename != nullptr)
     132             :     {
     133         320 :         string_list_t directories;
     134         160 :         if(has_flag(GETOPT_ENVIRONMENT_FLAG_SYSTEM_PARAMETERS))
     135             :         {
     136          13 :             if(f_parsed)
     137             :             {
     138             :                 // WARNING: at this point the command line and environment
     139             :                 //          variable may not be parsed in full if at all
     140             :                 //
     141             :                 //if(has_flag(SYSTEM_OPTION_CONFIGURATION_FILENAMES))
     142           3 :                 if(is_defined("config-dir"))
     143             :                 {
     144           2 :                     size_t const max(size("config-dir"));
     145           6 :                     for(size_t idx(0); idx < max; ++idx)
     146             :                     {
     147           4 :                         directories.push_back(get_string("config-dir", idx));
     148             :                     }
     149             :                 }
     150             :             }
     151             :             else
     152             :             {
     153             :                 // we've got to do some manual parsing (argh!)
     154             :                 //
     155          10 :                 directories = find_config_dir(argc, argv);
     156          10 :                 if(directories.empty())
     157             :                 {
     158          12 :                     string_list_t args(split_environment(f_environment_variable));
     159             : 
     160          12 :                     std::vector<char *> sub_argv;
     161           6 :                     sub_argv.resize(args.size() + 2);
     162           6 :                     sub_argv[0] = const_cast<char *>(f_program_fullname.c_str());
     163          17 :                     for(size_t idx(0); idx < args.size(); ++idx)
     164             :                     {
     165          11 :                         sub_argv[idx + 1] = const_cast<char *>(args[idx].c_str());
     166             :                     }
     167           6 :                     sub_argv[args.size() + 1] = nullptr;
     168             :                     
     169           6 :                     directories = find_config_dir(sub_argv.size() - 1, sub_argv.data());
     170             :                 }
     171             :             }
     172             :         }
     173             : 
     174         160 :         if(f_options_environment.f_configuration_directories != nullptr)
     175             :         {
     176         260 :             for(char const * const * configuration_directories(f_options_environment.f_configuration_directories)
     177         260 :               ; *configuration_directories != nullptr
     178             :               ; ++configuration_directories)
     179             :             {
     180         204 :                 directories.push_back(*configuration_directories);
     181             :             }
     182             :         }
     183             : 
     184         320 :         std::string const filename(f_options_environment.f_configuration_filename);
     185             : 
     186         375 :         for(auto directory : directories)
     187             :         {
     188         215 :             if(!directory.empty())
     189             :             {
     190         430 :                 std::string const full_filename(directory + ("/" + filename));
     191         430 :                 std::string const user_filename(handle_user_directory(full_filename));
     192         215 :                 if(user_filename == full_filename)
     193             :                 {
     194         168 :                     if(!writable)
     195             :                     {
     196         108 :                         result.push_back(user_filename);
     197             :                     }
     198             : 
     199         336 :                     string_list_t const with_project_name(insert_group_name(user_filename, f_options_environment.f_group_name, f_options_environment.f_project_name));
     200         168 :                     if(!with_project_name.empty())
     201             :                     {
     202         336 :                         result.insert(
     203         336 :                                   result.end()
     204             :                                 , with_project_name.begin()
     205         672 :                                 , with_project_name.end());
     206             :                     }
     207             :                 }
     208             :                 else
     209             :                 {
     210          47 :                     result.push_back(user_filename);
     211             :                 }
     212             :             }
     213             :         }
     214             :     }
     215             : 
     216         351 :     if(!exists)
     217             :     {
     218         303 :         return result;
     219             :     }
     220             : 
     221          96 :     string_list_t existing_files;
     222          48 :     int const mode(R_OK | (writable ? W_OK : 0));
     223         404 :     for(auto r : result)
     224             :     {
     225         356 :         if(access(r.c_str(), mode) == 0)
     226             :         {
     227          26 :             existing_files.push_back(r);
     228             :         }
     229             :     }
     230          48 :     return existing_files;
     231             : }
     232             : 
     233             : 
     234             : /** \brief Search for the "--config-dir" option in a set of arguments.
     235             :  *
     236             :  * This function searches the given list of \p argv arguments for the
     237             :  * "--config-dir".
     238             :  *
     239             :  * This is done that way because we prematurely need that information
     240             :  * in order to properly search for the configuration file. This is because
     241             :  * the "--config-dir" is not yet defined when we attempt to read the
     242             :  * user specific configuration file.
     243             :  *
     244             :  * \param[in] argc  The number of arguments.
     245             :  * \param[in] argv  The list of arguments to be searched.
     246             :  */
     247          16 : string_list_t getopt::find_config_dir(
     248             :           int argc
     249             :         , char * argv[])
     250             : {
     251          16 :     if(argv == nullptr)
     252             :     {
     253           2 :         return string_list_t();
     254             :     }
     255             : 
     256          28 :     string_list_t result;
     257          56 :     for(int idx(1); idx < argc; ++idx)
     258             :     {
     259          42 :         if(strcmp(argv[idx], "--config-dir") == 0)
     260             :         {
     261          10 :             for(++idx; idx < argc; ++idx)
     262             :             {
     263           7 :                 if(argv[idx][0] == '-')
     264             :                 {
     265           2 :                     --idx;
     266           2 :                     break;
     267             :                 }
     268           5 :                 result.push_back(argv[idx]);
     269             :             }
     270             :         }
     271          37 :         else if(strncmp(argv[idx], "--config-dir=", 13) == 0)
     272             :         {
     273           2 :             result.push_back(argv[idx] + 13);
     274             :         }
     275             :     }
     276             : 
     277          14 :     return result;
     278             : }
     279             : 
     280             : 
     281             : /** \brief This function checks for arguments in configuration files.
     282             :  *
     283             :  * Each configuration file is checked one after another. Each file that is
     284             :  * defined is loaded and each line is viewed as an option. If valid, it is
     285             :  * added to the resulting getopt list of options.
     286             :  *
     287             :  * Note that it is an error to define a command in a configuration file. If
     288             :  * that happens, an error occurs and the process stops. Technically this is
     289             :  * defined with the GETOPT_FLAG_CONFIGURATION_FILE flag in your opt table.
     290             :  *
     291             :  * The list of files is checked from beginning to end. So if a later file
     292             :  * changes an option of an earlier file, it is the one effective.
     293             :  *
     294             :  * The configuration file loader supports a project name as defined in the
     295             :  * get_project_name() function. It allows for a sub-directory to
     296             :  * be inserted between the path and the basename of the configuration
     297             :  * file. This allows for a file to be search in an extra sub-directory
     298             :  * so one can avoid changing the original definitions and only use
     299             :  * configuration files in the sub-directory. The path looks like this
     300             :  * when a project name is specified:
     301             :  *
     302             :  * \code
     303             :  *      <path>/<project name>.d/<basename>
     304             :  * \endcode
     305             :  *
     306             :  * Notice that we add a ".d" as usual in other projects under Linux.
     307             :  *
     308             :  * \exception getopt_exception_invalid
     309             :  * This function generates the getopt_exception_invalid exception whenever
     310             :  * something invalid is found in the list of options passed as the \p opts
     311             :  * parameter.
     312             :  *
     313             :  * \exception getopt_exception_default
     314             :  * The function detects whether two options are marked as the default
     315             :  * option (the one receiving parameters that are not used by another command
     316             :  * or match a command.) This exception is raised when such is detected.
     317             :  *
     318             :  * \param[in] argc  The number of arguments in argv.
     319             :  * \param[in] argv  The arguments passed to the finish_parsing() function.
     320             :  *
     321             :  * \sa process_configuration_file()
     322             :  * \sa get_configuration_filenames()
     323             :  * \sa finish_parsing()
     324             :  */
     325         257 : void getopt::parse_configuration_files(int argc, char * argv[])
     326             : {
     327         514 :     string_list_t const filenames(get_configuration_filenames(false, false, argc, argv));
     328             : 
     329         490 :     for(auto f : filenames)
     330             :     {
     331         233 :         process_configuration_file(f);
     332         233 :         f_parsed = false;
     333             :     }
     334             : 
     335         257 :     f_parsed = true;
     336         257 : }
     337             : 
     338             : 
     339             : /** \brief Parse one specific configuration file and process the results.
     340             :  *
     341             :  * This function reads one specific configuration file using a conf_file
     342             :  * object and then goes through the resulting arguments and add them to
     343             :  * the options of this getopt object.
     344             :  *
     345             :  * The options found in the configuration file must match an option by
     346             :  * its long name. In a configuration file, it is not allowed to have an
     347             :  * option which name is only one character.
     348             :  *
     349             :  * \note
     350             :  * If the filename points to a file which can't be read or does not exist,
     351             :  * then nothing happens and the function returns without an error.
     352             :  *
     353             :  * \todo
     354             :  * Extend the support by having the various flags that the conf_file
     355             :  * class supports appear in the list of configuration filenames.
     356             :  *
     357             :  * \param[in] filename  The name of the configuration file to check out.
     358             :  *
     359             :  * \sa parse_configuration_files()
     360             :  */
     361         244 : void getopt::process_configuration_file(std::string const & filename)
     362             : {
     363         244 :     option_info::set_configuration_filename(filename);
     364             : 
     365         487 :     conf_file_setup conf_setup(filename);
     366         244 :     if(!conf_setup.is_valid())
     367             :     {
     368             :         // a non-existant file is considered valid now so this should never
     369             :         // happen; later we may use the flag if we find errors in the file
     370             :         //
     371             :         return; // LCOV_EXCL_LINE
     372             :     }
     373         487 :     conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
     374             : 
     375         487 :     conf_file::sections_t sections(conf->get_sections());
     376             : 
     377             :     // is there a variable section?
     378             :     //
     379         244 :     if(f_options_environment.f_section_variables_name != nullptr)
     380             :     {
     381           2 :         conf->section_to_variables(
     382             :                       f_options_environment.f_section_variables_name
     383             :                     , f_variables);
     384             :     }
     385             : 
     386         244 :     if(!sections.empty())
     387             :     {
     388          11 :         std::string const name(CONFIGURATION_SECTIONS);
     389          11 :         option_info::pointer_t configuration_sections(get_option(name));
     390           6 :         if(configuration_sections == nullptr)
     391             :         {
     392           3 :             configuration_sections = std::make_shared<option_info>(name);
     393           3 :             configuration_sections->add_flag(
     394             :                           GETOPT_FLAG_MULTIPLE
     395             :                         | GETOPT_FLAG_CONFIGURATION_FILE
     396             :                         );
     397           3 :             f_options_by_name[configuration_sections->get_name()] = configuration_sections;
     398             :         }
     399           3 :         else if(!configuration_sections->has_flag(GETOPT_FLAG_MULTIPLE))
     400             :         {
     401           2 :             cppthread::log << cppthread::log_level_t::error
     402           1 :                            << "option \""
     403           1 :                            << name
     404           1 :                            << "\" must have GETOPT_FLAG_MULTIPLE set."
     405           2 :                            << cppthread::end;
     406           1 :             return;
     407             :         }
     408          16 :         for(auto s : sections)
     409             :         {
     410          11 :             if(!configuration_sections->has_value(s))
     411             :             {
     412           7 :                 configuration_sections->add_value(s, option_source_t::SOURCE_CONFIGURATION);
     413             :             }
     414             :         }
     415             :     }
     416             : 
     417         343 :     for(auto const & param : conf->get_parameters())
     418             :     {
     419             :         // in configuration files we only allow long arguments
     420             :         //
     421         196 :         option_info::pointer_t opt(get_option(param.first));
     422         100 :         if(opt == nullptr)
     423             :         {
     424          13 :             if(!has_flag(GETOPT_ENVIRONMENT_FLAG_DYNAMIC_PARAMETERS)
     425           5 :             || param.first.length() == 1)
     426             :             {
     427           6 :                 cppthread::log << cppthread::log_level_t::error
     428           3 :                                << "unknown option \""
     429           6 :                                << boost::replace_all_copy(param.first, "-", "_")
     430           3 :                                << "\" found in configuration file \""
     431           3 :                                << filename
     432           3 :                                << "\" on line "
     433           6 :                                << param.second.get_line()
     434           3 :                                << "."
     435          12 :                                << cppthread::end;
     436           3 :                 continue;
     437             :             }
     438             :             else
     439             :             {
     440             :                 // add a new parameter dynamically
     441             :                 //
     442           2 :                 opt = std::make_shared<option_info>(param.first);
     443           2 :                 opt->set_variables(f_variables);
     444             : 
     445           2 :                 opt->set_flags(GETOPT_FLAG_CONFIGURATION_FILE | GETOPT_FLAG_DYNAMIC);
     446             : 
     447             :                 // consider the first definition as the default
     448             :                 // (which is likely in our environment)
     449             :                 //
     450           2 :                 opt->set_default(param.second);
     451             : 
     452           2 :                 f_options_by_name[opt->get_name()] = opt;
     453             :             }
     454             :         }
     455             :         else
     456             :         {
     457          96 :             if(!opt->has_flag(GETOPT_FLAG_CONFIGURATION_FILE))
     458             :             {
     459             :                 // in configuration files we are expected to use '_' so
     460             :                 // print an error with such
     461             :                 //
     462           2 :                 cppthread::log << cppthread::log_level_t::error
     463           1 :                                << "option \""
     464           2 :                                << boost::replace_all_copy(param.first, "-", "_")
     465           1 :                                << "\" is not supported in configuration files (found in \""
     466           1 :                                << filename
     467           1 :                                << "\")."
     468           3 :                                << cppthread::end;
     469           1 :                 continue;
     470             :             }
     471             :         }
     472             : 
     473          96 :         if(opt != nullptr)
     474             :         {
     475          96 :             add_option_from_string(
     476             :                       opt
     477             :                     , param.second
     478             :                     , filename
     479             :                     , option_source_t::SOURCE_CONFIGURATION);
     480             :         }
     481             :     }
     482             : 
     483         243 :     f_parsed = true;
     484             : }
     485             : 
     486             : 
     487             : 
     488             : 
     489             : 
     490             : 
     491           6 : } // namespace advgetopt
     492             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13