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 | |
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 |
828 | |