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.