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 | 4a5bf21bf3d47c94020a12a43ab2c7fb3094be87 |
parent | 1c22b810cdeba5e66ea485338d03cbe8964e861f |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Fri, 7 Jun 2024 17:31:28 +0200 |
Add help option
Diffstat:M | args.hpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
M | demo.cpp | | | ++++++------- |
2 files changed, 136 insertions(+), 25 deletions(-)
diff --git a/args.hpp b/args.hpp
@@ -1,12 +1,14 @@
#ifndef ARGS_HPP
#define ARGS_HPP
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <exception>
#include <format>
#include <iostream>
#include <unordered_map>
#include <vector>
class Parser {
public:
@@ -15,12 +17,13 @@ class Parser {
const int key;
const char *arg;
const uint8_t options;
const char *message;
};
enum Option {
ARG_OPTIONAL = 0x1,
HIDDEN = 0x2,
ALIAS = 0x3,
ALIAS = 0x4,
};
enum Key {
@@ -37,17 +40,19 @@ class Parser {
const option_t *options;
const parse_f parser;
const char *doc;
};
Parser(const argp_t *argp) : argp(argp) {
Parser(argp_t *argp) : argp(argp) {
int key_last;
int i = 0;
while (true) {
const auto &option = argp->options[i];
if (!option.name && !option.key) break;
const auto &opt = argp->options[i];
if (!opt.name && !opt.key) break;
if (!option.key) {
if ((option.options & ALIAS) == 0) {
if (!opt.key) {
if ((opt.options & ALIAS) == 0) {
std::cerr << "non alias without a key\n";
throw new std::runtime_error("no key");
}
@@ -57,25 +62,43 @@ class Parser {
throw new std::runtime_error("no alias");
}
// TODO: connect aliases in --help
trie.insert(option.name, key_last);
trie.insert(opt.name, key_last);
help_entries.back().push(opt.name);
} else {
if (options.count(option.key)) {
std::cerr << std::format("duplicate key {}\n", option.key);
if (options.count(opt.key)) {
std::cerr << std::format("duplicate key {}\n", opt.key);
throw new std::runtime_error("duplicate key");
}
// TODO: connect aliases in --help
if (opt.name) trie.insert(opt.name, opt.key);
options[key_last = opt.key] = &opt;
if ((opt.options & ALIAS) == 0) {
help_entries.emplace_back(opt.arg, opt.message);
if (option.name) trie.insert(option.name, option.key);
options[option.key] = &option;
if (std::isprint(opt.key))
help_entries.back().push(opt.key);
if (opt.name) help_entries.back().push(opt.name);
} else {
if (!key_last) {
std::cerr << "no option to alias\n";
throw new std::runtime_error("no alias");
}
key_last = option.key;
if (std::isprint(opt.key))
help_entries.back().push(opt.key);
if (opt.name) help_entries.back().push(opt.name);
}
}
i++;
}
std::sort(begin(help_entries), end(help_entries));
help_entries.emplace_back(nullptr, "Give this help list");
help_entries.back().push("help");
help_entries.back().push('?');
}
int parse(int argc, char *argv[], void *input) {
@@ -100,6 +123,8 @@ class Parser {
for (int j = 0; opt[j]; j++) {
const char key = opt[j];
if (key == '?') help(argv[0]);
if (!options.count(key)) goto unknown;
const auto *option = options[key];
@@ -124,8 +149,14 @@ class Parser {
const char *opt = argv[i] + 2;
const auto eq = std::strchr(opt, '=');
const int key =
trie.get(!eq ? opt : std::string(opt, eq - opt));
std::string opt_s = !eq ? opt : std::string(opt, eq - opt);
if (opt_s == "help") {
if (eq) goto excess;
help(argv[0]);
}
const int key = trie.get(opt_s);
if (!key) goto unknown;
@@ -148,7 +179,7 @@ class Parser {
}
}
// parse rest argv as normal arguments
// parse rest argv as normal arguments
for (i = i + 1; i < argc; i++) {
argp->parser(Key::ARG, argv[i], input);
args++;
@@ -222,9 +253,90 @@ class Parser {
bool terminal = false;
};
class help_entry_t {
public:
help_entry_t(const char *arg, const char *message)
: m_arg(arg), m_message(message) {}
void push(char sh) { m_opt_short.push_back(sh); }
void push(const char *lg) { m_opt_long.push_back(lg); }
const auto arg() const { return m_arg; }
const auto message() const { return m_message; }
const auto &opt_short() const { return m_opt_short; }
const auto &opt_long() const { return m_opt_long; }
bool operator<(const help_entry_t &rhs) const {
if (m_opt_long.empty() && rhs.m_opt_long.empty())
return m_opt_short.front() < rhs.m_opt_short.front();
if (m_opt_long.empty())
return m_opt_short.front() <= rhs.m_opt_long.front()[0];
if (rhs.m_opt_long.empty())
return m_opt_long.front()[0] <= rhs.m_opt_short.front();
return std::strcmp(m_opt_long.front(), rhs.m_opt_long.front()) < 0;
}
private:
const char *m_arg = nullptr;
const char *m_message = nullptr;
std::vector<char> m_opt_short;
std::vector<const char *> m_opt_long;
};
void help(const char *name) {
std::cout << std::format("Usage: {} [OPTIONS...] {}\n\n", name,
argp->doc ? argp->doc : "");
for (const auto &entry : help_entries) {
std::size_t count = 0;
bool prev = false;
std::cout << " ";
for (const char c : entry.opt_short()) {
if (!prev) prev = true;
else
std::cout << ", ", count += 2;
const std::string message = std::format("-{}", c);
std::cout << message;
count += size(message);
}
if (!prev) std::cout << " ", count += 4;
for (const auto l : entry.opt_long()) {
if (!prev) prev = true;
else
std::cout << ", ", count += 2;
std::string message = std::format("--{}", l);
if (entry.arg()) message += std::format("[={}]", entry.arg());
std::cout << message;
count += size(message);
}
static const std::size_t limit = 30;
if (count < limit) std::cout << std::string(limit - count, ' ');
if (entry.message()) {
std::cout << std::format(" {}", entry.message());
}
std::cout << std::endl;
}
exit(0);
}
const argp_t *argp;
std::unordered_map<int, const option_t *> options;
std::vector<help_entry_t> help_entries;
trie_t trie;
};
diff --git a/demo.cpp b/demo.cpp
@@ -4,7 +4,6 @@
#include <vector>
void error(const std::string &message) { std::cerr << message << std::endl; }
struct arguments_t {
const char *output_file = "";
const char *input_file = "";
@@ -42,18 +41,18 @@ using enum Parser::Option;
// clang-format off
static const Parser::option_t options[] = {
{ "output", 'o', "file", ARG_OPTIONAL},
{ 0, 'i', "file", 0},
{ "debug", 777, 0, 0},
{ "hex", 'h', 0, 0},
{ "output", 'o', "file", ARG_OPTIONAL, "Output file, default stdout"},
{ 0, 'i', "file", 0, "Input file"},
{ "debug", 777, 0, 0, "Execute program in debugging mode"},
{ "hex", 'h', 0, 0, "Output in hex format"},
{"hexadecimal", 0, 0, ALIAS},
{"relocatable", 'r', 0, 0},
{"relocatable", 'r', 0, 0, "Output in relocatable format"},
{0},
};
// clang-format on
int main(int argc, char *argv[]) {
Parser::argp_t argp = {options, parse_opt};
Parser::argp_t argp = {options, parse_opt, "doc string"};
Parser parser(&argp);
arguments_t arguments;