From a9974aab04389cd7e372e4bbaca2e308d71c285d Mon Sep 17 00:00:00 2001 From: "Timur A. Fatkhullin" Date: Sun, 23 Feb 2025 19:22:03 +0300 Subject: [PATCH] add config file reader-parser --- cxx/CMakeLists.txt | 6 +- cxx/comm_server_configfile.h | 145 ++++++++++++++++++++++++++++++++++ cxx/tests/configfile_test.cpp | 39 +++++++++ 3 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 cxx/comm_server_configfile.h create mode 100644 cxx/tests/configfile_test.cpp diff --git a/cxx/CMakeLists.txt b/cxx/CMakeLists.txt index ab77b2c..2a880d4 100644 --- a/cxx/CMakeLists.txt +++ b/cxx/CMakeLists.txt @@ -87,7 +87,8 @@ add_library(${CNTR_PROTO_LIB} STATIC ${CNTR_PROTO_LIB_SRC}) set(MOUNT_SERVER_APP_SRC mount.h mount_server.cpp comm_server.h comm_server_endpoint.h) set(MOUNT_SERVER_APP mount_server) -add_executable(${MOUNT_SERVER_APP} ${MOUNT_SERVER_APP_SRC}) +add_executable(${MOUNT_SERVER_APP} ${MOUNT_SERVER_APP_SRC} + comm_server_configfile.h) target_link_libraries(${MOUNT_SERVER_APP} ${CNTR_PROTO_LIB} spdlog::spdlog_header_only) if (WITH_TESTS) @@ -96,4 +97,7 @@ if (WITH_TESTS) target_link_libraries(${CNTR_PROTO_TEST_APP} ${CNTR_PROTO_LIB} spdlog::spdlog_header_only) # target_link_libraries(${CNTR_PROTO_TEST_APP} ${CNTR_PROTO_LIB}) # target_include_directories(${CNTR_PROTO_TEST_APP} PUBLIC ${SPDLOG_INCLUDE_DIRS}) + + set(CFGFILE_TEST_APP configfile_test) + add_executable(${CFGFILE_TEST_APP} tests/configfile_test.cpp) endif() diff --git a/cxx/comm_server_configfile.h b/cxx/comm_server_configfile.h new file mode 100644 index 0000000..8c59c58 --- /dev/null +++ b/cxx/comm_server_configfile.h @@ -0,0 +1,145 @@ +#pragma once + +/* MOUNT CONTRO: COMPONENTS */ + + +#include +#include +#include +#include +#include + +#include "comm_server_endpoint.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::unordered_map>> + config_t; + + template + MccConfigfile(T&& filename = nullptr) + requires(std::same_as, std::nullptr_t> || traits::mcc_input_char_range) + { + if constexpr (traits::mcc_input_char_range) { + if (load(std::forward(filename))) { + } + } + } + + + std::error_code load(traits::mcc_input_char_range auto&& 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(); + _currentConfig.clear(); + + // read line-by-line + for (std::string line; std::getline(fin, 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); + } + + _currentConfig[skey] = elems; + } + } + } else { // the string contains from only spaces + continue; + } + } + + fin.close(); + + return {}; + } + + 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 diff --git a/cxx/tests/configfile_test.cpp b/cxx/tests/configfile_test.cpp new file mode 100644 index 0000000..a0f21b1 --- /dev/null +++ b/cxx/tests/configfile_test.cpp @@ -0,0 +1,39 @@ +#include + +#include "../comm_server_configfile.h" + + + +int main(int argc, char* argv[]) +{ + if (argc < 2) { + std::cout << "Usage: " << argv[0] << " cfg_filename\n"; + return 1; + } + + mcc::MccConfigfile cfg; + + auto ec = cfg.load(std::string_view{argv[1]}); + if (ec) { + std::cout << "ERR: " << ec.message() << "\n"; + return 1; + } + + for (auto& [key, v] : cfg.config()) { + std::cout << "<" << key << "> = "; + if (v.index()) { + if (v.index() > 1) { + for (auto& el : std::get<2>(v)) { + std::cout << "<" << el << "> "; + } + std::cout << "\n"; + } else { + std::cout << "<" << std::get<1>(v) << ">\n"; + } + } else { + std::cout << "\n"; + } + } + + return 0; +}