capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

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