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>
54#include <cppthread/guard.h>
55#include <cppthread/log.h>
56#include <cppthread/mutex.h>
74#include <snapdev/poison.h>
178 std::string
const & filename
184 : f_original_filename(filename)
185 , f_line_continuation(line_continuation)
186 , f_assignment_operator(assignment_operator == 0
188 : assignment_operator)
190 , f_section_operator(section_operator)
191 , f_name_separator(name_separator)
198 std::string
const & filename
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)
217 throw getopt_invalid(
"trying to load a configuration file using an empty filename.");
222 std::unique_ptr<char,
decltype(&::free)> fn(realpath(
f_original_filename.c_str(),
nullptr), &::free);
456 std::stringstream ss;
463 std::vector<std::string> params;
470 name =
"single-line";
497 throw getopt_logic_error(
"unexpected line continuation.");
500 params.push_back(
"line-continuation=" + name);
505 std::vector<std::string> assignments;
508 assignments.push_back(
"equal");
512 assignments.push_back(
"colon");
516 assignments.push_back(
"space");
520 assignments.push_back(
"extended");
522 if(!assignments.empty())
524 params.push_back(
"assignment-operator=" + snapdev::join_strings(assignments,
","));
530 std::vector<std::string> comment;
533 comment.push_back(
"ini");
537 comment.push_back(
"shell");
541 comment.push_back(
"cpp");
545 comment.push_back(
"save");
549 params.push_back(
"comment=none");
553 params.push_back(
"comment=" + snapdev::join_strings(comment,
","));
559 std::vector<std::string> section_operator;
562 section_operator.push_back(
"c");
566 section_operator.push_back(
"cpp");
570 section_operator.push_back(
"block");
574 section_operator.push_back(
"ini-file");
576 if(!section_operator.empty())
578 params.push_back(
"section-operator=" + snapdev::join_strings(section_operator,
","));
582 std::string
const query_string(snapdev::join_strings(params,
"&"));
583 if(!query_string.empty())
656 : f_value(rhs.f_value)
657 , f_comment(rhs.f_comment)
659 , f_assignment_operator(rhs.f_assignment_operator)
690parameter_value::operator std::string ()
const
706 std::string
const trimmed(snapdev::trim_string(comment));
814 if(it != g_conf_files.end())
816 if(it->second->get_setup().get_config_url() != setup.
get_config_url())
818 throw getopt_logic_error(
"trying to load configuration file \""
820 +
"\" but an existing configuration file with the same name was loaded with URL: \""
821 + it->second->get_setup().get_config_url()
851 g_conf_files.clear();
889 std::string backup_extension
890 ,
bool replace_backup
891 ,
bool prepend_warning
892 , std::string output_filename)
896 std::string
const & filename(output_filename.empty()
902 if(!backup_extension.empty())
905 if(stat(filename.c_str(), &s) == 0)
907 if(backup_extension[0] !=
'.'
908 && backup_extension[0] !=
'~')
910 backup_extension.insert(0, 1,
'.');
913 std::string
const backup_filename(filename + backup_extension);
916 || access(backup_filename.c_str(), F_OK) != 0)
918 if(unlink(backup_filename.c_str()) != 0
925 if(rename(filename.c_str(), backup_filename.c_str()) != 0)
936 if(snapdev::mkdir_p(filename,
true) != 0)
961 time_t
const now(time(
nullptr));
965 strftime(str_date,
sizeof(str_date),
"%Y/%m/%d", &t);
967 strftime(str_time,
sizeof(str_time),
"%H:%M:%S", &t);
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;
977 conf << p.second.get_comment(
true);
987 for(
auto const c : p.first)
989 conf << (c ==
'-' ?
'_' : c);
1009 std::string
const value(snapdev::string_replace_many(
1010 p.second.get_value()
1017 conf << value << std::endl;
1102 , std::string
const & parameter_name)
1125 auto it(std::find_if(
1130 return e.f_id == id;
1153 , std::string
const & parameter_name
1154 , std::string
const & value)
1164 for(
auto & e : callbacks)
1166 if(e.f_parameter_name.empty()
1167 || e.f_parameter_name == parameter_name)
1169 e.f_callback(shared_from_this(), action, parameter_name, value);
1319 std::replace(name.begin(), name.end(),
'_',
'-');
1347 std::replace(name.begin(), name.end(),
'_',
'-');
1363 return std::string();
1448 , std::string
const & value
1450 , std::string
const & comment)
1458 std::replace(section.begin(), section.end(),
'_',
'-');
1459 std::replace(name.begin(), name.end(),
'_',
'-');
1461 char const * n(name.c_str());
1477 snapdev::tokenize_string(section_list
1482 , &snapdev::string_predicate<string_list_t>);
1493 cppthread::log << cppthread::log_level_t::error
1496 <<
"\" cannot start with a period (.)."
1500 section_list.push_back(std::string(s, n - s));
1514 cppthread::log << cppthread::log_level_t::error
1517 <<
"\" cannot start with a scope operator (::)."
1521 section_list.push_back(std::string(s, n - s));
1536 cppthread::log << cppthread::log_level_t::error
1539 <<
"\" cannot end with a section operator or be empty."
1543 std::string param_name(s, n - s);
1545 std::string
const section_name(snapdev::join_strings(section_list,
"::"));
1548 && !section_list.empty())
1550 cppthread::log << cppthread::log_level_t::error
1553 <<
"\" cannot be added to section \""
1555 <<
"\" because there is no section support for this configuration file."
1560 && section_list.size() > 1)
1562 if(section_list.size() == 2
1565 section_list.erase(section_list.begin());
1567 if(section_list.size() > 1)
1569 cppthread::log << cppthread::log_level_t::error
1572 <<
"\" cannot be added to section \""
1574 <<
"\" because this configuration only accepts one section level."
1580 section_list.push_back(param_name);
1581 std::string
const full_name(snapdev::join_strings(section_list,
"::"));
1588 for(
auto sn : section_list)
1590 for(
char const * f(sn.c_str()); *f !=
'\0'; ++f)
1636 cppthread::log << cppthread::log_level_t::error
1639 <<
"\" from parameter \""
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 \";#/=:?+\\\")."
1663 if(!section_name.empty())
1684 cppthread::log << cppthread::log_level_t::warning
1689 <<
" in configuration file \""
1691 <<
"\" was found twice in the same configuration file."
1707 it->second.set_value(it->second.get_value() + value);
1711 cppthread::log << cppthread::log_level_t::error
1714 <<
"\" is already defined and it cannot be overridden with the ':=' operator on line "
1716 <<
" from configuration file \""
1751 std::replace(name.begin(), name.end(),
'_',
'-');
1842 return static_cast<std::uint8_t
>(c);
1865 throw getopt_logic_error(
"conf_file::ungetc() called when the f_unget_char variable member is not '\\0'.");
1898 return !line.empty();
1906 while(c ==
'\n' || c ==
'\r')
1943 || line.back() !=
'&')
1953 || line.back() !=
'\\')
1984 if(!line.empty() || c !=
'\n')
2032 std::string current_section;
2033 std::vector<std::string> sections;
2035 std::string last_comment;
2039 char const * s(str.c_str());
2051 last_comment += str;
2052 last_comment +=
'\n';
2059 current_section = sections.back();
2060 sections.pop_back();
2063 char const * str_name(s);
2064 char const * e(
nullptr);
2085 cppthread::log << cppthread::log_level_t::error
2086 <<
"option name from \""
2090 <<
" in configuration file \""
2092 <<
"\" cannot include a space, missing assignment operator?"
2101 if(e - str_name == 0)
2103 cppthread::log << cppthread::log_level_t::error
2104 <<
"no option name in \""
2108 <<
" from configuration file \""
2110 <<
"\", missing name before the assignment operator?"
2114 std::string name(str_name, e - str_name);
2115 std::replace(name.begin(), name.end(),
'_',
'-');
2118 cppthread::log << cppthread::log_level_t::error
2119 <<
"option names in configuration files cannot start with a dash or an underscore in \""
2123 <<
" from configuration file \""
2130 && name.length() >= 1
2135 if(!sections.empty())
2137 cppthread::log << cppthread::log_level_t::error
2138 <<
"`[...]` sections can't be used within a `section { ... }` on line "
2140 <<
" from configuration file \""
2153 cppthread::log << cppthread::log_level_t::error
2154 <<
"section names in configuration files cannot be followed by anything other than spaces in \""
2158 <<
" from configuration file \""
2164 if(name.length() == 1)
2168 current_section.clear();
2172 current_section = name.substr(1);
2173 current_section +=
"::";
2175 last_comment.clear();
2180 sections.push_back(current_section);
2181 current_section += name;
2182 current_section +=
"::";
2183 last_comment.clear();
2192 for(e = str.c_str() + str.length(); e > s; --e)
2199 std::size_t
const len(e - s);
2200 std::string
const value(snapdev::string_replace_many(
2214 last_comment.clear();
2220 cppthread::log << cppthread::log_level_t::error
2221 <<
"an error occurred while reading line "
2223 <<
" of configuration file \""
2228 if(!sections.empty())
2230 cppthread::log << cppthread::log_level_t::error
2231 <<
"unterminated `section { ... }`, the `}` is missing in configuration file \""
2258 || (*s ==
':' && s[1] ==
'='))
2280 throw getopt_logic_error(
"assignment not properly handled in is_assignment_operator()");
2365 std::string
const & section_name
2386 std::string starts_with(section_name);
2387 starts_with +=
"::";
2390 if(param.first.length() > starts_with.length()
2391 && strncmp(param.first.c_str(), starts_with.c_str(), starts_with.length()) == 0)
2394 param.first.substr(starts_with.length())
2396 , param.second.get_assignment_operator());
2421 && std::iswspace(c);
name_separator_t f_name_separator
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
line_continuation_t f_line_continuation
std::string const & get_original_filename() const
Get the original filename.
section_operator_t f_section_operator
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
name_separator_t get_name_separator() const
Retrieve the separator to use within names.
void set_section_to_ignore(std::string const §ion_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
callback_id_t f_next_callback_id
int section_to_variables(std::string const §ion_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 ¶meter_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
conf_file_setup const f_setup
variables::pointer_t f_variables
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
parameters_t f_parameters
void read_configuration()
Read a configuration file.
void value_changed(callback_action_t action, std::string const ¶meter_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
std::vector< callback_entry_t > callback_vector_t
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.
std::function< void(pointer_t conf_file, callback_action_t action, std::string const ¶meter_name, std::string const &value)> callback_t
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
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
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
constexpr section_operator_t SECTION_OPERATOR_INI_FILE
constexpr comment_t COMMENT_CPP
std::uint_fast16_t assignment_operator_t
std::uint_fast16_t section_operator_t
constexpr section_operator_t SECTION_OPERATOR_CPP
constexpr section_operator_t SECTION_OPERATOR_ONE_SECTION
constexpr comment_t COMMENT_SHELL
@ line_continuation_fortran
@ line_continuation_semicolon
@ line_continuation_rfc_822
@ line_continuation_single_line
@ line_continuation_msdos
constexpr assignment_operator_t ASSIGNMENT_OPERATOR_EQUAL
constexpr section_operator_t SECTION_OPERATOR_C
cppthread::mutex & get_global_mutex()
Get a global mutex.
constexpr name_separator_t NAME_SEPARATOR_DASHES
constexpr section_operator_t SECTION_OPERATOR_BLOCK
constexpr assignment_operator_t ASSIGNMENT_OPERATOR_SPACE
std::uint_fast16_t comment_t
constexpr comment_t COMMENT_INI
constexpr assignment_operator_t ASSIGNMENT_OPERATOR_EXTENDED
bool iswspace(int c)
Returns true if c is considered to be a whitespace.
std::string unquote(std::string const &s, std::string const &pairs)
Remove single (') or double (") quotes from a string.
std::uint_fast16_t name_separator_t
constexpr section_operator_t SECTION_OPERATOR_NONE
std::vector< std::string > string_list_t
constexpr assignment_operator_t ASSIGNMENT_OPERATOR_COLON
A few utility functions that are not specific to an object.