jpayne@69: // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors jpayne@69: // Licensed under the MIT License: jpayne@69: // jpayne@69: // Permission is hereby granted, free of charge, to any person obtaining a copy jpayne@69: // of this software and associated documentation files (the "Software"), to deal jpayne@69: // in the Software without restriction, including without limitation the rights jpayne@69: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell jpayne@69: // copies of the Software, and to permit persons to whom the Software is jpayne@69: // furnished to do so, subject to the following conditions: jpayne@69: // jpayne@69: // The above copyright notice and this permission notice shall be included in jpayne@69: // all copies or substantial portions of the Software. jpayne@69: // jpayne@69: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR jpayne@69: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, jpayne@69: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE jpayne@69: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER jpayne@69: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, jpayne@69: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN jpayne@69: // THE SOFTWARE. jpayne@69: jpayne@69: #pragma once jpayne@69: jpayne@69: #include "array.h" jpayne@69: #include "string.h" jpayne@69: #include "vector.h" jpayne@69: #include "function.h" jpayne@69: jpayne@69: KJ_BEGIN_HEADER jpayne@69: jpayne@69: namespace kj { jpayne@69: jpayne@69: class ProcessContext { jpayne@69: // Context for command-line programs. jpayne@69: jpayne@69: public: jpayne@69: virtual StringPtr getProgramName() = 0; jpayne@69: // Get argv[0] as passed to main(). jpayne@69: jpayne@69: KJ_NORETURN(virtual void exit()) = 0; jpayne@69: // Indicates program completion. The program is considered successful unless `error()` was jpayne@69: // called. Typically this exits with _Exit(), meaning that the stack is not unwound, buffers jpayne@69: // are not flushed, etc. -- it is the responsibility of the caller to flush any buffers that jpayne@69: // matter. However, an alternate context implementation e.g. for unit testing purposes could jpayne@69: // choose to throw an exception instead. jpayne@69: // jpayne@69: // At first this approach may sound crazy. Isn't it much better to shut down cleanly? What if jpayne@69: // you lose data? However, it turns out that if you look at each common class of program, _Exit() jpayne@69: // is almost always preferable. Let's break it down: jpayne@69: // jpayne@69: // * Commands: A typical program you might run from the command line is single-threaded and jpayne@69: // exits quickly and deterministically. Commands often use buffered I/O and need to flush jpayne@69: // those buffers before exit. However, most of the work performed by destructors is not jpayne@69: // flushing buffers, but rather freeing up memory, placing objects into freelists, and closing jpayne@69: // file descriptors. All of this is irrelevant if the process is about to exit anyway, and jpayne@69: // for a command that runs quickly, time wasted freeing heap space may make a real difference jpayne@69: // in the overall runtime of a script. Meanwhile, it is usually easy to determine exactly what jpayne@69: // resources need to be flushed before exit, and easy to tell if they are not being flushed jpayne@69: // (because the command fails to produce the expected output). Therefore, it is reasonably jpayne@69: // easy for commands to explicitly ensure all output is flushed before exiting, and it is jpayne@69: // probably a good idea for them to do so anyway, because write failures should be detected jpayne@69: // and handled. For commands, a good strategy is to allocate any objects that require clean jpayne@69: // destruction on the stack, and allow them to go out of scope before the command exits. jpayne@69: // Meanwhile, any resources which do not need to be cleaned up should be allocated as members jpayne@69: // of the command's main class, whose destructor normally will not be called. jpayne@69: // jpayne@69: // * Interactive apps: Programs that interact with the user (whether they be graphical apps jpayne@69: // with windows or console-based apps like emacs) generally exit only when the user asks them jpayne@69: // to. Such applications may store large data structures in memory which need to be synced jpayne@69: // to disk, such as documents or user preferences. However, relying on stack unwind or global jpayne@69: // destructors as the mechanism for ensuring such syncing occurs is probably wrong. First of jpayne@69: // all, it's 2013, and applications ought to be actively syncing changes to non-volatile jpayne@69: // storage the moment those changes are made. Applications can crash at any time and a crash jpayne@69: // should never lose data that is more than half a second old. Meanwhile, if a user actually jpayne@69: // does try to close an application while unsaved changes exist, the application UI should jpayne@69: // prompt the user to decide what to do. Such a UI mechanism is obviously too high level to jpayne@69: // be implemented via destructors, so KJ's use of _Exit() shouldn't make a difference here. jpayne@69: // jpayne@69: // * Servers: A good server is fault-tolerant, prepared for the possibility that at any time jpayne@69: // it could crash, the OS could decide to kill it off, or the machine it is running on could jpayne@69: // just die. So, using _Exit() should be no problem. In fact, servers generally never even jpayne@69: // call exit anyway; they are killed externally. jpayne@69: // jpayne@69: // * Batch jobs: A long-running batch job is something between a command and a server. It jpayne@69: // probably knows exactly what needs to be flushed before exiting, and it probably should be jpayne@69: // fault-tolerant. jpayne@69: // jpayne@69: // Meanwhile, regardless of program type, if you are adhering to KJ style, then the use of jpayne@69: // _Exit() shouldn't be a problem anyway: jpayne@69: // jpayne@69: // * KJ style forbids global mutable state (singletons) in general and global constructors and jpayne@69: // destructors in particular. Therefore, everything that could possibly need cleanup either jpayne@69: // lives on the stack or is transitively owned by something living on the stack. jpayne@69: // jpayne@69: // * Calling exit() simply means "Don't clean up anything older than this stack frame.". If you jpayne@69: // have resources that require cleanup before exit, make sure they are owned by stack frames jpayne@69: // beyond the one that eventually calls exit(). To be as safe as possible, don't place any jpayne@69: // state in your program's main class, and don't call exit() yourself. Then, runMainAndExit() jpayne@69: // will do it, and the only thing on the stack at that time will be your main class, which jpayne@69: // has no state anyway. jpayne@69: // jpayne@69: // TODO(someday): Perhaps we should use the new std::quick_exit(), so that at_quick_exit() is jpayne@69: // available for those who really think they need it. Unfortunately, it is not yet available jpayne@69: // on many platforms. jpayne@69: jpayne@69: virtual void warning(StringPtr message) = 0; jpayne@69: // Print the given message to standard error. A newline is printed after the message if it jpayne@69: // doesn't already have one. jpayne@69: jpayne@69: virtual void error(StringPtr message) = 0; jpayne@69: // Like `warning()`, but also sets a flag indicating that the process has failed, and that when jpayne@69: // it eventually exits it should indicate an error status. jpayne@69: jpayne@69: KJ_NORETURN(virtual void exitError(StringPtr message)) = 0; jpayne@69: // Equivalent to `error(message)` followed by `exit()`. jpayne@69: jpayne@69: KJ_NORETURN(virtual void exitInfo(StringPtr message)) = 0; jpayne@69: // Displays the given non-error message to the user and then calls `exit()`. This is used to jpayne@69: // implement things like --help. jpayne@69: jpayne@69: virtual void increaseLoggingVerbosity() = 0; jpayne@69: // Increase the level of detail produced by the debug logging system. `MainBuilder` invokes jpayne@69: // this if the caller uses the -v flag. jpayne@69: jpayne@69: // TODO(someday): Add interfaces representing standard OS resources like the filesystem, so that jpayne@69: // these things can be mocked out. jpayne@69: }; jpayne@69: jpayne@69: class TopLevelProcessContext final: public ProcessContext { jpayne@69: // A ProcessContext implementation appropriate for use at the actual entry point of a process jpayne@69: // (as opposed to when you are trying to call a program's main function from within some other jpayne@69: // program). This implementation writes errors to stderr, and its `exit()` method actually jpayne@69: // calls the C `quick_exit()` function. jpayne@69: jpayne@69: public: jpayne@69: explicit TopLevelProcessContext(StringPtr programName); jpayne@69: jpayne@69: struct CleanShutdownException { int exitCode; }; jpayne@69: // If the environment variable KJ_CLEAN_SHUTDOWN is set, then exit() will actually throw this jpayne@69: // exception rather than exiting. `kj::runMain()` catches this exception and returns normally. jpayne@69: // This is useful primarily for testing purposes, to assist tools like memory leak checkers that jpayne@69: // are easily confused by quick_exit(). jpayne@69: jpayne@69: StringPtr getProgramName() override; jpayne@69: KJ_NORETURN(void exit() override); jpayne@69: void warning(StringPtr message) override; jpayne@69: void error(StringPtr message) override; jpayne@69: KJ_NORETURN(void exitError(StringPtr message) override); jpayne@69: KJ_NORETURN(void exitInfo(StringPtr message) override); jpayne@69: void increaseLoggingVerbosity() override; jpayne@69: jpayne@69: private: jpayne@69: StringPtr programName; jpayne@69: bool cleanShutdown; jpayne@69: bool hadErrors = false; jpayne@69: }; jpayne@69: jpayne@69: typedef Function params)> MainFunc; jpayne@69: jpayne@69: int runMainAndExit(ProcessContext& context, MainFunc&& func, int argc, char* argv[]); jpayne@69: // Runs the given main function and then exits using the given context. If an exception is thrown, jpayne@69: // this will catch it, report it via the context and exit with an error code. jpayne@69: // jpayne@69: // Normally this function does not return, because returning would probably lead to wasting time jpayne@69: // on cleanup when the process is just going to exit anyway. However, to facilitate memory leak jpayne@69: // checkers and other tools that require a clean shutdown to do their job, if the environment jpayne@69: // variable KJ_CLEAN_SHUTDOWN is set, the function will in fact return an exit code, which should jpayne@69: // then be returned from main(). jpayne@69: // jpayne@69: // Most users will use the KJ_MAIN() macro rather than call this function directly. jpayne@69: jpayne@69: #define KJ_MAIN(MainClass) \ jpayne@69: int main(int argc, char* argv[]) { \ jpayne@69: ::kj::TopLevelProcessContext context(argv[0]); \ jpayne@69: MainClass mainObject(context); \ jpayne@69: return ::kj::runMainAndExit(context, mainObject.getMain(), argc, argv); \ jpayne@69: } jpayne@69: // Convenience macro for declaring a main function based on the given class. The class must have jpayne@69: // a constructor that accepts a ProcessContext& and a method getMain() which returns jpayne@69: // kj::MainFunc (probably building it using a MainBuilder). jpayne@69: jpayne@69: class MainBuilder { jpayne@69: // Builds a main() function with nice argument parsing. As options and arguments are parsed, jpayne@69: // corresponding callbacks are called, so that you never have to write a massive switch() jpayne@69: // statement to interpret arguments. Additionally, this approach encourages you to write jpayne@69: // main classes that have a reasonable API that can be used as an alternative to their jpayne@69: // command-line interface. jpayne@69: // jpayne@69: // All StringPtrs passed to MainBuilder must remain valid until option parsing completes. The jpayne@69: // assumption is that these strings will all be literals, making this an easy requirement. If jpayne@69: // not, consider allocating them in an Arena. jpayne@69: // jpayne@69: // Some flags are automatically recognized by the main functions built by this class: jpayne@69: // --help: Prints help text and exits. The help text is constructed based on the jpayne@69: // information you provide to the builder as you define each flag. jpayne@69: // --verbose: Increase logging verbosity. jpayne@69: // --version: Print version information and exit. jpayne@69: // jpayne@69: // Example usage: jpayne@69: // jpayne@69: // class FooMain { jpayne@69: // public: jpayne@69: // FooMain(kj::ProcessContext& context): context(context) {} jpayne@69: // jpayne@69: // bool setAll() { all = true; return true; } jpayne@69: // // Enable the --all flag. jpayne@69: // jpayne@69: // kj::MainBuilder::Validity setOutput(kj::StringPtr name) { jpayne@69: // // Set the output file. jpayne@69: // jpayne@69: // if (name.endsWith(".foo")) { jpayne@69: // outputFile = name; jpayne@69: // return true; jpayne@69: // } else { jpayne@69: // return "Output file must have extension .foo."; jpayne@69: // } jpayne@69: // } jpayne@69: // jpayne@69: // kj::MainBuilder::Validity processInput(kj::StringPtr name) { jpayne@69: // // Process an input file. jpayne@69: // jpayne@69: // if (!exists(name)) { jpayne@69: // return kj::str(name, ": file not found"); jpayne@69: // } jpayne@69: // // ... process the input file ... jpayne@69: // return true; jpayne@69: // } jpayne@69: // jpayne@69: // kj::MainFunc getMain() { jpayne@69: // return MainBuilder(context, "Foo Builder v1.5", "Reads s and builds a Foo.") jpayne@69: // .addOption({'a', "all"}, KJ_BIND_METHOD(*this, setAll), jpayne@69: // "Frob all the widgets. Otherwise, only some widgets are frobbed.") jpayne@69: // .addOptionWithArg({'o', "output"}, KJ_BIND_METHOD(*this, setOutput), jpayne@69: // "", "Output to . Must be a .foo file.") jpayne@69: // .expectOneOrMoreArgs("", KJ_BIND_METHOD(*this, processInput)) jpayne@69: // .build(); jpayne@69: // } jpayne@69: // jpayne@69: // private: jpayne@69: // bool all = false; jpayne@69: // kj::StringPtr outputFile; jpayne@69: // kj::ProcessContext& context; jpayne@69: // }; jpayne@69: jpayne@69: public: jpayne@69: MainBuilder(ProcessContext& context, StringPtr version, jpayne@69: StringPtr briefDescription, StringPtr extendedDescription = nullptr); jpayne@69: ~MainBuilder() noexcept(false); jpayne@69: jpayne@69: class OptionName { jpayne@69: public: jpayne@69: OptionName() = default; jpayne@69: inline OptionName(char shortName): isLong(false), shortName(shortName) {} jpayne@69: inline OptionName(const char* longName): isLong(true), longName(longName) {} jpayne@69: jpayne@69: private: jpayne@69: bool isLong; jpayne@69: union { jpayne@69: char shortName; jpayne@69: const char* longName; jpayne@69: }; jpayne@69: friend class MainBuilder; jpayne@69: }; jpayne@69: jpayne@69: class Validity { jpayne@69: public: jpayne@69: inline Validity(bool valid) { jpayne@69: if (!valid) errorMessage = heapString("invalid argument"); jpayne@69: } jpayne@69: inline Validity(const char* errorMessage) jpayne@69: : errorMessage(heapString(errorMessage)) {} jpayne@69: inline Validity(String&& errorMessage) jpayne@69: : errorMessage(kj::mv(errorMessage)) {} jpayne@69: jpayne@69: inline const Maybe& getError() const { return errorMessage; } jpayne@69: inline Maybe releaseError() { return kj::mv(errorMessage); } jpayne@69: jpayne@69: private: jpayne@69: Maybe errorMessage; jpayne@69: friend class MainBuilder; jpayne@69: }; jpayne@69: jpayne@69: MainBuilder& addOption(std::initializer_list names, Function callback, jpayne@69: StringPtr helpText); jpayne@69: // Defines a new option (flag). `names` is a list of characters and strings that can be used to jpayne@69: // specify the option on the command line. Single-character names are used with "-" while string jpayne@69: // names are used with "--". `helpText` is a natural-language description of the flag. jpayne@69: // jpayne@69: // `callback` is called when the option is seen. Its return value indicates whether the option jpayne@69: // was accepted. If not, further option processing stops, and error is written, and the process jpayne@69: // exits. jpayne@69: // jpayne@69: // Example: jpayne@69: // jpayne@69: // builder.addOption({'a', "all"}, KJ_BIND_METHOD(*this, showAll), "Show all files."); jpayne@69: // jpayne@69: // This option could be specified in the following ways: jpayne@69: // jpayne@69: // -a jpayne@69: // --all jpayne@69: // jpayne@69: // Note that single-character option names can be combined into a single argument. For example, jpayne@69: // `-abcd` is equivalent to `-a -b -c -d`. jpayne@69: // jpayne@69: // The help text for this option would look like: jpayne@69: // jpayne@69: // -a, --all jpayne@69: // Show all files. jpayne@69: // jpayne@69: // Note that help text is automatically word-wrapped. jpayne@69: jpayne@69: MainBuilder& addOptionWithArg(std::initializer_list names, jpayne@69: Function callback, jpayne@69: StringPtr argumentTitle, StringPtr helpText); jpayne@69: // Like `addOption()`, but adds an option which accepts an argument. `argumentTitle` is used in jpayne@69: // the help text. The argument text is passed to the callback. jpayne@69: // jpayne@69: // Example: jpayne@69: // jpayne@69: // builder.addOptionWithArg({'o', "output"}, KJ_BIND_METHOD(*this, setOutput), jpayne@69: // "", "Output to ."); jpayne@69: // jpayne@69: // This option could be specified with an argument of "foo" in the following ways: jpayne@69: // jpayne@69: // -ofoo jpayne@69: // -o foo jpayne@69: // --output=foo jpayne@69: // --output foo jpayne@69: // jpayne@69: // Note that single-character option names can be combined, but only the last option can have an jpayne@69: // argument, since the characters after the option letter are interpreted as the argument. E.g. jpayne@69: // `-abofoo` would be equivalent to `-a -b -o foo`. jpayne@69: // jpayne@69: // The help text for this option would look like: jpayne@69: // jpayne@69: // -o FILENAME, --output=FILENAME jpayne@69: // Output to FILENAME. jpayne@69: jpayne@69: MainBuilder& addSubCommand(StringPtr name, Function getSubParser, jpayne@69: StringPtr briefHelpText); jpayne@69: // If exactly the given name is seen as an argument, invoke getSubParser() and then pass all jpayne@69: // remaining arguments to the parser it returns. This is useful for implementing commands which jpayne@69: // have lots of sub-commands, like "git" (which has sub-commands "checkout", "branch", "pull", jpayne@69: // etc.). jpayne@69: // jpayne@69: // `getSubParser` is only called if the command is seen. This avoids building main functions jpayne@69: // for commands that aren't used. jpayne@69: // jpayne@69: // `briefHelpText` should be brief enough to show immediately after the command name on a single jpayne@69: // line. It will not be wrapped. Users can use the built-in "help" command to get extended jpayne@69: // help on a particular command. jpayne@69: jpayne@69: MainBuilder& expectArg(StringPtr title, Function callback); jpayne@69: MainBuilder& expectOptionalArg(StringPtr title, Function callback); jpayne@69: MainBuilder& expectZeroOrMoreArgs(StringPtr title, Function callback); jpayne@69: MainBuilder& expectOneOrMoreArgs(StringPtr title, Function callback); jpayne@69: // Set callbacks to handle arguments. `expectArg()` and `expectOptionalArg()` specify positional jpayne@69: // arguments with special handling, while `expect{Zero,One}OrMoreArgs()` specifies a handler for jpayne@69: // an argument list (the handler is called once for each argument in the list). `title` jpayne@69: // specifies how the argument should be represented in the usage text. jpayne@69: // jpayne@69: // All options callbacks are called before argument callbacks, regardless of their ordering on jpayne@69: // the command line. This matches GNU getopt's behavior of permuting non-flag arguments to the jpayne@69: // end of the argument list. Also matching getopt, the special option "--" indicates that the jpayne@69: // rest of the command line is all arguments, not options, even if they start with '-'. jpayne@69: // jpayne@69: // The interpretation of positional arguments is fairly flexible. The non-optional arguments can jpayne@69: // be expected at the beginning, end, or in the middle. If more arguments are specified than jpayne@69: // the number of non-optional args, they are assigned to the optional argument handlers in the jpayne@69: // order of registration. jpayne@69: // jpayne@69: // For example, say you called: jpayne@69: // builder.expectArg("", ...); jpayne@69: // builder.expectOptionalArg("", ...); jpayne@69: // builder.expectArg("", ...); jpayne@69: // builder.expectZeroOrMoreArgs("", ...); jpayne@69: // builder.expectArg("", ...); jpayne@69: // jpayne@69: // This command requires at least three arguments: foo, baz, and corge. If four arguments are jpayne@69: // given, the second is assigned to bar. If five or more arguments are specified, then the jpayne@69: // arguments between the third and last are assigned to qux. Note that it never makes sense jpayne@69: // to call `expect*OrMoreArgs()` more than once since only the first call would ever be used. jpayne@69: // jpayne@69: // In practice, you probably shouldn't create such complicated commands as in the above example. jpayne@69: // But, this flexibility seems necessary to support commands where the first argument is special jpayne@69: // as well as commands (like `cp`) where the last argument is special. jpayne@69: jpayne@69: MainBuilder& callAfterParsing(Function callback); jpayne@69: // Call the given function after all arguments have been parsed. jpayne@69: jpayne@69: MainFunc build(); jpayne@69: // Build the "main" function, which simply parses the arguments. Once this returns, the jpayne@69: // `MainBuilder` is no longer valid. jpayne@69: jpayne@69: private: jpayne@69: struct Impl; jpayne@69: Own impl; jpayne@69: jpayne@69: class MainImpl; jpayne@69: }; jpayne@69: jpayne@69: } // namespace kj jpayne@69: jpayne@69: KJ_END_HEADER