#pragma once // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/CLIUtils/CLI11 for details. #include #include #include #include #include "CLI/StringTools.hpp" namespace CLI { class App; namespace detail { /// Comma separated join, adds quotes if needed inline std::string ini_join(std::vector args) { std::ostringstream s; size_t start = 0; for(const auto &arg : args) { if(start++ > 0) s << " "; auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); if(it == arg.end()) s << arg; else if(arg.find(R"(")") == std::string::npos) s << R"(")" << arg << R"(")"; else s << R"(')" << arg << R"(')"; } return s.str(); } } // namespace detail /// Holds values to load into Options struct ConfigItem { /// This is the list of parents std::vector parents; /// This is the name std::string name; /// Listing of inputs std::vector inputs; /// The list of parents and name joined by "." std::string fullname() const { std::vector tmp = parents; tmp.emplace_back(name); return detail::join(tmp, "."); } }; /// This class provides a converter for configuration files. class Config { protected: std::vector items; public: /// Convert an app into a configuration virtual std::string to_config(const App *, bool, bool, std::string) const = 0; /// Convert a configuration into an app virtual std::vector from_config(std::istream &) const = 0; /// Convert a flag to a bool virtual std::vector to_flag(const ConfigItem &item) const { if(item.inputs.size() == 1) { std::string val = item.inputs.at(0); val = detail::to_lower(val); if(val == "true" || val == "on" || val == "yes") { return std::vector(1); } else if(val == "false" || val == "off" || val == "no") { return std::vector(); } else { try { size_t ui = std::stoul(val); return std::vector(ui); } catch(const std::invalid_argument &) { throw ConversionError::TrueFalse(item.fullname()); } } } else { throw ConversionError::TooManyInputsFlag(item.fullname()); } } /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure std::vector from_file(const std::string &name) { std::ifstream input{name}; if(!input.good()) throw FileError::Missing(name); return from_config(input); } /// virtual destructor virtual ~Config() = default; }; /// This converter works with INI files class ConfigINI : public Config { public: std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; std::vector from_config(std::istream &input) const override { std::string line; std::string section = "default"; std::vector output; while(getline(input, line)) { std::vector items_buffer; detail::trim(line); size_t len = line.length(); if(len > 1 && line[0] == '[' && line[len - 1] == ']') { section = line.substr(1, len - 2); } else if(len > 0 && line[0] != ';') { output.emplace_back(); ConfigItem &out = output.back(); // Find = in string, split and recombine auto pos = line.find('='); if(pos != std::string::npos) { out.name = detail::trim_copy(line.substr(0, pos)); std::string item = detail::trim_copy(line.substr(pos + 1)); items_buffer = detail::split_up(item); } else { out.name = detail::trim_copy(line); items_buffer = {"ON"}; } if(detail::to_lower(section) != "default") { out.parents = {section}; } if(out.name.find('.') != std::string::npos) { std::vector plist = detail::split(out.name, '.'); out.name = plist.back(); plist.pop_back(); out.parents.insert(out.parents.end(), plist.begin(), plist.end()); } out.inputs.insert(std::end(out.inputs), std::begin(items_buffer), std::end(items_buffer)); } } return output; } }; } // namespace CLI