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 772ee226e6e991514d5f4e9bcb32c1331833eb53
parent 7742f7e991646415702b55e4008c4bdf4a18a161
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Mon, 26 May 2025 15:50:04 +0200

Better handling for positional arguments

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

4 files changed, 99 insertions(+), 57 deletions(-)


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

@@ -26,9 +26,7 @@ public:


int main()
{
using poafloc::direct;
using poafloc::boolean;
using poafloc::parser;
using namespace poafloc; // NOLINT

auto program = parser<arguments> {
direct {

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

@@ -12,7 +12,8 @@ namespace poafloc


#define ENUM_ERROR \
invalid_option, invalid_positional, invalid_terminal, missing_argument, \
superfluous_argument, unknown_option, duplicate_option
missing_positional, superfluous_argument, superfluous_positional, \
unknown_option, duplicate_option
BASED_DECLARE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)
BASED_DEFINE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)
#undef ENUM_ERROR

@@ -28,8 +29,12 @@ static constexpr const char* error_get_message(error_code::enum_type error)

return "Invalid positional argument";
case error_code::missing_argument():
return "Missing argument for option: {}";
case error_code::missing_positional():
return "Too little positional arguments, require: {}";
case error_code::superfluous_argument():
return "Option doesn't require an argument: {}";
case error_code::superfluous_positional():
return "Too few positional arguments, require: {}";
case error_code::unknown_option():
return "Unknown option: {}";
case error_code::duplicate_option():

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

@@ -30,6 +30,7 @@ class option

public:
enum class type : based::bu8
{
argument,
direct,
optional,
boolean,

@@ -51,6 +52,23 @@ protected:

{
}

template<class Type, class Member = Type Record::*>
static auto create(Member member)
{
return [member](Record& record, std::string_view value)
{
if constexpr (std::is_invocable_v<Member, Record, std::string_view>) {
std::invoke(member, record, value);
} else if constexpr (std::is_invocable_v<Member, Record, Type>) {
std::invoke(member, record, convert<Type>(value));
} else if constexpr (std::is_assignable_v<Type, std::string_view>) {
std::invoke(member, record) = value;
} else {
std::invoke(member, record) = convert<Type>(value);
}
};
}

template<class T>
static T convert(std::string_view value)
{

@@ -76,31 +94,28 @@ public:


template<class Record, class Type>
requires(!std::same_as<bool, Type>)
class direct : public detail::option<Record>
class argument : public detail::option<Record>
{
using base = detail::option<Record>;
using member_type = Type Record::*;

static auto create(member_type member)
public:
explicit argument(std::string_view name, member_type member)
: base(base::type::argument, name, base::template create<Type>(member))
{
return [member](Record& record, std::string_view value)
{
if constexpr (std::is_invocable_v<member_type, Record, std::string_view>)
{
std::invoke(member, record, value);
} 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 {
std::invoke(member, record) = base::template convert<Type>(value);
}
};
}
};

template<class Record, class Type>
requires(!std::same_as<bool, Type>)
class direct : public detail::option<Record>
{
using base = detail::option<Record>;
using member_type = Type Record::*;

public:
direct(std::string_view opts, member_type member)
: base(base::type::direct, opts, create(member))
explicit direct(std::string_view opts, member_type member)
: base(base::type::direct, opts, base::template create<Type>(member))
{
}
};

@@ -121,7 +136,7 @@ class boolean : public detail::option<Record>

}

public:
boolean(std::string_view opts, member_type member)
explicit boolean(std::string_view opts, member_type member)
: base(base::type::boolean, opts, create(member))
{
}

@@ -306,12 +321,11 @@ public:

} // namespace detail

template<class Record>
class parser : public std::vector<std::string>
class parser
{
using positional = std::vector<std::string>;

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

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

@@ -328,6 +342,11 @@ class parser : public std::vector<std::string>


void process(const detail::option<Record>& option, std::string_view opts)
{
if (option.type() == option_t::type::argument) {
m_args.emplace_back(option);
return;
}

auto istr = std::istringstream(std::string(opts));
std::string str;

@@ -468,9 +487,12 @@ public:

(process(args, args.opts()), ...);
}

void operator()(Record& record, const char* argc, int argv)
void operator()(Record& record, int argc, const char** argv)
{
operator()(record, std::span(argc, argv));
std::vector<std::string_view> args(
argv + 1, argv + argc // NOLINT(*pointer*)
);
operator()(record, args);
}

void operator()(Record& record, std::span<std::string_view> args)

@@ -511,15 +533,26 @@ public:

}
}

