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 776211ea9fe149d853f69209b1d62bf464dda420
parent 7ee634ae13bd60f8789cfb1adc24cf79ede34d08
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Fri, 23 May 2025 17:42:08 +0200

Proper long and short option store and lookup

Diffstat:
M example/example.cpp | ++++ ----
M include/poafloc/poafloc.hpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -----------------

2 files changed, 160 insertions(+), 43 deletions(-)


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

@@ -29,19 +29,19 @@ int main()


const auto program = parser<arguments> {
option {
"-v,--value",
"-v --value",
&arguments::val,
},
option {
"-m,--multiply",
"-m --multiply",
&arguments::mul,
},
option {
"-n,--name",
"-n --name",
&arguments::name,
},
option {
"-p,--priv",
"-p --priv",
&arguments::set_priv,
},
};

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

@@ -1,8 +1,11 @@

#pragma once

#include <algorithm>
#include <concepts>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>

@@ -71,31 +74,160 @@ public:

[[nodiscard]] const std::string& opts() const { return m_opts; }
};

namespace detail
{

struct option_base
{
using value_type = std::size_t;
using return_type = std::optional<value_type>;

static constexpr const std::size_t size = 256;
static constexpr const auto sentinel = value_type {0xFFFFFFFFFFFFFFFF};

using container_type = std::array<value_type, size>;

static auto convert(char chr)
{
return static_cast<container_type::size_type>(chr);
}
};

class option_short : option_base
{
container_type m_opts = {};

[[nodiscard]] bool has(char chr) const
{
return m_opts[convert(chr)] != sentinel;
}

public:
option_short() { m_opts.fill(sentinel); }

[[nodiscard]] bool set(char chr, value_type idx)
{
if (has(chr)) {
return false;
}

m_opts[convert(chr)] = idx;
return true;
}

[[nodiscard]] return_type get(char chr) const
{
if (!has(chr)) {
return {};
}

return m_opts[convert(chr)];
}
};

class option_long : option_base
{
class Trie
{
std::array<std::unique_ptr<Trie>, size> m_children = {};
value_type m_value = sentinel;
std::size_t m_count = 0;
bool m_terminal = false;

public:
static bool set(Trie& trie, std::string_view key, value_type value)
{
Trie* crnt = &trie;

for (const auto c : key) {
crnt->m_count++;
if (!crnt->m_terminal) {
crnt->m_value = value;
}

const auto idx = convert(c);
if (crnt->m_children[idx] == nullptr) {
crnt->m_children[idx] = std::make_unique<Trie>();
}
crnt = crnt->m_children[idx].get();
}

if (crnt->m_terminal) {
return false;
}

crnt->m_value = value;
crnt->m_terminal = true;
return true;
}

static return_type get(const Trie& trie, std::string_view key)
{
const Trie* crnt = &trie;

for (const auto c : key) {
const auto idx = convert(c);
if (crnt->m_children[idx] == nullptr) {
return {};
}
crnt = crnt->m_children[idx].get();
}

if (crnt->m_terminal || crnt->m_count == 1) {
return crnt->m_value;
}

return {};
}
};

Trie m_trie;

public:
[[nodiscard]] bool set(std::string_view opt, value_type idx)
{
return Trie::set(m_trie, opt, idx);
}

[[nodiscard]] return_type get(std::string_view opt) const
{
return Trie::get(m_trie, opt);
}
};

} // namespace detail

template<class Record>
class parser
{
std::vector<option_base<Record>> m_setters;
std::vector<char> m_opt_short;
std::vector<std::string> m_opt_long;
using setter_t = option_base<Record>;
std::vector<setter_t> m_setters;

void process(std::string_view opts)
detail::option_short m_opt_short;
detail::option_long m_opt_long;

void process(const option_base<Record>& opt, std::string_view opts)
{
auto istr = std::istringstream(std::string(opts));
std::string opt;
std::string str;

m_opt_short.emplace_back('\0');
m_opt_long.emplace_back("");
while (std::getline(istr, opt, ',')) {
if (opt.size() < 2 || opt[0] != '-') {
while (std::getline(istr, str, ' ')) {
if (str.size() < 2 || str[0] != '-') {
continue;
}

if (opt[1] != '-') {
m_opt_short.back() = opt[1];
if (str[1] != '-') {
if (!m_opt_short.set(str[1], m_setters.size())) {
throw std::runtime_error("Duplicate short option");
}
} else {
m_opt_long.back() = opt.substr(2);
if (!m_opt_long.set(str.substr(2), m_setters.size())) {
throw std::runtime_error("Duplicate long option");
}
}
}

m_setters.emplace_back(opt);
}

public:

@@ -104,42 +236,27 @@ public:

requires(std::same_as<Record, typename Args::record_type> && ...)
{
constexpr auto size = sizeof...(Args);
m_opt_short.reserve(size);
m_opt_long.reserve(size);
(process(args.opts()), ...);

m_setters.reserve(size);
(m_setters.emplace_back(std::forward<Args>(args)), ...);
(process(args, args.opts()), ...);
}

bool set(char chr, Record& record, std::string_view value) const
void set(char chr, Record& record, std::string_view value) const
{
const auto itr =
std::find(std::begin(m_opt_short), std::end(m_opt_short), chr);

if (itr == m_opt_short.end()) {
return false;
const auto idx = m_opt_short.get(chr);
if (!idx.has_value()) {
throw std::runtime_error("Unknown short option");
}

const auto setter =
std::begin(m_setters) + std::distance(std::begin(m_opt_short), itr);
(*setter)(record, value);
return false;
m_setters[idx.value()](record, value);
}

bool set(std::string_view str, Record& record, std::string_view value) const
void set(std::string_view str, Record& record, std::string_view value) const
{
const auto itr =
std::find(std::begin(m_opt_long), std::end(m_opt_long), str);

if (itr == m_opt_long.end()) {
return false;
const auto idx = m_opt_long.get(str);
if (!idx.has_value()) {
throw std::runtime_error("Unknown long option");
}

const auto setter =
std::begin(m_setters) + std::distance(std::begin(m_opt_long), itr);
(*setter)(record, value);
return false;
m_setters[idx.value()](record, value);
}
};