| /* ---------------------------------------------------------------------------- |
| libconfig - A library for processing structured configuration files |
| libconfig chained - Extension for reading the configuration and defining |
| the configuration specification at once. |
| Copyright (C) 2016 Richard Schubert |
| |
| This file is part of libconfig contributions. |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public License |
| as published by the Free Software Foundation; either version 2.1 of |
| the License, or (at your option) any later version. |
| |
| This library is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Library General Public |
| License along with this library; if not, see |
| <http://www.gnu.org/licenses/>. |
| ---------------------------------------------------------------------------- |
| */ |
| |
| #pragma once |
| #ifndef _CHAINED_LIBCONFIG_H_ |
| #define _CHAINED_LIBCONFIG_H_ |
| |
| #include <libconfig.h++> |
| #include <cassert> |
| #include <fstream> |
| #include <sstream> |
| #include <iostream> |
| |
| namespace libconfig |
| { |
| class ChainedSetting |
| { |
| struct Variant |
| { |
| private: |
| bool isSet; |
| Setting::Type type; |
| |
| bool value_bool; |
| int64_t value_int; |
| double value_float; |
| std::string value_string; |
| |
| public: |
| |
| Variant() |
| : isSet(false) |
| , type(Setting::TypeNone) |
| { |
| } |
| Variant(bool value) |
| { |
| value_bool = value; |
| isSet = true; |
| type = Setting::TypeBoolean; |
| } |
| Variant(int32_t value) |
| { |
| value_int = value; |
| isSet = true; |
| type = Setting::TypeInt; |
| } |
| Variant(int64_t value) |
| { |
| value_int = value; |
| isSet = true; |
| type = Setting::TypeInt64; |
| } |
| Variant(double value) |
| { |
| value_float = value; |
| isSet = true; |
| type = Setting::TypeFloat; |
| } |
| Variant(std::string& value) |
| { |
| value_string = value; |
| isSet = true; |
| type = Setting::TypeString; |
| } |
| Variant(const char* value) |
| { |
| value_string = value; |
| isSet = true; |
| type = Setting::TypeString; |
| } |
| |
| operator bool() const { return value_bool; } |
| operator int() const { return (int)value_int; } |
| operator unsigned int() const { return (unsigned int)value_int; } |
| operator long() const { return (long)value_int; } |
| operator unsigned long() const { return (unsigned long)value_int; } |
| operator long long() const { return (long long)value_int; } |
| operator unsigned long long() const { return (unsigned long long)value_int; } |
| operator double() const { return value_float; } |
| operator float() const { return (float)value_float; } |
| operator std::string() const { return value_string; } |
| |
| const bool IsSet() const |
| { |
| return isSet; |
| } |
| |
| const Setting::Type GetType() const |
| { |
| return type; |
| } |
| }; |
| |
| public: |
| |
| // Starting point for method chained libconfig. |
| // Pass a custom ostream to intercept any error messages (useful for Applications with UI). |
| ChainedSetting(Setting& setting, std::ostream& err = std::cerr) |
| : name(setting.isRoot() ? "<root>" : (setting.getName() ? setting.getName() : "")) |
| , index(setting.getIndex()) |
| , parent(NULL) |
| , setting(&setting) |
| , err(err) |
| , isSettingMandatory(false) |
| , anySettingIsMissing(false) |
| , anyMandatorySettingIsMissing(false) |
| , capturedSpecification(NULL) |
| , capturedSetting(NULL) |
| { |
| } |
| |
| // Starts capturing any configuration readings into the temporary config object. |
| void captureExpectedSpecification(Config* temporaryConfigSpecification) |
| { |
| capturedSpecification = temporaryConfigSpecification; |
| capturedSetting = &capturedSpecification->getRoot(); |
| } |
| |
| // Returns the captured configuration specification, |
| // premised captureExpectedSpecification() was called earlier. |
| // The path parameter is needed to write the configuration |
| // to disk before it can be read into a usable string. |
| std::string getCapturedSpecification(const std::string& tempFilePath) |
| { |
| try |
| { |
| capturedSpecification->writeFile(tempFilePath.c_str()); |
| } |
| catch (const FileIOException&) |
| { |
| err << "I/O error while writing temporary setting file: " << tempFilePath << std::endl; |
| return ""; |
| } |
| |
| std::ifstream t(tempFilePath); |
| if (!t.is_open()) |
| { |
| err << "I/O error while reading temporary setting file: " << tempFilePath << std::endl; |
| return ""; |
| } |
| std::stringstream buffer; |
| buffer << t.rdbuf(); |
| |
| capturedSpecification = NULL; |
| |
| return buffer.str(); |
| } |
| |
| // Defines the default value for this setting if missing from config file. |
| template<typename T> |
| ChainedSetting& defaultValue(T defaultValue) |
| { |
| defaultVal = defaultValue; |
| return *this; |
| } |
| |
| // Defines the inclusive minimum value for this setting. |
| // A lesser value set in a configuration file will be clamped to this limit. |
| template<typename T> |
| ChainedSetting& min(T min) |
| { |
| minVal = min; |
| return *this; |
| } |
| |
| // Defines the inclusive maximum value for this setting. |
| // A greater value set in a configuration file will be clamped to this limit. |
| template<typename T> |
| ChainedSetting& max(T max) |
| { |
| maxVal = max; |
| return *this; |
| } |
| |
| // Defines this setting to be mandatory. |
| // Any mandatory value missing in the configuration file will raise an error. |
| // Use isAnyMandatorySettingMissing() to check for any violations. |
| ChainedSetting& isMandatory() |
| { |
| isSettingMandatory = true; |
| if (parent) parent->isMandatory(); |
| return *this; |
| } |
| |
| template<typename T> |
| operator T() |
| { |
| auto requestedType = GetRequestedType<T>(); |
| CheckType(defaultVal, requestedType); |
| CheckType(minVal, requestedType); |
| CheckType(maxVal, requestedType); |
| |
| CaptureSetting<T>(requestedType); |
| |
| if (!setting) |
| { |
| if (isSettingMandatory) |
| { |
| AlertMandatorySettingMissing<T>(); |
| } |
| PropagateAnySettingIsMissing(); |
| |
| return GetDefaultValue<T>(); |
| } |
| |
| try |
| { |
| T value = *setting; |
| if (minVal.IsSet()) |
| { |
| T min = minVal; |
| if (value < min) |
| { |
| err << "'" << setting->getPath() << "' setting is out of valid bounds (min: " << min << "). Value was: " << value << std::endl; |
| value = min; |
| } |
| } |
| if (maxVal.IsSet()) |
| { |
| T max = maxVal; |
| if (value > max) |
| { |
| err << "'" << setting->getPath() << "' setting is out of valid bounds (max: " << max << "). Value was: " << value << std::endl; |
| value = max; |
| } |
| } |
| return value; |
| } |
| catch (const SettingTypeException& tex) |
| { |
| err << "'" << tex.getPath() << "' setting is of wrong type." << std::endl; |
| } |
| |
| return GetDefaultValue<T>(); |
| } |
| |
| ChainedSetting operator[](const char *name) |
| { |
| CaptureSetting<Setting>(Setting::TypeGroup); |
| |
| if (!setting) |
| { |
| return ChainedSetting(name, this); |
| } |
| |
| if(setting->exists(name)) |
| { |
| return ChainedSetting((*setting)[name], this); |
| } |
| else |
| { |
| return ChainedSetting(name, this); |
| } |
| } |
| |
| inline ChainedSetting operator[](const std::string &name) |
| { |
| return(operator[](name.c_str())); |
| } |
| |
| ChainedSetting operator[](int index) |
| { |
| // This could also be an TypeArray but we cannot be sure here. |
| // By using TypeList we ensure it will always work. |
| CaptureSetting<Setting>(Setting::TypeList); |
| |
| if (!setting) |
| { |
| return ChainedSetting(index, this); |
| } |
| |
| if (index >= 0 && index < setting->getLength()) |
| { |
| return ChainedSetting((*setting)[index], this); |
| } |
| else |
| { |
| return ChainedSetting(index, this); |
| } |
| } |
| |
| int getLength() const |
| { |
| return setting ? setting->getLength() : 0; |
| } |
| |
| Setting::Type getType() const |
| { |
| return setting ? setting->getType() : Setting::TypeNone; |
| } |
| |
| // Indicates whether this setting is present in the read configuration file. |
| bool exists() const |
| { |
| return setting != NULL; |
| } |
| |
| bool isAnyMandatorySettingMissing() const |
| { |
| return anyMandatorySettingIsMissing; |
| } |
| |
| bool isAnySettingMissing() const |
| { |
| return anySettingIsMissing; |
| } |
| |
| void clearAnySettingMissingFlag() |
| { |
| anySettingIsMissing = false; |
| } |
| |
| private: |
| |
| ChainedSetting(Setting& setting, ChainedSetting* parent) |
| : name(setting.isRoot() ? "<root>" : (setting.getName() ? setting.getName() : "")) |
| , index(setting.getIndex()) |
| , parent(parent) |
| , setting(&setting) |
| , err(parent->err) |
| , isSettingMandatory(false) |
| , anySettingIsMissing(false) |
| , anyMandatorySettingIsMissing(false) |
| , capturedSpecification(NULL) |
| , capturedSetting(NULL) |
| { |
| } |
| |
| ChainedSetting(const std::string& name, ChainedSetting* parent) |
| : name(name) |
| , index(-1) |
| , parent(parent) |
| , setting(NULL) |
| , err(parent->err) |
| , isSettingMandatory(false) |
| , anySettingIsMissing(true) |
| , anyMandatorySettingIsMissing(false) |
| , capturedSpecification(NULL) |
| , capturedSetting(NULL) |
| { |
| } |
| |
| ChainedSetting(int index, ChainedSetting* parent) |
| : name("") |
| , index(index) |
| , parent(parent) |
| , setting(NULL) |
| , err(parent->err) |
| , isSettingMandatory(false) |
| , anySettingIsMissing(true) |
| , anyMandatorySettingIsMissing(false) |
| , capturedSpecification(NULL) |
| , capturedSetting(NULL) |
| { |
| } |
| |
| template<typename T> |
| void ConditionalSetCapturedDefaultValue() |
| { |
| *capturedSetting = GetDefaultValue<T>(); |
| } |
| |
| |
| |
| template<typename T> |
| void CaptureSetting(Setting::Type type) |
| { |
| if (!capturedSetting && parent && parent->capturedSetting) |
| { |
| if (name.length() > 0) |
| { |
| if (!parent->capturedSetting->exists(name)) |
| { |
| capturedSetting = &parent->capturedSetting->add(name, type); |
| } |
| else |
| { |
| capturedSetting = &(*parent->capturedSetting)[name.c_str()]; |
| } |
| } |
| else |
| { |
| if (index < parent->capturedSetting->getLength()) |
| { |
| capturedSetting = &(*parent->capturedSetting)[0]; |
| } |
| else |
| { |
| assert(index == parent->capturedSetting->getLength()); // you requested an index while omitting at least one of its previous siblings |
| capturedSetting = &parent->capturedSetting->add(type); |
| } |
| } |
| |
| ConditionalSetCapturedDefaultValue<T>(); |
| } |
| } |
| |
| |
| std::string GetPath() const |
| { |
| if (setting) |
| { |
| return setting->getPath(); |
| } |
| |
| std::string path = (name.length() > 0) ? name : "[" + std::to_string(index) + "]"; |
| if (parent) |
| { |
| auto parentPath = parent->GetPath(); |
| return (parentPath.length() > 0) ? (parentPath + ((name.length() == 0) ? "" : ".") + path) : path; |
| } |
| return path; |
| } |
| |
| void PropagateAnySettingIsMissing() |
| { |
| anySettingIsMissing = true; |
| if (parent) |
| { |
| parent->PropagateAnySettingIsMissing(); |
| } |
| } |
| |
| void PropagateAnyMandatorySettingIsMissing() |
| { |
| anyMandatorySettingIsMissing = true; |
| if (parent) |
| { |
| parent->PropagateAnyMandatorySettingIsMissing(); |
| } |
| } |
| |
| template<typename T> |
| void AlertMandatorySettingMissing() |
| { |
| PropagateAnyMandatorySettingIsMissing(); |
| |
| err << "Missing '" << GetPath() << "' setting in configuration file." << std::endl; |
| } |
| |
| template<typename T> |
| T GetUnsetDefaultValue() const |
| { |
| return (T)0; |
| } |
| |
| |
| |
| template<typename T> |
| T GetDefaultValue() const |
| { |
| if (defaultVal.IsSet()) |
| { |
| return (T)defaultVal; |
| } |
| |
| return GetUnsetDefaultValue<T>(); |
| } |
| |
| template<typename T> |
| Setting::Type GetRequestedType() const |
| { |
| // TODO @ Hemofektik: Check whether the outcommented line is still needed. static_assert(false) is checked on compile time and, well, asserts :) |
| // static_assert(false, "should never happen, unless you requested an unsupported type"); |
| return Setting::TypeNone; |
| } |
| |
| |
| void CheckType(const Variant& variant, Setting::Type expectedType) const |
| { |
| if (!variant.IsSet()) return; |
| if(expectedType != variant.GetType()) |
| { |
| assert(false); // fix your code to match the whole chain of this setting to one single type! |
| err << "'" << GetPath() << "' setting limits or default value is of incompatible type." << std::endl; |
| } |
| } |
| |
| std::string name; |
| int index; |
| ChainedSetting* parent; |
| Setting* setting; |
| std::ostream& err; |
| Variant defaultVal; |
| Variant minVal; |
| Variant maxVal; |
| bool isSettingMandatory; |
| bool anySettingIsMissing; |
| bool anyMandatorySettingIsMissing; |
| Config* capturedSpecification; |
| Setting* capturedSetting; |
| }; |
| |
| template<> |
| inline |
| void ChainedSetting::ConditionalSetCapturedDefaultValue<Setting>() { } |
| |
| template<> |
| inline |
| std::string ChainedSetting::GetUnsetDefaultValue() const |
| { |
| return ""; |
| } |
| |
| |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<int8_t>() const { return Setting::TypeInt; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<uint8_t>() const { return Setting::TypeInt; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<int16_t>() const { return Setting::TypeInt; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<uint16_t>() const { return Setting::TypeInt; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<int32_t>() const { return Setting::TypeInt; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<uint32_t>() const { return Setting::TypeInt; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<int64_t>() const { return Setting::TypeInt64; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<uint64_t>() const { return Setting::TypeInt64; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<float>() const { return Setting::TypeFloat; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<double>() const { return Setting::TypeFloat; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<std::string>() const { return Setting::TypeString; } |
| template<> inline Setting::Type ChainedSetting::GetRequestedType<bool>() const { return Setting::TypeBoolean; } |
| } |
| |
| #endif |