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 11cd944de37c2e4e5a09673d68f317fc2676b22e
parent 0774b9aeb57b54b263db4fa6d28e9134da86922e
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Wed, 4 Jun 2025 22:13:55 +0200

Add short help message, cleaner code

Diffstat:
M example/example.cpp | ++++++++ -----
M include/poafloc/error.hpp | +++++ ---
M include/poafloc/poafloc.hpp | ++++++++++++++ ---------
M source/help.cpp | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ------------
M source/option.cpp | +
M source/poafloc.cpp | ++++++++++++++++++ -----------

6 files changed, 156 insertions(+), 46 deletions(-)


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

@@ -46,10 +46,10 @@ int main()

&arguments::mul,
"NUM Multiplication constant",
},
direct {
"n name",
list {
"n names",
&arguments::name,
"NAME Name of the variable",
"NAME Names of the variables",
},
},
group {

@@ -60,7 +60,7 @@ int main()

"PRIV Private code",
},
boolean {
"f flag1",
"flag1",
&arguments::flag1,
"Some flag1",
},

@@ -73,7 +73,6 @@ int main()

};

const std::vector<std::string_view> cmd_args {
"--value=150",
"-m1.34",
"--name",
"Hello there!",

@@ -81,11 +80,15 @@ int main()

"General Kenobi!",
"--flag1",
"-F",
"150",
"40",
"30",
};

arguments args;

program.help_long();
program.help_short();

std::cout << args << '\n';
program(args, cmd_args);

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

@@ -12,9 +12,9 @@ namespace poafloc

{

#define ENUM_ERROR \
invalid_option, invalid_positional, invalid_terminal, missing_argument, \
missing_positional, superfluous_argument, superfluous_positional, \
unknown_option, duplicate_option
invalid_option, invalid_positional, invalid_terminal, missing_option, \
missing_argument, missing_positional, superfluous_argument, \
superfluous_positional, unknown_option, duplicate_option
BASED_DECLARE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)
BASED_DEFINE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)
#undef ENUM_ERROR

@@ -28,6 +28,8 @@ static constexpr const char* error_get_message(error_code::enum_type error)

return "Invalid positional argument: {}";
case error_code::invalid_terminal():
return "Invalid positional argument";
case error_code::missing_option():
return "Missing at least on of long and short options";
case error_code::missing_argument():
return "Missing argument for option: {}";
case error_code::missing_positional():

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

@@ -50,13 +50,17 @@ private:

type m_type;
func_t m_func;

based::vector<based::character, size_type> m_opts_short;
based::vector<std::string, size_type> m_opts_long;
based::character m_opt_short;
std::string m_opt_long;

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

protected:
// used for args
explicit option(type type, func_t func, std::string_view help = "");

// used for options
explicit option(
type type, std::string_view opts, func_t func, std::string_view help = ""
);

@@ -89,8 +93,12 @@ protected:

}

public:
[[nodiscard]] const auto& opts_long() const { return m_opts_long; }
[[nodiscard]] const auto& opts_short() const { return m_opts_short; }
[[nodiscard]] bool has_opt_long() const { return !m_opt_long.empty(); }
[[nodiscard]] bool has_opt_short() const { return m_opt_short != '\0'; }

[[nodiscard]] const std::string& opt_long() const { return m_opt_long; }
[[nodiscard]] based::character opt_short() const { return m_opt_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; }

@@ -122,7 +130,6 @@ public:

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

@@ -142,10 +149,7 @@ public:


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

@@ -485,6 +489,7 @@ protected:

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

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

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

@@ -1,3 +1,4 @@

#include <algorithm>
#include <format>
#include <iostream>

@@ -7,7 +8,38 @@

namespace poafloc::detail
{

void parser_base::help_long() const
namespace
{

std::string format_option_long(const option& option)
{
const auto& opt = option.opt_long();
switch (option.get_type()) {
case option::type::boolean:
return std::format("{}", opt);
case option::type::list:
return std::format("{}={}...", opt, option.name());
default:
return std::format("{}={}", opt, option.name());
}
}

std::string format_option_short(const option& option)
{
const auto& opt = option.opt_short();
switch (option.get_type()) {
case option::type::boolean:
return std::format("{}", opt);
case option::type::list:
return std::format("{} {}...", opt, option.name());
default:
return std::format("{} {}", opt, option.name());
}
}

} // namespace

void parser_base::help_usage() const
{
std::cerr << "Usage: program [OPTIONS]";
for (const auto& pos : m_pos) {

@@ -17,30 +49,27 @@ void parser_base::help_long() const

std::cerr << "...";
}
std::cerr << '\n';
}

void parser_base::help_long() const
{
help_usage();

auto idx = size_type(0_u);
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++];
const auto& opt = m_options[idx++];
std::string line;

line += " ";
for (const auto opt_short : option.opts_short()) {
line += std::format(" -{},", opt_short.chr());
if (opt.has_opt_short()) {
line += std::format(" -{},", opt.opt_short());
} else {
line += std::string(4, ' ');
}
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;
}
if (opt.has_opt_long()) {
line += std::format(" --{},", format_option_long(opt));
}
line.pop_back(); // get rid of superfluous ','

@@ -48,11 +77,74 @@ void parser_base::help_long() const

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';
std::cerr << line << opt.message() << '\n';
}
}

