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