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
|