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