Proper support for multiple values @ ConfigDefinition

pull/1186/head
Stephen Shelton 4 years ago
parent 28c1ca9c7a
commit 0a9515a94a
No known key found for this signature in database
GPG Key ID: EE4BADACCE8B631C

@ -98,14 +98,14 @@ ConfigDefinition::validateRequiredFields()
{ {
visitDefinitions(section, [&](const std::string&, const OptionDefinition_ptr& def) visitDefinitions(section, [&](const std::string&, const OptionDefinition_ptr& def)
{ {
if (def->required and def->numFound < 1) if (def->required and def->getNumberFound() < 1)
{ {
throw std::invalid_argument(stringify( throw std::invalid_argument(stringify(
"[", section, "]:", def->name, " is required but missing")); "[", section, "]:", def->name, " is required but missing"));
} }
// should be handled earlier in OptionDefinition::parseValue() // should be handled earlier in OptionDefinition::parseValue()
assert(def->numFound <= 1 or def->multiValued); assert(def->getNumberFound() <= 1 or def->multiValued);
}); });
}); });
} }
@ -176,7 +176,7 @@ ConfigDefinition::generateINIConfig(bool useValues)
oss << "# " << comment << "\n"; oss << "# " << comment << "\n";
} }
if (useValues and def->numFound > 0) if (useValues and def->getNumberFound() > 0)
{ {
oss << name << "=" << def->valueAsString(false) << "\n"; oss << name << "=" << def->valueAsString(false) << "\n";
} }

@ -42,6 +42,12 @@ namespace llarp
virtual void virtual void
parseValue(const std::string& input) = 0; parseValue(const std::string& input) = 0;
/// Subclasses should provide the number of values found.
///
/// @return number of values found
virtual size_t
getNumberFound() const = 0;
/// Subclasess should write their parsed value as a string, optionally falling back to any /// Subclasess should write their parsed value as a string, optionally falling back to any
/// specified default if `useDefault` is true. /// specified default if `useDefault` is true.
/// ///
@ -59,7 +65,6 @@ namespace llarp
std::string name; std::string name;
bool required = false; bool required = false;
bool multiValued = false; bool multiValued = false;
size_t numFound = 0;
}; };
/// The primary type-aware implementation of OptionDefinitionBase, this templated class allows /// The primary type-aware implementation of OptionDefinitionBase, this templated class allows
@ -105,21 +110,45 @@ namespace llarp
{ {
} }
/// Returns the parsed value, if available. Otherwise, provides the default value if the option /// Returns the first parsed value, if available. Otherwise, provides the default value if the
/// is not required. Otherwise, returns an empty optional. /// option is not required. Otherwise, returns an empty optional.
/// ///
/// @return an optional with the parsed value, the default value, or no value. /// @return an optional with the parsed value, the default value, or no value.
nonstd::optional<T> nonstd::optional<T>
getValue() const getValue() const
{ {
if (parsedValue) if (parsedValues.size())
return parsedValue.value(); return parsedValues[0];
else if (not required) else if (not required and not multiValued)
return defaultValue.value(); return defaultValue.value();
else else
return {}; return {};
} }
/// Returns the value at the given index.
///
/// @param index
/// @return the value at the given index, if it exists
/// @throws range_error exception if index >= size
T
getValueAt(size_t index) const
{
if (index >= parsedValues.size())
throw std::range_error(stringify(
"no value at index ", index, ", size: ", parsedValues.size()));
return parsedValues[index];
}
/// Returns the number of values found.
///
/// @return number of values found
size_t
getNumberFound() const override
{
return parsedValues.size();
}
std::string std::string
defaultValueAsString() override defaultValueAsString() override
{ {
@ -133,10 +162,10 @@ namespace llarp
void void
parseValue(const std::string& input) override parseValue(const std::string& input) override
{ {
if (not multiValued and parsedValue.has_value()) if (not multiValued and parsedValues.size() > 0)
{ {
throw std::invalid_argument(stringify("duplicate value for ", name, throw std::invalid_argument(stringify("duplicate value for ", name,
", previous value: ", parsedValue.value())); ", previous value: ", parsedValues[0]));
} }
std::istringstream iss(input); std::istringstream iss(input);
@ -148,8 +177,7 @@ namespace llarp
} }
else else
{ {
parsedValue = t; parsedValues.emplace_back(std::move(t));
numFound++;
} }
} }
@ -158,8 +186,8 @@ namespace llarp
valueAsString(bool useDefault) override valueAsString(bool useDefault) override
{ {
std::ostringstream oss; std::ostringstream oss;
if (parsedValue.has_value()) if (parsedValues.size() > 0)
oss << parsedValue.value(); oss << parsedValues[0];
else if (useDefault and defaultValue.has_value()) else if (useDefault and defaultValue.has_value())
oss << defaultValue.value(); oss << defaultValue.value();
@ -174,23 +202,37 @@ namespace llarp
void void
tryAccept() const override tryAccept() const override
{ {
if (required and not parsedValue.has_value()) if (required and parsedValues.size() == 0)
{ {
throw std::runtime_error(stringify("cannot call tryAccept() on [", throw std::runtime_error(stringify("cannot call tryAccept() on [",
section, "]:", name, " when required but no value available")); section, "]:", name, " when required but no value available"));
} }
// don't use default value if we are multi-valued and have no value
if (multiValued && parsedValues.size() == 0)
return;
if (acceptor) if (acceptor)
{ {
auto maybe = getValue(); if (multiValued)
assert(maybe.has_value()); // should be guaranteed by our earlier check {
// TODO: avoid copies here if possible for (const auto& value : parsedValues)
acceptor(maybe.value()); {
acceptor(value);
}
}
else
{
auto maybe = getValue();
assert(maybe.has_value()); // should be guaranteed by our earlier checks
// TODO: avoid copies here if possible
acceptor(maybe.value());
}
} }
} }
nonstd::optional<T> defaultValue; nonstd::optional<T> defaultValue;
nonstd::optional<T> parsedValue; // needs to be set when parseValue() called std::vector<T> parsedValues;
std::function<void(T)> acceptor; std::function<void(T)> acceptor;
}; };
@ -248,7 +290,7 @@ namespace llarp
/// called). /// called).
/// ///
/// If the specified option doesn't exist, an exception will be thrown. Otherwise, the option's /// If the specified option doesn't exist, an exception will be thrown. Otherwise, the option's
/// parsedValue() will be invoked, and should throw an exception if the string can't be parsed. /// parseValue() will be invoked, and should throw an exception if the string can't be parsed.
/// ///
/// @param section is the section this value resides in /// @param section is the section this value resides in
/// @param name is the name of the value /// @param name is the name of the value

