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 | e1b1257101cf4f047291177b21caaa39fccb368c |
parent | 8276d5c6b044d80e2f96ba4b0ac01956bf59ca85 |
author | Dimitrije Dobrota < mail@dimitrijedobrota.com > |
date | Sun, 25 May 2025 20:04:21 +0200 |
Short options can have =, naming enforcement
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*)