mountcontrol/cxx/comm_server_configfile.h
2025-03-17 18:30:52 +03:00

243 lines
6.7 KiB
C++

#pragma once
/* MOUNT CONTRO: COMPONENTS */
#include <concepts>
#include <cstddef>
#include <filesystem>
#include <fstream>
#include <unordered_map>
#include <variant>
#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<std::monostate, std::string, std::vector<std::string>> config_value_t;
typedef std::unordered_map<std::string, std::variant<std::monostate, std::string, std::vector<std::string>>>
config_t;
MccConfigfile() = default;
MccConfigfile(std::derived_from<std::basic_istream<char>> 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<std::decay_t<decltype(key)>, typename config_t::key_type>) {
return _currentConfig[key];
} else {
typename config_t::key_type skey;
std::ranges::copy(std::forward<decltype(key)>(key), std::back_inserter(skey));
return _currentConfig[key];
}
}
const config_value_t& operator[](traits::mcc_input_char_range auto&& key) const
{
return const_cast<const MccConfigfile&>(*this).operator[](std::forward<decltype(key)>(key));
}
std::error_code load(std::derived_from<std::basic_istream<char>> 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<std::string> 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