LCOV - code coverage report
Current view: top level - advgetopt - variables.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 82 82 100.0 %
Date: 2024-10-05 13:34:54 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : 
      20             : /** \file
      21             :  * \brief Advanced getopt implementation.
      22             :  *
      23             :  * The advgetopt class and implementation is an advanced library to parse
      24             :  * command line parameters from static definitions specified by the caller.
      25             :  *
      26             :  * The class supports the command line options, options found in a default
      27             :  * configuration file or in a user defined configuration file.
      28             :  *
      29             :  * The class also includes support for displaying error messages and help
      30             :  * information about all the command line arguments.
      31             :  */
      32             : 
      33             : // self
      34             : //
      35             : #include    "advgetopt/variables.h"
      36             : 
      37             : #include    "advgetopt/exception.h"
      38             : 
      39             : 
      40             : // C++
      41             : //
      42             : #include    <iostream>
      43             : 
      44             : 
      45             : // last include
      46             : //
      47             : #include    <snapdev/poison.h>
      48             : 
      49             : 
      50             : 
      51             : namespace advgetopt
      52             : {
      53             : 
      54             : 
      55             : 
      56             : /** \brief Canonicalize the variable name.
      57             :  *
      58             :  * This function canonicalizes the name of a variable.
      59             :  *
      60             :  * This means:
      61             :  *
      62             :  * * Replace a sequence of ':' with '::'.
      63             :  * * Replace a sequence of '.' with '::'.
      64             :  * * Replace a '_' with '-'.
      65             :  *
      66             :  * \note
      67             :  * Variable names that come from a configuration file will already have
      68             :  * been canonicalized, but the user can directly call the get_variable()
      69             :  * and set_variable() functions which will also benefit from this
      70             :  * conversion.
      71             :  *
      72             :  * \exception getopt_invalid
      73             :  * If the variable or one of the section names start with a digit, this
      74             :  * exception is raised. The exception is also raised if we detect an
      75             :  * empty section name (as in "::test" or "double::::scope" or "not..allowed").
      76             :  *
      77             :  * \param[in] name  The name of the variable.
      78             :  */
      79          89 : std::string variables::canonicalize_variable_name(std::string const & name)
      80             : {
      81          89 :     std::string result;
      82             : 
      83          89 :     bool first(true);
      84         911 :     for(char const * n(name.c_str()); *n != '\0'; ++n)
      85             :     {
      86         826 :         if(*n == ':' || *n == '.')
      87             :         {
      88          26 :             if(first)
      89             :             {
      90           2 :                 throw getopt_invalid(
      91             :                       "found an empty section name in \""
      92           2 :                     + name
      93           5 :                     + "\".");
      94             :             }
      95          62 :             while(n[1] == ':' || n[1] == '.')
      96             :             {
      97          37 :                 ++n;
      98             :             }
      99          25 :             result += "::";
     100          25 :             first = true;
     101             :         }
     102             :         else
     103             :         {
     104         800 :             if(first && *n >= '0' && *n <= '9')
     105             :             {
     106           6 :                 throw getopt_invalid(
     107             :                       "a variable name or section name in \""
     108           6 :                     + name
     109          15 :                     + "\" starts with a digit, which is not allowed.");
     110             :             }
     111         797 :             first = false;
     112         797 :             if(*n == '_')
     113             :             {
     114          11 :                 result += '-';
     115             :             }
     116             :             else
     117             :             {
     118         786 :                 result += *n;
     119             :             }
     120             :         }
     121             :     }
     122             : 
     123          85 :     return result;
     124           4 : }
     125             : 
     126             : 
     127             : /** \brief Check whether a variable is defined.
     128             :  *
     129             :  * If you want to verify that a variable is defined before retrieving
     130             :  * it, you can use this function. A variable can be set to the empty
     131             :  * string so checking the returned value of the get_variable() is
     132             :  * not sufficient to know whether the variable is defined or log.
     133             :  *
     134             :  * \param[in] name  The name of the variable to check.
     135             :  *
     136             :  * \return true if the variable is defined.
     137             :  */
     138          14 : bool variables::has_variable(std::string const & name) const
     139             : {
     140          14 :     auto it(f_variables.find(canonicalize_variable_name(name)));
     141          28 :     return it != f_variables.end();
     142             : }
     143             : 
     144             : 
     145             : /** \brief Return the value of the named variable.
     146             :  *
     147             :  * This function searches for the named variable and returns its value
     148             :  * if defined.
     149             :  *
     150             :  * \param[in] name  The name of the variable to retrieve.
     151             :  *
     152             :  * \return The variable value of an empty string.
     153             :  */
     154          41 : std::string variables::get_variable(std::string const & name) const
     155             : {
     156          41 :     auto it(f_variables.find(canonicalize_variable_name(name)));
     157          41 :     if(it != f_variables.end())
     158             :     {
     159          40 :         return it->second;
     160             :     }
     161             : 
     162           1 :     return std::string();
     163             : }
     164             : 
     165             : 
     166             : /** \brief Return a reference to the map of variables.
     167             :  *
     168             :  * This function returns a reference to the whole map of variables.
     169             :  *
     170             :  * The map is composed of named values. The first string is the name of
     171             :  * variables and the second string is the value.
     172             :  *
     173             :  * \note
     174             :  * It is not multi-thread safe since the variable make can be updated at any
     175             :  * time.
     176             :  *
     177             :  * \return The reference to the map of variables.
     178             :  */
     179          16 : variables::variable_t const & variables::get_variables() const
     180             : {
     181          16 :     return f_variables;
     182             : }
     183             : 
     184             : 
     185             : /** \brief Set a variable.
     186             :  *
     187             :  * This function sets a variable in the getopt object.
     188             :  *
     189             :  * The value of variables can be used to replace `${...}` entries in
     190             :  * parameters found on the command line or in configuration files.
     191             :  *
     192             :  * By default, if that variable already existed, then its value gets
     193             :  * replaced (assignment_t::ASSIGNMENT_SET).
     194             :  *
     195             :  * You can use this function to define a default after loading data with:
     196             :  *
     197             :  * \code
     198             :  * vars->set_variable("foo", "default value", assignment_t::ASSIGNMENT_OPTIONAL);
     199             :  * \endcode
     200             :  *
     201             :  * \note
     202             :  * The value of a variable can itself include `${...}` references.
     203             :  * When parsing a parameter for variables, such are replaced recursively.
     204             :  * See process_value() for details.
     205             :  *
     206             :  * \param[in] name  The name of the variable.
     207             :  * \param[in] value  The value of the variable.
     208             :  * \param[in] assignment  The operator to use to set this variable.
     209             :  *
     210             :  * \sa process_value()
     211             :  */
     212          30 : void variables::set_variable(
     213             :       std::string const & name
     214             :     , std::string const & value
     215             :     , assignment_t assignment)
     216             : {
     217          30 :     std::string const var(canonicalize_variable_name(name));
     218          30 :     auto it(f_variables.find(var));
     219          30 :     switch(assignment)
     220             :     {
     221           3 :     case assignment_t::ASSIGNMENT_OPTIONAL:
     222           3 :         if(it == f_variables.end())
     223             :         {
     224           2 :             f_variables[var] = value;
     225             :         }
     226           3 :         break;
     227             : 
     228           5 :     case assignment_t::ASSIGNMENT_APPEND:
     229           5 :         if(it == f_variables.end())
     230             :         {
     231           1 :             f_variables[var] = value;
     232             :         }
     233             :         else
     234             :         {
     235           4 :             f_variables[var] = it->second + value;
     236             :         }
     237           5 :         break;
     238             : 
     239           3 :     case assignment_t::ASSIGNMENT_NEW:
     240           3 :         if(it == f_variables.end())
     241             :         {
     242           2 :             f_variables[var] = value;
     243             :         }
     244             :         else
     245             :         {
     246           2 :             throw getopt_defined_twice(
     247             :                   "variable \""
     248           2 :                 + var
     249           5 :                 + "\" is already defined.");
     250             :         }
     251           2 :         break;
     252             : 
     253             :     //case assignment_t::ASSIGNMENT_NONE:
     254             :     //case assignment_t::ASSIGNMENT_SET:
     255          19 :     default:
     256          19 :         f_variables[var] = value;
     257          19 :         break;
     258             : 
     259             :     }
     260          59 : }
     261             : 
     262             : 
     263             : /** \brief Process variables against a parameter.
     264             :  *
     265             :  * Whenever a parameter is retrieved, its value is passed through this
     266             :  * function and if the variable processing is allowed, it searches for
     267             :  * `${...}` sequances and when such are found, it replaces them with the
     268             :  * corresponding variable content.
     269             :  *
     270             :  * The process is recursive meaning that if a variable includes the `${...}`
     271             :  * sequence, that variable will itself also be replaced.
     272             :  *
     273             :  * The variables can be defined in a `[variables]` section and by the
     274             :  * programmer by calling the set_variable() function.
     275             :  *
     276             :  * \note
     277             :  * This functionality is automatically used when the
     278             :  * SYSTEM_OPTION_PROCESS_VARIABLES flag is set in your environment definition.
     279             :  * If you prefer to have it only function for a few of your parameters, then
     280             :  * do not set the SYSTEM_OPTION_PROCESS_VARIABLES and only call this function
     281             :  * for the few values you want to include variables.
     282             :  *
     283             :  * \todo
     284             :  * Consider having a cache, although for variables that would return system
     285             :  * information, it could change at any time.
     286             :  *
     287             :  * \param[in] value  The parameter value to be processed.
     288             :  *
     289             :  * \return The processed value with the variables updated.
     290             :  */
     291          15 : std::string variables::process_value(std::string const & value) const
     292             : {
     293             :     // to support the recursivity, we call a sub-function which calls itself
     294             :     // whenever a variable is discovered to include another variable; that
     295             :     // recursivity is broken immediately if a variable includes itself;
     296             :     // this function is private
     297             :     //
     298          15 :     variable_names_t names;
     299          30 :     return recursive_process_value(value, names);
     300          15 : }
     301             : 
     302             : 
     303             : /** \brief Internal function processing variables recursively.
     304             :  *
     305             :  * This function goes through value and replaces the `${...}` with the
     306             :  * corresponding variable data. The content of a variable is itself
     307             :  * passed through this process so it is recursive.
     308             :  *
     309             :  * The function records which variables it has worked on so far to
     310             :  * prevent the function from re-adding the same variable (avoid infinite
     311             :  * loop).
     312             :  *
     313             :  * \param[in] value  The value to parse.
     314             :  * \param[in] names  A set of variable names that have already been processed.
     315             :  */
     316          33 : std::string variables::recursive_process_value(
     317             :       std::string const & value
     318             :     , variable_names_t & names) const
     319             : {
     320          33 :     std::string result;
     321             : 
     322         227 :     for(char const * s(value.c_str()); *s != '\0'; ++s)
     323             :     {
     324         195 :         char c(*s);
     325         195 :         if(c == '$' && s[1] == '{') // start variable reference?
     326             :         {
     327          21 :             s += 2;
     328          21 :             char const * name(s);
     329         144 :             for(; *s != '}' && *s != '\0'; ++s);
     330          21 :             if(*s == '\0')
     331             :             {
     332             :                 // invalid variable reference
     333             :                 //
     334           1 :                 result += "${";
     335           1 :                 result += name;
     336           1 :                 return result;
     337             :             }
     338             : 
     339             :             // TODO: add support for conversions like we have in bash
     340             :             //       (i.e. ${var:-extension} ${var%.extension} ...
     341             :             //       see man bash section "Parameter Expansion")
     342             :             //
     343             :             // TODO: add support for emitting errors on an undefined
     344             :             //       variable
     345             :             //
     346          40 :             std::string var(std::string(name, s - name));
     347          20 :             auto allowed(names.insert(var));
     348          20 :             if(allowed.second)
     349             :             {
     350          18 :                 result += recursive_process_value(get_variable(var), names);
     351          18 :                 names.erase(allowed.first);
     352             :             }
     353             :             else
     354             :             {
     355           2 :                 result += "<variable \"" + var + "\" loops>";
     356             :             }
     357          40 :         }
     358             :         else
     359             :         {
     360         174 :             result += c;
     361             :         }
     362             :     }
     363             : 
     364          32 :     return result;
     365             : } // LCOV_EXCL_LINE
     366             : 
     367             : 
     368             : 
     369             : } // namespace advgetopt
     370             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.14

Snap C++ | List of projects | List of versions