aboutsummaryrefslogtreecommitdiff
path: root/lib/configuration
diff options
context:
space:
mode:
authoraqua <aqua@iserlohn-fortress.net>2024-04-28 17:46:14 +0300
committeraqua <aqua@iserlohn-fortress.net>2024-04-28 17:46:14 +0300
commitfd87ed4d17dd8fc6fb7fbc936861393c6455815d (patch)
tree0d95ec29b6e6d5948c07ee59c5c9a94653d5ea6b /lib/configuration
parentAdded poi resources to sources (diff)
downloadsmolbote-fd87ed4d17dd8fc6fb7fbc936861393c6455815d.tar.xz
Added libConfiguration unit test
Diffstat (limited to 'lib/configuration')
-rw-r--r--lib/configuration/CMakeLists.txt6
-rw-r--r--lib/configuration/configuration.cpp118
-rw-r--r--lib/configuration/configuration.h51
-rw-r--r--lib/configuration/qt_specialization.cpp31
-rw-r--r--lib/configuration/qt_specialization.h8
-rw-r--r--lib/configuration/test/configuration.cpp57
6 files changed, 173 insertions, 98 deletions
diff --git a/lib/configuration/CMakeLists.txt b/lib/configuration/CMakeLists.txt
index 9e2f34e..7952eba 100644
--- a/lib/configuration/CMakeLists.txt
+++ b/lib/configuration/CMakeLists.txt
@@ -4,3 +4,9 @@ add_library(configuration STATIC
)
target_link_libraries(configuration PUBLIC Qt6::Gui)
target_include_directories(configuration PUBLIC ${CMAKE_CURRENT_LIST_DIR})
+
+add_executable(test_configuration test/configuration.cpp)
+target_link_libraries(test_configuration PUBLIC configuration GTest::gtest_main)
+gtest_add_tests(TARGET test_configuration
+ SOURCES test/configuration.cpp
+ WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/test)
diff --git a/lib/configuration/configuration.cpp b/lib/configuration/configuration.cpp
index 75f863c..f2a2218 100644
--- a/lib/configuration/configuration.cpp
+++ b/lib/configuration/configuration.cpp
@@ -10,13 +10,15 @@
#include <algorithm>
#include <fstream>
#include <iostream>
-#include <sstream>
#include <stdexcept>
+#include <variant>
#ifndef NO_QT_SPEC
#include <QStandardPaths>
#endif
+using namespace std::placeholders;
+
static std::unique_ptr<Configuration> s_conf;
Configuration::Configuration()
@@ -27,66 +29,64 @@ Configuration::Configuration()
}
}
-Configuration::Configuration(std::initializer_list<std::pair<std::string, conf_value_t>> l) noexcept
+Configuration::Configuration(std::initializer_list<std::pair<std::string, conf_value_t>> p_list) noexcept
#ifndef NO_QT_SPEC
: m_homePath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation).toStdString())
#endif
{
- for(const auto &i : l) {
- insert_or_assign(i.first, i.second);
+ for(const auto &item : p_list) {
+ insert_or_assign(item);
}
}
-void Configuration::read_file(const std::string &location)
+bool Configuration::read_file(const std::string &location)
{
- std::fstream fs(location, std::fstream::in);
- if(fs.is_open()) {
- read(fs);
- fs.close();
+ std::fstream fstream(location, std::fstream::in);
+ bool result = false;
+
+ if(fstream.is_open()) {
+ read(fstream);
+ fstream.close();
+ result = true;
}
+
+ return result;
}
inline auto strip(std::string &value)
{
- value.erase(value.begin(), std::find_if(value.begin(), value.end(), std::bind1st(std::not_equal_to<char>(), ' ')));
- value.erase(std::find_if(value.rbegin(), value.rend(), std::bind1st(std::not_equal_to<char>(), ' ')).base(), value.end());
+ // strip whitespace at the begining
+ value.erase(value.begin(), std::find_if(value.begin(), value.end(), std::bind(std::not_equal_to<>(), ' ', _1)));
+
+ // strip whitespace from the end
+ value.erase(std::find_if(value.rbegin(), value.rend(), std::bind(std::not_equal_to<>(), ' ', _1)).base(), value.end());
+
return value;
}
-inline auto parse(const std::string &line, std::string &section)
+inline std::optional<Configuration::kv_pair> parse_line(const std::string &line, /* in/out */ std::string &section)
{
- struct {
- bool pair = false;
- std::string key;
- std::string value;
- } ret;
-
- if(line[0] == '#' || line.length() == 0) {
- return ret;
- }
-
if(line.front() == '[' && line.back() == ']') {
section = line.substr(1, line.length() - 2) + '/';
- return ret;
+ return std::nullopt;
}
const auto pos = line.find_first_of('=');
if(pos == std::string::npos) {
- return ret;
+ return std::nullopt;
}
- ret.key = line.substr(0, pos);
- strip(ret.key);
- if(ret.key.empty()) {
- return ret;
- }
- ret.key = section + ret.key;
+ std::string key = line.substr(0, pos);
+ std::string val = line.substr(pos + 1);
+ strip(key);
+ strip(val);
- ret.value = line.substr(pos + 1);
- strip(ret.value);
+ if(key.empty()) {
+ return std::nullopt;
+ }
+ key = section + key;
- ret.pair = true;
- return ret;
+ return std::make_optional<Configuration::kv_pair>({ key, val });
}
#ifdef FUZZER
@@ -100,9 +100,16 @@ extern "C" int LLVMFuzzerTestOneInput(const char *Data, long long Size)
void Configuration::read(std::basic_istream<char> &input)
{
- std::string line, section;
+ std::string line;
+ std::string section;
while(std::getline(input, line)) {
+ // skip empty lines and comments
+ if(line.length() == 0 || line[0] == '#') {
+ continue;
+ }
+
+ // include file
if(line.rfind("@@") == 0) {
if(line.rfind("@@include ") == 0) {
read_file(line.substr(10));
@@ -110,23 +117,30 @@ void Configuration::read(std::basic_istream<char> &input)
continue;
}
- const auto pair = parse(line, section);
- if(pair.pair) {
+ const auto pair = parse_line(line, section);
+ if(pair) {
+ insert_or_assign(*pair);
+ }
+ }
+}
- if(this->count(pair.key) == 0) {
- // no type has been specified for this key, assuming std::string
- insert(std::make_pair(pair.key, pair.value));
- continue;
- }
+void Configuration::insert_or_assign(const Configuration::kv_pair &pair)
+{
+ // insert
+ if(count(pair.first) == 0) {
+ insert(pair);
+ } else {
+ // when assigning we have to keep the same type
+ const auto old_value = at(pair.first);
- auto v = at(pair.key);
- if(std::holds_alternative<std::string>(v)) {
- at(pair.key) = pair.value;
- } else if(std::holds_alternative<int>(v)) {
- at(pair.key) = std::stoi(pair.value);
- } else if(std::holds_alternative<bool>(v)) {
- at(pair.key) = (pair.value == "true");
- }
+ if(std::holds_alternative<std::string>(old_value)) {
+ at(pair.first) = pair.second;
+
+ } else if(std::holds_alternative<int>(old_value)) {
+ at(pair.first) = std::stoi(std::get<std::string>(pair.second));
+
+ } else if(std::holds_alternative<bool>(old_value)) {
+ at(pair.first) = (std::get<std::string>(pair.second) == "true");
}
}
}
@@ -150,9 +164,7 @@ std::ostream &operator<<(std::ostream &out, const Configuration &obj)
// unordered_map is, well, unordered, so grab the keys and sort them before printing them
std::vector<std::string> keys;
- for(const auto &pair : obj) {
- keys.emplace_back(pair.first);
- }
+ std::transform(obj.cbegin(), obj.cend(), std::inserter(keys, keys.end()), [](const auto &pair) { return pair.first; });
std::sort(keys.begin(), keys.end());
for(const auto &key : keys) {
diff --git a/lib/configuration/configuration.h b/lib/configuration/configuration.h
index cd3c244..30f258c 100644
--- a/lib/configuration/configuration.h
+++ b/lib/configuration/configuration.h
@@ -17,44 +17,34 @@
#include <unordered_map>
#include <variant>
-#if defined(__clang__)
-#define consumable(X) [[clang::consumable(X)]]
-#define return_typestate(X) [[clang::return_typestate(X)]]
-#define callable_when(X) [[clang::callable_when(X)]]
-#define param_typestate(X) [[clang::param_typestate(X)]]
-#else
-#define consumable(X)
-#define return_typestate(X)
-#define callable_when(X)
-#define param_typestate(X)
-#endif
-
-typedef std::variant<std::string, int, bool> conf_value_t;
+using conf_value_t = std::variant<std::string, int, bool>;
template <typename T>
-concept concept_value_t = std::is_arithmetic<T>::value || std::is_same<T, bool>::value || std::is_constructible<T, std::string>::value;
+concept concept_value_t = std::is_arithmetic_v<T> || std::is_same_v<T, bool> || std::is_constructible_v<T, std::string>;
-class consumable(unconsumed) Configuration : private std::unordered_map<std::string, conf_value_t>
+class Configuration : private std::unordered_map<std::string, conf_value_t>
{
friend std::ostream &operator<<(std::ostream &out, const Configuration &obj);
public:
- return_typestate(unconsumed) Configuration();
- return_typestate(unconsumed) Configuration(std::initializer_list<std::pair<std::string, conf_value_t>> l) noexcept;
+ using kv_pair = std::pair<std::string, conf_value_t>;
+
+ Configuration();
+ Configuration(std::initializer_list<std::pair<std::string, conf_value_t>> p_list) noexcept;
Configuration(const Configuration &) = delete;
Configuration &operator=(const Configuration &) = delete;
- return_typestate(unconsumed) Configuration(Configuration && other param_typestate(unconsumed)) = default;
+ Configuration(Configuration &&other) = default;
Configuration &operator=(Configuration &&) = delete;
~Configuration() = default;
- callable_when(unconsumed) void read_file(const std::string &location);
- callable_when(unconsumed) void read(std::basic_istream<char> & input);
+ bool read_file(const std::string &location);
+ void read(std::basic_istream<char> &input);
template <typename T>
- callable_when(unconsumed) [[nodiscard]] std::optional<T> value(const char *path) const
+ [[nodiscard]] std::optional<T> value(const char *path) const
{
if(use_global) {
return instance()->value<T>(path);
@@ -68,18 +58,18 @@ public:
}
template <concept_value_t T>
- callable_when(unconsumed) [[nodiscard]] std::optional<T> value(const char *path) const
+ [[nodiscard]] std::optional<T> value(const char *p_path) const
{
if(use_global) {
- return instance()->value<T>(path);
+ return instance()->value<T>(p_path);
}
- if(this->count(path) == 0) {
+ if(this->count(p_path) == 0) {
return std::nullopt;
}
// path is guaranteed to exist
- const auto value = at(path);
+ const auto value = at(p_path);
if(std::holds_alternative<int>(value)) {
if constexpr(std::is_arithmetic<T>::value) {
@@ -91,6 +81,8 @@ public:
} else if(std::holds_alternative<bool>(value)) {
if constexpr(std::is_constructible<T, bool>::value) {
return std::get<bool>(value);
+ } else if constexpr(std::is_arithmetic_v<T>) {
+ return static_cast<T>(std::get<bool>(value));
} else if constexpr(std::is_constructible<T, const char *>::value) {
return std::get<bool>(value) ? T{ "true" } : T{ "false" };
}
@@ -99,7 +91,9 @@ public:
auto str = std::get<std::string>(value);
try {
- if constexpr(std::is_floating_point<T>::value) {
+ if constexpr(std::is_same_v<T, bool>) {
+ return (str == "true");
+ } else if constexpr(std::is_floating_point<T>::value) {
return static_cast<T>(std::stod(str));
} else if constexpr(std::is_arithmetic<T>::value) {
return static_cast<T>(std::stol(str));
@@ -121,13 +115,16 @@ public:
}
template <typename T>
- callable_when(unconsumed) T &shortcut(T & /* unused */, const char * /* unused */) const
+ T &shortcut(T & /* unused */, const char * /* unused */) const
{
return T{};
}
static void move_global(std::unique_ptr<Configuration> && conf);
+protected:
+ void insert_or_assign(const kv_pair &pair);
+
private:
static Configuration *instance();
diff --git a/lib/configuration/qt_specialization.cpp b/lib/configuration/qt_specialization.cpp
index 8487e62..a97ed2b 100644
--- a/lib/configuration/qt_specialization.cpp
+++ b/lib/configuration/qt_specialization.cpp
@@ -1,36 +1,39 @@
#include "configuration.h"
-#include <stdexcept>
template <>
-callable_when(unconsumed) [[nodiscard]] std::optional<QString> Configuration::value(const char *path) const
+[[nodiscard]] std::optional<QString> Configuration::value(const char *path) const
{
- const auto v = value<std::string>(path);
- return v ? std::make_optional(QString::fromStdString(v.value())) : std::nullopt;
+ const auto result = value<std::string>(path);
+ return result ? std::make_optional(QString::fromStdString(result.value())) : std::nullopt;
}
template <>
-callable_when(unconsumed) [[nodiscard]] std::optional<QStringList> Configuration::value(const char *path) const
+[[nodiscard]] std::optional<QStringList> Configuration::value(const char *path) const
{
- const auto v = value<std::string>(path);
- return v ? std::make_optional(QString::fromStdString(v.value()).split(';')) : std::nullopt;
+ const auto result = value<std::string>(path);
+ return result ? std::make_optional(QString::fromStdString(result.value()).split(';')) : std::nullopt;
}
template <>
-callable_when(unconsumed) QAction &Configuration::shortcut(QAction &action, const char *name) const
+QAction &Configuration::shortcut(QAction &action, const char *name) const
{
- if(const auto shortcut = value<QString>(name)) {
+ if(const auto result = value<QString>(name)) {
const QString old_tooltip = action.toolTip();
- action.setShortcut(QKeySequence::fromString(shortcut.value()));
- action.setToolTip(QString("%1 (%2)").arg(old_tooltip, shortcut.value()));
+ const auto &result_value = result.value();
+
+ action.setShortcut(QKeySequence::fromString(result_value));
+ action.setToolTip(QString("%1 (%2)").arg(old_tooltip, result_value));
}
return action;
}
template <>
-callable_when(unconsumed) QKeySequence &Configuration::shortcut(QKeySequence &sequence, const char *name) const
+QKeySequence &Configuration::shortcut(QKeySequence &sequence, const char *name) const
{
- if(const auto shortcut = value<QString>(name)) {
- sequence = QKeySequence::fromString(shortcut.value());
+ if(const auto result = value<QString>(name)) {
+ const auto &result_value = result.value();
+
+ sequence = QKeySequence::fromString(result_value);
}
return sequence;
}
diff --git a/lib/configuration/qt_specialization.h b/lib/configuration/qt_specialization.h
index 9634261..af8466e 100644
--- a/lib/configuration/qt_specialization.h
+++ b/lib/configuration/qt_specialization.h
@@ -5,11 +5,11 @@
#include <QStringList>
template <>
-callable_when(unconsumed) [[nodiscard]] std::optional<QString> Configuration::value(const char *path) const;
+[[nodiscard]] std::optional<QString> Configuration::value(const char *path) const;
template <>
-callable_when(unconsumed) [[nodiscard]] std::optional<QStringList> Configuration::value(const char *path) const;
+[[nodiscard]] std::optional<QStringList> Configuration::value(const char *path) const;
template <>
-callable_when(unconsumed) QAction &Configuration::shortcut(QAction &, const char *) const;
+QAction &Configuration::shortcut(QAction &, const char *) const;
template <>
-callable_when(unconsumed) QKeySequence &Configuration::shortcut(QKeySequence &, const char *) const;
+QKeySequence &Configuration::shortcut(QKeySequence &, const char *) const;
diff --git a/lib/configuration/test/configuration.cpp b/lib/configuration/test/configuration.cpp
new file mode 100644
index 0000000..0796c11
--- /dev/null
+++ b/lib/configuration/test/configuration.cpp
@@ -0,0 +1,57 @@
+#include "configuration.h"
+#include <gtest/gtest.h>
+
+TEST(Configuration, ctor_empty_initializer_list)
+{
+ Configuration conf({});
+
+ std::cout << conf;
+}
+
+TEST(Configuration, ctor_initializer_list)
+{
+ Configuration conf({
+ { "some_string", "value1" },
+ { "some_bool", true },
+ { "some_int", 42 },
+ });
+
+ // operator<< should not throw exceptions
+ std::cout << conf;
+
+ EXPECT_EQ(*conf.value<std::string>("some_string"), "value1");
+ EXPECT_EQ(*conf.value<bool>("some_bool"), true);
+ EXPECT_EQ(*conf.value<int>("some_int"), 42);
+
+ EXPECT_FALSE(conf.value<int>("no_int"));
+}
+
+TEST(Configuration, read_file)
+{
+ // because the init_list is empty, all types are strings
+ Configuration conf({});
+
+ EXPECT_TRUE(conf.read_file("defaultrc.ini"));
+
+ std::cout << "---------\n";
+ std::cout << conf;
+ std::cout << "---------\n";
+
+ // defaultrc.ini contents
+ EXPECT_EQ(*conf.value<std::string>("name"), "Top level");
+ EXPECT_FALSE(conf.value<std::string>("comment"));
+
+ EXPECT_EQ(*conf.value<std::string>("main/name"), "Section Testing");
+
+ // casting a string to an integer
+ EXPECT_EQ(*conf.value<int>("number"), 12);
+
+ // casting a string to a boolean
+ EXPECT_EQ(*conf.value<std::string>("toggle"), "true");
+ EXPECT_EQ(*conf.value<bool>("toggle"), true);
+ EXPECT_EQ(*conf.value<bool>("main/toggle"), false);
+
+ // extrarc.ini contents
+ EXPECT_EQ(*conf.value<std::string>("over"), "extra");
+ EXPECT_EQ(*conf.value<std::string>("extra/name"), "extra section");
+}