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 6751c0563a2a6a1ce35df9a3e16d771980cfb282
parent 11cd944de37c2e4e5a09673d68f317fc2676b22e
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Thu, 5 Jun 2025 05:43:57 +0200

Tie in help mechanism

* program name gets propagated while parsing
* help options are parsed
* help options are displayed
* cleaner code

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

7 files changed, 266 insertions(+), 160 deletions(-)


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

@@ -73,6 +73,7 @@ int main()

};

const std::vector<std::string_view> cmd_args {
"example",
"-m1.34",
"--name",
"Hello there!",

@@ -87,8 +88,29 @@ int main()


arguments args;

program.help_long();
program.help_short();
{
try {
const std::vector<std::string_view> help {
"example",
"-?",
};
program(args, help);
} catch (const error<error_code::help>& err) {
(void)err;
}
}

{
try {
const std::vector<std::string_view> usage {
"example",
"--usage",
};
program(args, usage);
} catch (const error<error_code::help>& err) {
(void)err;
}
}

std::cout << args << '\n';
program(args, cmd_args);

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

@@ -12,9 +12,10 @@ namespace poafloc

{

#define ENUM_ERROR \
invalid_option, invalid_positional, invalid_terminal, missing_option, \
missing_argument, missing_positional, superfluous_argument, \
superfluous_positional, unknown_option, duplicate_option
help, empty, invalid_option, invalid_positional, invalid_terminal, \
missing_option, missing_argument, 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

@@ -22,6 +23,10 @@ BASED_DEFINE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)

static constexpr const char* error_get_message(error_code::enum_type error)
{
switch (error()) {
case error_code::help():
return "Help text was displayed";
case error_code::empty():
return "Option list is empty";
case error_code::invalid_option():
return "Invalid option name: {}";
case error_code::invalid_positional():

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

@@ -1,6 +1,5 @@

#pragma once

#include <cstring>
#include <functional>
#include <initializer_list>
#include <memory>

@@ -43,12 +42,12 @@ public:

};

private:
using func_t = std::function<void(void*, std::string_view)>;
using func_type = std::function<void(void*, std::string_view)>;

using size_type = based::u64;

type m_type;
func_t m_func;
func_type m_func;

based::character m_opt_short;
std::string m_opt_long;

@@ -58,11 +57,14 @@ private:


protected:
// used for args
explicit option(type type, func_t func, std::string_view help = "");
explicit option(type opt_type, func_type func, std::string_view help);

// used for options
explicit option(
type type, std::string_view opts, func_t func, std::string_view help = ""
type opt_type,
std::string_view opts,
func_type func,
std::string_view help
);

template<class Record, class Type, class Member = Type Record::*>

@@ -166,7 +168,7 @@ public:

using rec_type = Record;

explicit direct(
std::string_view opts, member_type member, std::string_view help = ""
std::string_view opts, member_type member, std::string_view help
)
: base(
base::type::direct,

@@ -178,19 +180,27 @@ public:

}
};

template<class Record>
template<class Record, class Type>
class boolean : public detail::option
{
using base = detail::option;
using member_type = bool Record::*;
using member_type = Type Record::*;

static auto create(member_type member)
{
return [member](void* record_raw, std::string_view value)
{
(void)value;
auto* record = static_cast<Record*>(record_raw);
std::invoke(member, record) = true;
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, bool>) {
std::invoke(member, record, true);
} else if constexpr (std::is_assignable_v<Type, std::string_view>) {
std::invoke(member, record) = value;
} else {
std::invoke(member, record) = true;
}
};
}

@@ -198,7 +208,7 @@ public:

using rec_type = Record;

explicit boolean(
std::string_view opts, member_type member, std::string_view help = ""
std::string_view opts, member_type member, std::string_view help
)
: base(base::type::boolean, opts, create(member), help)
{

@@ -216,7 +226,7 @@ public:

using rec_type = Record;

explicit list(
std::string_view opts, member_type member, std::string_view help = ""
std::string_view opts, member_type member, std::string_view help
)
: base(
base::type::list,

@@ -308,8 +318,8 @@ struct is_option<direct<Record, Type>> : based::true_type

{
};

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

@@ -454,12 +464,20 @@ class parser_base


using next_t = std::span<const std::string_view>;

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_long_opt(
std::string_view program, void* record, std::string_view arg, next_t next
) const;
next_t hdl_short_opts(
std::string_view program, void* record, std::string_view arg, next_t next
) const;
next_t hdl_short_opt(
void* record, based::character opt, std::string_view rest, next_t next
) const;

void help_usage(std::string_view program) const;
[[nodiscard]] bool help_long(std::string_view program) const;
[[nodiscard]] bool help_short(std::string_view program) const;

protected:
template<class... Groups>
explicit parser_base(Groups&&... groups)

@@ -484,14 +502,24 @@ protected:

m_groups.emplace_back(m_options.size(), group.name());
};
(process(groups), ...);

process(group<parser_base> {
"Informational Options",
boolean {
"? help",
&parser_base::help_long,
"Give this help list",
},
boolean {
"usage",
&parser_base::help_short,
"Give a short usage message",
},
});
}

void operator()(void* record, int argc, const char** argv);
void operator()(void* record, std::span<const std::string_view> args);

void help_usage() const;
void help_long() const;
void help_short() const;
};

} // namespace detail

