Skip to content

Commit

Permalink
customizable parser and vector delimiter
Browse files Browse the repository at this point in the history
  • Loading branch information
artpaul committed Apr 8, 2021
1 parent 0d46091 commit 8f3ce83
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 31 deletions.
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Below are a few of the features which cxxopts supports:
- Both short and long versions supported (i.e. `-o value`, `-ovalue` and `--option value` or `--option=value` respectively)
- Supports multiple values (i.e. `-o <val1> -o <val2>`)
- Supports delimited values (i.e. `--option=val1,val2,val3`)
- Supports custom parsers
- Supports vector of vector values (i.e `--opt=1,2,3 --opt=4,5,6`)
* **Groups**: Arguments can be made part of a group for the purposes of displaying help messages
* **Default Values**
- Supports default value from ENV variable
Expand Down Expand Up @@ -95,15 +97,21 @@ therefore, `-o false` does not work.
## Vector values

Parsing of list of values in form of an `std::vector<T>` is also supported, as long as `T`
can be parsed. To separate single values in a list the definition `CXXOPTS_VECTOR_DELIMITER`
is used, which is ',' by default. Ensure that you use no whitespaces between values because
those would be interpreted as the next command-line option. Example for a command-line option
can be parsed. To separate single values in a list the ',' is used by default.
Ensure that you use no whitespaces between values because those would be interpreted
as the next command-line option. Example for a command-line option
that can be parsed as a `std::vector<double>`:

~~~
--my_list=1,-2.1,3,4.5
~~~

To set up custom separation character (semicolon, for example) use:

```cpp
cxxopts::value<std::vector<std::string>>()->delimiter(';')
```
## Value from ENV variable
When a parameter is not set, a value will be fetched from an environment variable (if such variable is defined).
Expand Down Expand Up @@ -207,6 +215,21 @@ The string after the program name on the first line of the help can be
completely replaced by calling `options.custom_help`. Note that you might
also want to override the positional help by calling `options.positional_help`.

## Custom parsers

