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 | 7742f7e991646415702b55e4008c4bdf4a18a161 |
parent | 8761a59a9b8b58395c8c52cd7d5fc0d21570d4d9 |
author | Dimitrije Dobrota < mail@dimitrijedobrota.com > |
date | Mon, 26 May 2025 14:17:05 +0200 |
Separate direct and boolean option types
M | example/example.cpp | | | ++++++++ ------- |
M | include/poafloc/poafloc.hpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ------------------------- |
M | test/source/parser.cpp | | | ++++++++++++++++++ ----------------- |
3 files changed, 119 insertions(+), 65 deletions(-)
diff --git a/ example/example.cpp b/ example/example.cpp
@@ -26,31 +26,32 @@
public:
int main()
{
using poafloc::option;
using poafloc::direct;
using poafloc::boolean;
using poafloc::parser;
auto program = parser<arguments> {
option {
direct {
"v value",
&arguments::val,
},
option {
direct {
"m multiply",
&arguments::mul,
},
option {
direct {
"n name",
&arguments::name,
},
option {
direct {
"p priv",
&arguments::set_priv,
},
option {
boolean {
"f flag1",
&arguments::flag1,
},
option {
boolean {
"F flag2",
&arguments::flag2,
},
diff --git a/ include/poafloc/poafloc.hpp b/ include/poafloc/poafloc.hpp
@@ -13,28 +13,58 @@
#include <string_view>
#include <vector>
#include <based/enum/enum.hpp>
#include <based/types/types.hpp>
#include "poafloc/error.hpp"
namespace poafloc
{
namespace detail
{
template<class Record>
class option_base
class option
{
public:
enum class type : based::bu8
{
direct,
optional,
boolean,
list,
};
private:
using func_t = std::function<void(Record&, std::string_view)>;
type m_type;
std::string m_opts;
func_t m_func;
bool m_arg;
protected:
explicit option_base(func_t func, bool argument)
: m_func(std::move(func))
, m_arg(argument)
explicit option(type type, std::string_view opts, func_t func)
: m_type(type)
, m_opts(opts)
, m_func(std::move(func))
{
}
template<class T>
static T convert(std::string_view value)
{
T tmp;
auto istr = std::istringstream(std::string(value));
istr >> tmp;
return tmp;
}
public:
[[nodiscard]] bool argument() const { return m_arg; }
using record_type = Record;
[[nodiscard]] const std::string& opts() const { return m_opts; }
[[nodiscard]] type type() const { return m_type; }
void operator()(Record& record, std::string_view value) const
{
@@ -42,11 +72,13 @@
public:
}
};
} // namespace detail
template<class Record, class Type>
class option : public option_base<Record>
requires(!std::same_as<bool, Type>)
class direct : public detail::option<Record>
{
std::string m_opts;
using base = detail::option<Record>;
using member_type = Type Record::*;
static auto create(member_type member)
@@ -56,39 +88,49 @@
class option : public option_base<Record>
if constexpr (std::is_invocable_v<member_type, Record, std::string_view>)
{
std::invoke(member, record, value);
} else if constexpr (std::same_as<bool, Type>) {
std::invoke(member, record) = true;
} else if constexpr (requires(Type tmp, std::string_view val) {
tmp = val;
})
{
} 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 {
auto istr = std::istringstream(std::string(value));
Type tmp;
istr >> tmp;
std::invoke(member, record) = tmp;
std::invoke(member, record) = base::template convert<Type>(value);
}
};
}
public:
using record_type = Record;
using value_type = Type;
direct(std::string_view opts, member_type member)
: base(base::type::direct, opts, create(member))
{
}
};
option(std::string_view opts, member_type member)
: option_base<Record>(create(member), !std::same_as<bool, Type>)
, m_opts(opts)
template<class Record>
class boolean : public detail::option<Record>
{
using base = detail::option<Record>;
using member_type = bool Record::*;
static auto create(member_type member)
{
return [member](Record& record, std::string_view value)
{
(void)value;
std::invoke(member, record) = true;
};
}
[[nodiscard]] const std::string& opts() const { return m_opts; }
public:
boolean(std::string_view opts, member_type member)
: base(base::type::boolean, opts, create(member))
{
}
};
namespace detail
{
struct option_base
struct option_lookup_base
{
using size_t = std::size_t;
using value_type = std::size_t;
@@ -106,7 +148,7 @@
struct option_base
}
};
class option_short : option_base
class option_short : option_lookup_base
{
static constexpr auto size = static_cast<size_t>(2 * 26);
std::array<value_type, size> m_opts = {};
@@ -161,7 +203,7 @@
public:
}
};
class option_long : option_base
class option_long : option_lookup_base
{
class trie_t
{
@@ -268,13 +310,23 @@
class parser : public std::vector<std::string>
{
using positional = std::vector<std::string>;
using option_t = option_base<Record>;
using option_t = detail::option<Record>;
std::vector<option_t> m_options;
detail::option_short m_opt_short;
detail::option_long m_opt_long;
void process(const option_base<Record>& option, std::string_view opts)
static constexpr bool is_option(std::string_view arg)
{
return arg.starts_with("-");
}
static constexpr bool is_next_option(std::span<std::string_view> args)
{
return !args.empty() && is_option(args.front());
}
void process(const detail::option<Record>& option, std::string_view opts)
{
auto istr = std::istringstream(std::string(opts));
std::string str;
@@ -338,7 +390,7 @@
class parser : public std::vector<std::string>
const auto option = get_option(opt);
if (!option.argument()) {
if (option.type() == option_t::type::boolean) {
throw error<error_code::superfluous_argument>(opt);
}
@@ -352,7 +404,7 @@
class parser : public std::vector<std::string>
const auto option = get_option(arg);
if (!option.argument()) {
if (option.type() == option_t::type::boolean) {
option(record, "true");
return handle_res::ok;
}
@@ -373,7 +425,7 @@
class parser : public std::vector<std::string>
const auto opt = arg[opt_idx];
const auto option = get_option(opt);
if (!option.argument()) {
if (option.type() == option_t::type::boolean) {
option(record, "true");
continue;
}
@@ -429,13 +481,7 @@
public:
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] != '-') {
if (!is_option(arg_raw)) {
break;
}
@@ -443,6 +489,12 @@
public:
throw error<error_code::unknown_option>("-");
}
if (arg_raw == "--") {
terminal = true;
++arg_idx;
break;
}
const auto res = arg_raw[1] != '-'
? handle_short_opts(
record, arg_raw.substr(1), args.subspan(arg_idx + 1)
@@ -464,7 +516,7 @@
public:
if (!terminal && arg == "--") {
throw error<error_code::invalid_terminal>(arg);
}
if (!terminal && (arg.starts_with("-") || arg.starts_with("--"))) {
if (!terminal && is_option(arg)) {
throw error<error_code::invalid_positional>(arg);
}
positional::emplace_back(arg);
diff --git a/ test/source/parser.cpp b/ test/source/parser.cpp
@@ -7,9 +7,10 @@
#include "poafloc/poafloc.hpp"
using poafloc::boolean;
using poafloc::direct;
using poafloc::error;
using poafloc::error_code;
using poafloc::option;
using poafloc::parser;
// NOLINTBEGIN(*complexity*)
@@ -26,7 +27,7 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
option {"1", &arguments::flag},
boolean {"1", &arguments::flag},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);
@@ -37,7 +38,7 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
option {"FLAG", &arguments::flag},
boolean {"FLAG", &arguments::flag},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);
@@ -48,7 +49,7 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
option {"1value", &arguments::value},
direct {"1value", &arguments::value},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);
@@ -59,8 +60,8 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
option {"f flag", &arguments::flag},
option {"f follow", &arguments::value},
boolean {"f flag", &arguments::flag},
direct {"f follow", &arguments::value},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::duplicate_option>);
@@ -71,8 +72,8 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
option {"f flag", &arguments::flag},
option {"v flag", &arguments::value},
boolean {"f flag", &arguments::flag},
direct {"v flag", &arguments::value},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::duplicate_option>);
@@ -87,7 +88,7 @@
TEST_CASE("flag", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
option {"f flag", &arguments::flag},
boolean {"f flag", &arguments::flag},
};
SECTION("short")
@@ -148,7 +149,7 @@
TEST_CASE("option string", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
option {"n name", &arguments::name},
direct {"n name", &arguments::name},
};
SECTION("short")
@@ -272,7 +273,7 @@
TEST_CASE("option value", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
option {"v value", &arguments::value},
direct {"v value", &arguments::value},
};
SECTION("short")
@@ -397,8 +398,8 @@
TEST_CASE("positional", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
option {"f flag", &arguments::flag},
option {"v value", &arguments::value},
boolean {"f flag", &arguments::flag},
direct {"v value", &arguments::value},
};
SECTION("empty")
@@ -525,10 +526,10 @@
TEST_CASE("multiple", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
option {"f flag1", &arguments::flag1},
option {"F flag2", &arguments::flag2},
option {"v value1", &arguments::value1},
option {"V value2", &arguments::value2},
boolean {"f flag1", &arguments::flag1},
boolean {"F flag2", &arguments::flag2},
direct {"v value1", &arguments::value1},
direct {"V value2", &arguments::value2},
};
SECTION("valid")