std::size_t count = 0;
for (; arg_idx != std::size(args); ++arg_idx) {
const auto arg = args[arg_idx];
if (!terminal && arg == "--") {
throw error<error_code::invalid_terminal>(arg);
}

if (!terminal && is_option(arg)) {
throw error<error_code::invalid_positional>(arg);
}
positional::emplace_back(arg);

if (count == m_args.size()) {
throw error<error_code::superfluous_positional>(m_args.size());
}

m_args[count++](record, arg);
}

if (count != m_args.size()) {
throw error<error_code::missing_positional>(m_args.size());
}
}
};

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

@@ -7,11 +7,7 @@


#include "poafloc/poafloc.hpp"

using poafloc::boolean;
using poafloc::direct;
using poafloc::error;
using poafloc::error_code;
using poafloc::parser;
using namespace poafloc; // NOLINT

// NOLINTBEGIN(*complexity*)
TEST_CASE("invalid", "[poafloc/parser]")

@@ -395,33 +391,44 @@ TEST_CASE("positional", "[poafloc/parser]")

{
bool flag = false;
int value = 0;
std::string one;
std::string two;
} args;

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

SECTION("empty")
{
std::vector<std::string_view> cmdline = {};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(program.empty());
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_positional>);
}

SECTION("one")
{
std::vector<std::string_view> cmdline = {"one"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(program[0] == "one");
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_positional>);
REQUIRE(args.one == "one");
}

SECTION("two")
{
std::vector<std::string_view> cmdline = {"one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "two");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}

SECTION("three")
{
std::vector<std::string_view> cmdline = {"one", "two", "three"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::superfluous_positional>);
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}

SECTION("flag short")

@@ -429,8 +436,8 @@ TEST_CASE("positional", "[poafloc/parser]")

std::vector<std::string_view> cmdline = {"-f", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag == true);
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "two");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}

SECTION("flag long")

@@ -438,8 +445,8 @@ TEST_CASE("positional", "[poafloc/parser]")

std::vector<std::string_view> cmdline = {"--flag", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag == true);
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "two");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}

SECTION("value short")

@@ -447,8 +454,8 @@ TEST_CASE("positional", "[poafloc/parser]")

std::vector<std::string_view> cmdline = {"-v", "135", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "two");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}

SECTION("value short together")

@@ -456,8 +463,8 @@ TEST_CASE("positional", "[poafloc/parser]")

std::vector<std::string_view> cmdline = {"-v135", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "two");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}

SECTION("value short together")

@@ -465,8 +472,8 @@ TEST_CASE("positional", "[poafloc/parser]")

std::vector<std::string_view> cmdline = {"-v=135", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "two");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}

SECTION("value long")

@@ -474,8 +481,8 @@ TEST_CASE("positional", "[poafloc/parser]")

std::vector<std::string_view> cmdline = {"--value", "135", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "two");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}

SECTION("value long equal")

@@ -483,17 +490,16 @@ TEST_CASE("positional", "[poafloc/parser]")

std::vector<std::string_view> cmdline = {"--value=135", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "two");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}

SECTION("flag short terminal")
{
std::vector<std::string_view> cmdline = {"--", "one", "-f", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "-f");
REQUIRE(program[2] == "two");
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::superfluous_positional>);
REQUIRE(args.one == "one");
REQUIRE(args.two == "-f");
}

SECTION("invalid terminal")