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