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 | 772ee226e6e991514d5f4e9bcb32c1331833eb53 |
parent | 7742f7e991646415702b55e4008c4bdf4a18a161 |
author | Dimitrije Dobrota < mail@dimitrijedobrota.com > |
date | Mon, 26 May 2025 15:50:04 +0200 |
Better handling for positional arguments
M | example/example.cpp | | | + --- |
M | include/poafloc/error.hpp | | | ++++++ - |
M | include/poafloc/poafloc.hpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ------------------------- |
M | test/source/parser.cpp | | | +++++++++++++++++++++++++++++++++++ ----------------------------- |
4 files changed, 99 insertions(+), 57 deletions(-)
diff --git a/ example/example.cpp b/ example/example.cpp
@@ -26,9 +26,7 @@
public:
int main()
{
using poafloc::direct;
using poafloc::boolean;
using poafloc::parser;
using namespace poafloc; // NOLINT
auto program = parser<arguments> {
direct {
diff --git a/ include/poafloc/error.hpp b/ include/poafloc/error.hpp
@@ -12,7 +12,8 @@
namespace poafloc
#define ENUM_ERROR \
invalid_option, invalid_positional, invalid_terminal, missing_argument, \
superfluous_argument, unknown_option, duplicate_option
missing_positional, superfluous_argument, superfluous_positional, \
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
@@ -28,8 +29,12 @@
static constexpr const char* error_get_message(error_code::enum_type error)
return "Invalid positional argument";
case error_code::missing_argument():
return "Missing argument for option: {}";
case error_code::missing_positional():
return "Too little positional arguments, require: {}";
case error_code::superfluous_argument():
return "Option doesn't require an argument: {}";
case error_code::superfluous_positional():
return "Too few positional arguments, require: {}";
case error_code::unknown_option():
return "Unknown option: {}";
case error_code::duplicate_option():
diff --git a/ include/poafloc/poafloc.hpp b/ include/poafloc/poafloc.hpp
@@ -30,6 +30,7 @@
class option
public:
enum class type : based::bu8
{
argument,
direct,
optional,
boolean,
@@ -51,6 +52,23 @@
protected:
{
}
template<class Type, class Member = Type Record::*>
static auto create(Member member)
{
return [member](Record& record, std::string_view value)
{
if constexpr (std::is_invocable_v<Member, Record, std::string_view>) {
std::invoke(member, record, value);
} else if constexpr (std::is_invocable_v<Member, Record, Type>) {
std::invoke(member, record, convert<Type>(value));
} else if constexpr (std::is_assignable_v<Type, std::string_view>) {
std::invoke(member, record) = value;
} else {
std::invoke(member, record) = convert<Type>(value);
}
};
}
template<class T>
static T convert(std::string_view value)
{
@@ -76,31 +94,28 @@
public:
template<class Record, class Type>
requires(!std::same_as<bool, Type>)
class direct : public detail::option<Record>
class argument : public detail::option<Record>
{
using base = detail::option<Record>;
using member_type = Type Record::*;
static auto create(member_type member)
public:
explicit argument(std::string_view name, member_type member)
: base(base::type::argument, name, base::template create<Type>(member))
{
return [member](Record& record, std::string_view value)
{
if constexpr (std::is_invocable_v<member_type, Record, std::string_view>)
{
std::invoke(member, record, value);
} else if constexpr (std::is_invocable_v<member_type, Record, Type>) {
std::invoke(member, record, base::template convert<Type>(value));
} else if constexpr (std::is_assignable_v<Type, std::string_view>) {
std::invoke(member, record) = value;
} else {
std::invoke(member, record) = base::template convert<Type>(value);
}
};
}
};
template<class Record, class Type>
requires(!std::same_as<bool, Type>)
class direct : public detail::option<Record>
{
using base = detail::option<Record>;
using member_type = Type Record::*;
public:
direct(std::string_view opts, member_type member)
: base(base::type::direct, opts, create(member))
explicit direct(std::string_view opts, member_type member)
: base(base::type::direct, opts, base::template create<Type>(member))
{
}
};
@@ -121,7 +136,7 @@
class boolean : public detail::option<Record>
}
public:
boolean(std::string_view opts, member_type member)
explicit boolean(std::string_view opts, member_type member)
: base(base::type::boolean, opts, create(member))
{
}
@@ -306,12 +321,11 @@
public:
} // namespace detail
template<class Record>
class parser : public std::vector<std::string>
class parser
{
using positional = std::vector<std::string>;
using option_t = detail::option<Record>;
std::vector<option_t> m_options;
std::vector<option_t> m_args;
detail::option_short m_opt_short;
detail::option_long m_opt_long;
@@ -328,6 +342,11 @@
class parser : public std::vector<std::string>
void process(const detail::option<Record>& option, std::string_view opts)
{
if (option.type() == option_t::type::argument) {
m_args.emplace_back(option);
return;
}
auto istr = std::istringstream(std::string(opts));
std::string str;
@@ -468,9 +487,12 @@
public:
(process(args, args.opts()), ...);
}
void operator()(Record& record, const char* argc, int argv)
void operator()(Record& record, int argc, const char** argv)
{
operator()(record, std::span(argc, argv));
std::vector<std::string_view> args(
argv + 1, argv + argc // NOLINT(*pointer*)
);
operator()(record, args);
}
void operator()(Record& record, std::span<std::string_view> args)
@@ -511,15 +533,26 @@
public:
}
}
std::size_t count = 0;
for (; arg_idx != std::size(args); ++arg_idx) {
const auto arg = args[arg_idx];
if (!terminal && arg == "--") {
throw error<error_code::invalid_terminal>(arg);
}
if (!terminal && is_option(arg)) {
throw error<error_code::invalid_positional>(arg);
}
positional::emplace_back(arg);
if (count == m_args.size()) {
throw error<error_code::superfluous_positional>(m_args.size());
}
m_args[count++](record, arg);
}
if (count != m_args.size()) {
throw error<error_code::missing_positional>(m_args.size());
}
}
};
diff --git a/ test/source/parser.cpp b/ test/source/parser.cpp
@@ -7,11 +7,7 @@
#include "poafloc/poafloc.hpp"
using poafloc::boolean;
using poafloc::direct;
using poafloc::error;
using poafloc::error_code;
using poafloc::parser;
using namespace poafloc; // NOLINT
// NOLINTBEGIN(*complexity*)
TEST_CASE("invalid", "[poafloc/parser]")
@@ -395,33 +391,44 @@
TEST_CASE("positional", "[poafloc/parser]")
{
bool flag = false;
int value = 0;
std::string one;
std::string two;
} args;
auto program = parser<arguments> {
boolean {"f flag", &arguments::flag},
direct {"v value", &arguments::value},
argument {"one", &arguments::one},
argument {"two", &arguments::two},
};
SECTION("empty")
{
std::vector<std::string_view> cmdline = {};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(program.empty());
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_positional>);
}
SECTION("one")
{
std::vector<std::string_view> cmdline = {"one"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(program[0] == "one");
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::missing_positional>);
REQUIRE(args.one == "one");
}
SECTION("two")
{
std::vector<std::string_view> cmdline = {"one", "two"};
REQUIRE_NOTHROW(program(args, cmdline));
REQUIRE(program[0] == "one");
REQUIRE(program[1] == "two");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}
SECTION("three")
{
std::vector<std::string_view> cmdline = {"one", "two", "three"};
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::superfluous_positional>);
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}
SECTION("flag short")
@@ -429,8 +436,8 @@
TEST_CASE("positional", "[poafloc/parser]")
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");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}
SECTION("flag long")
@@ -438,8 +445,8 @@
TEST_CASE("positional", "[poafloc/parser]")
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");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}
SECTION("value short")
@@ -447,8 +454,8 @@
TEST_CASE("positional", "[poafloc/parser]")
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");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}
SECTION("value short together")
@@ -456,8 +463,8 @@
TEST_CASE("positional", "[poafloc/parser]")
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");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}
SECTION("value short together")
@@ -465,8 +472,8 @@
TEST_CASE("positional", "[poafloc/parser]")
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");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}
SECTION("value long")
@@ -474,8 +481,8 @@
TEST_CASE("positional", "[poafloc/parser]")
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");
REQUIRE(args.one == "one");
REQUIRE(args.two == "two");
}
SECTION("value long equal")
@@ -483,17 +490,16 @@
TEST_CASE("positional", "[poafloc/parser]")
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");
REQUIRE(args.one == "one");
REQUIRE(args.two == "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");
REQUIRE_THROWS_AS(program(args, cmdline), error<error_code::superfluous_positional>);
REQUIRE(args.one == "one");
REQUIRE(args.two == "-f");
}
SECTION("invalid terminal")