Skip to content
58 changes: 58 additions & 0 deletions include/popl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,40 @@ class Switch : public Value<bool>
void parse(OptionName what_name, const char* value) override;
};

/// Bounded value option
/**
* Bounded value option
* Checks if the value meets boundary predicate,
* for example ( x > 3 && x < 99 )
*/
template <class T>
class BoundedValue : public Value<T>
{
public:
using Predicate = bool(*)(T);

/// Construct a BoundedValue Option
/// @param short_name the option's short name. Must be empty or one character.
/// @param long_name the option's long name. Can be empty.
/// @param description the Option's description that will be shown in the help message
/// @param predicate the option's correctness predicate, returns true if option is correct
BoundedValue(const std::string& short_name, const std::string& long_name, const std::string& description, Predicate predicate);

/// Construct a BoundedValue Option
/// @param short_name the option's short name. Must be empty or one character.
/// @param long_name the option's long name. Can be empty.
/// @param description the Option's description that will be shown in the help message
/// @param predicate the option's correctness predicate, returns true if option is correct
/// @param default_val the Option's default value
/// @param assign_to pointer to a variable to assign the parsed command line value to
BoundedValue(const std::string& short_name, const std::string& long_name, const std::string& description, Predicate predicate, const T& default_val, T* assign_to = nullptr);

protected:
void parse(OptionName what_name, const char* value) override;

private:
Predicate predicate_;
};

using Option_ptr = std::shared_ptr<Option>;

Expand Down Expand Up @@ -400,6 +433,7 @@ class invalid_option : public std::invalid_argument
missing_argument,
invalid_argument,
too_many_arguments,
argument_out_of_bound,
missing_option
};

Expand Down Expand Up @@ -838,7 +872,31 @@ inline Argument Switch::argument_type() const
return Argument::no;
}

/// BoundedValue implementation /////////////////////////////////

template <class T>
BoundedValue<T>::BoundedValue(const std::string& short_name, const std::string& long_name, const std::string& description, Predicate predicate)
: Value<T>(short_name, long_name, description), predicate_(predicate)
{

}

template <class T>
BoundedValue<T>::BoundedValue(const std::string& short_name, const std::string& long_name, const std::string& description, Predicate predicate, const T& default_val, T* assign_to)
: Value<T>(short_name, long_name, description, default_val, assign_to), predicate_(predicate)
{

}

template <class T>
inline void BoundedValue<T>::parse(OptionName what_name, const char* value)
{
Value<T>::parse(what_name, value);
for ( const auto& v : this->values_)
if ( !predicate_( v))
throw invalid_option(this, invalid_option::Error::argument_out_of_bound, what_name, value,
"argument is out of bound for " + this->name(what_name, true) + ": '" + value + "'");
}

/// OptionParser implementation /////////////////////////////////

Expand Down
22 changes: 22 additions & 0 deletions test/test_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ TEST_CASE("command line")
}
}

TEST_CASE("wrong predicated option")
{
OptionParser op("Allowed options");
std::vector<const char*> args = {"popl", "-i", "9"};

auto int_option = op.add<BoundedValue<int>>("i", "int", "test for int value, should be divisible by 8", [](int x){ return x % 8 == 0; }, 64);

CHECK_THROWS_AS(op.parse(args.size(), args.data()), invalid_option);
}

TEST_CASE("correct predicated option")
{
OptionParser op("Allowed options");
std::vector<const char*> args = {"popl", "-i", "64"};

auto int_option = op.add<BoundedValue<int>>("i", "int", "test for int value, should be divisible by 8", [](int x){ return x % 8 == 0; }, 9);

op.parse(args.size(), args.data());
REQUIRE(int_option->is_set() == true);
REQUIRE(int_option->count() == 1);
REQUIRE(int_option->value() == 64);
}

TEST_CASE("config file")
{
Expand Down