poafloc

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

poafloc.cpp (8737B)


      1 #include "poafloc.hpp"
      2 
      3 #include <algorithm>
      4 #include <cstring>
      5 #include <format>
      6 #include <iostream>
      7 #include <sstream>
      8 
      9 namespace poafloc {
     10 
     11 int parse(const arg_t *argp, int argc, char *argv[], unsigned flags,
     12           void *input) noexcept {
     13     Parser parser(argp, flags, input);
     14     return parser.parse(argc, argv);
     15 }
     16 
     17 void usage(const Parser *parser) { help(parser, stderr, Help::STD_USAGE); }
     18 void help(const Parser *parser, FILE *stream, unsigned flags) {
     19     if (!parser || !stream) return;
     20 
     21     if (flags & LONG) parser->help(stream);
     22     else if (flags & USAGE) parser->usage(stream);
     23     else if (flags & SEE) parser->see(stream);
     24 
     25     if (parser->flags() & NO_EXIT) return;
     26     if (flags & EXIT_ERR) exit(2);
     27     if (flags & EXIT_OK) exit(0);
     28 }
     29 
     30 void failure(const Parser *parser, int status, int errnum, const char *fmt,
     31              std::va_list args) {
     32     (void)errnum;
     33     std::fprintf(stderr, "%s: ", parser->name());
     34     std::vfprintf(stderr, fmt, args);
     35     std::putc('\n', stderr);
     36     if (status) exit(status);
     37 }
     38 
     39 void failure(const Parser *parser, int status, int errnum, const char *fmt,
     40              ...) {
     41     std::va_list args;
     42     va_start(args, fmt);
     43     failure(parser, status, errnum, fmt, args);
     44     va_end(args);
     45 }
     46 
     47 Parser::Parser(const arg_t *argp, unsigned flags, void *input)
     48     : argp(argp), m_flags(flags), m_input(input) {
     49     int group = 0, key_last = 0;
     50     bool hidden = false;
     51 
     52     for (int i = 0; true; i++) {
     53         const auto &opt = argp->options[i];
     54         if (!opt.name && !opt.key && !opt.message) break;
     55 
     56         if (!opt.name && !opt.key) {
     57             group = opt.group ? opt.group : group + 1;
     58             help_entries.emplace_back(nullptr, opt.message, group);
     59             continue;
     60         }
     61 
     62         if (!opt.key) {
     63             // non alias without a key, silently ignoring
     64             if (!(opt.flags & ALIAS)) continue;
     65 
     66             // nothing to alias, silently ignoring
     67             if (!key_last) continue;
     68 
     69             // option not valid, silently ignoring
     70             if (!trie.insert(opt.name, key_last)) continue;
     71 
     72             if (hidden) continue;
     73             if (opt.flags & Option::HIDDEN) continue;
     74 
     75             help_entries.back().push(opt.name);
     76         } else {
     77             // duplicate key, silently ignoring
     78             if (options.count(opt.key)) continue;
     79 
     80             if (opt.name) trie.insert(opt.name, opt.key);
     81             options[key_last = opt.key] = &opt;
     82 
     83             bool arg_opt = opt.flags & Option::ARG_OPTIONAL;
     84 
     85             if (!(opt.flags & ALIAS)) {
     86                 if ((hidden = opt.flags & Option::HIDDEN)) continue;
     87 
     88                 help_entries.emplace_back(opt.arg, opt.message, group,
     89                                           arg_opt);
     90 
     91                 if (opt.name) help_entries.back().push(opt.name);
     92                 if (std::isprint(opt.key)) help_entries.back().push(opt.key);
     93             } else {
     94                 // nothing to alias, silently ignoring
     95                 if (!key_last) continue;
     96 
     97                 if (hidden) continue;
     98                 if (opt.flags & Option::HIDDEN) continue;
     99 
    100                 if (opt.name) help_entries.back().push(opt.name);
    101                 if (std::isprint(opt.key)) help_entries.back().push(opt.key);
    102             }
    103         }
    104     }
    105 
    106     if (!(m_flags & NO_HELP)) {
    107         help_entries.emplace_back(nullptr, "Give this help list", -1);
    108         help_entries.back().push("help");
    109         help_entries.back().push('?');
    110 
    111         help_entries.emplace_back(nullptr, "Give a short usage message", -1);
    112         help_entries.back().push("usage");
    113     }
    114 
    115     std::sort(begin(help_entries), end(help_entries));
    116 }
    117 
    118 int Parser::parse(int argc, char *argv[]) {
    119     std::vector<const char *> args;
    120     int arg_cnt = 0, err_code = 0, i;
    121 
    122     const bool is_help = !(m_flags & NO_HELP);
    123     const bool is_error = !(m_flags & NO_ERRS);
    124 
    125     m_name = basename(argv[0]);
    126 
    127     argp->parse(Key::INIT, 0, this);
    128 
    129     for (i = 1; i < argc; i++) {
    130         if (argv[i][0] != '-') {
    131             if (m_flags & IN_ORDER) argp->parse(Key::ARG, argv[i], this);
    132             else args.push_back(argv[i]);
    133             arg_cnt++;
    134             continue;
    135         }
    136 
    137         // stop parsing options, rest are normal arguments
    138         if (!std::strcmp(argv[i], "--")) break;
    139 
    140         if (argv[i][1] != '-') { // short option
    141             const char *opt = argv[i] + 1;
    142 
    143             // loop over ganged options
    144             for (int j = 0; opt[j]; j++) {
    145                 const char key = opt[j];
    146 
    147                 if (is_help && key == '?') {
    148                     if (is_error) ::poafloc::help(this, stderr, STD_HELP);
    149                     continue;
    150                 }
    151 
    152                 if (!options.count(key)) {
    153                     err_code = handle_unknown(1, argv[i]);
    154                     goto error;
    155                 }
    156 
    157                 const auto *option = options[key];
    158                 bool is_opt = option->flags & ARG_OPTIONAL;
    159                 if (!option->arg) argp->parse(key, nullptr, this);
    160                 if (opt[j + 1] != 0) {
    161                     argp->parse(key, opt + j + 1, this);
    162                     break;
    163                 } else if (is_opt) argp->parse(key, nullptr, this);
    164                 else if (i + 1 != argc) {
    165                     argp->parse(key, argv[++i], this);
    166                     break;
    167                 } else {
    168                     err_code = handle_missing(1, argv[i]);
    169                     goto error;
    170                 }
    171             }
    172         } else { // long option
    173             const char *opt = argv[i] + 2;
    174             const auto is_eq = std::strchr(opt, '=');
    175 
    176             std::string opt_s = !is_eq ? opt : std::string(opt, is_eq - opt);
    177 
    178             if (is_help && opt_s == "help") {
    179                 if (is_eq) {
    180                     err_code = handle_excess(argv[i]);
    181                     goto error;
    182                 }
    183                 if (is_error) ::poafloc::help(this, stderr, STD_HELP);
    184                 continue;
    185             }
    186 
    187             if (is_help && opt_s == "usage") {
    188                 if (is_eq) {
    189                     err_code = handle_excess(argv[i]);
    190                     goto error;
    191                 }
    192                 if (is_error) ::poafloc::help(this, stderr, STD_USAGE);
    193                 continue;
    194             }
    195 
    196             const int key = trie.get(opt_s.data());
    197             if (!key) {
    198                 err_code = handle_unknown(0, argv[i]);
    199                 goto error;
    200             }
    201 
    202             const auto *option = options[key];
    203             if (!option->arg && is_eq) {
    204                 err_code = handle_excess(argv[i]);
    205                 goto error;
    206             }
    207 
    208             bool is_opt = option->flags & ARG_OPTIONAL;
    209             if (!option->arg) argp->parse(key, nullptr, this);
    210             else if (is_eq) argp->parse(key, is_eq + 1, this);
    211             else if (is_opt) argp->parse(key, nullptr, this);
    212             else if (i + 1 != argc) argp->parse(key, argv[++i], this);
    213             else {
    214                 err_code = handle_missing(0, argv[i]);
    215                 goto error;
    216             }
    217         }
    218     }
    219 
    220     // parse previous arguments if IN_ORDER is not set
    221     for (const auto arg : args) {
    222         argp->parse(Key::ARG, arg, this);
    223     }
    224 
    225     // parse rest argv as normal arguments
    226     for (i = i + 1; i < argc; i++) {
    227         argp->parse(Key::ARG, argv[i], this);
    228         arg_cnt++;
    229     }
    230 
    231     if (!arg_cnt) argp->parse(Key::NO_ARGS, 0, this);
    232 
    233     argp->parse(Key::END, 0, this);
    234     argp->parse(Key::SUCCESS, 0, this);
    235 
    236     return 0;
    237 
    238 error:
    239     return err_code;
    240 }
    241 
    242 int Parser::handle_unknown(bool shrt, const char *argv) {
    243     if (m_flags & NO_ERRS) return argp->parse(Key::ERROR, 0, this);
    244 
    245     static const char *const unknown_fmt[2] = {
    246         "unrecognized option '-%s'\n",
    247         "invalid option -- '%s'\n",
    248     };
    249 
    250     failure(this, 1, 0, unknown_fmt[shrt], argv + 1);
    251     see(stderr);
    252 
    253     if (m_flags & NO_EXIT) return 1;
    254     exit(1);
    255 }
    256 
    257 int Parser::handle_missing(bool shrt, const char *argv) {
    258     if (m_flags & NO_ERRS) return argp->parse(Key::ERROR, 0, this);
    259 
    260     static const char *const missing_fmt[2] = {
    261         "option '-%s' requires an argument\n",
    262         "option requires an argument -- '%s'\n",
    263     };
    264 
    265     failure(this, 2, 0, missing_fmt[shrt], argv + 1);
    266     see(stderr);
    267 
    268     if (m_flags & NO_EXIT) return 2;
    269     exit(2);
    270 }
    271 
    272 int Parser::handle_excess(const char *argv) {
    273     if (m_flags & NO_ERRS) return argp->parse(Key::ERROR, 0, this);
    274 
    275     failure(this, 3, 0, "option '%s' doesn't allow an argument\n", argv);
    276     see(stderr);
    277 
    278     if (m_flags & NO_EXIT) return 3;
    279     exit(3);
    280 }
    281 
    282 const char *Parser::basename(const char *name) {
    283     const char *name_sh = std::strrchr(name, '/');
    284     return name_sh ? name_sh + 1 : name;
    285 }
    286 
    287 } // namespace args