diff options
| author | Markus Mittendrein <git@maxmitti.tk> | 2016-02-16 15:18:14 +0100 |
|---|---|---|
| committer | Markus Mittendrein <git@maxmitti.tk> | 2016-02-16 15:18:14 +0100 |
| commit | 681f2be56c45e3945cd6cbc56ac14317a97ec9c1 (patch) | |
| tree | 8c0a78a97ab82a22df6802f5c49e3e1077fb9161 | |
| download | qt-config-681f2be56c45e3945cd6cbc56ac14317a97ec9c1.tar.gz qt-config-681f2be56c45e3945cd6cbc56ac14317a97ec9c1.zip | |
Initial commit
| -rw-r--r-- | ConfigBase.cpp | 207 | ||||
| -rw-r--r-- | ConfigBase.hpp | 330 |
2 files changed, 537 insertions, 0 deletions
diff --git a/ConfigBase.cpp b/ConfigBase.cpp new file mode 100644 index 0000000..ce4bcc0 --- /dev/null +++ b/ConfigBase.cpp @@ -0,0 +1,207 @@ +#include "CRSMConfig.hpp" + +#include <QFile> + +void ConfigBase::addConfigValue(QString name, ConfigValueBase* value) +{ + configValues[name] = value; +} + +void ConfigBase::setConfigValue(const QString &name, const QString &value) +{ + ConfigValueBase& configValue = getConfigValue(name); + configValue.setValue(Util::unescape(value)); +} + +QString ConfigBase::getConfigValueLine(const QString &name) +{ + if(configValues.contains(name)) + { + ConfigValueBase& value = *configValues[name]; + switch(value.type()) + { + case ConfigValueType::Map: + { + QString ret; + const QMap<QString, QString>& values = (dynamic_cast<ConfigValueMap*>(&value))->getValues(); + foreach(const QString& mapKey, values.keys()) + { + ret += name + "[" + Util::escape(mapKey, '\\', "]") + "]" + " = " + Util::escape(values.value(mapKey)) + "\n"; + } + return ret; + break; + } + case ConfigValueType::List: + { + QString ret; + foreach(const QString& val, (dynamic_cast<ConfigValueList*>(&value))->getValues()) + { + ret += name + " += " + Util::escape(val) + "\n"; + } + return ret; + break; + } + default: + return name + " = " + Util::escape(value.value()) + "\n"; + } + } + else + { + throw ConfigException("Unknown config value \"" + name.toStdString() + "\""); + } +} + +void ConfigBase::addConfigListEntry(const QString &name, const QString &value) +{ + ConfigValueBase& configValue = getConfigValue(name); + if(configValue.type() != ConfigValueType::List) + { + throw ConfigException("Cannot add a list entry to a non-list-config value \"" + name.toStdString() + "\""); + } + ((ConfigValueList*)&configValue)->append(Util::unescape(value)); +} + +void ConfigBase::removeConfigMapListEntry(const QString &name, const QString &value) +{ + ConfigValueBase& configValue = getConfigValue(name); + if(configValue.type() == ConfigValueType::List) + { + ((ConfigValueList*)&configValue)->remove(Util::unescape(value)); + } + else if(configValue.type() == ConfigValueType::Map) + { + ((ConfigValueMap*)&configValue)->remove(Util::unescape(value)); + } + else + { + throw ConfigException("Cannot remove a list entry from a value which is neither a list nor a map \"" + name.toStdString() + "\""); + } +} + +void ConfigBase::setConfigMapValue(const QString &name, const QString &key, const QString &value) +{ + ConfigValueBase& configValue = getConfigValue(name); + if(configValue.type() != ConfigValueType::Map) + { + throw ConfigException("Cannot set a map value on a non-map-config value \"" + name.toStdString() + "\""); + } + ((ConfigValueMap*)&configValue)->setKeyValue(Util::unescape(key), Util::unescape(value)); +} + +ConfigValueBase& ConfigBase::getConfigValue(const QString& name) +{ + if(!configValues.contains(name)) + { + throw ConfigException("Unknown config value \"" + name.toStdString() + "\""); + } + else + { + return *configValues[name]; + } +} + +QString ConfigBase::read(const QString &fileName, bool writeDefault) +{ + curFileName = fileName; + QString ret = ""; + + QFile config(fileName); + if(!config.exists()) + { + if(writeDefault) + { + if(write(fileName)) + { + return fileName + ": The config-file did not exist, a new one with default values has been created.\n"; + } + else + { + return fileName + ": The config-file did not exist, a new one could not be created.\n"; + } + } + else + { + return ret; + } + } + else if(config.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QString line; + for(size_t lineNr = 1; !config.atEnd(); ++lineNr) + { + try + { + line = config.readLine().trimmed(); + if(!line.isEmpty()) + { + setConfigLine(line); + } + } + catch(ConfigException e) + { + ret += fileName + ":" + QString::number(lineNr) + ": " + e.what() + "\n"; + } + } + } + else + { + return fileName + ": The config-file could not be read.\n"; + } + + return ret; +} + +bool ConfigBase::write(QString fileName) +{ + if(fileName.isEmpty()) + { + fileName = curFileName; + } + if(fileName.isEmpty()) + { + return false; + } + QFile config(fileName); + if(config.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + { + foreach(const QString& key, configValues.keys()) + { + config.write(getConfigValueLine(key).toUtf8()); + } + + return true; + } + else + { + return false; + } +} + +void ConfigBase::setConfigLine(const QString &line) +{ + static QRegExp valueExp(R"(^([^=]+)=(.*)$)"); + static QRegExp listExp(R"(^([^=]+)\+=(.*)$)"); + static QRegExp listMapRemoveExp(R"(^([^=]+)\-=(.*)$)"); + static QRegExp mapExp(R"(^([^\[]+)\[((?:[^\]\\]|\\\]|\\\\)+)\]\s*=\s*(.*)$)"); + + if(listExp.exactMatch(line)) + { + addConfigListEntry(listExp.cap(1).trimmed(), listExp.cap(2).trimmed()); + } + else if(listMapRemoveExp.exactMatch(line)) + { + removeConfigMapListEntry(listMapRemoveExp.cap(1).trimmed(), listMapRemoveExp.cap(2).trimmed()); + } + else if(mapExp.exactMatch(line)) + { + setConfigMapValue(mapExp.cap(1).trimmed(), mapExp.cap(2).trimmed(), mapExp.cap(3).trimmed()); + } + else if(valueExp.exactMatch(line)) + { + setConfigValue(valueExp.cap(1).trimmed(), valueExp.cap(2).trimmed()); + } + else + { + throw ConfigException("Syntax error: unkown syntax"); + } +} diff --git a/ConfigBase.hpp b/ConfigBase.hpp new file mode 100644 index 0000000..ade454a --- /dev/null +++ b/ConfigBase.hpp @@ -0,0 +1,330 @@ +#ifndef CONFIGBASE +#define CONFIGBASE + +#include <QString> +#include <QStringList> +#include <QList> +#include <QMap> +#include <QRegularExpression> + +#include <exception> +#include <type_traits> +#include <limits> + +#include "Util.hpp" + +using ConfigException = std::logic_error; + +namespace ConfigValueType { +enum Type { + None, + String, + Integer, + Boolean, + List, + Map +}; +} + +class ConfigValueBase { +public: + + virtual void setValue(const QString&) { } + virtual QString value() { return ""; } + virtual ConfigValueType::Type type() { return ConfigValueType::Type::None; } + + + + template<typename Type> + static Type getValue(const QString& value); + + template<typename Type> + static QString getStringValue(Type val); +}; + +template<typename Type, typename = void> +class ConfigValue : public ConfigValueBase { +public: + virtual void setValue(const QString&) { throw ConfigException("Cannot assign"); } + virtual QString value() { throw ConfigException("Cannot get the value"); } +}; + +using String = QString; +using Integer = int; +using Port = ushort; +using Boolean = bool; +#define List(Type) QList<Type> +#define Map(KeyType, ValueType) QMap<KeyType, ValueType> + + +#define ConfigVal(Value) {#Value, mkConfigValue(Value)} +#define ConfigValEx(Value, ...) {#Value, mkConfigValue(Value, __VA_ARGS__)} + +template<> +class ConfigValue<String> : public ConfigValueBase { + String& config; + bool trimmedQuotes = false; + +public: + ConfigValue(String& config) : config(config) { } + + virtual void setValue(const QString& value) { config = Util::trimQuotes(value, trimmedQuotes); } + virtual QString value() + { + static QRegularExpression quoteExp(R"(^(\s.*|.*\s)$)"); + if(trimmedQuotes || quoteExp.match(config).hasMatch()) + { + return "\"" + config + "\""; + } + else + { + return config; + } + } + virtual ConfigValueType::Type type() { return ConfigValueType::Type::String; } +}; + +template<typename Type> +class ConfigValue<Type, typename std::enable_if<std::is_integral<Type>::value>::type> : public ConfigValueBase { + Type& config; + +public: + ConfigValue(Type& config) : config(config) { } + + virtual void setValue(const QString& value) + { + bool ok = true; + long long longLongVal = value.toLongLong(&ok, 0); + if(!ok) throw ConfigException("\"" + value.toStdString() + "\" could not be parsed as an Integer"); + if(longLongVal < std::numeric_limits<Type>::min() || longLongVal > std::numeric_limits<Type>::max()) throw ConfigException((QString::number(longLongVal) + " is out of range (" + QString::number(std::numeric_limits<Type>::min()) + " - " + QString::number(std::numeric_limits<Type>::max()) + ")").toStdString()); + config = static_cast<Type>(longLongVal); + } + virtual QString value() { return QString::number(config); } + virtual ConfigValueType::Type type() { return ConfigValueType::Type::Integer; } +}; + +template<> +class ConfigValue<Boolean> : public ConfigValueBase { + Boolean& config; + +public: + ConfigValue(Boolean& config) : config(config) { } + + virtual void setValue(const QString& value) + { + if(value == "true") + { + config = true; + } + else if(value == "false") + { + config = false; + } + else + { + throw ConfigException("\"" + value.toStdString() + "\" could not be parsed as a Boolean"); + } + } + virtual QString value() { return config ? "true" : "false"; } + virtual ConfigValueType::Type type() { return ConfigValueType::Type::Boolean; } +}; + +class ConfigValueList : public ConfigValueBase { +public: + virtual void append(const QString& entry) = 0; + virtual void remove(const QString& entry) = 0; + virtual QStringList getValues() = 0; +}; + +class ConfigValueMap : public ConfigValueBase { +public: + virtual void setKeyValue(const QString& key, const QString& value) = 0; + virtual void remove(const QString& key) = 0; + virtual QMap<QString, QString> getValues() = 0; +}; + +template<typename Type> +class ConfigValue<QList<Type>> : public ConfigValueList { + using ListType = QList<Type>; + + ListType& config; + const bool deduplicate; + const QChar splitChar; + +public: + ConfigValue(ListType& config, bool deduplicate = true, const QChar splitChar = ';') : config(config), deduplicate(deduplicate), splitChar(splitChar) {} + + virtual ListType& list() { return config; } + virtual ConfigValueType::Type type() { return ConfigValueType::Type::List; } + + virtual void append(const QString &entry) + { + Type val = ConfigValueBase::getValue<Type>(entry); + if(!deduplicate || !config.contains(val)) + { + config.append(val); + } + } + + virtual void remove(const QString &entry) { config.removeAll(ConfigValueBase::getValue<Type>(entry)); } + + virtual QStringList getValues() + { + QStringList ret; + + foreach(Type val, config) + { + ret.append(ConfigValueBase::getStringValue(val)); + } + + return ret; + } + + void setValue(const QString& value) + { + config.clear(); + if(value.isEmpty()) + { + return; + } + QStringList parts = Util::splitEscaped(value, splitChar); + foreach(const QString& part, parts) + { + config.append(ConfigValueBase::getValue<Type>(part)); + } + } + + QString value() + { + QStringList parts; + foreach(const Type& part, config) + { + parts.append(ConfigValueBase::getStringValue(part)); + } + return Util::joinEscape(parts, splitChar); + } +}; + +template<typename KeyType, typename ValueType> +class ConfigValue<QMap<KeyType, ValueType>> : public ConfigValueMap { + using MapType = QMap<KeyType, ValueType>; + + MapType& config; + const QChar assignChar; + const QChar splitChar; + +public: + ConfigValue(MapType& config, const QChar assignChar = ':', const QChar splitChar = ';') : config(config), assignChar(assignChar), splitChar(splitChar) {} + + virtual MapType& map() { return config; } + virtual ConfigValueType::Type type() { return ConfigValueType::Type::Map; } + + virtual void setKeyValue(const QString& key, const QString& value) + { + config[ConfigValueBase::getValue<KeyType>(key)] = ConfigValueBase::getValue<ValueType>(value); + } + + virtual QMap<QString, QString> getValues() + { + QMap<QString, QString> ret; + + foreach(KeyType key, config.keys()) + { + ret[ConfigValueBase::getStringValue(key)] = ConfigValueBase::getStringValue(config[key]); + } + + return ret; + } + + virtual void remove(const QString &key) + { + config.remove(ConfigValueBase::getValue<KeyType>(key)); + } + + QString value() + { + QStringList ret; + foreach(const KeyType& key, config.keys()) + { + ret.append(Util::joinEscape({ConfigValueBase::getStringValue(key), ConfigValueBase::getStringValue(config.value(key))}, assignChar)); + } + return Util::joinEscape(ret, splitChar); + } + + void setValue(const QString& value) + { + config.clear(); + if(value.isEmpty()) + { + return; + } + QStringList parts = Util::splitEscaped(value, splitChar); + foreach(const QString& part, parts) + { + QStringList parts = Util::splitEscaped(part, assignChar); + if(parts.length() != 2) + { + throw ConfigException("Cannot parse corrupt map key-value-pair"); + } + config[ConfigValueBase::getValue<KeyType>(parts.first())] = ConfigValueBase::getValue<ValueType>(parts.at(1)); + } + } +}; + +template<typename Type> +class ConfigValue<Type, typename std::enable_if<std::is_enum<Type>::value>::type> : public ConfigValue<typename std::underlying_type<Type>::type> { + using UndType = typename std::underlying_type<Type>::type; +public: + ConfigValue(Type& config) : ConfigValue<UndType>::ConfigValue((UndType&)config) {} +}; + +template<typename Type, typename... Types> +ConfigValue<Type>* mkConfigValue(Type& Value, Types... values) +{ + return new ConfigValue<Type>(Value, values...); +} + +template<typename Type> +Type ConfigValueBase::getValue(const QString& value) +{ + Type ret; + ConfigValue<Type> val(ret); + val.setValue(value); + return ret; +} + +template<typename Type> +QString ConfigValueBase::getStringValue(Type value) +{ + ConfigValue<Type> val(value); + return val.value(); +} + +class ConfigBase +{ +public: + explicit ConfigBase(QMap<QString, ConfigValueBase*> configValues) : configValues(configValues) {} + + void setConfigValue(const QString& name, const QString& value); + QString getConfigValueLine(const QString& name); + void addConfigListEntry(const QString& name, const QString& value); + void removeConfigMapListEntry(const QString &name, const QString &value); + void setConfigMapValue(const QString& name, const QString& key, const QString& value); + + QString read(const QString& fileName, bool writeDefault = true); + bool write(QString fileName = ""); + + void setConfigLine(const QString& line); + +protected: + void addConfigValue(QString name, ConfigValueBase *value); + ConfigValueBase& getConfigValue(const QString &name); + + QMap<QString, ConfigValueBase*> configValues; + +private: + QString curFileName = ""; +}; + +#endif // CONFIGBASE + |
