Compare commits

..

6 Commits
main ... dev

Author SHA1 Message Date
matthias@arch
9687a335ce Added testing with doctest 2023-02-01 21:29:33 +01:00
matthias@arch
742e608e01 moved value restriction to own class 2023-02-01 18:47:09 +01:00
matthias@arch
599f7ea81b added more warnings 2023-02-01 18:46:23 +01:00
matthias@arch
0c01e743f8 marked unused param 2023-02-01 18:46:01 +01:00
matthias@arch
c45bd6137c Allowed vals can be set in CI 2023-01-30 16:06:17 +01:00
matthias@arch
68d04eb43f Added IsPointerToType 2023-01-30 16:05:53 +01:00
8 changed files with 241 additions and 371 deletions

View File

@ -1,5 +1,6 @@
CXX = /usr/bin/g++
CXXFLAGS = -std=c++20 -MMD -MP -O3
CXXFLAGS += -Wall -Wpedantic -Wextra
SRCDIRS = $(wildcard */)
IFLAGS = $(foreach dir,$(SRCDIRS), -I$(dir))
IFLAGS += $(foreach dir,$(SRCDIRS), -I../$(dir))
@ -17,7 +18,7 @@ DEPENDS = ${OBJECTS:.o=.d}
CXXFLAGS += $(IFLAGS)
.PHONY: install debug run clean docs test
.PHONY: install uninstall update debug run clean docs test
#
# BUILDING
#
@ -43,6 +44,7 @@ $(OBJECT_DIRS):
#
# INSTALLATION
#
update: uninstall install
install: $(LIB) $(HEADER_INST)
@{ [ -z "$(DESTDIR)" ] && echo "Please set the DESTDIR variable (probably to /usr or /usr/local)" && exit 1; } || true
install -D -m 755 $< $(DESTDIR)/lib/$(subst ../,,$<)

View File

