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 6565e520e7a3acb45284c2c28afb345758702871
parent e7c1d542664675380159dd485031ee0399add06d
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Tue, 27 May 2025 10:09:05 +0200

Handle lists, handler restructure

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

3 files changed, 245 insertions(+), 58 deletions(-)


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

@@ -154,6 +154,24 @@ public:

}
};

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

public:
using record_type = Record;

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

namespace detail
{

@@ -219,6 +237,11 @@ struct is_option<boolean<Record>> : based::true_type

{
};

template<class Record, class Type>
struct is_option<list<Record, Type>> : based::true_type
{
};

template<class T>
concept IsOption = is_option<T>::value;

@@ -325,18 +348,12 @@ class parser_base

[[nodiscard]] const option& get_option(char opt) const;
[[nodiscard]] const option& get_option(std::string_view opt) const;

enum class handle_res : std::uint8_t
{
next,
ok,
};

handle_res handle_long_opt(
void* record, std::string_view arg, std::span<std::string_view> next
) const;
using next_t = std::span<std::string_view>;

handle_res handle_short_opts(
void* record, std::string_view arg, std::span<std::string_view> next
next_t hdl_long_opt(void* record, std::string_view arg, next_t next) const;
next_t hdl_short_opts(void* record, std::string_view arg, next_t next) const;
next_t hdl_short_opt(
void* record, char opt, std::string_view rest, next_t next
) const;

protected:

diff --git a/ source/poafloc.cpp b/ source/poafloc.cpp

@@ -12,7 +12,7 @@ constexpr bool is_option(std::string_view arg)


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

} // namespace

@@ -55,7 +55,7 @@ void parser_base::operator()(void* record, std::span<std::string_view> args)

std::size_t arg_idx = 0;
bool terminal = false;

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

if (!::is_option(arg_raw)) {

@@ -72,18 +72,11 @@ void parser_base::operator()(void* record, std::span<std::string_view> args)

break;
}

const auto next = args.subspan(arg_idx + 1);
const auto res = arg_raw[1] != '-'
? handle_short_opts(
record, arg_raw.substr(1), args.subspan(arg_idx + 1)
)
: handle_long_opt(record, arg_raw.substr(2), args.subspan(arg_idx + 1));
switch (res) {
case handle_res::ok:
break;
case handle_res::next:
arg_idx++;
break;
}
? hdl_short_opts(record, arg_raw.substr(1), next)
: hdl_long_opt(record, arg_raw.substr(2), next);
arg_idx = std::size(args) - std::size(res);
}

std::size_t count = 0;

@@ -109,48 +102,68 @@ void parser_base::operator()(void* record, std::span<std::string_view> args)

}
}

