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
|