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 6bae612d291e8d715ea35b03873cb846928eca1d
parent 4d7e51ac5f4f93d0a3c599dbc091c34b100563ca
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Wed, 28 May 2025 11:10:24 +0200

Add long help function

Diffstat:
M CMakeLists.txt | +
M example/example.cpp | +++++++++++++++++++++ ----
M include/poafloc/poafloc.hpp | ++++++++++++++++++++++++++++++++++++++++++++++++ ----------------------------------
A source/help.cpp | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M source/poafloc.cpp | ++++++++++++++++++++++++++++++++++++++++ --------------

5 files changed, 203 insertions(+), 77 deletions(-)


diff --git a/ CMakeLists.txt b/ CMakeLists.txt

@@ -21,6 +21,7 @@ add_library(

poafloc_poafloc
source/poafloc.cpp
source/option.cpp
source/help.cpp
)
add_library(poafloc::poafloc ALIAS poafloc_poafloc)
target_link_libraries(poafloc_poafloc PUBLIC based::based)

diff --git a/ example/example.cpp b/ example/example.cpp

@@ -29,31 +29,45 @@ int main()

using namespace poafloc; // NOLINT

auto program = parser<arguments> {
group {
"standard",
direct {
"v value",
positional {
argument {
"value",
&arguments::val,
},
argument_list {
"rest",
&arguments::val,
},
},
group {
"standard",
direct {
"m multiply",
&arguments::mul,
"NUM Multiplication constant",
},
direct {
"n name",
&arguments::name,
"NAME Name of the variable",
},
},
group {
"test",
direct {
"p priv",
&arguments::set_priv,
"PRIV Private code",
},
boolean {
"f flag1",
&arguments::flag1,
"Some flag1",
},
boolean {
"F flag2",
&arguments::flag2,
"Some flag2",
},
},
};

@@ -71,9 +85,12 @@ int main()


arguments args;

program.help_long();
/*
std::cout << args << '\n';
program(args, cmd_args);
std::cout << args << '\n';
*/

return 0;
}

diff --git a/ include/poafloc/poafloc.hpp b/ include/poafloc/poafloc.hpp

@@ -18,6 +18,8 @@

#include <based/utility/forward.hpp>
#include <based/utility/move.hpp>

#include "poafloc/error.hpp"

namespace poafloc
{

@@ -40,16 +42,17 @@ private:

using func_t = std::function<void(void*, std::string_view)>;

type m_type;
std::string m_opts;
func_t m_func;
std::vector<char> m_opts_short;
std::vector<std::string> m_opts_long;

std::string m_name;
std::string m_message;

protected:
explicit option(type type, std::string_view opts, func_t func)
: m_type(type)
, m_opts(opts)
, m_func(std::move(func))
{
}
explicit option(
type type, std::string_view opts, func_t func, std::string_view help = ""
);

template<class Record, class Type, class Member = Type Record::*>
static auto create(Member member)

@@ -79,8 +82,11 @@ protected:

}

public:
[[nodiscard]] const std::string& opts() const { return m_opts; }
[[nodiscard]] type type() const { return m_type; }
[[nodiscard]] const auto& opts_long() const { return m_opts_long; }
[[nodiscard]] const auto& opts_short() const { return m_opts_short; }
[[nodiscard]] const std::string& name() const { return m_name; }
[[nodiscard]] const std::string& message() const { return m_message; }
[[nodiscard]] type get_type() const { return m_type; }

void operator()(void* record, std::string_view value) const
{

@@ -109,8 +115,30 @@ public:

explicit argument(std::string_view name, member_type member)
: base(
base::type::argument,
name,
base::template create<Record, Type>(member)
"",
base::template create<Record, Type>(member),
name
)
{
}
};

template<class Record, class Type>
requires(!based::SameAs<bool, Type>)
class argument_list : public detail::option
{
using base = detail::option;
using member_type = Type Record::*;

public:
using rec_type = Record;

explicit argument_list(std::string_view name, member_type member)
: base(
base::type::list,
"",
base::template create<Record, Type>(member),
name
)
{
}

@@ -126,11 +154,14 @@ class direct : public detail::option

public:
using rec_type = Record;

explicit direct(std::string_view opts, member_type member)
explicit direct(
std::string_view opts, member_type member, std::string_view help = ""
)
: base(
base::type::direct,
opts,
base::template create<Record, Type>(member)
base::template create<Record, Type>(member),
help
)
{
}

@@ -155,8 +186,10 @@ class boolean : public detail::option

public:
using rec_type = Record;

explicit boolean(std::string_view opts, member_type member)
: base(base::type::boolean, opts, create(member))
explicit boolean(
std::string_view opts, member_type member, std::string_view help = ""
)
: base(base::type::boolean, opts, create(member), help)
{
}
};

@@ -171,9 +204,14 @@ class list : public detail::option

public:
using rec_type = Record;

explicit list(std::string_view opts, member_type member)
explicit list(
std::string_view opts, member_type member, std::string_view help = ""
)
: base(
base::type::list, opts, base::template create<Record, Type>(member)
base::type::list,
opts,
base::template create<Record, Type>(member),
help
)
{
}

@@ -183,52 +221,40 @@ namespace detail

{

template<class T>
struct is_argument : based::false_type
struct is_positional : 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;

template<class T>
struct is_list : based::false_type
struct is_positional<argument_list<Record, Type>> : based::true_type
{
};

template<class Record, class Type>
struct is_list<list<Record, Type>> : based::true_type
struct is_positional<argument<Record, Type>> : based::true_type
{
};

template<class T>
concept IsList = is_list<T>::value;
concept IsPositional = is_positional<T>::value;

class positional_base : public std::vector<detail::option>
{
using base = std::vector<option>;

protected:
template<detail::IsArgument Arg, detail::IsArgument... Args>
template<detail::IsPositional Arg, detail::IsPositional... Args>
explicit positional_base(Arg&& arg, Args&&... args)
: base(std::initializer_list<option> {
based::forward<Arg>(arg),
based::forward<Args>(args)...,
})
{
}

template<detail::IsArgument... Args, detail::IsList List>
explicit positional_base(Args&&... args, List&& lst)
: base(std::initializer_list<option> {
based::forward<Args>(args)...,
based::forward<List>(lst),
})
{
for (std::size_t i = 0; i < base::size() - 1; i++) {
if (base::operator[](i).get_type() == option::type::list) {
throw runtime_error("invalid positional constructor");
}
}
}

public:

@@ -236,7 +262,7 @@ public:


[[nodiscard]] bool is_list() const
{
return !empty() && back().type() == option::type::list;
return !empty() && back().get_type() == option::type::list;
}
};

@@ -247,29 +273,17 @@ struct positional : detail::positional_base

{
using rec_type = Record;

template<detail::IsArgument Arg, detail::IsArgument... Args>
template<detail::IsPositional Arg, detail::IsPositional... Args>
explicit positional(Arg&& arg, Args&&... args)
requires detail::SameRec<Arg, Args...>
: positional_base(based::forward<Arg>(arg), based::forward<Args>(args)...)
{
}

template<detail::IsArgument... Args, detail::IsList List>
explicit positional(Args&&... args, List&& list)
requires detail::SameRec<List, Args...>
: positional_base(
based::forward<Args>(args)..., based::forward<List>(list)
)
{
}
};

template<detail::IsArgument Arg, detail::IsArgument... Args>
template<detail::IsPositional Arg, detail::IsPositional... Args>
positional(Arg&& arg, Args&&... args) -> positional<detail::rec_type<Arg>>;

template<detail::IsArgument... Args, detail::IsList List>
positional(Args&&... args, List&& list) -> positional<detail::rec_type<List>>;

namespace detail
{

@@ -394,12 +408,14 @@ class parser_base

{
std::vector<option> m_options;

positional_base m_pos;
using group_t = std::pair<std::size_t, std::string>;
std::vector<group_t> m_groups;

detail::option_short m_opt_short;
detail::option_long m_opt_long;
positional_base m_pos;
option_short m_opt_short;
option_long m_opt_long;

void process(const option& option, std::string_view opts);
void process(const option& option);

[[nodiscard]] const option& get_option(char opt) const;
[[nodiscard]] const option& get_option(std::string_view opt) const;

@@ -426,18 +442,23 @@ protected:

: m_pos(based::forward<decltype(positional)>(positional))
{
m_options.reserve(m_options.size() + (groups.size() + ...));
m_groups.reserve(sizeof...(groups));

const auto process = [&](const auto& group)
{
for (const auto& option : group) {
this->process(option, option.opts());
this->process(option);
}
m_groups.emplace_back(m_options.size(), group.name());
};
(process(groups), ...);
}

void operator()(void* record, int argc, const char** argv);
void operator()(void* record, std::span<std::string_view> args);

void help_long() const;
void help_short() const;
};

} // namespace detail

@@ -468,6 +489,9 @@ struct parser : detail::parser_base

{
}

using parser_base::help_long;
using parser_base::help_short;

void operator()(Record& record, int argc, const char** argv)
{
parser_base::operator()(&record, argc, argv);

diff --git a/ source/help.cpp b/ source/help.cpp

@@ -0,0 +1,58 @@

#include <format>
#include <iostream>

#include "based/algorithms/max.hpp"
#include "poafloc/poafloc.hpp"

namespace poafloc::detail
{

void parser_base::help_long() const
{
std::cerr << "Usage: program [OPTIONS]";
for (const auto& pos : m_pos) {
std::cerr << std::format(" {}", pos.name());
}
if (m_pos.is_list()) {
std::cerr << "...";
}
std::cerr << '\n';

std::size_t idx = 0;
for (const auto& [end_idx, name] : m_groups) {
std::cerr << std::format("\n{}:\n", name);
while (idx < end_idx) {
const auto& option = m_options[idx++];
std::string line;

line += " ";
for (const auto opt_short : option.opts_short()) {
line += std::format(" -{},", opt_short);
}
for (const auto& opt_long : option.opts_long()) {
switch (option.get_type()) {
case option::type::boolean:
line += std::format(" --{},", opt_long);
break;
case option::type::list:
line += std::format(" --{}={}...,", opt_long, option.name());
break;
default:
line += std::format(" --{}={},", opt_long, option.name());
break;
}
}
line.pop_back(); // get rid of superfluous ','

static constexpr const auto zero = std::size_t {0};
static constexpr const auto mid = std::size_t {30};
line += std::string(based::max(zero, mid - std::size(line)), ' ');

std::cerr << line << option.message() << '\n';
}
}
}

void parser_base::help_short() const {}

} // namespace poafloc::detail

diff --git a/ source/poafloc.cpp b/ source/poafloc.cpp

@@ -1,3 +1,5 @@

#include <algorithm>

#include "poafloc/poafloc.hpp"

#include "poafloc/error.hpp"

@@ -20,22 +22,46 @@ constexpr bool is_next_option(std::span<std::string_view> args)

namespace poafloc::detail
{

void parser_base::process(const option& option, std::string_view opts)
option::option(
option::type type, std::string_view opts, func_t func, std::string_view help
)
: m_type(type)
, m_func(std::move(func))
{
auto istr = std::istringstream(std::string(opts));
std::string str;

while (std::getline(istr, str, ' ')) {
if (std::size(str) == 1) {
const auto& opt = str[0];
if (!m_opt_short.set(opt, std::size(m_options))) {
throw error<error_code::duplicate_option>(opt);
}
m_opts_short.emplace_back(str[0]);
} else {
const auto& opt = str;
if (!m_opt_long.set(opt, std::size(m_options))) {
throw error<error_code::duplicate_option>(opt);
}
m_opts_long.emplace_back(str);
}
}

std::ranges::sort(m_opts_short);
std::ranges::sort(m_opts_long);

if (type != option::type::boolean) {
const auto pos = help.find(' ');
m_name = help.substr(0, pos);
m_message = help.substr(pos + 1);
} else {
m_message = help;
}
}

void parser_base::process(const option& option)
{
for (const auto opt_short : option.opts_short()) {
if (!m_opt_short.set(opt_short, std::size(m_options))) {
throw error<error_code::duplicate_option>(opt_short);
}
}

for (const auto& opt_long : option.opts_long()) {
if (!m_opt_long.set(opt_long, std::size(m_options))) {
throw error<error_code::duplicate_option>(opt_long);
}
}

@@ -131,7 +157,7 @@ parser_base::next_t parser_base::hdl_short_opt(

throw error<error_code::missing_argument>(opt);
}

if (option.type() != option::type::list) {
if (option.get_type() != option::type::list) {
option(record, next.front());
return next.subspan(1);
}

@@ -153,7 +179,7 @@ parser_base::next_t parser_base::hdl_short_opts(

const auto opt = arg[opt_idx];
const auto option = get_option(opt);

if (option.type() != option::type::boolean) {
if (option.get_type() != option::type::boolean) {
break;
}

@@ -177,7 +203,7 @@ parser_base::next_t parser_base::hdl_long_opt(


const auto option = get_option(opt);

if (option.type() == option::type::boolean) {
if (option.get_type() == option::type::boolean) {
throw error<error_code::superfluous_argument>(opt);
}

@@ -192,7 +218,7 @@ parser_base::next_t parser_base::hdl_long_opt(

const auto opt = arg;
const auto option = get_option(opt);

if (option.type() == option::type::boolean) {
if (option.get_type() == option::type::boolean) {
option(record, "true");
return next;
}

@@ -201,7 +227,7 @@ parser_base::next_t parser_base::hdl_long_opt(

throw error<error_code::missing_argument>(opt);
}

if (option.type() != option::type::list) {
if (option.get_type() != option::type::list) {
option(record, next.front());
return next.subspan(1);
}