advgetopt 2.0.50
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
1220int conf_file::get_errno(bool clear) const
1221{
1222 cppthread::guard lock(get_global_mutex());
1223
1224 int e(f_errno);
1225 if(clear)
1226 {
1227 f_errno = 0;
1228 }
1229 return e;
1230}
1231
1232
1251
1252
1264
1265
1284{
1285 cppthread::guard lock(get_global_mutex());
1286
1287 return f_sections;
1288}
1289
1290
1309{
1310 cppthread::guard lock(get_global_mutex());
1311
1312 return f_parameters;
1313}
1314
1315
1331bool conf_file::has_parameter(std::string name) const
1332{
1333 std::replace(name.begin(), name.end(), '_', '-');
1334
1335 cppthread::guard lock(get_global_mutex());
1336
1337 auto it(f_parameters.find(name));
1338 return it != f_parameters.end();
1339}
1340
1341
1359std::string conf_file::get_parameter(std::string name) const
1360{
1361 std::replace(name.begin(), name.end(), '_', '-');
1362
1363 cppthread::guard lock(get_global_mutex());
1364
1365 auto it(f_parameters.find(name));
1366 if(it != f_parameters.end())
1367 {
1368 if(f_variables != nullptr)
1369 {
1370 return f_variables->process_value(it->second);
1371 }
1372 else
1373 {
1374 return it->second;
1375 }
1376 }
1377 return std::string();
1378}
1379
1380
1460 std::string section
1461 , std::string name
1462 , std::string const & value
1463 , assignment_t a
1464 , std::string const & comment)
1465{
1466 // use the tokenize_string() function because we do not want to support
1467 // quoted strings in this list of sections which our split_string()
1468 // does automatically
1469 //
1470 string_list_t section_list;
1471
1472 std::replace(section.begin(), section.end(), '_', '-');
1473 std::replace(name.begin(), name.end(), '_', '-');
1474
1475 char const * n(name.c_str());
1476
1477 // global scope? if so ignore the section parameter
1478 //
1480 && n[0] == ':'
1481 && n[1] == ':')
1482 {
1483 do
1484 {
1485 ++n;
1486 }
1487 while(*n == ':');
1488 }
1489 else
1490 {
1491 snapdev::tokenize_string(section_list
1492 , section
1493 , "::"
1494 , true
1495 , std::string()
1496 , &snapdev::string_predicate<string_list_t>);
1497 }
1498
1499 char const * s(n);
1500 while(*n != '\0')
1501 {
1503 && *n == '.')
1504 {
1505 if(s == n)
1506 {
1507 cppthread::log << cppthread::log_level_t::error
1508 << "option name \""
1509 << name
1510 << "\" cannot start with a period (.)."
1511 << cppthread::end;
1512 return false;
1513 }
1514 section_list.push_back(std::string(s, n - s));
1515 do
1516 {
1517 ++n;
1518 }
1519 while(*n == '.');
1520 s = n;
1521 }
1523 && n[0] == ':'
1524 && n[1] == ':')
1525 {
1526 if(s == n)
1527 {
1528 cppthread::log << cppthread::log_level_t::error
1529 << "option name \""
1530 << name
1531 << "\" cannot start with a scope operator (::)."
1532 << cppthread::end;
1533 return false;
1534 }
1535 section_list.push_back(std::string(s, n - s));
1536 do
1537 {
1538 ++n;
1539 }
1540 while(*n == ':');
1541 s = n;
1542 }
1543 else
1544 {
1545 ++n;
1546 }
1547 }
1548 if(s == n)
1549 {
1550 cppthread::log << cppthread::log_level_t::error
1551 << "option name \""
1552 << name
1553 << "\" cannot end with a section operator or be empty."
1554 << cppthread::end;
1555 return false;
1556 }
1557 std::string param_name(s, n - s);
1558
1559 std::string const section_name(snapdev::join_strings(section_list, "::"));
1560
1562 && !section_list.empty())
1563 {
1564 cppthread::log << cppthread::log_level_t::error
1565 << "option name \""
1566 << name
1567 << "\" cannot be added to section \""
1568 << section_name
1569 << "\" because there is no section support for this configuration file."
1570 << cppthread::end;
1571 return false;
1572 }
1574 && section_list.size() > 1)
1575 {
1576 if(section_list.size() == 2
1577 && section_list[0] == f_setup.get_section_to_ignore())
1578 {
1579 section_list.erase(section_list.begin());
1580 }
1581 if(section_list.size() > 1)
1582 {
1583 cppthread::log << cppthread::log_level_t::error
1584 << "option name \""
1585 << name
1586 << "\" cannot be added to section \""
1587 << section_name
1588 << "\" because this configuration only accepts one section level."
1589 << cppthread::end;
1590 return false;
1591 }
1592 }
1593
1594 section_list.push_back(param_name);
1595 std::string const full_name(snapdev::join_strings(section_list, "::"));
1596
1597 // verify that each section name only includes characters we accept
1598 // for a parameter name
1599 //
1600 // WARNING: we do not test with full_name because it includes ':'
1601 //
1602 for(auto sn : section_list)
1603 {
1604 for(char const * f(sn.c_str()); *f != '\0'; ++f)
1605 {
1606 switch(*f)
1607 {
1608 case '\001': // forbid controls
1609 case '\002':
1610 case '\003':
1611 case '\004':
1612 case '\005':
1613 case '\006':
1614 case '\007':
1615 case '\010':
1616 case '\011':
1617 case '\012':
1618 case '\013':
1619 case '\014':
1620 case '\015':
1621 case '\016':
1622 case '\017':
1623 case '\020':
1624 case '\021':
1625 case '\022':
1626 case '\023':
1627 case '\024':
1628 case '\025':
1629 case '\026':
1630 case '\027':
1631 case '\030':
1632 case '\031':
1633 case '\032':
1634 case '\033':
1635 case '\034':
1636 case '\035':
1637 case '\036':
1638 case '\037':
1639 case ' ': // forbid spaces
1640 case '\'': // forbid all quotes
1641 case '"': // forbid all quotes
1642 case ';': // forbid all comment operators
1643 case '#': // forbid all comment operators
1644 case '/': // forbid all comment operators
1645 case '=': // forbid all assignment operators
1646 case ':': // forbid all assignment operators
1647 case '?': // forbid all assignment operators (for later)
1648 case '+': // forbid all assignment operators (for later)
1649 case '\\': // forbid backslashes
1650 cppthread::log << cppthread::log_level_t::error
1651 << "section \""
1652 << sn
1653 << "\" from parameter \""
1654 << full_name
1655 << "\" on line "
1656 << f_line
1657 << " in configuration file \""
1659 << "\" includes a character (\\"
1660 << std::oct << std::setfill('0') << std::setw(3) << static_cast<int>(*f) << std::dec
1661 << ") not acceptable for a section or parameter name (controls, space, quotes, and \";#/=:?+\\\")."
1662 << cppthread::end;
1663 return false;
1664
1665 }
1666 }
1667 }
1668
1669 cppthread::guard lock(get_global_mutex());
1670
1671 // add the section to the list of sections
1672 //
1673 // TODO: should we have a list of all the parent sections? Someone can
1674 // write "a::b::c::d = 123" and we currently only get section
1675 // "a::b::c", no section "a" and no section "a::b".
1676 //
1677 if(!section_name.empty())
1678 {
1679 f_sections.insert(section_name);
1680 }
1681
1683 auto it(f_parameters.find(full_name));
1684 if(it == f_parameters.end())
1685 {
1686 f_parameters[full_name] = value;
1687 f_parameters[full_name].set_comment(comment);
1688 f_parameters[full_name].set_line(f_line);
1689 f_parameters[full_name].set_assignment_operator(a);
1690 }
1691 else
1692 {
1693 if(f_reading)
1694 {
1695 // this is just a warning; it can be neat to know about such
1696 // problems and fix them early
1697 //
1698 cppthread::log << cppthread::log_level_t::warning
1699 << "parameter \""
1700 << full_name
1701 << "\" on line "
1702 << f_line
1703 << " in configuration file \""
1705 << "\" was found twice in the same configuration file."
1706 << cppthread::end;
1707 }
1708
1709 switch(a)
1710 {
1713 it->second = value;
1714 break;
1715
1717 // already set, do not overwrite
1718 return false;
1719
1721 it->second.set_value(it->second.get_value() + value);
1722 break;
1723
1725 cppthread::log << cppthread::log_level_t::error
1726 << "parameter \""
1727 << name
1728 << "\" is already defined and it cannot be overridden with the ':=' operator on line "
1729 << f_line
1730 << " from configuration file \""
1732 << "\"."
1733 << cppthread::end;
1734 return false;
1735
1736 }
1737
1739 }
1740
1741 if(!f_reading)
1742 {
1743 f_modified = true;
1744
1745 value_changed(action, full_name, value);
1746 }
1747
1748 return true;
1749}
1750
1751
1763bool conf_file::erase_parameter(std::string name)
1764{
1765 std::replace(name.begin(), name.end(), '_', '-');
1766
1767 auto it(f_parameters.find(name));
1768 if(it == f_parameters.end())
1769 {
1770 return false;
1771 }
1772
1773 f_parameters.erase(it);
1774
1775 if(!f_reading)
1776 {
1777 f_modified = true;
1778
1779 value_changed(callback_action_t::erased, name, std::string());
1780 }
1781
1782 return true;
1783}
1784
1785
1796{
1797 while(!f_parameters.empty())
1798 {
1799 erase_parameter(f_parameters.begin()->first);
1800 }
1801}
1802
1803
1816{
1817 return f_modified;
1818}
1819
1820
1839int conf_file::getc(std::ifstream & in)
1840{
1841 if(f_unget_char != '\0')
1842 {
1843 int const r(f_unget_char);
1844 f_unget_char = '\0';
1845 return r;
1846 }
1847
1848 char c;
1849 in.get(c);
1850
1851 if(!in)
1852 {
1853 return EOF;
1854 }
1855
1856 return static_cast<std::uint8_t>(c);
1857}
1858
1859
1876{
1877 if(f_unget_char != '\0')
1878 {
1879 throw getopt_logic_error("conf_file::ungetc() called when the f_unget_char variable member is not '\\0'."); // LCOV_EXCL_LINE
1880 }
1881 f_unget_char = c;
1882}
1883
1884
1903bool conf_file::get_line(std::ifstream & in, std::string & line)
1904{
1905 line.clear();
1906
1907 for(;;)
1908 {
1909 int c(getc(in));
1910 if(c == EOF)
1911 {
1912 return !line.empty();
1913 }
1914 if(c == ';'
1916 {
1917 return true;
1918 }
1919
1920 while(c == '\n' || c == '\r')
1921 {
1922 // count the "\r\n" sequence as one line
1923 //
1924 if(c == '\r')
1925 {
1926 c = getc(in);
1927 if(c != '\n')
1928 {
1929 ungetc(c);
1930 }
1931 c = '\n';
1932 }
1933
1934 ++f_line;
1936 {
1938 // continuation support
1939 return true;
1940
1942 c = getc(in);
1943 if(!iswspace(c))
1944 {
1945 ungetc(c);
1946 return true;
1947 }
1948 do
1949 {
1950 c = getc(in);
1951 }
1952 while(iswspace(c));
1953 break;
1954
1956 if(line.empty()
1957 || line.back() != '&')
1958 {
1959 return true;
1960 }
1961 line.pop_back();
1962 c = getc(in);
1963 break;
1964
1966 if(line.empty()
1967 || line.back() != '\\')
1968 {
1969 return true;
1970 }
1971 line.pop_back();
1972 c = getc(in);
1973 break;
1974
1976 c = getc(in);
1977 if(c != '&')
1978 {
1979 ungetc(c);
1980 return true;
1981 }
1982 c = getc(in);
1983 break;
1984
1986 // if we have a comment, we want to return immediately;
1987 // at this time, the comments are not multi-line so
1988 // the call can return true only if we were reading the
1989 // very first line
1990 //
1991 if(is_comment(line.c_str()))
1992 {
1993 return true;
1994 }
1995 // the semicolon is checked earlier, just keep the newline
1996 // in this case (but not at the start)
1997 //
1998 if(!line.empty() || c != '\n')
1999 {
2000 line += c;
2001 }
2002 c = getc(in);
2003 break;
2004
2005 }
2006 }
2007
2008 // we just read the last line
2009 if(c == EOF)
2010 {
2011 return true;
2012 }
2013
2014 line += c;
2015 }
2016}
2017
2018
2034{
2035 snapdev::safe_variable<decltype(f_reading)> safe_reading(f_reading, true);
2036
2037 std::ifstream conf(f_setup.get_filename());
2038 if(!conf)
2039 {
2040 f_errno = errno;
2041 return;
2042 }
2043 f_exists = true;
2044
2045 bool const save_comment((f_setup.get_comment() & COMMENT_SAVE) != 0);
2046 std::string current_section;
2047 std::vector<std::string> sections;
2048 std::string str;
2049 std::string last_comment;
2050 f_line = 0;
2051 while(get_line(conf, str))
2052 {
2053 char const * s(str.c_str());
2054 while(iswspace(*s))
2055 {
2056 ++s;
2057 }
2058 if(*s == '\0'
2059 || is_comment(s))
2060 {
2061 // skip empty lines and comments
2062 //
2063 if(save_comment)
2064 {
2065 last_comment += str;
2066 last_comment += '\n'; // str does not include the newline
2067 }
2068 continue;
2069 }
2071 && *s == '}')
2072 {
2073 current_section = sections.back();
2074 sections.pop_back();
2075 continue;
2076 }
2077 char const * str_name(s);
2078 char const * e(nullptr);
2080 && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}'))
2081 && ((f_setup.get_section_operator() & SECTION_OPERATOR_INI_FILE) == 0 || *s != ']')
2082 && *s != '\0'
2083 && !iswspace(*s))
2084 {
2085 ++s;
2086 }
2087 if(iswspace(*s))
2088 {
2089 e = s;
2090 while(iswspace(*s))
2091 {
2092 ++s;
2093 }
2094 if(*s != '\0'
2097 && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}')))
2098 {
2099 cppthread::log << cppthread::log_level_t::error
2100 << "option name from \""
2101 << str
2102 << "\" on line "
2103 << f_line
2104 << " in configuration file \""
2106 << "\" cannot include a space, missing assignment operator?"
2107 << cppthread::end;
2108 continue;
2109 }
2110 }
2111 if(e == nullptr)
2112 {
2113 e = s;
2114 }
2115 if(e - str_name == 0)
2116 {
2117 cppthread::log << cppthread::log_level_t::error
2118 << "no option name in \""
2119 << str
2120 << "\" on line "
2121 << f_line
2122 << " from configuration file \""
2124 << "\", missing name before the assignment operator?"
2125 << cppthread::end;
2126 continue;
2127 }
2128 std::string name(str_name, e - str_name);
2129 std::replace(name.begin(), name.end(), '_', '-');
2130 if(name[0] == '-')
2131 {
2132 cppthread::log << cppthread::log_level_t::error
2133 << "option names in configuration files cannot start with a dash or an underscore in \""
2134 << str
2135 << "\" on line "
2136 << f_line
2137 << " from configuration file \""
2139 << "\"."
2140 << cppthread::end;
2141 continue;
2142 }
2144 && name.length() >= 1
2145 && name[0] == '['
2146 && *s == ']')
2147 {
2148 ++s;
2149 if(!sections.empty())
2150 {
2151 cppthread::log << cppthread::log_level_t::error
2152 << "`[...]` sections can't be used within a `section { ... }` on line "
2153 << f_line
2154 << " from configuration file \""
2156 << "\"."
2157 << cppthread::end;
2158 continue;
2159 }
2160 while(iswspace(*s))
2161 {
2162 ++s;
2163 }
2164 if(*s != '\0'
2165 && !is_comment(s))
2166 {
2167 cppthread::log << cppthread::log_level_t::error
2168 << "section names in configuration files cannot be followed by anything other than spaces in \""
2169 << str
2170 << "\" on line "
2171 << f_line
2172 << " from configuration file \""
2174 << "\"."
2175 << cppthread::end;
2176 continue;
2177 }
2178 if(name.length() == 1)
2179 {
2180 // "[]" removes the section
2181 //
2182 current_section.clear();
2183 }
2184 else
2185 {
2186 current_section = name.substr(1);
2187 current_section += "::";
2188 }
2189 last_comment.clear();
2190 }
2192 && *s == '{')
2193 {
2194 sections.push_back(current_section);
2195 current_section += name;
2196 current_section += "::";
2197 last_comment.clear();
2198 }
2199 else
2200 {
2201 assignment_t const a(is_assignment_operator(s, true));
2202 while(iswspace(*s))
2203 {
2204 ++s;
2205 }
2206 for(e = str.c_str() + str.length(); e > s; --e)
2207 {
2208 if(!iswspace(e[-1]))
2209 {
2210 break;
2211 }
2212 }
2213 std::size_t const len(e - s);
2214 std::string const value(snapdev::string_replace_many(
2215 std::string(s, len)
2216 , {
2217 { "\\\\", "\\" },
2218 { "\\r", "\r" },
2219 { "\\n", "\n" },
2220 { "\\t", "\t" },
2221 }));
2223 current_section
2224 , name
2225 , unquote(value)
2226 , a
2227 , last_comment);
2228 last_comment.clear();
2229 }
2230 }
2231 if(!conf.eof())
2232 {
2233 f_errno = errno; // LCOV_EXCL_LINE
2234 cppthread::log << cppthread::log_level_t::error // LCOV_EXCL_LINE
2235 << "an error occurred while reading line " // LCOV_EXCL_LINE
2236 << f_line // LCOV_EXCL_LINE
2237 << " of configuration file \"" // LCOV_EXCL_LINE
2238 << f_setup.get_filename() // LCOV_EXCL_LINE
2239 << "\"." // LCOV_EXCL_LINE
2240 << cppthread::end; // LCOV_EXCL_LINE
2241 }
2242 if(!sections.empty())
2243 {
2244 cppthread::log << cppthread::log_level_t::error
2245 << "unterminated `section { ... }`, the `}` is missing in configuration file \""
2247 << "\"."
2248 << cppthread::end;
2249 }
2250}
2251
2252
2266 char const * & s
2267 , bool skip) const
2268{
2269 assignment_operator_t const assignment_operator(f_setup.get_assignment_operator());
2270 if(*s == '+'
2271 || *s == '?'
2272 || (*s == ':' && s[1] == '='))
2273 {
2274 if((assignment_operator & ASSIGNMENT_OPERATOR_EXTENDED) != 0
2275 && s[1] == '=')
2276 {
2277 char op(s[0]);
2278 if(skip)
2279 {
2280 s += 2;
2281 }
2282 switch(op)
2283 {
2284 case '+':
2286
2287 case '?':
2289
2290 case ':':
2292
2293 default:
2294 throw getopt_logic_error("assignment not properly handled in is_assignment_operator()");
2295
2296 }
2297 }
2298 }
2299 else if(((assignment_operator & ASSIGNMENT_OPERATOR_EQUAL) != 0 && *s == '=')
2300 || ((assignment_operator & ASSIGNMENT_OPERATOR_COLON) != 0 && *s == ':')
2301 || ((assignment_operator & ASSIGNMENT_OPERATOR_SPACE) != 0 && std::iswspace(*s)))
2302 {
2303 if(skip)
2304 {
2305 ++s;
2306 }
2308 }
2309
2311}
2312
2313
2334bool conf_file::is_comment(char const * s) const
2335{
2336 comment_t const comment(f_setup.get_comment());
2337 if((comment & COMMENT_INI) != 0
2338 && *s == ';')
2339 {
2340 return true;
2341 }
2342
2343 if((comment & COMMENT_SHELL) != 0
2344 && *s == '#')
2345 {
2346 return true;
2347 }
2348
2349 if((comment & COMMENT_CPP) != 0
2350 && s[0] == '/'
2351 && s[1] == '/')
2352 {
2353 return true;
2354 }
2355
2356 return false;
2357}
2358
2359
2379 std::string const & section_name
2380 , variables::pointer_t vars)
2381{
2382 if(vars == nullptr)
2383 {
2384 return -1;
2385 }
2386
2387 // verify/canonicalize the section variable name
2388 //
2389 auto section(f_sections.find(section_name));
2390 if(section == f_sections.end())
2391 {
2392 return -1;
2393 }
2394
2395 // do not view that section as such anymore
2396 //
2397 f_sections.erase(section);
2398
2399 int found(0);
2400 std::string starts_with(section_name);
2401 starts_with += "::";
2402 for(auto const & param : get_parameters())
2403 {
2404 if(param.first.length() > starts_with.length()
2405 && strncmp(param.first.c_str(), starts_with.c_str(), starts_with.length()) == 0)
2406 {
2407 vars->set_variable(
2408 param.first.substr(starts_with.length())
2409 , param.second
2410 , param.second.get_assignment_operator());
2411 ++found;
2412
2413 // this is safe because get_parameters() returned
2414 // a copy of the list of parameters
2415 //
2416 erase_parameter(param.first);
2417 }
2418 }
2419
2420 return found;
2421}
2422
2423
2431bool iswspace(int c)
2432{
2433 return c != '\n'
2434 && c != '\r'
2435 && std::iswspace(c);
2436}
2437
2438
2439} // namespace advgetopt
2440// 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.
int get_errno(bool clear=true) const
Get the error number opening/reading/writing the configuration file.
void remove_callback(callback_id_t id)
Remove a callback.
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.