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 e1b1257101cf4f047291177b21caaa39fccb368c
parent 8276d5c6b044d80e2f96ba4b0ac01956bf59ca85
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Sun, 25 May 2025 20:04:21 +0200

Short options can have =, naming enforcement

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

3 files changed, 112 insertions(+), 19 deletions(-)


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

@@ -11,8 +11,8 @@ namespace poafloc

{

#define ENUM_ERROR \
invalid_char, missing_argument, superfluous_argument, unknown_option, \
duplicate_option
invalid_char, invalid_option, missing_argument, superfluous_argument, \
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 +22,8 @@ static constexpr const char* error_get_message(error_code::enum_type error)

switch (error()) {
case error_code::invalid_char():
return "Invalid char in option: {}";
case error_code::invalid_option():
return "Invalid option name: {}";
case error_code::missing_argument():
return "Missing argument for option: {}";
case error_code::superfluous_argument():

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

@@ -95,12 +95,17 @@ struct option_base


static constexpr const auto sentinel = value_type {0xFFFFFFFFFFFFFFFF};

static constexpr bool is_valid(char c)
static constexpr bool is_alnum(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9');
}

static constexpr bool is_alpha(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

static constexpr const auto size_char = 256;
static constexpr const auto size = []()
{

@@ -142,10 +147,6 @@ struct option_base


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

return mapping[static_cast<container_type::size_type>(
static_cast<unsigned char>(chr)
)];

@@ -156,6 +157,15 @@ class option_short : option_base

{
container_type m_opts = {};

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

return option_base::convert(chr);
}

[[nodiscard]] bool has(char chr) const
{
return m_opts[convert(chr)] != sentinel;

@@ -164,6 +174,8 @@ class option_short : option_base

public:
option_short() { m_opts.fill(sentinel); }

static bool is_valid(char chr) { return option_base::is_alpha(chr); }

[[nodiscard]] bool set(char chr, value_type idx)
{
if (has(chr)) {

@@ -186,6 +198,15 @@ public:


class option_long : option_base
{
static auto convert(char chr)
{
if (!is_alnum(chr)) {
throw error<error_code::invalid_char>(chr);
}

return option_base::convert(chr);
}

class trie_t
{
std::array<std::unique_ptr<trie_t>, size> m_children = {};

@@ -243,6 +264,11 @@ class option_long : option_base

trie_t m_trie;

public:
static bool is_valid(std::string_view opt)
{
return std::ranges::all_of(opt, option_base::is_alnum);
}

[[nodiscard]] bool set(std::string_view opt, value_type idx)
{
return trie_t::set(m_trie, opt, idx);

@@ -273,11 +299,21 @@ class parser

while (std::getline(istr, str, ' ')) {
if (std::size(str) == 1) {
const auto& opt = str[0];

if (!detail::option_short::is_valid(opt)) {
throw error<error_code::invalid_option>(opt);
}

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

if (!detail::option_long::is_valid(opt)) {
throw error<error_code::invalid_option>(opt);
}

if (!m_opt_long.set(opt, std::size(m_options))) {
throw error<error_code::duplicate_option>(opt);
}

@@ -349,7 +385,7 @@ class parser

}

if (!next.empty()) {
option(record, *std::begin(next));
option(record, next.front());
return handle_res::next;
}

@@ -369,14 +405,25 @@ class parser

continue;
}

if (opt_idx + 1 != std::size(arg)) {
option(record, arg.substr(opt_idx + 1));
break;
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);
}

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

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

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

@@ -408,14 +455,10 @@ public:

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

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

if (arg_raw == "--") {
break;
}
if (arg_raw[0] != '-') {
if (arg_raw[0] != '-' || arg_raw.size() < 2) {
// TODO positional arg
unhandled_positional(arg_raw);
continue;

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

@@ -125,6 +125,13 @@ TEST_CASE("option string", "[poafloc/parser]")

REQUIRE(args.name == "something");
}

SECTION("short equal")
{
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"};

@@ -167,6 +174,13 @@ TEST_CASE("option string", "[poafloc/parser]")

REQUIRE(args.name == "default");
}

SECTION("short equal 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"};

@@ -235,6 +249,13 @@ TEST_CASE("option value", "[poafloc/parser]")

REQUIRE(args.value == 135);
}

SECTION("short equal")
{
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"};

@@ -277,6 +298,13 @@ TEST_CASE("option value", "[poafloc/parser]")

REQUIRE(args.value == 0);
}

SECTION("short equal 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"};

@@ -373,6 +401,26 @@ TEST_CASE("multiple", "[poafloc/parser]")

REQUIRE(args.value1 == "F");
REQUIRE(args.value2 == "default");
}

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

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

// NOLINTEND(*complexity*)