std::cerr << '\n';
}

void parser_base::help_short() const {}
void parser_base::help_short() const
{
std::vector<std::string> opts_short;
std::vector<std::string> opts_long;
std::string flags;

for (const auto& opt : m_options) {
if (opt.has_opt_short()) {
if (opt.get_type() == option::type::boolean) {
flags += opt.opt_short().chr();
} else {
opts_short.emplace_back(std::format("[-{}]", format_option_short(opt)));
}
}

if (opt.has_opt_long()) {
opts_long.emplace_back(std::format("[--{}]", format_option_long(opt)));
}
}

std::ranges::sort(opts_short);
std::ranges::sort(opts_long);
std::ranges::sort(flags);

static const std::string_view usage = "Usage: ";
std::cerr << usage << ' ';

std::string line;
const auto print = [&line](std::string_view data)
{
static constexpr const auto lim = std::size_t {60};
if (std::size(line) + std::size(data) > lim) {
std::cerr << line << '\n';
line = std::string(std::size(usage), ' ');
}
line += " ";
line += data;
};

line += "program";
if (!flags.empty()) {
line += std::format(" [-{}]", flags);
}

for (const auto& opt : opts_short) {
print(opt);
}

for (const auto& opt : opts_long) {
print(opt);
}

for (const auto& pos : m_pos) {
print(pos.name());
}

std::cerr << line;
if (m_pos.is_list()) {
std::cerr << "...";
}
std::cerr << '\n' << '\n';
}

} // namespace poafloc::detail

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

@@ -125,6 +125,7 @@ trie_t::opt_type trie_t::get(const trie_t& trie, std::string_view key)


return {};
}

} // namespace poafloc::detail

// option_long

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

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

#include <algorithm>

#include "poafloc/poafloc.hpp"

#include "poafloc/error.hpp"

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

namespace poafloc::detail
{

option::option(option::type type, func_t func, std::string_view help)
: m_type(type)
, m_func(std::move(func))
, m_name(help)
{
}

option::option(
option::type type, std::string_view opts, func_t func, std::string_view help
)

@@ -33,14 +38,15 @@ option::option(


while (std::getline(istr, str, ' ')) {
if (std::size(str) == 1) {
m_opts_short.emplace_back(str[0]);
m_opt_short = str[0];
} else {
m_opts_long.emplace_back(str);
m_opt_long = str;
}
}

std::ranges::sort(m_opts_short);
std::ranges::sort(m_opts_long);
if (!has_opt_short() && !has_opt_long()) {
throw error<error_code::missing_option>();
}

if (type != option::type::boolean) {
const auto pos = help.find(' ');

@@ -53,13 +59,15 @@ option::option(


void parser_base::process(const option& option)
{
for (const auto opt_short : option.opts_short()) {
if (option.has_opt_short()) {
const auto& opt_short = option.opt_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 (option.has_opt_long()) {
const auto& opt_long = option.opt_long();
if (!m_opt_long.set(opt_long, std::size(m_options))) {
throw error<error_code::duplicate_option>(opt_long);
}

@@ -107,7 +115,7 @@ void parser_base::operator()(

arg_idx = std::size(args) - std::size(res);
}

auto count = 0_u64;
size_type count = 0_u;
while (arg_idx != std::size(args)) {
const auto arg = args[arg_idx++];
if (!is_term && arg == "--") {

@@ -200,11 +208,10 @@ parser_base::next_t parser_base::hdl_long_opt(

{
const auto equal = arg.find('=');
if (equal != std::string::npos) {
auto opt = arg.substr(0, equal - 1);
auto opt = arg.substr(0, equal);
const auto value = arg.substr(equal + 1);

const auto option = get_option(opt);

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