advgetopt 2.0.49
Parse complex command line arguments and configuration files in C++.
conf_file.cpp
Go to the documentation of this file.
1// Copyright (c) 2006-2025 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
179 std::string const & filename
180 , line_continuation_t line_continuation
181 , assignment_operator_t assignment_operator
182 , comment_t comment
183 , section_operator_t section_operator
184 , name_separator_t name_separator)
185 : f_original_filename(filename)
186 , f_line_continuation(line_continuation)
187 , f_assignment_operator(assignment_operator == 0
189 : assignment_operator)
190 , f_comment(comment)
191 , f_section_operator(section_operator)
192 , f_name_separator(name_separator)
193{
194 initialize();
195}
196
197
199 std::string const & filename
200 , conf_file_setup const & original)
201 : f_original_filename(filename)
202 , f_line_continuation(original.f_line_continuation)
203 , f_assignment_operator(original.f_assignment_operator == 0
205 : original.f_assignment_operator)
206 , f_comment(original.f_comment)
207 , f_section_operator(original.f_section_operator)
208 , f_name_separator(original.f_name_separator)
209{
210 initialize();
211}
212
213
215{
216 if(f_original_filename.empty())
217 {
218 throw getopt_invalid("trying to load a configuration file using an empty filename.");
219 }
220
221 // canonicalization so we can properly cache files
222 //
223 std::unique_ptr<char, decltype(&::free)> fn(realpath(f_original_filename.c_str(), nullptr), &::free);
224 if(fn != nullptr)
225 {
226 f_filename = fn.get();
227 }
228 else
229 {
231 }
232}
233
234
252{
253 return !f_filename.empty();
254}
255
256
266std::string const & conf_file_setup::get_original_filename() const
267{
268 return f_original_filename;
269}
270
271
287std::string const & conf_file_setup::get_filename() const
288{
289 return f_filename;
290}
291
292
323
324
350
351
389
390
433
434
454{
455 if(f_url.empty())
456 {
457 std::stringstream ss;
458
459 ss << "file://"
460 << (f_filename.empty()
461 ? "/<empty>"
462 : f_filename);
463
464 std::vector<std::string> params;
466 {
467 std::string name;
468 switch(f_line_continuation)
469 {
471 name = "single-line";
472 break;
473
475 name = "rfc-822";
476 break;
477
479 name = "msdos";
480 break;
481
482 // we should not ever receive this one since we don't enter
483 // this block when the value is "unix"
484 //
485 //case line_continuation_t::line_continuation_unix:
486 // name = "unix";
487 // break;
488
490 name = "fortran";
491 break;
492
494 name = "semi-colon";
495 break;
496
497 default:
498 throw getopt_logic_error("unexpected line continuation.");
499
500 }
501 params.push_back("line-continuation=" + name);
502 }
503
505 {
506 std::vector<std::string> assignments;
508 {
509 assignments.push_back("equal");
510 }
512 {
513 assignments.push_back("colon");
514 }
516 {
517 assignments.push_back("space");
518 }
520 {
521 assignments.push_back("extended");
522 }
523 if(!assignments.empty())
524 {
525 params.push_back("assignment-operator=" + snapdev::join_strings(assignments, ","));
526 }
527 }
528
530 {
531 std::vector<std::string> comment;
532 if((f_comment & COMMENT_INI) != 0)
533 {
534 comment.push_back("ini");
535 }
536 if((f_comment & COMMENT_SHELL) != 0)
537 {
538 comment.push_back("shell");
539 }
540 if((f_comment & COMMENT_CPP) != 0)
541 {
542 comment.push_back("cpp");
543 }
544 if((f_comment & COMMENT_SAVE) != 0)
545 {
546 comment.push_back("save");
547 }
548 if(comment.empty())
549 {
550 params.push_back("comment=none");
551 }
552 else
553 {
554 params.push_back("comment=" + snapdev::join_strings(comment, ","));
555 }
556 }
557
559 {
560 std::vector<std::string> section_operator;
562 {
563 section_operator.push_back("c");
564 }
566 {
567 section_operator.push_back("cpp");
568 }
570 {
571 section_operator.push_back("block");
572 }
574 {
575 section_operator.push_back("ini-file");
576 }
577 if(!section_operator.empty())
578 {
579 params.push_back("section-operator=" + snapdev::join_strings(section_operator, ","));
580 }
581 }
582
583 std::string const query_string(snapdev::join_strings(params, "&"));
584 if(!query_string.empty())
585 {
586 ss << '?'
587 << query_string;
588 }
589
590 f_url = ss.str();
591 }
592
593 return f_url;
594}
595
596
613
614
625void conf_file_setup::set_section_to_ignore(std::string const & section_name)
626{
627 f_section_to_ignore = section_name;
628}
629
630
639std::string const & conf_file_setup::get_section_to_ignore() const
640{
641 return f_section_to_ignore;
642}
643
644
645
646
647
648
649
650
654
655
657 : f_value(rhs.f_value)
658 , f_comment(rhs.f_comment)
659 , f_line(rhs.f_line)
660 , f_assignment_operator(rhs.f_assignment_operator)
661{
662}
663
664
665parameter_value::parameter_value(std::string const & value)
666 : f_value(value)
667{
668}
669
670
672{
673 if(this != &rhs)
674 {
675 f_value = rhs.f_value;
676 f_comment = rhs.f_comment;
677 f_line = rhs.f_line;
679 }
680 return *this;
681}
682
683
685{
686 f_value = value;
687 return *this;
688}
689
690
691parameter_value::operator std::string () const
692{
693 return f_value;
694}
695
696
697void parameter_value::set_value(std::string const & value)
698{
699 f_value = value;
700}
701
702
703void parameter_value::set_comment(std::string const & comment)
704{
705 // ignore if the comment is only composed of spaces, tabs, empty lines
706 //
707 std::string const trimmed(snapdev::trim_string(comment));
708 if(trimmed.empty())
709 {
710 f_comment.clear();
711 }
712 else
713 {
714 // IMPORTANT: we do not save the trimmed version we only use that
715 // to make sure it's not a completely empty comment
716 //
717 f_comment = comment;
718 }
719}
720
721
723{
724 f_line = line;
725}
726
727
732
733
734std::string const & parameter_value::get_value() const
735{
736 return f_value;
737}
738
739
740std::string parameter_value::get_comment(bool ensure_newline) const
741{
742 if(f_comment.empty())
743 {
744 return f_comment;
745 }
746
747 if(ensure_newline
748 && f_comment.back() != '\n')
749 {
750 return f_comment + '\n';
751 }
752
753 return f_comment;
754}
755
756
758{
759 return f_line;
760}
761
762
767
768
769
770
771
772
773
774
775
811{
812 cppthread::guard lock(get_global_mutex());
813
814 auto it(g_conf_files.find(setup.get_filename()));
815 if(it != g_conf_files.end())
816 {
817 if(it->second->get_setup().get_config_url() != setup.get_config_url())
818 {
819 throw getopt_logic_error("trying to load configuration file \""
820 + setup.get_config_url()
821 + "\" but an existing configuration file with the same name was loaded with URL: \""
822 + it->second->get_setup().get_config_url()
823 + "\".");
824 }
825 return it->second;
826 }
827
828 // TODO: look into not blocking "forever"?
829 //
830 conf_file::pointer_t cf(new conf_file(setup));
831 g_conf_files[setup.get_filename()] = cf;
832 return cf;
833}
834
835
850{
851 cppthread::guard lock(get_global_mutex());
852 g_conf_files.clear();
853}
854
855
890 std::string backup_extension
891 , bool replace_backup
892 , bool prepend_warning
893 , std::string output_filename)
894{
895 if(f_modified)
896 {
897 std::string const & filename(output_filename.empty()
899 : output_filename);
900
901 // create backup?
902 //
903 if(!backup_extension.empty())
904 {
905 struct stat s = {};
906 if(stat(filename.c_str(), &s) == 0)
907 {
908 if(backup_extension[0] != '.'
909 && backup_extension[0] != '~')
910 {
911 backup_extension.insert(0, 1, '.');
912 }
913
914 std::string const backup_filename(filename + backup_extension);
915
916 if(replace_backup
917 || access(backup_filename.c_str(), F_OK) != 0)
918 {
919 if(unlink(backup_filename.c_str()) != 0
920 && errno != ENOENT)
921 {
922 f_errno = errno; // LCOV_EXCL_LINE
923 return false; // LCOV_EXCL_LINE
924 }
925
926 if(rename(filename.c_str(), backup_filename.c_str()) != 0)
927 {
928 f_errno = errno; // LCOV_EXCL_LINE
929 return false; // LCOV_EXCL_LINE
930 }
931 }
932 }
933 }
934
935 // TODO: look at adding the user:group info
936 //
937 if(snapdev::mkdir_p(filename, true) != 0)
938 {
939 f_errno = errno; // LCOV_EXCL_LINE
940 return false; // LCOV_EXCL_LINE
941 }
942
943 // save parameters to file
944 //
945 std::ofstream conf;
946 conf.open(filename);
947 if(!conf.is_open())
948 {
949 f_errno = errno; // LCOV_EXCL_LINE
950 return false; // LCOV_EXCL_LINE
951 }
952
953 // header warning with date & time
954 //
955 // (but only if the user doesn't already save comments otherwise
956 // that one would get re-added each time--some form of recursivity)
957 //
958 if(prepend_warning
959 && (f_parameters.empty()
960 || f_parameters.begin()->second.get_comment().empty()))
961 {
962 time_t const now(time(nullptr));
963 tm t;
964 gmtime_r(&now, &t);
965 char str_date[16];
966 strftime(str_date, sizeof(str_date), "%Y/%m/%d", &t);
967 char str_time[16];
968 strftime(str_time, sizeof(str_time), "%H:%M:%S", &t);
969
970 conf << "# This file was auto-generated by advgetopt on " << str_date << " at " << str_time << "." << std::endl
971 << "# Making modifications here is likely safe unless the tool handling this" << std::endl
972 << "# configuration file is actively working on it while you do the edits." << std::endl;
973 }
974 for(auto p : f_parameters)
975 {
976 // if the value has a comment, output it
977 //
978 conf << p.second.get_comment(true);
979
981 {
982 // `first` already has dashes
983 //
984 conf << p.first;
985 }
986 else
987 {
988 for(auto const c : p.first)
989 {
990 conf << (c == '-' ? '_' : c);
991 }
992 }
993
995 {
996 conf << ' ';
997 }
999 {
1000 conf << ':';
1001 }
1002 else
1003 {
1004 conf << '=';
1005 }
1006
1007 // prevent saving \r and \n characters as is when part of the
1008 // value; also double \ otherwise reading those back would fail
1009 //
1010 std::string const value(snapdev::string_replace_many(
1011 p.second.get_value()
1012 , {
1013 { "\\", "\\\\" },
1014 { "\\r", "\\r" },
1015 { "\\n", "\\n" },
1016 { "\\t", "\\t" },
1017 }));
1018 conf << value << std::endl;
1019
1020 if(!conf)
1021 {
1022 return false; // LCOV_EXCL_LINE
1023 }
1024 }
1025
1026 // it all worked, it's considered saved now
1027 //
1028 f_modified = false;
1029 }
1030
1031 return true;
1032}
1033
1034
1047conf_file::conf_file(conf_file_setup const & setup)
1048 : f_setup(setup)
1049{
1051}
1052
1053
1068{
1069 return f_setup;
1070}
1071
1072
1102 callback_t const & c
1103 , std::string const & parameter_name)
1104{
1105 cppthread::guard lock(get_global_mutex());
1106
1108 f_callbacks.emplace_back(f_next_callback_id, c, parameter_name);
1109 return f_next_callback_id;
1110}
1111
1112
1123{
1124 cppthread::guard lock(get_global_mutex());
1125
1126 auto it(std::find_if(
1127 f_callbacks.begin()
1128 , f_callbacks.end()
1129 , [id](auto e)
1130 {
1131 return e.f_id == id;
1132 }));
1133 if(it != f_callbacks.end())
1134 {
1135 f_callbacks.erase(it);
1136 }
1137}
1138
1139
1153 callback_action_t action
1154 , std::string const & parameter_name
1155 , std::string const & value)
1156{
1157 callback_vector_t callbacks;
1158 callbacks.reserve(f_callbacks.size());
1159
1160 {
1161 cppthread::guard lock(get_global_mutex());
1162 callbacks = f_callbacks;
1163 }
1164
1165 for(auto & e : callbacks)
1166 {
1167 if(e.f_parameter_name.empty()
1168 || e.f_parameter_name == parameter_name)
1169 {
1170 e.f_callback(shared_from_this(), action, parameter_name, value);
1171 }
1172 }
1173}
1174
1175
1189{
1190 cppthread::guard lock(get_global_mutex());
1191
1192 return f_exists;
1193}
1194
1195
1213{
1214 cppthread::guard lock(get_global_mutex());
1215
1216 return f_errno;
1217}
1218
1219
1238
1239
1251
1252
1271{
1272 cppthread::guard lock(get_global_mutex());
1273
1274 return f_sections;
1275}
1276
1277
1296{
1297 cppthread::guard lock(get_global_mutex());
1298
1299 return f_parameters;
1300}
1301
1302
1318bool conf_file::has_parameter(std::string name) const
1319{
1320 std::replace(name.begin(), name.end(), '_', '-');
1321
1322 cppthread::guard lock(get_global_mutex());
1323
1324 auto it(f_parameters.find(name));
1325 return it != f_parameters.end();
1326}
1327
1328
1346std::string conf_file::get_parameter(std::string name) const
1347{
1348 std::replace(name.begin(), name.end(), '_', '-');
1349
1350 cppthread::guard lock(get_global_mutex());
1351
1352 auto it(f_parameters.find(name));
1353 if(it != f_parameters.end())
1354 {
1355 if(f_variables != nullptr)
1356 {
1357 return f_variables->process_value(it->second);
1358 }
1359 else
1360 {
1361 return it->second;
1362 }
1363 }
1364 return std::string();
1365}
1366
1367
1447 std::string section
1448 , std::string name
1449 , std::string const & value
1450 , assignment_t a
1451 , std::string const & comment)
1452{
1453 // use the tokenize_string() function because we do not want to support
1454 // quoted strings in this list of sections which our split_string()
1455 // does automatically
1456 //
1457 string_list_t section_list;
1458
1459 std::replace(section.begin(), section.end(), '_', '-');
1460 std::replace(name.begin(), name.end(), '_', '-');
1461
1462 char const * n(name.c_str());
1463
1464 // global scope? if so ignore the section parameter
1465 //
1467 && n[0] == ':'
1468 && n[1] == ':')
1469 {
1470 do
1471 {
1472 ++n;
1473 }
1474 while(*n == ':');
1475 }
1476 else
1477 {
1478 snapdev::tokenize_string(section_list
1479 , section
1480 , "::"
1481 , true
1482 , std::string()
1483 , &snapdev::string_predicate<string_list_t>);
1484 }
1485
1486 char const * s(n);
1487 while(*n != '\0')
1488 {
1490 && *n == '.')
1491 {
1492 if(s == n)
1493 {
1494 cppthread::log << cppthread::log_level_t::error
1495 << "option name \""
1496 << name
1497 << "\" cannot start with a period (.)."
1498 << cppthread::end;
1499 return false;
1500 }
1501 section_list.push_back(std::string(s, n - s));
1502 do
1503 {
1504 ++n;
1505 }
1506 while(*n == '.');
1507 s = n;
1508 }
1510 && n[0] == ':'
1511 && n[1] == ':')
1512 {
1513 if(s == n)
1514 {
1515 cppthread::log << cppthread::log_level_t::error
1516 << "option name \""
1517 << name
1518 << "\" cannot start with a scope operator (::)."
1519 << cppthread::end;
1520 return false;
1521 }
1522 section_list.push_back(std::string(s, n - s));
1523 do
1524 {
1525 ++n;
1526 }
1527 while(*n == ':');
1528 s = n;
1529 }
1530 else
1531 {
1532 ++n;
1533 }
1534 }
1535 if(s == n)
1536 {
1537 cppthread::log << cppthread::log_level_t::error
1538 << "option name \""
1539 << name
1540 << "\" cannot end with a section operator or be empty."
1541 << cppthread::end;
1542 return false;
1543 }
1544 std::string param_name(s, n - s);
1545
1546 std::string const section_name(snapdev::join_strings(section_list, "::"));
1547
1549 && !section_list.empty())
1550 {
1551 cppthread::log << cppthread::log_level_t::error
1552 << "option name \""
1553 << name
1554 << "\" cannot be added to section \""
1555 << section_name
1556 << "\" because there is no section support for this configuration file."
1557 << cppthread::end;
1558 return false;
1559 }
1561 && section_list.size() > 1)
1562 {
1563 if(section_list.size() == 2
1564 && section_list[0] == f_setup.get_section_to_ignore())
1565 {
1566 section_list.erase(section_list.begin());
1567 }
1568 if(section_list.size() > 1)
1569 {
1570 cppthread::log << cppthread::log_level_t::error
1571 << "option name \""
1572 << name
1573 << "\" cannot be added to section \""
1574 << section_name
1575 << "\" because this configuration only accepts one section level."
1576 << cppthread::end;
1577 return false;
1578 }
1579 }
1580
1581 section_list.push_back(param_name);
1582 std::string const full_name(snapdev::join_strings(section_list, "::"));
1583
1584 // verify that each section name only includes characters we accept
1585 // for a parameter name
1586 //
1587 // WARNING: we do not test with full_name because it includes ':'
1588 //
1589 for(auto sn : section_list)
1590 {
1591 for(char const * f(sn.c_str()); *f != '\0'; ++f)
1592 {
1593 switch(*f)
1594 {
1595 case '\001': // forbid controls
1596 case '\002':
1597 case '\003':
1598 case '\004':
1599 case '\005':
1600 case '\006':
1601 case '\007':
1602 case '\010':
1603 case '\011':
1604 case '\012':
1605 case '\013':
1606 case '\014':
1607 case '\015':
1608 case '\016':
1609 case '\017':
1610 case '\020':
1611 case '\021':
1612 case '\022':
1613 case '\023':
1614 case '\024':
1615 case '\025':
1616 case '\026':
1617 case '\027':
1618 case '\030':
1619 case '\031':
1620 case '\032':
1621 case '\033':
1622 case '\034':
1623 case '\035':
1624 case '\036':
1625 case '\037':
1626 case ' ': // forbid spaces
1627 case '\'': // forbid all quotes
1628 case '"': // forbid all quotes
1629 case ';': // forbid all comment operators
1630 case '#': // forbid all comment operators
1631 case '/': // forbid all comment operators
1632 case '=': // forbid all assignment operators
1633 case ':': // forbid all assignment operators
1634 case '?': // forbid all assignment operators (for later)
1635 case '+': // forbid all assignment operators (for later)
1636 case '\\': // forbid backslashes
1637 cppthread::log << cppthread::log_level_t::error
1638 << "section \""
1639 << sn
1640 << "\" from parameter \""
1641 << full_name
1642 << "\" on line "
1643 << f_line
1644 << " in configuration file \""
1646 << "\" includes a character (\\"
1647 << std::oct << std::setfill('0') << std::setw(3) << static_cast<int>(*f) << std::dec
1648 << ") not acceptable for a section or parameter name (controls, space, quotes, and \";#/=:?+\\\")."
1649 << cppthread::end;
1650 return false;
1651
1652 }
1653 }
1654 }
1655
1656 cppthread::guard lock(get_global_mutex());
1657
1658 // add the section to the list of sections
1659 //
1660 // TODO: should we have a list of all the parent sections? Someone can
1661 // write "a::b::c::d = 123" and we currently only get section
1662 // "a::b::c", no section "a" and no section "a::b".
1663 //
1664 if(!section_name.empty())
1665 {
1666 f_sections.insert(section_name);
1667 }
1668
1670 auto it(f_parameters.find(full_name));
1671 if(it == f_parameters.end())
1672 {
1673 f_parameters[full_name] = value;
1674 f_parameters[full_name].set_comment(comment);
1675 f_parameters[full_name].set_line(f_line);
1676 f_parameters[full_name].set_assignment_operator(a);
1677 }
1678 else
1679 {
1680 if(f_reading)
1681 {
1682 // this is just a warning; it can be neat to know about such
1683 // problems and fix them early
1684 //
1685 cppthread::log << cppthread::log_level_t::warning
1686 << "parameter \""
1687 << full_name
1688 << "\" on line "
1689 << f_line
1690 << " in configuration file \""
1692 << "\" was found twice in the same configuration file."
1693 << cppthread::end;
1694 }
1695
1696 switch(a)
1697 {
1700 it->second = value;
1701 break;
1702
1704 // already set, do not overwrite
1705 return false;
1706
1708 it->second.set_value(it->second.get_value() + value);
1709 break;
1710
1712 cppthread::log << cppthread::log_level_t::error
1713 << "parameter \""
1714 << name
1715 << "\" is already defined and it cannot be overridden with the ':=' operator on line "
1716 << f_line
1717 << " from configuration file \""
1719 << "\"."
1720 << cppthread::end;
1721 return false;
1722
1723 }
1724
1726 }
1727
1728 if(!f_reading)
1729 {
1730 f_modified = true;
1731
1732 value_changed(action, full_name, value);
1733 }
1734
1735 return true;
1736}
1737
1738
1750bool conf_file::erase_parameter(std::string name)
1751{
1752 std::replace(name.begin(), name.end(), '_', '-');
1753
1754 auto it(f_parameters.find(name));
1755 if(it == f_parameters.end())
1756 {
1757 return false;
1758 }
1759
1760 f_parameters.erase(it);
1761
1762 if(!f_reading)
1763 {
1764 f_modified = true;
1765
1766 value_changed(callback_action_t::erased, name, std::string());
1767 }
1768
1769 return true;
1770}
1771
1772
1783{
1784 while(!f_parameters.empty())
1785 {
1786 erase_parameter(f_parameters.begin()->first);
1787 }
1788}
1789
1790
1803{
1804 return f_modified;
1805}
1806
1807
1826int conf_file::getc(std::ifstream & in)
1827{
1828 if(f_unget_char != '\0')
1829 {
1830 int const r(f_unget_char);
1831 f_unget_char = '\0';
1832 return r;
1833 }
1834
1835 char c;
1836 in.get(c);
1837
1838 if(!in)
1839 {
1840 return EOF;
1841 }
1842
1843 return static_cast<std::uint8_t>(c);
1844}
1845
1846
1863{
1864 if(f_unget_char != '\0')
1865 {
1866 throw getopt_logic_error("conf_file::ungetc() called when the f_unget_char variable member is not '\\0'."); // LCOV_EXCL_LINE
1867 }
1868 f_unget_char = c;
1869}
1870
1871
1890bool conf_file::get_line(std::ifstream & in, std::string & line)
1891{
1892 line.clear();
1893
1894 for(;;)
1895 {
1896 int c(getc(in));
1897 if(c == EOF)
1898 {
1899 return !line.empty();
1900 }
1901 if(c == ';'
1903 {
1904 return true;
1905 }
1906
1907 while(c == '\n' || c == '\r')
1908 {
1909 // count the "\r\n" sequence as one line
1910 //
1911 if(c == '\r')
1912 {
1913 c = getc(in);
1914 if(c != '\n')
1915 {
1916 ungetc(c);
1917 }
1918 c = '\n';
1919 }
1920
1921 ++f_line;
1923 {
1925 // continuation support
1926 return true;
1927
1929 c = getc(in);
1930 if(!iswspace(c))
1931 {
1932 ungetc(c);
1933 return true;
1934 }
1935 do
1936 {
1937 c = getc(in);
1938 }
1939 while(iswspace(c));
1940 break;
1941
1943 if(line.empty()
1944 || line.back() != '&')
1945 {
1946 return true;
1947 }
1948 line.pop_back();
1949 c = getc(in);
1950 break;
1951
1953 if(line.empty()
1954 || line.back() != '\\')
1955 {
1956 return true;
1957 }
1958 line.pop_back();
1959 c = getc(in);
1960 break;
1961
1963 c = getc(in);
1964 if(c != '&')
1965 {
1966 ungetc(c);
1967 return true;
1968 }
1969 c = getc(in);
1970 break;
1971
1973 // if we have a comment, we want to return immediately;
1974 // at this time, the comments are not multi-line so
1975 // the call can return true only if we were reading the
1976 // very first line
1977 //
1978 if(is_comment(line.c_str()))
1979 {
1980 return true;
1981 }
1982 // the semicolon is checked earlier, just keep the newline
1983 // in this case (but not at the start)
1984 //
1985 if(!line.empty() || c != '\n')
1986 {
1987 line += c;
1988 }
1989 c = getc(in);
1990 break;
1991
1992 }
1993 }
1994
1995 // we just read the last line
1996 if(c == EOF)
1997 {
1998 return true;
1999 }
2000
2001 line += c;
2002 }
2003}
2004
2005
2021{
2022 snapdev::safe_variable<decltype(f_reading)> safe_reading(f_reading, true);
2023
2024 std::ifstream conf(f_setup.get_filename());
2025 if(!conf)
2026 {
2027 f_errno = errno;
2028 return;
2029 }
2030 f_exists = true;
2031
2032 bool const save_comment((f_setup.get_comment() & COMMENT_SAVE) != 0);
2033 std::string current_section;
2034 std::vector<std::string> sections;
2035 std::string str;
2036 std::string last_comment;
2037 f_line = 0;
2038 while(get_line(conf, str))
2039 {
2040 char const * s(str.c_str());
2041 while(iswspace(*s))
2042 {
2043 ++s;
2044 }
2045 if(*s == '\0'
2046 || is_comment(s))
2047 {
2048 // skip empty lines and comments
2049 //
2050 if(save_comment)
2051 {
2052 last_comment += str;
2053 last_comment += '\n'; // str does not include the newline
2054 }
2055 continue;
2056 }
2058 && *s == '}')
2059 {
2060 current_section = sections.back();
2061 sections.pop_back();
2062 continue;
2063 }
2064 char const * str_name(s);
2065 char const * e(nullptr);
2067 && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}'))
2068 && ((f_setup.get_section_operator() & SECTION_OPERATOR_INI_FILE) == 0 || *s != ']')
2069 && *s != '\0'
2070 && !iswspace(*s))
2071 {
2072 ++s;
2073 }
2074 if(iswspace(*s))
2075 {
2076 e = s;
2077 while(iswspace(*s))
2078 {
2079 ++s;
2080 }
2081 if(*s != '\0'
2084 && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}')))
2085 {
2086 cppthread::log << cppthread::log_level_t::error
2087 << "option name from \""
2088 << str
2089 << "\" on line "
2090 << f_line
2091 << " in configuration file \""
2093 << "\" cannot include a space, missing assignment operator?"
2094 << cppthread::end;
2095 continue;
2096 }
2097 }
2098 if(e == nullptr)
2099 {
2100 e = s;
2101 }
2102 if(e - str_name == 0)
2103 {
2104 cppthread::log << cppthread::log_level_t::error
2105 << "no option name in \""
2106 << str
2107 << "\" on line "
2108 << f_line
2109 << " from configuration file \""
2111 << "\", missing name before the assignment operator?"
2112 << cppthread::end;
2113 continue;
2114 }
2115 std::string name(str_name, e - str_name);
2116 std::replace(name.begin(), name.end(), '_', '-');
2117 if(name[0] == '-')
2118 {
2119 cppthread::log << cppthread::log_level_t::error
2120 << "option names in configuration files cannot start with a dash or an underscore in \""
2121 << str
2122 << "\" on line "
2123 << f_line
2124 << " from configuration file \""
2126 << "\"."
2127 << cppthread::end;
2128 continue;
2129 }
2131 && name.length() >= 1
2132 && name[0] == '['
2133 && *s == ']')
2134 {
2135 ++s;
2136 if(!sections.empty())
2137 {
2138 cppthread::log << cppthread::log_level_t::error
2139 << "`[...]` sections can't be used within a `section { ... }` on line "
2140 << f_line
2141 << " from configuration file \""
2143 << "\"."
2144 << cppthread::end;
2145 continue;
2146 }
2147 while(iswspace(*s))
2148 {
2149 ++s;
2150 }
2151 if(*s != '\0'
2152 && !is_comment(s))
2153 {
2154 cppthread::log << cppthread::log_level_t::error
2155 << "section names in configuration files cannot be followed by anything other than spaces in \""
2156 << str
2157 << "\" on line "
2158 << f_line
2159 << " from configuration file \""
2161 << "\"."
2162 << cppthread::end;
2163 continue;
2164 }
2165 if(name.length() == 1)
2166 {
2167 // "[]" removes the section
2168 //
2169 current_section.clear();
2170 }
2171 else
2172 {
2173 current_section = name.substr(1);
2174 current_section += "::";
2175 }
2176 last_comment.clear();
2177 }
2179 && *s == '{')
2180 {
2181 sections.push_back(current_section);
2182 current_section += name;
2183 current_section += "::";
2184 last_comment.clear();
2185 }
2186 else
2187 {
2188 assignment_t const a(is_assignment_operator(s, true));
2189 while(iswspace(*s))
2190 {
2191 ++s;
2192 }
2193 for(e = str.c_str() + str.length(); e > s; --e)
2194 {
2195 if(!iswspace(e[-1]))
2196 {
2197 break;
2198 }
2199 }
2200 std::size_t const len(e - s);
2201 std::string const value(snapdev::string_replace_many(
2202 std::string(s, len)
2203 , {
2204 { "\\\\", "\\" },
2205 { "\\r", "\r" },
2206 { "\\n", "\n" },
2207 { "\\t", "\t" },
2208 }));
2210 current_section
2211 , name
2212 , unquote(value)
2213 , a
2214 , last_comment);
2215 last_comment.clear();
2216 }
2217 }
2218 if(!conf.eof())
2219 {
2220 f_errno = errno; // LCOV_EXCL_LINE
2221 cppthread::log << cppthread::log_level_t::error // LCOV_EXCL_LINE
2222 << "an error occurred while reading line " // LCOV_EXCL_LINE
2223 << f_line // LCOV_EXCL_LINE
2224 << " of configuration file \"" // LCOV_EXCL_LINE
2225 << f_setup.get_filename() // LCOV_EXCL_LINE
2226 << "\"." // LCOV_EXCL_LINE
2227 << cppthread::end; // LCOV_EXCL_LINE
2228 }
2229 if(!sections.empty())
2230 {
2231 cppthread::log << cppthread::log_level_t::error
2232 << "unterminated `section { ... }`, the `}` is missing in configuration file \""
2234 << "\"."
2235 << cppthread::end;
2236 }
2237}
2238
2239
2253 char const * & s
2254 , bool skip) const
2255{
2256 assignment_operator_t const assignment_operator(f_setup.get_assignment_operator());
2257 if(*s == '+'
2258 || *s == '?'
2259 || (*s == ':' && s[1] == '='))
2260 {
2261 if((assignment_operator & ASSIGNMENT_OPERATOR_EXTENDED) != 0
2262 && s[1] == '=')
2263 {
2264 char op(s[0]);
2265 if(skip)
2266 {
2267 s += 2;
2268 }
2269 switch(op)
2270 {
2271 case '+':
2273
2274 case '?':
2276
2277 case ':':
2279
2280 default:
2281 throw getopt_logic_error("assignment not properly handled in is_assignment_operator()");
2282
2283 }
2284 }
2285 }
2286 else if(((assignment_operator & ASSIGNMENT_OPERATOR_EQUAL) != 0 && *s == '=')
2287 || ((assignment_operator & ASSIGNMENT_OPERATOR_COLON) != 0 && *s == ':')
2288 || ((assignment_operator & ASSIGNMENT_OPERATOR_SPACE) != 0 && std::iswspace(*s)))
2289 {
2290 if(skip)
2291 {
2292 ++s;
2293 }
2295 }
2296
2298}
2299
2300
2321bool conf_file::is_comment(char const * s) const
2322{
2323 comment_t const comment(f_setup.get_comment());
2324 if((comment & COMMENT_INI) != 0
2325 && *s == ';')
2326 {
2327 return true;
2328 }
2329
2330 if((comment & COMMENT_SHELL) != 0
2331 && *s == '#')
2332 {
2333 return true;
2334 }
2335
2336 if((comment & COMMENT_CPP) != 0
2337 && s[0] == '/'
2338 && s[1] == '/')
2339 {
2340 return true;
2341 }
2342
2343 return false;
2344}
2345
2346
2366 std::string const & section_name
2367 , variables::pointer_t vars)
2368{
2369 if(vars == nullptr)
2370 {
2371 return -1;
2372 }
2373
2374 // verify/canonicalize the section variable name
2375 //
2376 auto section(f_sections.find(section_name));
2377 if(section == f_sections.end())
2378 {
2379 return -1;
2380 }
2381
2382 // do not view that section as such anymore
2383 //
2384 f_sections.erase(section);
2385
2386 int found(0);
2387 std::string starts_with(section_name);
2388 starts_with += "::";
2389 for(auto const & param : get_parameters())
2390 {
2391 if(param.first.length() > starts_with.length()
2392 && strncmp(param.first.c_str(), starts_with.c_str(), starts_with.length()) == 0)
2393 {
2394 vars->set_variable(
2395 param.first.substr(starts_with.length())
2396 , param.second
2397 , param.second.get_assignment_operator());
2398 ++found;
2399
2400 // this is safe because get_parameters() returned
2401 // a copy of the list of parameters
2402 //
2403 erase_parameter(param.first);
2404 }
2405 }
2406
2407 return found;
2408}
2409
2410
2418bool iswspace(int c)
2419{
2420 return c != '\n'
2421 && c != '\r'
2422 && std::iswspace(c);
2423}
2424
2425
2426} // namespace advgetopt
2427// 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.
Definition version.h:37
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.