LCOV - code coverage report
Current view: top level - advgetopt - advgetopt_config.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 137 137 100.0 %
Date: 2021-09-08 17:05:25 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13