parser_base::handle_res parser_base::handle_short_opts(
void* record, std::string_view arg, std::span<std::string_view> next
parser_base::next_t parser_base::hdl_short_opt(
void* record, char opt, std::string_view rest, next_t next
) const
{
for (std::size_t opt_idx = 0; opt_idx < std::size(arg); opt_idx++) {
const auto opt = arg[opt_idx];
const auto option = get_option(opt);

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

const auto rest = arg.substr(opt_idx + 1);
if (rest.empty()) {
if (!next.empty()) {
option(record, next.front());
return handle_res::next;
}

throw error<error_code::missing_argument>(opt);
}
const auto option = get_option(opt);

if (!rest.empty()) {
if (rest.front() != '=') {
option(record, rest);
return handle_res::ok;
return next;
}

const auto value = rest.substr(1);
if (!value.empty()) {
option(record, value);
return handle_res::ok;
return next;
}

throw error<error_code::missing_argument>(opt);
}

return handle_res::ok;
if (is_next_option(next)) {
throw error<error_code::missing_argument>(opt);
}

if (option.type() != option::type::list) {
option(record, next.front());
return next.subspan(1);
}

while (!is_next_option(next)) {
option(record, next.front());
next = next.subspan(1);
}

return next;
}

parser_base::handle_res parser_base::handle_long_opt(
void* record, std::string_view arg, std::span<std::string_view> next
parser_base::next_t parser_base::hdl_short_opts(
void* record, std::string_view arg, next_t next
) const
{
std::size_t opt_idx = 0;
while (opt_idx < std::size(arg)) {
const auto opt = arg[opt_idx];
const auto option = get_option(opt);

if (option.type() != option::type::boolean) {
break;
}

option(record, "true");
opt_idx++;
}

return opt_idx == std::size(arg)
? next
: hdl_short_opt(record, arg[opt_idx], arg.substr(opt_idx + 1), next);
}

parser_base::next_t parser_base::hdl_long_opt(
void* record, std::string_view arg, next_t next
) const
{
const auto equal = arg.find('=');

@@ -166,25 +179,35 @@ parser_base::handle_res parser_base::handle_long_opt(


if (!value.empty()) {
option(record, value);
return handle_res::ok;
return next;
}

throw error<error_code::missing_argument>(opt);
}

const auto option = get_option(arg);
const auto opt = arg;
const auto option = get_option(opt);

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

if (is_next_option(next)) {
throw error<error_code::missing_argument>(opt);
}

if (option.type() != option::type::list) {
option(record, next.front());
return next.subspan(1);
}

if (!next.empty()) {
while (!is_next_option(next)) {
option(record, next.front());
return handle_res::next;
next = next.subspan(1);
}

throw error<error_code::missing_argument>(arg);
return next;
}

[[nodiscard]] const option& parser_base::get_option(char opt) const

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

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

}
}

TEST_CASE("flag", "[poafloc/parser]")
TEST_CASE("boolean", "[poafloc/parser]")
{
struct arguments
{

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

}
}

TEST_CASE("option string", "[poafloc/parser]")
TEST_CASE("direct string", "[poafloc/parser]")
{
struct arguments
{

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

}
}

TEST_CASE("option value", "[poafloc/parser]")
TEST_CASE("direct value", "[poafloc/parser]")
{
struct arguments
{

@@ -410,6 +410,153 @@ TEST_CASE("option value", "[poafloc/parser]")

}
}

TEST_CASE("list", "[poafloc/parser]")
{
struct arguments
{
void add(std::string_view value) { list.emplace_back(value); }

std::vector<std::string> list;
} args;

auto program = parser<arguments> {group {
"unnamed",
list {"l list", &arguments::add},
}};

SECTION("short empty")
{
std::vector<std::string_view> cmdline = {"-l"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
}

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

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

SECTION("short three")
{
std::vector<std::string_view> cmdline = {"-l", "one", "two", "three"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(std::size(args.list) == 3);
REQUIRE(args.list[0] == "one");
REQUIRE(args.list[1] == "two");
REQUIRE(args.list[2] == "three");
}

SECTION("short terminal")
{
std::vector<std::string_view> cmdline = {"-l", "--"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
}

SECTION("short terminal one")
{
std::vector<std::string_view> cmdline = {"-l", "one", "--"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(std::size(args.list) == 1);
REQUIRE(args.list[0] == "one");
}

SECTION("short terminal two")
{
std::vector<std::string_view> cmdline = {"-l", "one", "two", "--"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(std::size(args.list) == 2);
REQUIRE(args.list[0] == "one");
REQUIRE(args.list[1] == "two");
}

SECTION("short terminal three")
{
std::vector<std::string_view> cmdline = {"-l", "one", "two", "three", "--"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(std::size(args.list) == 3);
REQUIRE(args.list[0] == "one");
REQUIRE(args.list[1] == "two");
REQUIRE(args.list[2] == "three");
}

SECTION("long empty")
{
std::vector<std::string_view> cmdline = {"--list"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
}

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

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

SECTION("long three")
{
std::vector<std::string_view> cmdline = {"--list", "one", "two", "three"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(std::size(args.list) == 3);
REQUIRE(args.list[0] == "one");
REQUIRE(args.list[1] == "two");
REQUIRE(args.list[2] == "three");
}

SECTION("long terminal")
{
std::vector<std::string_view> cmdline = {"--list", "--"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
}

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

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

SECTION("long terminal three")
{
std::vector<std::string_view> cmdline = {"--list", "one", "two", "three", "--"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(std::size(args.list) == 3);
REQUIRE(args.list[0] == "one");
REQUIRE(args.list[1] == "two");
REQUIRE(args.list[2] == "three");
}
}

TEST_CASE("positional", "[poafloc/parser]")
{
struct arguments