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