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 64db814bc7776fd9567faa466ffbb7813932d83d
parent bcfaf8bb42d296a36126dd9e8a2ffca7bd91f659
author Dimitrije Dobrota <mail@dimitrijedobrota.com>
date Tue, 11 Jun 2024 14:49:54 +0200

Streamline error handling, added a lot of flags

Diffstat:
M CMakeLists.txt | + -
M demo/main.c | + -
M demo/main.cpp | ++ --
M include/args.h | ++++++++++++++++++++++++++++++++++++++++++++++ -
M include/args.hpp | ++++++++++++++++++++++++++++++ ------
M src/args.cpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ ---------------------------
M src/c_bindings.cpp | +++++++++++++++++++ --
M src/help.cpp | ++++++++++++++++++++++++++++++++ -----------------------------

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