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:
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