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#include "main.h"
27#include "debug.h"
28#include "arena.h"
29#include "miniposix.h"
30#include <map>
31#include <set>
32#include <stdlib.h>
33#include <errno.h>
34#include <limits.h>
35
36#if _WIN32
37#define WIN32_LEAN_AND_MEAN
38#ifndef NOMINMAX
39#define NOMINMAX 1
40#endif
41#include <windows.h>
42#include "windows-sanity.h"
43#else
44#include <sys/uio.h>
45#endif
46
47namespace kj {
48
49// =======================================================================================
50
51TopLevelProcessContext::TopLevelProcessContext(StringPtr programName)
52 : programName(programName),
53 cleanShutdown(getenv("KJ_CLEAN_SHUTDOWN") != nullptr) {
54 printStackTraceOnCrash();
55}
56
57StringPtr TopLevelProcessContext::getProgramName() {
58 return programName;
59}
60
61void 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
77void 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
88void setStandardIoMode(int fd) {}
89#endif
90
91static 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
187void TopLevelProcessContext::warning(StringPtr message) {
188 writeLineToFd(STDERR_FILENO, message);
189}
190
191void TopLevelProcessContext::error(StringPtr message) {
192 hadErrors = true;
193 writeLineToFd(STDERR_FILENO, message);
194}
195
196void TopLevelProcessContext::exitError(StringPtr message) {
197 error(message);
198 exit();
199}
200
201void TopLevelProcessContext::exitInfo(StringPtr message) {
202 writeLineToFd(STDOUT_FILENO, message);
203 exit();
204}
205
206void 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
213int 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
244struct 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
335MainBuilder::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
344MainBuilder::~MainBuilder() noexcept(false) {}
345
346MainBuilder& 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
353MainBuilder& 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
362MainBuilder& 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
374MainBuilder& 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}
379MainBuilder& 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}
385MainBuilder& 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}
391MainBuilder& 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
398MainBuilder& 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
405class MainBuilder::MainImpl {
406public:
407 MainImpl(Own<Impl>&& impl): impl(kj::mv(impl)) {}
408
409 void operator()(StringPtr programName, ArrayPtr<const StringPtr> params);
410
411private:
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
419MainFunc MainBuilder::build() {
420 return MainImpl(kj::mv(impl));
421}
422
423void 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
629void 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
636class MainBuilder::Impl::OptionDisplayOrder {
637public:
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
688void 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
779void 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
828