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
|