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 | b93b3c06a77571f6b965e0c103f5d36b3b5c7725 |
parent | 772ee226e6e991514d5f4e9bcb32c1331833eb53 |
author | Dimitrije Dobrota < mail@dimitrijedobrota.com > |
date | Mon, 26 May 2025 16:45:23 +0200 |
Accumulate options into named groups
M | example/example.cpp | | | ++++++++++++++++++++++++++ ----------------------- |
M | include/poafloc/poafloc.hpp | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ------------ |
M | test/source/parser.cpp | | | ++++++++++++++++++++++++++++++++++++++++++++++++++ ------------------ |
3 files changed, 198 insertions(+), 60 deletions(-)
diff --git a/ example/example.cpp b/ example/example.cpp
@@ -29,29 +29,32 @@
int main()
using namespace poafloc; // NOLINT
auto program = parser<arguments> {
direct {
"v value",
&arguments::val,
},
direct {
"m multiply",
&arguments::mul,
},
direct {
"n name",
&arguments::name,
},
direct {
"p priv",
&arguments::set_priv,
},
boolean {
"f flag1",
&arguments::flag1,
},
boolean {
"F flag2",
&arguments::flag2,
group {
"standard",
direct {
"v value",
&arguments::val,
},
direct {
"m multiply",
&arguments::mul,
},
direct {
"n name",
&arguments::name,
},
direct {
"p priv",
&arguments::set_priv,
},
boolean {
"f flag1",
&arguments::flag1,
},
boolean {
"F flag2",
&arguments::flag2,
},
},
};
diff --git a/ include/poafloc/poafloc.hpp b/ include/poafloc/poafloc.hpp
@@ -5,6 +5,7 @@
#include <cstring>
#include <format>
#include <functional>
#include <initializer_list>
#include <memory>
#include <optional>
#include <span>
@@ -13,7 +14,7 @@
#include <string_view>
#include <vector>
#include <based/enum/enum.hpp>
#include <based/trait/integral_constant.hpp>
#include <based/types/types.hpp>
#include "poafloc/error.hpp"
@@ -106,6 +107,43 @@
public:
}
};
namespace detail
{
template<class T>
struct is_argument : based::false_type
{
};
template<class Record, class Type>
struct is_argument<argument<Record, Type>> : based::true_type
{
};
template<class T>
concept IsArgument = is_argument<T>::value;
} // namespace detail
template<class Record>
class positional : public std::vector<detail::option<Record>>
{
using option_t = detail::option<Record>;
using base = std::vector<option_t>;
public:
explicit positional(detail::IsArgument auto... args)
requires(std::same_as<Record, typename decltype(args)::record_type> && ...)
: base(std::initializer_list<option_t> {
based::forward<decltype(args)>(args)...
})
{
}
};
positional(detail::IsArgument auto arg, detail::IsArgument auto... args)
-> positional<typename decltype(arg)::record_type>;
template<class Record, class Type>
requires(!std::same_as<bool, Type>)
class direct : public detail::option<Record>
@@ -145,6 +183,58 @@
public:
namespace detail
{
template<class T>
struct is_option : based::false_type
{
};
template<class Record, class Type>
struct is_option<direct<Record, Type>> : based::true_type
{
};
template<class Record>
struct is_option<boolean<Record>> : based::true_type
{
};
template<class T>
concept IsOption = is_option<T>::value;
} // namespace detail
template<class Record>
class group : public std::vector<detail::option<Record>>
{
using option_t = detail::option<Record>;
using base = std::vector<option_t>;
std::string m_name;
public:
using record_type = Record;
explicit group(std::string_view name, detail::IsOption auto... args)
requires(std::same_as<Record, typename decltype(args)::record_type> && ...)
: base(std::initializer_list<option_t> {
based::forward<decltype(args)>(args)...
})
, m_name(name)
{
}
[[nodiscard]] const auto& name() const { return m_name; }
};
group(
std::string_view name,
detail::IsOption auto arg,
detail::IsOption auto... args
) -> group<typename decltype(arg)::record_type>;
namespace detail
{
struct option_lookup_base
{
using size_t = std::size_t;
@@ -325,7 +415,11 @@
class parser
{
using option_t = detail::option<Record>;
std::vector<option_t> m_options;
std::vector<option_t> m_args;
using positional_t = positional<Record>;
positional_t m_positional;
using group_t = group<Record>;
detail::option_short m_opt_short;
detail::option_long m_opt_long;
@@ -340,13 +434,8 @@
class parser
return !args.empty() && is_option(args.front());
}
void process(const detail::option<Record>& option, std::string_view opts)
void process(const option_t& 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;
@@ -367,6 +456,13 @@
class parser
m_options.emplace_back(option);
}
void process_group(const group_t& group)
{
for (const auto& option : group) {
process(option, option.opts());
}
}
[[nodiscard]] const option_t& get_option(char opt) const
{
const auto idx = m_opt_short.get(opt);
@@ -477,14 +573,21 @@
class parser
}
public:
template<class... Args>
explicit parser(Args&&... args)
requires(std::same_as<Record, typename Args::record_type> && ...)
template<class... Groups>
explicit parser(Groups&&... groups)
requires(std::same_as<group_t, Groups> && ...)
{
constexpr auto size = sizeof...(Args);
m_options.reserve(m_options.size() + (groups.size() + ...));
(process_group(groups), ...);
}
m_options.reserve(size);
(process(args, args.opts()), ...);
template<class... Groups>
explicit parser(positional_t positional, Groups&&... groups)
requires(std::same_as<group_t, Groups> && ...)
: m_positional(std::move(positional))
{
m_options.reserve(m_options.size() + (groups.size() + ...));
(process_group(groups), ...);
}
void operator()(Record& record, int argc, const char** argv)
@@ -544,15 +647,15 @@
public:
throw error<error_code::invalid_positional>(arg);
}
if (count == m_args.size()) {
throw error<error_code::superfluous_positional>(m_args.size());
if (count == m_positional.size()) {
throw error<error_code::superfluous_positional>(m_positional.size());
}
m_args[count++](record, arg);
m_positional[count++](record, arg);
}
if (count != m_args.size()) {
throw error<error_code::missing_positional>(m_args.size());
if (count != m_positional.size()) {
throw error<error_code::missing_positional>(m_positional.size());
}
}
};
diff --git a/ test/source/parser.cpp b/ test/source/parser.cpp
@@ -23,7 +23,10 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
boolean {"1", &arguments::flag},
group {
"unnamed",
boolean {"1", &arguments::flag},
},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);
@@ -34,7 +37,10 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
boolean {"FLAG", &arguments::flag},
group {
"unnamed",
boolean {"FLAG", &arguments::flag},
},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);
@@ -45,7 +51,10 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
direct {"1value", &arguments::value},
group {
"unnamed",
direct {"1value", &arguments::value},
},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);
@@ -56,8 +65,11 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
boolean {"f flag", &arguments::flag},
direct {"f follow", &arguments::value},
group {
"unnamed",
boolean {"f flag", &arguments::flag},
direct {"f follow", &arguments::value},
},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::duplicate_option>);
@@ -68,8 +80,11 @@
TEST_CASE("invalid", "[poafloc/parser]")
auto construct = []()
{
return parser<arguments> {
boolean {"f flag", &arguments::flag},
direct {"v flag", &arguments::value},
group {
"unnamed",
boolean {"f flag", &arguments::flag},
direct {"v flag", &arguments::value},
},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::duplicate_option>);
@@ -84,7 +99,10 @@
TEST_CASE("flag", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
boolean {"f flag", &arguments::flag},
group {
"unnamed",
boolean {"f flag", &arguments::flag},
},
};
SECTION("short")
@@ -145,7 +163,10 @@
TEST_CASE("option string", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
direct {"n name", &arguments::name},
group {
"unnamed",
direct {"n name", &arguments::name},
},
};
SECTION("short")
@@ -269,7 +290,10 @@
TEST_CASE("option value", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
direct {"v value", &arguments::value},
group {
"unnamed",
direct {"v value", &arguments::value},
},
};
SECTION("short")
@@ -396,10 +420,15 @@
TEST_CASE("positional", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
boolean {"f flag", &arguments::flag},
direct {"v value", &arguments::value},
argument {"one", &arguments::one},
argument {"two", &arguments::two},
positional {
argument {"one", &arguments::one},
argument {"two", &arguments::two},
},
group {
"unnamed",
boolean {"f flag", &arguments::flag},
direct {"v value", &arguments::value},
}
};
SECTION("empty")
@@ -532,10 +561,13 @@
TEST_CASE("multiple", "[poafloc/parser]")
} args;
auto program = parser<arguments> {
boolean {"f flag1", &arguments::flag1},
boolean {"F flag2", &arguments::flag2},
direct {"v value1", &arguments::value1},
direct {"V value2", &arguments::value2},
group {
"unnamed",
boolean {"f flag1", &arguments::flag1},
boolean {"F flag2", &arguments::flag2},
direct {"v value1", &arguments::value1},
direct {"V value2", &arguments::value2},
},
};
SECTION("valid")