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

Generated by: LCOV version 1.13