poafloc

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

poafloc.cpp (9242B)


      1 #include <algorithm>
      2 #include <cstring>
      3 #include <format>
      4 #include <iostream>
      5 #include <sstream>
      6 
      7 #include "poafloc/poafloc.hpp"
      8 
      9 namespace poafloc {
     10 
     11 int parse(const arg_t* argp,
     12           int argc,
     13           char* argv[],
     14           unsigned flags,
     15           void* input) noexcept
     16 {
     17   Parser parser(argp, flags, input);
     18   return parser.parse(static_cast<std::size_t>(argc), argv);
     19 }
     20 
     21 void usage(const Parser* parser)
     22 {
     23   help(parser, stderr, Help::STD_USAGE);
     24 }
     25 
     26 void help(const Parser* parser, FILE* stream, unsigned flags)
     27 {
     28   if ((parser == nullptr) || (stream == nullptr)) return;
     29 
     30   if ((flags & LONG) != 0U) parser->help(stream);
     31   else if ((flags & USAGE) != 0U) parser->usage(stream);
     32   else if ((flags & SEE) != 0U) parser->see(stream);
     33 
     34   if ((parser->flags() & NO_EXIT) != 0U) return;
     35 
     36   if ((flags & EXIT_ERR) != 0U) exit(2);
     37   if ((flags & EXIT_OK) != 0U) exit(0);
     38 }
     39 
     40 void failure(const Parser* parser,
     41              int status,
     42              int errnum,
     43              const char* fmt,
     44              std::va_list args)
     45 {
     46   (void)errnum;
     47 
     48   std::cerr << parser->name() << ": ";
     49   std::ignore = std::vfprintf(stderr, fmt, args);  // NOLINT
     50   std::cerr << '\n';
     51 
     52   if (status != 0) exit(status);
     53 }
     54 
     55 void failure(
     56     const Parser* parser, int status, int errnum, const char* fmt, ...)
     57 {
     58   std::va_list args;
     59   va_start(args, fmt);
     60   failure(parser, status, errnum, fmt, args);
     61   va_end(args);
     62 }
     63 
     64 Parser::Parser(const arg_t* argp, unsigned flags, void* input)
     65     : m_argp(argp)
     66     , m_flags(flags)
     67     , m_input(input)
     68 {
     69   int group    = 0;
     70   int key_last = 0;
     71   bool hidden  = false;
     72 
     73   for (int i = 0; true; i++)
     74   {
     75     const auto& opt = argp->options[i];  // NOLINT
     76 
     77     if ((opt.name == nullptr) && (opt.key == 0) && (opt.message == nullptr))
     78       break;
     79 
     80     if ((opt.name == nullptr) && (opt.key == 0))
     81     {
     82       group = opt.group != 0U ? opt.group : group + 1;
     83       m_help_entries.emplace_back(nullptr, opt.message, group);
     84       continue;
     85     }
     86 
     87     if (opt.key == 0)
     88     {
     89       // non alias without a key, silently ignoring
     90       if ((opt.flags & ALIAS) == 0) continue;
     91 
     92       // nothing to alias, silently ignoring
     93       if (key_last == 0) continue;
     94 
     95       // option not valid, silently ignoring
     96       if (!m_trie.insert(opt.name, key_last)) continue;
     97 
     98       if (hidden) continue;
     99       if ((opt.flags & Option::HIDDEN) != 0U) continue;
    100 
    101       m_help_entries.back().push(opt.name);
    102     }
    103     else
    104     {
    105       // duplicate key, silently ignoring
    106       if (m_options.contains(opt.key)) continue;
    107 
    108       if (opt.name != nullptr) m_trie.insert(opt.name, opt.key);
    109       m_options[key_last = opt.key] = &opt;
    110 
    111       bool const arg_opt = (opt.flags & Option::ARG_OPTIONAL) != 0U;
    112 
    113       if ((opt.flags & ALIAS) == 0U)
    114       {
    115         hidden = (opt.flags & Option::HIDDEN) != 0;
    116         if (hidden) continue;
    117 
    118         m_help_entries.emplace_back(opt.arg, opt.message, group, arg_opt);
    119 
    120         if (opt.name != nullptr) m_help_entries.back().push(opt.name);
    121 
    122         if (std::isprint(opt.key) != 0U)
    123         {
    124           m_help_entries.back().push(static_cast<char>(opt.key & 0xFF));
    125         }
    126       }
    127       else
    128       {
    129         // nothing to alias, silently ignoring
    130         if (key_last == 0) continue;
    131         if (hidden) continue;
    132         if ((opt.flags & Option::HIDDEN) != 0U) continue;
    133 
    134         if (opt.name != nullptr) m_help_entries.back().push(opt.name);
    135         if (std::isprint(opt.key) != 0U)
    136         {
    137           m_help_entries.back().push(static_cast<char>(opt.key & 0xFF));
    138         }
    139       }
    140     }
    141   }
    142 
    143   if ((m_flags & NO_HELP) == 0U)
    144   {
    145     m_help_entries.emplace_back(nullptr, "Give this help list", -1);
    146     m_help_entries.back().push("help");
    147     m_help_entries.back().push('?');
    148     m_help_entries.emplace_back(nullptr, "Give a short usage message", -1);
    149     m_help_entries.back().push("usage");
    150   }
    151 
    152   std::sort(begin(m_help_entries), end(m_help_entries));
    153 }
    154 
    155 int Parser::parse(std::size_t argc, char* argv[])
    156 {
    157   const std::span args(argv, argv + argc);
    158 
    159   std::vector<const char*> args_free;
    160   std::size_t idx = 0;
    161   int arg_cnt     = 0;
    162   int err_code    = 0;
    163 
    164   const bool is_help  = (m_flags & NO_HELP) == 0U;
    165   const bool is_error = (m_flags & NO_ERRS) == 0U;
    166 
    167   m_name = basename(args[0]);
    168   m_argp->parse(Key::INIT, nullptr, this);
    169 
    170   for (idx = 1; idx < argc; idx++)
    171   {
    172     if (args[idx][0] != '-')
    173     {
    174       if ((m_flags & IN_ORDER) != 0U) m_argp->parse(Key::ARG, args[idx], this);
    175       else args_free.push_back(args[idx]);
    176 
    177       arg_cnt++;
    178       continue;
    179     }
    180 
    181     // stop parsing options, rest are normal arguments
    182     if (std::strcmp(args[idx], "--") == 0) break;
    183 
    184     if (args[idx][1] != '-')
    185     {  // short option
    186       const std::string opt = args[idx] + 1;
    187 
    188       // loop over ganged options
    189       for (std::size_t j = 0; opt[j] != 0; j++)
    190       {
    191         const char key = opt[j];
    192 
    193         if (is_help && key == '?')
    194         {
    195           if (is_error) ::poafloc::help(this, stderr, STD_HELP);
    196           continue;
    197         }
    198 
    199         if (!m_options.contains(key))
    200         {
    201           err_code = handle_unknown(false, args[idx]);
    202           goto error;
    203         }
    204 
    205         const auto* option = m_options[key];
    206         bool const is_opt  = (option->flags & ARG_OPTIONAL) != 0;
    207 
    208         if (option->arg == nullptr)
    209         {
    210           m_argp->parse(key, nullptr, this);
    211           continue;
    212         }
    213 
    214         if (opt[j + 1] != 0)
    215         {
    216           m_argp->parse(key, opt.substr(j + 1).c_str(), this);
    217           break;
    218         }
    219 
    220         if (is_opt)
    221         {
    222           m_argp->parse(key, nullptr, this);
    223           continue;
    224         }
    225 
    226         if (idx + 1 != argc)
    227         {
    228           m_argp->parse(key, args[++idx], this);
    229           break;
    230         }
    231 
    232         err_code = handle_missing(true, args[idx]);
    233         goto error;
    234       }
    235     }
    236     else
    237     {  // long option
    238       const std::string tmp = args[idx] + 2;
    239       const auto pos        = tmp.find_first_of('=');
    240       const auto opt        = tmp.substr(0, pos);
    241       const auto arg        = tmp.substr(pos + 1);
    242 
    243       if (is_help && opt == "help")
    244       {
    245         if (pos != std::string::npos)
    246         {
    247           err_code = handle_excess(args[idx]);
    248           goto error;
    249         }
    250 
    251         if (!is_error) continue;
    252         ::poafloc::help(this, stderr, STD_HELP);
    253       }
    254 
    255       if (is_help && opt == "usage")
    256       {
    257         if (pos != std::string::npos)
    258         {
    259           err_code = handle_excess(args[idx]);
    260           goto error;
    261         }
    262 
    263         if (!is_error) continue;
    264         ::poafloc::help(this, stderr, STD_USAGE);
    265       }
    266 
    267       const int key = m_trie.get(opt);
    268       if (key == 0)
    269       {
    270         err_code = handle_unknown(false, args[idx]);
    271         goto error;
    272       }
    273 
    274       const auto* option = m_options[key];
    275       if (pos != std::string::npos && option->arg == nullptr)
    276       {
    277         err_code = handle_excess(args[idx]);
    278         goto error;
    279       }
    280 
    281       const bool is_opt = (option->flags & ARG_OPTIONAL) != 0;
    282 
    283       if (option->arg == nullptr)
    284       {
    285         m_argp->parse(key, nullptr, this);
    286         continue;
    287       }
    288 
    289       if (pos != std::string::npos)
    290       {
    291         m_argp->parse(key, arg.c_str(), this);
    292         continue;
    293       }
    294 
    295       if (is_opt)
    296       {
    297         m_argp->parse(key, nullptr, this);
    298         continue;
    299       }
    300 
    301       if (idx + 1 != argc)
    302       {
    303         m_argp->parse(key, args[++idx], this);
    304         continue;
    305       }
    306 
    307       err_code = handle_missing(false, args[idx]);
    308       goto error;
    309     }
    310   }
    311 
    312   // parse previous arguments if IN_ORDER is not set
    313   for (const auto* const arg : args_free) m_argp->parse(Key::ARG, arg, this);
    314 
    315   // parse rest argv as normal arguments
    316   for (idx = idx + 1; idx < argc; idx++)
    317   {
    318     m_argp->parse(Key::ARG, args[idx], this);
    319     arg_cnt++;
    320   }
    321 
    322   if (arg_cnt == 0) m_argp->parse(Key::NO_ARGS, nullptr, this);
    323 
    324   m_argp->parse(Key::END, nullptr, this);
    325   m_argp->parse(Key::SUCCESS, nullptr, this);
    326 
    327   return 0;
    328 
    329 error:
    330   m_argp->parse(Key::ERROR, nullptr, this);
    331   return err_code;
    332 }
    333 
    334 int Parser::handle_unknown(bool shrt, const char* argv)
    335 {
    336   if ((m_flags & NO_ERRS) != 0U)
    337     return m_argp->parse(Key::ERROR, nullptr, this);
    338 
    339   static const char* const unknown_fmt[2] = {
    340       "unrecognized option '-%s'\n",
    341       "invalid option -- '%s'\n",
    342   };
    343 
    344   failure(this, 1, 0, unknown_fmt[shrt], argv + 1);  // NOLINT
    345   see(stderr);
    346 
    347   if ((m_flags & NO_EXIT) != 0U) return 1;
    348   exit(1);
    349 }
    350 
    351 int Parser::handle_missing(bool shrt, const char* argv)
    352 {
    353   if ((m_flags & NO_ERRS) != 0U)
    354     return m_argp->parse(Key::ERROR, nullptr, this);
    355 
    356   static const char* const missing_fmt[2] = {
    357       "option '-%s' requires an argument\n",
    358       "option requires an argument -- '%s'\n",
    359   };
    360 
    361   failure(this, 2, 0, missing_fmt[shrt], argv + 1);  // NOLINT
    362   see(stderr);
    363 
    364   if ((m_flags & NO_EXIT) != 0U) return 2;
    365   exit(2);
    366 }
    367 
    368 int Parser::handle_excess(const char* argv)
    369 {
    370   if ((m_flags & NO_ERRS) != 0U)
    371   {
    372     return m_argp->parse(Key::ERROR, nullptr, this);
    373   }
    374 
    375   failure(this, 3, 0, "option '%s' doesn't allow an argument\n", argv);
    376   see(stderr);
    377 
    378   if ((m_flags & NO_EXIT) != 0U) return 3;
    379   exit(3);
    380 }
    381 
    382 std::string Parser::basename(const std::string& name)
    383 {
    384   return name.substr(name.find_first_of('/') + 1);
    385 }
    386 
    387 }  // namespace poafloc