poafloc

poafloc - Parser Of Arguments For Lines Of Commands
git clone git://git.dimitrijedobrota.com/poafloc.git
Log | Files | Refs | README | LICENSE

commit 4a5bf21bf3d47c94020a12a43ab2c7fb3094be87
parent 1c22b810cdeba5e66ea485338d03cbe8964e861f
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Fri,  7 Jun 2024 19:31:28 +0200

Add help option

Diffstat:
Margs.hpp | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mdemo.cpp | 13++++++-------
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;