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 | 11cd944de37c2e4e5a09673d68f317fc2676b22e |
parent | 0774b9aeb57b54b263db4fa6d28e9134da86922e |
author | Dimitrije Dobrota < mail@dimitrijedobrota.com > |
date | Wed, 4 Jun 2025 22:13:55 +0200 |
Add short help message, cleaner code
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);
}