@@ -522,9 +550,6 @@ struct parser : detail::parser_base

{
}

using parser_base::help_long;
using parser_base::help_short;

void operator()(Record& record, int argc, const char** argv)
{
parser_base::operator()(&record, argc, argv);

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

@@ -16,11 +16,11 @@ std::string format_option_long(const option& option)

const auto& opt = option.opt_long();
switch (option.get_type()) {
case option::type::boolean:
return std::format("{}", opt);
return std::format("--{}", opt);
case option::type::list:
return std::format("{}={}...", opt, option.name());
return std::format("--{}={}...", opt, option.name());
default:
return std::format("{}={}", opt, option.name());
return std::format("--{}={}", opt, option.name());
}
}

@@ -29,19 +29,21 @@ std::string format_option_short(const option& option)

const auto& opt = option.opt_short();
switch (option.get_type()) {
case option::type::boolean:
return std::format("{}", opt);
return std::format("-{}", opt);
case option::type::list:
return std::format("{} {}...", opt, option.name());
return std::format("-{} {}...", opt, option.name());
default:
return std::format("{} {}", opt, option.name());
return std::format("-{} {}", opt, option.name());
}
}

} // namespace

void parser_base::help_usage() const
void parser_base::help_usage(std::string_view program) const
{
std::cerr << "Usage: program [OPTIONS]";
std::cerr << "Usage: ";
std::cerr << program;
std::cerr << " [OPTIONS]";
for (const auto& pos : m_pos) {
std::cerr << std::format(" {}", pos.name());
}

@@ -51,9 +53,9 @@ void parser_base::help_usage() const

std::cerr << '\n';
}

void parser_base::help_long() const
bool parser_base::help_long(std::string_view program) const
{
help_usage();
help_usage(program);

auto idx = size_type(0_u);
for (const auto& [end_idx, name] : m_groups) {

@@ -69,9 +71,9 @@ void parser_base::help_long() const

line += std::string(4, ' ');
}
if (opt.has_opt_long()) {
line += std::format(" --{},", format_option_long(opt));
line += " ";
line += format_option_long(opt);
}
line.pop_back(); // get rid of superfluous ','

static constexpr const auto zero = std::size_t {0};
static constexpr const auto mid = std::size_t {30};

@@ -82,9 +84,10 @@ void parser_base::help_long() const

}

std::cerr << '\n';
return true;
}

void parser_base::help_short() const
bool parser_base::help_short(std::string_view program) const
{
std::vector<std::string> opts_short;
std::vector<std::string> opts_long;

@@ -95,12 +98,12 @@ void parser_base::help_short() const

if (opt.get_type() == option::type::boolean) {
flags += opt.opt_short().chr();
} else {
opts_short.emplace_back(std::format("[-{}]", format_option_short(opt)));
opts_short.emplace_back(format_option_short(opt));
}
}

if (opt.has_opt_long()) {
opts_long.emplace_back(std::format("[--{}]", format_option_long(opt)));
opts_long.emplace_back(format_option_long(opt));
}
}

