#pragma once /* MOUNT CONTRO: COMPONENTS */ #include #include #include #include #include #include #include "mcc_traits.h" #include "utils.h" namespace mcc { /* * A VERY SIMPLE CONFIG-FILE READER-PARSER * * format: * key = value1, value2, ... * * a line beginning from '#' symbol is a comment and will be skipped * a line containing from only ' ' is skipped */ class MccConfigfile { public: static constexpr char COMMENT_SYMBOL = '#'; static constexpr char KEY_VALUE_DELIM = '='; static constexpr char VALUE_VALUE_DELIM = ','; typedef std::variant> config_value_t; typedef std::unordered_map>> config_t; MccConfigfile() = default; MccConfigfile(std::derived_from> auto const& stream) : MccConfigfile() { load(stream); } MccConfigfile(traits::mcc_input_char_range auto const& filename) : MccConfigfile() { load(filename); } // factor for default config static MccConfigfile generateDefault() { MccConfigfile cfg; // default listen TCP port cfg["endpoint"] = "tcp://0.0.0.0:3490"; // default minimal pointable altitude (in degrees) cfg["min_alt"] = "10.0"; return cfg; } config_value_t& operator[](traits::mcc_input_char_range auto&& key) { if constexpr (std::convertible_to, typename config_t::key_type>) { return _currentConfig[key]; } else { typename config_t::key_type skey; std::ranges::copy(std::forward(key), std::back_inserter(skey)); return _currentConfig[key]; } } const config_value_t& operator[](traits::mcc_input_char_range auto&& key) const { return const_cast(*this).operator[](std::forward(key)); } std::error_code load(std::derived_from> auto& stream) { _currentConfig.clear(); // read line-by-line for (std::string line; std::getline(stream, line);) { if (line.empty()) { continue; } auto sv = utils::trimSpaces(line, utils::TrimType::TRIM_LEFT); if (sv.size()) { if (sv[0] == COMMENT_SYMBOL) { // comment string continue; } auto it = std::ranges::find(sv, KEY_VALUE_DELIM); if (it == sv.begin()) { // empty key! skip! continue; } auto key = utils::trimSpaces(std::string_view{sv.begin(), it}, utils::TrimType::TRIM_RIGHT); std::string skey = {key.begin(), key.end()}; if (it == sv.end()) { // only key _currentConfig[skey] = {}; } else { // key and value auto vals = utils::trimSpaces(std::string_view{it + 1, sv.end()}); auto it_dlm = std::ranges::find(vals, VALUE_VALUE_DELIM); if (it_dlm == vals.end()) { // scalar value _currentConfig[skey] = std::string{vals.begin(), vals.end()}; } else { // vector of values std::vector elems; // while (it_dlm != vals.end()) { // auto el = utils::trimSpaces(std::string_view{vals.begin(), it_dlm}); // elems.emplace_back(el.begin(), el.end()); // vals = {it_dlm + 1, vals.end()}; // it_dlm = std::ranges::find(vals, VALUE_VALUE_DELIM); // } for (const auto& el : std::views::split(vals, VALUE_VALUE_DELIM) | std::views::transform([](const auto& sv) { return utils::trimSpaces(sv); })) { elems.emplace_back(el.begin(), el.end()); } _currentConfig[skey] = elems; } } } else { // the string contains from only spaces continue; } } return {}; } std::error_code load(traits::mcc_input_char_range auto const& filename) { std::filesystem::path pt(filename.begin(), filename.end()); // first, check file existence std::error_code ec; auto pt_cn = std::filesystem::canonical(pt, ec); if (ec) { return ec; } std::ifstream fin(pt_cn); if (!fin.is_open()) { return std::make_error_code(std::errc::no_such_file_or_directory); // return std::make_error_code(std::errc::permission_denied); } _currentFilename = pt_cn.string(); load(fin); fin.close(); return {}; } std::error_code save() const { std::ofstream fst(_currentFilename, std::ios::trunc); if (!fst.is_open()) { return std::make_error_code(std::errc::no_such_file_or_directory); } const std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); fst << "#\n"; fst << "# Created by MccConfigfile class (" << std::put_time(std::localtime(&now), "[%F %T]") << ")\n"; fst << "#\n"; for (auto& [key, value] : _currentConfig) { fst << key; if (auto v_str = std::get_if<1>(&value)) { fst << " = " << *v_str; } else if (auto v_vec = std::get_if<2>(&value)) { fst << " = " << (*v_vec)[0]; for (size_t i = 1; i < v_vec->size(); ++i) { fst << ", " << (*v_vec)[i]; } } // std::monostate here fst << "\n"; } fst.close(); return {}; } std::error_code save(traits::mcc_input_char_range auto const& filename) { if (std::distance(filename.begin(), filename.end()) == 0) { return std::make_error_code(std::errc::no_such_file_or_directory); } std::ranges::copy(filename, std::back_inserter(_currentFilename)); return save(); } std::string currentFilename() const { return _currentFilename; } const config_t& config() const { return _currentConfig; } // config_t& config() // { // return _currentConfig; // } private: std::string _currentFilename; config_t _currentConfig; }; } // namespace mcc