poafloc

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

commit 64db814bc7776fd9567faa466ffbb7813932d83d
parent bcfaf8bb42d296a36126dd9e8a2ffca7bd91f659
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Tue, 11 Jun 2024 16:49:54 +0200

Streamline error handling, added a lot of flags

Diffstat:
MCMakeLists.txt | 2+-
Mdemo/main.c | 2+-
Mdemo/main.cpp | 4++--
Minclude/args.h | 47++++++++++++++++++++++++++++++++++++++++++++++-
Minclude/args.hpp | 36++++++++++++++++++++++++++++++------
Msrc/args.cpp | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/c_bindings.cpp | 21+++++++++++++++++++--
Msrc/help.cpp | 61++++++++++++++++++++++++++++++++-----------------------------
8 files changed, 273 insertions(+), 109 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project( args - VERSION 0.0.7 + VERSION 0.0.8 DESCRIPTION "Command Line Argument Parser" LANGUAGES CXX C ) diff --git a/demo/main.c b/demo/main.c @@ -63,7 +63,7 @@ static const args_argp_t argp = { int main(int argc, char *argv[]) { arguments_t arguments = {0}; - if (args_parse(&argp, argc, argv, &arguments)) { + if (args_parse(&argp, argc, argv, 0, &arguments)) { error("There was an error while parsing arguments"); return 1; } diff --git a/demo/main.cpp b/demo/main.cpp @@ -34,7 +34,7 @@ int parse_opt(int key, const char *arg, Parser *parser) { case 'o': arguments->output_file = arg ? arg : "stdout"; break; case 'i': arguments->input_file = arg; break; case Key::ARG: arguments->args.push_back(arg); break; - case Key::ERROR: std::cerr << "handled error\n"; + case Key::ERROR: help(parser, stderr, STD_ERR); } return 0; @@ -65,7 +65,7 @@ static const argp_t argp = { int main(int argc, char *argv[]) { arguments_t arguments; - if (parse(&argp, argc, argv, &arguments)) { + if (parse(&argp, argc, argv, 0, &arguments)) { error("There was an error while parsing arguments"); return 1; } diff --git a/include/args.h b/include/args.h @@ -3,9 +3,13 @@ #ifdef __cplusplus +#include <cstdio> + #define MANGLE_ENUM(enumn, name) name #define ENUM_OPTION Option #define ENUM_KEY Key +#define ENUM_HELP Help +#define ENUM_PARSE Parse extern "C" { namespace args { @@ -15,9 +19,13 @@ typedef Parser args_parser; #else +#include <stdio.h> + #define MANGLE_ENUM(enumn, name) ARGS_##enumn##_##name #define ENUM_OPTION args_option_e #define ENUM_KEY args_key_e +#define ENUM_HELP args_help_e +#define ENUM_PARSE args_parse_e struct __Parser; typedef struct __Parser args_parser; @@ -57,11 +65,48 @@ enum ENUM_KEY { MANGLE_ENUM(KEY, ERROR) = 0x1000005, }; +enum ENUM_HELP { + + MANGLE_ENUM(HELP, SHORT_USAGE) = 0x1, + MANGLE_ENUM(HELP, USAGE) = 0x2, + MANGLE_ENUM(HELP, SEE) = 0x4, + MANGLE_ENUM(HELP, LONG) = 0x8, + + MANGLE_ENUM(HELP, EXIT_ERR) = 0x10, + MANGLE_ENUM(HELP, EXIT_OK) = 0x20, + + MANGLE_ENUM(HELP, STD_ERR) = MANGLE_ENUM(HELP, SEE) | + MANGLE_ENUM(HELP, EXIT_ERR), + + MANGLE_ENUM(HELP, STD_HELP) = MANGLE_ENUM(HELP, LONG) | + MANGLE_ENUM(HELP, EXIT_OK), + + MANGLE_ENUM(HELP, STD_USAGE) = MANGLE_ENUM(HELP, USAGE) | + MANGLE_ENUM(HELP, EXIT_ERR), +}; + +enum ENUM_PARSE { + MANGLE_ENUM(PARSE, NO_ERRS) = 0x1, + // MANGLE_ENUM(PARSE, NO_ARGS), + MANGLE_ENUM(PARSE, NO_HELP) = 0x2, + MANGLE_ENUM(PARSE, NO_EXIT) = 0x4, + MANGLE_ENUM(PARSE, SILENT) = 0x8, +}; + #if !defined __cplusplus || defined WITH_C_BINDINGS -int args_parse(const args_argp_t *argp, int argc, char *argv[], void *input); void *args_parser_input(args_parser *parser); +void args_usage(args_parser *parser); + +void argp_help(const args_parser *state, FILE *stream, unsigned flags); + +int args_parse(const args_argp_t *argp, int argc, char *argv[], unsigned flags, + void *input); + +void argp_failure(const args_parser *parser, int status, int errnum, + const char *fmt, ...); + #endif #undef MANGLE_ENUM diff --git a/include/args.hpp b/include/args.hpp @@ -3,6 +3,7 @@ #include "args.h" +#include <cstdarg> #include <string> #include <unordered_map> #include <vector> @@ -12,16 +13,29 @@ namespace args { using option_t = args_option_t; using argp_t = args_argp_t; -int parse(const argp_t *argp, int argc, char *argv[], void *input) noexcept; +int parse(const argp_t *argp, int argc, char *argv[], unsigned flags, + void *input) noexcept; + +void usage(const Parser *parser); +void help(const Parser *parser, FILE *stream, unsigned flags); + +void failure(const Parser *parser, int status, int errnum, const char *fmt, + va_list args); + +void failure(const Parser *parser, int status, int errnum, const char *fmt, + ...); class Parser { public: void *input() const { return m_input; } + const char *name() const { return m_name; } + unsigned flags() const { return m_flags; } private: - friend int parse(const argp_t *, int, char **, void *) noexcept; + friend int parse(const argp_t *, int, char **, unsigned, void *) noexcept; + friend void help(const Parser *parser, FILE *stream, unsigned flags); - Parser(const argp_t *argp, void *input); + Parser(const argp_t *argp, unsigned flags, void *input); Parser(const Parser &) = delete; Parser(Parser &&) = delete; Parser &operator=(const Parser &) = delete; @@ -30,9 +44,16 @@ class Parser { int parse(int argc, char *argv[], void *input); - void print_usage(const char *name) const; - void help(const char *name) const; - void usage(const char *name) const; + int handle_unknown(bool shrt, const char *argv); + int handle_missing(bool shrt, const char *argv); + int handle_excess(bool shrt, const char *argv); + + void print_usage(FILE *stream) const; + void help(FILE *stream) const; + void usage(FILE *stream) const; + void see(FILE *stream) const; + + static const char *basename(const char *name); struct help_entry_t { help_entry_t(const char *arg, const char *message, int group, @@ -75,8 +96,11 @@ class Parser { }; const argp_t *argp; + unsigned m_flags; void *m_input; + const char *m_name; + std::unordered_map<int, const option_t *> options; std::vector<help_entry_t> help_entries; trie_t trie; diff --git a/src/args.cpp b/src/args.cpp @@ -8,12 +8,42 @@ namespace args { -int parse(const argp_t *argp, int argc, char *argv[], void *input) noexcept { - Parser parser(argp, input); +int parse(const argp_t *argp, int argc, char *argv[], unsigned flags, + void *input) noexcept { + Parser parser(argp, flags, input); return parser.parse(argc, argv, &parser); } -Parser::Parser(const argp_t *argp, void *input) : argp(argp), m_input(input) { +void usage(const Parser *parser) { help(parser, stderr, Help::STD_USAGE); } + +void help(const Parser *parser, FILE *stream, unsigned flags) { + if (!parser || !stream) return; + + if (flags & LONG) parser->help(stream); + else if (flags & USAGE) parser->usage(stream); + else if (flags & SEE) parser->see(stream); + + if (parser->flags() & NO_EXIT) return; + if (flags & EXIT_ERR) exit(2); + if (flags & EXIT_OK) exit(0); +} + +void failure(const Parser *parser, int status, int errnum, const char *fmt, + std::va_list args) { + std::fprintf(stderr, "%s: ", parser->name()); + std::vfprintf(stderr, fmt, args); +} + +void failure(const Parser *parser, int status, int errnum, const char *fmt, + ...) { + std::va_list args; + va_start(args, fmt); + failure(parser, status, errnum, fmt, args); + va_end(args); +} + +Parser::Parser(const argp_t *argp, unsigned flags, void *input) + : argp(argp), m_flags(flags), m_input(input) { int group = 0, key_last = 0; bool hidden = false; @@ -28,7 +58,7 @@ Parser::Parser(const argp_t *argp, void *input) : argp(argp), m_input(input) { } if (!opt.key) { - if ((opt.flags & ALIAS) == 0) { + if (!(opt.flags & ALIAS)) { // non alias without a key, silently ignoring continue; } @@ -58,16 +88,14 @@ Parser::Parser(const argp_t *argp, void *input) : argp(argp), m_input(input) { bool arg_opt = opt.flags & Option::ARG_OPTIONAL; - if ((opt.flags & ALIAS) == 0) { + if (!(opt.flags & ALIAS)) { if ((hidden = opt.flags & Option::HIDDEN)) continue; help_entries.emplace_back(opt.arg, opt.message, group, arg_opt); if (opt.name) help_entries.back().push(opt.name); - if (std::isprint(opt.key)) { - help_entries.back().push(opt.key); - } + if (std::isprint(opt.key)) help_entries.back().push(opt.key); } else { if (!key_last) { // nothing to alias, silently ignoring @@ -78,26 +106,28 @@ Parser::Parser(const argp_t *argp, void *input) : argp(argp), m_input(input) { if (opt.flags & Option::HIDDEN) continue; if (opt.name) help_entries.back().push(opt.name); - if (std::isprint(opt.key)) { - help_entries.back().push(opt.key); - } + if (std::isprint(opt.key)) help_entries.back().push(opt.key); } } } - help_entries.emplace_back(nullptr, "Give this help list", -1); - help_entries.back().push("help"); - help_entries.back().push('?'); + if (!(m_flags & NO_HELP)) { + help_entries.emplace_back(nullptr, "Give this help list", -1); + help_entries.back().push("help"); + help_entries.back().push('?'); - help_entries.emplace_back(nullptr, "Give a short usage message", -1); - help_entries.back().push("usage"); + help_entries.emplace_back(nullptr, "Give a short usage message", -1); + help_entries.back().push("usage"); + } std::sort(begin(help_entries), end(help_entries)); } int Parser::parse(int argc, char *argv[], void *input) { - int args = 0, i; + const bool is_help = !(m_flags & NO_HELP); + int args = 0, err_code = 0, i; + m_name = basename(argv[0]); argp->parse(Key::INIT, 0, this); for (i = 1; i < argc; i++) { @@ -117,64 +147,76 @@ int Parser::parse(int argc, char *argv[], void *input) { for (int j = 0; opt[j]; j++) { const char key = opt[j]; - if (key == '?') help(argv[0]); + if (is_help && key == '?') help(stderr); - if (!options.count(key)) goto unknown; - const auto *option = options[key]; - - const char *arg = nullptr; - if (option->arg) { - if (opt[j + 1] != 0) { - // rest of the line is option argument - arg = opt + j + 1; - } else if ((option->flags & ARG_OPTIONAL) == 0) { - // next argv is option argument - if (i == argc) goto missing; - arg = argv[++i]; - } + if (!options.count(key)) { + err_code = handle_unknown(1, argv[i]); + goto error; } - argp->parse(key, arg, this); - - // if last option required argument we are done - if (arg) break; + const auto *option = options[key]; + bool is_opt = option->flags & ARG_OPTIONAL; + if (!option->arg) argp->parse(key, nullptr, this); + if (opt[j + 1] != 0) { + argp->parse(key, opt + j + 1, this); + break; + } else if (!is_opt) argp->parse(key, nullptr, this); + else if (i + 1 != argc) { + argp->parse(key, argv[++i], this); + break; + } else { + err_code = handle_missing(1, argv[i]); + goto error; + } } } else { // long option const char *opt = argv[i] + 2; - const auto eq = std::strchr(opt, '='); + const auto is_eq = std::strchr(opt, '='); - std::string opt_s = !eq ? opt : std::string(opt, eq - opt); + std::string opt_s = !is_eq ? opt : std::string(opt, is_eq - opt); - if (opt_s == "help") { - if (eq) goto excess; - help(argv[0]); + if (is_help && opt_s == "help") { + if (is_eq) { + err_code = handle_excess(0, argv[i]); + goto error; + } + help(stderr); + continue; } - if (opt_s == "usage") { - if (eq) goto excess; - usage(argv[0]); + if (is_help && opt_s == "usage") { + if (is_eq) { + err_code = handle_excess(0, argv[i]); + goto error; + } + usage(stderr); + continue; } const int key = trie.get(opt_s.data()); - if (!key) goto unknown; + if (!key) { + err_code = handle_unknown(0, argv[i]); + goto error; + } const auto *option = options[key]; const char *arg = nullptr; - if (!option->arg && eq) goto excess; - if (option->arg) { - if (eq) { - // everything after = is option argument - arg = eq + 1; - } else if ((option->flags & ARG_OPTIONAL) == 0) { - // next argv is option argument - if (i == argc) goto missing; - arg = argv[++i]; - } + if (!option->arg && is_eq) { + err_code = handle_excess(0, argv[i]); + goto error; } - argp->parse(key, arg, this); + bool is_opt = option->flags & ARG_OPTIONAL; + if (!option->arg) argp->parse(key, nullptr, this); + else if (is_eq) argp->parse(key, is_eq + 1, this); + else if (is_opt) argp->parse(key, nullptr, this); + else if (i + 1 != argc) argp->parse(key, argv[++i], this); + else { + err_code = handle_missing(0, argv[i]); + goto error; + } } } @@ -191,20 +233,53 @@ int Parser::parse(int argc, char *argv[], void *input) { return 0; -unknown: - std::cerr << std::format("unknown option {}\n", argv[i]); - argp->parse(Key::ERROR, 0, this); - return 1; +error: + return err_code; +} + +int Parser::handle_unknown(bool shrt, const char *argv) { + if (m_flags & NO_ERRS) return argp->parse(Key::ERROR, 0, this); + + static const char *const unknown_fmt[2] = { + "unrecognized option '-%s'\n", + "invalid option -- '%s'\n", + }; -missing: - std::cerr << std::format("option {} missing a value\n", argv[i]); - argp->parse(Key::ERROR, 0, this); - return 2; + failure(this, 1, 0, unknown_fmt[shrt], argv + 1); + see(stderr); + + if(m_flags & NO_EXIT) return 1; + exit(1); +} + +int Parser::handle_missing(bool shrt, const char *argv) { + if (m_flags & NO_ERRS) return argp->parse(Key::ERROR, 0, this); + + static const char *const missing_fmt[2] = { + "option '-%s' requires an argument\n", + "option requires an argument -- '%s'\n", + }; + + failure(this, 2, 0, missing_fmt[shrt], argv + 1); + see(stderr); + + if(m_flags & NO_EXIT) return 2; + exit(2); +} + +int Parser::handle_excess(bool shrt, const char *argv) { + if (m_flags & NO_ERRS) return argp->parse(Key::ERROR, 0, this); + + failure(this, 3, 0, "option '%s' doesn't allow an argument\n", argv); + see(stderr); + + if(m_flags & NO_EXIT) return 3; + exit(3); +} -excess: - std::cerr << std::format("option {} don't require a value\n", argv[i]); - argp->parse(Key::ERROR, 0, this); - return 3; +const char *Parser::basename(const char *name) { + const char *name_sh = std::strrchr(name, '/'); + return name_sh ? name_sh + 1 : name; } } // namespace args diff --git a/src/c_bindings.cpp b/src/c_bindings.cpp @@ -1,12 +1,29 @@ #include "args.h" #include "args.hpp" +#include <cstdarg> + namespace args { -int args_parse(const args_argp_t *argp, int argc, char *argv[], void *input) { - return parse(argp, argc, argv, input); +int args_parse(const args_argp_t *argp, int argc, char *argv[], unsigned flags, + void *input) { + return parse(argp, argc, argv, flags, input); } void *args_parser_input(args_parser *parser) { return parser->input(); } +void args_usage(args_parser *parser) { return usage(parser); } + +void argp_help(const args_parser *parser, FILE *stream, unsigned flags) { + help(parser, stream, flags); +} + +void argp_failure(const args_parser *parser, int status, int errnum, + const char *fmt, ...) { + std::va_list args; + va_start(args, fmt); + failure(parser, status, errnum, fmt, args); + va_end(args); +} + } // namespace args diff --git a/src/help.cpp b/src/help.cpp @@ -2,7 +2,6 @@ #include <cstring> #include <format> -#include <iostream> #include <sstream> namespace args { @@ -31,21 +30,22 @@ bool Parser::help_entry_t::operator<(const help_entry_t &rhs) const { return std::strcmp(opt_long.front(), rhs.opt_long.front()) < 0; } -void Parser::print_usage(const char *name) const { +void Parser::print_usage(FILE *stream) const { if (argp->doc) { std::istringstream iss(argp->doc); std::string s; std::getline(iss, s, '\n'); - std::cout << " " << s; + std::fprintf(stream, " %s", s.c_str()); while (std::getline(iss, s, '\n')) { - std::cout << std::format("\n or: {} [OPTIONS...] {}", name, s); + std::fprintf(stream, "\n or: %s [OPTIONS...] %s", m_name, + s.c_str()); } } } -void Parser::help(const char *name) const { +void Parser::help(FILE *stream) const { std::string m1, m2; if (argp->message) { std::istringstream iss(argp->message); @@ -53,18 +53,19 @@ void Parser::help(const char *name) const { std::getline(iss, m2, '\v'); } - std::cout << std::format("Usage: {} [OPTIONS...]", name); - print_usage(name); - if (!m1.empty()) std::cout << "\n" << m1; - std::cout << "\n\n"; + std::fprintf(stream, "Usage: %s [OPTIONS...]", m_name); + print_usage(stream); + + if (!m1.empty()) std::fprintf(stream, "\n%s", m1.c_str()); + std::fprintf(stream, "\n\n"); bool first = true; for (const auto &entry : help_entries) { bool prev = false; if (entry.opt_short.empty() && entry.opt_long.empty()) { - if (!first) std::cout << "\n"; - if (entry.message) std::cout << " " << entry.message << ":\n"; + if (!first) std::putc('\n', stream); + if (entry.message) std::fprintf(stream, " %s:\n", entry.message); continue; } @@ -97,50 +98,48 @@ void Parser::help(const char *name) const { else message += std::format("={}", entry.arg); } - static const std::size_t limit = 30; + static const int limit = 30; if (size(message) < limit) { message += std::string(limit - size(message), ' '); } - std::cout << message; + std::fprintf(stream, "%s", message.c_str()); if (entry.message) { std::istringstream iss(entry.message); std::size_t count = 0; std::string s; - std::cout << " "; + std::fprintf(stream, " "); while (iss >> s) { count += size(s); if (count > limit) { - std::cout << std::endl << std::string(limit + 5, ' '); + std::fprintf(stream, "\n%*c", limit + 5, ' '); count = size(s); } - std::cout << s << " "; + std::fprintf(stream, "%s ", s.c_str()); } } - std::cout << std::endl; + std::putc('\n', stream); } - if (!m2.empty()) std::cout << "\n" << m2 << "\n"; - - exit(0); + if (!m2.empty()) std::fprintf(stream, "\n%s\n", m2.c_str()); } -void Parser::usage(const char *name) const { +void Parser::usage(FILE *stream) const { static const std::size_t limit = 60; static std::size_t count = 0; - static const auto print = [](const std::string &message) { + static const auto print = [&stream](const std::string &message) { if (count + size(message) > limit) { - std::cout << "\n "; + std::fprintf(stream, "\n "); count = 6; } - std::cout << message; + std::fprintf(stream, "%s", message.c_str()); count += size(message); }; - std::string message = std::format("Usage: {}", name); + std::string message = std::format("Usage: {}", m_name); message += " [-"; for (const auto &entry : help_entries) { @@ -151,7 +150,7 @@ void Parser::usage(const char *name) const { } message += "]"; - std::cout << message; + std::fprintf(stream, "%s", message.c_str()); count = size(message); for (const auto &entry : help_entries) { @@ -177,10 +176,14 @@ void Parser::usage(const char *name) const { } } - print_usage(name); - std::cout << std::endl; + print_usage(stream); + std::putc('\n', stream); +} - exit(0); +void Parser::see(FILE *stream) const { + std::fprintf(stream, + "Try '%s --help' or '%s --usage' for more information\n", + m_name, m_name); } } // namespace args