@@ -108,7 +111,7 @@ void parser_base::help_short() const

std::ranges::sort(opts_long);
std::ranges::sort(flags);

static const std::string_view usage = "Usage: ";
static const std::string_view usage = "Usage:";
std::cerr << usage << ' ';

std::string line;

@@ -123,17 +126,17 @@ void parser_base::help_short() const

line += data;
};

line += "program";
line += program;
if (!flags.empty()) {
line += std::format(" [-{}]", flags);
print(std::format("[-{}]", flags));
}

for (const auto& opt : opts_short) {
print(opt);
print(std::format("[{}]", opt));
}

for (const auto& opt : opts_long) {
print(opt);
print(std::format("[{}]", opt));
}

for (const auto& pos : m_pos) {

@@ -145,6 +148,7 @@ void parser_base::help_short() const

std::cerr << "...";
}
std::cerr << '\n' << '\n';
return true;
}

} // namespace poafloc::detail

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

@@ -18,7 +18,7 @@ struct short_map

{
constexpr bool operator()(based::character chr) const
{
return based::is_alpha(chr);
return based::is_alpha(chr) || chr == '?';
}
};

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

@@ -5,14 +5,14 @@

namespace
{

constexpr bool is_option(std::string_view arg)
constexpr bool is_option_str(std::string_view arg)
{
return arg.starts_with("-");
}

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

} // namespace

@@ -20,17 +20,20 @@ constexpr bool is_next_option(std::span<const std::string_view> args)