@ -10,13 +10,13 @@ TEST_CASE("OptionDefinition int parse test", "[config]")
llarp::OptionDefinition<int> def("foo", "bar", false, 42); llarp::OptionDefinition<int> def("foo", "bar", false, 42);
CHECK(def.getValue() == 42); CHECK(def.getValue() == 42);
CHECK(def.numFound == 0); CHECK(def.getNumberFound() == 0);
CHECK(def.defaultValueAsString() == "42"); CHECK(def.defaultValueAsString() == "42");
CHECK_NOTHROW(def.parseValue("43")); CHECK_NOTHROW(def.parseValue("43"));
CHECK(def.getValue() == 43); CHECK(def.getValue() == 43);
CHECK(def.numFound == 1); CHECK(def.getNumberFound() == 1);
CHECK(def.defaultValueAsString() == "42"); CHECK(def.defaultValueAsString() == "42");
} }
@ -30,7 +30,7 @@ TEST_CASE("OptionDefinition string parse test", "[config]")
CHECK_NOTHROW(def.parseValue("foo")); CHECK_NOTHROW(def.parseValue("foo"));
CHECK(def.getValue() == "foo"); CHECK(def.getValue() == "foo");
CHECK(def.numFound == 1); CHECK(def.getNumberFound() == 1);
} }
TEST_CASE("OptionDefinition multiple parses test", "[config]") TEST_CASE("OptionDefinition multiple parses test", "[config]")
@ -41,12 +41,13 @@ TEST_CASE("OptionDefinition multiple parses test", "[config]")
CHECK_NOTHROW(def.parseValue("9")); CHECK_NOTHROW(def.parseValue("9"));
CHECK(def.getValue() == 9); CHECK(def.getValue() == 9);
CHECK(def.numFound == 1); CHECK(def.getNumberFound() == 1);
// should allow since it is multi-value // should allow since it is multi-value
CHECK_NOTHROW(def.parseValue("12")); CHECK_NOTHROW(def.parseValue("12"));
CHECK(def.getValue() == 12); CHECK(def.getValue() == 9); // getValue() should return first value
CHECK(def.numFound == 2); REQUIRE(def.getNumberFound() == 2);
} }
{ {
@ -54,11 +55,11 @@ TEST_CASE("OptionDefinition multiple parses test", "[config]")
CHECK_NOTHROW(def.parseValue("3")); CHECK_NOTHROW(def.parseValue("3"));
CHECK(def.getValue() == 3); CHECK(def.getValue() == 3);
CHECK(def.numFound == 1); CHECK(def.getNumberFound() == 1);
// shouldn't allow since not multi-value // shouldn't allow since not multi-value
CHECK_THROWS(def.parseValue("2")); CHECK_THROWS(def.parseValue("2"));
CHECK(def.numFound == 1); CHECK(def.getNumberFound() == 1);
} }
} }
@ -342,3 +343,23 @@ TEST_CASE("ConfigDefinition AssignmentAcceptor", "[config]")
REQUIRE(val == 3); REQUIRE(val == 3);
} }
TEST_CASE("ConfigDefinition multiple values", "[config]")
{
llarp::ConfigDefinition config;
std::vector<int> values;
config.defineOption<int>("foo", "bar", false, true, 2, [&](int arg) {
values.push_back(arg);
});
config.addConfigValue("foo", "bar", "1");
config.addConfigValue("foo", "bar", "2");
config.addConfigValue("foo", "bar", "3");
CHECK_NOTHROW(config.acceptAllOptions());
REQUIRE(values.size() == 3);
CHECK(values[0] == 1);
CHECK(values[1] == 2);
CHECK(values[2] == 3);
}

Loading…
Cancel
Save