main.c++ (26000B)
1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #ifndef _GNU_SOURCE 23 #define _GNU_SOURCE 24 #endif 25 26 #if _WIN32 27 #include "win32-api-version.h" 28 #endif 29 30 #include "main.h" 31 #include "debug.h" 32 #include "arena.h" 33 #include "miniposix.h" 34 #include <map> 35 #include <set> 36 #include <stdlib.h> 37 #include <errno.h> 38 #include <limits.h> 39 40 #if _WIN32 41 #include <windows.h> 42 #include "windows-sanity.h" 43 #else 44 #include <sys/uio.h> 45 #endif 46 47 namespace kj { 48 49 // ======================================================================================= 50 51 TopLevelProcessContext::TopLevelProcessContext(StringPtr programName) 52 : programName(programName), 53 cleanShutdown(getenv("KJ_CLEAN_SHUTDOWN") != nullptr) { 54 printStackTraceOnCrash(); 55 } 56 57 StringPtr TopLevelProcessContext::getProgramName() { 58 return programName; 59 } 60 61 void TopLevelProcessContext::exit() { 62 int exitCode = hadErrors ? 1 : 0; 63 if (cleanShutdown) { 64 #if KJ_NO_EXCEPTIONS 65 // This is the best we can do. 66 warning("warning: KJ_CLEAN_SHUTDOWN may not work correctly when compiled " 67 "with -fno-exceptions."); 68 ::exit(exitCode); 69 #else 70 throw CleanShutdownException { exitCode }; 71 #endif 72 } 73 _exit(exitCode); 74 } 75 76 #if _WIN32 77 void setStandardIoMode(int fd) { 78 // Set mode to binary if the fd is not a console. 79 HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); 80 DWORD consoleMode; 81 if (GetConsoleMode(handle, &consoleMode)) { 82 // It's a console. 83 } else { 84 KJ_SYSCALL(_setmode(fd, _O_BINARY)); 85 } 86 } 87 #else 88 void setStandardIoMode(int fd) {} 89 #endif 90 91 static void writeLineToFd(int fd, StringPtr message) { 92 // Write the given message to the given file descriptor with a trailing newline iff the message 93 // is non-empty and doesn't already have a trailing newline. We use writev() to do this in a 94 // single system call without any copying (OS permitting). 95 96 if (message.size() == 0) { 97 return; 98 } 99 100 #if _WIN32 101 KJ_STACK_ARRAY(char, newlineExpansionBuffer, 2 * (message.size() + 1), 128, 512); 102 char* p = newlineExpansionBuffer.begin(); 103 for(char ch : message) { 104 if(ch == '\n') { 105 *(p++) = '\r'; 106 } 107 *(p++) = ch; 108 } 109 if(!message.endsWith("\n")) { 110 *(p++) = '\r'; 111 *(p++) = '\n'; 112 } 113 114 size_t newlineExpandedSize = p - newlineExpansionBuffer.begin(); 115 116 KJ_ASSERT(newlineExpandedSize <= newlineExpansionBuffer.size()); 117 118 HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); 119 DWORD consoleMode; 120 bool redirectedToFile = !GetConsoleMode(handle, &consoleMode); 121 122 DWORD writtenSize; 123 if(redirectedToFile) { 124 WriteFile(handle, newlineExpansionBuffer.begin(), newlineExpandedSize, &writtenSize, nullptr); 125 } else { 126 KJ_STACK_ARRAY(wchar_t, buffer, newlineExpandedSize, 128, 512); 127 128 size_t finalSize = MultiByteToWideChar( 129 CP_UTF8, 130 0, 131 newlineExpansionBuffer.begin(), 132 newlineExpandedSize, 133 buffer.begin(), 134 buffer.size()); 135 136 KJ_ASSERT(finalSize <= buffer.size()); 137 138 WriteConsoleW(handle, buffer.begin(), finalSize, &writtenSize, nullptr); 139 } 140 #else 141 // Unfortunately the writev interface requires non-const pointers even though it won't modify 142 // the data. 143 struct iovec vec[2]; 144 vec[0].iov_base = const_cast<char*>(message.begin()); 145 vec[0].iov_len = message.size(); 146 vec[1].iov_base = const_cast<char*>("\n"); 147 vec[1].iov_len = 1; 148 149 struct iovec* pos = vec; 150 151 // Only use the second item in the vec if the message doesn't already end with \n. 152 uint count = message.endsWith("\n") ? 1 : 2; 153 154 for (;;) { 155 ssize_t n = writev(fd, pos, count); 156 if (n < 0) { 157 if (errno == EINTR) { 158 continue; 159 } else { 160 // This function is meant for writing to stdout and stderr. If writes fail on those FDs 161 // there's not a whole lot we can reasonably do, so just ignore it. 162 return; 163 } 164 } 165 166 // Update chunks to discard what was successfully written. 167 for (;;) { 168 if (count == 0) { 169 // Done writing. 170 return; 171 } else if (pos->iov_len <= implicitCast<size_t>(n)) { 172 // Wrote this entire chunk. 173 n -= pos->iov_len; 174 ++pos; 175 --count; 176 } else { 177 // Wrote only part of this chunk. Adjust the pointer and then retry. 178 pos->iov_base = reinterpret_cast<byte*>(pos->iov_base) + n; 179 pos->iov_len -= n; 180 break; 181 } 182 } 183 } 184 #endif 185 } 186 187 void TopLevelProcessContext::warning(StringPtr message) { 188 writeLineToFd(STDERR_FILENO, message); 189 } 190 191 void TopLevelProcessContext::error(StringPtr message) { 192 hadErrors = true; 193 writeLineToFd(STDERR_FILENO, message); 194 } 195 196 void TopLevelProcessContext::exitError(StringPtr message) { 197 error(message); 198 exit(); 199 } 200 201 void TopLevelProcessContext::exitInfo(StringPtr message) { 202 writeLineToFd(STDOUT_FILENO, message); 203 exit(); 204 } 205 206 void TopLevelProcessContext::increaseLoggingVerbosity() { 207 // At the moment, there is only one log level that isn't enabled by default. 208 _::Debug::setLogLevel(_::Debug::Severity::INFO); 209 } 210 211 // ======================================================================================= 212 213 int runMainAndExit(ProcessContext& context, MainFunc&& func, int argc, char* argv[]) { 214 setStandardIoMode(STDIN_FILENO); 215 setStandardIoMode(STDOUT_FILENO); 216 setStandardIoMode(STDERR_FILENO); 217 218 #if !KJ_NO_EXCEPTIONS 219 try { 220 #endif 221 KJ_ASSERT(argc > 0); 222 223 KJ_STACK_ARRAY(StringPtr, params, argc - 1, 8, 32); 224 for (int i = 1; i < argc; i++) { 225 params[i - 1] = argv[i]; 226 } 227 228 KJ_IF_MAYBE(exception, runCatchingExceptions([&]() { 229 func(argv[0], params); 230 })) { 231 context.error(str("*** Uncaught exception ***\n", *exception)); 232 } 233 context.exit(); 234 #if !KJ_NO_EXCEPTIONS 235 } catch (const TopLevelProcessContext::CleanShutdownException& e) { 236 return e.exitCode; 237 } 238 #endif 239 KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT 240 } 241 242 // ======================================================================================= 243 244 struct MainBuilder::Impl { 245 inline Impl(ProcessContext& context, StringPtr version, 246 StringPtr briefDescription, StringPtr extendedDescription) 247 : context(context), version(version), 248 briefDescription(briefDescription), extendedDescription(extendedDescription) {} 249 250 ProcessContext& context; 251 StringPtr version; 252 StringPtr briefDescription; 253 StringPtr extendedDescription; 254 255 Arena arena; 256 257 struct CharArrayCompare { 258 inline bool operator()(const ArrayPtr<const char>& a, const ArrayPtr<const char>& b) const { 259 int cmp = memcmp(a.begin(), b.begin(), min(a.size(), b.size())); 260 if (cmp == 0) { 261 return a.size() < b.size(); 262 } else { 263 return cmp < 0; 264 } 265 } 266 }; 267 268 struct Option { 269 ArrayPtr<OptionName> names; 270 bool hasArg; 271 union { 272 Function<Validity()>* func; 273 Function<Validity(StringPtr)>* funcWithArg; 274 }; 275 StringPtr argTitle; 276 StringPtr helpText; 277 }; 278 279 class OptionDisplayOrder; 280 281 std::map<char, Option*> shortOptions; 282 std::map<ArrayPtr<const char>, Option*, CharArrayCompare> longOptions; 283 284 struct SubCommand { 285 Function<MainFunc()> func; 286 StringPtr helpText; 287 }; 288 std::map<StringPtr, SubCommand> subCommands; 289 290 struct Arg { 291 StringPtr title; 292 Function<Validity(StringPtr)> callback; 293 uint minCount; 294 uint maxCount; 295 }; 296 297 Vector<Arg> args; 298 299 Maybe<Function<Validity()>> finalCallback; 300 301 Option& addOption(std::initializer_list<OptionName> names, bool hasArg, StringPtr helpText) { 302 KJ_REQUIRE(names.size() > 0, "option must have at least one name"); 303 304 Option& option = arena.allocate<Option>(); 305 option.names = arena.allocateArray<OptionName>(names.size()); 306 uint i = 0; 307 for (auto& name: names) { 308 option.names[i++] = name; 309 if (name.isLong) { 310 KJ_REQUIRE( 311 longOptions.insert(std::make_pair(StringPtr(name.longName).asArray(), &option)).second, 312 "duplicate option", name.longName); 313 } else { 314 KJ_REQUIRE( 315 shortOptions.insert(std::make_pair(name.shortName, &option)).second, 316 "duplicate option", name.shortName); 317 } 318 } 319 option.hasArg = hasArg; 320 option.helpText = helpText; 321 return option; 322 } 323 324 Validity printVersion() { 325 context.exitInfo(version); 326 return true; 327 } 328 329 Validity increaseVerbosity() { 330 context.increaseLoggingVerbosity(); 331 return true; 332 } 333 }; 334 335 MainBuilder::MainBuilder(ProcessContext& context, StringPtr version, 336 StringPtr briefDescription, StringPtr extendedDescription) 337 : impl(heap<Impl>(context, version, briefDescription, extendedDescription)) { 338 addOption({"verbose"}, KJ_BIND_METHOD(*impl, increaseVerbosity), 339 "Log informational messages to stderr; useful for debugging."); 340 addOption({"version"}, KJ_BIND_METHOD(*impl, printVersion), 341 "Print version information and exit."); 342 } 343 344 MainBuilder::~MainBuilder() noexcept(false) {} 345 346 MainBuilder& MainBuilder::addOption(std::initializer_list<OptionName> names, 347 Function<Validity()> callback, 348 StringPtr helpText) { 349 impl->addOption(names, false, helpText).func = &impl->arena.copy(kj::mv(callback)); 350 return *this; 351 } 352 353 MainBuilder& MainBuilder::addOptionWithArg(std::initializer_list<OptionName> names, 354 Function<Validity(StringPtr)> callback, 355 StringPtr argumentTitle, StringPtr helpText) { 356 auto& opt = impl->addOption(names, true, helpText); 357 opt.funcWithArg = &impl->arena.copy(kj::mv(callback)); 358 opt.argTitle = argumentTitle; 359 return *this; 360 } 361 362 MainBuilder& MainBuilder::addSubCommand(StringPtr name, Function<MainFunc()> getSubParser, 363 StringPtr helpText) { 364 KJ_REQUIRE(impl->args.size() == 0, "cannot have sub-commands when expecting arguments"); 365 KJ_REQUIRE(impl->finalCallback == nullptr, 366 "cannot have a final callback when accepting sub-commands"); 367 KJ_REQUIRE( 368 impl->subCommands.insert(std::make_pair( 369 name, Impl::SubCommand { kj::mv(getSubParser), helpText })).second, 370 "duplicate sub-command", name); 371 return *this; 372 } 373 374 MainBuilder& MainBuilder::expectArg(StringPtr title, Function<Validity(StringPtr)> callback) { 375 KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments"); 376 impl->args.add(Impl::Arg { title, kj::mv(callback), 1, 1 }); 377 return *this; 378 } 379 MainBuilder& MainBuilder::expectOptionalArg( 380 StringPtr title, Function<Validity(StringPtr)> callback) { 381 KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments"); 382 impl->args.add(Impl::Arg { title, kj::mv(callback), 0, 1 }); 383 return *this; 384 } 385 MainBuilder& MainBuilder::expectZeroOrMoreArgs( 386 StringPtr title, Function<Validity(StringPtr)> callback) { 387 KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments"); 388 impl->args.add(Impl::Arg { title, kj::mv(callback), 0, UINT_MAX }); 389 return *this; 390 } 391 MainBuilder& MainBuilder::expectOneOrMoreArgs( 392 StringPtr title, Function<Validity(StringPtr)> callback) { 393 KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments"); 394 impl->args.add(Impl::Arg { title, kj::mv(callback), 1, UINT_MAX }); 395 return *this; 396 } 397 398 MainBuilder& MainBuilder::callAfterParsing(Function<Validity()> callback) { 399 KJ_REQUIRE(impl->finalCallback == nullptr, "callAfterParsing() can only be called once"); 400 KJ_REQUIRE(impl->subCommands.empty(), "cannot have a final callback when accepting sub-commands"); 401 impl->finalCallback = kj::mv(callback); 402 return *this; 403 } 404 405 class MainBuilder::MainImpl { 406 public: 407 MainImpl(Own<Impl>&& impl): impl(kj::mv(impl)) {} 408 409 void operator()(StringPtr programName, ArrayPtr<const StringPtr> params); 410 411 private: 412 Own<Impl> impl; 413 414 KJ_NORETURN(void usageError(StringPtr programName, StringPtr message)); 415 KJ_NORETURN(void printHelp(StringPtr programName)); 416 void wrapText(Vector<char>& output, StringPtr indent, StringPtr text); 417 }; 418 419 MainFunc MainBuilder::build() { 420 return MainImpl(kj::mv(impl)); 421 } 422 423 void MainBuilder::MainImpl::operator()(StringPtr programName, ArrayPtr<const StringPtr> params) { 424 Vector<StringPtr> arguments; 425 426 for (size_t i = 0; i < params.size(); i++) { 427 StringPtr param = params[i]; 428 if (param == "--") { 429 // "--" ends option parsing. 430 arguments.addAll(params.begin() + i + 1, params.end()); 431 break; 432 } else if (param.startsWith("--")) { 433 // Long option. 434 ArrayPtr<const char> name; 435 Maybe<StringPtr> maybeArg; 436 KJ_IF_MAYBE(pos, param.findFirst('=')) { 437 name = param.slice(2, *pos); 438 maybeArg = param.slice(*pos + 1); 439 } else { 440 name = param.slice(2); 441 } 442 auto iter = impl->longOptions.find(name); 443 if (iter == impl->longOptions.end()) { 444 if (param == "--help") { 445 printHelp(programName); 446 } else { 447 usageError(programName, str("--", name, ": unrecognized option")); 448 } 449 } else { 450 const Impl::Option& option = *iter->second; 451 if (option.hasArg) { 452 // Argument expected. 453 KJ_IF_MAYBE(arg, maybeArg) { 454 // "--foo=blah": "blah" is the argument. 455 KJ_IF_MAYBE(error, (*option.funcWithArg)(*arg).releaseError()) { 456 usageError(programName, str(param, ": ", *error)); 457 } 458 } else if (i + 1 < params.size() && 459 !(params[i + 1].startsWith("-") && params[i + 1].size() > 1)) { 460 // "--foo blah": "blah" is the argument. 461 ++i; 462 KJ_IF_MAYBE(error, (*option.funcWithArg)(params[i]).releaseError()) { 463 usageError(programName, str(param, "=", params[i], ": ", *error)); 464 } 465 } else { 466 usageError(programName, str("--", name, ": missing argument")); 467 } 468 } else { 469 // No argument expected. 470 if (maybeArg == nullptr) { 471 KJ_IF_MAYBE(error, (*option.func)().releaseError()) { 472 usageError(programName, str(param, ": ", *error)); 473 } 474 } else { 475 usageError(programName, str("--", name, ": option does not accept an argument")); 476 } 477 } 478 } 479 } else if (param.startsWith("-") && param.size() > 1) { 480 // Short option(s). 481 for (uint j = 1; j < param.size(); j++) { 482 char c = param[j]; 483 auto iter = impl->shortOptions.find(c); 484 if (iter == impl->shortOptions.end()) { 485 usageError(programName, str("-", c, ": unrecognized option")); 486 } else { 487 const Impl::Option& option = *iter->second; 488 if (option.hasArg) { 489 // Argument expected. 490 if (j + 1 < param.size()) { 491 // Rest of flag is argument. 492 StringPtr arg = param.slice(j + 1); 493 KJ_IF_MAYBE(error, (*option.funcWithArg)(arg).releaseError()) { 494 usageError(programName, str("-", c, " ", arg, ": ", *error)); 495 } 496 break; 497 } else if (i + 1 < params.size() && 498 !(params[i + 1].startsWith("-") && params[i + 1].size() > 1)) { 499 // Next parameter is argument. 500 ++i; 501 KJ_IF_MAYBE(error, (*option.funcWithArg)(params[i]).releaseError()) { 502 usageError(programName, str("-", c, " ", params[i], ": ", *error)); 503 } 504 break; 505 } else { 506 usageError(programName, str("-", c, ": missing argument")); 507 } 508 } else { 509 // No argument expected. 510 KJ_IF_MAYBE(error, (*option.func)().releaseError()) { 511 usageError(programName, str("-", c, ": ", *error)); 512 } 513 } 514 } 515 } 516 } else if (!impl->subCommands.empty()) { 517 // A sub-command name. 518 auto iter = impl->subCommands.find(param); 519 if (iter != impl->subCommands.end()) { 520 MainFunc subMain = iter->second.func(); 521 subMain(str(programName, ' ', param), params.slice(i + 1, params.size())); 522 return; 523 } else if (param == "help") { 524 if (i + 1 < params.size()) { 525 iter = impl->subCommands.find(params[i + 1]); 526 if (iter != impl->subCommands.end()) { 527 // Run the sub-command with "--help" as the argument. 528 MainFunc subMain = iter->second.func(); 529 StringPtr dummyArg = "--help"; 530 subMain(str(programName, ' ', params[i + 1]), arrayPtr(&dummyArg, 1)); 531 return; 532 } else if (params[i + 1] == "help") { 533 uint count = 0; 534 for (uint j = i + 2; 535 j < params.size() && (params[j] == "help" || params[j] == "--help"); 536 j++) { 537 ++count; 538 } 539 540 switch (count) { 541 case 0: 542 impl->context.exitInfo("Help about help? We must go deeper..."); 543 break; 544 case 1: 545 impl->context.exitInfo( 546 "Yo dawg, I heard you like help. So I wrote you some help about how to use " 547 "help so you can get help on help."); 548 break; 549 case 2: 550 impl->context.exitInfo("Help, I'm trapped in a help text factory!"); 551 break; 552 default: 553 if (count < 10) { 554 impl->context.exitError("Killed by signal 911 (SIGHELP)"); 555 } else { 556 impl->context.exitInfo("How to keep an idiot busy..."); 557 } 558 break; 559 } 560 } else { 561 usageError(programName, str(params[i + 1], ": unknown command")); 562 } 563 } else { 564 printHelp(programName); 565 } 566 } else { 567 // Arguments are not accepted, so this is an error. 568 usageError(programName, str(param, ": unknown command")); 569 } 570 } else { 571 // Just a regular argument. 572 arguments.add(param); 573 } 574 } 575 576 // ------------------------------------ 577 // Handle arguments. 578 // ------------------------------------ 579 580 if (!impl->subCommands.empty()) { 581 usageError(programName, "missing command"); 582 } 583 584 // Count the number of required arguments, so that we know how to distribute the optional args. 585 uint requiredArgCount = 0; 586 for (auto& argSpec: impl->args) { 587 requiredArgCount += argSpec.minCount; 588 } 589 590 // Now go through each argument spec and consume arguments with it. 591 StringPtr* argPos = arguments.begin(); 592 for (auto& argSpec: impl->args) { 593 uint i = 0; 594 for (; i < argSpec.minCount; i++) { 595 if (argPos == arguments.end()) { 596 usageError(programName, str("missing argument ", argSpec.title)); 597 } else { 598 KJ_IF_MAYBE(error, argSpec.callback(*argPos).releaseError()) { 599 usageError(programName, str(*argPos, ": ", *error)); 600 } 601 ++argPos; 602 --requiredArgCount; 603 } 604 } 605 606 // If we have more arguments than we need, and this argument spec will accept extras, give 607 // them to it. 608 for (; i < argSpec.maxCount && arguments.end() - argPos > requiredArgCount; ++i) { 609 KJ_IF_MAYBE(error, argSpec.callback(*argPos).releaseError()) { 610 usageError(programName, str(*argPos, ": ", *error)); 611 } 612 ++argPos; 613 } 614 } 615 616 // Did we consume all the arguments? 617 while (argPos < arguments.end()) { 618 usageError(programName, str(*argPos++, ": too many arguments")); 619 } 620 621 // Run the final callback, if any. 622 KJ_IF_MAYBE(f, impl->finalCallback) { 623 KJ_IF_MAYBE(error, (*f)().releaseError()) { 624 usageError(programName, *error); 625 } 626 } 627 } 628 629 void MainBuilder::MainImpl::usageError(StringPtr programName, StringPtr message) { 630 impl->context.exitError(kj::str( 631 programName, ": ", message, 632 "\nTry '", programName, " --help' for more information.")); 633 KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT 634 } 635 636 class MainBuilder::Impl::OptionDisplayOrder { 637 public: 638 bool operator()(const Option* a, const Option* b) const { 639 if (a == b) return false; 640 641 char aShort = '\0'; 642 char bShort = '\0'; 643 644 for (auto& name: a->names) { 645 if (name.isLong) { 646 if (aShort == '\0') { 647 aShort = name.longName[0]; 648 } 649 } else { 650 aShort = name.shortName; 651 break; 652 } 653 } 654 for (auto& name: b->names) { 655 if (name.isLong) { 656 if (bShort == '\0') { 657 bShort = name.longName[0]; 658 } 659 } else { 660 bShort = name.shortName; 661 break; 662 } 663 } 664 665 if (aShort < bShort) return true; 666 if (aShort > bShort) return false; 667 668 StringPtr aLong; 669 StringPtr bLong; 670 671 for (auto& name: a->names) { 672 if (name.isLong) { 673 aLong = name.longName; 674 break; 675 } 676 } 677 for (auto& name: b->names) { 678 if (name.isLong) { 679 bLong = name.longName; 680 break; 681 } 682 } 683 684 return aLong < bLong; 685 } 686 }; 687 688 void MainBuilder::MainImpl::printHelp(StringPtr programName) { 689 Vector<char> text(1024); 690 691 std::set<const Impl::Option*, Impl::OptionDisplayOrder> sortedOptions; 692 693 for (auto& entry: impl->shortOptions) { 694 sortedOptions.insert(entry.second); 695 } 696 for (auto& entry: impl->longOptions) { 697 sortedOptions.insert(entry.second); 698 } 699 700 text.addAll(str("Usage: ", programName, sortedOptions.empty() ? "" : " [<option>...]")); 701 702 if (impl->subCommands.empty()) { 703 for (auto& arg: impl->args) { 704 text.add(' '); 705 if (arg.minCount == 0) { 706 text.addAll(str("[", arg.title, arg.maxCount > 1 ? "...]" : "]")); 707 } else { 708 text.addAll(str(arg.title, arg.maxCount > 1 ? "..." : "")); 709 } 710 } 711 } else { 712 text.addAll(StringPtr(" <command> [<arg>...]")); 713 } 714 text.addAll(StringPtr("\n\n")); 715 716 wrapText(text, "", impl->briefDescription); 717 718 if (!impl->subCommands.empty()) { 719 text.addAll(StringPtr("\nCommands:\n")); 720 size_t maxLen = 0; 721 for (auto& command: impl->subCommands) { 722 maxLen = kj::max(maxLen, command.first.size()); 723 } 724 for (auto& command: impl->subCommands) { 725 text.addAll(StringPtr(" ")); 726 text.addAll(command.first); 727 for (size_t i = command.first.size(); i < maxLen; i++) { 728 text.add(' '); 729 } 730 text.addAll(StringPtr(" ")); 731 text.addAll(command.second.helpText); 732 text.add('\n'); 733 } 734 text.addAll(str( 735 "\nSee '", programName, " help <command>' for more information on a specific command.\n")); 736 } 737 738 if (!sortedOptions.empty()) { 739 text.addAll(StringPtr("\nOptions:\n")); 740 741 for (auto opt: sortedOptions) { 742 text.addAll(StringPtr(" ")); 743 bool isFirst = true; 744 for (auto& name: opt->names) { 745 if (isFirst) { 746 isFirst = false; 747 } else { 748 text.addAll(StringPtr(", ")); 749 } 750 if (name.isLong) { 751 text.addAll(str("--", name.longName)); 752 if (opt->hasArg) { 753 text.addAll(str("=", opt->argTitle)); 754 } 755 } else { 756 text.addAll(str("-", name.shortName)); 757 if (opt->hasArg) { 758 text.addAll(opt->argTitle); 759 } 760 } 761 } 762 text.add('\n'); 763 wrapText(text, " ", opt->helpText); 764 } 765 766 text.addAll(StringPtr(" --help\n Display this help text and exit.\n")); 767 } 768 769 if (impl->extendedDescription.size() > 0) { 770 text.add('\n'); 771 wrapText(text, "", impl->extendedDescription); 772 } 773 774 text.add('\0'); 775 impl->context.exitInfo(String(text.releaseAsArray())); 776 KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT 777 } 778 779 void MainBuilder::MainImpl::wrapText(Vector<char>& output, StringPtr indent, StringPtr text) { 780 uint width = 80 - indent.size(); 781 782 while (text.size() > 0) { 783 output.addAll(indent); 784 785 KJ_IF_MAYBE(lineEnd, text.findFirst('\n')) { 786 if (*lineEnd <= width) { 787 output.addAll(text.slice(0, *lineEnd + 1)); 788 text = text.slice(*lineEnd + 1); 789 continue; 790 } 791 } 792 793 if (text.size() <= width) { 794 output.addAll(text); 795 output.add('\n'); 796 break; 797 } 798 799 uint wrapPos = width; 800 for (;; wrapPos--) { 801 if (wrapPos == 0) { 802 // Hmm, no good place to break words. Just break in the middle. 803 wrapPos = width; 804 break; 805 } else if (text[wrapPos] == ' ' && text[wrapPos - 1] != ' ') { 806 // This position is a space and is preceded by a non-space. Wrap here. 807 break; 808 } 809 } 810 811 output.addAll(text.slice(0, wrapPos)); 812 output.add('\n'); 813 814 // Skip spaces after the text that was printed. 815 while (text[wrapPos] == ' ') { 816 ++wrapPos; 817 } 818 if (text[wrapPos] == '\n') { 819 // Huh, apparently there were a whole bunch of spaces at the end of the line followed by a 820 // newline. Skip the newline as well so we don't print a blank line. 821 ++wrapPos; 822 } 823 text = text.slice(wrapPos); 824 } 825 } 826 827 } // namespace kj