namespace poafloc::detail
{

option::option(option::type type, func_t func, std::string_view help)
: m_type(type)
option::option(option::type opt_type, func_type func, std::string_view help)
: m_type(opt_type)
, m_func(std::move(func))
, m_name(help)
{
}

option::option(
option::type type, std::string_view opts, func_t func, std::string_view help
option::type opt_type,
std::string_view opts,
func_type func,
std::string_view help
)
: m_type(type)
: m_type(opt_type)
, m_func(std::move(func))
{
auto istr = std::istringstream(std::string(opts));

@@ -48,7 +51,7 @@ option::option(

throw error<error_code::missing_option>();
}

if (type != option::type::boolean) {
if (opt_type != option::type::boolean) {
const auto pos = help.find(' ');
m_name = help.substr(0, pos);
m_message = help.substr(pos + 1);

@@ -79,7 +82,7 @@ void parser_base::process(const option& option)

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

@@ -88,13 +91,18 @@ void parser_base::operator()(

void* record, std::span<const std::string_view> args
)
{
std::size_t arg_idx = 0;
if (args.empty()) {
throw error<error_code::empty>();
}

const auto program = args[0];
std::size_t arg_idx = 1;
bool is_term = false;

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

if (!::is_option(arg_raw)) {
if (!is_option_str(arg_raw)) {
break;
}

@@ -110,8 +118,8 @@ void parser_base::operator()(


const auto next = args.subspan(arg_idx + 1);
const auto res = arg_raw[1] != '-'
? hdl_short_opts(record, arg_raw.substr(1), next)
: hdl_long_opt(record, arg_raw.substr(2), next);
? hdl_short_opts(program, record, arg_raw.substr(1), next)
: hdl_long_opt(program, record, arg_raw.substr(2), next);
arg_idx = std::size(args) - std::size(res);
}

@@ -122,7 +130,7 @@ void parser_base::operator()(

throw error<error_code::invalid_terminal>(arg);
}

if (!is_term && ::is_option(arg)) {
if (!is_term && is_option_str(arg)) {
throw error<error_code::invalid_positional>(arg);
}

@@ -181,19 +189,24 @@ parser_base::next_t parser_base::hdl_short_opt(

}

parser_base::next_t parser_base::hdl_short_opts(
void* record, std::string_view arg, next_t next
std::string_view program, 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 (opt == '?') {
(void)help_long(program);
throw error<error_code::help>();
}

const auto option = get_option(opt);
if (option.get_type() != option::type::boolean) {
break;
}

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

@@ -203,7 +216,7 @@ parser_base::next_t parser_base::hdl_short_opts(

}

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

@@ -225,10 +238,20 @@ parser_base::next_t parser_base::hdl_long_opt(

}

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

if (opt == "help") {
(void)help_long(program);
throw error<error_code::help>();
}

if (opt == "usage") {
(void)help_short(program);
throw error<error_code::help>();
}

const auto option = get_option(opt);
if (option.get_type() == option::type::boolean) {
option(record, "true");
option(record, program);
return next;
}

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

@@ -19,6 +19,19 @@ TEST_CASE("invalid", "[poafloc/parser]")

int value;
};

SECTION("empty")
{
auto program = parser<arguments> {
group {
"unnamed",
boolean {"f", &arguments::flag, "NUM something"},
},
};
arguments args = {};
std::vector<std::string_view> cmdline = {};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::empty>);
};

SECTION("short number")
{
auto construct = []()

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

return parser<arguments> {
group {
"unnamed",
boolean {"1", &arguments::flag},
boolean {"1", &arguments::flag, "NUM something"},
},
};
};

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

return parser<arguments> {
group {
"unnamed",
boolean {"FLAG", &arguments::flag},
boolean {"FLAG", &arguments::flag, "something"},
},
};
};

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

return parser<arguments> {
group {
"unnamed",
direct {"1value", &arguments::value},
direct {"1value", &arguments::value, "NUM something"},
},
};
};

@@ -68,8 +81,8 @@ TEST_CASE("invalid", "[poafloc/parser]")

return parser<arguments> {
group {
"unnamed",
boolean {"f flag", &arguments::flag},
direct {"f follow", &arguments::value},
boolean {"f flag", &arguments::flag, "something"},
direct {"f follow", &arguments::value, "NUM something"},
},
};
};

@@ -83,8 +96,8 @@ TEST_CASE("invalid", "[poafloc/parser]")

return parser<arguments> {
group {
"unnamed",
boolean {"f flag", &arguments::flag},
direct {"v flag", &arguments::value},
boolean {"f flag", &arguments::flag, "something"},
direct {"v flag", &arguments::value, "something"},
},
};
};

@@ -102,55 +115,55 @@ TEST_CASE("boolean", "[poafloc/parser]")

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

SECTION("short")
{
std::vector<std::string_view> cmdline = {"-f"};
std::vector<std::string_view> cmdline = {"test", "-f"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag == true);
}

SECTION("long")
{
std::vector<std::string_view> cmdline = {"--flag"};
std::vector<std::string_view> cmdline = {"test", "--flag"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag == true);
}

SECTION("long partial")
{
std::vector<std::string_view> cmdline = {"--fl"};
std::vector<std::string_view> cmdline = {"test", "--fl"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag == true);
}

SECTION("short unknown")
{
std::vector<std::string_view> cmdline = {"-u"};
std::vector<std::string_view> cmdline = {"test", "-u"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.flag == false);
}

SECTION("long unknown")
{
std::vector<std::string_view> cmdline = {"--unknown"};
std::vector<std::string_view> cmdline = {"test", "--unknown"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.flag == false);
}

SECTION("long superfluous")
{
std::vector<std::string_view> cmdline = {"--fl=something"};
std::vector<std::string_view> cmdline = {"test", "--fl=something"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::superfluous_argument>);
REQUIRE(args.flag == false);
}

SECTION("long superfluous missing")
{
std::vector<std::string_view> cmdline = {"--fl="};
std::vector<std::string_view> cmdline = {"test", "--fl="};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::superfluous_argument>);
REQUIRE(args.flag == false);
}

@@ -166,118 +179,118 @@ TEST_CASE("direct string", "[poafloc/parser]")

auto program = parser<arguments> {
group {
"unnamed",
direct {"n name", &arguments::name},
direct {"n name", &arguments::name, "NAME something"},
},
};

SECTION("short")
{
std::vector<std::string_view> cmdline = {"-n", "something"};
std::vector<std::string_view> cmdline = {"test", "-n", "something"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.name == "something");
}

SECTION("short equal")
{
std::vector<std::string_view> cmdline = {"-n=something"};
std::vector<std::string_view> cmdline = {"test", "-n=something"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.name == "something");
}

SECTION("short together")
{
std::vector<std::string_view> cmdline = {"-nsomething"};
std::vector<std::string_view> cmdline = {"test", "-nsomething"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.name == "something");
}

SECTION("long")
{
std::vector<std::string_view> cmdline = {"--name", "something"};
std::vector<std::string_view> cmdline = {"test", "--name", "something"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.name == "something");
}

SECTION("long partial")
{
std::vector<std::string_view> cmdline = {"--na", "something"};
std::vector<std::string_view> cmdline = {"test", "--na", "something"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.name == "something");
}

SECTION("long equal")
{
std::vector<std::string_view> cmdline = {"--name=something"};
std::vector<std::string_view> cmdline = {"test", "--name=something"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.name == "something");
}

SECTION("long equal partial")
{
std::vector<std::string_view> cmdline = {"--na=something"};
std::vector<std::string_view> cmdline = {"test", "--na=something"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.name == "something");
}

SECTION("short missing")
{
std::vector<std::string_view> cmdline = {"-n"};
std::vector<std::string_view> cmdline = {"test", "-n"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.name == "default");
}

SECTION("short equal missing")
{
std::vector<std::string_view> cmdline = {"-n="};
std::vector<std::string_view> cmdline = {"test", "-n="};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.name == "default");
}

