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

poafloc.cpp (9917B)


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