@ -1,8 +1,12 @@
#pragma once
#include <concepts>
#include <cstddef>
#include <ranges>
#include <memory>
#include <type_traits>
namespace gz::util {
/// Satisfied when T is in PackTypes
template<typename T, typename... PackTypes>
@ -23,6 +27,19 @@ namespace gz::util {
template<typename T, typename ValueType>
concept ContiguousRange = std::ranges::contiguous_range<T> and std::same_as<std::ranges::range_value_t<T>, ValueType>;
/// ObjectType accessible via *t and convertible to bool
template<typename T, typename ObjectType>
concept IsPointerToType = requires (T t/*, std::ptrdiff_t idx*/) {
/* { t.operator*() } -> std::same_as<std::add_lvalue_reference_t<ObjectType>>; */
/* { t.operator->() } -> std::same_as<std::add_pointer_t<ObjectType>>; */
/* { t.operator[](idx) } -> std::same_as<std::add_lvalue_reference_t<ObjectType>>; */
{ *t } -> std::same_as<std::add_lvalue_reference_t<ObjectType>>;
/* { t-> } -> std::same_as<std::add_lvalue_reference_t<ObjectType>>; */
{ static_cast<bool>(t) };
};
static_assert(IsPointerToType<std::shared_ptr<int>, int>, "");
static_assert(IsPointerToType<int*, int>, "");
}

18
src/gz-util.hpp Normal file
View File

@ -0,0 +1,18 @@
#pragma once
/**
* @file
* @brief Includes all headers from this library
* @details
* Not recommended - this is mainly used for testing the library.
*/
#include "settings_manager.hpp"
#include "log.hpp"
#include "string/conversion.hpp"
#include "string/utility.hpp"
#include "regex.hpp"
#include "concepts.hpp"
#include "container/queue.hpp"
#include "container/ringbuffer.hpp"

View File

@ -315,7 +315,7 @@ class Log {
void vlog(const char* appendChars, T&& t, Args&&... args) requires (!util::Stringy<T>);
/// End for the recursion
void vlog(const char* appendChars) {};
void vlog([[maybe_unused]] const char* appendChars) {};
private:
void init();

View File

@ -1,10 +1,6 @@
#pragma once
#include "file_io.hpp"
#include "exceptions.hpp"
#include "string/conversion.hpp"
#include "string/utility.hpp"
#include "concepts.hpp"
#include <functional>
#include <iostream>
@ -13,65 +9,12 @@
#include <set>
#include <type_traits>
#include <typeinfo>
#include <unordered_map>
#include <variant>
#include "settings_manager/common.hpp"
#include "settings_manager/restricted_value.hpp"
namespace gz {
template<typename T>
concept Number = std::integral<T> || std::floating_point<T>;
template<typename T>
concept NotNumber = !Number<T>;
/* template<typename T, typename... PackTypes> */
/* concept IntegralInPack = std::integral<T> && util::IsInPack<T, PackTypes...>; */
/* template<typename T, typename... PackTypes> */
/* concept FloatingPointInPack = std::floating_point<T> && util::IsInPack<T, PackTypes...>; */
template<typename T, typename... PackTypes>
concept NumberInPack = Number<T> && util::IsInPack<T, PackTypes...>;
template<typename T, typename... PackTypes>
concept NotNumberInPack = NotNumber<T> && util::IsInPack<T, PackTypes...>;
enum SettingsManagerAllowedValueTypes {
SM_RANGE, SM_LIST,
};
/**
* @brief Information about the allowed values
* @details
* If type is
* - SM_RANGE -> allowedValues must contain two numbers [min, max]
* - SM_LIST -> allowedValues must contain strings, which are the allowed values
*/
template<StringConvertible... CacheTypes>
struct SettingsManagerAllowedValues {
SettingsManagerAllowedValueTypes type;
std::variant<std::vector<std::string>, std::vector<CacheTypes>... > allowedValues;
};
/**
* @brief Check if struct is valid.
* @throws InvalidType if:
* - the type contained in the variant allowedValues is not T
* - type is SM_RANGE but T is not an integral or floating point type
*
* @throws InvalidArgument if:
* - vector contained in allowedValues is empty
* - type is SM_RANGE but allowedValues is not of size 2
* - type is SM_RANGE but allowedValues[0] >= allowedValues[1]
*/
template<Number T, StringConvertible... CacheTypes>
void hasCorrectFormat(SettingsManagerAllowedValues<CacheTypes...>& ab);
template<NotNumber T, StringConvertible... CacheTypes>
void hasCorrectFormat(SettingsManagerAllowedValues<CacheTypes...>& av);
/* template<std::integral T, StringConvertible... CacheTypes> */
/* void hasCorrectFormat(SettingsManagerAllowedValues<CacheTypes...>& ab); */
/**
* @brief Creation info for SettingsManager
*/
@ -102,22 +45,6 @@ namespace gz {
* @brief Wether to write the values to filepath when destroying the SettingsManager
*/
bool writeFileOnExit = false;
/*
* @brief A map containing SettingsMangerAllowedValues to restrict values
* @details
* If allowedValues contains key, its value must be allowed by SettingsMangerAllowedValues struct.
* If it is not allowed, any operation that tries to set an invalid value will throw InvalidArgument or be ignored:
* - in constructor: always ignored
* - in SettingsManager::readFileOnCreation: always ignored
* - in SettingsManager::setAllowedValues: always ignored
* - in SettingsManager::set: depends on throwExceptionWhenNewValueNotAllowed
* @see sm_validity
*/
/* util::unordered_string_map<SettingsManagerAllowedValues<CacheTypes...>> allowedValues; */
/**
* @brief Wether to throw an exception when trying to set an invalid value
*/
bool throwExceptionWhenNewValueNotAllowed = true;
};
using SettingsCallbackFunction = std::function<void(const std::string&)>;
@ -129,11 +56,6 @@ namespace gz {
* The SettingsManager is basically a map with extra features.
* It stores key-value pairs in a map. Both key and value are strings, but other types can also be stored.
*
* @section sm_validity Restricting values
* SettingsManager can restrict the values for a given keys to a range of numbers, or a list of possible strings.
* This can be done by providing @ref SettingsMangerAllowedValues::allowedValues "a map containing information on the allowed values" in the create info.
* The settings manager will then never return an invalid value, but it might still throw errors if there is no value set.
*
* @section sm_cache Storing other types
* While all values are internally strings, SettingsManager can also store different types.
* The types must satisfy the concept StringConvertible = ConstructibleFromString and ConvertibleToString.
@ -293,11 +215,11 @@ namespace gz {
* @ingroup sm_allowed_values
* @brief Check if a value is allowed for key
*/
template<NumberInPack<std::string, CacheTypes...> T>
template<util::NumberInPack<std::string, CacheTypes...> T>
bool isValueAllowed(const std::string& key, const T& value) const noexcept;
/* template<IntegralInPack<std::string, CacheTypes...> T> */
/* bool isValueAllowed(const std::string& key, const T& value) const noexcept; */
template<NotNumberInPack<std::string, CacheTypes...> T>
template<util::NotNumberInPack<std::string, CacheTypes...> T>
bool isValueAllowed(const std::string& key, const T& value) const noexcept;
/**
@ -317,7 +239,7 @@ namespace gz {
template<util::IsInPack<CacheTypes...> T>
void setAllowedValues(const std::string& key, std::vector<T>& allowedValues, SettingsManagerAllowedValueTypes type=SM_LIST);
template<util::IsInPack<CacheTypes...> T>
void setAllowedValues(const std::string& key, std::vector<T>&& allowedValues, SettingsManagerAllowedValueTypes type=SM_LIST) { setAllowedValues<T>(key, allowedValues, type); };
void setAllowedValues(const std::string& key, std::vector<T>&& allowedValues, SettingsManagerAllowedValueTypes type=SM_LIST) { setAllowedValues<T>(key, allowedValues, type); }
/**
* @ingroup sm_allowed_values
@ -352,7 +274,7 @@ namespace gz {
*
*/
template<std::same_as<void> Void>
void initCache() {};
void initCache() {}
std::set<std::string> cacheTypes;
util::unordered_string_map<util::unordered_string_map<std::variant<std::monostate, CacheTypes...>>> settingsCache;
/* template<StringConvertible T> */
@ -360,23 +282,6 @@ namespace gz {
/**
* @}
*/
/**
* @name Restricting values
* @see sm_validity
* @{
*/
// Allowed Values
/**
* @brief Make sure the allowedValues map is valid
* @throws InvalidArgument if any of the initial allowedValues structs is invalid
*/
void initAllowedValues();
util::unordered_string_map<SettingsManagerAllowedValues<CacheTypes...>> allowedValues;
bool throwExceptionWhenNewValueNotAllowed;
/**
* @}
*/
util::unordered_string_map<std::string> settings;
util::unordered_string_map<SettingsCallbackFunction> settingsCallbackFunctions;
@ -391,94 +296,6 @@ namespace gz {
}
namespace gz {
/* template<StringConvertible... CacheTypes, NumberInPack<std::string, CacheTypes...> T> */
/* template<std::integral T, StringConvertible... CacheTypes> */
/* void hasCorrectFormat(SettingsManagerAllowedValues<CacheTypes...>& av) { */
/* const std::vector<T>* v = nullptr; */
/* try { */
/* v = &std::get<std::vector<T>>(av.allowedValues); */
/* } */
/* catch (std::bad_variant_access& e) { */
/* throw InvalidType("allowedValues variant does not contain type T", "SettingsManagerAllowedValues::hasCorrectFormat"); */
/* } */
/* switch (av.type) { */
/* case SM_LIST: */
/* if (v->empty()) { */
/* throw InvalidArgument("Allowed value vector needs to have at least one element when AllowedValueType is SM_LIST, but is empty.", "SettingsManagerAllowedValues::hasCorrectFormat"); */
/* } */
/* break; */
/* case SM_RANGE: */
/* static_assert(std::floating_point<T> || std::integral<T>, "Type must be integral or floating point when using SM_RANGE."); */
/* std::size_t size = std::get<std::vector<int>>(av.allowedValues).size(); */
/* if (size == 2) { */
/* v->push_back(static_cast<T>(1)); */
/* } */
/* else if (size != 3) { */
/* throw InvalidArgument("AllowedValueType is SM_RANGE with integral type but allowedValues does not have size 2 or 3.", "SettingsManagerAllowedValues::hasCorrectFormat"); */
/* } */
/* // check min < max */
/* if (v[0] >= v[1]) { */
/* throw InvalidArgument("AllowedValueType is SM_RANGE but allowedValues[0] is larger than allowedValues[1].", "SettingsManagerAllowedValues::hasCorrectFormat"); */
/* } */
/* break; */
/* } // switch */
/* } */
template<Number T, StringConvertible... CacheTypes>
void hasCorrectFormat(SettingsManagerAllowedValues<CacheTypes...>& av) {
static_assert(util::IsInPack<T, CacheTypes...>, "T must be be in pack CacheTypes");
const std::vector<T>* v = nullptr;
try {
v = &std::get<std::vector<T>>(av.allowedValues);
}
catch (std::bad_variant_access& e) {
throw InvalidType("allowedValues variant does not contain type T", "SettingsManagerAllowedValues::hasCorrectFormat");
}
switch (av.type) {
case SM_LIST:
if (v->empty()) {
throw InvalidArgument("Allowed value vector needs to have at least one element when AllowedValueType is SM_LIST, but is empty.", "SettingsManagerAllowedValues::hasCorrectFormat");
}
break;
case SM_RANGE:
static_assert(std::floating_point<T> || std::integral<T>, "Type must be integral or floating point when using SM_RANGE.");
std::size_t size = v->size();
if (size != 2) {
throw InvalidArgument("AllowedValueType is SM_RANGE with floating point type but allowedValues does not have size 2.", "SettingsManagerAllowedValues::hasCorrectFormat");
}
// check min <= max
if (v->at(0) > v->at(1)) {
/* throw InvalidArgument("AllowedValueType is SM_RANGE but allowedValues[0] >= allowedValues[1].", "SettingsManagerAllowedValues::hasCorrectFormat"); */
throw InvalidArgument("AllowedValueType is SM_RANGE but allowedValues[0]=" + toString(v->at(0)) + " > " + toString(v->at(1)) + "=allowedValues[1].", "SettingsManagerAllowedValues::hasCorrectFormat");
}
break;
} // switch
}
/* template<StringConvertible... CacheTypes, NotNumberInPack<std::string, CacheTypes...> T> */
template<NotNumber T, StringConvertible... CacheTypes>
void hasCorrectFormat(SettingsManagerAllowedValues<CacheTypes...>& av) {
static_assert(util::IsInPack<T, CacheTypes...>, "T must be be in pack CacheTypes");
const std::vector<T>* v = nullptr;
try {
v = &std::get<std::vector<T>>(av.allowedValues);
}
catch (std::bad_variant_access& e) {
throw InvalidType("allowedValues variant does not contain type T", "SettingsManagerAllowedValues::hasCorrectFormat");
}
switch (av.type) {
case SM_LIST:
if (v->empty()) {
throw InvalidArgument("Allowed value vector needs to have at least one element when AllowedValueType is SM_LIST, but is empty.", "SettingsManagerAllowedValues::hasCorrectFormat");
}
break;
case SM_RANGE:
throw InvalidArgument("Type must be integral or floating point when using SM_RANGE, but is <" + std::string(typeid(T).name()) + ">");
break;
} // switch
}
//
// SETTINGS MANAGER
//
@ -486,9 +303,6 @@ namespace gz {
SettingsManager<CacheTypes...>::SettingsManager(SettingsManagerCreateInfo<CacheTypes...>& createInfo) {
insertFallbacks = createInfo.insertFallbacks;
writeFileOnExit = createInfo.writeFileOnExit;
throwExceptionWhenNewValueNotAllowed = createInfo.throwExceptionWhenNewValueNotAllowed;
/* allowedValues = std::move(createInfo.allowedValues); */
settings = std::move(createInfo.initialValues);
filepath = createInfo.filepath;
@ -496,13 +310,6 @@ namespace gz {
readFromFile(false);
}
// erase invalid initalValues
/* for (auto it = settings.begin(); it != settings.end(); it++) { */
/* if (!isValueAllowed<std::string>(it->first, it->second)) { */
/* it = settings.erase(it); */
/* } */
/* } */
initCache<void, CacheTypes...>();
}
@ -541,18 +348,12 @@ namespace gz {
template<StringConvertible... CacheTypes>
template<util::IsInPack<CacheTypes...> T>
const T& SettingsManager<CacheTypes...>::get(const std::string& key) {
static_assert(util::IsInPack<T, CacheTypes...>, "Type T is not in parameter pack CacheTypes...");
/* if (!isRegisteredType<T>()) { */
/* throw InvalidType("Invalid type: '" + std::string(typeid(T).name()) + "'", "SettingsManager::get"); */
/* } */
if (!settings.contains(key)) {
throw InvalidArgument("Invalid key: '" + key + "'", "SettingsManager::get");
}
// if not cached -> cache
if (!settingsCache[typeid(T).name()].contains(key)) {
try {
/* settingsCache[typeid(T).name()][key].emplace(fromString<T>(settings[key])); */
/* settingsCache[typeid(T).name()][key](fromString<T>(settings[key])); */
settingsCache[typeid(T).name()][key] = std::variant<std::monostate, CacheTypes...>(fromString<T>(settings[key]));
}
catch (...) {
@ -581,9 +382,6 @@ namespace gz {
template<StringConvertible... CacheTypes>
template<util::IsInPack<CacheTypes...> T>
const T& SettingsManager<CacheTypes...>::getOr(const std::string& key, const T& fallback) requires std::copy_constructible<T> {
/* if (!isRegisteredType<T>()) { */
/* throw InvalidType("Invalid type: '" + std::string(typeid(T).name()) + "'", "SettingsManager::getOr"); */
/* } */
if (settings.contains(key)) {
return get<T>(key);
}
@ -629,15 +427,6 @@ namespace gz {
//
template<StringConvertible... CacheTypes>
void SettingsManager<CacheTypes...>::set(const std::string& key, const std::string& value) {
// check if new value is allowed
if (!isValueAllowed<std::string>(key, value)) {
if (throwExceptionWhenNewValueNotAllowed) {
throw InvalidArgument("Value '" + value + "' is not allowed. Key: '" + key + "'", "SettingsManager::set");
}
else {
return;
}
}
// remove old value from cache
for (auto& [type, cache] : settingsCache) {
if (cache.contains(key)) {
@ -662,29 +451,18 @@ namespace gz {
template<StringConvertible... CacheTypes>
template<util::IsInPack<CacheTypes...> T>
void SettingsManager<CacheTypes...>::set(const std::string& key, const T& value) {
/* if (!isRegisteredType<T>()) { */
/* throw InvalidType("Invalid type: '" + std::string(typeid(T).name()) + "'", "SettingsManager::set<" + std::string(typeid(T).name()) + ">"); */
/* } */
// convert to string
std::string s;
try {
s = gz::toString(value);
}
// TODO why cathes?
catch (std::exception& e) {
throw InvalidArgument("Could not convert value to string, an exception occured: '" + std::string(e.what()) + "'. Key: '" + key + "'", "SettingsManager::set<" + std::string(typeid(T).name()) + ">");
}
catch (...) {
throw InvalidArgument("Could not convert value to string, an exception occured. Key: '" + key + "'", "SettingsManager::set<" + std::string(typeid(T).name()) + ">");
}
// check if new value is allowed
if (!isValueAllowed<T>(key, value)) {
if (throwExceptionWhenNewValueNotAllowed) {
throw InvalidArgument("Value '" + s + "' is not allowed. Key: '" + key + "'", "SettingsManager::set<" + std::string(typeid(T).name()) + ">");
}
else {
return;
}
}
// remove old value from cache
for (auto& [type, cache] : settingsCache) {
if (cache.contains(key)) {
@ -709,145 +487,6 @@ namespace gz {
}
//
// ALLOWED VALUES
//
/* template<StringConvertible... CacheTypes> */
/* template<IntegralInPack<std::string, CacheTypes...> T> */
/* bool SettingsManager<CacheTypes...>::isValueAllowed(const std::string& key, const T& value) const noexcept { */
/* if (!allowedValues.contains(key)) { */
/* return true; */
/* } */
/* const std::vector<T>* av = nullptr; */
/* try { */
/* av = &std::get<std::vector<T>>(allowedValues.at(key).allowedValues); */
/* } */
/* catch (std::bad_variant_access&) { */
/* return false; */
/* } */
/* switch (allowedValues.at(key).type) { */
/* case SM_LIST: { */
/* for (auto it = av->begin(); it != av->end(); it++) { */
/* if (*it == value) { */
/* return true; */
/* } */
/* } */
/* } */
/* break; */
/* case SM_RANGE: { */
/* bool valid = true; */
/* // value >= lowest */
/* valid &= value >= av->at(0); */
/* // value < highest */
/* valid &= value < av->at(1); */
/* // value == lowest + n * step */
/* valid &= (value - av->at(0)) % av->at(2) == 0; */
/* return valid; */
/* } */
/* break; */
/* } // switch */
/* return false; */
/* } */
template<StringConvertible... CacheTypes>
template<NumberInPack<std::string, CacheTypes...> T>
bool SettingsManager<CacheTypes...>::isValueAllowed(const std::string& key, const T& value) const noexcept {
if (!allowedValues.contains(key)) {
return true;
}
const std::vector<T>* av = nullptr;
try {
av = &std::get<std::vector<T>>(allowedValues.at(key).allowedValues);
}
catch (std::bad_variant_access&) {
return false;
}
switch (allowedValues.at(key).type) {
case SM_LIST: {
for (auto it = av->begin(); it != av->end(); it++) {
if (*it == value) {
return true;
}
}
}
break;
case SM_RANGE: {
bool valid = true;
// value >= lowest
valid &= value >= av->at(0);
// value <= highest
valid &= value <= av->at(1);
return valid;
}
break;
} // switch
return false;
}
template<StringConvertible... CacheTypes>
template<NotNumberInPack<std::string, CacheTypes...> T>
bool SettingsManager<CacheTypes...>::isValueAllowed(const std::string& key, const T& value) const noexcept {
if (!allowedValues.contains(key)) {
return true;
}
const std::vector<T>* av = nullptr;
try {
av = &std::get<std::vector<T>>(allowedValues.at(key).allowedValues);
}
catch (std::bad_variant_access&) {
return false;
}
switch (allowedValues.at(key).type) {
case SM_LIST: {
for (auto it = av->begin(); it != av->end(); it++) {
if (*it == value) {
return true;
}
}
}
break;
case SM_RANGE: {
return false;
}
break;
} // switch
return false;
}
template<StringConvertible... CacheTypes>
template<util::IsInPack<CacheTypes...> T>
void SettingsManager<CacheTypes...>::setAllowedValues(const std::string& key, std::vector<T>& allowed_vector, SettingsManagerAllowedValueTypes type) {
/* std::cout << "setAllowedValues: " << typeid(std::vector<T>).name() << " - " << typeid(T).name() << "\n"; */
SettingsManagerAllowedValues<CacheTypes...> av;
av.type = type;
av.allowedValues = std::variant<std::vector<std::string>, std::vector<CacheTypes>...>(std::move(allowed_vector));
hasCorrectFormat<T>(av);
allowedValues[key] = std::move(av);
// erase the current value if it is no longer allowed
if (settingsCache[typeid(T).name()].contains(key)) {
/* std::cout << "setAllowedValues <" << typeid(T).name() << ">\n"; */
if (!isValueAllowed<T>(key, std::get<T>(settingsCache[typeid(T).name()].at(key)))) {
settings.erase(key);
settingsCache[typeid(T).name()].erase(key);
}
}
else if (settings.contains(key)) {
try {
if (!isValueAllowed<T>(key, fromString<T>(settings.at(key)))) {
settings.erase(key);
}
}
catch (...) {
settings.erase(key);
}
}
}
template<StringConvertible... CacheTypes>
void SettingsManager<CacheTypes...>::removeAllowedValues(const std::string& key) noexcept {
allowedValues.erase(key);
}
//
// CALLBACKS
//
template<StringConvertible... CacheTypes>

View File

@ -0,0 +1,73 @@
#pragma once
#include "value_restriction.hpp"
namespace gz {
template<typename T>
class RestrictedValue {
public:
/**
* @brief Create a value that is restricted to the restriction
* @throws InvalidArgument if initial does not satisfy the restriction
*/
RestrictedValue(T&& initial, ValueRestriction<T>&& restriction);
/**
* @brief Get the value.
* @details
* This function will never return a value that does not satisfy the restriction
*/
inline const T& get() const { return val; }
/**
* @brief Set a new value
* @throws InvalidArgument if initial does not satisfy the restriction
*/
void operator=(const T& v);
void operator=(T&& v);
void operator=(const std::string& s) const requires (ConstructibleFromString<T> && !std::same_as<std::string, T>);
inline const ValueRestriction<T>& getRestriction() const { return restriction; }
private:
T val;
ValueRestriction<T> restriction;
};
}
namespace gz {
template<typename T>
RestrictedValue<T>::RestrictedValue(T&& initial, ValueRestriction<T>&& restriction) {
}
template<typename T>
void RestrictedValue<T>::operator=(T&& v) {
if (restriction.isValueAllowed(v)) {
val = std::move(v);
}
else {
throw InvalidArgument("Trying to assign invalid value", "RestrictedValue::operator=");
}
}
template<typename T>
void RestrictedValue<T>::operator=(const T& v) {
if (restriction.isValueAllowed(v)) {
val = v;
}
else {
throw InvalidArgument("Trying to assign invalid value", "RestrictedValue::operator=");
}
}
template<typename T>
void RestrictedValue<T>::operator=(const std::string& s) const requires (ConstructibleFromString<T> && !std::same_as<std::string, T>) {
T v;
try {
v = fromString<T>(s);
}
catch (...) {
throw InvalidArgument(std::string("Can not convert string to type <") + typeid(T).name() + ">", "RestrictedValue::operator=");
}
*this = std::move(v);
}
} // namespace gz

View File

@ -0,0 +1,110 @@
#pragma once
#include "common.hpp"
#include <functional>
#include <type_traits>
namespace gz {
enum SettingsManagerAllowedValueTypes {
SM_RANGE, SM_LIST,
};
/**
* @brief A value restriction can check if a variable satisfies the restriction.
* @details
* The restriction can be:
* - SM_RANGE -> value needs to be in [min, max]
* - SM_LIST -> value must be one of
*/
template<typename T>
class ValueRestriction {
public:
/**
* @todo allow any class that has <= and >=
* @brief Restriction is a range [lower, upper]
*/
ValueRestriction(const T& lower, const T& upper) requires util::Number<T>;
/**
* @brief Restriction is a list of allowedValues
*/
ValueRestriction(std::vector<T>&& allowedValues) noexcept;
/**
* @brief Check if a value is allowed according to SettingsManagerAllowedValues
*/
bool isValueAllowed(const T& value) const noexcept;
/**
* @brief Construct a value from string and check if it is allowed according to SettingsManagerAllowedValues
* @throws Exceptions that occur while s is converted to T (using gz::fromString())
*/
bool isValueAllowed(const std::string& s) const requires (ConstructibleFromString<T> && !std::same_as<std::string, T>);
inline const SettingsManagerAllowedValueTypes& getType() const { return type; }
private:
const SettingsManagerAllowedValueTypes type;
std::vector<T> allowedValues; // using vector and not set to also store lower, upper in there
};
} // namespace gz
namespace gz {
//
// VALUERESTICTION IMPLEMENTATION
//
template<typename T>
ValueRestriction<T>::ValueRestriction(const T& lower, const T& upper) requires util::Number<T>
: type(SettingsManagerAllowedValueTypes::SM_RANGE) {
if (lower > upper) {
throw InvalidArgument("ValueRestriction: lower(=\"" + gz::toString(lower) + "\") is larger than upper(=\"" + gz::toString(upper) + "\")");
}
allowedValues.push_back(lower);
allowedValues.push_back(upper);
}
template<typename T>
ValueRestriction<T>::ValueRestriction(std::vector<T>&& allowedValues) noexcept
: type(SettingsManagerAllowedValueTypes::SM_LIST) {
this->allowedValues = std::move(allowedValues);
}
template<typename T>
bool ValueRestriction<T>::isValueAllowed(const std::string& s) const requires (ConstructibleFromString<T> && !std::same_as<std::string, T>) {
T val = fromString<T>(s);
return isValue(val);
}
template<typename T>
bool ValueRestriction<T>::isValueAllowed(const T& value) const noexcept {
switch (this->type) {
case SM_LIST: {
for (auto it = this->allowedValues.begin(); it != this->allowedValues.end(); it++) {
if (*it == value) { return true; }
}
}
break;
case SM_RANGE: {
bool valid = true;
// value >= lowest
valid &= value >= allowedValues.at(0);
// value <= highest
valid &= value <= allowedValues.at(1);
return valid;
}
break;
} // switch
return false;
}
} // namespace gz
#ifdef GZ_UTIL_TESTING
// TODO
TEST_CASE("")
CHECK_THROWS()
#endif

11
src/testing.cpp Normal file
View File

@ -0,0 +1,11 @@
#ifdef GZ_UTIL_TESTING
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>
#include "gz-util.hpp"
#endif
/**
* @file
* @brief Includes all headers and implements `main` for testing
*/