SECTION("long missing")
{
std::vector<std::string_view> cmdline = {"--name"};
std::vector<std::string_view> cmdline = {"test", "--name"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.name == "default");
}

SECTION("long partial missing")
{
std::vector<std::string_view> cmdline = {"--na"};
std::vector<std::string_view> cmdline = {"test", "--na"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.name == "default");
}

SECTION("long equal missing")
{
std::vector<std::string_view> cmdline = {"--name="};
std::vector<std::string_view> cmdline = {"test", "--name="};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.name == "default");
}

SECTION("long equal partial missing")
{
std::vector<std::string_view> cmdline = {"--na="};
std::vector<std::string_view> cmdline = {"test", "--na="};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.name == "default");
}

SECTION("short unknown")
{
std::vector<std::string_view> cmdline = {"-u", "something"};
std::vector<std::string_view> cmdline = {"test", "-u", "something"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.name == "default");
}

SECTION("long unknown")
{
std::vector<std::string_view> cmdline = {"--unknown", "something"};
std::vector<std::string_view> cmdline = {"test", "--unknown", "something"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.name == "default");
}

SECTION("long equal unknown")
{
std::vector<std::string_view> cmdline = {"--unknown=something"};
std::vector<std::string_view> cmdline = {"test", "--unknown=something"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.name == "default");
}

@@ -293,118 +306,118 @@ TEST_CASE("direct value", "[poafloc/parser]")

auto program = parser<arguments> {
group {
"unnamed",
direct {"v value", &arguments::value},
direct {"v value", &arguments::value, "NUM something"},
},
};

