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>
179 std::string
const & filename
185 : f_original_filename(filename)
186 , f_line_continuation(line_continuation)
187 , f_assignment_operator(assignment_operator == 0
189 : assignment_operator)
191 , f_section_operator(section_operator)
192 , f_name_separator(name_separator)
199 std::string
const & filename
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)
218 throw getopt_invalid(
"trying to load a configuration file using an empty filename.");
223 std::unique_ptr<char,
decltype(&::free)> fn(realpath(
f_original_filename.c_str(),
nullptr), &::free);
457 std::stringstream ss;
464 std::vector<std::string> params;
471 name =
"single-line";
498 throw getopt_logic_error(
"unexpected line continuation.");
501 params.push_back(
"line-continuation=" + name);
506 std::vector<std::string> assignments;
509 assignments.push_back(
"equal");
513 assignments.push_back(
"colon");
517 assignments.push_back(
"space");
521 assignments.push_back(
"extended");
523 if(!assignments.empty())
525 params.push_back(
"assignment-operator=" + snapdev::join_strings(assignments,
","));
531 std::vector<std::string> comment;
534 comment.push_back(
"ini");
538 comment.push_back(
"shell");
542 comment.push_back(
"cpp");
546 comment.push_back(
"save");
550 params.push_back(
"comment=none");
554 params.push_back(
"comment=" + snapdev::join_strings(comment,
","));
560 std::vector<std::string> section_operator;
563 section_operator.push_back(
"c");
567 section_operator.push_back(
"cpp");
571 section_operator.push_back(
"block");
575 section_operator.push_back(
"ini-file");
577 if(!section_operator.empty())
579 params.push_back(
"section-operator=" + snapdev::join_strings(section_operator,
","));
583 std::string
const query_string(snapdev::join_strings(params,
"&"));
584 if(!query_string.empty())
657 : f_value(rhs.f_value)
658 , f_comment(rhs.f_comment)
660 , f_assignment_operator(rhs.f_assignment_operator)
691parameter_value::operator std::string ()
const
707 std::string
const trimmed(snapdev::trim_string(comment));
815 if(it != g_conf_files.end())
817 if(it->second->get_setup().get_config_url() != setup.
get_config_url())
819 throw getopt_logic_error(
"trying to load configuration file \""
821 +
"\" but an existing configuration file with the same name was loaded with URL: \""
822 + it->second->get_setup().get_config_url()
852 g_conf_files.clear();
890 std::string backup_extension
891 ,
bool replace_backup
892 ,
bool prepend_warning
893 , std::string output_filename)
897 std::string
const & filename(output_filename.empty()
903 if(!backup_extension.empty())
906 if(stat(filename.c_str(), &s) == 0)
908 if(backup_extension[0] !=
'.'
909 && backup_extension[0] !=
'~')
911 backup_extension.insert(0, 1,
'.');
914 std::string
const backup_filename(filename + backup_extension);
917 || access(backup_filename.c_str(), F_OK) != 0)
919 if(unlink(backup_filename.c_str()) != 0
926 if(rename(filename.c_str(), backup_filename.c_str()) != 0)
937 if(snapdev::mkdir_p(filename,
true) != 0)
962 time_t
const now(time(
nullptr));
966 strftime(str_date,
sizeof(str_date),
"%Y/%m/%d", &t);
968 strftime(str_time,
sizeof(str_time),
"%H:%M:%S", &t);
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;
978 conf << p.second.get_comment(
true);
988 for(
auto const c : p.first)
990 conf << (c ==
'-' ?
'_' : c);
1010 std::string
const value(snapdev::string_replace_many(
1011 p.second.get_value()
1018 conf << value << std::endl;
1103 , std::string
const & parameter_name)
1126 auto it(std::find_if(
1131 return e.f_id == id;
1154 , std::string
const & parameter_name
1155 , std::string
const & value)
1165 for(
auto & e : callbacks)
1167 if(e.f_parameter_name.empty()
1168 || e.f_parameter_name == parameter_name)
1170 e.f_callback(shared_from_this(), action, parameter_name, value);
1320 std::replace(name.begin(), name.end(),
'_',
'-');
1348 std::replace(name.begin(), name.end(),
'_',
'-');
1364 return std::string();
1449 , std::string
const & value
1451 , std::string
const & comment)
1459 std::replace(section.begin(), section.end(),
'_',
'-');
1460 std::replace(name.begin(), name.end(),
'_',
'-');
1462 char const * n(name.c_str());
1478 snapdev::tokenize_string(section_list
1483 , &snapdev::string_predicate<string_list_t>);
1494 cppthread::log << cppthread::log_level_t::error
1497 <<
"\" cannot start with a period (.)."
1501 section_list.push_back(std::string(s, n - s));
1515 cppthread::log << cppthread::log_level_t::error
1518 <<
"\" cannot start with a scope operator (::)."
1522 section_list.push_back(std::string(s, n - s));
1537 cppthread::log << cppthread::log_level_t::error
1540 <<
"\" cannot end with a section operator or be empty."
1544 std::string param_name(s, n - s);
1546 std::string
const section_name(snapdev::join_strings(section_list,
"::"));
1549 && !section_list.empty())
1551 cppthread::log << cppthread::log_level_t::error
1554 <<
"\" cannot be added to section \""
1556 <<
"\" because there is no section support for this configuration file."
1561 && section_list.size() > 1)
1563 if(section_list.size() == 2
1566 section_list.erase(section_list.begin());
1568 if(section_list.size() > 1)
1570 cppthread::log << cppthread::log_level_t::error
1573 <<
"\" cannot be added to section \""
1575 <<
"\" because this configuration only accepts one section level."
1581 section_list.push_back(param_name);
1582 std::string
const full_name(snapdev::join_strings(section_list,
"::"));
1589 for(
auto sn : section_list)
1591 for(
char const * f(sn.c_str()); *f !=
'\0'; ++f)
1637 cppthread::log << cppthread::log_level_t::error
1640 <<
"\" from parameter \""
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 \";#/=:?+\\\")."
1664 if(!section_name.empty())
1685 cppthread::log << cppthread::log_level_t::warning
1690 <<
" in configuration file \""
1692 <<
"\" was found twice in the same configuration file."
1708 it->second.set_value(it->second.get_value() + value);
1712 cppthread::log << cppthread::log_level_t::error
1715 <<
"\" is already defined and it cannot be overridden with the ':=' operator on line "
1717 <<
" from configuration file \""
1752 std::replace(name.begin(), name.end(),
'_',
'-');
1843 return static_cast<std::uint8_t
>(c);
1866 throw getopt_logic_error(
"conf_file::ungetc() called when the f_unget_char variable member is not '\\0'.");
1899 return !line.empty();
1907 while(c ==
'\n' || c ==
'\r')
1944 || line.back() !=
'&')
1954 || line.back() !=
'\\')
1985 if(!line.empty() || c !=
'\n')
2033 std::string current_section;
2034 std::vector<std::string> sections;
2036 std::string last_comment;
2040 char const * s(str.c_str());
2052 last_comment += str;
2053 last_comment +=
'\n';
2060 current_section = sections.back();
2061 sections.pop_back();
2064 char const * str_name(s);
2065 char const * e(
nullptr);
2086 cppthread::log << cppthread::log_level_t::error
2087 <<
"option name from \""
2091 <<
" in configuration file \""
2093 <<
"\" cannot include a space, missing assignment operator?"
2102 if(e - str_name == 0)
2104 cppthread::log << cppthread::log_level_t::error
2105 <<
"no option name in \""
2109 <<
" from configuration file \""
2111 <<
"\", missing name before the assignment operator?"
2115 std::string name(str_name, e - str_name);
2116 std::replace(name.begin(), name.end(),
'_',
'-');
2119 cppthread::log << cppthread::log_level_t::error
2120 <<
"option names in configuration files cannot start with a dash or an underscore in \""
2124 <<
" from configuration file \""
2131 && name.length() >= 1
2136 if(!sections.empty())
2138 cppthread::log << cppthread::log_level_t::error
2139 <<
"`[...]` sections can't be used within a `section { ... }` on line "
2141 <<
" from configuration file \""
2154 cppthread::log << cppthread::log_level_t::error
2155 <<
"section names in configuration files cannot be followed by anything other than spaces in \""
2159 <<
" from configuration file \""
2165 if(name.length() == 1)
2169 current_section.clear();
2173 current_section = name.substr(1);
2174 current_section +=
"::";
2176 last_comment.clear();
2181 sections.push_back(current_section);
2182 current_section += name;
2183 current_section +=
"::";
2184 last_comment.clear();
2193 for(e = str.c_str() + str.length(); e > s; --e)
2200 std::size_t
const len(e - s);
2201 std::string
const value(snapdev::string_replace_many(
2215 last_comment.clear();
2221 cppthread::log << cppthread::log_level_t::error
2222 <<
"an error occurred while reading line "
2224 <<
" of configuration file \""
2229 if(!sections.empty())
2231 cppthread::log << cppthread::log_level_t::error
2232 <<
"unterminated `section { ... }`, the `}` is missing in configuration file \""
2259 || (*s ==
':' && s[1] ==
'='))
2281 throw getopt_logic_error(
"assignment not properly handled in is_assignment_operator()");
2366 std::string
const & section_name
2387 std::string starts_with(section_name);
2388 starts_with +=
"::";
2391 if(param.first.length() > starts_with.length()
2392 && strncmp(param.first.c_str(), starts_with.c_str(), starts_with.length()) == 0)
2395 param.first.substr(starts_with.length())
2397 , param.second.get_assignment_operator());
2422 && 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.