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 8276d5c6b044d80e2f96ba4b0ac01956bf59ca85
parent 0ccb06358841f2d7b833bce0e5a5dca850597f1c
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Sat, 24 May 2025 10:27:28 +0200

Test suite for parser

* Rename error_t to error_code due to a conflict with std

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

4 files changed, 402 insertions(+), 22 deletions(-)


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

@@ -13,22 +13,22 @@ namespace poafloc

#define ENUM_ERROR \
invalid_char, missing_argument, superfluous_argument, unknown_option, \
duplicate_option
BASED_DECLARE_ENUM(error_t, based::bu8, 0, ENUM_ERROR)
BASED_DEFINE_ENUM(error_t, based::bu8, 0, ENUM_ERROR)
BASED_DECLARE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)
BASED_DEFINE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)
#undef ENUM_ERROR

static constexpr const char* error_get_message(error_t::enum_type error)
static constexpr const char* error_get_message(error_code::enum_type error)
{
switch (error()) {
case error_t::invalid_char():
case error_code::invalid_char():
return "Invalid char in option: {}";
case error_t::missing_argument():
case error_code::missing_argument():
return "Missing argument for option: {}";
case error_t::superfluous_argument():
case error_code::superfluous_argument():
return "Option doesn't require an argument: {}";
case error_t::unknown_option():
case error_code::unknown_option():
return "Unknown option: {}";
case error_t::duplicate_option():
case error_code::duplicate_option():
return "Duplicate option: {}";
default:
return "poafloc error, should not happen...";

@@ -44,7 +44,7 @@ public:

}
};

template<error_t::enum_type e>
template<error_code::enum_type e>
class error : public runtime_error
{
public:

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

@@ -143,7 +143,7 @@ struct option_base

static auto convert(char chr)
{
if (!is_valid(chr)) {
throw error<error_t::invalid_char>(chr);
throw error<error_code::invalid_char>(chr);
}

return mapping[static_cast<container_type::size_type>(

@@ -274,12 +274,12 @@ class parser

if (std::size(str) == 1) {
const auto& opt = str[0];
if (!m_opt_short.set(opt, std::size(m_options))) {
throw error<error_t::duplicate_option>(opt);
throw error<error_code::duplicate_option>(opt);
}
} else {
const auto& opt = str;
if (!m_opt_long.set(opt, std::size(m_options))) {
throw error<error_t::duplicate_option>(opt);
throw error<error_code::duplicate_option>(opt);
}
}
}

@@ -291,7 +291,7 @@ class parser

{
const auto idx = m_opt_short.get(opt);
if (!idx.has_value()) {
throw error<error_t::unknown_option>(opt);
throw error<error_code::unknown_option>(opt);
}
return m_options[idx.value()];
}

@@ -300,7 +300,7 @@ class parser

{
const auto idx = m_opt_long.get(opt);
if (!idx.has_value()) {
throw error<error_t::unknown_option>(opt);
throw error<error_code::unknown_option>(opt);
}
return m_options[idx.value()];
}

@@ -330,7 +330,7 @@ class parser

const auto option = get_option(opt);

if (!option.argument()) {
throw error<error_t::superfluous_argument>(opt);
throw error<error_code::superfluous_argument>(opt);
}

if (!value.empty()) {

@@ -338,7 +338,7 @@ class parser

return handle_res::ok;
}

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

const auto option = get_option(arg);

@@ -353,7 +353,7 @@ class parser

return handle_res::next;
}

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

handle_res handle_short_opts(

@@ -379,7 +379,7 @@ class parser

return handle_res::next;
}

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

return handle_res::ok;

@@ -409,7 +409,7 @@ public:

const auto arg_raw = args[arg_idx];

if (arg_raw.size() < 2) {
throw error<error_t::unknown_option>(arg_raw);
throw error<error_code::unknown_option>(arg_raw);
}

if (arg_raw == "--") {

diff --git a/ test/CMakeLists.txt b/ test/CMakeLists.txt

@@ -17,14 +17,16 @@ include(Catch)


# ---- Tests ----

function(add_test DIR NAME)
add_executable("${NAME}" "source/${DIR}/${NAME}.cpp")
target_link_libraries("${NAME}" PRIVATE based::based)
function(add_test NAME)
add_executable("${NAME}" "source/${NAME}.cpp")
target_link_libraries("${NAME}" PRIVATE poafloc::poafloc)
target_link_libraries("${NAME}" PRIVATE Catch2::Catch2WithMain)
target_compile_features("${NAME}" PRIVATE cxx_std_20)
catch_discover_tests("${NAME}")
endfunction()

add_test(parser)

# ---- End-of-file commands ----

add_folders(Test)

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

@@ -0,0 +1,378 @@

#define CATCH_CONFIG_RUNTIME_STATIC_REQUIRE

#include <string_view>
#include <vector>

#include <catch2/catch_test_macros.hpp>

#include "poafloc/poafloc.hpp"

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

// NOLINTBEGIN(*complexity*)
TEST_CASE("invalid", "[poafloc/parser]")
{
struct arguments
{
bool flag;
int value;
};

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

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

TEST_CASE("flag", "[poafloc/parser]")
{
struct arguments
{
bool flag = false;
} args;

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

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

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

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

SECTION("short unknown")
{
std::vector<std::string_view> cmdline = {"-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"};
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"};
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="};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::superfluous_argument>);
REQUIRE(args.flag == false);
}
}

TEST_CASE("option string", "[poafloc/parser]")
{
struct arguments
{
std::string name = "default";
} args;

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

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

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

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

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

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

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

SECTION("short missing")
{
std::vector<std::string_view> cmdline = {"-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"};
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"};
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="};
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="};
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"};
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"};
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"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.name == "default");
}
}

TEST_CASE("option value", "[poafloc/parser]")
{
struct arguments
{
int value = 0;
} args;

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

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

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

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

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

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

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

SECTION("short missing")
{
std::vector<std::string_view> cmdline = {"-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"};
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"};
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="};
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="};
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"};
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"};
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"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::unknown_option>);
REQUIRE(args.value == 0);
}
}

TEST_CASE("multiple", "[poafloc/parser]")
{
struct arguments
{
bool flag1 = false;
bool flag2 = false;
std::string value1 = "default";
std::string value2 = "default";
} args;

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

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

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

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

// NOLINTEND(*complexity*)