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 |

commit64db814bc7776fd9567faa466ffbb7813932d83d
parentbcfaf8bb42d296a36126dd9e8a2ffca7bd91f659
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateTue, 11 Jun 2024 14:49:54 +0200

Streamline error handling, added a lot of flags

Diffstat:
MCMakeLists.txt|+-
Mdemo/main.c|+-
Mdemo/main.cpp|++--
Minclude/args.h|++++++++++++++++++++++++++++++++++++++++++++++-
Minclude/args.hpp|++++++++++++++++++++++++++++++------
Msrc/args.cpp|+++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/c_bindings.cpp|+++++++++++++++++++--
Msrc/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