diff options
author | John Ericson <John.Ericson@Obsidian.Systems> | 2023-05-19 10:56:59 -0400 |
---|---|---|
committer | John Ericson <John.Ericson@Obsidian.Systems> | 2023-06-18 23:31:18 -0400 |
commit | d2ce2e89b178e7e7467cf4c1e572704a4c2ca75e (patch) | |
tree | 02aa329cd6ae719276abdfe953500783b91e1e23 /src/libutil | |
parent | c8825e9d8c3fa802811f3829d055e3ef9aae90e2 (diff) |
Split `OptionalPathSetting` from `PathSetting`
Rather than doing `allowEmpty` as boolean, have separate types and use
`std::optional`. This makes it harder to forget the possibility of an
empty path.
The `build-hook` setting was categorized as a `PathSetting`, but
actually it was split into arguments. No good! Now, it is
`Setting<Strings>` which actually reflects what it means and how it is
used.
Because of the subtyping, we now also have support for
`Setting<std::optional<String>>` in general. I imagine this can be used
to clean up many more settings also.
Diffstat (limited to 'src/libutil')
-rw-r--r-- | src/libutil/abstract-setting-to-json.hh | 1 | ||||
-rw-r--r-- | src/libutil/config-impl.hh | 61 | ||||
-rw-r--r-- | src/libutil/config.cc | 63 | ||||
-rw-r--r-- | src/libutil/config.hh | 31 |
4 files changed, 111 insertions, 45 deletions
diff --git a/src/libutil/abstract-setting-to-json.hh b/src/libutil/abstract-setting-to-json.hh index 7b6c3fcb5..d506dfb74 100644 --- a/src/libutil/abstract-setting-to-json.hh +++ b/src/libutil/abstract-setting-to-json.hh @@ -3,6 +3,7 @@ #include <nlohmann/json.hpp> #include "config.hh" +#include "json-utils.hh" namespace nix { template<typename T> diff --git a/src/libutil/config-impl.hh b/src/libutil/config-impl.hh index b6cae5ec3..14822050e 100644 --- a/src/libutil/config-impl.hh +++ b/src/libutil/config-impl.hh @@ -50,8 +50,11 @@ template<> void BaseSetting<std::set<ExperimentalFeature>>::appendOrSet(std::set template<typename T> void BaseSetting<T>::appendOrSet(T && newValue, bool append) { - static_assert(!trait::appendable, "using default `appendOrSet` implementation with an appendable type"); + static_assert( + !trait::appendable, + "using default `appendOrSet` implementation with an appendable type"); assert(!append); + value = std::move(newValue); } @@ -68,4 +71,60 @@ void BaseSetting<T>::set(const std::string & str, bool append) } } +template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string & category); + +template<typename T> +void BaseSetting<T>::convertToArg(Args & args, const std::string & category) +{ + args.addFlag({ + .longName = name, + .description = fmt("Set the `%s` setting.", name), + .category = category, + .labels = {"value"}, + .handler = {[this](std::string s) { overridden = true; set(s); }}, + .experimentalFeature = experimentalFeature, + }); + + if (isAppendable()) + args.addFlag({ + .longName = "extra-" + name, + .description = fmt("Append to the `%s` setting.", name), + .category = category, + .labels = {"value"}, + .handler = {[this](std::string s) { overridden = true; set(s, true); }}, + .experimentalFeature = experimentalFeature, + }); +} + +#define DECLARE_CONFIG_SERIALISER(TY) \ + template<> TY BaseSetting< TY >::parse(const std::string & str) const; \ + template<> std::string BaseSetting< TY >::to_string() const; + +DECLARE_CONFIG_SERIALISER(std::string) +DECLARE_CONFIG_SERIALISER(std::optional<std::string>) +DECLARE_CONFIG_SERIALISER(bool) +DECLARE_CONFIG_SERIALISER(Strings) +DECLARE_CONFIG_SERIALISER(StringSet) +DECLARE_CONFIG_SERIALISER(StringMap) +DECLARE_CONFIG_SERIALISER(std::set<ExperimentalFeature>) + +template<typename T> +T BaseSetting<T>::parse(const std::string & str) const +{ + static_assert(std::is_integral<T>::value, "Integer required."); + + if (auto n = string2Int<T>(str)) + return *n; + else + throw UsageError("setting '%s' has invalid value '%s'", name, str); +} + +template<typename T> +std::string BaseSetting<T>::to_string() const +{ + static_assert(std::is_integral<T>::value, "Integer required."); + + return std::to_string(value); +} + } diff --git a/src/libutil/config.cc b/src/libutil/config.cc index 085a884dc..38d406e8a 100644 --- a/src/libutil/config.cc +++ b/src/libutil/config.cc @@ -219,29 +219,6 @@ void AbstractSetting::convertToArg(Args & args, const std::string & category) { } -template<typename T> -void BaseSetting<T>::convertToArg(Args & args, const std::string & category) -{ - args.addFlag({ - .longName = name, - .description = fmt("Set the `%s` setting.", name), - .category = category, - .labels = {"value"}, - .handler = {[this](std::string s) { overridden = true; set(s); }}, - .experimentalFeature = experimentalFeature, - }); - - if (isAppendable()) - args.addFlag({ - .longName = "extra-" + name, - .description = fmt("Append to the `%s` setting.", name), - .category = category, - .labels = {"value"}, - .handler = {[this](std::string s) { overridden = true; set(s, true); }}, - .experimentalFeature = experimentalFeature, - }); -} - template<> std::string BaseSetting<std::string>::parse(const std::string & str) const { return str; @@ -252,21 +229,17 @@ template<> std::string BaseSetting<std::string>::to_string() const return value; } -template<typename T> -T BaseSetting<T>::parse(const std::string & str) const +template<> std::optional<std::string> BaseSetting<std::optional<std::string>>::parse(const std::string & str) const { - static_assert(std::is_integral<T>::value, "Integer required."); - if (auto n = string2Int<T>(str)) - return *n; + if (str == "") + return std::nullopt; else - throw UsageError("setting '%s' has invalid value '%s'", name, str); + return { str }; } -template<typename T> -std::string BaseSetting<T>::to_string() const +template<> std::string BaseSetting<std::optional<std::string>>::to_string() const { - static_assert(std::is_integral<T>::value, "Integer required."); - return std::to_string(value); + return value ? *value : ""; } template<> bool BaseSetting<bool>::parse(const std::string & str) const @@ -403,17 +376,27 @@ template class BaseSetting<StringSet>; template class BaseSetting<StringMap>; template class BaseSetting<std::set<ExperimentalFeature>>; -Path PathSetting::parse(const std::string & str) const +static Path parsePath(const AbstractSetting & s, const std::string & str) { - if (str == "") { - if (allowEmpty) - return ""; - else - throw UsageError("setting '%s' cannot be empty", name); - } else + if (str == "") + throw UsageError("setting '%s' is a path and paths cannot be empty", s.name); + else return canonPath(str); } +Path PathSetting::parse(const std::string & str) const +{ + return parsePath(*this, str); +} + +std::optional<Path> OptionalPathSetting::parse(const std::string & str) const +{ + if (str == "") + return std::nullopt; + else + return parsePath(*this, str); +} + bool GlobalConfig::set(const std::string & name, const std::string & value) { for (auto & config : *configRegistrations) diff --git a/src/libutil/config.hh b/src/libutil/config.hh index 2675baed7..cc8532587 100644 --- a/src/libutil/config.hh +++ b/src/libutil/config.hh @@ -353,21 +353,20 @@ public: /** * A special setting for Paths. These are automatically canonicalised * (e.g. "/foo//bar/" becomes "/foo/bar"). + * + * It is mandatory to specify a path; i.e. the empty string is not + * permitted. */ class PathSetting : public BaseSetting<Path> { - bool allowEmpty; - public: PathSetting(Config * options, - bool allowEmpty, const Path & def, const std::string & name, const std::string & description, const std::set<std::string> & aliases = {}) : BaseSetting<Path>(def, true, name, description, aliases) - , allowEmpty(allowEmpty) { options->addSetting(this); } @@ -379,6 +378,30 @@ public: void operator =(const Path & v) { this->assign(v); } }; +/** + * Like `PathSetting`, but the absence of a path is also allowed. + * + * `std::optional` is used instead of the empty string for clarity. + */ +class OptionalPathSetting : public BaseSetting<std::optional<Path>> +{ +public: + + OptionalPathSetting(Config * options, + const std::optional<Path> & def, + const std::string & name, + const std::string & description, + const std::set<std::string> & aliases = {}) + : BaseSetting<std::optional<Path>>(def, true, name, description, aliases) + { + options->addSetting(this); + } + + std::optional<Path> parse(const std::string & str) const override; + + void operator =(const std::optional<Path> & v) { this->assign(v); } +}; + struct GlobalConfig : public AbstractConfig { typedef std::vector<Config*> ConfigRegistrations; |