LCOV - code coverage report
Current view: top level - advgetopt - advgetopt_options.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 156 156 100.0 %
Date: 2019-07-15 03:11:49 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * File:
       3             :  *    advgetopt/advgetopt_options.cpp -- advanced get option implementation
       4             :  *
       5             :  * License:
       6             :  *    Copyright (c) 2006-2019  Made to Order Software Corp.  All Rights Reserved
       7             :  *
       8             :  *    https://snapwebsites.org/
       9             :  *    contact@m2osw.com
      10             :  *
      11             :  *    This program is free software; you can redistribute it and/or modify
      12             :  *    it under the terms of the GNU General Public License as published by
      13             :  *    the Free Software Foundation; either version 2 of the License, or
      14             :  *    (at your option) any later version.
      15             :  *
      16             :  *    This program is distributed in the hope that it will be useful,
      17             :  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
      18             :  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      19             :  *    GNU General Public License for more details.
      20             :  *
      21             :  *    You should have received a copy of the GNU General Public License along
      22             :  *    with this program; if not, write to the Free Software Foundation, Inc.,
      23             :  *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      24             :  *
      25             :  * Authors:
      26             :  *    Alexis Wilke   alexis@m2osw.com
      27             :  *    Doug Barbieri  doug@m2osw.com
      28             :  */
      29             : 
      30             : /** \file
      31             :  * \brief Advanced getopt data access implementation.
      32             :  *
      33             :  * The advgetopt class has many function used to access the data in the
      34             :  * class. These functions are gathered here.
      35             :  *
      36             :  * This file is covered by the following tests:
      37             :  *
      38             :  * \li options_parser
      39             :  * \li invalid_options_parser
      40             :  * \li valid_options_files
      41             :  * \li invalid_options_files
      42             :  */
      43             : 
      44             : // self
      45             : //
      46             : #include    "advgetopt/advgetopt.h"
      47             : 
      48             : 
      49             : // advgetopt lib
      50             : //
      51             : #include    "advgetopt/conf_file.h"
      52             : #include    "advgetopt/exception.h"
      53             : #include    "advgetopt/log.h"
      54             : 
      55             : 
      56             : // last include
      57             : //
      58             : #include <snapdev/poison.h>
      59             : 
      60             : 
      61             : 
      62             : 
      63             : namespace advgetopt
      64             : {
      65             : 
      66             : 
      67             : 
      68             : 
      69             : 
      70             : 
      71             : 
      72             : /** \brief Reset all the options.
      73             :  *
      74             :  * This function goes through the list of options and mark them all as
      75             :  * undefined. This is useful if you want to reuse a getopt object.
      76             :  *
      77             :  * The effect is that all calls to is_defined() made afterward return false
      78             :  * until new arguments get parsed.
      79             :  */
      80           2 : void getopt::reset()
      81             : {
      82          18 :     for(auto & opt : f_options_by_name)
      83             :     {
      84          16 :         opt.second->reset();
      85             :     }
      86           2 : }
      87             : 
      88             : 
      89             : /** \brief Parse the options to option_info objects.
      90             :  *
      91             :  * This function transforms an array of options in a vector of option_info
      92             :  * objects.
      93             :  *
      94             :  * \param[in] opts  An array of options to be parsed.
      95             :  * \param[in] ignore_duplicates  Whether to ignore potential duplicates.
      96             :  */
      97         413 : void getopt::parse_options_info(option const * opts, bool ignore_duplicates)
      98             : {
      99         413 :     if(opts == nullptr)
     100             :     {
     101          63 :         return;
     102             :     }
     103             : 
     104         956 :     for(
     105        1306 :       ; (opts->f_flags & GETOPT_FLAG_END) == 0
     106             :       ; ++opts)
     107             :     {
     108         964 :         if(opts->f_name == nullptr
     109         963 :         || opts->f_name[0] == '\0')
     110             :         {
     111           2 :             throw getopt_exception_logic("option long name missing or empty.");
     112             :         }
     113         962 :         if(opts->f_name[1] == '\0')
     114             :         {
     115           1 :             throw getopt_exception_logic("a long name option must be at least 2 characters.");
     116             :         }
     117             : 
     118         961 :         if(get_option(opts->f_name, true) != nullptr)
     119             :         {
     120           2 :             if(ignore_duplicates)
     121             :             {
     122           1 :                 continue;
     123             :             }
     124             :             throw getopt_exception_logic(
     125             :                       std::string("option named \"")
     126           2 :                     + opts->f_name
     127           3 :                     + "\" found twice.");
     128             :         }
     129         959 :         short_name_t short_name(opts->f_short_name);
     130         959 :         if(get_option(short_name, true) != nullptr)
     131             :         {
     132           2 :             if(ignore_duplicates)
     133             :             {
     134           1 :                 short_name = U'\0';
     135             :             }
     136             :             else
     137             :             {
     138             :                 throw getopt_exception_logic(
     139             :                           "option with short name \""
     140           2 :                         + short_name_to_string(opts->f_short_name)
     141           3 :                         + "\" found twice.");
     142             :             }
     143             :         }
     144             : 
     145             :         option_info::pointer_t o(std::make_shared<option_info>(
     146             :                                               opts->f_name
     147        1915 :                                             , short_name));
     148             : 
     149         957 :         o->add_flag(opts->f_flags);
     150         957 :         o->set_default(opts->f_default);
     151         957 :         o->set_help(opts->f_help);
     152         957 :         o->set_multiple_separators(opts->f_multiple_separators);
     153             : 
     154         957 :         if(opts->f_validator != nullptr)
     155             :         {
     156           6 :             o->set_validator(opts->f_validator);
     157             :         }
     158             : 
     159         957 :         if(o->is_default_option())
     160             :         {
     161          42 :             if(f_default_option != nullptr)
     162             :             {
     163           1 :                 throw getopt_exception_logic("two default options found after check of long names duplication.");
     164             :             }
     165          41 :             if(o->has_flag(GETOPT_FLAG_FLAG))
     166             :             {
     167           1 :                 throw getopt_exception_logic("a default option must accept parameters, it can't be a GETOPT_FLAG_FLAG.");
     168             :             }
     169             : 
     170          40 :             f_default_option = o;
     171             :         }
     172             : 
     173         955 :         f_options_by_name[opts->f_name] = o;
     174         955 :         if(short_name != NO_SHORT_NAME)
     175             :         {
     176         544 :             f_options_by_short_name[short_name] = o;
     177             :         }
     178             :     }
     179             : }
     180             : 
     181             : 
     182             : /** \brief Check for a file with option definitions.
     183             :  *
     184             :  * This function tries to read a file of options for this application.
     185             :  * These are similar to the option structure, only it is defined in a
     186             :  * file.
     187             :  *
     188             :  * The format of the file is like so:
     189             :  *
     190             :  * \li Option names are defined on a line by themselves between square brackets.
     191             :  * \li Parameters of that option are defined below as a `name=<value>`.
     192             :  *
     193             :  * Example:
     194             :  *
     195             :  * \code
     196             :  *     [<command-name>]
     197             :  *     short_name=<character>
     198             :  *     default=<default value>
     199             :  *     help=<help sentence>
     200             :  *     validator=<validator name>[(<param>)]|/<regex>/<flags>
     201             :  *     alias=<name of aliased option>
     202             :  *     allowed=command-line,environment-variable,configuration-file
     203             :  *     show-usage-on-error
     204             :  *     no-arguments|multiple
     205             :  *     required
     206             :  * \endcode
     207             :  */
     208         319 : void getopt::parse_options_from_file()
     209             : {
     210         328 :     std::string filename;
     211             : 
     212         319 :     if(f_options_environment.f_project_name == nullptr
     213         316 :     || f_options_environment.f_project_name[0] == '\0')
     214             :     {
     215           5 :         return;
     216             :     }
     217             : 
     218         314 :     if(f_options_environment.f_options_files_directory == nullptr
     219          73 :     || f_options_environment.f_options_files_directory[0] == '\0')
     220             :     {
     221         243 :         filename = "/usr/share/advgetopt/options";
     222             :     }
     223             :     else
     224             :     {
     225          71 :         filename = f_options_environment.f_options_files_directory;
     226          71 :         if(filename.back() != '/')
     227             :         {
     228          71 :             filename += '/';
     229             :         }
     230             :     }
     231         314 :     filename += f_options_environment.f_project_name;
     232         314 :     filename += ".ini";
     233             : 
     234             :     conf_file_setup conf_setup(filename
     235             :                              , line_continuation_t::unix
     236             :                              , ASSIGNMENT_OPERATOR_EQUAL
     237             :                              , COMMENT_INI | COMMENT_SHELL
     238         323 :                              , SECTION_OPERATOR_INI_FILE | SECTION_OPERATOR_ONE_SECTION);
     239         314 :     if(!conf_setup.is_valid())
     240             :     {
     241         305 :         return;
     242             :     }
     243             : 
     244          18 :     conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
     245          18 :     conf_file::sections_t const & sections(conf->get_sections());
     246          22 :     for(auto & section_name : sections)
     247             :     {
     248          16 :         std::string::size_type pos(section_name.find("::"));
     249          16 :         if(pos != std::string::npos)
     250             :         {
     251             :             // this should never happen since we use the
     252             :             // SECTION_OPERATOR_ONE_SECTION flag
     253             :             //
     254             :             throw getopt_exception_logic(                               // LCOV_EXCL_LINE
     255             :                       "section \""                                      // LCOV_EXCL_LINE
     256             :                     + section_name                                      // LCOV_EXCL_LINE
     257             :                     + "\" includes a section separator (::) in \""      // LCOV_EXCL_LINE
     258             :                     + filename                                          // LCOV_EXCL_LINE
     259             :                     + "\". We only support one level.");                // LCOV_EXCL_LINE
     260             :         }
     261             : 
     262          32 :         std::string const parameter_name(section_name);
     263          32 :         std::string const short_name(conf->get_parameter(parameter_name + "::shortname"));
     264          16 :         if(short_name.length() > 1)
     265             :         {
     266             :             throw getopt_exception_logic(
     267             :                       "option \""
     268           2 :                     + section_name
     269           2 :                     + "\" has an invalid short name in \""
     270           2 :                     + filename
     271           3 :                     + "\", it can't be more than one character.");
     272             :         }
     273          15 :         short_name_t sn('\0');
     274          15 :         if(short_name.length() == 1)
     275             :         {
     276          13 :             sn = short_name[0];
     277             :         }
     278             : 
     279          30 :         option_info::pointer_t opt(std::make_shared<option_info>(parameter_name, sn));
     280             : 
     281          30 :         std::string const default_name(parameter_name + "::default");
     282          15 :         if(conf->has_parameter(default_name))
     283             :         {
     284          18 :             std::string const default_value(conf->get_parameter(default_name));
     285           9 :             opt->set_default(unquote(default_value));
     286             :         }
     287             : 
     288          15 :         opt->set_help(conf->get_parameter(parameter_name + "::help"));
     289             : 
     290          30 :         std::string const validator_name_and_params(conf->get_parameter(parameter_name + "::validator"));
     291          15 :         opt->set_validator(validator_name_and_params);
     292             : 
     293          28 :         std::string const alias_name(parameter_name + "::alias");
     294          14 :         if(conf->has_parameter(alias_name))
     295             :         {
     296           6 :             if(!opt->get_help().empty())
     297             :             {
     298             :                 throw getopt_exception_logic(
     299             :                           "option \""
     300           2 :                         + section_name
     301           2 :                         + "\" is an alias and as such it can't include a help=... parameter in \""
     302           2 :                         + filename
     303           3 :                         + "\".");
     304             :             }
     305           5 :             opt->set_help(conf->get_parameter(alias_name));
     306           5 :             opt->add_flag(GETOPT_FLAG_ALIAS);
     307             :         }
     308             : 
     309          26 :         std::string const allowed_name(parameter_name + "::allowed");
     310          13 :         if(conf->has_parameter(allowed_name))
     311             :         {
     312          26 :             std::string const allowed_list(conf->get_parameter(allowed_name));
     313          26 :             string_list_t allowed;
     314          13 :             split_string(allowed_list, allowed, {","});
     315          32 :             for(auto const & a : allowed)
     316             :             {
     317          19 :                 if(a == "command-line")
     318             :                 {
     319          10 :                     opt->add_flag(GETOPT_FLAG_COMMAND_LINE);
     320             :                 }
     321           9 :                 else if(a == "environment-variable")
     322             :                 {
     323           6 :                     opt->add_flag(GETOPT_FLAG_ENVIRONMENT_VARIABLE);
     324             :                 }
     325           3 :                 else if(a == "configuration-file")
     326             :                 {
     327           3 :                     opt->add_flag(GETOPT_FLAG_CONFIGURATION_FILE);
     328             :                 }
     329             :             }
     330             :         }
     331             : 
     332          13 :         if(conf->has_parameter(parameter_name + "::show-usage-on-error"))
     333             :         {
     334           1 :             opt->add_flag(GETOPT_FLAG_SHOW_USAGE_ON_ERROR);
     335             :         }
     336             : 
     337          13 :         if(conf->has_parameter(parameter_name + "::no-arguments"))
     338             :         {
     339           4 :             opt->add_flag(GETOPT_FLAG_FLAG);
     340             :         }
     341             : 
     342          13 :         if(conf->has_parameter(parameter_name + "::multiple"))
     343             :         {
     344           1 :             opt->add_flag(GETOPT_FLAG_MULTIPLE);
     345             :         }
     346             : 
     347          13 :         if(conf->has_parameter(parameter_name + "::required"))
     348             :         {
     349           5 :             opt->add_flag(GETOPT_FLAG_REQUIRED);
     350             :         }
     351             : 
     352          13 :         f_options_by_name[parameter_name] = opt;
     353          13 :         if(sn != NO_SHORT_NAME)
     354             :         {
     355          11 :             f_options_by_short_name[sn] = opt;
     356             :         }
     357             :     }
     358             : }
     359             : 
     360             : 
     361             : /** \brief Link options marked as a GETOPT_FLAG_ALIAS.
     362             :  *
     363             :  * After we defined all the options, go through the list again to find
     364             :  * aliases and link them with their corresponding alias option.
     365             :  *
     366             :  * \exception getopt_exception_invalid
     367             :  * All aliases must exist or this exception is raised.
     368             :  */
     369         244 : void getopt::link_aliases()
     370             : {
     371         964 :     for(auto & c : f_options_by_name)
     372             :     {
     373         724 :         if(c.second->has_flag(GETOPT_FLAG_ALIAS))
     374             :         {
     375          22 :             std::string const & alias_name(c.second->get_help());
     376          22 :             if(alias_name.empty())
     377             :             {
     378             :                 throw getopt_exception_logic(
     379             :                           "the default value of your alias cannot be an empty string for \""
     380           4 :                         + c.first
     381           6 :                         + "\".");
     382             :             }
     383             : 
     384             :             // we have to use the `true` flag in this get_option() because
     385             :             // aliases may not yet be defined
     386             :             //
     387          40 :             option_info::pointer_t alias(get_option(alias_name, true));
     388          20 :             if(alias == nullptr)
     389             :             {
     390             :                 throw getopt_exception_logic(
     391             :                           "no option named \""
     392           2 :                         + alias_name
     393           2 :                         + "\" to satisfy the alias of \""
     394           3 :                         + c.first
     395           3 :                         + "\".");
     396             :             }
     397             : 
     398          19 :             flag_t const expected_flags(c.second->get_flags() & ~GETOPT_FLAG_ALIAS);
     399          19 :             if(alias->get_flags() != expected_flags)
     400             :             {
     401           2 :                 std::stringstream ss;
     402           1 :                 ss << std::hex
     403           1 :                    << "the flags of alias \""
     404           2 :                    << c.first
     405           1 :                    << "\" (0x"
     406           1 :                    << expected_flags
     407           1 :                    << ") are different than the flags of \""
     408           1 :                    << alias_name
     409           1 :                    << "\" (0x"
     410           2 :                    << alias->get_flags()
     411           1 :                    << ").";
     412           1 :                 throw getopt_exception_logic(ss.str());
     413             :             }
     414             : 
     415          18 :             c.second->set_alias_destination(alias);
     416             :         }
     417             :     }
     418         240 : }
     419             : 
     420             : 
     421             : /** \brief Assign a short name to an option.
     422             :  *
     423             :  * This function allows for dynamically assigning a short name to an option.
     424             :  * This is useful for cases where a certain number of options may be added
     425             :  * dynamically and may share the same short name or similar situation.
     426             :  *
     427             :  * On our end we like to add `-c` as the short name of the `--config-dir`
     428             :  * command line or environment variable option. However, some of our tools
     429             :  * use `-c` for other reason (i.e. our `cxpath` tool uses `-c` for its
     430             :  * `--compile` option.) So we do not want to have it has a default in
     431             :  * that option. Instead we assign it afterward.
     432             :  *
     433             :  * **IMPORTANT:** To make this call useful, make sure to make it before
     434             :  * you call the parse functions. Setting the short name after the parsing
     435             :  * was done is going to be useless.
     436             :  *
     437             :  * \note
     438             :  * This function requires you to make use of the constructor without the
     439             :  * `argc` and `argv` parameters, add the short name, then run all the
     440             :  * parsing.
     441             :  *
     442             :  * \note
     443             :  * The function calls the option_info::set_short_name() function which
     444             :  * may raise an exception if the option already has a short name (or if
     445             :  * you inadvertendly passed NO_SHORT_NAME.)
     446             :  *
     447             :  * \exception getopt_exception_logic
     448             :  * The same short name cannot be used more than once. This exception is
     449             :  * raised if it is discovered that another option already makes use of
     450             :  * this short name. This exception is also raised if the \p name
     451             :  * parameter does not reference an existing option.
     452             :  *
     453             :  * \param[in] name  The name of the option which is to receive a short name.
     454             :  * \param[in] short_name  The short name to assigned to the \p name option.
     455             :  */
     456           7 : void getopt::set_short_name(std::string const & name, short_name_t short_name)
     457             : {
     458           7 :     auto it(f_options_by_short_name.find(short_name));
     459           7 :     if(it != f_options_by_short_name.end())
     460             :     {
     461             :         throw getopt_exception_logic(
     462             :                   "found another option (\""
     463           2 :                 + it->second->get_name()
     464           2 :                 + "\") with short name '"
     465           4 :                 + short_name_to_string(short_name)
     466           3 :                 + "'.");
     467             :     }
     468             : 
     469           6 :     auto opt(f_options_by_name.find(name));
     470           6 :     if(opt == f_options_by_name.end())
     471             :     {
     472             :         throw getopt_exception_logic(
     473             :                   "option with name \""
     474           2 :                 + name
     475           3 :                 + "\" not found.");
     476             :     }
     477             : 
     478           5 :     opt->second->set_short_name(short_name);
     479             : 
     480           3 :     f_options_by_short_name[short_name] = opt->second;
     481           3 : }
     482             : 
     483             : 
     484             : 
     485             : 
     486           6 : } // namespace advgetopt
     487             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.12