text: // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
  // Licensed under the MIT License:
  //
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to deal
  // in the Software without restriction, including without limitation the rights
  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  //
  // The above copyright notice and this permission notice shall be included in
  // all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  // THE SOFTWARE.
  
  #include "main.h"
  #include "debug.h"
  #include "arena.h"
  #include "miniposix.h"
  #include <map>
  #include <set>
  #include <stdlib.h>
  #include <errno.h>
  #include <limits.h>
  
  #if _WIN32
  #define WIN32_LEAN_AND_MEAN
  #ifndef NOMINMAX
  #define NOMINMAX 1
  #endif
  #include <windows.h>
  #include "windows-sanity.h"
  #else
  #include <sys/uio.h>
  #endif
  
  namespace kj {
  
  // =======================================================================================
  
  TopLevelProcessContext::TopLevelProcessContext(StringPtr programName)
      : programName(programName),
        cleanShutdown(getenv("KJ_CLEAN_SHUTDOWN") != nullptr) {
    printStackTraceOnCrash();
  }
  
  StringPtr TopLevelProcessContext::getProgramName() {
    return programName;
  }
  
  void TopLevelProcessContext::exit() {
    int exitCode = hadErrors ? 1 : 0;
    if (cleanShutdown) {
  #if KJ_NO_EXCEPTIONS
      // This is the best we can do.
      warning("warning: KJ_CLEAN_SHUTDOWN may not work correctly when compiled "
              "with -fno-exceptions.");
      ::exit(exitCode);
  #else
      throw CleanShutdownException { exitCode };
  #endif
    }
    _exit(exitCode);
  }
  
  #if _WIN32
  void setStandardIoMode(int fd) {
    // Set mode to binary if the fd is not a console.
    HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
    DWORD consoleMode;
    if (GetConsoleMode(handle, &consoleMode)) {
      // It's a console.
    } else {
      KJ_SYSCALL(_setmode(fd, _O_BINARY));
    }
  }
  #else
  void setStandardIoMode(int fd) {}
  #endif
  
  static void writeLineToFd(int fd, StringPtr message) {
    // Write the given message to the given file descriptor with a trailing newline iff the message
    // is non-empty and doesn't already have a trailing newline.  We use writev() to do this in a
    // single system call without any copying (OS permitting).
  
    if (message.size() == 0) {
      return;
    }
  
  #if _WIN32
    KJ_STACK_ARRAY(char, newlineExpansionBuffer, 2 * (message.size() + 1), 128, 512);
    char* p = newlineExpansionBuffer.begin();
    for(char ch : message) {
      if(ch == '\n') {
        *(p++) = '\r';
      }
      *(p++) = ch;
    }
    if(!message.endsWith("\n")) {
      *(p++) = '\r';
      *(p++) = '\n';
    }
  
    size_t newlineExpandedSize = p - newlineExpansionBuffer.begin();
  
    KJ_ASSERT(newlineExpandedSize <= newlineExpansionBuffer.size());
  
    HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
    DWORD consoleMode;
    bool redirectedToFile = !GetConsoleMode(handle, &consoleMode);
  
    DWORD writtenSize;
    if(redirectedToFile) {
      WriteFile(handle, newlineExpansionBuffer.begin(), newlineExpandedSize, &writtenSize, nullptr);
    } else {
      KJ_STACK_ARRAY(wchar_t, buffer, newlineExpandedSize, 128, 512);
  
      size_t finalSize = MultiByteToWideChar(
        CP_UTF8,
        0,
        newlineExpansionBuffer.begin(),
        newlineExpandedSize,
        buffer.begin(),
        buffer.size());
  
      KJ_ASSERT(finalSize <= buffer.size());
  
      WriteConsoleW(handle, buffer.begin(), finalSize, &writtenSize, nullptr);
    }
  #else
    // Unfortunately the writev interface requires non-const pointers even though it won't modify
    // the data.
    struct iovec vec[2];
    vec[0].iov_base = const_cast<char*>(message.begin());
    vec[0].iov_len = message.size();
    vec[1].iov_base = const_cast<char*>("\n");
    vec[1].iov_len = 1;
  
    struct iovec* pos = vec;
  
    // Only use the second item in the vec if the message doesn't already end with \n.
    uint count = message.endsWith("\n") ? 1 : 2;
  
    for (;;) {
      ssize_t n = writev(fd, pos, count);
      if (n < 0) {
        if (errno == EINTR) {
          continue;
        } else {
          // This function is meant for writing to stdout and stderr.  If writes fail on those FDs
          // there's not a whole lot we can reasonably do, so just ignore it.
          return;
        }
      }
  
      // Update chunks to discard what was successfully written.
      for (;;) {
        if (count == 0) {
          // Done writing.
          return;
        } else if (pos->iov_len <= implicitCast<size_t>(n)) {
          // Wrote this entire chunk.
          n -= pos->iov_len;
          ++pos;
          --count;
        } else {
          // Wrote only part of this chunk.  Adjust the pointer and then retry.
          pos->iov_base = reinterpret_cast<byte*>(pos->iov_base) + n;
          pos->iov_len -= n;
          break;
        }
      }
    }
  #endif
  }
  
  void TopLevelProcessContext::warning(StringPtr message) {
    writeLineToFd(STDERR_FILENO, message);
  }
  
  void TopLevelProcessContext::error(StringPtr message) {
    hadErrors = true;
    writeLineToFd(STDERR_FILENO, message);
  }
  
  void TopLevelProcessContext::exitError(StringPtr message) {
    error(message);
    exit();
  }
  
  void TopLevelProcessContext::exitInfo(StringPtr message) {
    writeLineToFd(STDOUT_FILENO, message);
    exit();
  }
  
  void TopLevelProcessContext::increaseLoggingVerbosity() {
    // At the moment, there is only one log level that isn't enabled by default.
    _::Debug::setLogLevel(_::Debug::Severity::INFO);
  }
  
  // =======================================================================================
  
  int runMainAndExit(ProcessContext& context, MainFunc&& func, int argc, char* argv[]) {
    setStandardIoMode(STDIN_FILENO);
    setStandardIoMode(STDOUT_FILENO);
    setStandardIoMode(STDERR_FILENO);
  
  #if !KJ_NO_EXCEPTIONS
    try {
  #endif
      KJ_ASSERT(argc > 0);
  
      KJ_STACK_ARRAY(StringPtr, params, argc - 1, 8, 32);
      for (int i = 1; i < argc; i++) {
        params[i - 1] = argv[i];
      }
  
      KJ_IF_MAYBE(exception, runCatchingExceptions([&]() {
        func(argv[0], params);
      })) {
        context.error(str("*** Uncaught exception ***\n", *exception));
      }
      context.exit();
  #if !KJ_NO_EXCEPTIONS
    } catch (const TopLevelProcessContext::CleanShutdownException& e) {
      return e.exitCode;
    }
  #endif
    KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT
  }
  
  // =======================================================================================
  
  struct MainBuilder::Impl {
    inline Impl(ProcessContext& context, StringPtr version,
                StringPtr briefDescription, StringPtr extendedDescription)
        : context(context), version(version),
          briefDescription(briefDescription), extendedDescription(extendedDescription) {}
  
    ProcessContext& context;
    StringPtr version;
    StringPtr briefDescription;
    StringPtr extendedDescription;
  
    Arena arena;
  
    struct CharArrayCompare {
      inline bool operator()(const ArrayPtr<const char>& a, const ArrayPtr<const char>& b) const {
        int cmp = memcmp(a.begin(), b.begin(), min(a.size(), b.size()));
        if (cmp == 0) {
          return a.size() < b.size();
        } else {
          return cmp < 0;
        }
      }
    };
  
    struct Option {
      ArrayPtr<OptionName> names;
      bool hasArg;
      union {
        Function<Validity()>* func;
        Function<Validity(StringPtr)>* funcWithArg;
      };
      StringPtr argTitle;
      StringPtr helpText;
    };
  
    class OptionDisplayOrder;
  
    std::map<char, Option*> shortOptions;
    std::map<ArrayPtr<const char>, Option*, CharArrayCompare> longOptions;
  
    struct SubCommand {
      Function<MainFunc()> func;
      StringPtr helpText;
    };
    std::map<StringPtr, SubCommand> subCommands;
  
    struct Arg {
      StringPtr title;
      Function<Validity(StringPtr)> callback;
      uint minCount;
      uint maxCount;
    };
  
    Vector<Arg> args;
  
    Maybe<Function<Validity()>> finalCallback;
  
    Option& addOption(std::initializer_list<OptionName> names, bool hasArg, StringPtr helpText) {
      KJ_REQUIRE(names.size() > 0, "option must have at least one name");
  
      Option& option = arena.allocate<Option>();
      option.names = arena.allocateArray<OptionName>(names.size());
      uint i = 0;
      for (auto& name: names) {
        option.names[i++] = name;
        if (name.isLong) {
          KJ_REQUIRE(
              longOptions.insert(std::make_pair(StringPtr(name.longName).asArray(), &option)).second,
              "duplicate option", name.longName);
        } else {
          KJ_REQUIRE(
              shortOptions.insert(std::make_pair(name.shortName, &option)).second,
              "duplicate option", name.shortName);
        }
      }
      option.hasArg = hasArg;
      option.helpText = helpText;
      return option;
    }
  
    Validity printVersion() {
      context.exitInfo(version);
      return true;
    }
  
    Validity increaseVerbosity() {
      context.increaseLoggingVerbosity();
      return true;
    }
  };
  
  MainBuilder::MainBuilder(ProcessContext& context, StringPtr version,
                           StringPtr briefDescription, StringPtr extendedDescription)
      : impl(heap<Impl>(context, version, briefDescription, extendedDescription)) {
    addOption({"verbose"}, KJ_BIND_METHOD(*impl, increaseVerbosity),
              "Log informational messages to stderr; useful for debugging.");
    addOption({"version"}, KJ_BIND_METHOD(*impl, printVersion),
              "Print version information and exit.");
  }
  
  MainBuilder::~MainBuilder() noexcept(false) {}
  
  MainBuilder& MainBuilder::addOption(std::initializer_list<OptionName> names,
                                      Function<Validity()> callback,
                                      StringPtr helpText) {
    impl->addOption(names, false, helpText).func = &impl->arena.copy(kj::mv(callback));
    return *this;
  }
  
  MainBuilder& MainBuilder::addOptionWithArg(std::initializer_list<OptionName> names,
                                             Function<Validity(StringPtr)> callback,
                                             StringPtr argumentTitle, StringPtr helpText) {
    auto& opt = impl->addOption(names, true, helpText);
    opt.funcWithArg = &impl->arena.copy(kj::mv(callback));
    opt.argTitle = argumentTitle;
    return *this;
  }
  
  MainBuilder& MainBuilder::addSubCommand(StringPtr name, Function<MainFunc()> getSubParser,
                                          StringPtr helpText) {
    KJ_REQUIRE(impl->args.size() == 0, "cannot have sub-commands when expecting arguments");
    KJ_REQUIRE(impl->finalCallback == nullptr,
               "cannot have a final callback when accepting sub-commands");
    KJ_REQUIRE(
        impl->subCommands.insert(std::make_pair(
            name, Impl::SubCommand { kj::mv(getSubParser), helpText })).second,
        "duplicate sub-command", name);
    return *this;
  }
  
  MainBuilder& MainBuilder::expectArg(StringPtr title, Function<Validity(StringPtr)> callback) {
    KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments");
    impl->args.add(Impl::Arg { title, kj::mv(callback), 1, 1 });
    return *this;
  }
  MainBuilder& MainBuilder::expectOptionalArg(
      StringPtr title, Function<Validity(StringPtr)> callback) {
    KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments");
    impl->args.add(Impl::Arg { title, kj::mv(callback), 0, 1 });
    return *this;
  }
  MainBuilder& MainBuilder::expectZeroOrMoreArgs(
      StringPtr title, Function<Validity(StringPtr)> callback) {
    KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments");
    impl->args.add(Impl::Arg { title, kj::mv(callback), 0, UINT_MAX });
    return *this;
  }
  MainBuilder& MainBuilder::expectOneOrMoreArgs(
      StringPtr title, Function<Validity(StringPtr)> callback) {
    KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments");
    impl->args.add(Impl::Arg { title, kj::mv(callback), 1, UINT_MAX });
    return *this;
  }
  
  MainBuilder& MainBuilder::callAfterParsing(Function<Validity()> callback) {
    KJ_REQUIRE(impl->finalCallback == nullptr, "callAfterParsing() can only be called once");
    KJ_REQUIRE(impl->subCommands.empty(), "cannot have a final callback when accepting sub-commands");
    impl->finalCallback = kj::mv(callback);
    return *this;
  }
  
  class MainBuilder::MainImpl {
  public:
    MainImpl(Own<Impl>&& impl): impl(kj::mv(impl)) {}
  
    void operator()(StringPtr programName, ArrayPtr<const StringPtr> params);
  
  private:
    Own<Impl> impl;
  
    KJ_NORETURN(void usageError(StringPtr programName, StringPtr message));
    KJ_NORETURN(void printHelp(StringPtr programName));
    void wrapText(Vector<char>& output, StringPtr indent, StringPtr text);
  };
  
  MainFunc MainBuilder::build() {
    return MainImpl(kj::mv(impl));
  }
  
  void MainBuilder::MainImpl::operator()(StringPtr programName, ArrayPtr<const StringPtr> params) {
    Vector<StringPtr> arguments;
  
    for (size_t i = 0; i < params.size(); i++) {
      StringPtr param = params[i];
      if (param == "--") {
        // "--" ends option parsing.
        arguments.addAll(params.begin() + i + 1, params.end());
        break;
      } else if (param.startsWith("--")) {
        // Long option.
        ArrayPtr<const char> name;
        Maybe<StringPtr> maybeArg;
        KJ_IF_MAYBE(pos, param.findFirst('=')) {
          name = param.slice(2, *pos);
          maybeArg = param.slice(*pos + 1);
        } else {
          name = param.slice(2);
        }
        auto iter = impl->longOptions.find(name);
        if (iter == impl->longOptions.end()) {
          if (param == "--help") {
            printHelp(programName);
          } else {
            usageError(programName, str("--", name, ": unrecognized option"));
          }
        } else {
          const Impl::Option& option = *iter->second;
          if (option.hasArg) {
            // Argument expected.
            KJ_IF_MAYBE(arg, maybeArg) {
              // "--foo=blah": "blah" is the argument.
              KJ_IF_MAYBE(error, (*option.funcWithArg)(*arg).releaseError()) {
                usageError(programName, str(param, ": ", *error));
              }
            } else if (i + 1 < params.size() &&
                       !(params[i + 1].startsWith("-") && params[i + 1].size() > 1)) {
              // "--foo blah": "blah" is the argument.
              ++i;
              KJ_IF_MAYBE(error, (*option.funcWithArg)(params[i]).releaseError()) {
                usageError(programName, str(param, "=", params[i], ": ", *error));
              }
            } else {
              usageError(programName, str("--", name, ": missing argument"));
            }
          } else {
            // No argument expected.
            if (maybeArg == nullptr) {
              KJ_IF_MAYBE(error, (*option.func)().releaseError()) {
                usageError(programName, str(param, ": ", *error));
              }
            } else {
              usageError(programName, str("--", name, ": option does not accept an argument"));
            }
          }
        }
      } else if (param.startsWith("-") && param.size() > 1) {
        // Short option(s).
        for (uint j = 1; j < param.size(); j++) {
          char c = param[j];
          auto iter = impl->shortOptions.find(c);
          if (iter == impl->shortOptions.end()) {
            usageError(programName, str("-", c, ": unrecognized option"));
          } else {
            const Impl::Option& option = *iter->second;
            if (option.hasArg) {
              // Argument expected.
              if (j + 1 < param.size()) {
                // Rest of flag is argument.
                StringPtr arg = param.slice(j + 1);
                KJ_IF_MAYBE(error, (*option.funcWithArg)(arg).releaseError()) {
                  usageError(programName, str("-", c, " ", arg, ": ", *error));
                }
                break;
              } else if (i + 1 < params.size() &&
                         !(params[i + 1].startsWith("-") && params[i + 1].size() > 1)) {
                // Next parameter is argument.
                ++i;
                KJ_IF_MAYBE(error, (*option.funcWithArg)(params[i]).releaseError()) {
                  usageError(programName, str("-", c, " ", params[i], ": ", *error));
                }
                break;
              } else {
                usageError(programName, str("-", c, ": missing argument"));
              }
            } else {
              // No argument expected.
              KJ_IF_MAYBE(error, (*option.func)().releaseError()) {
                usageError(programName, str("-", c, ": ", *error));
              }
            }
          }
        }
      } else if (!impl->subCommands.empty()) {
        // A sub-command name.
        auto iter = impl->subCommands.find(param);
        if (iter != impl->subCommands.end()) {
          MainFunc subMain = iter->second.func();
          subMain(str(programName, ' ', param), params.slice(i + 1, params.size()));
          return;
        } else if (param == "help") {
          if (i + 1 < params.size()) {
            iter = impl->subCommands.find(params[i + 1]);
            if (iter != impl->subCommands.end()) {
              // Run the sub-command with "--help" as the argument.
              MainFunc subMain = iter->second.func();
              StringPtr dummyArg = "--help";
              subMain(str(programName, ' ', params[i + 1]), arrayPtr(&dummyArg, 1));
              return;
            } else if (params[i + 1] == "help") {
              uint count = 0;
              for (uint j = i + 2;
                   j < params.size() && (params[j] == "help" || params[j] == "--help");
                   j++) {
                ++count;
              }
  
              switch (count) {
                case 0:
                  impl->context.exitInfo("Help about help?  We must go deeper...");
                  break;
                case 1:
                  impl->context.exitInfo(
                      "Yo dawg, I heard you like help.  So I wrote you some help about how to use "
                      "help so you can get help on help.");
                  break;
                case 2:
                  impl->context.exitInfo("Help, I'm trapped in a help text factory!");
                  break;
                default:
                  if (count < 10) {
                    impl->context.exitError("Killed by signal 911 (SIGHELP)");
                  } else {
                    impl->context.exitInfo("How to keep an idiot busy...");
                  }
                  break;
              }
            } else {
              usageError(programName, str(params[i + 1], ": unknown command"));
            }
          } else {
            printHelp(programName);
          }
        } else {
          // Arguments are not accepted, so this is an error.
          usageError(programName, str(param, ": unknown command"));
        }
      } else {
        // Just a regular argument.
        arguments.add(param);
      }
    }
  
    // ------------------------------------
    // Handle arguments.
    // ------------------------------------
  
    if (!impl->subCommands.empty()) {
      usageError(programName, "missing command");
    }
  
    // Count the number of required arguments, so that we know how to distribute the optional args.
    uint requiredArgCount = 0;
    for (auto& argSpec: impl->args) {
      requiredArgCount += argSpec.minCount;
    }
  
    // Now go through each argument spec and consume arguments with it.
    StringPtr* argPos = arguments.begin();
    for (auto& argSpec: impl->args) {
      uint i = 0;
      for (; i < argSpec.minCount; i++) {
        if (argPos == arguments.end()) {
          usageError(programName, str("missing argument ", argSpec.title));
        } else {
          KJ_IF_MAYBE(error, argSpec.callback(*argPos).releaseError()) {
            usageError(programName, str(*argPos, ": ", *error));
          }
          ++argPos;
          --requiredArgCount;
        }
      }
  
      // If we have more arguments than we need, and this argument spec will accept extras, give
      // them to it.
      for (; i < argSpec.maxCount && arguments.end() - argPos > requiredArgCount; ++i) {
        KJ_IF_MAYBE(error, argSpec.callback(*argPos).releaseError()) {
          usageError(programName, str(*argPos, ": ", *error));
        }
        ++argPos;
      }
    }
  
    // Did we consume all the arguments?
    while (argPos < arguments.end()) {
      usageError(programName, str(*argPos++, ": too many arguments"));
    }
  
    // Run the final callback, if any.
    KJ_IF_MAYBE(f, impl->finalCallback) {
      KJ_IF_MAYBE(error, (*f)().releaseError()) {
        usageError(programName, *error);
      }
    }
  }
  
  void MainBuilder::MainImpl::usageError(StringPtr programName, StringPtr message) {
    impl->context.exitError(kj::str(
        programName, ": ", message,
        "\nTry '", programName, " --help' for more information."));
    KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT
  }
  
  class MainBuilder::Impl::OptionDisplayOrder {
  public:
    bool operator()(const Option* a, const Option* b) const {
      if (a == b) return false;
  
      char aShort = '\0';
      char bShort = '\0';
  
      for (auto& name: a->names) {
        if (name.isLong) {
          if (aShort == '\0') {
            aShort = name.longName[0];
          }
        } else {
          aShort = name.shortName;
          break;
        }
      }
      for (auto& name: b->names) {
        if (name.isLong) {
          if (bShort == '\0') {
            bShort = name.longName[0];
          }
        } else {
          bShort = name.shortName;
          break;
        }
      }
  
      if (aShort < bShort) return true;
      if (aShort > bShort) return false;
  
      StringPtr aLong;
      StringPtr bLong;
  
      for (auto& name: a->names) {
        if (name.isLong) {
          aLong = name.longName;
          break;
        }
      }
      for (auto& name: b->names) {
        if (name.isLong) {
          bLong = name.longName;
          break;
        }
      }
  
      return aLong < bLong;
    }
  };
  
  void MainBuilder::MainImpl::printHelp(StringPtr programName) {
    Vector<char> text(1024);
  
    std::set<const Impl::Option*, Impl::OptionDisplayOrder> sortedOptions;
  
    for (auto& entry: impl->shortOptions) {
      sortedOptions.insert(entry.second);
    }
    for (auto& entry: impl->longOptions) {
      sortedOptions.insert(entry.second);
    }
  
    text.addAll(str("Usage: ", programName, sortedOptions.empty() ? "" : " [<option>...]"));
  
    if (impl->subCommands.empty()) {
      for (auto& arg: impl->args) {
        text.add(' ');
        if (arg.minCount == 0) {
          text.addAll(str("[", arg.title, arg.maxCount > 1 ? "...]" : "]"));
        } else {
          text.addAll(str(arg.title, arg.maxCount > 1 ? "..." : ""));
        }
      }
    } else {
      text.addAll(StringPtr(" <command> [<arg>...]"));
    }
    text.addAll(StringPtr("\n\n"));
  
    wrapText(text, "", impl->briefDescription);
  
    if (!impl->subCommands.empty()) {
      text.addAll(StringPtr("\nCommands:\n"));
      size_t maxLen = 0;
      for (auto& command: impl->subCommands) {
        maxLen = kj::max(maxLen, command.first.size());
      }
      for (auto& command: impl->subCommands) {
        text.addAll(StringPtr("  "));
        text.addAll(command.first);
        for (size_t i = command.first.size(); i < maxLen; i++) {
          text.add(' ');
        }
        text.addAll(StringPtr("  "));
        text.addAll(command.second.helpText);
        text.add('\n');
      }
      text.addAll(str(
          "\nSee '", programName, " help <command>' for more information on a specific command.\n"));
    }
  
    if (!sortedOptions.empty()) {
      text.addAll(StringPtr("\nOptions:\n"));
  
      for (auto opt: sortedOptions) {
        text.addAll(StringPtr("    "));
        bool isFirst = true;
        for (auto& name: opt->names) {
          if (isFirst) {
            isFirst = false;
          } else {
            text.addAll(StringPtr(", "));
          }
          if (name.isLong) {
            text.addAll(str("--", name.longName));
            if (opt->hasArg) {
              text.addAll(str("=", opt->argTitle));
            }
          } else {
            text.addAll(str("-", name.shortName));
            if (opt->hasArg) {
              text.addAll(opt->argTitle);
            }
          }
        }
        text.add('\n');
        wrapText(text, "        ", opt->helpText);
      }
  
      text.addAll(StringPtr("    --help\n        Display this help text and exit.\n"));
    }
  
    if (impl->extendedDescription.size() > 0) {
      text.add('\n');
      wrapText(text, "", impl->extendedDescription);
    }
  
    text.add('\0');
    impl->context.exitInfo(String(text.releaseAsArray()));
    KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT
  }
  
  void MainBuilder::MainImpl::wrapText(Vector<char>& output, StringPtr indent, StringPtr text) {
    uint width = 80 - indent.size();
  
    while (text.size() > 0) {
      output.addAll(indent);
  
      KJ_IF_MAYBE(lineEnd, text.findFirst('\n')) {
        if (*lineEnd <= width) {
          output.addAll(text.slice(0, *lineEnd + 1));
          text = text.slice(*lineEnd + 1);
          continue;
        }
      }
  
      if (text.size() <= width) {
        output.addAll(text);
        output.add('\n');
        break;
      }
  
      uint wrapPos = width;
      for (;; wrapPos--) {
        if (wrapPos == 0) {
          // Hmm, no good place to break words.  Just break in the middle.
          wrapPos = width;
          break;
        } else if (text[wrapPos] == ' ' && text[wrapPos - 1] != ' ') {
          // This position is a space and is preceded by a non-space.  Wrap here.
          break;
        }
      }
  
      output.addAll(text.slice(0, wrapPos));
      output.add('\n');
  
      // Skip spaces after the text that was printed.
      while (text[wrapPos] == ' ') {
        ++wrapPos;
      }
      if (text[wrapPos] == '\n') {
        // Huh, apparently there were a whole bunch of spaces at the end of the line followed by a
        // newline.  Skip the newline as well so we don't print a blank line.
        ++wrapPos;
      }
      text = text.slice(wrapPos);
    }
  }
  
  }  // namespace kj
  