diff --git a/minipp/minipp.hpp b/minipp/minipp.hpp index edc70b4..e522c68 100644 --- a/minipp/minipp.hpp +++ b/minipp/minipp.hpp @@ -5,17 +5,17 @@ // #define MINIPP_IMPLEMENTATION in a single translation unit before including this header! /* - Copyright (c) 2025 mariiaan - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files - (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. + Copyright (c) 2025 mariiaan + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. */ // enabled helpful debug messages via std::cout (for parsing and writing) @@ -30,22 +30,22 @@ namespace minipp { - enum class EResult - { - /* Errors */ - KeyNotPresent = -1, - KeyAlreadyPresent = -2, - SectionNotPresent = -3, - SectionAlreadyPresent = -4, - FileIOError = -5, - InvalidDataType = -6, - FormatError = -7, + enum class EResult + { + /* Errors */ + KeyNotPresent = -1, + KeyAlreadyPresent = -2, + SectionNotPresent = -3, + SectionAlreadyPresent = -4, + FileIOError = -5, + InvalidDataType = -6, + FormatError = -7, ArrayDataTypeInconsistency = -8, - /* OK Codes */ - Success = +1, - ValueOverwritten = +2 - }; + /* OK Codes */ + Success = +1, + ValueOverwritten = +2 + }; enum class EIntStyle { @@ -54,128 +54,128 @@ namespace minipp Binary }; - class MiniPPFile - { - public: - class Value - { - friend class MiniPPFile; - - protected: - std::vector m_comments; - - public: - virtual EResult Parse(const std::string& str) noexcept = 0; - virtual EResult ToString(std::string& destination) const noexcept = 0; - virtual ~Value() = default; - std::vector& GetComments() noexcept { return m_comments; } - const std::vector& GetComments() const noexcept { return m_comments; } - }; - - class Values - { - public: - class StringValue : public Value - { - private: - std::string m_value; - - public: - StringValue() = default; - StringValue(const std::string& str) : m_value(str) {}; - EResult Parse(const std::string& str) noexcept override; - EResult ToString(std::string& destination) const noexcept override; - const std::string& GetValue() const noexcept { return m_value; } - }; - - class IntValue : public Value - { - private: - int64_t m_value = 0; - EIntStyle m_style = EIntStyle::Decimal; - - public: - IntValue() = default; - IntValue(int64_t value) : m_value(value) {}; - EResult Parse(const std::string& str) noexcept override; - EResult ToString(std::string& destination) const noexcept override; - int64_t GetValue() const noexcept { return m_value; } - }; - - class BooleanValue : public Value - { - private: - bool m_value = false; - - public: - BooleanValue() = default; - BooleanValue(bool value) : m_value(value) {}; - EResult Parse(const std::string& str) noexcept override; - EResult ToString(std::string& destination) const noexcept override; - bool GetValue() const noexcept { return m_value; } - }; - - class FloatValue : public Value - { - private: - double m_value = 0.0f; - - public: - FloatValue() = default; - FloatValue(double value) : m_value(value) {}; - EResult Parse(const std::string& str) noexcept override; - EResult ToString(std::string& destination) const noexcept override; - double GetValue() const noexcept { return m_value; } - }; - - class ArrayValue : public Value - { - private: - std::vector> m_values; - - public: - ArrayValue() = default; - ArrayValue(const ArrayValue&) = delete; - ArrayValue& operator=(const ArrayValue&) = delete; - - public: - EResult Parse(const std::string& str) noexcept override; - EResult ToString(std::string& destination) const noexcept override; - std::vector>& GetValues() noexcept { return m_values; } - const std::vector>& GetValues() const noexcept { return m_values; } - - Value* operator[](size_t index) noexcept - { - if (index >= m_values.size()) - return nullptr; - return m_values[index].get(); - } - }; - }; - - class Section - { + class MiniPPFile + { + public: + class Value + { friend class MiniPPFile; - private: - std::unordered_map m_values; - std::unordered_map m_subSections; - std::vector m_comments; + protected: + std::vector m_comments; - public: + public: + virtual EResult Parse(const std::string& str) noexcept = 0; + virtual EResult ToString(std::string& destination) const noexcept = 0; + virtual ~Value() = default; + std::vector& GetComments() noexcept { return m_comments; } + const std::vector& GetComments() const noexcept { return m_comments; } + }; + + class Values + { + public: + class StringValue : public Value + { + private: + std::string m_value; + + public: + StringValue() = default; + StringValue(const std::string& str) : m_value(str) {}; + EResult Parse(const std::string& str) noexcept override; + EResult ToString(std::string& destination) const noexcept override; + const std::string& GetValue() const noexcept { return m_value; } + }; + + class IntValue : public Value + { + private: + int64_t m_value = 0; + EIntStyle m_style = EIntStyle::Decimal; + + public: + IntValue() = default; + IntValue(int64_t value) : m_value(value) {}; + EResult Parse(const std::string& str) noexcept override; + EResult ToString(std::string& destination) const noexcept override; + int64_t GetValue() const noexcept { return m_value; } + }; + + class BooleanValue : public Value + { + private: + bool m_value = false; + + public: + BooleanValue() = default; + BooleanValue(bool value) : m_value(value) {}; + EResult Parse(const std::string& str) noexcept override; + EResult ToString(std::string& destination) const noexcept override; + bool GetValue() const noexcept { return m_value; } + }; + + class FloatValue : public Value + { + private: + double m_value = 0.0f; + + public: + FloatValue() = default; + FloatValue(double value) : m_value(value) {}; + EResult Parse(const std::string& str) noexcept override; + EResult ToString(std::string& destination) const noexcept override; + double GetValue() const noexcept { return m_value; } + }; + + class ArrayValue : public Value + { + private: + std::vector> m_values; + + public: + ArrayValue() = default; + ArrayValue(const ArrayValue&) = delete; + ArrayValue& operator=(const ArrayValue&) = delete; + + public: + EResult Parse(const std::string& str) noexcept override; + EResult ToString(std::string& destination) const noexcept override; + std::vector>& GetValues() noexcept { return m_values; } + const std::vector>& GetValues() const noexcept { return m_values; } + + Value* operator[](size_t index) noexcept + { + if (index >= m_values.size()) + return nullptr; + return m_values[index].get(); + } + }; + }; + + class Section + { + friend class MiniPPFile; + + private: + std::unordered_map m_values; + std::unordered_map m_subSections; + std::vector m_comments; + + public: std::vector& GetComments() noexcept { return m_comments; } const std::vector& GetComments() const noexcept { return m_comments; } - public: - Section() = default; - ~Section(); - Section(const Section&) = delete; + public: + Section() = default; + ~Section(); + Section(const Section&) = delete; Section& operator=(const Section&) = delete; - public: - template - EResult GetValue(const std::string& key, ValueDataType** target) noexcept - { + public: + template + EResult GetValue(const std::string& key, ValueDataType** target) noexcept + { static_assert(std::is_base_of::value, "ValueDataType must be a subclass of Value"); int64_t firstSeparatorIndex = Tools::FirstIndexOf(key, '.'); if (firstSeparatorIndex != -1) @@ -187,74 +187,74 @@ namespace minipp return EResult::SectionNotPresent; return it->second->GetValue(rest, target); } - auto it = m_values.find(key); - if (it == m_values.end()) - return EResult::KeyNotPresent; + auto it = m_values.find(key); + if (it == m_values.end()) + return EResult::KeyNotPresent; - auto val = dynamic_cast(it->second); - if (val == nullptr) - return EResult::InvalidDataType; + auto val = dynamic_cast(it->second); + if (val == nullptr) + return EResult::InvalidDataType; - *target = val; - return EResult::Success; - } + *target = val; + return EResult::Success; + } - template - EResult SetValue(const std::string& name, std::unique_ptr value, bool allowOverwrite = false) noexcept - { - static_assert(std::is_base_of::value, "ValueDataType must be a subclass of Value"); - if (m_values.find(name) != m_values.end()) - if (!allowOverwrite) - return EResult::KeyAlreadyPresent; - else - delete m_values[name]; + template + EResult SetValue(const std::string& name, std::unique_ptr value, bool allowOverwrite = false) noexcept + { + static_assert(std::is_base_of::value, "ValueDataType must be a subclass of Value"); + if (m_values.find(name) != m_values.end()) + if (!allowOverwrite) + return EResult::KeyAlreadyPresent; + else + delete m_values[name]; - m_values[name] = value.release(); - return EResult::Success; - } + m_values[name] = value.release(); + return EResult::Success; + } - public: - EResult GetSubSection(const std::string& key, Section** destination) const noexcept; - EResult SetSubSection(const std::string& name, std::unique_ptr
value, bool allowOverwrite = false) noexcept; - }; + public: + EResult GetSubSection(const std::string& key, Section** destination) const noexcept; + EResult SetSubSection(const std::string& name, std::unique_ptr
value, bool allowOverwrite = false) noexcept; + }; - public: - MiniPPFile() = default; + public: + MiniPPFile() = default; - private: - Section m_rootSection{}; + private: + Section m_rootSection{}; - private: - static std::unique_ptr ParseValue(std::string value); - static minipp::EResult WriteSection(const Section* section, std::ofstream& ofs, std::string partTreeName) noexcept; + private: + static std::unique_ptr ParseValue(std::string value); + static minipp::EResult WriteSection(const Section* section, std::ofstream& ofs, std::string partTreeName) noexcept; - public: - EResult Parse(const std::string& path) noexcept; - EResult Write(const std::string& path) const noexcept; + public: + EResult Parse(const std::string& path) noexcept; + EResult Write(const std::string& path) const noexcept; - public: - const Section& GetRoot() const noexcept { return m_rootSection; } - Section& GetRoot() noexcept { return m_rootSection; } + public: + const Section& GetRoot() const noexcept { return m_rootSection; } + Section& GetRoot() noexcept { return m_rootSection; } - public: + public: static bool IsResultOk(EResult result) noexcept; - private: - class Tools - { - public: - static bool StringStartsWith(const std::string& str, const std::string& beg); - static bool StringEndsWith(const std::string& str, const std::string& end); - static void StringTrim(std::string& str); - static bool IsNameValid(const std::string& name) noexcept; - static int64_t FirstIndexOf(const std::string& str, char c) noexcept; - static int64_t LastIndexOf(const std::string& str, char c) noexcept; - static std::pair SplitInTwo(const std::string& str, int64_t firstLength) noexcept; - static std::vector SplitByDelimiter(const std::string& str, char delimiter) noexcept; - static void RemoveAll(std::string& str, char old); - static bool IsIntegerDecimal(const std::string& str) noexcept; - }; - }; + private: + class Tools + { + public: + static bool StringStartsWith(const std::string& str, const std::string& beg); + static bool StringEndsWith(const std::string& str, const std::string& end); + static void StringTrim(std::string& str); + static bool IsNameValid(const std::string& name) noexcept; + static int64_t FirstIndexOf(const std::string& str, char c) noexcept; + static int64_t LastIndexOf(const std::string& str, char c) noexcept; + static std::pair SplitInTwo(const std::string& str, int64_t firstLength) noexcept; + static std::vector SplitByDelimiter(const std::string& str, char delimiter) noexcept; + static void RemoveAll(std::string& str, char old); + static bool IsIntegerDecimal(const std::string& str) noexcept; + }; + }; } #ifdef MINIPP_IMPLEMENTATION @@ -267,9 +267,9 @@ namespace minipp #if MINIPP_ENABLE_DEBUG_OUTPUT #include - #define COUT(msg) std::cout << "[minipp] " << msg << std::endl + #define COUT(msg) std::cout << "[minipp] " << msg << std::endl #else - #define COUT(msg) + #define COUT(msg) #endif #define ASSERT(condition) if (!condition) abort(); @@ -279,117 +279,117 @@ namespace minipp minipp::EResult minipp::MiniPPFile::Values::StringValue::Parse(const std::string& str) noexcept { - m_value = ""; - for (size_t i = 0; i < str.size(); ++i) - { - if (str[i] == '\\') - { - if (i + 1 >= str.size()) - { + m_value = ""; + for (size_t i = 0; i < str.size(); ++i) + { + if (str[i] == '\\') + { + if (i + 1 >= str.size()) + { COUT("Syntax error: '\\' at end of string"); - return EResult::FormatError; - } + return EResult::FormatError; + } - switch (str[i + 1]) - { - case '\"': - m_value.push_back('\"'); - break; - case 'n': - m_value.push_back('\n'); - break; - case 't': - m_value.push_back('\t'); - break; - case 'r': - m_value.push_back('\r'); - break; - case '\\': - m_value.push_back('\\'); - break; - default: + switch (str[i + 1]) + { + case '\"': + m_value.push_back('\"'); + break; + case 'n': + m_value.push_back('\n'); + break; + case 't': + m_value.push_back('\t'); + break; + case 'r': + m_value.push_back('\r'); + break; + case '\\': + m_value.push_back('\\'); + break; + default: COUT("Syntax error: Unknown escape sequence '\\" << str[i + 1] << "'"); - return EResult::FormatError; - } - ++i; - } - else if (str[i] == '"') - return EResult::FormatError; - else - m_value.push_back(str[i]); - } - return EResult::Success; + return EResult::FormatError; + } + ++i; + } + else if (str[i] == '"') + return EResult::FormatError; + else + m_value.push_back(str[i]); + } + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Values::StringValue::ToString(std::string& destination) const noexcept { - std::string sanitizedValue = m_value; - for (size_t i = 0; i < sanitizedValue.size(); ++i) - { - switch (sanitizedValue[i]) - { - case '\n': - sanitizedValue.replace(i, 1, "\\n"); - i++; - break; - case '\t': - sanitizedValue.replace(i, 1, "\\t"); - i++; - break; - case '\r': - sanitizedValue.replace(i, 1, "\\r"); - i++; - break; - case '\\': - sanitizedValue.insert(i, 1, '\\'); - i++; - break; - case '\"': - sanitizedValue.insert(i, 1, '\\'); - i++; - break; - } - } + std::string sanitizedValue = m_value; + for (size_t i = 0; i < sanitizedValue.size(); ++i) + { + switch (sanitizedValue[i]) + { + case '\n': + sanitizedValue.replace(i, 1, "\\n"); + i++; + break; + case '\t': + sanitizedValue.replace(i, 1, "\\t"); + i++; + break; + case '\r': + sanitizedValue.replace(i, 1, "\\r"); + i++; + break; + case '\\': + sanitizedValue.insert(i, 1, '\\'); + i++; + break; + case '\"': + sanitizedValue.insert(i, 1, '\\'); + i++; + break; + } + } - destination = "\"" + sanitizedValue + "\""; - return EResult::Success; + destination = "\"" + sanitizedValue + "\""; + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Values::IntValue::Parse(const std::string& str) noexcept { - std::string sanitizedValue = str; - Tools::RemoveAll(sanitizedValue, '_'); - if (sanitizedValue.empty()) - { + std::string sanitizedValue = str; + Tools::RemoveAll(sanitizedValue, '_'); + if (sanitizedValue.empty()) + { COUT("Empty string for integer value."); - return EResult::FormatError; - } + return EResult::FormatError; + } - char lastCharacter = str.back(); - auto rest = str.substr(0, str.size() - 1); - try - { - if (lastCharacter == 'h') - { - m_value = std::stoll(rest, nullptr, 16); - m_style = EIntStyle::Hexadecimal; - } - else if (lastCharacter == 'b') - { - m_value = std::stoll(rest, nullptr, 2); - m_style = EIntStyle::Binary; - } - else - { - if (!Tools::IsIntegerDecimal(sanitizedValue)) - { - COUT("Invalid decimal integer value: " << sanitizedValue); - return EResult::FormatError; - } - m_value = std::stoll(sanitizedValue); - m_style = EIntStyle::Decimal; - } - } + char lastCharacter = str.back(); + auto rest = str.substr(0, str.size() - 1); + try + { + if (lastCharacter == 'h') + { + m_value = std::stoll(rest, nullptr, 16); + m_style = EIntStyle::Hexadecimal; + } + else if (lastCharacter == 'b') + { + m_value = std::stoll(rest, nullptr, 2); + m_style = EIntStyle::Binary; + } + else + { + if (!Tools::IsIntegerDecimal(sanitizedValue)) + { + COUT("Invalid decimal integer value: " << sanitizedValue); + return EResult::FormatError; + } + m_value = std::stoll(sanitizedValue); + m_style = EIntStyle::Decimal; + } + } catch (const std::invalid_argument&) { COUT("Invalid integer value: " << sanitizedValue); @@ -401,30 +401,30 @@ minipp::EResult minipp::MiniPPFile::Values::IntValue::Parse(const std::string& s return EResult::FormatError; } - return EResult::Success; + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Values::IntValue::ToString(std::string& destination) const noexcept { - switch (m_style) - { - case EIntStyle::Decimal: - { - destination = std::to_string(m_value); - break; - } - case EIntStyle::Hexadecimal: - { - std::stringstream ss; + switch (m_style) + { + case EIntStyle::Decimal: + { + destination = std::to_string(m_value); + break; + } + case EIntStyle::Hexadecimal: + { + std::stringstream ss; ss << std::hex << m_value << "h"; - destination = ss.str(); - break; - } + destination = ss.str(); + break; + } case EIntStyle::Binary: { std::bitset<64> bs(m_value); - destination = bs.to_string() + "b"; - size_t i; + destination = bs.to_string() + "b"; + size_t i; for (i = 0; i < destination.size(); ++i) // cut of leading zeros if (destination[i] == '1') { @@ -434,566 +434,566 @@ minipp::EResult minipp::MiniPPFile::Values::IntValue::ToString(std::string& dest if (i == destination.size()) // we dont wan't 64 zeros for a 0 value destination = "0b"; break; - } - default: + } + default: COUT("Invalid integer style."); return EResult::FormatError; } - return EResult::Success; + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Values::BooleanValue::Parse(const std::string& str) noexcept { - if (str == "true") - m_value = true; - else if (str == "false") - m_value = false; - else - { + if (str == "true") + m_value = true; + else if (str == "false") + m_value = false; + else + { COUT("Invalid boolean value: " << str << " (may only contain lowercase true and false)"); - return EResult::FormatError; - } - return EResult::Success; + return EResult::FormatError; + } + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Values::BooleanValue::ToString(std::string& destination) const noexcept { - destination = m_value ? "true" : "false"; - return EResult::Success; + destination = m_value ? "true" : "false"; + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Values::FloatValue::Parse(const std::string& str) noexcept { - m_value = std::stod(str); - return EResult::Success; + m_value = std::stod(str); + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Values::FloatValue::ToString(std::string& destination) const noexcept { - destination = std::to_string(m_value); - return EResult::Success; + destination = std::to_string(m_value); + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Values::ArrayValue::Parse(const std::string& str) noexcept { - std::string nValue = str; - if (str.front() != '[' || str.back() != ']') - { + std::string nValue = str; + if (str.front() != '[' || str.back() != ']') + { COUT("Array value must be enclosed in [] brackets."); - return EResult::FormatError; - } - nValue = str.substr(1, str.size() - 2); - if (nValue.empty()) - return EResult::Success; + return EResult::FormatError; + } + nValue = str.substr(1, str.size() - 2); + if (nValue.empty()) + return EResult::Success; - int64_t bracketCounter = 0; + int64_t bracketCounter = 0; bool isInString = false; // we may encounter array value separators "," inside strings; we need to ignore those - std::vector elements; - std::string currentElement; + std::vector elements; + std::string currentElement; - for (size_t i = 0; i < str.size(); ++i) - { - char c = str[i]; + for (size_t i = 0; i < str.size(); ++i) + { + char c = str[i]; - if (isInString) - { - if (c == '\\') - ++i; - else if (c == '"') - { - isInString = false; - currentElement += c; - } - else - currentElement += c; - } - else if (c == '\\') - ++i; - else if (c == '"') - { - currentElement += c; - isInString = true; - } - else - { - if (c == '[') - { - if (++bracketCounter > 1) - currentElement += c; - } - else if (c == ']') - { - --bracketCounter; - if (bracketCounter < 0) - { + if (isInString) + { + if (c == '\\') + ++i; + else if (c == '"') + { + isInString = false; + currentElement += c; + } + else + currentElement += c; + } + else if (c == '\\') + ++i; + else if (c == '"') + { + currentElement += c; + isInString = true; + } + else + { + if (c == '[') + { + if (++bracketCounter > 1) + currentElement += c; + } + else if (c == ']') + { + --bracketCounter; + if (bracketCounter < 0) + { COUT("Array brackets are not balanced. (One ] too much or encountered too early)"); - return EResult::FormatError; - } - else if (bracketCounter >= 1) - currentElement += c; - } - else if (c == ',' && bracketCounter == 1) - { - elements.push_back(currentElement); - currentElement = ""; - } - else if (c != ' ' && c != '\t') - currentElement += c; - } - } + return EResult::FormatError; + } + else if (bracketCounter >= 1) + currentElement += c; + } + else if (c == ',' && bracketCounter == 1) + { + elements.push_back(currentElement); + currentElement = ""; + } + else if (c != ' ' && c != '\t') + currentElement += c; + } + } if (bracketCounter != 0) // will always be a positive value because negative values are caught earlier - { + { COUT("Array brackets are not balanced. (Missing " << bracketCounter << " closing brackets)"); - return EResult::FormatError; - } + return EResult::FormatError; + } - if (!currentElement.empty()) - elements.push_back(currentElement); - size_t lastTypeIdHash = 0; - bool hasTypeHash = false; + if (!currentElement.empty()) + elements.push_back(currentElement); + size_t lastTypeIdHash = 0; + bool hasTypeHash = false; - for (auto& elem : elements) - { - auto parsed = ParseValue(elem); - if (parsed == nullptr) - return EResult::FormatError; + for (auto& elem : elements) + { + auto parsed = ParseValue(elem); + if (parsed == nullptr) + return EResult::FormatError; - if (!hasTypeHash) - { - lastTypeIdHash = typeid(*parsed).hash_code(); - hasTypeHash = true; - } - else if (typeid(*parsed).hash_code() != lastTypeIdHash) - return EResult::ArrayDataTypeInconsistency; + if (!hasTypeHash) + { + lastTypeIdHash = typeid(*parsed).hash_code(); + hasTypeHash = true; + } + else if (typeid(*parsed).hash_code() != lastTypeIdHash) + return EResult::ArrayDataTypeInconsistency; - m_values.push_back(std::move(parsed)); - } - return EResult::Success; + m_values.push_back(std::move(parsed)); + } + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Values::ArrayValue::ToString(std::string& destination) const noexcept { - std::string buf; - size_t lastTypeIdHash = 0; - bool hasTypeHash = false; + std::string buf; + size_t lastTypeIdHash = 0; + bool hasTypeHash = false; - std::stringstream ss; - for (const auto& val : m_values) - { - EResult result = val->ToString(buf); - if (!IsResultOk(result)) - return result; + std::stringstream ss; + for (const auto& val : m_values) + { + EResult result = val->ToString(buf); + if (!IsResultOk(result)) + return result; - if (!hasTypeHash) - { - lastTypeIdHash = typeid(*val).hash_code(); - hasTypeHash = true; - } - else if (typeid(*val).hash_code() != lastTypeIdHash) - return EResult::ArrayDataTypeInconsistency; + if (!hasTypeHash) + { + lastTypeIdHash = typeid(*val).hash_code(); + hasTypeHash = true; + } + else if (typeid(*val).hash_code() != lastTypeIdHash) + return EResult::ArrayDataTypeInconsistency; - ss << buf << ", "; - } - std::string valueString = ss.str(); + ss << buf << ", "; + } + std::string valueString = ss.str(); if (!valueString.empty()) // remove the last ", " if there are any elements - valueString = valueString.substr(0, valueString.size() - 2); + valueString = valueString.substr(0, valueString.size() - 2); - destination = "[" + valueString + "]"; - return EResult::Success; + destination = "[" + valueString + "]"; + return EResult::Success; } #pragma endregion minipp::MiniPPFile::Section::~Section() { - for (auto& pair : m_values) - delete pair.second; - for (auto& pair : m_subSections) - delete pair.second; + for (auto& pair : m_values) + delete pair.second; + for (auto& pair : m_subSections) + delete pair.second; } minipp::EResult minipp::MiniPPFile::Section::GetSubSection(const std::string& key, Section** destination) const noexcept { - auto pathIndex = key.find('.'); - std::string thisKey = key; - std::string rest; - if (pathIndex != std::string::npos) - { - thisKey = key.substr(0, pathIndex); - rest = key.substr(pathIndex + 1); - } + auto pathIndex = key.find('.'); + std::string thisKey = key; + std::string rest; + if (pathIndex != std::string::npos) + { + thisKey = key.substr(0, pathIndex); + rest = key.substr(pathIndex + 1); + } - auto it = m_subSections.find(thisKey); - if (it == m_subSections.end()) - { - COUT("Sub-Section not found: " << thisKey); - return EResult::SectionNotPresent; - } - if (rest.empty()) - { - *destination = it->second; - return EResult::Success; - } + auto it = m_subSections.find(thisKey); + if (it == m_subSections.end()) + { + COUT("Sub-Section not found: " << thisKey); + return EResult::SectionNotPresent; + } + if (rest.empty()) + { + *destination = it->second; + return EResult::Success; + } - return it->second->GetSubSection(rest, destination); + return it->second->GetSubSection(rest, destination); } minipp::EResult minipp::MiniPPFile::Section::SetSubSection(const std::string& name, std::unique_ptr
value, bool allowOverwrite) noexcept { - if (m_subSections.find(name) != m_subSections.end()) - if (!allowOverwrite) - return EResult::SectionAlreadyPresent; - else - delete m_subSections[name]; + if (m_subSections.find(name) != m_subSections.end()) + if (!allowOverwrite) + return EResult::SectionAlreadyPresent; + else + delete m_subSections[name]; - m_subSections[name] = value.release(); - return EResult::Success; + m_subSections[name] = value.release(); + return EResult::Success; } std::unique_ptr minipp::MiniPPFile::ParseValue(std::string value) { - char valueFirstChar = value.front(); - char valueLastChar = value.back(); - if (valueFirstChar == '"') - { - if (valueLastChar != '"') - return nullptr; + char valueFirstChar = value.front(); + char valueLastChar = value.back(); + if (valueFirstChar == '"') + { + if (valueLastChar != '"') + return nullptr; - // is string - value = value.substr(1, value.size() - 2); - auto strValue = std::make_unique(); - auto parseResult = strValue->Parse(value); - if (!IsResultOk(parseResult)) - return nullptr; + // is string + value = value.substr(1, value.size() - 2); + auto strValue = std::make_unique(); + auto parseResult = strValue->Parse(value); + if (!IsResultOk(parseResult)) + return nullptr; - return strValue; - } - else if (valueLastChar == 'e') - { - auto boolValue = std::make_unique(); - auto parseResult = boolValue->Parse(value); - if (!IsResultOk(parseResult)) - return nullptr; + return strValue; + } + else if (valueLastChar == 'e') + { + auto boolValue = std::make_unique(); + auto parseResult = boolValue->Parse(value); + if (!IsResultOk(parseResult)) + return nullptr; - return boolValue; - } - else if (valueLastChar == 'f') - { - auto floatValue = std::make_unique(); - auto parseResult = floatValue->Parse(value); - if (!IsResultOk(parseResult)) - return nullptr; + return boolValue; + } + else if (valueLastChar == 'f') + { + auto floatValue = std::make_unique(); + auto parseResult = floatValue->Parse(value); + if (!IsResultOk(parseResult)) + return nullptr; - return floatValue; - } - else if (valueLastChar == ']') - { - auto arrayValue = std::make_unique(); - auto parseResult = arrayValue->Parse(value); - if (!IsResultOk(parseResult)) - return nullptr; + return floatValue; + } + else if (valueLastChar == ']') + { + auto arrayValue = std::make_unique(); + auto parseResult = arrayValue->Parse(value); + if (!IsResultOk(parseResult)) + return nullptr; - return arrayValue; - } - else - { - auto intValue = std::make_unique(); - auto parseResult = intValue->Parse(value); - if (!IsResultOk(parseResult)) - return nullptr; + return arrayValue; + } + else + { + auto intValue = std::make_unique(); + auto parseResult = intValue->Parse(value); + if (!IsResultOk(parseResult)) + return nullptr; - return intValue; - } + return intValue; + } } minipp::EResult minipp::MiniPPFile::WriteSection(const Section* section, std::ofstream& ofs, std::string partTreeName) noexcept { - if (section->m_values.size() > 0) - { - std::string valueString; - for (const auto& pair : section->m_values) - { - if (!Tools::IsNameValid(pair.first)) - { - COUT("Invalid name for key: " << pair.first); - return EResult::FormatError; - } + if (section->m_values.size() > 0) + { + std::string valueString; + for (const auto& pair : section->m_values) + { + if (!Tools::IsNameValid(pair.first)) + { + COUT("Invalid name for key: " << pair.first); + return EResult::FormatError; + } for (const auto& comment : pair.second->m_comments) ofs << comment << std::endl; - ofs << pair.first << " = "; - auto result = pair.second->ToString(valueString); + ofs << pair.first << " = "; + auto result = pair.second->ToString(valueString); if (!IsResultOk(result)) return result; - ofs << valueString << std::endl; - } - ofs << std::endl; - } - if (!partTreeName.empty()) + ofs << valueString << std::endl; + } + ofs << std::endl; + } + if (!partTreeName.empty()) partTreeName += "."; - for (const auto& pair : section->m_subSections) - { - if (!Tools::IsNameValid(pair.first)) - { - COUT("Invalid name for section: " << pair.first); - return EResult::FormatError; - } + for (const auto& pair : section->m_subSections) + { + if (!Tools::IsNameValid(pair.first)) + { + COUT("Invalid name for section: " << pair.first); + return EResult::FormatError; + } for (const auto& comment : pair.second->m_comments) ofs << comment << std::endl; - ofs << "[" << partTreeName << pair.first << "]" << std::endl; - auto result = WriteSection(pair.second, ofs, partTreeName + pair.first); - if (!IsResultOk(result)) - return result; - } + ofs << "[" << partTreeName << pair.first << "]" << std::endl; + auto result = WriteSection(pair.second, ofs, partTreeName + pair.first); + if (!IsResultOk(result)) + return result; + } - return EResult::Success; + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Parse(const std::string& path) noexcept { - std::ifstream ifs; - ifs.open(path); - if (!ifs.is_open()) - return EResult::FileIOError; + std::ifstream ifs; + ifs.open(path); + if (!ifs.is_open()) + return EResult::FileIOError; - int64_t lineCounter = 0; - Section* currentSection = nullptr; + int64_t lineCounter = 0; + Section* currentSection = nullptr; - std::vector commentBuffer; + std::vector commentBuffer; - std::string currentLine; - while (std::getline(ifs, currentLine)) - { - ++lineCounter; - Tools::StringTrim(currentLine); - if (currentLine.empty()) - continue; - char firstChar = currentLine[0]; - char lastChar = currentLine[currentLine.size() - 1]; + std::string currentLine; + while (std::getline(ifs, currentLine)) + { + ++lineCounter; + Tools::StringTrim(currentLine); + if (currentLine.empty()) + continue; + char firstChar = currentLine[0]; + char lastChar = currentLine[currentLine.size() - 1]; - if (firstChar == '#') - { + if (firstChar == '#') + { commentBuffer.push_back(currentLine); - continue; - } + continue; + } - if (firstChar == '[') - { - if (lastChar != ']') - { - COUT_SYNTAX_ERROR(lineCounter, "Expected ']' at the end of the line."); - return EResult::FormatError; - } - std::string sectionName = currentLine.substr(1, currentLine.size() - 2); - Tools::StringTrim(sectionName); - if (sectionName.empty()) - { - COUT_SYNTAX_ERROR(lineCounter, "Expected section path. Found empty section begin notation."); - return EResult::FormatError; - } - // Create section tree - Section* ubSection = &m_rootSection; + if (firstChar == '[') + { + if (lastChar != ']') + { + COUT_SYNTAX_ERROR(lineCounter, "Expected ']' at the end of the line."); + return EResult::FormatError; + } + std::string sectionName = currentLine.substr(1, currentLine.size() - 2); + Tools::StringTrim(sectionName); + if (sectionName.empty()) + { + COUT_SYNTAX_ERROR(lineCounter, "Expected section path. Found empty section begin notation."); + return EResult::FormatError; + } + // Create section tree + Section* ubSection = &m_rootSection; - std::vector sectionPath = Tools::SplitByDelimiter(sectionName, '.'); - for (size_t i = 0; i < sectionPath.size(); ++i) - { - const std::string& sectionName = sectionPath[i]; - if (!Tools::IsNameValid(sectionName)) - { - COUT_SYNTAX_ERROR(lineCounter, "Invalid section name. (\"" << sectionName << "\") May only contain [a - z][A - Z][0 - 9] and _."); - return EResult::FormatError; - } + std::vector sectionPath = Tools::SplitByDelimiter(sectionName, '.'); + for (size_t i = 0; i < sectionPath.size(); ++i) + { + const std::string& sectionName = sectionPath[i]; + if (!Tools::IsNameValid(sectionName)) + { + COUT_SYNTAX_ERROR(lineCounter, "Invalid section name. (\"" << sectionName << "\") May only contain [a - z][A - Z][0 - 9] and _."); + return EResult::FormatError; + } - EResult result; - result = ubSection->SetSubSection(sectionName, std::make_unique
(), false); - if (result == EResult::SectionAlreadyPresent && i == sectionPath.size() - 1) - { - COUT_SYNTAX_ERROR(lineCounter, "All (sub-) sections may only be defined once."); - return EResult::FormatError; - } - result = ubSection->GetSubSection(sectionName, &ubSection); - ASSERT(IsResultOk(result)); // should never happen - } - currentSection = ubSection; + EResult result; + result = ubSection->SetSubSection(sectionName, std::make_unique
(), false); + if (result == EResult::SectionAlreadyPresent && i == sectionPath.size() - 1) + { + COUT_SYNTAX_ERROR(lineCounter, "All (sub-) sections may only be defined once."); + return EResult::FormatError; + } + result = ubSection->GetSubSection(sectionName, &ubSection); + ASSERT(IsResultOk(result)); // should never happen + } + currentSection = ubSection; currentSection->m_comments = commentBuffer; - commentBuffer.clear(); - continue; - } - if (currentSection == nullptr) - { - COUT_SYNTAX_ERROR(lineCounter, "Expected section begin before key-value pair."); - return EResult::FormatError; - } + commentBuffer.clear(); + continue; + } + if (currentSection == nullptr) + { + COUT_SYNTAX_ERROR(lineCounter, "Expected section begin before key-value pair."); + return EResult::FormatError; + } - int64_t keyValueDelimiterIndex = Tools::FirstIndexOf(currentLine, '='); - if (keyValueDelimiterIndex == -1) - { - COUT_SYNTAX_ERROR(lineCounter, "Expected '=' in line."); - return EResult::FormatError; - } + int64_t keyValueDelimiterIndex = Tools::FirstIndexOf(currentLine, '='); + if (keyValueDelimiterIndex == -1) + { + COUT_SYNTAX_ERROR(lineCounter, "Expected '=' in line."); + return EResult::FormatError; + } - auto keyValuePair = Tools::SplitInTwo(currentLine, keyValueDelimiterIndex); - Tools::StringTrim(keyValuePair.first); - Tools::StringTrim(keyValuePair.second); - if (keyValuePair.first.empty()) - { - COUT_SYNTAX_ERROR(lineCounter, "Expected key in line."); - return EResult::FormatError; - } - if (keyValuePair.second.empty()) - { - COUT_SYNTAX_ERROR(lineCounter, "Empty keys are not allowed"); - return EResult::FormatError; - } - auto parsedValue = ParseValue(keyValuePair.second); - if (parsedValue == nullptr) - { - COUT_SYNTAX_ERROR(lineCounter, "Invalid value"); - return EResult::FormatError; - } + auto keyValuePair = Tools::SplitInTwo(currentLine, keyValueDelimiterIndex); + Tools::StringTrim(keyValuePair.first); + Tools::StringTrim(keyValuePair.second); + if (keyValuePair.first.empty()) + { + COUT_SYNTAX_ERROR(lineCounter, "Expected key in line."); + return EResult::FormatError; + } + if (keyValuePair.second.empty()) + { + COUT_SYNTAX_ERROR(lineCounter, "Empty keys are not allowed"); + return EResult::FormatError; + } + auto parsedValue = ParseValue(keyValuePair.second); + if (parsedValue == nullptr) + { + COUT_SYNTAX_ERROR(lineCounter, "Invalid value"); + return EResult::FormatError; + } parsedValue->m_comments = commentBuffer; - commentBuffer.clear(); + commentBuffer.clear(); - currentSection->SetValue(keyValuePair.first, std::move(parsedValue), false); - } + currentSection->SetValue(keyValuePair.first, std::move(parsedValue), false); + } - return EResult::Success; + return EResult::Success; } minipp::EResult minipp::MiniPPFile::Write(const std::string& path) const noexcept { - std::ofstream ofs; - ofs.open(path); - if (!ofs.is_open()) - return EResult::FileIOError; + std::ofstream ofs; + ofs.open(path); + if (!ofs.is_open()) + return EResult::FileIOError; - return WriteSection(&m_rootSection, ofs, ""); + return WriteSection(&m_rootSection, ofs, ""); } bool minipp::MiniPPFile::IsResultOk(EResult result) noexcept { - return static_cast(result) > 0; + return static_cast(result) > 0; } #pragma region Tools bool minipp::MiniPPFile::Tools::StringStartsWith(const std::string& str, const std::string& beg) { - if (str.size() < beg.size()) - return false; + if (str.size() < beg.size()) + return false; - for (size_t i = 0; i < beg.size(); ++i) - if (str[i] != beg[i]) - return false; - return true; + for (size_t i = 0; i < beg.size(); ++i) + if (str[i] != beg[i]) + return false; + return true; } bool minipp::MiniPPFile::Tools::StringEndsWith(const std::string& str, const std::string& end) { - if (str.size() < end.size()) - return false; + if (str.size() < end.size()) + return false; - for (size_t i = 0; i < end.size(); ++i) - if (str[str.size() - i - 1] != end[end.size() - i - 1]) - return false; - return true; + for (size_t i = 0; i < end.size(); ++i) + if (str[str.size() - i - 1] != end[end.size() - i - 1]) + return false; + return true; } void minipp::MiniPPFile::Tools::StringTrim(std::string& str) { - if (str.empty()) - return; - size_t start = 0; - size_t end = str.size() - 1; - while (str[start] == ' ' || str[start] == '\t') - start++; - while (str[end] == ' ' || str[end] == '\t') - end--; - str = str.substr(start, end - start + 1); + if (str.empty()) + return; + size_t start = 0; + size_t end = str.size() - 1; + while (str[start] == ' ' || str[start] == '\t') + start++; + while (str[end] == ' ' || str[end] == '\t') + end--; + str = str.substr(start, end - start + 1); } bool minipp::MiniPPFile::Tools::IsNameValid(const std::string& name) noexcept { - for (const char c : name) - { - if ( - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || - (c == '_')) - continue; - return false; - } - return true; + for (const char c : name) + { + if ( + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + (c == '_')) + continue; + return false; + } + return true; } int64_t minipp::MiniPPFile::Tools::FirstIndexOf(const std::string& str, char c) noexcept { - int64_t index = 0; - for (const char ch : str) - { - if (ch == c) - return index; - ++index; - } - return -1; + int64_t index = 0; + for (const char ch : str) + { + if (ch == c) + return index; + ++index; + } + return -1; } int64_t minipp::MiniPPFile::Tools::LastIndexOf(const std::string& str, char c) noexcept { - int64_t index = str.size() - 1; - for (int64_t i = str.size() - 1; i >= 0; --i) - { - if (str[i] == c) - return index; - --index; - } - return -1; + int64_t index = str.size() - 1; + for (int64_t i = str.size() - 1; i >= 0; --i) + { + if (str[i] == c) + return index; + --index; + } + return -1; } std::pair minipp::MiniPPFile::Tools::SplitInTwo(const std::string& str, int64_t firstLength) noexcept { - return std::make_pair(str.substr(0, firstLength), str.substr(firstLength + 1)); + return std::make_pair(str.substr(0, firstLength), str.substr(firstLength + 1)); } std::vector minipp::MiniPPFile::Tools::SplitByDelimiter(const std::string& str, char delimiter) noexcept { - std::vector elements; - std::string tmp; + std::vector elements; + std::string tmp; - for (const char c : str) - { - if (c == delimiter) - { - elements.push_back(tmp); - tmp.clear(); - } - else - tmp.push_back(c); - } + for (const char c : str) + { + if (c == delimiter) + { + elements.push_back(tmp); + tmp.clear(); + } + else + tmp.push_back(c); + } - if (!tmp.empty()) - elements.push_back(tmp); - return elements; + if (!tmp.empty()) + elements.push_back(tmp); + return elements; } void minipp::MiniPPFile::Tools::RemoveAll(std::string& str, char old) { - str.erase(std::remove(str.begin(), str.end(), old), str.end()); + str.erase(std::remove(str.begin(), str.end(), old), str.end()); } bool minipp::MiniPPFile::Tools::IsIntegerDecimal(const std::string& str) noexcept { - for (size_t i = 0; i < str.size(); ++i) - if (str[i] < '0' || str[i] > '9') - return false; - return true; + for (size_t i = 0; i < str.size(); ++i) + if (str[i] < '0' || str[i] > '9') + return false; + return true; } #pragma endregion