```cpp
template <>
struct cxxopts::value_parser<custom_type> {
using value_type = custom_type;
/// Is this a container type?
static constexpr bool is_container = <true> | <false>;

void parse(const cxxopts::parse_contex& ctx, const std::string& text, custom_type& value) {
// parse value from text here
}
};
```
# Command / subcommand pattern
In case then a program has some global options and specific sets of options
Expand Down
110 changes: 82 additions & 28 deletions include/cxxopts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ namespace cxxopts {
class options;
class parse_result;

template <typename T>
struct value_parser;

static constexpr struct {
uint8_t major, minor, patch;
} version = {
Expand Down Expand Up @@ -173,6 +176,19 @@ void throw_or_mimic(Args&& ... args) {

/**@}*/


/**
* \defgroup Values setup and parsing
* @{
*/

/**
* Settings for customizing parser behaviour.
*/
struct parse_context {
char delimiter{CXXOPTS_VECTOR_DELIMITER};
};

namespace detail {

#if defined(__GNUC__)
Expand Down Expand Up @@ -232,6 +248,13 @@ class value_base : public std::enable_shared_from_this<value_base> {
return shared_from_this();
}

/** Sets delimiter for list values. */
std::shared_ptr<value_base>
delimiter(const char del) {
parse_ctx_.delimiter = del;
return shared_from_this();
}

/** Sets env variable. */
std::shared_ptr<value_base>
env(const std::string& var) {
Expand Down Expand Up @@ -271,13 +294,13 @@ class value_base : public std::enable_shared_from_this<value_base> {
/** Parses the given text into the value. */
void
parse(const std::string& text) const {
return do_parse(text);
return do_parse(parse_ctx_, text);
}

/** Parses the default value. */
void
parse() const {
return do_parse(default_value_);
return do_parse(parse_ctx_, default_value_);
}

protected:
Expand All @@ -288,7 +311,7 @@ class value_base : public std::enable_shared_from_this<value_base> {
do_is_container() const noexcept = 0;

virtual void
do_parse(const std::string& text) const = 0;
do_parse(const parse_context& ctx, const std::string& text) const = 0;

void
set_default_and_implicit() {
Expand All @@ -304,6 +327,7 @@ class value_base : public std::enable_shared_from_this<value_base> {
std::string default_value_{};
std::string env_var_{};
std::string implicit_value_{};
parse_context parse_ctx_{};

bool default_{false};
bool env_{false};
Expand Down Expand Up @@ -364,22 +388,6 @@ parse_value(const std::string& text, T& value) {
stringstream_parser(text, value);
}

template <typename T>
void
parse_value(const std::string& text, std::vector<T>& value) {
if (text.empty()) {
value.push_back(T());
return;
}
std::stringstream in{text};
std::string token;
while (!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
T v;
parse_value(token, v);
value.emplace_back(std::move(v));
}
}

#ifdef CXXOPTS_HAS_OPTIONAL
template <typename T>
void
Expand All @@ -390,14 +398,9 @@ parse_value(const std::string& text, std::optional<T>& value) {
}
#endif

template <typename T>
struct is_container_type : std::false_type {};

template <typename T>
struct is_container_type<std::vector<T>> : std::true_type {};

template <typename T>
class basic_value : public value_base {
using parser_type = value_parser<T>;
public:
basic_value()
: result_(new T{})
Expand Down Expand Up @@ -425,12 +428,12 @@ class basic_value : public value_base {

bool
do_is_container() const noexcept final override {
return is_container_type<T>::value;
return parser_type::is_container;
}

void
do_parse(const std::string& text) const override {
parse_value(text, *store_);
do_parse(const parse_context& ctx, const std::string& text) const override {
parser_type().parse(ctx, text, *store_);
}

private:
Expand All @@ -444,6 +447,55 @@ class basic_value : public value_base {

} // namespace detail

/**
* A parser for values of type T.
*/
template <typename T>
struct value_parser {
using value_type = T;
/// By default, value can not act as container.
static constexpr bool is_container = false;

void parse(const parse_context&, const std::string& text, T& value) {
detail::parse_value(text, value);
}
};

template <typename T>
struct value_parser<std::vector<T>> {
using value_type = T;
/// Value of type std::vector<T> can act as container.
static constexpr bool is_container = true;

void parse(
const parse_context& ctx,
const std::string& text,
std::vector<T>& value)
{
using parser_type = value_parser<T>;

static_assert(!parser_type::is_container ||
!value_parser<typename parser_type::value_type>::is_container,
"dimensions of a container type should not exceed 2");

if (text.empty()) {
value.push_back(T());
} else if (parser_type::is_container) {
T v;
parser_type().parse(ctx, text, v);
value.emplace_back(std::move(v));
} else {
std::stringstream in{text};
std::string token;
while (!in.eof() && std::getline(in, token, ctx.delimiter)) {
T v;
parser_type().parse(ctx, token, v);
value.emplace_back(std::move(v));
}
}
}
};

/**
* Creates value holder for the specific type.
*/
Expand All @@ -462,6 +514,8 @@ value(T& t) {
return std::make_shared<detail::basic_value<T>>(&t);
}

/**@}*/

class option_details {
public:
option_details(
Expand Down
60 changes: 60 additions & 0 deletions test/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -998,3 +998,63 @@ TEST_CASE("Value from ENV variable", "[options]") {
// env not defined, should fallback to default value
CHECK(result["empty"].as<int>() == 1);
}

using char_pair = std::pair<char, char>;

template <>
struct cxxopts::value_parser<char_pair> {
static constexpr bool is_container = false;

void parse(const parse_context&, const std::string& text, char_pair& value) {
if (text.size() != 3) {
cxxopts::throw_or_mimic<argument_incorrect_type>(text, "char_pair");
}
value.first = text[0];
value.second = text[2];
}
};

TEST_CASE("Custom parser", "[parser]") {
cxxopts::options options("parser", " - test custom parser");
options.add_options()
("f,foo", "foo option", cxxopts::value<std::pair<char,char>>());

const Argv argv({"test", "--foo", "5=4"});
const auto result = options.parse(argv.argc(), argv.argv());
CHECK(result.count("foo") == 1);
CHECK(result["foo"].as<char_pair>().first == '5');
CHECK(result["foo"].as<char_pair>().second == '4');
}

TEST_CASE("Custom delimiter", "[parser]") {
cxxopts::options options("parser", " - test vector of vector");
options.add_options()
("test", "vector input", cxxopts::value<std::vector<std::string>>()->delimiter(';'));

const Argv argv({"test", "--test=a;b;c", "--test=x,y,z"});
const auto result = options.parse(argv.argc(), argv.argv());
const auto tests = result["test"].as<std::vector<std::string>>();

CHECK(result.count("test") == 2);
CHECK(tests.size() == 4);
CHECK(tests[0] == "a");
CHECK(tests[1] == "b");
CHECK(tests[2] == "c");
CHECK(tests[3] == "x,y,z");
}

TEST_CASE("Vector of vector", "[parser]") {
cxxopts::options options("parser", " - test vector of vector");
options.add_options()
("test", "vector input", cxxopts::value<std::vector<std::vector<float>>>());

const Argv argv({"test", "--test=10.0", "--test=10.0,10.0", "--test=10.0,10.0,10.0"});
const auto result = options.parse(argv.argc(), argv.argv());
const auto tests = result["test"].as<std::vector<std::vector<float>>>();

CHECK(result.count("test") == 3);
CHECK(tests.size() == 3);
CHECK(tests[0].size() == 1);
CHECK(tests[1].size() == 2);
CHECK(tests[2].size() == 3);
}

0 comments on commit 8f3ce83

Please sign in to comment.