LCOV - code coverage report
Current view: top level - advgetopt - validator.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 245 245 100.0 %
Date: 2022-07-15 17:40:56 Functions: 28 30 93.3 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2006-2022  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             : 
      21             : /** \file
      22             :  * \brief Advanced getopt version functions.
      23             :  *
      24             :  * The advgetopt environment is versioned. The functions available here
      25             :  * give you access to the version, in case you wanted to make sure you
      26             :  * had a minimum version or had some special case options when you
      27             :  * want to be able to support various versions.
      28             :  */
      29             : 
      30             : // self
      31             : //
      32             : #include    "advgetopt/validator.h"
      33             : 
      34             : #include    "advgetopt/exception.h"
      35             : #include    "advgetopt/validator_list.h"
      36             : 
      37             : 
      38             : // cppthread
      39             : //
      40             : #include    <cppthread/log.h>
      41             : 
      42             : 
      43             : // snapdev
      44             : //
      45             : #include    <snapdev/not_reached.h>
      46             : #include    <snapdev/join_strings.h>
      47             : 
      48             : 
      49             : // boost
      50             : //
      51             : #include    <boost/algorithm/string/trim.hpp>
      52             : 
      53             : 
      54             : // C++
      55             : //
      56             : #include    <map>
      57             : 
      58             : 
      59             : // last include
      60             : //
      61             : #include    <snapdev/poison.h>
      62             : 
      63             : 
      64             : 
      65             : namespace advgetopt
      66             : {
      67             : 
      68             : 
      69             : 
      70             : namespace
      71             : {
      72             : 
      73             : 
      74             : typedef std::map<std::string, validator_factory const *>    factory_map_t;
      75             : 
      76             : factory_map_t * g_validator_factories;
      77             : 
      78             : 
      79             : enum class token_t
      80             : {
      81             :     TOK_EOF,
      82             : 
      83             :     TOK_STRING,
      84             :     TOK_IDENTIFIER,
      85             :     TOK_REGEX,
      86             : 
      87             :     TOK_OPEN_PARENTHESIS,
      88             :     TOK_CLOSE_PARENTHESIS,
      89             :     TOK_COMMA,
      90             :     TOK_OR,
      91             : 
      92             :     TOK_INVALID,
      93             : };
      94             : 
      95             : 
      96         391 : class token
      97             : {
      98             : public:
      99         222 :     token(token_t tok, std::string const & value = std::string())
     100         222 :         : f_token(tok)
     101         222 :         , f_value(value)
     102             :     {
     103         222 :     }
     104             : 
     105         444 :     token_t tok() const
     106             :     {
     107         444 :         return f_token;
     108             :     }
     109             : 
     110          96 :     std::string const & value() const
     111             :     {
     112          96 :         return f_value;
     113             :     }
     114             : 
     115             : private:
     116             :     token_t         f_token = token_t::TOK_EOF;
     117             :     std::string     f_value = std::string();
     118             : };
     119             : 
     120             : class lexer
     121             : {
     122             : public:
     123          53 :     lexer(char const * in)
     124          53 :         : f_in(in)
     125             :     {
     126          53 :     }
     127             : 
     128         240 :     token next_token()
     129             :     {
     130             :         for(;;)
     131             :         {
     132         240 :             int c(getc());
     133         240 :             switch(c)
     134             :             {
     135          43 :             case '\0':
     136          43 :                 return token(token_t::TOK_EOF);
     137             : 
     138          34 :             case '(':
     139          34 :                 return token(token_t::TOK_OPEN_PARENTHESIS);
     140             : 
     141          21 :             case ')':
     142          21 :                 return token(token_t::TOK_CLOSE_PARENTHESIS);
     143             : 
     144          19 :             case ',':
     145          19 :                 return token(token_t::TOK_COMMA);
     146             : 
     147           1 :             case '|':
     148           1 :                 c = getc();
     149           1 :                 if(c != '|')    // allow for || like in C
     150             :                 {
     151           1 :                     ungetc(c);
     152             :                 }
     153           1 :                 return token(token_t::TOK_OR);
     154             : 
     155          11 :             case '"':
     156             :             case '\'':
     157             :                 {
     158          11 :                     int const quote(c);
     159          22 :                     std::string s;
     160             :                     for(;;)
     161             :                     {
     162         505 :                         c = getc();
     163         258 :                         if(c == quote)
     164             :                         {
     165          11 :                             break;
     166             :                         }
     167         247 :                         s += static_cast<char>(c);
     168             :                     }
     169          11 :                     return token(token_t::TOK_STRING, s);
     170             :                 }
     171             : 
     172          18 :             case '/':
     173             :                 {
     174          36 :                     std::string r;
     175             :                     for(;;)
     176             :                     {
     177         270 :                         r += static_cast<char>(c);
     178         144 :                         c = getc();
     179         144 :                         if(c == '/')
     180             :                         {
     181          16 :                             r += static_cast<char>(c);
     182          16 :                             break;
     183             :                         }
     184         128 :                         if(c < ' ' && c != '\t')
     185             :                         {
     186           2 :                             cppthread::log << cppthread::log_level_t::error
     187           1 :                                            << "validator(): unexpected character for a regular expression ("
     188           2 :                                            << static_cast<int>(c)
     189           1 :                                            << ")."
     190           3 :                                            << cppthread::end;
     191           1 :                             return token(token_t::TOK_INVALID);
     192             :                         }
     193         127 :                         if(c == '\\')
     194             :                         {
     195             :                             // we keep the backslash, it's important when
     196             :                             // further parsing happens
     197             :                             //
     198           7 :                             r += c;
     199             : 
     200           7 :                             c = getc();
     201           7 :                             if(c < ' ' && c != '\t')
     202             :                             {
     203           2 :                                 cppthread::log << cppthread::log_level_t::error
     204           1 :                                                << "validator(): unexpected escaped character for a regular expression ("
     205           2 :                                                << static_cast<int>(c)
     206           1 :                                                << ")."
     207           3 :                                                << cppthread::end;
     208           1 :                                 return token(token_t::TOK_INVALID);
     209             :                             }
     210             :                         }
     211             :                     }
     212             :                     // also allow for flags after the closing '/'
     213             :                     //
     214             :                     // at this time we only support 'i' but here we allow any
     215             :                     // letter for forward compatibility
     216             :                     //
     217             :                     for(;;)
     218             :                     {
     219          32 :                         c = getc();
     220          24 :                         if(c == '\0')
     221             :                         {
     222          13 :                             break;
     223             :                         }
     224          11 :                         if(c < 'a' || c > 'z')
     225             :                         {
     226           3 :                             ungetc(c);
     227           3 :                             if(c != ','
     228           2 :                             && c != ')')
     229             :                             {
     230           2 :                                 cppthread::log << cppthread::log_level_t::error
     231           1 :                                                << "validator(): unexpected flag character for a regular expression ("
     232           2 :                                                << static_cast<int>(c)
     233           1 :                                                << ")."
     234           3 :                                                << cppthread::end;
     235           1 :                                 return token(token_t::TOK_INVALID);
     236             :                             }
     237           2 :                             break;
     238             :                         }
     239           8 :                         r += c;
     240             :                     }
     241          15 :                     return token(token_t::TOK_REGEX, r);
     242             :                 }
     243             : 
     244          18 :             case ' ':
     245             :                 // ignore spaces
     246          18 :                 break;
     247             : 
     248          75 :             default:
     249             :                 {
     250         150 :                     std::string id;
     251         403 :                     for(;;)
     252             :                     {
     253         478 :                         switch(c)
     254             :                         {
     255          63 :                         case '(':
     256             :                         case ')':
     257             :                         case ',':
     258             :                         case '|':
     259             :                         case ' ':
     260          63 :                             ungetc(c);
     261             :                             [[fallthrough]];
     262          72 :                         case '\0':
     263          72 :                             return token(token_t::TOK_IDENTIFIER, id);
     264             : 
     265         406 :                         default:
     266         406 :                             if(c < ' ' || c > '~')
     267             :                             {
     268           6 :                                 cppthread::log << cppthread::log_level_t::error
     269           3 :                                                << "validator(): unexpected character for an identifier ("
     270           6 :                                                << static_cast<int>(c)
     271           3 :                                                << ")."
     272           9 :                                                << cppthread::end;
     273           3 :                                 return token(token_t::TOK_INVALID);
     274             :                             }
     275         403 :                             break;
     276             : 
     277             :                         }
     278         403 :                         id += static_cast<char>(c);
     279         403 :                         c = getc();
     280             :                     }
     281             :                 }
     282             :                 break;
     283             : 
     284             :             }
     285          18 :         }
     286             :         snapdev::NOT_REACHED();
     287             :     }
     288             : 
     289           8 :     std::string remains() const
     290             :     {
     291           8 :         if(*f_in == '\0')
     292             :         {
     293           5 :             return std::string("...EOS");
     294             :         }
     295             : 
     296           3 :         return f_in;
     297             :     }
     298             : 
     299             : private:
     300        1077 :     int getc()
     301             :     {
     302        1077 :         if(f_c != '\0')
     303             :         {
     304          64 :             int const c(f_c);
     305          64 :             f_c = '\0';
     306          64 :             return c;
     307             :         }
     308             : 
     309        1013 :         if(*f_in == '\0')
     310             :         {
     311          65 :             return '\0';
     312             :         }
     313             :         else
     314             :         {
     315         948 :             int const c(*f_in);
     316         948 :             ++f_in;
     317         948 :             return c;
     318             :         }
     319             :     }
     320             : 
     321          67 :     void ungetc(int c)
     322             :     {
     323          67 :         if(f_c != '\0')
     324             :         {
     325             :             throw getopt_logic_error("ungetc() already called once, getc() must be called in between now"); // LCOV_EXCL_LINE
     326             :         }
     327          67 :         f_c = c;
     328          67 :     }
     329             : 
     330             :     char const *    f_in = nullptr;
     331             :     int             f_c = '\0';
     332             : };
     333             : 
     334             : 
     335         136 : class validator_with_params
     336             : {
     337             : public:
     338             :     typedef std::vector<validator_with_params>   vector_t;
     339             : 
     340          52 :     validator_with_params(std::string const & name)
     341          52 :         : f_name(name)
     342             :     {
     343          52 :     }
     344             : 
     345          39 :     std::string const & get_name() const
     346             :     {
     347          39 :         return f_name;
     348             :     }
     349             : 
     350          57 :     void add_param(std::string const & param)
     351             :     {
     352          57 :         f_params.push_back(param);
     353          57 :     }
     354             : 
     355          39 :     string_list_t const & get_params() const
     356             :     {
     357          39 :         return f_params;
     358             :     }
     359             : 
     360             : private:
     361             :     std::string     f_name = std::string();
     362             :     string_list_t   f_params = string_list_t();
     363             : };
     364             : 
     365             : 
     366          53 : class parser
     367             : {
     368             : public:
     369          53 :     parser(lexer & l)
     370          53 :         : f_lexer(l)
     371             :     {
     372          53 :     }
     373             : 
     374          53 :     bool parse()
     375             :     {
     376         106 :         token t(f_lexer.next_token());
     377          53 :         if(t.tok() == token_t::TOK_EOF)
     378             :         {
     379             :             // empty list
     380             :             //
     381           1 :             return true;
     382             :         }
     383             : 
     384             :         // TODO: show location on an error
     385             :         //
     386             :         for(;;)
     387             :         {
     388          53 :             switch(t.tok())
     389             :             {
     390          13 :             case token_t::TOK_REGEX:
     391             :                 {
     392          26 :                     validator_with_params v("regex");
     393          13 :                     v.add_param(t.value());
     394          13 :                     f_validators.push_back(v);
     395             : 
     396          26 :                     t = f_lexer.next_token();
     397             :                 }
     398          13 :                 break;
     399             : 
     400          39 :             case token_t::TOK_IDENTIFIER:
     401             :                 {
     402          67 :                     validator_with_params v(t.value());
     403             : 
     404          39 :                     t = f_lexer.next_token();
     405          39 :                     if(t.tok() == token_t::TOK_OPEN_PARENTHESIS)
     406             :                     {
     407          32 :                         t = f_lexer.next_token();
     408          32 :                         if(t.tok() != token_t::TOK_CLOSE_PARENTHESIS)
     409             :                         {
     410             :                             for(;;)
     411             :                             {
     412          67 :                                 if(t.tok() == token_t::TOK_INVALID)
     413             :                                 {
     414           4 :                                     return false;
     415             :                                 }
     416          90 :                                 if(t.tok() != token_t::TOK_IDENTIFIER
     417          14 :                                 && t.tok() != token_t::TOK_STRING
     418          48 :                                 && t.tok() != token_t::TOK_REGEX)
     419             :                                 {
     420           2 :                                     cppthread::log << cppthread::log_level_t::error
     421           1 :                                                    << "validator(): expected a regex, an identifier or a string inside the () of a parameter. Remaining input: \""
     422           2 :                                                    << f_lexer.remains()
     423           1 :                                                    << "\""
     424           3 :                                                    << cppthread::end;
     425           1 :                                     return false;
     426             :                                 }
     427          44 :                                 v.add_param(t.value());
     428             : 
     429          44 :                                 t = f_lexer.next_token();
     430          44 :                                 if(t.tok() == token_t::TOK_CLOSE_PARENTHESIS)
     431             :                                 {
     432          20 :                                     break;
     433             :                                 }
     434             : 
     435          24 :                                 if(t.tok() == token_t::TOK_EOF)
     436             :                                 {
     437           8 :                                     cppthread::log << cppthread::log_level_t::error
     438           4 :                                                    << "validator(): parameter list must end with ')'. Remaining input: \""
     439           8 :                                                    << f_lexer.remains()
     440           4 :                                                    << "\""
     441          12 :                                                    << cppthread::end;
     442           4 :                                     return false;
     443             :                                 }
     444             : 
     445          20 :                                 if(t.tok() != token_t::TOK_COMMA)
     446             :                                 {
     447           2 :                                     if(t.tok() == token_t::TOK_INVALID)
     448             :                                     {
     449           1 :                                         return false;
     450             :                                     }
     451           2 :                                     cppthread::log << cppthread::log_level_t::error
     452           1 :                                                    << "validator(): parameters must be separated by ','. Remaining input: \""
     453           2 :                                                    << f_lexer.remains()
     454           1 :                                                    << "\""
     455           3 :                                                    << cppthread::end;
     456           1 :                                     return false;
     457             :                                 }
     458           1 :                                 do
     459             :                                 {
     460          19 :                                     t = f_lexer.next_token();
     461             :                                 }
     462          19 :                                 while(t.tok() == token_t::TOK_COMMA);
     463             :                             }
     464             :                         }
     465          21 :                         t = f_lexer.next_token();
     466             :                     }
     467             : 
     468          67 :                     f_validators.push_back(v);
     469             :                 }
     470          28 :                 break;
     471             : 
     472           1 :             default:
     473           1 :                 if(t.tok() != token_t::TOK_INVALID)
     474             :                 {
     475           2 :                     cppthread::log << cppthread::log_level_t::error
     476             :                                    << "validator(): unexpected token in validator definition;"
     477           1 :                                       " expected an identifier. Remaining input: \""
     478           2 :                                    << f_lexer.remains()
     479           1 :                                    << "\"."
     480           3 :                                    << cppthread::end;
     481             :                 }
     482           1 :                 return false;
     483             : 
     484             :             }
     485             : 
     486          41 :             if(t.tok() == token_t::TOK_EOF)
     487             :             {
     488          38 :                 return true;
     489             :             }
     490             : 
     491           3 :             if(t.tok() != token_t::TOK_OR)
     492             :             {
     493           2 :                 if(t.tok() != token_t::TOK_INVALID)
     494             :                 {
     495           2 :                     cppthread::log << cppthread::log_level_t::error
     496           1 :                                    << "validator(): validator definitions must be separated by '|'. Remaining input: \""
     497           2 :                                    << f_lexer.remains()
     498           1 :                                    << "\""
     499           3 :                                    << cppthread::end;
     500             :                 }
     501           2 :                 return false;
     502             :             }
     503             : 
     504           1 :             t = f_lexer.next_token();
     505           1 :         }
     506             :         snapdev::NOT_REACHED();
     507             :     }
     508             : 
     509          39 :     validator_with_params::vector_t const & get_validators() const
     510             :     {
     511          39 :         return f_validators;
     512             :     }
     513             : 
     514             : private:
     515             :     lexer &         f_lexer;
     516             :     validator_with_params::vector_t
     517             :                     f_validators = validator_with_params::vector_t();
     518             : };
     519             : 
     520             : 
     521             : 
     522             : } // no name namespace
     523             : 
     524             : 
     525             : 
     526             : /** \brief The destructor to ease derived classes.
     527             :  *
     528             :  * At this point this destructor does nothing more than help with the
     529             :  * virtual table.
     530             :  */
     531          19 : validator_factory::~validator_factory()
     532             : {
     533          19 : }
     534             : 
     535             : 
     536             : 
     537             : 
     538             : 
     539             : 
     540             : /** \brief The validator destructor to support virtuals.
     541             :  *
     542             :  * This destructor is defined so virtual functions work as expected including
     543             :  * the deleter.
     544             :  */
     545         183 : validator::~validator()
     546             : {
     547         183 : }
     548             : 
     549             : 
     550             : /** \fn std::string const & validator::name() const;
     551             :  * \brief Return the name of the validator.
     552             :  *
     553             :  * The name() function is used to get the name of the validator.
     554             :  * Validators are recognized by name and added to your options
     555             :  * using their name.
     556             :  *
     557             :  * Note that when an option specifies a validator which it can't find,
     558             :  * then an error occurs.
     559             :  *
     560             :  * \return The name of the validator.
     561             :  */
     562             : 
     563             : 
     564             : /** \fn bool validator::validate(std::string const & value) const;
     565             :  * \brief Return true if \p value validates agains this validator.
     566             :  *
     567             :  * The function parses the \p value parameter and if it matches the
     568             :  * allowed parameters, then it returns true.
     569             :  *
     570             :  * \param[in] value  The value to validate.
     571             :  *
     572             :  * \return true if the value validates.
     573             :  */
     574             : 
     575             : 
     576          19 : void validator::register_validator(validator_factory const & factory)
     577             : {
     578          19 :     if(g_validator_factories == nullptr)
     579             :     {
     580           2 :         g_validator_factories = new factory_map_t();
     581             :     }
     582          19 :     auto it(g_validator_factories->find(factory.get_name()));
     583          19 :     if(it != g_validator_factories->end())
     584             :     {
     585             :         throw getopt_logic_error(
     586             :                   "you have two or more validator factories named \""
     587           2 :                 + factory.get_name()
     588           3 :                 + "\".");
     589             :     }
     590          18 :     (*g_validator_factories)[factory.get_name()] = &factory;
     591          18 : }
     592             : 
     593             : 
     594         184 : validator::pointer_t validator::create(std::string const & name, string_list_t const & data)
     595             : {
     596         184 :     if(g_validator_factories == nullptr)
     597             :     {
     598             :         return validator::pointer_t();  // LCOV_EXCL_LINE
     599             :     }
     600             : 
     601         184 :     auto it(g_validator_factories->find(name));
     602         184 :     if(it == g_validator_factories->end())
     603             :     {
     604           1 :         return validator::pointer_t();
     605             :     }
     606             : 
     607         183 :     return it->second->create(data);
     608             : }
     609             : 
     610             : 
     611             : /** \brief Set the validator for this option.
     612             :  *
     613             :  * This function parses the specified name and optional parameters and
     614             :  * create a corresponding validator for this option.
     615             :  *
     616             :  * The \p name_and_params string can be defined as:
     617             :  *
     618             :  * \code
     619             :  *     <validator-name>(<param1>, <param2>, ...)
     620             :  * \endcode
     621             :  *
     622             :  * The list of parameters is optional. There may be an empty, just one,
     623             :  * or any number of parameters. How the parameters are parsed is left
     624             :  * to the validator to decide.
     625             :  *
     626             :  * If the input string is empty, the current validator, if one is
     627             :  * installed, gets removed.
     628             :  *
     629             :  * \param[in] name_and_params  The validator name and parameters.
     630             :  */
     631          75 : validator::pointer_t validator::create(std::string const & name_and_params)
     632             : {
     633          75 :     if(name_and_params.empty())
     634             :     {
     635          22 :         return validator::pointer_t();
     636             :     }
     637             : 
     638             :     // the name and parameters can be written as a function call, we have
     639             :     // a special case for regex which do not require the function call
     640             :     //
     641             :     //   validator_list: name_and_params
     642             :     //                 | name_and_params ',' validator_list
     643             :     //
     644             :     //   name_and_params: name '(' params ')'
     645             :     //                  | '/' ... '/'              /* regex special case */
     646             :     //
     647             :     //   name: [a-zA-Z_][a-zA-Z_0-9]*
     648             :     //
     649             :     //   params: (thing - [,()'" ])
     650             :     //         | '\'' (thing - '\'') '\''
     651             :     //         | '"' (thing - '"') '"'
     652             :     //
     653             :     //   thing: [ -~]*
     654             :     //        | '\\' [ -~]
     655             :     //
     656             : 
     657          53 :     lexer l(name_and_params.c_str());
     658         106 :     parser p(l);
     659          53 :     if(!p.parse())
     660             :     {
     661          14 :         return validator::pointer_t();
     662             :     }
     663             : 
     664          39 :     validator_with_params::vector_t const & validators(p.get_validators());
     665             : 
     666          39 :     if(validators.size() == 0)
     667             :     {
     668           1 :         return validator::pointer_t();
     669             :     }
     670             : 
     671          38 :     if(validators.size() == 1)
     672             :     {
     673          37 :         return create(validators[0].get_name(), validators[0].get_params());
     674             :     }
     675             : 
     676             :     // we need a list validator to handle this case
     677             :     //
     678           2 :     validator::pointer_t lst(create("list", string_list_t()));
     679           2 :     validator_list::pointer_t list(std::dynamic_pointer_cast<validator_list>(lst));
     680           1 :     if(list == nullptr)
     681             :     {
     682             :         throw getopt_logic_error("we just created a list and the dynamic cast failed.");    // LCOV_EXCL_LINE
     683             :     }
     684           3 :     for(auto const & v : validators)
     685             :     {
     686           2 :         list->add_validator(create(v.get_name(), v.get_params()));
     687             :     }
     688             : 
     689           1 :     return list;
     690             : }
     691             : 
     692             : 
     693             : 
     694           6 : } // namespace advgetopt
     695             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13