LCOV - code coverage report
Current view: top level - advgetopt - advgetopt_config.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 106 106 100.0 %
Date: 2020-11-13 17:54:34 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * License:
       3             :  *    Copyright (c) 2006-2019  Made to Order Software Corp.  All Rights Reserved
       4             :  *
       5             :  *    https://snapwebsites.org/
       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             :  * \param[in] exists  Remove files that do not exist from the list.
      78             :  * \param[in] writable  Only return files we consider writable.
      79             :  *
      80             :  * \return The list of configuration filenames.
      81             :  */
      82         334 : string_list_t getopt::get_configuration_filenames(bool exists, bool writable) const
      83             : {
      84         668 :     string_list_t result;
      85             : 
      86         334 :     if(f_options_environment.f_configuration_files != nullptr)
      87             :     {
      88             :         // load options from configuration files specified as is by caller
      89             :         //
      90         453 :         for(char const * const * configuration_files(f_options_environment.f_configuration_files)
      91         453 :           ; *configuration_files != nullptr
      92             :           ; ++configuration_files)
      93             :         {
      94         351 :             char const * filename(*configuration_files);
      95         351 :             if(*filename != '\0')
      96             :             {
      97         702 :                 std::string const user_filename(handle_user_directory(filename));
      98         351 :                 if(user_filename == filename)
      99             :                 {
     100         344 :                     if(!writable)
     101             :                     {
     102         202 :                         result.push_back(user_filename);
     103             :                     }
     104             : 
     105         688 :                     string_list_t const with_project_name(insert_group_name(user_filename, f_options_environment.f_group_name, f_options_environment.f_project_name));
     106         344 :                     if(!with_project_name.empty())
     107             :                     {
     108         344 :                         result.insert(
     109         688 :                                   result.end()
     110             :                                 , with_project_name.begin()
     111        1032 :                                 , with_project_name.end());
     112             :                     }
     113             :                 }
     114             :                 else
     115             :                 {
     116           7 :                     result.push_back(user_filename);
     117             :                 }
     118             :             }
     119             :         }
     120             :     }
     121             : 
     122         334 :     if(f_options_environment.f_configuration_filename != nullptr)
     123             :     {
     124         316 :         string_list_t directories;
     125         158 :         if(has_flag(GETOPT_ENVIRONMENT_FLAG_SYSTEM_PARAMETERS))
     126             :         {
     127          11 :             if(is_defined("config-dir"))
     128             :             {
     129           2 :                 size_t const max(size("config-dir"));
     130           6 :                 for(size_t idx(0); idx < max; ++idx)
     131             :                 {
     132           4 :                     directories.push_back(get_string("config-dir", idx));
     133             :                 }
     134             :             }
     135             :         }
     136             : 
     137         158 :         if(f_options_environment.f_configuration_directories != nullptr)
     138             :         {
     139         260 :             for(char const * const * configuration_directories(f_options_environment.f_configuration_directories)
     140         260 :               ; *configuration_directories != nullptr
     141             :               ; ++configuration_directories)
     142             :             {
     143         204 :                 directories.push_back(*configuration_directories);
     144             :             }
     145             :         }
     146             : 
     147         316 :         std::string const filename(f_options_environment.f_configuration_filename);
     148             : 
     149         366 :         for(auto directory : directories)
     150             :         {
     151         208 :             if(!directory.empty())
     152             :             {
     153         416 :                 std::string const full_filename(directory + ("/" + filename));
     154         416 :                 std::string const user_filename(handle_user_directory(full_filename));
     155         208 :                 if(user_filename == full_filename)
     156             :                 {
     157         161 :                     if(!writable)
     158             :                     {
     159         101 :                         result.push_back(user_filename);
     160             :                     }
     161             : 
     162         322 :                     string_list_t const with_project_name(insert_group_name(user_filename, f_options_environment.f_group_name, f_options_environment.f_project_name));
     163         161 :                     if(!with_project_name.empty())
     164             :                     {
     165         161 :                         result.insert(
     166         322 :                                   result.end()
     167             :                                 , with_project_name.begin()
     168         483 :                                 , with_project_name.end());
     169             :                     }
     170             :                 }
     171             :                 else
     172             :                 {
     173          47 :                     result.push_back(user_filename);
     174             :                 }
     175             :             }
     176             :         }
     177             :     }
     178             : 
     179         334 :     if(!exists)
     180             :     {
     181         286 :         return result;
     182             :     }
     183             : 
     184          96 :     string_list_t existing_files;
     185          48 :     int const mode(R_OK | (writable ? W_OK : 0));
     186         404 :     for(auto r : result)
     187             :     {
     188         356 :         if(access(r.c_str(), mode) == 0)
     189             :         {
     190          26 :             existing_files.push_back(r);
     191             :         }
     192             :     }
     193          48 :     return existing_files;
     194             : }
     195             : 
     196             : 
     197             : /** \brief This function checks for arguments in configuration files.
     198             :  *
     199             :  * Each configuration file is checked one after another. Each file that is
     200             :  * defined is loaded and each line is viewed as an option. If valid, it is
     201             :  * added to the resulting getopt list of options.
     202             :  *
     203             :  * Note that it is an error to define a command in a configuration file. If
     204             :  * that happens, an error occurs and the process stops. Technically this is
     205             :  * defined with the GETOPT_FLAG_CONFIGURATION_FILE flag in your opt table.
     206             :  *
     207             :  * The list of files is checked from beginning to end. So if a later file
     208             :  * changes an option of an earlier file, it is the one effective.
     209             :  *
     210             :  * The configuration file loader supports a project name as defined in the
     211             :  * get_project_name() function. It allows for a sub-directory to
     212             :  * be inserted between the path and the basename of the configuration
     213             :  * file. This allows for a file to be search in an extra sub-directory
     214             :  * so one can avoid changing the original definitions and only use
     215             :  * configuration files in the sub-directory. The path looks like this
     216             :  * when a project name is specified:
     217             :  *
     218             :  * \code
     219             :  *      <path>/<project name>.d/<basename>
     220             :  * \endcode
     221             :  *
     222             :  * Notice that we add a ".d" as usual in other projects under Linux.
     223             :  *
     224             :  * \exception getopt_exception_invalid
     225             :  * This function generates the getopt_exception_invalid exception whenever
     226             :  * something invalid is found in the list of options passed as the \p opts
     227             :  * parameter.
     228             :  *
     229             :  * \exception getopt_exception_default
     230             :  * The function detects whether two options are marked as the default
     231             :  * option (the one receiving parameters that are not used by another command
     232             :  * or match a command.) This exception is raised when such is detected.
     233             :  *
     234             :  * \sa process_configuration_file()
     235             :  */
     236         240 : void getopt::parse_configuration_files()
     237             : {
     238         480 :     string_list_t const filenames(get_configuration_filenames(false, false));
     239             : 
     240         462 :     for(auto f : filenames)
     241             :     {
     242         222 :         process_configuration_file(f);
     243             :     }
     244         240 : }
     245             : 
     246             : 
     247             : /** \brief Parse one specific configuration file and process the results.
     248             :  *
     249             :  * This function reads one specific configuration file using a conf_file
     250             :  * object and then goes through the resulting arguments and add them to
     251             :  * the options of this getopt object.
     252             :  *
     253             :  * The options found in the configuration file must match an option by
     254             :  * its long name. In a configuration file, it is not allowed to have an
     255             :  * option which name is only one character.
     256             :  *
     257             :  * \note
     258             :  * If the filename points to a file which can't be read or does not exist,
     259             :  * then nothing happens and the function returns without an error.
     260             :  *
     261             :  * \todo
     262             :  * Extend the support by having the various flags that the conf_file
     263             :  * class supports appear in the list of configuration filenames.
     264             :  *
     265             :  * \param[in] filename  The name of the configuration file to check out.
     266             :  *
     267             :  * \sa parse_configuration_files()
     268             :  */
     269         231 : void getopt::process_configuration_file(std::string const & filename)
     270             : {
     271         461 :     conf_file_setup conf_setup(filename);
     272         231 :     if(!conf_setup.is_valid())
     273             :     {
     274             :         // a non-existant file is considered valid now so this should never
     275             :         // happen; later we may use the flag if we find errors in the file
     276             :         //
     277             :         return; // LCOV_EXCL_LINE
     278             :     }
     279         461 :     conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
     280             : 
     281         461 :     conf_file::sections_t sections(conf->get_sections());
     282         231 :     if(!sections.empty())
     283             :     {
     284           9 :         std::string const name(CONFIGURATION_SECTIONS);
     285           9 :         option_info::pointer_t configuration_sections(get_option(name));
     286           5 :         if(configuration_sections == nullptr)
     287             :         {
     288           2 :             configuration_sections = std::make_shared<option_info>(name);
     289           2 :             configuration_sections->add_flag(
     290             :                           GETOPT_FLAG_MULTIPLE
     291             :                         | GETOPT_FLAG_CONFIGURATION_FILE
     292             :                         );
     293           2 :             f_options_by_name[configuration_sections->get_name()] = configuration_sections;
     294             :         }
     295           3 :         else if(!configuration_sections->has_flag(GETOPT_FLAG_MULTIPLE))
     296             :         {
     297           2 :             cppthread::log << cppthread::log_level_t::error
     298           1 :                            << "option \""
     299           1 :                            << name
     300           1 :                            << "\" must have GETOPT_FLAG_MULTIPLE set."
     301           1 :                            << cppthread::end;
     302           1 :             return;
     303             :         }
     304          13 :         for(auto s : sections)
     305             :         {
     306           9 :             if(!configuration_sections->has_value(s))
     307             :             {
     308           5 :                 configuration_sections->add_value(s);
     309             :             }
     310             :         }
     311             :     }
     312             : 
     313         309 :     for(auto const & param : conf->get_parameters())
     314             :     {
     315             :         // in configuration files we only allow long arguments
     316             :         //
     317         154 :         option_info::pointer_t opt(get_option(param.first));
     318          79 :         if(opt == nullptr)
     319             :         {
     320          13 :             if(!has_flag(GETOPT_ENVIRONMENT_FLAG_DYNAMIC_PARAMETERS)
     321           5 :             || param.first.length() == 1)
     322             :             {
     323           6 :                 cppthread::log << cppthread::log_level_t::error
     324           3 :                                << "unknown option \""
     325           6 :                                << boost::replace_all_copy(param.first, "-", "_")
     326           3 :                                << "\" found in configuration file \""
     327           3 :                                << filename
     328           3 :                                << "\"."
     329           3 :                                << cppthread::end;
     330           3 :                 continue;
     331             :             }
     332             :             else
     333             :             {
     334             :                 // add a new parameter dynamically
     335             :                 //
     336           2 :                 opt = std::make_shared<option_info>(param.first);
     337             : 
     338           2 :                 opt->set_flags(GETOPT_FLAG_CONFIGURATION_FILE | GETOPT_FLAG_DYNAMIC);
     339             : 
     340             :                 // consider the first definition as the default
     341             :                 // (which is likely in our environment)
     342             :                 //
     343           2 :                 opt->set_default(param.second);
     344             : 
     345           2 :                 f_options_by_name[opt->get_name()] = opt;
     346             :             }
     347             :         }
     348             :         else
     349             :         {
     350          75 :             if(!opt->has_flag(GETOPT_FLAG_CONFIGURATION_FILE))
     351             :             {
     352             :                 // in configuration files we are expected to use '_' so
     353             :                 // print an error with such
     354             :                 //
     355           2 :                 cppthread::log << cppthread::log_level_t::error
     356           1 :                                << "option \""
     357           2 :                                << boost::replace_all_copy(param.first, "-", "_")
     358           1 :                                << "\" is not supported in configuration files (found in \""
     359           1 :                                << filename
     360           1 :                                << "\")."
     361           1 :                                << cppthread::end;
     362           1 :                 continue;
     363             :             }
     364             :         }
     365             : 
     366          75 :         if(opt != nullptr)
     367             :         {
     368          75 :             add_option_from_string(opt, param.second, filename);
     369             :         }
     370             :     }
     371             : }
     372             : 
     373             : 
     374             : 
     375             : 
     376             : 
     377             : 
     378           6 : } // namespace advgetopt
     379             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13