SECTION("short")
{
std::vector<std::string_view> cmdline = {"-v", "135"};
std::vector<std::string_view> cmdline = {"test", "-v", "135"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
}

SECTION("short equal")
{
std::vector<std::string_view> cmdline = {"-v=135"};
std::vector<std::string_view> cmdline = {"test", "-v=135"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
}

SECTION("short together")
{
std::vector<std::string_view> cmdline = {"-v135"};
std::vector<std::string_view> cmdline = {"test", "-v135"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
}

SECTION("long")
{
std::vector<std::string_view> cmdline = {"--value", "135"};
std::vector<std::string_view> cmdline = {"test", "--value", "135"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
}

SECTION("long partial")
{
std::vector<std::string_view> cmdline = {"--val", "135"};
std::vector<std::string_view> cmdline = {"test", "--val", "135"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
}

SECTION("long equal")
{
std::vector<std::string_view> cmdline = {"--value=135"};
std::vector<std::string_view> cmdline = {"test", "--value=135"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
}

SECTION("long equal partial")
{
std::vector<std::string_view> cmdline = {"--val=135"};
std::vector<std::string_view> cmdline = {"test", "--val=135"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
}

SECTION("short missing")
{
std::vector<std::string_view> cmdline = {"-v"};
std::vector<std::string_view> cmdline = {"test", "-v"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.value == 0);
}

SECTION("short equal missing")
{
std::vector<std::string_view> cmdline = {"-v="};
std::vector<std::string_view> cmdline = {"test", "-v="};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.value == 0);
}

SECTION("long missing")
{
std::vector<std::string_view> cmdline = {"--value"};
std::vector<std::string_view> cmdline = {"test", "--value"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.value == 0);
}

SECTION("long partial missing")
{
std::vector<std::string_view> cmdline = {"--val"};
std::vector<std::string_view> cmdline = {"test", "--val"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.value == 0);
}

SECTION("long equal missing")
{
std::vector<std::string_view> cmdline = {"--value="};
std::vector<std::string_view> cmdline = {"test", "--value="};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.value == 0);
}

SECTION("long equal partial missing")
{
std::vector<std::string_view> cmdline = {"--val="};
std::vector<std::string_view> cmdline = {"test", "--val="};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_argument>);
REQUIRE(args.value == 0);
}

SECTION("short unknown")
{
std::vector<std::string_view> cmdline = {"-u", "135"};
std::vector<std::string_view> cmdline = {"test", "-u", "135"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.value == 0);
}

SECTION("long unknown")
{
std::vector<std::string_view> cmdline = {"--unknown", "135"};
std::vector<std::string_view> cmdline = {"test", "--unknown", "135"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.value == 0);
}

SECTION("long equal unknown")
{
std::vector<std::string_view> cmdline = {"--unknown=135"};
std::vector<std::string_view> cmdline = {"test", "--unknown=135"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.value == 0);
}

@@ -421,18 +434,18 @@ TEST_CASE("list", "[poafloc/parser]")


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

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

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

@@ -440,7 +453,7 @@ TEST_CASE("list", "[poafloc/parser]")


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

@@ -449,7 +462,9 @@ TEST_CASE("list", "[poafloc/parser]")


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

@@ -459,13 +474,13 @@ TEST_CASE("list", "[poafloc/parser]")


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

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

@@ -473,7 +488,7 @@ TEST_CASE("list", "[poafloc/parser]")


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

@@ -482,7 +497,9 @@ TEST_CASE("list", "[poafloc/parser]")


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

@@ -492,13 +509,13 @@ TEST_CASE("list", "[poafloc/parser]")


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

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

@@ -506,7 +523,7 @@ TEST_CASE("list", "[poafloc/parser]")


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

@@ -515,7 +532,9 @@ TEST_CASE("list", "[poafloc/parser]")


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

@@ -525,13 +544,13 @@ TEST_CASE("list", "[poafloc/parser]")


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

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

@@ -539,7 +558,9 @@ TEST_CASE("list", "[poafloc/parser]")


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

@@ -549,7 +570,7 @@ TEST_CASE("list", "[poafloc/parser]")

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

@@ -576,27 +597,29 @@ TEST_CASE("positional", "[poafloc/parser]")

},
group {
"unnamed",
boolean {"f flag", &arguments::flag},
direct {"v value", &arguments::value},
boolean {"f flag", &arguments::flag, "something"},
direct {"v value", &arguments::value, "NUM something"},
}
};

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

SECTION("one")
{
std::vector<std::string_view> cmdline = {"one"};
std::vector<std::string_view> cmdline = {"test", "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"};
std::vector<std::string_view> cmdline = {"test", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");

@@ -604,7 +627,7 @@ TEST_CASE("positional", "[poafloc/parser]")


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

@@ -612,7 +635,7 @@ TEST_CASE("positional", "[poafloc/parser]")


SECTION("flag short")
{
std::vector<std::string_view> cmdline = {"-f", "one", "two"};
std::vector<std::string_view> cmdline = {"test", "-f", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag == true);
REQUIRE(args.one == "one");

@@ -621,7 +644,7 @@ TEST_CASE("positional", "[poafloc/parser]")


SECTION("flag long")
{
std::vector<std::string_view> cmdline = {"--flag", "one", "two"};
std::vector<std::string_view> cmdline = {"test", "--flag", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag == true);
REQUIRE(args.one == "one");

@@ -630,7 +653,7 @@ TEST_CASE("positional", "[poafloc/parser]")


SECTION("value short")
{
std::vector<std::string_view> cmdline = {"-v", "135", "one", "two"};
std::vector<std::string_view> cmdline = {"test", "-v", "135", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(args.one == "one");

@@ -639,7 +662,7 @@ TEST_CASE("positional", "[poafloc/parser]")


SECTION("value short together")
{
std::vector<std::string_view> cmdline = {"-v135", "one", "two"};
std::vector<std::string_view> cmdline = {"test", "-v135", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(args.one == "one");

@@ -648,7 +671,7 @@ TEST_CASE("positional", "[poafloc/parser]")


SECTION("value short together")
{
std::vector<std::string_view> cmdline = {"-v=135", "one", "two"};
std::vector<std::string_view> cmdline = {"test", "-v=135", "one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(args.one == "one");

@@ -657,7 +680,9 @@ TEST_CASE("positional", "[poafloc/parser]")


SECTION("value long")
{
std::vector<std::string_view> cmdline = {"--value", "135", "one", "two"};
std::vector<std::string_view> cmdline = {
"test", "--value", "135", "one", "two"
};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(args.one == "one");

@@ -666,7 +691,9 @@ TEST_CASE("positional", "[poafloc/parser]")


SECTION("value long equal")
{
std::vector<std::string_view> cmdline = {"--value=135", "one", "two"};
std::vector<std::string_view> cmdline = {
"test", "--value=135", "one", "two"
};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.value == 135);
REQUIRE(args.one == "one");

@@ -675,7 +702,7 @@ TEST_CASE("positional", "[poafloc/parser]")


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

@@ -683,19 +710,19 @@ TEST_CASE("positional", "[poafloc/parser]")


SECTION("invalid terminal")
{
std::vector<std::string_view> cmdline = {"one", "--", "-f", "two"};
std::vector<std::string_view> cmdline = {"test", "one", "--", "-f", "two"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::invalid_terminal>);
}

SECTION("flag short non-terminal")
{
std::vector<std::string_view> cmdline = {"one", "-f", "two"};
std::vector<std::string_view> cmdline = {"test", "one", "-f", "two"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::invalid_positional>);
}

SECTION("flag long non-terminal")
{
std::vector<std::string_view> cmdline = {"one", "--flag", "two"};
std::vector<std::string_view> cmdline = {"test", "one", "--flag", "two"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::invalid_positional>);
}
}

@@ -713,16 +740,16 @@ TEST_CASE("multiple", "[poafloc/parser]")

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

SECTION("valid")
{
std::vector<std::string_view> cmdline = {"--flag1", "--flag2"};
std::vector<std::string_view> cmdline = {"test", "--flag1", "--flag2"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag1 == true);
REQUIRE(args.flag2 == true);

@@ -732,7 +759,7 @@ TEST_CASE("multiple", "[poafloc/parser]")


SECTION("partial overlap")
{
std::vector<std::string_view> cmdline = {"--fla", "--fla"};
std::vector<std::string_view> cmdline = {"test", "--fla", "--fla"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.flag1 == false);
REQUIRE(args.flag2 == false);

@@ -742,7 +769,7 @@ TEST_CASE("multiple", "[poafloc/parser]")


SECTION("together")
{
std::vector<std::string_view> cmdline = {"-fvF"};
std::vector<std::string_view> cmdline = {"test", "-fvF"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag1 == true);
REQUIRE(args.flag2 == false);

@@ -752,7 +779,7 @@ TEST_CASE("multiple", "[poafloc/parser]")


SECTION("together equal")
{
std::vector<std::string_view> cmdline = {"-fv=F"};
std::vector<std::string_view> cmdline = {"test", "-fv=F"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag1 == true);
REQUIRE(args.flag2 == false);

@@ -762,7 +789,7 @@ TEST_CASE("multiple", "[poafloc/parser]")


SECTION("together next")
{
std::vector<std::string_view> cmdline = {"-fv", "F"};
std::vector<std::string_view> cmdline = {"test", "-fv", "F"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(args.flag1 == true);
REQUIRE(args.flag2 == false);