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