advgetopt 2.0.47
Parse complex command line arguments and configuration files in C++.
conf_file.cpp
Go to the documentation of this file.
1// Copyright (c) 2006-2024 Made to Order Software Corp. All Rights Reserved
2//
3// https://snapwebsites.org/project/advgetopt
4// contact@m2osw.com
5//
6// This program is free software; you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation; either version 2 of the License, or
9// (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License along
17// with this program; if not, write to the Free Software Foundation, Inc.,
18// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
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/join_strings.h>
45#include <snapdev/mkdir_p.h>
46#include <snapdev/safe_variable.h>
47#include <snapdev/string_replace_many.h>
48#include <snapdev/tokenize_string.h>
49#include <snapdev/trim_string.h>
50
51
52// cppthread
53//
54#include <cppthread/guard.h>
55#include <cppthread/log.h>
56#include <cppthread/mutex.h>
57
58
59// C++
60//
61#include <algorithm>
62#include <fstream>
63#include <iomanip>
64
65
66// C
67//
68#include <string.h>
69#include <sys/stat.h>
70
71
72// last include
73//
74#include <snapdev/poison.h>
75
76
77
78namespace advgetopt
79{
80
81
82// from utils.cpp
83//
84// (it's here because we do not want to make cppthread public in
85// out header files--we could have an advgetopt_private.h, though)
86//
87cppthread::mutex & get_global_mutex();
88
89
90
97namespace
98{
99
100
101
118typedef std::map<std::string, conf_file::pointer_t> conf_file_map_t;
119
120
134
135
136} // no name namespace
137
138
139
140
141
178 std::string const & filename
179 , line_continuation_t line_continuation
180 , assignment_operator_t assignment_operator
181 , comment_t comment
182 , section_operator_t section_operator
183 , name_separator_t name_separator)
184 : f_original_filename(filename)
185 , f_line_continuation(line_continuation)
186 , f_assignment_operator(assignment_operator == 0
188 : assignment_operator)
189 , f_comment(comment)
190 , f_section_operator(section_operator)
191 , f_name_separator(name_separator)
192{
193 initialize();
194}
195
196
198 std::string const & filename
199 , conf_file_setup const & original)
200 : f_original_filename(filename)
201 , f_line_continuation(original.f_line_continuation)
202 , f_assignment_operator(original.f_assignment_operator == 0
204 : original.f_assignment_operator)
205 , f_comment(original.f_comment)
206 , f_section_operator(original.f_section_operator)
207 , f_name_separator(original.f_name_separator)
208{
209 initialize();
210}
211
212
214{
215 if(f_original_filename.empty())
216 {
217 throw getopt_invalid("trying to load a configuration file using an empty filename.");
218 }
219
220 // canonicalization so we can properly cache files
221 //
222 std::unique_ptr<char, decltype(&::free)> fn(realpath(f_original_filename.c_str(), nullptr), &::free);
223 if(fn != nullptr)
224 {
225 f_filename = fn.get();
226 }
227 else
228 {
230 }
231}
232
233
251{
252 return !f_filename.empty();
253}
254
255
265std::string const & conf_file_setup::get_original_filename() const
266{
267 return f_original_filename;
268}
269
270
286std::string const & conf_file_setup::get_filename() const
287{
288 return f_filename;
289}
290
291
322
323
349
350
388
389
432
433
453{
454 if(f_url.empty())
455 {
456 std::stringstream ss;
457
458 ss << "file://"
459 << (f_filename.empty()
460 ? "/<empty>"
461 : f_filename);
462
463 std::vector<std::string> params;
465 {
466 std::string name;
467 switch(f_line_continuation)
468 {
470 name = "single-line";
471 break;
472
474 name = "rfc-822";
475 break;
476
478 name = "msdos";
479 break;
480
481 // we should not ever receive this one since we don't enter
482 // this block when the value is "unix"
483 //
484 //case line_continuation_t::line_continuation_unix:
485 // name = "unix";
486 // break;
487
489 name = "fortran";
490 break;
491
493 name = "semi-colon";
494 break;
495
496 default:
497 throw getopt_logic_error("unexpected line continuation.");
498
499 }
500 params.push_back("line-continuation=" + name);
501 }
502
504 {
505 std::vector<std::string> assignments;
507 {
508 assignments.push_back("equal");
509 }
511 {
512 assignments.push_back("colon");
513 }
515 {
516 assignments.push_back("space");
517 }
519 {
520 assignments.push_back("extended");
521 }
522 if(!assignments.empty())
523 {
524 params.push_back("assignment-operator=" + snapdev::join_strings(assignments, ","));
525 }
526 }
527
529 {
530 std::vector<std::string> comment;
531 if((f_comment & COMMENT_INI) != 0)
532 {
533 comment.push_back("ini");
534 }
535 if((f_comment & COMMENT_SHELL) != 0)
536 {
537 comment.push_back("shell");
538 }
539 if((f_comment & COMMENT_CPP) != 0)
540 {
541 comment.push_back("cpp");
542 }
543 if((f_comment & COMMENT_SAVE) != 0)
544 {
545 comment.push_back("save");
546 }
547 if(comment.empty())
548 {
549 params.push_back("comment=none");
550 }
551 else
552 {
553 params.push_back("comment=" + snapdev::join_strings(comment, ","));
554 }
555 }
556
558 {
559 std::vector<std::string> section_operator;
561 {
562 section_operator.push_back("c");
563 }
565 {
566 section_operator.push_back("cpp");
567 }
569 {
570 section_operator.push_back("block");
571 }
573 {
574 section_operator.push_back("ini-file");
575 }
576 if(!section_operator.empty())
577 {
578 params.push_back("section-operator=" + snapdev::join_strings(section_operator, ","));
579 }
580 }
581
582 std::string const query_string(snapdev::join_strings(params, "&"));
583 if(!query_string.empty())
584 {
585 ss << '?'
586 << query_string;
587 }
588
589 f_url = ss.str();
590 }
591
592 return f_url;
593}
594
595
612
613
624void conf_file_setup::set_section_to_ignore(std::string const & section_name)
625{
626 f_section_to_ignore = section_name;
627}
628
629
638std::string const & conf_file_setup::get_section_to_ignore() const
639{
640 return f_section_to_ignore;
641}
642
643
644
645
646
647
648
649
653
654
656 : f_value(rhs.f_value)
657 , f_comment(rhs.f_comment)
658 , f_line(rhs.f_line)
659 , f_assignment_operator(rhs.f_assignment_operator)
660{
661}
662
663
664parameter_value::parameter_value(std::string const & value)
665 : f_value(value)
666{
667}
668
669
671{
672 if(this != &rhs)
673 {
674 f_value = rhs.f_value;
675 f_comment = rhs.f_comment;
676 f_line = rhs.f_line;
678 }
679 return *this;
680}
681
682
684{
685 f_value = value;
686 return *this;
687}
688
689
690parameter_value::operator std::string () const
691{
692 return f_value;
693}
694
695
696void parameter_value::set_value(std::string const & value)
697{
698 f_value = value;
699}
700
701
702void parameter_value::set_comment(std::string const & comment)
703{
704 // ignore if the comment is only composed of spaces, tabs, empty lines
705 //
706 std::string const trimmed(snapdev::trim_string(comment));
707 if(trimmed.empty())
708 {
709 f_comment.clear();
710 }
711 else
712 {
713 // IMPORTANT: we do not save the trimmed version we only use that
714 // to make sure it's not a completely empty comment
715 //
716 f_comment = comment;
717 }
718}
719
720
722{
723 f_line = line;
724}
725
726
731
732
733std::string const & parameter_value::get_value() const
734{
735 return f_value;
736}
737
738
739std::string parameter_value::get_comment(bool ensure_newline) const
740{
741 if(f_comment.empty())
742 {
743 return f_comment;
744 }
745
746 if(ensure_newline
747 && f_comment.back() != '\n')
748 {
749 return f_comment + '\n';
750 }
751
752 return f_comment;
753}
754
755
757{
758 return f_line;
759}
760
761
766
767
768
769
770
771
772
773
774
810{
811 cppthread::guard lock(get_global_mutex());
812
813 auto it(g_conf_files.find(setup.get_filename()));
814 if(it != g_conf_files.end())
815 {
816 if(it->second->get_setup().get_config_url() != setup.get_config_url())
817 {
818 throw getopt_logic_error("trying to load configuration file \""
819 + setup.get_config_url()
820 + "\" but an existing configuration file with the same name was loaded with URL: \""
821 + it->second->get_setup().get_config_url()
822 + "\".");
823 }
824 return it->second;
825 }
826
827 // TODO: look into not blocking "forever"?
828 //
829 conf_file::pointer_t cf(new conf_file(setup));
830 g_conf_files[setup.get_filename()] = cf;
831 return cf;
832}
833
834
849{
850 cppthread::guard lock(get_global_mutex());
851 g_conf_files.clear();
852}
853
854
889 std::string backup_extension
890 , bool replace_backup
891 , bool prepend_warning
892 , std::string output_filename)
893{
894 if(f_modified)
895 {
896 std::string const & filename(output_filename.empty()
898 : output_filename);
899
900 // create backup?
901 //
902 if(!backup_extension.empty())
903 {
904 struct stat s = {};
905 if(stat(filename.c_str(), &s) == 0)
906 {
907 if(backup_extension[0] != '.'
908 && backup_extension[0] != '~')
909 {
910 backup_extension.insert(0, 1, '.');
911 }
912
913 std::string const backup_filename(filename + backup_extension);
914
915 if(replace_backup
916 || access(backup_filename.c_str(), F_OK) != 0)
917 {
918 if(unlink(backup_filename.c_str()) != 0
919 && errno != ENOENT)
920 {
921 f_errno = errno; // LCOV_EXCL_LINE
922 return false; // LCOV_EXCL_LINE
923 }
924
925 if(rename(filename.c_str(), backup_filename.c_str()) != 0)
926 {
927 f_errno = errno; // LCOV_EXCL_LINE
928 return false; // LCOV_EXCL_LINE
929 }
930 }
931 }
932 }
933
934 // TODO: look at adding the user:group info
935 //
936 if(snapdev::mkdir_p(filename, true) != 0)
937 {
938 f_errno = errno; // LCOV_EXCL_LINE
939 return false; // LCOV_EXCL_LINE
940 }
941
942 // save parameters to file
943 //
944 std::ofstream conf;
945 conf.open(filename);
946 if(!conf.is_open())
947 {
948 f_errno = errno; // LCOV_EXCL_LINE
949 return false; // LCOV_EXCL_LINE
950 }
951
952 // header warning with date & time
953 //
954 // (but only if the user doesn't already save comments otherwise
955 // that one would get re-added each time--some form of recursivity)
956 //
957 if(prepend_warning
958 && (f_parameters.empty()
959 || f_parameters.begin()->second.get_comment().empty()))
960 {
961 time_t const now(time(nullptr));
962 tm t;
963 gmtime_r(&now, &t);
964 char str_date[16];
965 strftime(str_date, sizeof(str_date), "%Y/%m/%d", &t);
966 char str_time[16];
967 strftime(str_time, sizeof(str_time), "%H:%M:%S", &t);
968
969 conf << "# This file was auto-generated by advgetopt on " << str_date << " at " << str_time << "." << std::endl
970 << "# Making modifications here is likely safe unless the tool handling this" << std::endl
971 << "# configuration file is actively working on it while you do the edits." << std::endl;
972 }
973 for(auto p : f_parameters)
974 {
975 // if the value has a comment, output it
976 //
977 conf << p.second.get_comment(true);
978
980 {
981 // `first` already has dashes
982 //
983 conf << p.first;
984 }
985 else
986 {
987 for(auto const c : p.first)
988 {
989 conf << (c == '-' ? '_' : c);
990 }
991 }
992
994 {
995 conf << ' ';
996 }
998 {
999 conf << ':';
1000 }
1001 else
1002 {
1003 conf << '=';
1004 }
1005
1006 // prevent saving \r and \n characters as is when part of the
1007 // value; also double \ otherwise reading those back would fail
1008 //
1009 std::string const value(snapdev::string_replace_many(
1010 p.second.get_value()
1011 , {
1012 { "\\", "\\\\" },
1013 { "\\r", "\\r" },
1014 { "\\n", "\\n" },
1015 { "\\t", "\\t" },
1016 }));
1017 conf << value << std::endl;
1018
1019 if(!conf)
1020 {
1021 return false; // LCOV_EXCL_LINE
1022 }
1023 }
1024
1025 // it all worked, it's considered saved now
1026 //
1027 f_modified = false;
1028 }
1029
1030 return true;
1031}
1032
1033
1046conf_file::conf_file(conf_file_setup const & setup)
1047 : f_setup(setup)
1048{
1050}
1051
1052
1067{
1068 return f_setup;
1069}
1070
1071
1101 callback_t const & c
1102 , std::string const & parameter_name)
1103{
1104 cppthread::guard lock(get_global_mutex());
1105
1107 f_callbacks.emplace_back(f_next_callback_id, c, parameter_name);
1108 return f_next_callback_id;
1109}
1110
1111
1122{
1123 cppthread::guard lock(get_global_mutex());
1124
1125 auto it(std::find_if(
1126 f_callbacks.begin()
1127 , f_callbacks.end()
1128 , [id](auto e)
1129 {
1130 return e.f_id == id;
1131 }));
1132 if(it != f_callbacks.end())
1133 {
1134 f_callbacks.erase(it);
1135 }
1136}
1137
1138
1152 callback_action_t action
1153 , std::string const & parameter_name
1154 , std::string const & value)
1155{
1156 callback_vector_t callbacks;
1157 callbacks.reserve(f_callbacks.size());
1158
1159 {
1160 cppthread::guard lock(get_global_mutex());
1161 callbacks = f_callbacks;
1162 }
1163
1164 for(auto & e : callbacks)
1165 {
1166 if(e.f_parameter_name.empty()
1167 || e.f_parameter_name == parameter_name)
1168 {
1169 e.f_callback(shared_from_this(), action, parameter_name, value);
1170 }
1171 }
1172}
1173
1174
1188{
1189 cppthread::guard lock(get_global_mutex());
1190
1191 return f_exists;
1192}
1193
1194
1212{
1213 cppthread::guard lock(get_global_mutex());
1214
1215 return f_errno;
1216}
1217
1218
1237
1238
1250
1251
1270{
1271 cppthread::guard lock(get_global_mutex());
1272
1273 return f_sections;
1274}
1275
1276
1295{
1296 cppthread::guard lock(get_global_mutex());
1297
1298 return f_parameters;
1299}
1300
1301
1317bool conf_file::has_parameter(std::string name) const
1318{
1319 std::replace(name.begin(), name.end(), '_', '-');
1320
1321 cppthread::guard lock(get_global_mutex());
1322
1323 auto it(f_parameters.find(name));
1324 return it != f_parameters.end();
1325}
1326
1327
1345std::string conf_file::get_parameter(std::string name) const
1346{
1347 std::replace(name.begin(), name.end(), '_', '-');
1348
1349 cppthread::guard lock(get_global_mutex());
1350
1351 auto it(f_parameters.find(name));
1352 if(it != f_parameters.end())
1353 {
1354 if(f_variables != nullptr)
1355 {
1356 return f_variables->process_value(it->second);
1357 }
1358 else
1359 {
1360 return it->second;
1361 }
1362 }
1363 return std::string();
1364}
1365
1366
1446 std::string section
1447 , std::string name
1448 , std::string const & value
1449 , assignment_t a
1450 , std::string const & comment)
1451{
1452 // use the tokenize_string() function because we do not want to support
1453 // quoted strings in this list of sections which our split_string()
1454 // does automatically
1455 //
1456 string_list_t section_list;
1457
1458 std::replace(section.begin(), section.end(), '_', '-');
1459 std::replace(name.begin(), name.end(), '_', '-');
1460
1461 char const * n(name.c_str());
1462
1463 // global scope? if so ignore the section parameter
1464 //
1466 && n[0] == ':'
1467 && n[1] == ':')
1468 {
1469 do
1470 {
1471 ++n;
1472 }
1473 while(*n == ':');
1474 }
1475 else
1476 {
1477 snapdev::tokenize_string(section_list
1478 , section
1479 , "::"
1480 , true
1481 , std::string()
1482 , &snapdev::string_predicate<string_list_t>);
1483 }
1484
1485 char const * s(n);
1486 while(*n != '\0')
1487 {
1489 && *n == '.')
1490 {
1491 if(s == n)
1492 {
1493 cppthread::log << cppthread::log_level_t::error
1494 << "option name \""
1495 << name
1496 << "\" cannot start with a period (.)."
1497 << cppthread::end;
1498 return false;
1499 }
1500 section_list.push_back(std::string(s, n - s));
1501 do
1502 {
1503 ++n;
1504 }
1505 while(*n == '.');
1506 s = n;
1507 }
1509 && n[0] == ':'
1510 && n[1] == ':')
1511 {
1512 if(s == n)
1513 {
1514 cppthread::log << cppthread::log_level_t::error
1515 << "option name \""
1516 << name
1517 << "\" cannot start with a scope operator (::)."
1518 << cppthread::end;
1519 return false;
1520 }
1521 section_list.push_back(std::string(s, n - s));
1522 do
1523 {
1524 ++n;
1525 }
1526 while(*n == ':');
1527 s = n;
1528 }
1529 else
1530 {
1531 ++n;
1532 }
1533 }
1534 if(s == n)
1535 {
1536 cppthread::log << cppthread::log_level_t::error
1537 << "option name \""
1538 << name
1539 << "\" cannot end with a section operator or be empty."
1540 << cppthread::end;
1541 return false;
1542 }
1543 std::string param_name(s, n - s);
1544
1545 std::string const section_name(snapdev::join_strings(section_list, "::"));
1546
1548 && !section_list.empty())
1549 {
1550 cppthread::log << cppthread::log_level_t::error
1551 << "option name \""
1552 << name
1553 << "\" cannot be added to section \""
1554 << section_name
1555 << "\" because there is no section support for this configuration file."
1556 << cppthread::end;
1557 return false;
1558 }
1560 && section_list.size() > 1)
1561 {
1562 if(section_list.size() == 2
1563 && section_list[0] == f_setup.get_section_to_ignore())
1564 {
1565 section_list.erase(section_list.begin());
1566 }
1567 if(section_list.size() > 1)
1568 {
1569 cppthread::log << cppthread::log_level_t::error
1570 << "option name \""
1571 << name
1572 << "\" cannot be added to section \""
1573 << section_name
1574 << "\" because this configuration only accepts one section level."
1575 << cppthread::end;
1576 return false;
1577 }
1578 }
1579
1580 section_list.push_back(param_name);
1581 std::string const full_name(snapdev::join_strings(section_list, "::"));
1582
1583 // verify that each section name only includes characters we accept
1584 // for a parameter name
1585 //
1586 // WARNING: we do not test with full_name because it includes ':'
1587 //
1588 for(auto sn : section_list)
1589 {
1590 for(char const * f(sn.c_str()); *f != '\0'; ++f)
1591 {
1592 switch(*f)
1593 {
1594 case '\001': // forbid controls
1595 case '\002':
1596 case '\003':
1597 case '\004':
1598 case '\005':
1599 case '\006':
1600 case '\007':
1601 case '\010':
1602 case '\011':
1603 case '\012':
1604 case '\013':
1605 case '\014':
1606 case '\015':
1607 case '\016':
1608 case '\017':
1609 case '\020':
1610 case '\021':
1611 case '\022':
1612 case '\023':
1613 case '\024':
1614 case '\025':
1615 case '\026':
1616 case '\027':
1617 case '\030':
1618 case '\031':
1619 case '\032':
1620 case '\033':
1621 case '\034':
1622 case '\035':
1623 case '\036':
1624 case '\037':
1625 case ' ': // forbid spaces
1626 case '\'': // forbid all quotes
1627 case '"': // forbid all quotes
1628 case ';': // forbid all comment operators
1629 case '#': // forbid all comment operators
1630 case '/': // forbid all comment operators
1631 case '=': // forbid all assignment operators
1632 case ':': // forbid all assignment operators
1633 case '?': // forbid all assignment operators (for later)
1634 case '+': // forbid all assignment operators (for later)
1635 case '\\': // forbid backslashes
1636 cppthread::log << cppthread::log_level_t::error
1637 << "section \""
1638 << sn
1639 << "\" from parameter \""
1640 << full_name
1641 << "\" on line "
1642 << f_line
1643 << " in configuration file \""
1645 << "\" includes a character (\\"
1646 << std::oct << std::setfill('0') << std::setw(3) << static_cast<int>(*f) << std::dec
1647 << ") not acceptable for a section or parameter name (controls, space, quotes, and \";#/=:?+\\\")."
1648 << cppthread::end;
1649 return false;
1650
1651 }
1652 }
1653 }
1654
1655 cppthread::guard lock(get_global_mutex());
1656
1657 // add the section to the list of sections
1658 //
1659 // TODO: should we have a list of all the parent sections? Someone can
1660 // write "a::b::c::d = 123" and we currently only get section
1661 // "a::b::c", no section "a" and no section "a::b".
1662 //
1663 if(!section_name.empty())
1664 {
1665 f_sections.insert(section_name);
1666 }
1667
1669 auto it(f_parameters.find(full_name));
1670 if(it == f_parameters.end())
1671 {
1672 f_parameters[full_name] = value;
1673 f_parameters[full_name].set_comment(comment);
1674 f_parameters[full_name].set_line(f_line);
1675 f_parameters[full_name].set_assignment_operator(a);
1676 }
1677 else
1678 {
1679 if(f_reading)
1680 {
1681 // this is just a warning; it can be neat to know about such
1682 // problems and fix them early
1683 //
1684 cppthread::log << cppthread::log_level_t::warning
1685 << "parameter \""
1686 << full_name
1687 << "\" on line "
1688 << f_line
1689 << " in configuration file \""
1691 << "\" was found twice in the same configuration file."
1692 << cppthread::end;
1693 }
1694
1695 switch(a)
1696 {
1699 it->second = value;
1700 break;
1701
1703 // already set, do not overwrite
1704 return false;
1705
1707 it->second.set_value(it->second.get_value() + value);
1708 break;
1709
1711 cppthread::log << cppthread::log_level_t::error
1712 << "parameter \""
1713 << name
1714 << "\" is already defined and it cannot be overridden with the ':=' operator on line "
1715 << f_line
1716 << " from configuration file \""
1718 << "\"."
1719 << cppthread::end;
1720 return false;
1721
1722 }
1723
1725 }
1726
1727 if(!f_reading)
1728 {
1729 f_modified = true;
1730
1731 value_changed(action, full_name, value);
1732 }
1733
1734 return true;
1735}
1736
1737
1749bool conf_file::erase_parameter(std::string name)
1750{
1751 std::replace(name.begin(), name.end(), '_', '-');
1752
1753 auto it(f_parameters.find(name));
1754 if(it == f_parameters.end())
1755 {
1756 return false;
1757 }
1758
1759 f_parameters.erase(it);
1760
1761 if(!f_reading)
1762 {
1763 f_modified = true;
1764
1765 value_changed(callback_action_t::erased, name, std::string());
1766 }
1767
1768 return true;
1769}
1770
1771
1782{
1783 while(!f_parameters.empty())
1784 {
1785 erase_parameter(f_parameters.begin()->first);
1786 }
1787}
1788
1789
1802{
1803 return f_modified;
1804}
1805
1806
1825int conf_file::getc(std::ifstream & in)
1826{
1827 if(f_unget_char != '\0')
1828 {
1829 int const r(f_unget_char);
1830 f_unget_char = '\0';
1831 return r;
1832 }
1833
1834 char c;
1835 in.get(c);
1836
1837 if(!in)
1838 {
1839 return EOF;
1840 }
1841
1842 return static_cast<std::uint8_t>(c);
1843}
1844
1845
1862{
1863 if(f_unget_char != '\0')
1864 {
1865 throw getopt_logic_error("conf_file::ungetc() called when the f_unget_char variable member is not '\\0'."); // LCOV_EXCL_LINE
1866 }
1867 f_unget_char = c;
1868}
1869
1870
1889bool conf_file::get_line(std::ifstream & in, std::string & line)
1890{
1891 line.clear();
1892
1893 for(;;)
1894 {
1895 int c(getc(in));
1896 if(c == EOF)
1897 {
1898 return !line.empty();
1899 }
1900 if(c == ';'
1902 {
1903 return true;
1904 }
1905
1906 while(c == '\n' || c == '\r')
1907 {
1908 // count the "\r\n" sequence as one line
1909 //
1910 if(c == '\r')
1911 {
1912 c = getc(in);
1913 if(c != '\n')
1914 {
1915 ungetc(c);
1916 }
1917 c = '\n';
1918 }
1919
1920 ++f_line;
1922 {
1924 // continuation support
1925 return true;
1926
1928 c = getc(in);
1929 if(!iswspace(c))
1930 {
1931 ungetc(c);
1932 return true;
1933 }
1934 do
1935 {
1936 c = getc(in);
1937 }
1938 while(iswspace(c));
1939 break;
1940
1942 if(line.empty()
1943 || line.back() != '&')
1944 {
1945 return true;
1946 }
1947 line.pop_back();
1948 c = getc(in);
1949 break;
1950
1952 if(line.empty()
1953 || line.back() != '\\')
1954 {
1955 return true;
1956 }
1957 line.pop_back();
1958 c = getc(in);
1959 break;
1960
1962 c = getc(in);
1963 if(c != '&')
1964 {
1965 ungetc(c);
1966 return true;
1967 }
1968 c = getc(in);
1969 break;
1970
1972 // if we have a comment, we want to return immediately;
1973 // at this time, the comments are not multi-line so
1974 // the call can return true only if we were reading the
1975 // very first line
1976 //
1977 if(is_comment(line.c_str()))
1978 {
1979 return true;
1980 }
1981 // the semicolon is checked earlier, just keep the newline
1982 // in this case (but not at the start)
1983 //
1984 if(!line.empty() || c != '\n')
1985 {
1986 line += c;
1987 }
1988 c = getc(in);
1989 break;
1990
1991 }
1992 }
1993
1994 // we just read the last line
1995 if(c == EOF)
1996 {
1997 return true;
1998 }
1999
2000 line += c;
2001 }
2002}
2003
2004
2020{
2021 snapdev::safe_variable<decltype(f_reading)> safe_reading(f_reading, true);
2022
2023 std::ifstream conf(f_setup.get_filename());
2024 if(!conf)
2025 {
2026 f_errno = errno;
2027 return;
2028 }
2029 f_exists = true;
2030
2031 bool const save_comment((f_setup.get_comment() & COMMENT_SAVE) != 0);
2032 std::string current_section;
2033 std::vector<std::string> sections;
2034 std::string str;
2035 std::string last_comment;
2036 f_line = 0;
2037 while(get_line(conf, str))
2038 {
2039 char const * s(str.c_str());
2040 while(iswspace(*s))
2041 {
2042 ++s;
2043 }
2044 if(*s == '\0'
2045 || is_comment(s))
2046 {
2047 // skip empty lines and comments
2048 //
2049 if(save_comment)
2050 {
2051 last_comment += str;
2052 last_comment += '\n'; // str does not include the newline
2053 }
2054 continue;
2055 }
2057 && *s == '}')
2058 {
2059 current_section = sections.back();
2060 sections.pop_back();
2061 continue;
2062 }
2063 char const * str_name(s);
2064 char const * e(nullptr);
2066 && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}'))
2067 && ((f_setup.get_section_operator() & SECTION_OPERATOR_INI_FILE) == 0 || *s != ']')
2068 && *s != '\0'
2069 && !iswspace(*s))
2070 {
2071 ++s;
2072 }
2073 if(iswspace(*s))
2074 {
2075 e = s;
2076 while(iswspace(*s))
2077 {
2078 ++s;
2079 }
2080 if(*s != '\0'
2083 && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}')))
2084 {
2085 cppthread::log << cppthread::log_level_t::error
2086 << "option name from \""
2087 << str
2088 << "\" on line "
2089 << f_line
2090 << " in configuration file \""
2092 << "\" cannot include a space, missing assignment operator?"
2093 << cppthread::end;
2094 continue;
2095 }
2096 }
2097 if(e == nullptr)
2098 {
2099 e = s;
2100 }
2101 if(e - str_name == 0)
2102 {
2103 cppthread::log << cppthread::log_level_t::error
2104 << "no option name in \""
2105 << str
2106 << "\" on line "
2107 << f_line
2108 << " from configuration file \""
2110 << "\", missing name before the assignment operator?"
2111 << cppthread::end;
2112 continue;
2113 }
2114 std::string name(str_name, e - str_name);
2115 std::replace(name.begin(), name.end(), '_', '-');
2116 if(name[0] == '-')
2117 {
2118 cppthread::log << cppthread::log_level_t::error
2119 << "option names in configuration files cannot start with a dash or an underscore in \""
2120 << str
2121 << "\" on line "
2122 << f_line
2123 << " from configuration file \""
2125 << "\"."
2126 << cppthread::end;
2127 continue;
2128 }
2130 && name.length() >= 1
2131 && name[0] == '['
2132 && *s == ']')
2133 {
2134 ++s;
2135 if(!sections.empty())
2136 {
2137 cppthread::log << cppthread::log_level_t::error
2138 << "`[...]` sections can't be used within a `section { ... }` on line "
2139 << f_line
2140 << " from configuration file \""
2142 << "\"."
2143 << cppthread::end;
2144 continue;
2145 }
2146 while(iswspace(*s))
2147 {
2148 ++s;
2149 }
2150 if(*s != '\0'
2151 && !is_comment(s))
2152 {
2153 cppthread::log << cppthread::log_level_t::error
2154 << "section names in configuration files cannot be followed by anything other than spaces in \""
2155 << str
2156 << "\" on line "
2157 << f_line
2158 << " from configuration file \""
2160 << "\"."
2161 << cppthread::end;
2162 continue;
2163 }
2164 if(name.length() == 1)
2165 {
2166 // "[]" removes the section
2167 //
2168 current_section.clear();
2169 }
2170 else
2171 {
2172 current_section = name.substr(1);
2173 current_section += "::";
2174 }
2175 last_comment.clear();
2176 }
2178 && *s == '{')
2179 {
2180 sections.push_back(current_section);
2181 current_section += name;
2182 current_section += "::";
2183 last_comment.clear();
2184 }
2185 else
2186 {
2187 assignment_t const a(is_assignment_operator(s, true));
2188 while(iswspace(*s))
2189 {
2190 ++s;
2191 }
2192 for(e = str.c_str() + str.length(); e > s; --e)
2193 {
2194 if(!iswspace(e[-1]))
2195 {
2196 break;
2197 }
2198 }
2199 std::size_t const len(e - s);
2200 std::string const value(snapdev::string_replace_many(
2201 std::string(s, len)
2202 , {
2203 { "\\\\", "\\" },
2204 { "\\r", "\r" },
2205 { "\\n", "\n" },
2206 { "\\t", "\t" },
2207 }));
2209 current_section
2210 , name
2211 , unquote(value)
2212 , a
2213 , last_comment);
2214 last_comment.clear();
2215 }
2216 }
2217 if(!conf.eof())
2218 {
2219 f_errno = errno; // LCOV_EXCL_LINE
2220 cppthread::log << cppthread::log_level_t::error // LCOV_EXCL_LINE
2221 << "an error occurred while reading line " // LCOV_EXCL_LINE
2222 << f_line // LCOV_EXCL_LINE
2223 << " of configuration file \"" // LCOV_EXCL_LINE
2224 << f_setup.get_filename() // LCOV_EXCL_LINE
2225 << "\"." // LCOV_EXCL_LINE
2226 << cppthread::end; // LCOV_EXCL_LINE
2227 }
2228 if(!sections.empty())
2229 {
2230 cppthread::log << cppthread::log_level_t::error
2231 << "unterminated `section { ... }`, the `}` is missing in configuration file \""
2233 << "\"."
2234 << cppthread::end;
2235 }
2236}
2237
2238
2252 char const * & s
2253 , bool skip) const
2254{
2255 assignment_operator_t const assignment_operator(f_setup.get_assignment_operator());
2256 if(*s == '+'
2257 || *s == '?'
2258 || (*s == ':' && s[1] == '='))
2259 {
2260 if((assignment_operator & ASSIGNMENT_OPERATOR_EXTENDED) != 0
2261 && s[1] == '=')
2262 {
2263 char op(s[0]);
2264 if(skip)
2265 {
2266 s += 2;
2267 }
2268 switch(op)
2269 {
2270 case '+':
2272
2273 case '?':
2275
2276 case ':':
2278
2279 default:
2280 throw getopt_logic_error("assignment not properly handled in is_assignment_operator()");
2281
2282 }
2283 }
2284 }
2285 else if(((assignment_operator & ASSIGNMENT_OPERATOR_EQUAL) != 0 && *s == '=')
2286 || ((assignment_operator & ASSIGNMENT_OPERATOR_COLON) != 0 && *s == ':')
2287 || ((assignment_operator & ASSIGNMENT_OPERATOR_SPACE) != 0 && std::iswspace(*s)))
2288 {
2289 if(skip)
2290 {
2291 ++s;
2292 }
2294 }
2295
2297}
2298
2299
2320bool conf_file::is_comment(char const * s) const
2321{
2322 comment_t const comment(f_setup.get_comment());
2323 if((comment & COMMENT_INI) != 0
2324 && *s == ';')
2325 {
2326 return true;
2327 }
2328
2329 if((comment & COMMENT_SHELL) != 0
2330 && *s == '#')
2331 {
2332 return true;
2333 }
2334
2335 if((comment & COMMENT_CPP) != 0
2336 && s[0] == '/'
2337 && s[1] == '/')
2338 {
2339 return true;
2340 }
2341
2342 return false;
2343}
2344
2345
2365 std::string const & section_name
2366 , variables::pointer_t vars)
2367{
2368 if(vars == nullptr)
2369 {
2370 return -1;
2371 }
2372
2373 // verify/canonicalize the section variable name
2374 //
2375 auto section(f_sections.find(section_name));
2376 if(section == f_sections.end())
2377 {
2378 return -1;
2379 }
2380
2381 // do not view that section as such anymore
2382 //
2383 f_sections.erase(section);
2384
2385 int found(0);
2386 std::string starts_with(section_name);
2387 starts_with += "::";
2388 for(auto const & param : get_parameters())
2389 {
2390 if(param.first.length() > starts_with.length()
2391 && strncmp(param.first.c_str(), starts_with.c_str(), starts_with.length()) == 0)
2392 {
2393 vars->set_variable(
2394 param.first.substr(starts_with.length())
2395 , param.second
2396 , param.second.get_assignment_operator());
2397 ++found;
2398
2399 // this is safe because get_parameters() returned
2400 // a copy of the list of parameters
2401 //
2402 erase_parameter(param.first);
2403 }
2404 }
2405
2406 return found;
2407}
2408
2409
2417bool iswspace(int c)
2418{
2419 return c != '\n'
2420 && c != '\r'
2421 && std::iswspace(c);
2422}
2423
2424
2425} // namespace advgetopt
2426// vim: ts=4 sw=4 et
name_separator_t f_name_separator
Definition conf_file.h:149
std::string const & get_section_to_ignore() const
Retrieve the name to be ignore.
comment_t get_comment() const
std::string f_section_to_ignore
Definition conf_file.h:143
line_continuation_t f_line_continuation
Definition conf_file.h:144
std::string const & get_original_filename() const
Get the original filename.
section_operator_t f_section_operator
Definition conf_file.h:147
std::string const & get_filename() const
Get the filename.
line_continuation_t get_line_continuation() const
Get the line continuation setting.
assignment_operator_t get_assignment_operator() const
Get the accepted assignment operators.
conf_file_setup(std::string const &filename, line_continuation_t line_continuation=line_continuation_t::line_continuation_unix, assignment_operator_t assignment_operator=ASSIGNMENT_OPERATOR_EQUAL, comment_t comment=COMMENT_INI|COMMENT_SHELL, section_operator_t section_operator=SECTION_OPERATOR_INI_FILE, name_separator_t name_separator=NAME_SEPARATOR_UNDERSCORES)
Initialize the file setup object.
bool is_valid() const
Check whether the setup is considered valid.
std::string f_original_filename
Definition conf_file.h:141
name_separator_t get_name_separator() const
Retrieve the separator to use within names.
void set_section_to_ignore(std::string const &section_name)
Set a section name to ignore.
std::string get_config_url() const
Transform the setup in a URL.
section_operator_t get_section_operator() const
Get the accepted section operators.
assignment_operator_t f_assignment_operator
Definition conf_file.h:145
callback_id_t f_next_callback_id
Definition conf_file.h:280
int section_to_variables(std::string const &section_name, variables::pointer_t var)
Look for a section to convert in a list of variables.
conf_file_setup const & get_setup() const
Get the configuration file setup.
bool set_parameter(std::string section, std::string name, std::string const &value, assignment_t op=assignment_t::ASSIGNMENT_NONE, std::string const &comment=std::string())
Set a parameter.
callback_id_t add_callback(callback_t const &c, std::string const &parameter_name=std::string())
Add a callback to detect when changes happen.
void erase_all_parameters()
Clear the list of all existing parameters from this file.
static void reset_conf_files()
Forget all the cached configuration files.
variables::pointer_t get_variables() const
Retrieve the currently attached variables.
bool exists() const
Whether an input file was found.
std::map< std::string, parameter_value > parameters_t
Definition conf_file.h:188
conf_file_setup const f_setup
Definition conf_file.h:267
variables::pointer_t f_variables
Definition conf_file.h:277
bool save_configuration(std::string backup_extension=std::string(".bak"), bool replace_backup=false, bool prepend_warning=true, std::string output_filename=std::string())
Save the configuration file.
callback_vector_t f_callbacks
Definition conf_file.h:279
parameters_t f_parameters
Definition conf_file.h:278
sections_t f_sections
Definition conf_file.h:276
void read_configuration()
Read a configuration file.
void value_changed(callback_action_t action, std::string const &parameter_name, std::string const &value)
Call whenever the value changed so we can handle callbacks.
std::string get_parameter(std::string name) const
Get the named parameter.
assignment_t is_assignment_operator(char const *&s, bool skip) const
Check whether c is an assignment operator.
bool was_modified() const
Check whether this configuration file was modified.
bool has_parameter(std::string name) const
Check whether a parameter is defined.
void ungetc(int c)
Restore one character.
std::shared_ptr< conf_file > pointer_t
Definition conf_file.h:186
std::vector< callback_entry_t > callback_vector_t
Definition conf_file.h:254
bool is_comment(char const *s) const
Check whether the string starts with a comment introducer.
parameters_t get_parameters() const
Get a list of parameters.
sections_t get_sections() const
Get a list of sections.
void set_variables(variables::pointer_t variables)
Attach a variables object to the configuration file.
bool erase_parameter(std::string name)
Erase the named parameter from this configuration file.
void remove_callback(callback_id_t id)
Remove a callback.
int get_errno() const
Get the error number opening/reading the configuration file.
string_set_t sections_t
Definition conf_file.h:187
std::function< void(pointer_t conf_file, callback_action_t action, std::string const &parameter_name, std::string const &value)> callback_t
Definition conf_file.h:193
static pointer_t get_conf_file(conf_file_setup const &setup)
Create and read a conf_file.
bool get_line(std::ifstream &stream, std::string &line)
Get one line.
int getc(std::ifstream &stream)
Read one characte from the input stream.
std::string const & get_value() const
parameter_value & operator=(parameter_value const &rhs)
assignment_t get_assignment_operator() const
assignment_t f_assignment_operator
Definition conf_file.h:178
void set_assignment_operator(assignment_t a)
void set_value(std::string const &value)
void set_comment(std::string const &comment)
std::string get_comment(bool ensure_newline=false) const
std::shared_ptr< variables > pointer_t
Definition variables.h:61
Declaration of the conf_file class used to read a configuration file.
Definitions of the advanced getopt exceptions.
std::map< std::string, conf_file::pointer_t > conf_file_map_t
A map of configuration files.
conf_file_map_t g_conf_files
The configuration files.
The advgetopt environment to parse command line options.
constexpr comment_t COMMENT_SAVE
Definition conf_file.h:86
constexpr section_operator_t SECTION_OPERATOR_INI_FILE
Definition conf_file.h:97
constexpr comment_t COMMENT_CPP
Definition conf_file.h:84
std::uint_fast16_t assignment_operator_t
Definition conf_file.h:69
std::uint_fast16_t section_operator_t
Definition conf_file.h:91
constexpr section_operator_t SECTION_OPERATOR_CPP
Definition conf_file.h:95
constexpr section_operator_t SECTION_OPERATOR_ONE_SECTION
Definition conf_file.h:99
constexpr comment_t COMMENT_SHELL
Definition conf_file.h:83
line_continuation_t
Definition conf_file.h:59
constexpr assignment_operator_t ASSIGNMENT_OPERATOR_EQUAL
Definition conf_file.h:71
constexpr section_operator_t SECTION_OPERATOR_C
Definition conf_file.h:94
cppthread::mutex & get_global_mutex()
Get a global mutex.
Definition utils.cpp:123
constexpr name_separator_t NAME_SEPARATOR_DASHES
Definition conf_file.h:107
constexpr section_operator_t SECTION_OPERATOR_BLOCK
Definition conf_file.h:96
constexpr assignment_operator_t ASSIGNMENT_OPERATOR_SPACE
Definition conf_file.h:73
std::uint_fast16_t comment_t
Definition conf_file.h:79
constexpr comment_t COMMENT_INI
Definition conf_file.h:82
constexpr assignment_operator_t ASSIGNMENT_OPERATOR_EXTENDED
Definition conf_file.h:74
bool iswspace(int c)
Returns true if c is considered to be a whitespace.
callback_action_t
Definition conf_file.h:51
std::string unquote(std::string const &s, std::string const &pairs)
Remove single (') or double (") quotes from a string.
Definition utils.cpp:168
std::uint_fast16_t name_separator_t
Definition conf_file.h:104
constexpr section_operator_t SECTION_OPERATOR_NONE
Definition conf_file.h:93
std::vector< std::string > string_list_t
Definition utils.h:41
constexpr assignment_operator_t ASSIGNMENT_OPERATOR_COLON
Definition conf_file.h:72
A few utility functions that are not specific to an object.

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.