diff --git a/Chess/ConfigProperty.hpp b/Chess/ConfigProperty.hpp index 8a552c4..e49b9f5 100644 --- a/Chess/ConfigProperty.hpp +++ b/Chess/ConfigProperty.hpp @@ -1,14 +1,35 @@ #pragma once +#include <cctype> + +inline int stob(const std::string& _Str) +{ + const char *_Ptr = _Str.c_str(); + + if (!_Ptr) + std::_Xinvalid_argument("invalid stob argument"); + + std::string low(_Str); + std::transform(low.begin(), low.end(), low.begin(), + [](unsigned char c) { return std::tolower(c); } + ); + + if (!low.compare("true")) return true; + if (!low.compare("false")) return false; + + std::_Xinvalid_argument("stob argument out of range"); +} + class ConfigProperty { private: - std::string m_name; - std::string m_comment; - bool m_isRead; protected: + std::string m_name; + std::string m_comment; + using string = const std::string&; virtual void fromString(string value) = 0; + virtual std::string getVerboseComment() = 0; public: using Ptr = std::shared_ptr<ConfigProperty>; @@ -20,6 +41,7 @@ class ConfigProperty { IsChanged(false), m_isRead(false) { } + virtual ~ConfigProperty() {} bool isRead() const { return m_isRead; @@ -29,13 +51,15 @@ class ConfigProperty { m_isRead = true; fromString(value); } + virtual std::string valueToString() = 0; ConfigProperty& setComment(string comment) { m_comment = comment; return *this; } - - string getComment() { + std::string getComment(bool isVerbose = true) { + if (isVerbose) + return m_comment + ' ' + getVerboseComment(); return m_comment; } @@ -45,11 +69,28 @@ class ConfigProperty { }; class StringProperty : public ConfigProperty { -private: std::string m_defaultValue; std::string m_value; + std::string getVerboseComment() override { + return "[default: " + m_defaultValue + ']'; + } + + void fromString(string value) override { + m_value = value; + } + + std::string valueToString() override { + return m_value; + } + public: + using Ptr = std::shared_ptr<StringProperty>; + + static Ptr create(string name, string defaultValue, string comment = "") { + return std::make_shared<StringProperty>(name, defaultValue, comment); + } + StringProperty(string name, string defaultValue, string comment = "") : m_defaultValue(defaultValue), m_value(defaultValue), @@ -63,18 +104,37 @@ class StringProperty : public ConfigProperty { IsChanged = true; m_value = value; } - - virtual void fromString(string value) override { - m_value = value; - } }; class BoolProperty : public ConfigProperty { -private: bool m_defaultValue; bool m_value; + std::string getVerboseComment() override { + std::string defVal = m_value ? "true" : "false"; + return "[default: " + defVal + ']'; + } + + void fromString(const std::string & value) override { + try { + m_value = stob(value); + } + catch (...) { + m_value = m_defaultValue; + } + } + + std::string valueToString() override { + return m_value ? "true" : "false"; + } + public: + using Ptr = std::shared_ptr<BoolProperty>; + + static Ptr create(string name, bool defaultValue, string comment = "") { + return std::make_shared<BoolProperty>(name, defaultValue, comment); + } + BoolProperty(string name, bool defaultValue, string comment = "") : m_defaultValue(defaultValue), m_value(defaultValue), @@ -88,33 +148,28 @@ class BoolProperty : public ConfigProperty { IsChanged = true; m_value = value; } - - virtual void fromString(const std::string & value) override { - try { - m_value = String::stob(value); - } - catch (const std::invalid_argument&) { - m_value = m_defaultValue; - } - } }; template<typename T, typename PropType> class NumericProperty : public ConfigProperty { -public: - T m_defaultValue; - T m_value; - T m_minValue; - T m_maxValue; + std::string getVerboseComment() override { + T defaultMinValue = std::numeric_limits<T>::min(); + T defaultMaxValue = std::numeric_limits<T>::max(); + std::stringstream ss; + ss << '['; + if (m_minValue != defaultMinValue && m_maxValue != defaultMaxValue) + ss << "range: " << m_minValue << " ~ " << m_maxValue << ", "; + else { + if (m_minValue != defaultMinValue) + ss << "min: " << m_minValue << ", "; + if (m_maxValue != defaultMaxValue) + ss << "max: " << m_maxValue << ", "; + } + ss << "default: " << m_defaultValue << ']'; + return ss.str(); + } -protected: - NumericProperty(string name, T defaultValue, T minValue, T maxValue, string comment = "") : - m_value(defaultValue), - m_defaultValue(defaultValue), - m_minValue(minValue), - m_maxValue(maxValue), - ConfigProperty(name, comment) - { + inline void innerCreate(T defaultValue, T minValue, T maxValue) { if (defaultValue < minValue) throw new std::invalid_argument("Defaultvalue is smaller than MinValue"); @@ -123,9 +178,36 @@ class NumericProperty : public ConfigProperty { if (minValue > maxValue) throw new std::invalid_argument("MinValue is larger than MaxValue"); + + m_defaultValue = defaultValue; + m_value = defaultValue; + m_minValue = minValue; + m_maxValue = maxValue; } +protected: + T m_defaultValue; + T m_value; + T m_minValue; + T m_maxValue; + public: + NumericProperty(string name, T defaultValue, string comment = "") : + ConfigProperty(name, comment) { + innerCreate(defaultValue, std::numeric_limits<T>::min(), std::numeric_limits<T>::max()); + } + NumericProperty(string name, T defaultValue, T minValue, T maxValue, string comment = "") : + ConfigProperty(name, comment) { + innerCreate(defaultValue, minValue, maxValue); + } + + static std::shared_ptr<PropType> create(string name, T defaultValue, string comment = "") { + return std::make_shared<PropType>(name, defaultValue, comment); + } + static std::shared_ptr<PropType> create(string name, T defaultValue, T minValue, T maxValue, string comment = "") { + return std::make_shared<PropType>(name, defaultValue, minValue, maxValue, comment); + } + PropType& setMinValue(T minValue) { m_minValue = minValue; return static_cast<PropType&>(*this); @@ -156,31 +238,80 @@ class NumericProperty : public ConfigProperty { }; class IntProperty : public NumericProperty<int, IntProperty> { + + void fromString(const std::string & value) override { + try { + setValue(std::stoi(value)); + } + catch (...) { + m_value = m_defaultValue; + } + } + + std::string valueToString() override { + return std::to_string(m_value); + } + public: - IntProperty(string name, int defaultValue, string comment = "") : - NumericProperty(name, defaultValue, INT_MIN, INT_MAX, comment) { } + using Ptr = std::shared_ptr<IntProperty>; + using NumericProperty::NumericProperty; +}; - IntProperty(string name, int defaultValue, int minValue, int maxValue, string comment = "") : - NumericProperty(name, defaultValue, minValue, maxValue, comment) { } +class LongProperty : public NumericProperty<long long, LongProperty> { + using Long = long long; - virtual void fromString(const std::string & value) override { + void fromString(const std::string & value) override { try { - setValue(std::stoi(value)); + setValue(std::stoll(value)); } - catch (const std::invalid_argument&) { + catch (...) { m_value = m_defaultValue; } } + + std::string valueToString() override { + return std::to_string(m_value); + } + +public: + using Ptr = std::shared_ptr<LongProperty>; + using NumericProperty::NumericProperty; }; -//class LongProperty : public ConfigProperty { -// -//}; -// -//class FloatProperty : public ConfigProperty { -// -//}; -// -//class DoubleProperty : public ConfigProperty { -// -//}; \ No newline at end of file +class FloatProperty : public NumericProperty<float, FloatProperty> { + void fromString(const std::string & value) override { + try { + setValue(std::stof(value)); + } + catch (...) { + m_value = m_defaultValue; + } + } + + std::string valueToString() override { + return std::to_string(m_value); + } + +public: + using Ptr = std::shared_ptr<FloatProperty>; + using NumericProperty::NumericProperty; +}; + +class DoubleProperty : public NumericProperty<double, DoubleProperty> { + void fromString(const std::string& value) override { + try { + setValue(std::stod(value)); + } + catch (...) { + m_value = m_defaultValue; + } + } + + std::string valueToString() override { + return std::to_string(m_value); + } + +public: + using Ptr = std::shared_ptr<DoubleProperty>; + using NumericProperty::NumericProperty; +}; \ No newline at end of file diff --git a/Chess/Configuration.cpp b/Chess/Configuration.cpp index a81575e..ad672dc 100644 --- a/Chess/Configuration.cpp +++ b/Chess/Configuration.cpp @@ -10,10 +10,21 @@ Configuration::~Configuration() void Configuration::save() { + lock_guard lock(m_fileLock); + try { m_file.open(m_filePath, std::fstream::out); + if (!m_file.is_open()) + std::cerr << "Unable to open stream for file: " << m_filePath << std::endl; + for (auto it : m_properties) { + if (IncludeComments) + m_file << commentTag << ' ' << it.second->getComment(VerboseComments) << std::endl; + m_file << it.second->getName() << delimiter << it.second->valueToString() << std::endl; + if (IncludeWhiteLine) + m_file << std::endl; + } } catch (const std::fstream::failure& e) { std::cerr << e.code().message() << " " << e.what() << std::endl; @@ -24,10 +35,90 @@ void Configuration::save() void Configuration::load() { + lock_guard lock(m_fileLock); + + try { + m_file.open(m_filePath, std::fstream::in); + if (!m_file.is_open()) + std::cerr << "Unable to open stream for file: " << m_filePath << std::endl; + + std::string nextLine; + while (std::getline(m_file, nextLine)) { + processLine(nextLine); + } + } + catch (const std::fstream::failure& e) { + std::cerr << e.code().message() << " " << e.what() << std::endl; + } + + m_file.close(); +} + +void Configuration::processLine(const std::string & nextLine) +{ + int nameStart = -1; + + for (int i = 0; i < nextLine.length(); i++) { + switch (nextLine[i]) { + case commentTag: //Ignore comment + { + if (nameStart < 1) + return; + break; + } + case delimiter: //Update registered property + { + std::string propName = nextLine.substr(nameStart, i - nameStart); + std::string valueStr = nextLine.substr(i + 1, nextLine.length() - i + 1); + + auto it = m_properties.find(propName); + if (it != m_properties.end()) + it->second->readProperty(valueStr); + + std::cout << propName << "=" << valueStr << std::endl; + i = nextLine.length(); + break; + } + case ' ': //Ignore whitespace + { + continue; + break; + } + default: { + if (nameStart == -1) + nameStart = i; + break; + } + } + } +} + +void Configuration::registerProperty(ConfigProperty::Ptr configProperty) { + lock_guard lock(m_fileLock); + m_properties.emplace(configProperty->getName(), configProperty); +} + +ConfigProperty::Ptr Configuration::get(const std::string& key) +{ + lock_guard lock(m_fileLock); + auto it = m_properties.find(key); + if (it == m_properties.end()) + return nullptr; + return it->second; +} +bool Configuration::remove(const std::string & key) +{ + lock_guard lock(m_fileLock); + auto it = m_properties.find(key); + if (it == m_properties.end()) + return false; + m_properties.erase(it); + return true; } void Configuration::clearProperties() { + lock_guard lock(m_fileLock); m_properties.clear(); } diff --git a/Chess/Configuration.h b/Chess/Configuration.h index 2b7eec1..fe3ff0a 100644 --- a/Chess/Configuration.h +++ b/Chess/Configuration.h @@ -6,14 +6,23 @@ class Configuration { private: + static const char delimiter = '='; + static const char commentTag = '#'; + std::map<std::string, ConfigProperty::Ptr> m_properties; std::fstream m_file; std::string m_filePath; std::mutex m_fileLock; + using lock_guard = const std::lock_guard<std::mutex>; + void processLine(const std::string& nextLine); public: + bool IncludeComments = true; + bool IncludeWhiteLine = true; + bool VerboseComments = true; + Configuration(const std::string& path); virtual ~Configuration(); @@ -22,10 +31,20 @@ class Configuration void registerProperty(ConfigProperty::Ptr configProperty); ConfigProperty::Ptr get(const std::string& key); + bool remove(const std::string& key); void clearProperties(); template<typename T> - std::shared_ptr<T> get(const std::string& key); + std::shared_ptr<T> getType(const std::string& key) + { + auto it = m_properties.find(key); + if (it == m_properties.end()) + throw new std::exception(("Property with key " + key + " does not exist!").c_str()); + + auto baseProp = std::dynamic_pointer_cast<T>(it->second); + return baseProp; + } }; +