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 Implementation of the option_info class.
23 : *
24 : * This is the implementation of the class used to load and save
25 : * configuration files.
26 : *
27 : * \warning
28 : * This version uses the advgetopt::conf_file which sorts the fields
29 : * it reads, therefore, the output is going to be correct, but possibly
30 : * sorted in a "funny way", especially if you keep the comments and
31 : * some of the values are commented out.
32 : */
33 :
34 : // self
35 : //
36 : #include "advgetopt/conf_file.h"
37 :
38 :
39 : // advgetopt lib
40 : //
41 : #include "advgetopt/exception.h"
42 : #include "advgetopt/utils.h"
43 :
44 :
45 : // snapdev lib
46 : //
47 : #include <snapdev/mkdir_p.h>
48 : #include <snapdev/safe_variable.h>
49 : #include <snapdev/string_replace_many.h>
50 : #include <snapdev/tokenize_string.h>
51 : #include <snapdev/trim_string.h>
52 :
53 :
54 : // cppthread lib
55 : //
56 : #include <cppthread/guard.h>
57 : #include <cppthread/log.h>
58 : #include <cppthread/mutex.h>
59 :
60 :
61 : // boost lib
62 : //
63 : #include <boost/algorithm/string/join.hpp>
64 :
65 :
66 : // C++ lib
67 : //
68 : #include <algorithm>
69 : #include <fstream>
70 :
71 :
72 : // C lib
73 : //
74 : #include <sys/stat.h>
75 :
76 :
77 : // last include
78 : //
79 : #include <snapdev/poison.h>
80 :
81 :
82 :
83 : namespace advgetopt
84 : {
85 :
86 :
87 : // from utils.cpp
88 : //
89 : // (it's here because we do not want to make cppthread public in
90 : // out header files--we could have an advgetopt_private.h, though)
91 : //
92 : cppthread::mutex & get_global_mutex();
93 :
94 :
95 :
96 : /** \brief Private conf_file data.
97 : *
98 : * The conf_file has a few globals used to cache configuration files.
99 : * Since it has to work in a multi-thread environment, we also have
100 : * a mutex.
101 : */
102 : namespace
103 : {
104 :
105 :
106 :
107 : /** \brief A map of configuration files.
108 : *
109 : * This typedef defines a type used to hold all the configuration files
110 : * that were loaded so far.
111 : *
112 : * The map is indexed by a string representing the full path to the
113 : * configuration file.
114 : *
115 : * The value is a shared pointer to configuration file. Since we may
116 : * share that data between multiple users, it made sense to force you
117 : * to use a configuration file smart pointer. Note, though, that we
118 : * never destroy the pointer until we quit (i.e. you cannot force a
119 : * re-load of the configuration file. Changes that happen in memory
120 : * are visible to all users, but changes to the actual configuration
121 : * file are complete invisible to use.)
122 : */
123 : typedef std::map<std::string, conf_file::pointer_t> conf_file_map_t;
124 :
125 :
126 : /** \brief The configuration files.
127 : *
128 : * This global defines a list of configuration files indexed by
129 : * filename (full path, but not the URL, just a path.)
130 : *
131 : * Whenever a configuration file is being retrieved with the
132 : * conf_file::get_conf_file() function, it is first searched
133 : * in this map. If it exists in the map, that version gets
134 : * used (if the URL of the two setups match one to one.)
135 : * If there is no such file in the map, then a new one is
136 : * created by loading the corresponding file.
137 : */
138 2 : conf_file_map_t g_conf_files = conf_file_map_t();
139 :
140 :
141 : } // no name namespace
142 :
143 :
144 :
145 :
146 :
147 : /** \brief Initialize the file setup object.
148 : *
149 : * This constructor initializes the setup object which can later be used
150 : * to search for an existing conf_file or creating a new conf_file.
151 : *
152 : * The setup holds the various parameters used to know how to load a
153 : * configuration file in memory. The parameters include
154 : *
155 : * \li \p filename -- the name of the file to read as a configuration file.
156 : * \li \p line_continuation -- how lines in the files are being read; in
157 : * most cases a line in a text file ends when a newline character (`\\n`)
158 : * is found; this parameter allows for lines that span (continue) on
159 : * multiple text lines. Only one type of continuation or no continue
160 : * (a.k.a. "single line") can be used per file.
161 : * \li \p assignment_operator -- the character(s) accepted between the
162 : * name of a variable and its value; by default this is the equal sign
163 : * (`=`). Multiple operators can be accepted.
164 : * \li \p comment -- how comments are introduced when supported. Multiple
165 : * introducers can be accepted within one file. By default we accept the
166 : * Unix Shell (`#`) and INI file (`;`) comment introducers.
167 : * \li \p section_operator -- the set of characters accepted as section
168 : * separator. By default we accept the INI file syntax (the `[section]`
169 : * syntax.)
170 : *
171 : * \note
172 : * If the filename represent an existing file, then the name is going to
173 : * get canonicalized before it gets saved in the structure. Otherwise it
174 : * gets saved as is.
175 : *
176 : * \param[in] filename A valid filename.
177 : * \param[in] line_continue One of the line_continuation_t values.
178 : * \param[in] assignment_operator A set of assignment operator flags.
179 : * \param[in] comment A set of comment flags.
180 : * \param[in] section_operator A set of section operator flags.
181 : */
182 28567 : conf_file_setup::conf_file_setup(
183 : std::string const & filename
184 : , line_continuation_t line_continuation
185 : , assignment_operator_t assignment_operator
186 : , comment_t comment
187 : , section_operator_t section_operator
188 28567 : , name_separator_t name_separator)
189 : : f_original_filename(filename)
190 : , f_line_continuation(line_continuation)
191 28567 : , f_assignment_operator(assignment_operator == 0
192 28567 : ? ASSIGNMENT_OPERATOR_EQUAL
193 : : assignment_operator)
194 : , f_comment(comment)
195 : , f_section_operator(section_operator)
196 57135 : , f_name_separator(name_separator)
197 : {
198 28567 : if(filename.empty())
199 : {
200 1 : throw getopt_invalid("trying to load a configuration file using an empty filename.");
201 : }
202 :
203 : // canonicalization so we can properly cache files
204 : //
205 57132 : std::unique_ptr<char, decltype(&::free)> fn(realpath(filename.c_str(), nullptr), &::free);
206 28566 : if(fn != nullptr)
207 : {
208 28055 : f_filename = fn.get();
209 : }
210 : else
211 : {
212 511 : f_filename = filename;
213 : }
214 28566 : }
215 :
216 :
217 : /** \brief Check whether the setup is considered valid.
218 : *
219 : * This function is used to check whether the conf_file_setup is valid or
220 : * not. It is valid when everything is in order, which at this point means
221 : * the filename is not empty.
222 : *
223 : * All the other parameters are always viewed as being valid.
224 : *
225 : * \warning
226 : * The is_valid() always returns true at this time. We always save the
227 : * filename. I'm not totally sure why I wanted to not have a way to get
228 : * a valid configuration file by viewing a non-existing file as the same
229 : * as an empty file. Now that's what happens.
230 : *
231 : * \return true if the conf_file_setup is considered valid.
232 : */
233 25940 : bool conf_file_setup::is_valid() const
234 : {
235 25940 : return !f_filename.empty();
236 : }
237 :
238 :
239 : /** \brief Get the original filename.
240 : *
241 : * When creating a new conf_file_setup, you have to specify a filename.
242 : * This function returns that string exactly, without canonicalization.
243 : *
244 : * \return The filename as specified at the time of construction.
245 : *
246 : * \sa get_filename()
247 : */
248 25227 : std::string const & conf_file_setup::get_original_filename() const
249 : {
250 25227 : return f_original_filename;
251 : }
252 :
253 :
254 : /** \brief Get the filename.
255 : *
256 : * When creating a new conf_file_setup, you have to specify a filename.
257 : * This function returns that filename after it was canonicalized by
258 : * the constructor.
259 : *
260 : * The canonicalization process computes the full path to the real
261 : * file. If such does not exist then no filename is defined, so this
262 : * function may return an empty string.
263 : *
264 : * \return The canonicalized filename or the original filename if
265 : * realpath() failed.
266 : *
267 : * \sa get_original_filename()
268 : */
269 29335 : std::string const & conf_file_setup::get_filename() const
270 : {
271 29335 : return f_filename;
272 : }
273 :
274 :
275 : /** \brief Get the line continuation setting.
276 : *
277 : * This function returns the line continuation for this setup.
278 : *
279 : * This parameter is not a set of flags. We only support one type of
280 : * line continuation per file. Many continuations could be contradictory
281 : * if used simultaneously.
282 : *
283 : * The continuation setting is one of the following:
284 : *
285 : * \li line_continuation_t::single_line -- no continuation support; any
286 : * definition must be on one single line.
287 : * \li line_continuation_t::rfc_822 -- like email/HTTP, whitespace at
288 : * the start of the next line means that the current line continues there;
289 : * those whitespaces get removed from the value so if you want a space
290 : * between two lines, make sure to finish the current line with a space.
291 : * \li line_continuation_t::msdos -- `&` at end of the line.
292 : * \li line_continuation_t::unix -- `\` at end of the line.
293 : * \li line_continuation_t::fortran -- `&` at the start of the next line;
294 : * there cannot be any spaces, the `&` has to be the very first character.
295 : * \li line_continuation_t::semicolon -- `;` ends the _line_; when reading
296 : * a line with this continuation mode, the reader stops only when it finds
297 : * the `;` or EOF (also if a comment is found.)
298 : *
299 : * \return a line continuation mode.
300 : */
301 26336 : line_continuation_t conf_file_setup::get_line_continuation() const
302 : {
303 26336 : return f_line_continuation;
304 : }
305 :
306 :
307 : /** \brief Get the accepted assignment operators.
308 : *
309 : * This function returns the set of flags describing the list of
310 : * accepted operators one can use to do assignments.
311 : *
312 : * Right now we support the follow:
313 : *
314 : * \li ASSIGNMENT_OPERATOR_EQUAL -- the equal (`=`) character, like in
315 : * most Unix configuration files and shell scripts.
316 : * \li ASSIGNMENT_OPERATOR_COLON -- the colon (`:`) character, like in
317 : * email and HTTP headers.
318 : * \li ASSIGNMENT_OPERATOR_SPACE -- the space (` `) character; this is
319 : * less used, but many Unix configuration files still use this scheme.
320 : *
321 : * \todo
322 : * Add support for additional operators such as:
323 : * \todo
324 : * \li `+=` -- append data
325 : * \li `?=` -- set to this value if not yet set
326 : *
327 : * \return The set of accepted assignment operators.
328 : *
329 : * \sa is_assignment_operator()
330 : */
331 1145186 : assignment_operator_t conf_file_setup::get_assignment_operator() const
332 : {
333 1145186 : return f_assignment_operator;
334 : }
335 :
336 :
337 : /** Get the comment flags.
338 : *
339 : * This function returns the comment flags. These describe which type
340 : * of comments are supported in this configuration file.
341 : *
342 : * Currently we support:
343 : *
344 : * \li COMMENT_INI -- INI file like comments, these are introduced with
345 : * a semi-colon (`;`) and end with a newline.
346 : * \li COMMENT_SHELL -- Unix shell like comments, these are introduced
347 : * with a hash (`#`) and end with a newline.
348 : * \li COMMENT_CPP -- C++ like comments, these are introduced with two
349 : * slashes (`//`) and end with a newline.
350 : *
351 : * Right now we only support line comments. Configuration entries cannot
352 : * include comments. A comment character can be preceeded by spaces and
353 : * tabs.
354 : *
355 : * Line continuation is taken in account with comments. So the following
356 : * when the line continuation is set to Unix is one long comment:
357 : *
358 : * \code
359 : * # line continuation works with comments \
360 : * just like with any other line... because the \
361 : * continuation character and the newline characters \
362 : * just get removed before the get_line() function \
363 : * returns...
364 : * \endcode
365 : *
366 : * \return The comment flags.
367 : *
368 : * \sa is_comment()
369 : */
370 26483 : comment_t conf_file_setup::get_comment() const
371 : {
372 26483 : return f_comment;
373 : }
374 :
375 :
376 : /** \brief Get the accepted section operators.
377 : *
378 : * This function returns the flags representing which of the
379 : * section operators are accepted.
380 : *
381 : * We currently support the following types of sections:
382 : *
383 : * \li SECTION_OPERATOR_NONE -- no sections are accepted.
384 : * \li SECTION_OPERATOR_C -- the period (`.`) is viewed as a section/name
385 : * separator as when you access a variable member in a structure.
386 : * \li SECTION_OPERATOR_CPP -- the scope operator (`::`) is viewed as a
387 : * section/name separator; if used at the very beginning, it is viewed
388 : * as "global scope" and whatever other section is currently active is
389 : * ignored.
390 : * \li SECTION_OPERATOR_BLOCK -- the configuration files can include
391 : * opening (`{`) and closing (`}`) curvly brackets to group parameters
392 : * together; a name must preceed the opening bracket, it represents
393 : * the section name.
394 : * \li SECTION_OPERATOR_INI_FILE -- like in the MS-DOS .ini files, the
395 : * configuration file can include square brackets to mark sections; this
396 : * method limits the number of section names to one level.
397 : *
398 : * \bug
399 : * The INI file support does not verify that a section name does not
400 : * itself include more sub-sections. For example, the following would
401 : * be three section names:
402 : * \bug
403 : * \code
404 : * [a::b::c]
405 : * var=123
406 : * \endcode
407 : * \bug
408 : * So in effect, the variable named `var` ends up in section `a`,
409 : * sub-section `b`, and sub-sub-section `c` (or section `a::b::c`.)
410 : * Before saving the results in the parameters, all section operators
411 : * get transformed to the C++ scope (`::`) operator, which is why that
412 : * operator used in any name ends up looking like a section separator.
413 : */
414 46071 : section_operator_t conf_file_setup::get_section_operator() const
415 : {
416 46071 : return f_section_operator;
417 : }
418 :
419 :
420 : /** \brief Transform the setup in a URL.
421 : *
422 : * This function transforms the configuration file setup in a unique URL.
423 : * This URL allows us to verify that two setup are the same so when
424 : * attempting to reload the same configuration file, we can make sure
425 : * you are attempting to do so with the same URL.
426 : *
427 : * This is because trying to read the same file with, for example, line
428 : * continuation set to Unix the first time and then set to MS-DOS the
429 : * second time would not load the same thing is either line continuation
430 : * was used.
431 : *
432 : * \todo
433 : * We should look into have a set_config_url() or have a constructor
434 : * which accepts a URL.
435 : *
436 : * \return The URL representing this setup.
437 : */
438 42074 : std::string conf_file_setup::get_config_url() const
439 : {
440 42074 : if(f_url.empty())
441 : {
442 57164 : std::stringstream ss;
443 :
444 : ss << "file://"
445 85746 : << (f_filename.empty()
446 57164 : ? "/<empty>"
447 28582 : : f_filename);
448 :
449 57164 : std::vector<std::string> params;
450 28582 : if(f_line_continuation != line_continuation_t::line_continuation_unix)
451 : {
452 46274 : std::string name;
453 23137 : switch(f_line_continuation)
454 : {
455 4223 : case line_continuation_t::line_continuation_single_line:
456 4223 : name = "single-line";
457 4223 : break;
458 :
459 4727 : case line_continuation_t::line_continuation_rfc_822:
460 4727 : name = "rfc-822";
461 4727 : break;
462 :
463 4727 : case line_continuation_t::line_continuation_msdos:
464 4727 : name = "msdos";
465 4727 : break;
466 :
467 : // we should not ever receive this one since we don't enter
468 : // this block when the value is "unix"
469 : //
470 : //case line_continuation_t::line_continuation_unix:
471 : // name = "unix";
472 : // break;
473 :
474 4728 : case line_continuation_t::line_continuation_fortran:
475 4728 : name = "fortran";
476 4728 : break;
477 :
478 4727 : case line_continuation_t::line_continuation_semicolon:
479 4727 : name = "semi-colon";
480 4727 : break;
481 :
482 5 : default:
483 5 : throw getopt_logic_error("unexpected line continuation.");
484 :
485 : }
486 23132 : params.push_back("line-continuation=" + name);
487 : }
488 :
489 28577 : if(f_assignment_operator != ASSIGNMENT_OPERATOR_EQUAL)
490 : {
491 42326 : std::vector<std::string> assignments;
492 21163 : if((f_assignment_operator & ASSIGNMENT_OPERATOR_EQUAL) != 0)
493 : {
494 10577 : assignments.push_back("equal");
495 : }
496 21163 : if((f_assignment_operator & ASSIGNMENT_OPERATOR_COLON) != 0)
497 : {
498 14111 : assignments.push_back("colon");
499 : }
500 21163 : if((f_assignment_operator & ASSIGNMENT_OPERATOR_SPACE) != 0)
501 : {
502 14104 : assignments.push_back("space");
503 : }
504 21163 : if(!assignments.empty())
505 : {
506 21163 : params.push_back("assignment-operator=" + boost::algorithm::join(assignments, ","));
507 : }
508 : }
509 :
510 : if(f_comment != COMMENT_INI | COMMENT_SHELL)
511 : {
512 57154 : std::vector<std::string> comment;
513 28577 : if((f_comment & COMMENT_INI) != 0)
514 : {
515 12836 : comment.push_back("ini");
516 : }
517 28577 : if((f_comment & COMMENT_SHELL) != 0)
518 : {
519 12381 : comment.push_back("shell");
520 : }
521 28577 : if((f_comment & COMMENT_CPP) != 0)
522 : {
523 12379 : comment.push_back("cpp");
524 : }
525 28577 : if((f_comment & COMMENT_SAVE) != 0)
526 : {
527 0 : comment.push_back("save");
528 : }
529 28577 : if(comment.empty())
530 : {
531 3816 : params.push_back("comment=none");
532 : }
533 : else
534 : {
535 24761 : params.push_back("comment=" + boost::algorithm::join(comment, ","));
536 : }
537 : }
538 :
539 28577 : if(f_section_operator != SECTION_OPERATOR_INI_FILE)
540 : {
541 53142 : std::vector<std::string> section_operator;
542 26571 : if((f_section_operator & SECTION_OPERATOR_C) != 0)
543 : {
544 13005 : section_operator.push_back("c");
545 : }
546 26571 : if((f_section_operator & SECTION_OPERATOR_CPP) != 0)
547 : {
548 12996 : section_operator.push_back("cpp");
549 : }
550 26571 : if((f_section_operator & SECTION_OPERATOR_BLOCK) != 0)
551 : {
552 12991 : section_operator.push_back("block");
553 : }
554 26571 : if((f_section_operator & SECTION_OPERATOR_INI_FILE) != 0)
555 : {
556 11445 : section_operator.push_back("ini-file");
557 : }
558 26571 : if(!section_operator.empty())
559 : {
560 24457 : params.push_back("section-operator=" + boost::algorithm::join(section_operator, ","));
561 : }
562 : }
563 :
564 57154 : std::string const query_string(boost::algorithm::join(params, "&"));
565 28577 : if(!query_string.empty())
566 : {
567 : ss << '?'
568 28577 : << query_string;
569 : }
570 :
571 28577 : f_url = ss.str();
572 : }
573 :
574 42069 : return f_url;
575 : }
576 :
577 :
578 : /** \brief Retrieve the separator to use within names.
579 : *
580 : * A parameter name can include dashes or underscores. The advgetopt supports
581 : * either one and internally, it saves the names with dashes. Most other tools,
582 : * though will only expect one or the other, most likely underscores, which is the
583 : * default here.
584 : *
585 : * You can specify either one when building the conf_file_setup. At the moment,
586 : * there is no option to keep the dashes and underscores as found in the input.
587 : *
588 : * \return Whether to save the names with underscores or dashes.
589 : */
590 3 : name_separator_t conf_file_setup::get_name_separator() const
591 : {
592 3 : return f_name_separator;
593 : }
594 :
595 :
596 :
597 :
598 :
599 :
600 :
601 590 : parameter_value::parameter_value()
602 : {
603 590 : }
604 :
605 :
606 604 : parameter_value::parameter_value(parameter_value const & rhs)
607 : : f_value(rhs.f_value)
608 : , f_comment(rhs.f_comment)
609 604 : , f_line(rhs.f_line)
610 : {
611 604 : }
612 :
613 :
614 0 : parameter_value::parameter_value(std::string const & value)
615 0 : : f_value(value)
616 : {
617 0 : }
618 :
619 :
620 0 : parameter_value & parameter_value::operator = (parameter_value const & rhs)
621 : {
622 0 : if(this != &rhs)
623 : {
624 0 : f_value = rhs.f_value;
625 0 : f_comment = rhs.f_comment;
626 0 : f_line = rhs.f_line;
627 : }
628 0 : return *this;
629 : }
630 :
631 :
632 601 : parameter_value & parameter_value::operator = (std::string const & value)
633 : {
634 601 : f_value = value;
635 601 : return *this;
636 : }
637 :
638 :
639 603 : parameter_value::operator std::string () const
640 : {
641 603 : return f_value;
642 : }
643 :
644 :
645 0 : void parameter_value::set_value(std::string const & value)
646 : {
647 0 : f_value = value;
648 0 : }
649 :
650 :
651 590 : void parameter_value::set_comment(std::string const & comment)
652 : {
653 : // ignore if the comment is only composed of spaces, tabs, empty lines
654 : //
655 1180 : std::string const trimmed(snapdev::trim_string(comment));
656 590 : if(trimmed.empty())
657 : {
658 590 : f_comment.clear();
659 : }
660 : else
661 : {
662 : // IMPORTANT: we do not save the trimmed version we only use that
663 : // to make sure it's not a completely empty comment
664 : //
665 0 : f_comment = comment;
666 : }
667 590 : }
668 :
669 :
670 590 : void parameter_value::set_line(int line)
671 : {
672 590 : f_line = line;
673 590 : }
674 :
675 :
676 3 : std::string const & parameter_value::get_value() const
677 : {
678 3 : return f_value;
679 : }
680 :
681 :
682 4 : std::string parameter_value::get_comment(bool ensure_newline) const
683 : {
684 4 : if(f_comment.empty())
685 : {
686 4 : return f_comment;
687 : }
688 :
689 0 : if(ensure_newline
690 0 : && f_comment.back() != '\n')
691 : {
692 0 : return f_comment + '\n';
693 : }
694 :
695 0 : return f_comment;
696 : }
697 :
698 :
699 3 : int parameter_value::get_line() const
700 : {
701 3 : return f_line;
702 : }
703 :
704 :
705 :
706 :
707 :
708 :
709 :
710 :
711 :
712 : /** \brief Create and read a conf_file.
713 : *
714 : * This function creates a new conf_file object unless one with the same
715 : * filename already exists.
716 : *
717 : * If the configuration file was already loaded, then that pointer gets
718 : * returned instead of reloading the file. There is currently no API to
719 : * allow for the removal because another thread or function may have
720 : * the existing pointer cached and we want all instances of a configuration
721 : * file to be the same (i.e. if you update the value of a parameter then
722 : * that new value should be visible by all the users of that configuration
723 : * file.) Therefore, you can think of a configuration file as a global
724 : * variable.
725 : *
726 : * \note
727 : * Any number of call this function to load a given file always returns
728 : * exactly the same pointer.
729 : *
730 : * \todo
731 : * With the communicator, we will at some point implement a class
732 : * used to detect that a file changed, allowing us to get a signal
733 : * and reload the file as required. This get_conf_file() function
734 : * will greatly benefit from such since that way we can automatically
735 : * reload the configuration file. In other words, process A could
736 : * make a change, then process B reloads and sees the change that
737 : * process A made. Such an implementation will require a proper
738 : * locking mechanism of the configuration files while modifications
739 : * are being performed.
740 : *
741 : * \param[in] setup The settings to be used in this configuration file reader.
742 : *
743 : * \return A pointer to the configuration file data.
744 : */
745 3360 : conf_file::pointer_t conf_file::get_conf_file(conf_file_setup const & setup)
746 : {
747 6720 : cppthread::guard lock(get_global_mutex());
748 :
749 3360 : auto it(g_conf_files.find(setup.get_filename()));
750 3360 : if(it != g_conf_files.end())
751 : {
752 3036 : if(it->second->get_setup().get_config_url() != setup.get_config_url())
753 : {
754 : throw getopt_logic_error("trying to load configuration file \""
755 5250 : + setup.get_config_url()
756 7875 : + "\" but an existing configuration file with the same name was loaded with URL: \""
757 10500 : + it->second->get_setup().get_config_url()
758 7875 : + "\".");
759 : }
760 411 : return it->second;
761 : }
762 :
763 : // TODO: look into not blocking "forever"?
764 : //
765 648 : conf_file::pointer_t cf(new conf_file(setup));
766 324 : g_conf_files[setup.get_filename()] = cf;
767 324 : return cf;
768 : }
769 :
770 :
771 : /** \brief Save the configuration file.
772 : *
773 : * This function saves the current data from this configuration file to
774 : * the output file. It overwrites the existing file.
775 : *
776 : * Note that when you load configuration files for the command line, you
777 : * may load data from many different files. This function only handles
778 : * the data found in this very file and only that data and whatever
779 : * modifications you made is included in the output .
780 : *
781 : * If the conf_file is not marked as modified, the function returns
782 : * immediately with true.
783 : *
784 : * The assignment operator used is the space if allowed, the colon if
785 : * allowed, otherwise it falls back to the equal operator. At this time,
786 : * the colon and equal operators are not preceeded or followed by a space
787 : * (i.e. `name=value`).
788 : *
789 : * \todo
790 : * Fix the canonicalization of the filename on a first save. Right now,
791 : * the original filename was used but the path could change when saving
792 : * (see the realpath() call in the constructor; this needs to be fixed).
793 : *
794 : * \param[in] backup_extension If not empty, create a backup with that
795 : * extension.
796 : * \param[in] replace_backup If true and a backup exists, replace it.
797 : * \param[in] prepend_warning Whether to write a warning at the start of
798 : * the file.
799 : * \param[in] output_filename The output filename; if empty, fallback to
800 : * the filename defined in conf_file_setup.
801 : *
802 : * \return true if the save worked as expected.
803 : */
804 2 : bool conf_file::save_configuration(
805 : std::string backup_extension
806 : , bool replace_backup
807 : , bool prepend_warning
808 : , std::string output_filename)
809 : {
810 2 : if(f_modified)
811 : {
812 1 : std::string const & filename(output_filename.empty()
813 1 : ? f_setup.get_filename()
814 1 : : output_filename);
815 :
816 : // create backup?
817 : //
818 1 : if(!backup_extension.empty())
819 : {
820 1 : struct stat s = {};
821 1 : if(stat(filename.c_str(), &s) == 0)
822 : {
823 2 : if(backup_extension[0] != '.'
824 1 : && backup_extension[0] != '~')
825 : {
826 0 : backup_extension.insert(0, 1, '.');
827 : }
828 :
829 2 : std::string const backup_filename(filename + backup_extension);
830 :
831 1 : if(replace_backup
832 1 : || access(backup_filename.c_str(), F_OK) != 0)
833 : {
834 2 : if(unlink(backup_filename.c_str()) != 0
835 1 : && errno != ENOENT)
836 : {
837 : f_errno = errno; // LCOV_EXCL_LINE
838 : return false; // LCOV_EXCL_LINE
839 : }
840 :
841 1 : if(rename(filename.c_str(), backup_filename.c_str()) != 0)
842 : {
843 : f_errno = errno; // LCOV_EXCL_LINE
844 : return false; // LCOV_EXCL_LINE
845 : }
846 : }
847 : }
848 : }
849 :
850 : // TODO: look at adding the user:group info
851 : //
852 1 : if(snapdev::mkdir_p(filename, true) != 0)
853 : {
854 : f_errno = errno; // LCOV_EXCL_LINE
855 : return false; // LCOV_EXCL_LINE
856 : }
857 :
858 : // save parameters to file
859 : //
860 2 : std::ofstream conf;
861 1 : conf.open(filename);
862 1 : if(!conf.is_open())
863 : {
864 : f_errno = errno; // LCOV_EXCL_LINE
865 : return false; // LCOV_EXCL_LINE
866 : }
867 :
868 : // header warning with date & time
869 : //
870 : // (but only if the user doesn't already save comments otherwise
871 : // that one would get re-added each time--some form of recursivity)
872 : //
873 1 : if(prepend_warning
874 2 : && (f_parameters.empty()
875 3 : || f_parameters.begin()->second.get_comment().empty()))
876 : {
877 1 : time_t const now(time(nullptr));
878 1 : tm t;
879 1 : gmtime_r(&now, &t);
880 1 : char str_date[16];
881 1 : strftime(str_date, sizeof(str_date), "%Y/%m/%d", &t);
882 1 : char str_time[16];
883 1 : strftime(str_time, sizeof(str_time), "%H:%M:%S", &t);
884 :
885 1 : conf << "# This file was auto-generated by advgetopt on " << str_date << " at " << str_time << "." << std::endl
886 1 : << "# Making modifications here is likely safe unless the tool handling this" << std::endl
887 1 : << "# configuration file is actively working on it while you do the edits." << std::endl;
888 : }
889 4 : for(auto p : f_parameters)
890 : {
891 : // if the value has a comment, output it
892 : //
893 3 : conf << p.second.get_comment(true);
894 :
895 3 : if(f_setup.get_name_separator() == NAME_SEPARATOR_DASHES)
896 : {
897 : // `first` already has dashes
898 : //
899 0 : conf << p.first;
900 : }
901 : else
902 : {
903 6 : for(auto const c : p.first)
904 : {
905 3 : conf << (c == '-' ? '_' : c);
906 : }
907 : }
908 :
909 3 : if((f_setup.get_assignment_operator() & ASSIGNMENT_OPERATOR_SPACE) != 0)
910 : {
911 0 : conf << ' ';
912 : }
913 3 : else if((f_setup.get_assignment_operator() & ASSIGNMENT_OPERATOR_COLON) != 0)
914 : {
915 0 : conf << ':';
916 : }
917 : else
918 : {
919 3 : conf << '=';
920 : }
921 :
922 : // prevent saving \r and \n characters as is when part of the
923 : // value; also double \ otherwise reading those back would fail
924 : //
925 3 : std::string const value(snapdev::string_replace_many(
926 3 : p.second.get_value()
927 : , {
928 : { "\\", "\\\\" },
929 : { "\\r", "\\r" },
930 : { "\\n", "\\n" },
931 : { "\\t", "\\t" },
932 9 : }));
933 3 : conf << value << std::endl;
934 :
935 3 : if(!conf)
936 : {
937 : return false; // LCOV_EXCL_LINE
938 : }
939 : }
940 :
941 : // it all worked, it's considered saved now
942 : //
943 1 : f_modified = false;
944 : }
945 :
946 2 : return true;
947 : }
948 :
949 :
950 : /** \brief Initialize and read a configuration file.
951 : *
952 : * This constructor initializes this conf_file object and then reads the
953 : * corresponding configuration file.
954 : *
955 : * Note that you have to use the create_conf_file() function for you
956 : * to be able to create a configuration file. It is done that way became
957 : * a file can be read only once. Once loaded, it gets cached until your
958 : * application quits.
959 : *
960 : * \param[in] setup The configuration file setup.
961 : */
962 324 : conf_file::conf_file(conf_file_setup const & setup)
963 324 : : f_setup(setup)
964 : {
965 324 : read_configuration();
966 324 : }
967 :
968 :
969 : /** \brief Get the configuration file setup.
970 : *
971 : * This function returns a copy of the setup used to load this
972 : * configuration file.
973 : *
974 : * \note
975 : * This function has no mutex protection because the setup can't
976 : * change so there is no multi-thread protection necessary (the
977 : * fact that you hold a shared pointer to the conf_file object
978 : * is enough protection in this case.)
979 : *
980 : * \return A reference to this configuration file setup.
981 : */
982 5809 : conf_file_setup const & conf_file::get_setup() const
983 : {
984 5809 : return f_setup;
985 : }
986 :
987 :
988 : /** \brief Add a callback to detect when changes happen.
989 : *
990 : * This function is used to attach a callback to this configuration file.
991 : * This is useful if you'd like to know when a change happen to a parameter
992 : * in this configuration file.
993 : *
994 : * The callbacks get called when:
995 : *
996 : * \li The set_parameter() is called and the parameter gets created.
997 : * \li The set_parameter() is called and the parameter gets updated.
998 : * \li The erase_parameter() is called and the parameter gets erased.
999 : *
1000 : * You can cancel your callback by calling the remove_callback() function
1001 : * with the identifier returned by this function.
1002 : *
1003 : * To attach another object to your callback, you can either create
1004 : * a callback which is attached to your object and a function
1005 : * member or use std::bind() to attach the object to the function
1006 : * call.
1007 : *
1008 : * If you specifcy a \p parameter_name, the callback is called only if the
1009 : * parameter has that specific name.
1010 : *
1011 : * \param[in] c The new callback std::function.
1012 : * \param[in] parameter_name The parameter name or an empty string.
1013 : *
1014 : * \return The callback identifier (useful if you want to be able to remove it).
1015 : */
1016 2 : conf_file::callback_id_t conf_file::add_callback(
1017 : callback_t const & c
1018 : , std::string const & parameter_name)
1019 : {
1020 4 : cppthread::guard lock(get_global_mutex());
1021 :
1022 2 : ++f_next_callback_id;
1023 2 : f_callbacks.emplace_back(f_next_callback_id, c, parameter_name);
1024 4 : return f_next_callback_id;
1025 : }
1026 :
1027 :
1028 : /** \brief Remove a callback.
1029 : *
1030 : * This function is the opposite of the add_callback(). It removes a callback
1031 : * that you previously added. This is useful if you are interested in hearing
1032 : * about the changing values when set a first time but are not interested at
1033 : * all about future changes.
1034 : *
1035 : * \param[in] id The id returned by the add_callback() function.
1036 : */
1037 2 : void conf_file::remove_callback(callback_id_t id)
1038 : {
1039 4 : cppthread::guard lock(get_global_mutex());
1040 :
1041 2 : auto it(std::find_if(
1042 : f_callbacks.begin()
1043 : , f_callbacks.end()
1044 1 : , [id](auto e)
1045 1 : {
1046 1 : return e.f_id == id;
1047 3 : }));
1048 2 : if(it != f_callbacks.end())
1049 : {
1050 1 : f_callbacks.erase(it);
1051 : }
1052 2 : }
1053 :
1054 :
1055 : /** \brief Call whenever the value changed so we can handle callbacks.
1056 : *
1057 : * This function is called on a change of the internal values.
1058 : *
1059 : * The function is used to call the callbacks that were added to this
1060 : * option_info object. The function first copies the existing list of
1061 : * callbacks so you can safely update the list from within a callback.
1062 : *
1063 : * \warning
1064 : * Destroying your advgetopt::getopt option is not safe while a callback
1065 : * is running.
1066 : */
1067 27 : void conf_file::value_changed(
1068 : callback_action_t action
1069 : , std::string const & parameter_name
1070 : , std::string const & value)
1071 : {
1072 54 : callback_vector_t callbacks;
1073 27 : callbacks.reserve(f_callbacks.size());
1074 :
1075 : {
1076 54 : cppthread::guard lock(get_global_mutex());
1077 27 : callbacks = f_callbacks;
1078 : }
1079 :
1080 33 : for(auto & e : callbacks)
1081 : {
1082 12 : if(e.f_parameter_name.empty()
1083 6 : || e.f_parameter_name == parameter_name)
1084 : {
1085 6 : e.f_callback(shared_from_this(), action, parameter_name, value);
1086 : }
1087 : }
1088 27 : }
1089 :
1090 :
1091 : /** \brief Whether an input file was found.
1092 : *
1093 : * This function returns true if a file was opened for reading. Whether the
1094 : * file is valid is not marked in this flag.
1095 : *
1096 : * If you want to know whether an error occurred while reading the file,
1097 : * try the get_errno().
1098 : *
1099 : * \return true if a file was read.
1100 : *
1101 : * \sa get_errno()
1102 : */
1103 0 : bool conf_file::exists() const
1104 : {
1105 0 : cppthread::guard lock(get_global_mutex());
1106 :
1107 0 : return f_exists;
1108 : }
1109 :
1110 :
1111 : /** \brief Get the error number opening/reading the configuration file.
1112 : *
1113 : * The class registers the errno value whenever an I/O error happens
1114 : * while handling the configuration file. In most cases the function
1115 : * is expected to return 0.
1116 : *
1117 : * The ENOENT error should not happen since the setup is going to be
1118 : * marked as invalid when a configuration file does not exist and
1119 : * you should not end up creation a conf_file object when that
1120 : * happens. However, it is expected when you want to make some
1121 : * changes to a few parameters and save them back to file (i.e.
1122 : * the very first time there will be no file under the writable
1123 : * configuration folder.)
1124 : *
1125 : * \return The last errno detected while accessing the configuration file.
1126 : */
1127 153 : int conf_file::get_errno() const
1128 : {
1129 306 : cppthread::guard lock(get_global_mutex());
1130 :
1131 306 : return f_errno;
1132 : }
1133 :
1134 :
1135 : /** \brief Attach a variables object to the configuration file.
1136 : *
1137 : * The get_parameter() of the configuration file can be transformed to
1138 : * apply user variables to the values of the parameters.
1139 : *
1140 : * By default, this is not used by the getopt since it loads the values
1141 : * in its tables which then apply the variable when the get_value() is
1142 : * called on the getopt_info objects.
1143 : *
1144 : * \note
1145 : * You can detach a variables object by attaching a null pointer.
1146 : *
1147 : * \param[in] variables The variable to attach to this configuration file.
1148 : */
1149 1 : void conf_file::set_variables(variables::pointer_t variables)
1150 : {
1151 1 : f_variables = variables;
1152 1 : }
1153 :
1154 :
1155 : /** \brief Retrieve the currently attached variables.
1156 : *
1157 : * This function returns the attached variables. This function may return
1158 : * a nullptr.
1159 : *
1160 : * \return The variables attached to the configuration file or nullptr.
1161 : */
1162 1 : variables::pointer_t conf_file::get_variables() const
1163 : {
1164 1 : return f_variables;
1165 : }
1166 :
1167 :
1168 : /** \brief Get a list of sections.
1169 : *
1170 : * This function returns a copy of the list of sections defined in
1171 : * this configuration file. In most cases, you should not need this
1172 : * function since you are expected to know what parameters may be
1173 : * defined. There are times though when it can be very practical.
1174 : * For example, the options_config.cpp makes use of it since each
1175 : * section is a parameter which we do not know the name of until
1176 : * we have access to this array of sections.
1177 : *
1178 : * \note
1179 : * We return a list because in a multithread environment another thread
1180 : * may decide to make changes to the list of parameters which has the
1181 : * side effect of eventually adding a section.
1182 : *
1183 : * \return A copy of the list of sections.
1184 : */
1185 730 : conf_file::sections_t conf_file::get_sections() const
1186 : {
1187 1460 : cppthread::guard lock(get_global_mutex());
1188 :
1189 1460 : return f_sections;
1190 : }
1191 :
1192 :
1193 : /** \brief Get a list of parameters.
1194 : *
1195 : * This function returns a copy of the list of parameters defined in
1196 : * this configuration file.
1197 : *
1198 : * \note
1199 : * We return a list because in a multithread environment another thread
1200 : * may decide to make changes to the list of parameters (including
1201 : * erasing a parameter.)
1202 : *
1203 : * \remarks
1204 : * Note that the parameters, when retrieved in this way, are returned raw.
1205 : * This means the variables are not going to be applied to the values. You
1206 : * can still do so by yourself calling the process_value() function.
1207 : *
1208 : * \return A copy of the list of parameters.
1209 : */
1210 402 : conf_file::parameters_t conf_file::get_parameters() const
1211 : {
1212 804 : cppthread::guard lock(get_global_mutex());
1213 :
1214 804 : return f_parameters;
1215 : }
1216 :
1217 :
1218 : /** \brief Check whether a parameter is defined.
1219 : *
1220 : * This function checks for the existance of a parameter. It is a good
1221 : * idea to first check for the existance of a parameter since the
1222 : * get_parameter() function may otherwise return an empty string and
1223 : * you cannot know whether that empty string means that the parameter
1224 : * was not defined or it was set to the empty string.
1225 : *
1226 : * \param[in] name The name of the parameter to check.
1227 : *
1228 : * \return true if the parameter is defined, false otherwise.
1229 : *
1230 : * \sa get_parameter()
1231 : * \sa set_parameter()
1232 : */
1233 667 : bool conf_file::has_parameter(std::string name) const
1234 : {
1235 667 : std::replace(name.begin(), name.end(), '_', '-');
1236 :
1237 1334 : cppthread::guard lock(get_global_mutex());
1238 :
1239 667 : auto it(f_parameters.find(name));
1240 1334 : return it != f_parameters.end();
1241 : }
1242 :
1243 :
1244 : /** \brief Get the named parameter.
1245 : *
1246 : * This function searches for the specified parameter. If that parameter
1247 : * exists, then its value is returned. Note that the value of a parameter
1248 : * may be the empty string.
1249 : *
1250 : * If the parameter does not exist, the function returns the empty string.
1251 : * To distinguish between an undefined parameter and a parameter set to
1252 : * the empty string, use the has_parameter() function.
1253 : *
1254 : * \param[in] name The name of the parameter to retrieve.
1255 : *
1256 : * \return The current value of the parameter or an empty string.
1257 : *
1258 : * \sa has_parameter()
1259 : * \sa set_parameter()
1260 : */
1261 633 : std::string conf_file::get_parameter(std::string name) const
1262 : {
1263 633 : std::replace(name.begin(), name.end(), '_', '-');
1264 :
1265 1266 : cppthread::guard lock(get_global_mutex());
1266 :
1267 633 : auto it(f_parameters.find(name));
1268 633 : if(it != f_parameters.end())
1269 : {
1270 490 : if(f_variables != nullptr)
1271 : {
1272 5 : return f_variables->process_value(it->second);
1273 : }
1274 : else
1275 : {
1276 485 : return it->second;
1277 : }
1278 : }
1279 143 : return std::string();
1280 : }
1281 :
1282 :
1283 : /** \brief Set a parameter.
1284 : *
1285 : * This function sets a parameter to the specified value.
1286 : *
1287 : * The name of the value includes the \p section names and the \p name
1288 : * parameter concatenated with a C++ scope operator (::) in between
1289 : * (unless \p section is the empty string in which case no scope operator
1290 : * gets added).
1291 : *
1292 : * When the \p name parameter starts with a scope parameter, the \p section
1293 : * parameter is ignored. This allows one to ignore the current section
1294 : * (i.e. the last '[...]' or any '\<name> { ... }').
1295 : *
1296 : * The \p section parameter is a list of section names separated by
1297 : * the C++ scope operator (::).
1298 : *
1299 : * The \p name parameter may include C (.) and/or C++ (::) section
1300 : * separators when the configuration file supports those. Internally,
1301 : * those get moved to the \p section parameter. That allows us to
1302 : * verify that the number of sections is valid.
1303 : *
1304 : * This function may be called any number of time. The last value is
1305 : * the one kept. While reading the configuration file, though, a warning
1306 : * is generated when a parameter gets overwritten since this is often the
1307 : * source of a problem.
1308 : *
1309 : * In the following configuration file:
1310 : *
1311 : * \code
1312 : * var=name
1313 : * var=twice
1314 : * \endcode
1315 : *
1316 : * the variable named `var` is set to `twice` on exit. A warning
1317 : * will have been generated about the fact that the variable was
1318 : * set twice while reading the configuration file.
1319 : *
1320 : * The full name of the parameter (i.e. section + name) cannot include any
1321 : * of the following characters:
1322 : *
1323 : * \li control characters (any character between 0x00 and 0x1F)
1324 : * \li a space (0x20)
1325 : * \li a backslash (`\`)
1326 : * \li quotation (`"` and `'`)
1327 : * \li comment (';', '#', '/')
1328 : * \li assignment operators ('=', ':', '?', '+')
1329 : *
1330 : * \note
1331 : * The \p section and \p name parameters have underscores (`_`)
1332 : * replaced with dashes (`-`) before getting used. The very first
1333 : * character can be a dash. This allows you to therefore create
1334 : * a form of internal parameters; i.e. parameters which cannot
1335 : * appear in a configuration file, an environment variable or on
1336 : * the command line (where parameter are not allowed to start with
1337 : * a dash).
1338 : *
1339 : * \warning
1340 : * It is important to note that when a \p name includes a C++ scope
1341 : * operator, the final parameter name looks like it includes a section
1342 : * name (i.e. the name "a::b", when the C++ section flag is not set,
1343 : * is accepted as is; so the final parameter name is going to be "a::b"
1344 : * and therefore it will include what looks like a section name.)
1345 : * There should not be any concern about this small \em glitch though
1346 : * since you do not have to accept any such parameter.
1347 : *
1348 : * \todo
1349 : * The section/name combo should be dealt with inside this function
1350 : * instead of outside, especially if the are to support all the
1351 : * namespace operators.
1352 : *
1353 : * \param[in] section The list of sections or an empty string.
1354 : * \param[in] name The name of the parameter.
1355 : * \param[in] value The value of the parameter.
1356 : * \param[in] comment The comment appearing before value.
1357 : *
1358 : * \return true if the parameter was modified, false if an error occurs.
1359 : */
1360 720 : bool conf_file::set_parameter(
1361 : std::string section
1362 : , std::string name
1363 : , std::string const & value
1364 : , std::string const & comment)
1365 : {
1366 : // use the tokenize_string() function because we do not want to support
1367 : // quoted strings in this list of sections which our split_string()
1368 : // does automatically
1369 : //
1370 1440 : string_list_t section_list;
1371 :
1372 720 : std::replace(section.begin(), section.end(), '_', '-');
1373 720 : std::replace(name.begin(), name.end(), '_', '-');
1374 :
1375 720 : char const * n(name.c_str());
1376 :
1377 : // global scope? if so ignore the section parameter
1378 : //
1379 1440 : if((f_setup.get_section_operator() & SECTION_OPERATOR_CPP) != 0
1380 32 : && n[0] == ':'
1381 722 : && n[1] == ':')
1382 : {
1383 2 : do
1384 : {
1385 4 : ++n;
1386 : }
1387 4 : while(*n == ':');
1388 : }
1389 : else
1390 : {
1391 1436 : snapdev::tokenize_string(section_list
1392 : , section
1393 : , "::"
1394 : , true
1395 1436 : , std::string()
1396 : , &snapdev::string_predicate<string_list_t>);
1397 : }
1398 :
1399 720 : char const * s(n);
1400 8496 : while(*n != '\0')
1401 : {
1402 7780 : if((f_setup.get_section_operator() & SECTION_OPERATOR_C) != 0
1403 3890 : && *n == '.')
1404 : {
1405 32 : if(s == n)
1406 : {
1407 2 : cppthread::log << cppthread::log_level_t::error
1408 1 : << "option name \""
1409 1 : << name
1410 1 : << "\" cannot start with a period (.)."
1411 2 : << cppthread::end;
1412 1 : return false;
1413 : }
1414 31 : section_list.push_back(std::string(s, n - s));
1415 8 : do
1416 : {
1417 39 : ++n;
1418 : }
1419 39 : while(*n == '.');
1420 31 : s = n;
1421 : }
1422 7716 : else if((f_setup.get_section_operator() & SECTION_OPERATOR_CPP) != 0
1423 66 : && n[0] == ':'
1424 3870 : && n[1] == ':')
1425 : {
1426 12 : if(s == n)
1427 : {
1428 2 : cppthread::log << cppthread::log_level_t::error
1429 1 : << "option name \""
1430 1 : << name
1431 1 : << "\" cannot start with a scope operator (::)."
1432 2 : << cppthread::end;
1433 1 : return false;
1434 : }
1435 11 : section_list.push_back(std::string(s, n - s));
1436 11 : do
1437 : {
1438 22 : ++n;
1439 : }
1440 22 : while(*n == ':');
1441 11 : s = n;
1442 : }
1443 : else
1444 : {
1445 3846 : ++n;
1446 : }
1447 : }
1448 718 : if(s == n)
1449 : {
1450 4 : cppthread::log << cppthread::log_level_t::error
1451 2 : << "option name \""
1452 2 : << name
1453 2 : << "\" cannot end with a section operator or be empty."
1454 4 : << cppthread::end;
1455 2 : return false;
1456 : }
1457 1432 : std::string param_name(s, n - s);
1458 :
1459 1432 : std::string const section_name(boost::algorithm::join(section_list, "::"));
1460 :
1461 1432 : if(f_setup.get_section_operator() == SECTION_OPERATOR_NONE
1462 716 : && !section_list.empty())
1463 : {
1464 2 : cppthread::log << cppthread::log_level_t::error
1465 1 : << "option name \""
1466 1 : << name
1467 1 : << "\" cannot be added to section \""
1468 1 : << section_name
1469 1 : << "\" because there is no section support for this configuration file."
1470 2 : << cppthread::end;
1471 1 : return false;
1472 : }
1473 1430 : if((f_setup.get_section_operator() & SECTION_OPERATOR_ONE_SECTION) != 0
1474 715 : && section_list.size() > 1)
1475 : {
1476 10 : cppthread::log << cppthread::log_level_t::error
1477 5 : << "option name \""
1478 5 : << name
1479 5 : << "\" cannot be added to section \""
1480 5 : << section_name
1481 5 : << "\" because this configuration only accepts one section level."
1482 10 : << cppthread::end;
1483 5 : return false;
1484 : }
1485 :
1486 710 : section_list.push_back(param_name);
1487 1420 : std::string const full_name(boost::algorithm::join(section_list, "::"));
1488 :
1489 : // verify that each section name only includes characters we accept
1490 : // for a parameter name
1491 : //
1492 : // WARNING: we do not test with full_name because it includes ':'
1493 : //
1494 1486 : for(auto sn : section_list)
1495 : {
1496 4981 : for(char const * f(sn.c_str()); *f != '\0'; ++f)
1497 : {
1498 4205 : switch(*f)
1499 : {
1500 109 : case '\001': // forbid controls
1501 : case '\002':
1502 : case '\003':
1503 : case '\004':
1504 : case '\005':
1505 : case '\006':
1506 : case '\007':
1507 : case '\010':
1508 : case '\011':
1509 : case '\012':
1510 : case '\013':
1511 : case '\014':
1512 : case '\015':
1513 : case '\016':
1514 : case '\017':
1515 : case '\020':
1516 : case '\021':
1517 : case '\022':
1518 : case '\023':
1519 : case '\024':
1520 : case '\025':
1521 : case '\026':
1522 : case '\027':
1523 : case '\030':
1524 : case '\031':
1525 : case '\032':
1526 : case '\033':
1527 : case '\034':
1528 : case '\035':
1529 : case '\036':
1530 : case '\037':
1531 : case ' ': // forbid spaces
1532 : case '\'': // forbid all quotes
1533 : case '"': // forbid all quotes
1534 : case ';': // forbid all comment operators
1535 : case '#': // forbid all comment operators
1536 : case '/': // forbid all comment operators
1537 : case '=': // forbid all assignment operators
1538 : case ':': // forbid all assignment operators
1539 : case '?': // forbid all assignment operators (for later)
1540 : case '+': // forbid all assignment operators (for later)
1541 : case '\\': // forbid backslashes
1542 218 : cppthread::log << cppthread::log_level_t::error
1543 109 : << "parameter \""
1544 109 : << full_name
1545 109 : << "\" on line "
1546 109 : << f_line
1547 109 : << " in configuration file \""
1548 109 : << f_setup.get_filename()
1549 109 : << "\" includes a character not acceptable for a section or parameter name (controls, space, quotes, and \";#/=:?+\\\")."
1550 218 : << cppthread::end;
1551 109 : return false;
1552 :
1553 : }
1554 : }
1555 : }
1556 :
1557 1202 : cppthread::guard lock(get_global_mutex());
1558 :
1559 : // add the section to the list of sections
1560 : //
1561 : // TODO: should we have a list of all the parent sections? Someone can
1562 : // write "a::b::c::d = 123" and we currently only get section
1563 : // "a::b::c", no section "a" and no section "a::b".
1564 : //
1565 601 : if(!section_name.empty())
1566 : {
1567 157 : f_sections.insert(section_name);
1568 : }
1569 :
1570 601 : callback_action_t action(callback_action_t::created);
1571 601 : auto it(f_parameters.find(full_name));
1572 601 : if(it == f_parameters.end())
1573 : {
1574 590 : f_parameters[full_name] = value;
1575 590 : f_parameters[full_name].set_comment(comment);
1576 590 : f_parameters[full_name].set_line(f_line);
1577 : }
1578 : else
1579 : {
1580 11 : if(f_reading)
1581 : {
1582 : // this is just a warning; it can be neat to know about such
1583 : // problems and fix them early
1584 : //
1585 4 : cppthread::log << cppthread::log_level_t::warning
1586 2 : << "parameter \""
1587 2 : << full_name
1588 2 : << "\" on line "
1589 2 : << f_line
1590 2 : << " in configuration file \""
1591 2 : << f_setup.get_filename()
1592 2 : << "\" was found twice in the same configuration file."
1593 4 : << cppthread::end;
1594 : }
1595 :
1596 11 : it->second = value;
1597 :
1598 11 : action = callback_action_t::updated;
1599 : }
1600 :
1601 601 : if(!f_reading)
1602 : {
1603 11 : f_modified = true;
1604 :
1605 11 : value_changed(action, full_name, value);
1606 : }
1607 :
1608 601 : return true;
1609 : }
1610 :
1611 :
1612 : /** \brief Erase the named parameter from this configuration file.
1613 : *
1614 : * This function can be used to remove the specified parameter from
1615 : * this configuration file.
1616 : *
1617 : * If that parameter is not defined in the file, then nothing happens.
1618 : *
1619 : * \param[in] name The name of the parameter to remove.
1620 : *
1621 : * \return true if the parameter was removed, false if it did not exist.
1622 : */
1623 17 : bool conf_file::erase_parameter(std::string name)
1624 : {
1625 17 : std::replace(name.begin(), name.end(), '_', '-');
1626 :
1627 17 : auto it(f_parameters.find(name));
1628 17 : if(it == f_parameters.end())
1629 : {
1630 1 : return false;
1631 : }
1632 :
1633 16 : f_parameters.erase(it);
1634 :
1635 16 : if(!f_reading)
1636 : {
1637 16 : f_modified = true;
1638 :
1639 16 : value_changed(callback_action_t::erased, name, std::string());
1640 : }
1641 :
1642 16 : return true;
1643 : }
1644 :
1645 :
1646 : /** \brief Clear the list of all existing parameters from this file.
1647 : *
1648 : * This function goes through the list of parameters it contains and
1649 : * erase each one of them in turn.
1650 : *
1651 : * The function calls the erase_parameter() function with each one of
1652 : * the parameter still in the list. It is done that way to make sure that
1653 : * the value_changed() function gets called as expected for each value.
1654 : */
1655 0 : void conf_file::erase_all_parameters()
1656 : {
1657 0 : while(!f_parameters.empty())
1658 : {
1659 0 : erase_parameter(f_parameters.begin()->first);
1660 : }
1661 0 : }
1662 :
1663 :
1664 : /** \brief Check whether this configuration file was modified.
1665 : *
1666 : * This function returns the value of the f_modified flag which is true
1667 : * if any value was createed, updated, or erased from the configuration
1668 : * file since after it was loaded.
1669 : *
1670 : * This tells you whether you should call the save() function, assuming
1671 : * you want to keep such changes.
1672 : *
1673 : * \return true if changes were made to this file parameters.
1674 : */
1675 10 : bool conf_file::was_modified() const
1676 : {
1677 10 : return f_modified;
1678 : }
1679 :
1680 :
1681 : /** \brief Read one characte from the input stream.
1682 : *
1683 : * This function reads one character from the input stream and returns it
1684 : * as an `int`.
1685 : *
1686 : * If there is an ungotten character (i.e. ungetc() was called) then that
1687 : * character is returned.
1688 : *
1689 : * When the end of the file is reached, this function returns -1.
1690 : *
1691 : * \note
1692 : * This function is oblivious of UTF-8. It should not matter since any
1693 : * Unicode character would anyway be treated as is.
1694 : *
1695 : * \param[in,out] in The input stream.
1696 : *
1697 : * \return The character read or -1 when EOF is reached.
1698 : */
1699 14564 : int conf_file::getc(std::ifstream & in)
1700 : {
1701 14564 : if(f_unget_char != '\0')
1702 : {
1703 34 : int const r(f_unget_char);
1704 34 : f_unget_char = '\0';
1705 34 : return r;
1706 : }
1707 :
1708 14530 : char c;
1709 14530 : in.get(c);
1710 :
1711 14530 : if(!in)
1712 : {
1713 219 : return EOF;
1714 : }
1715 :
1716 14311 : return static_cast<std::uint8_t>(c);
1717 : }
1718 :
1719 :
1720 : /** \brief Restore one character.
1721 : *
1722 : * This function is used whenever we read one additional character to
1723 : * know whether a certain character followed another. For example, we
1724 : * check for a `'\\n'` whenever we find a `'\\r'`. However, if the
1725 : * character right after the `'\\r'` is not a `'\\n'` we call this
1726 : * ungetc() function so next time we can re-read that same character.
1727 : *
1728 : * \note
1729 : * You can call ungetc() only once between calls to getc(). The
1730 : * current buffer is just one single character. Right now our
1731 : * parser doesn't need more than that.
1732 : *
1733 : * \param[in] c The character to restore.
1734 : */
1735 34 : void conf_file::ungetc(int c)
1736 : {
1737 34 : if(f_unget_char != '\0')
1738 : {
1739 : throw getopt_logic_error("conf_file::ungetc() called when the f_unget_char variable member is not '\\0'."); // LCOV_EXCL_LINE
1740 : }
1741 34 : f_unget_char = c;
1742 34 : }
1743 :
1744 :
1745 : /** \brief Get one line.
1746 : *
1747 : * This function reads one line. The function takes the line continuation
1748 : * setup in account. So for example a line that ends with a backslash
1749 : * continues on the next line when the line continuation is setup to Unix.
1750 : *
1751 : * Note that by default comments are also continued. So a backslash in
1752 : * Unix mode continues a comment on the next line.
1753 : *
1754 : * There is a special case with the semicolon continuation setup. When
1755 : * the line starts as a comment, it will end on the first standalone
1756 : * newline (i.e. a comment does not need to end with a semi-colon.)
1757 : *
1758 : * \param[in,out] in The input stream.
1759 : * \param[out] line Where the line gets saved.
1760 : *
1761 : * \return true if a line was read, false on EOF.
1762 : */
1763 1156 : bool conf_file::get_line(std::ifstream & in, std::string & line)
1764 : {
1765 1156 : line.clear();
1766 :
1767 : for(;;)
1768 : {
1769 14476 : int c(getc(in));
1770 14476 : if(c == EOF)
1771 : {
1772 218 : return !line.empty();
1773 : }
1774 14258 : if(c == ';'
1775 14258 : && f_setup.get_line_continuation() == line_continuation_t::line_continuation_semicolon)
1776 : {
1777 1 : return true;
1778 : }
1779 :
1780 14319 : while(c == '\n' || c == '\r')
1781 : {
1782 : // count the "\r\n" sequence as one line
1783 : //
1784 967 : if(c == '\r')
1785 : {
1786 21 : c = getc(in);
1787 21 : if(c != '\n')
1788 : {
1789 3 : ungetc(c);
1790 : }
1791 21 : c = '\n';
1792 : }
1793 :
1794 967 : ++f_line;
1795 967 : switch(f_setup.get_line_continuation())
1796 : {
1797 76 : case line_continuation_t::line_continuation_single_line:
1798 : // continuation support
1799 76 : return true;
1800 :
1801 17 : case line_continuation_t::line_continuation_rfc_822:
1802 17 : c = getc(in);
1803 17 : if(!iswspace(c))
1804 : {
1805 15 : ungetc(c);
1806 15 : return true;
1807 : }
1808 2 : do
1809 : {
1810 4 : c = getc(in);
1811 : }
1812 4 : while(iswspace(c));
1813 2 : break;
1814 :
1815 17 : case line_continuation_t::line_continuation_msdos:
1816 34 : if(line.empty()
1817 17 : || line.back() != '&')
1818 : {
1819 16 : return true;
1820 : }
1821 1 : line.pop_back();
1822 1 : c = getc(in);
1823 1 : break;
1824 :
1825 823 : case line_continuation_t::line_continuation_unix:
1826 1646 : if(line.empty()
1827 823 : || line.back() != '\\')
1828 : {
1829 812 : return true;
1830 : }
1831 11 : line.pop_back();
1832 11 : c = getc(in);
1833 11 : break;
1834 :
1835 17 : case line_continuation_t::line_continuation_fortran:
1836 17 : c = getc(in);
1837 17 : if(c != '&')
1838 : {
1839 16 : ungetc(c);
1840 16 : return true;
1841 : }
1842 1 : c = getc(in);
1843 1 : break;
1844 :
1845 17 : case line_continuation_t::line_continuation_semicolon:
1846 : // if we have a comment, we want to return immediately;
1847 : // at this time, the comments are not multi-line so
1848 : // the call can return true only if we were reading the
1849 : // very first line
1850 : //
1851 17 : if(is_comment(line.c_str()))
1852 : {
1853 1 : return true;
1854 : }
1855 : // the semicolon is checked earlier, just keep the newline
1856 : // in this case (but not at the start)
1857 : //
1858 16 : if(!line.empty() || c != '\n')
1859 : {
1860 15 : line += c;
1861 : }
1862 16 : c = getc(in);
1863 16 : break;
1864 :
1865 : }
1866 : }
1867 :
1868 : // we just read the last line
1869 13321 : if(c == EOF)
1870 : {
1871 1 : return true;
1872 : }
1873 :
1874 13320 : line += c;
1875 13320 : }
1876 : }
1877 :
1878 :
1879 : /** \brief Read a configuration file.
1880 : *
1881 : * This function reads a configuration file and saves all the parameters it
1882 : * finds in a map which can later be checked against an option table for
1883 : * validation.
1884 : *
1885 : * \todo
1886 : * Add support for quotes in configuration files as parameters are otherwise
1887 : * saved as a separated list of parameters losing the number of spaces between
1888 : * each entry.
1889 : *
1890 : * \todo
1891 : * Add support for reading a backup file if the main file is not found.
1892 : */
1893 324 : void conf_file::read_configuration()
1894 : {
1895 542 : snapdev::safe_variable<decltype(f_reading)> safe_reading(f_reading, true);
1896 :
1897 542 : std::ifstream conf(f_setup.get_filename());
1898 324 : if(!conf)
1899 : {
1900 106 : f_errno = errno;
1901 106 : return;
1902 : }
1903 218 : f_exists = true;
1904 :
1905 218 : bool const save_comment((f_setup.get_comment() & COMMENT_SAVE) != 0);
1906 436 : std::string current_section;
1907 436 : std::vector<std::string> sections;
1908 436 : std::string str;
1909 436 : std::string last_comment;
1910 218 : f_line = 0;
1911 2094 : while(get_line(conf, str))
1912 : {
1913 938 : char const * s(str.c_str());
1914 1034 : while(iswspace(*s))
1915 : {
1916 48 : ++s;
1917 : }
1918 2047 : if(*s == '\0'
1919 938 : || is_comment(s))
1920 : {
1921 : // skip empty lines and comments
1922 : //
1923 171 : if(save_comment)
1924 : {
1925 0 : last_comment += str;
1926 0 : last_comment += '\n'; // str does not include the newline
1927 : }
1928 171 : continue;
1929 : }
1930 1539 : if((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) != 0
1931 767 : && *s == '}')
1932 : {
1933 5 : current_section = sections.back();
1934 5 : sections.pop_back();
1935 5 : continue;
1936 : }
1937 762 : char const * str_name(s);
1938 762 : char const * e(nullptr);
1939 9142 : while(!is_assignment_operator(*s)
1940 4286 : && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}'))
1941 4286 : && ((f_setup.get_section_operator() & SECTION_OPERATOR_INI_FILE) == 0 || *s != ']')
1942 4244 : && *s != '\0'
1943 9181 : && !iswspace(*s))
1944 : {
1945 4190 : ++s;
1946 : }
1947 762 : if(iswspace(*s))
1948 : {
1949 42 : e = s;
1950 294 : while(iswspace(*s))
1951 : {
1952 126 : ++s;
1953 : }
1954 87 : if(*s != '\0'
1955 42 : && !is_assignment_operator(*s)
1956 12 : && (f_setup.get_assignment_operator() & ASSIGNMENT_OPERATOR_SPACE) == 0
1957 51 : && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}')))
1958 : {
1959 6 : cppthread::log << cppthread::log_level_t::error
1960 3 : << "option name from \""
1961 3 : << str
1962 3 : << "\" on line "
1963 3 : << f_line
1964 3 : << " in configuration file \""
1965 3 : << f_setup.get_filename()
1966 3 : << "\" cannot include a space, missing assignment operator?"
1967 6 : << cppthread::end;
1968 3 : continue;
1969 : }
1970 : }
1971 759 : if(e == nullptr)
1972 : {
1973 720 : e = s;
1974 : }
1975 760 : if(e - str_name == 0)
1976 : {
1977 2 : cppthread::log << cppthread::log_level_t::error
1978 1 : << "no option name in \""
1979 1 : << str
1980 1 : << "\" on line "
1981 1 : << f_line
1982 1 : << " from configuration file \""
1983 1 : << f_setup.get_filename()
1984 1 : << "\", missing name before the assignment operator?"
1985 2 : << cppthread::end;
1986 1 : continue;
1987 : }
1988 1512 : std::string name(str_name, e - str_name);
1989 758 : std::replace(name.begin(), name.end(), '_', '-');
1990 760 : if(name[0] == '-')
1991 : {
1992 4 : cppthread::log << cppthread::log_level_t::error
1993 2 : << "option names in configuration files cannot start with a dash or an underscore in \""
1994 2 : << str
1995 2 : << "\" on line "
1996 2 : << f_line
1997 2 : << " from configuration file \""
1998 2 : << f_setup.get_filename()
1999 2 : << "\"."
2000 4 : << cppthread::end;
2001 2 : continue;
2002 : }
2003 1512 : if((f_setup.get_section_operator() & SECTION_OPERATOR_INI_FILE) != 0
2004 267 : && name.length() >= 1
2005 267 : && name[0] == '['
2006 798 : && *s == ']')
2007 : {
2008 42 : ++s;
2009 43 : if(!sections.empty())
2010 : {
2011 2 : cppthread::log << cppthread::log_level_t::error
2012 1 : << "`[...]` sections can't be used within a `section { ... }` on line "
2013 1 : << f_line
2014 1 : << " from configuration file \""
2015 1 : << f_setup.get_filename()
2016 1 : << "\"."
2017 2 : << cppthread::end;
2018 1 : continue;
2019 : }
2020 45 : while(iswspace(*s))
2021 : {
2022 2 : ++s;
2023 : }
2024 83 : if(*s != '\0'
2025 41 : && !is_comment(s))
2026 : {
2027 2 : cppthread::log << cppthread::log_level_t::error
2028 1 : << "section names in configuration files cannot be followed by anything other than spaces in \""
2029 1 : << str
2030 1 : << "\" on line "
2031 1 : << f_line
2032 1 : << " from configuration file \""
2033 1 : << f_setup.get_filename()
2034 1 : << "\"."
2035 2 : << cppthread::end;
2036 1 : continue;
2037 : }
2038 40 : if(name.length() == 1)
2039 : {
2040 : // "[]" removes the section
2041 : //
2042 1 : current_section.clear();
2043 : }
2044 : else
2045 : {
2046 39 : current_section = name.substr(1);
2047 39 : current_section += "::";
2048 : }
2049 40 : last_comment.clear();
2050 : }
2051 1428 : else if((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) != 0
2052 714 : && *s == '{')
2053 : {
2054 6 : sections.push_back(current_section);
2055 6 : current_section += name;
2056 6 : current_section += "::";
2057 6 : last_comment.clear();
2058 : }
2059 : else
2060 : {
2061 708 : if(is_assignment_operator(*s))
2062 : {
2063 690 : ++s;
2064 : }
2065 770 : while(iswspace(*s))
2066 : {
2067 31 : ++s;
2068 : }
2069 736 : for(e = str.c_str() + str.length(); e > s; --e)
2070 : {
2071 720 : if(!iswspace(e[-1]))
2072 : {
2073 692 : break;
2074 : }
2075 : }
2076 708 : size_t const len(e - s);
2077 708 : std::string const value(snapdev::string_replace_many(
2078 1416 : std::string(s, len)
2079 : , {
2080 : { "\\\\", "\\" },
2081 : { "\\r", "\r" },
2082 : { "\\n", "\n" },
2083 : { "\\t", "\t" },
2084 2832 : }));
2085 1416 : set_parameter(
2086 : current_section
2087 : , name
2088 1416 : , unquote(value)
2089 : , last_comment);
2090 708 : last_comment.clear();
2091 : }
2092 : }
2093 218 : if(!conf.eof())
2094 : {
2095 : f_errno = errno; // LCOV_EXCL_LINE
2096 : cppthread::log << cppthread::log_level_t::error // LCOV_EXCL_LINE
2097 : << "an error occurred while reading line " // LCOV_EXCL_LINE
2098 : << f_line // LCOV_EXCL_LINE
2099 : << " of configuration file \"" // LCOV_EXCL_LINE
2100 : << f_setup.get_filename() // LCOV_EXCL_LINE
2101 : << "\"." // LCOV_EXCL_LINE
2102 : << cppthread::end; // LCOV_EXCL_LINE
2103 : }
2104 218 : if(!sections.empty())
2105 : {
2106 2 : cppthread::log << cppthread::log_level_t::error
2107 1 : << "unterminated `section { ... }`, the `}` is missing in configuration file \""
2108 1 : << f_setup.get_filename()
2109 1 : << "\"."
2110 2 : << cppthread::end;
2111 : }
2112 : }
2113 :
2114 :
2115 : /** \brief Check whether `c` is an assignment operator.
2116 : *
2117 : * This function checks the \p c parameter to know whether it matches
2118 : * one of the character allowed as an assignment character.
2119 : *
2120 : * \param[in] c The character to be checked.
2121 : *
2122 : * \return true if c is considered to represent an assignment character.
2123 : */
2124 1119814 : bool conf_file::is_assignment_operator(int c) const
2125 : {
2126 1119814 : assignment_operator_t const assignment_operator(f_setup.get_assignment_operator());
2127 2239495 : return ((assignment_operator & ASSIGNMENT_OPERATOR_EQUAL) != 0 && c == '=')
2128 1118452 : || ((assignment_operator & ASSIGNMENT_OPERATOR_COLON) != 0 && c == ':')
2129 2238244 : || ((assignment_operator & ASSIGNMENT_OPERATOR_SPACE) != 0 && std::iswspace(c));
2130 : }
2131 :
2132 :
2133 : /** \brief Check whether the string starts with a comment introducer.
2134 : *
2135 : * This function checks whether the \p s string starts with a comment.
2136 : *
2137 : * We support different types of comment introducers. This function
2138 : * checks the flags as defined in the constructor and returns true
2139 : * if the type of character introducer defines a comment.
2140 : *
2141 : * We currently support:
2142 : *
2143 : * \li .ini file comments, introduced by a semi-colon (;)
2144 : *
2145 : * \li Shell file comments, introduced by a hash character (#)
2146 : *
2147 : * \li C++ comment, introduced by two slashes (//)
2148 : *
2149 : * \param[in] s The string to check for a comment.
2150 : *
2151 : * \return `true` if the string represents a comment.
2152 : */
2153 911 : bool conf_file::is_comment(char const * s) const
2154 : {
2155 911 : comment_t const comment(f_setup.get_comment());
2156 911 : if((comment & COMMENT_INI) != 0
2157 302 : && *s == ';')
2158 : {
2159 5 : return true;
2160 : }
2161 :
2162 906 : if((comment & COMMENT_SHELL) != 0
2163 575 : && *s == '#')
2164 : {
2165 117 : return true;
2166 : }
2167 :
2168 789 : if((comment & COMMENT_CPP) != 0
2169 10 : && s[0] == '/'
2170 5 : && s[1] == '/')
2171 : {
2172 5 : return true;
2173 : }
2174 :
2175 784 : return false;
2176 : }
2177 :
2178 :
2179 : /** \brief Look for a section to convert in a list of variables.
2180 : *
2181 : * This function checks for a section named \p section_name. If it exists,
2182 : * then it gets converted to a set of variables in \p var and gets
2183 : * removed from the conf_file list of sections.
2184 : *
2185 : * \note
2186 : * The getopt has an f_variables field used to save variables. This is
2187 : * usually the same one that will be set in a conf_file. However, by
2188 : * default a conf_file is not assigned a variables object.
2189 : *
2190 : * \param[in] section_name The name of the section to convert to variables.
2191 : * \param[in] var The variables object where the parameters are saved as
2192 : * variables.
2193 : *
2194 : * \return -1 if the secontion doesn't exist, the number of parameters
2195 : * converted otherwise
2196 : */
2197 3 : int conf_file::section_to_variables(
2198 : std::string const & section_name
2199 : , variables::pointer_t var)
2200 : {
2201 : // verify/canonicalize the section variable name
2202 : //
2203 3 : auto section(f_sections.find(section_name));
2204 3 : if(section == f_sections.end())
2205 : {
2206 1 : return -1;
2207 : }
2208 :
2209 : // do not view that section as such anymore
2210 : //
2211 2 : f_sections.erase(section);
2212 :
2213 2 : int found(0);
2214 4 : std::string starts_with(section_name);
2215 2 : starts_with += "::";
2216 27 : for(auto const & param : get_parameters())
2217 : {
2218 58 : if(param.first.length() > starts_with.length()
2219 67 : && param.first.substr(0, starts_with.length()) == starts_with)
2220 : {
2221 15 : var->set_variable(param.first.substr(starts_with.length()), param.second);
2222 15 : ++found;
2223 :
2224 : // this is safe because get_parameters() returned
2225 : // a copy of the list of parameters
2226 : //
2227 15 : erase_parameter(param.first);
2228 : }
2229 : }
2230 :
2231 2 : return found;
2232 : }
2233 :
2234 :
2235 : /** \brief Returns true if \p c is considered to be a whitespace.
2236 : *
2237 : * Our iswspace() function is equivalent to the std::iswspace() function
2238 : * except that `'\\r'` and `'\\n'` are never viewed as white spaces.
2239 : *
2240 : * \return true if c is considered to be a white space character.
2241 : */
2242 1121780 : bool iswspace(int c)
2243 : {
2244 : return c != '\n'
2245 1121774 : && c != '\r'
2246 2243553 : && std::iswspace(c);
2247 : }
2248 :
2249 :
2250 6 : } // namespace advgetopt
2251 : // vim: ts=4 sw=4 et
|