poaflocParser 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
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