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