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 8761a59a9b8b58395c8c52cd7d5fc0d21570d4d9
parent 5f5f0f004741116ad48e455e4326c7c39bf46e6f
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Sun, 25 May 2025 21:36:29 +0200

Handle positional arguments and terminal

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

4 files changed, 165 insertions(+), 20 deletions(-)


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

@@ -29,7 +29,7 @@ int main()

using poafloc::option;
using poafloc::parser;

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

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

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

{

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

switch (error()) {
case error_code::invalid_option():
return "Invalid option name: {}";
case error_code::invalid_positional():
return "Invalid positional argument: {}";
case error_code::invalid_terminal():
return "Invalid positional argument";
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

@@ -264,8 +264,10 @@ public:

} // namespace detail

template<class Record>
class parser
class parser : public std::vector<std::string>
{
using positional = std::vector<std::string>;

using option_t = option_base<Record>;
std::vector<option_t> m_options;

@@ -414,25 +416,31 @@ public:

(process(args, args.opts()), ...);
}

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

void operator()(Record& record, std::span<std::string_view> args) const
void operator()(Record& record, std::span<std::string_view> args)
{
std::size_t arg_idx = 0;
bool terminal = false;

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

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

if (arg_raw[0] != '-') {
break;
}

if (arg_raw.size() == 1) {
throw error<error_code::unknown_option>("-");
}

const auto res = arg_raw[1] != '-'

@@ -452,7 +460,14 @@ public:

}

for (; arg_idx != std::size(args); ++arg_idx) {
unhandled_positional(args[arg_idx]);
const auto arg = args[arg_idx];
if (!terminal && arg == "--") {
throw error<error_code::invalid_terminal>(arg);
}
if (!terminal && (arg.starts_with("-") || arg.starts_with("--"))) {
throw error<error_code::invalid_positional>(arg);
}
positional::emplace_back(arg);
}
}
};

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

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


SECTION("short number")
{
const auto construct = []()
auto construct = []()
{
return parser<arguments> {
option {"1", &arguments::flag},

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


SECTION("long upper")
{
const auto construct = []()
auto construct = []()
{
return parser<arguments> {
option {"FLAG", &arguments::flag},

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


SECTION("long number start")
{
const auto construct = []()
auto construct = []()
{
return parser<arguments> {
option {"1value", &arguments::value},

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


SECTION("short duplicate")
{
const auto construct = []()
auto construct = []()
{
return parser<arguments> {
option {"f flag", &arguments::flag},

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


SECTION("long duplicate")
{
const auto construct = []()
auto construct = []()
{
return parser<arguments> {
option {"f flag", &arguments::flag},

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

bool flag = false;
} args;

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

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

std::string name = "default";
} args;

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

@@ -271,7 +271,7 @@ TEST_CASE("option value", "[poafloc/parser]")

int value = 0;
} args;

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

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

}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

SECTION("invalid terminal")
{
std::vector<std::string_view> cmdline = {"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"};
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"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::invalid_positional>);
}
}

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

@@ -398,7 +524,7 @@ TEST_CASE("multiple", "[poafloc/parser]")

std::string value2 = "default";
} args;

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