poafloc

Parser 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

Diffstat:
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")