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

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

Generated by: LCOV version 1.14

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