poafloc

Parser Of Arguments For Lines Of Commands
git clone git://git.dimitrijedobrota.com/poafloc.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit 7742f7e991646415702b55e4008c4bdf4a18a161
parent 8761a59a9b8b58395c8c52cd7d5fc0d21570d4d9
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Mon, 26 May 2025 14:17:05 +0200

Separate direct and boolean option types

Diffstat:
M example/example.cpp | ++++++++ -------
M include/poafloc/poafloc.hpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -------------------------
M test/source/parser.cpp | ++++++++++++++++++ -----------------

3 files changed, 119 insertions(+), 65 deletions(-)


diff --git a/ example/example.cpp b/ example/example.cpp

@@ -26,31 +26,32 @@ public:


int main()
{
using poafloc::option;
using poafloc::direct;
using poafloc::boolean;
using poafloc::parser;

auto program = parser<arguments> {
option {
direct {
"v value",
&arguments::val,
},
option {
direct {
"m multiply",
&arguments::mul,
},
option {
direct {
"n name",
&arguments::name,
},
option {
direct {
"p priv",
&arguments::set_priv,
},
option {
boolean {
"f flag1",
&arguments::flag1,
},
option {
boolean {
"F flag2",
&arguments::flag2,
},

diff --git a/ include/poafloc/poafloc.hpp b/ include/poafloc/poafloc.hpp

@@ -13,28 +13,58 @@

#include <string_view>
#include <vector>

#include <based/enum/enum.hpp>
#include <based/types/types.hpp>

#include "poafloc/error.hpp"

namespace poafloc
{

namespace detail
{

template<class Record>
class option_base
class option
{
public:
enum class type : based::bu8
{
direct,
optional,
boolean,
list,
};

private:
using func_t = std::function<void(Record&, std::string_view)>;

type m_type;
std::string m_opts;
func_t m_func;
bool m_arg;

protected:
explicit option_base(func_t func, bool argument)
: m_func(std::move(func))
, m_arg(argument)
explicit option(type type, std::string_view opts, func_t func)
: m_type(type)
, m_opts(opts)
, m_func(std::move(func))
{
}

template<class T>
static T convert(std::string_view value)
{
T tmp;
auto istr = std::istringstream(std::string(value));
istr >> tmp;
return tmp;
}

public:
[[nodiscard]] bool argument() const { return m_arg; }
using record_type = Record;

[[nodiscard]] const std::string& opts() const { return m_opts; }
[[nodiscard]] type type() const { return m_type; }

void operator()(Record& record, std::string_view value) const
{

@@ -42,11 +72,13 @@ public:

}
};

} // namespace detail

template<class Record, class Type>
class option : public option_base<Record>
requires(!std::same_as<bool, Type>)
class direct : public detail::option<Record>
{
std::string m_opts;

using base = detail::option<Record>;
using member_type = Type Record::*;

static auto create(member_type member)

@@ -56,39 +88,49 @@ class option : public option_base<Record>

if constexpr (std::is_invocable_v<member_type, Record, std::string_view>)
{
std::invoke(member, record, value);
} else if constexpr (std::same_as<bool, Type>) {
std::invoke(member, record) = true;
} else if constexpr (requires(Type tmp, std::string_view val) {
tmp = val;
})
{
} else if constexpr (std::is_invocable_v<member_type, Record, Type>) {
std::invoke(member, record, base::template convert<Type>(value));
} else if constexpr (std::is_assignable_v<Type, std::string_view>) {
std::invoke(member, record) = value;
} else {
auto istr = std::istringstream(std::string(value));
Type tmp;
istr >> tmp;
std::invoke(member, record) = tmp;
std::invoke(member, record) = base::template convert<Type>(value);
}
};
}

public:
using record_type = Record;
using value_type = Type;
direct(std::string_view opts, member_type member)
: base(base::type::direct, opts, create(member))
{
}
};

option(std::string_view opts, member_type member)
: option_base<Record>(create(member), !std::same_as<bool, Type>)
, m_opts(opts)
template<class Record>
class boolean : public detail::option<Record>
{
using base = detail::option<Record>;
using member_type = bool Record::*;

static auto create(member_type member)
{
return [member](Record& record, std::string_view value)
{
(void)value;
std::invoke(member, record) = true;
};
}

[[nodiscard]] const std::string& opts() const { return m_opts; }
public:
boolean(std::string_view opts, member_type member)
: base(base::type::boolean, opts, create(member))
{
}
};

namespace detail
{

struct option_base
struct option_lookup_base
{
using size_t = std::size_t;
using value_type = std::size_t;

@@ -106,7 +148,7 @@ struct option_base

}
};

class option_short : option_base
class option_short : option_lookup_base
{
static constexpr auto size = static_cast<size_t>(2 * 26);
std::array<value_type, size> m_opts = {};

@@ -161,7 +203,7 @@ public:

}
};

class option_long : option_base
class option_long : option_lookup_base
{
class trie_t
{

@@ -268,13 +310,23 @@ class parser : public std::vector<std::string>

{
using positional = std::vector<std::string>;

using option_t = option_base<Record>;
using option_t = detail::option<Record>;
std::vector<option_t> m_options;

detail::option_short m_opt_short;
detail::option_long m_opt_long;

void process(const option_base<Record>& option, std::string_view opts)
static constexpr bool is_option(std::string_view arg)
{
return arg.starts_with("-");
}

static constexpr bool is_next_option(std::span<std::string_view> args)
{
return !args.empty() && is_option(args.front());
}

void process(const detail::option<Record>& option, std::string_view opts)
{
auto istr = std::istringstream(std::string(opts));
std::string str;

@@ -338,7 +390,7 @@ class parser : public std::vector<std::string>


const auto option = get_option(opt);

if (!option.argument()) {
if (option.type() == option_t::type::boolean) {
throw error<error_code::superfluous_argument>(opt);
}

@@ -352,7 +404,7 @@ class parser : public std::vector<std::string>


const auto option = get_option(arg);

if (!option.argument()) {
if (option.type() == option_t::type::boolean) {
option(record, "true");
return handle_res::ok;
}

@@ -373,7 +425,7 @@ class parser : public std::vector<std::string>

const auto opt = arg[opt_idx];
const auto option = get_option(opt);

if (!option.argument()) {
if (option.type() == option_t::type::boolean) {
option(record, "true");
continue;
}

@@ -429,13 +481,7 @@ public:

for (; arg_idx != std::size(args); ++arg_idx) {
const auto arg_raw = args[arg_idx];

if (arg_raw == "--") {
terminal = true;
++arg_idx;
break;
}

if (arg_raw[0] != '-') {
if (!is_option(arg_raw)) {
break;
}

@@ -443,6 +489,12 @@ public:

throw error<error_code::unknown_option>("-");
}

if (arg_raw == "--") {
terminal = true;
++arg_idx;
break;
}

const auto res = arg_raw[1] != '-'
? handle_short_opts(
record, arg_raw.substr(1), args.subspan(arg_idx + 1)

@@ -464,7 +516,7 @@ public:

if (!terminal && arg == "--") {
throw error<error_code::invalid_terminal>(arg);
}
if (!terminal && (arg.starts_with("-") || arg.starts_with("--"))) {
if (!terminal && is_option(arg)) {
throw error<error_code::invalid_positional>(arg);
}
positional::emplace_back(arg);

diff --git a/ test/source/parser.cpp b/ test/source/parser.cpp

@@ -7,9 +7,10 @@


#include "poafloc/poafloc.hpp"

using poafloc::boolean;
using poafloc::direct;
using poafloc::error;
using poafloc::error_code;
using poafloc::option;
using poafloc::parser;

// NOLINTBEGIN(*complexity*)

@@ -26,7 +27,7 @@ TEST_CASE("invalid", "[poafloc/parser]")

auto construct = []()
{
return parser<arguments> {
option {"1", &arguments::flag},
boolean {"1", &arguments::flag},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);

@@ -37,7 +38,7 @@ TEST_CASE("invalid", "[poafloc/parser]")

auto construct = []()
{
return parser<arguments> {
option {"FLAG", &arguments::flag},
boolean {"FLAG", &arguments::flag},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);

@@ -48,7 +49,7 @@ TEST_CASE("invalid", "[poafloc/parser]")

auto construct = []()
{
return parser<arguments> {
option {"1value", &arguments::value},
direct {"1value", &arguments::value},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);

@@ -59,8 +60,8 @@ TEST_CASE("invalid", "[poafloc/parser]")

auto construct = []()
{
return parser<arguments> {
option {"f flag", &arguments::flag},
option {"f follow", &arguments::value},
boolean {"f flag", &arguments::flag},
direct {"f follow", &arguments::value},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::duplicate_option>);

@@ -71,8 +72,8 @@ TEST_CASE("invalid", "[poafloc/parser]")

auto construct = []()
{
return parser<arguments> {
option {"f flag", &arguments::flag},
option {"v flag", &arguments::value},
boolean {"f flag", &arguments::flag},
direct {"v flag", &arguments::value},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::duplicate_option>);

@@ -87,7 +88,7 @@ TEST_CASE("flag", "[poafloc/parser]")

} args;

auto program = parser<arguments> {
option {"f flag", &arguments::flag},
boolean {"f flag", &arguments::flag},
};

SECTION("short")

@@ -148,7 +149,7 @@ TEST_CASE("option string", "[poafloc/parser]")

} args;

auto program = parser<arguments> {
option {"n name", &arguments::name},
direct {"n name", &arguments::name},
};

SECTION("short")

@@ -272,7 +273,7 @@ TEST_CASE("option value", "[poafloc/parser]")

} args;

auto program = parser<arguments> {
option {"v value", &arguments::value},
direct {"v value", &arguments::value},
};

SECTION("short")

@@ -397,8 +398,8 @@ TEST_CASE("positional", "[poafloc/parser]")

} args;

auto program = parser<arguments> {
option {"f flag", &arguments::flag},
option {"v value", &arguments::value},
boolean {"f flag", &arguments::flag},
direct {"v value", &arguments::value},
};

SECTION("empty")

@@ -525,10 +526,10 @@ TEST_CASE("multiple", "[poafloc/parser]")

} args;

auto program = parser<arguments> {
option {"f flag1", &arguments::flag1},
option {"F flag2", &arguments::flag2},
option {"v value1", &arguments::value1},
option {"V value2", &arguments::value2},
boolean {"f flag1", &arguments::flag1},
boolean {"F flag2", &arguments::flag2},
direct {"v value1", &arguments::value1},
direct {"V value2", &arguments::value2},
};

SECTION("valid")