experimental/io/FsUtil.h \
experimental/JSONSchema.h \
experimental/LockFreeRingBuffer.h \
+ experimental/NestedCommandLineApp.h \
experimental/ProgramOptions.h \
experimental/Select64.h \
experimental/StringKeyedCommon.h \
experimental/FunctionScheduler.cpp \
experimental/io/FsUtil.cpp \
experimental/JSONSchema.cpp \
+ experimental/NestedCommandLineApp.cpp \
experimental/ProgramOptions.cpp \
experimental/Select64.cpp \
experimental/TestUtil.cpp
--- /dev/null
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/experimental/NestedCommandLineApp.h>
+
+#include <iostream>
+#include <folly/FileUtil.h>
+#include <folly/Format.h>
+#include <folly/experimental/io/FsUtil.h>
+
+namespace po = ::boost::program_options;
+
+namespace folly {
+
+namespace {
+
+// Guess the program name as basename(executable)
+std::string guessProgramName() {
+ try {
+ return fs::executable_path().filename().native();
+ } catch (const std::exception&) {
+ return "UNKNOWN";
+ }
+}
+
+} // namespace
+
+ProgramExit::ProgramExit(int status, const std::string& msg)
+ : std::runtime_error(msg),
+ status_(status) {
+ CHECK_NE(status, 0);
+}
+
+NestedCommandLineApp::NestedCommandLineApp(
+ std::string programName,
+ std::string version,
+ InitFunction initFunction)
+ : programName_(std::move(programName)),
+ version_(std::move(version)),
+ initFunction_(std::move(initFunction)),
+ globalOptions_("Global options") {
+ addCommand("help", "[command]",
+ "Display help (globally or for a given command)",
+ "Displays help (globally or for a given command).",
+ [this] (const po::variables_map& vm,
+ const std::vector<std::string>& args) {
+ displayHelp(vm, args);
+ });
+
+ globalOptions_.add_options()
+ ("help,h", "Display help (globally or for a given command)")
+ ("version", "Display version information");
+}
+
+po::options_description& NestedCommandLineApp::addCommand(
+ std::string name,
+ std::string argStr,
+ std::string shortHelp,
+ std::string fullHelp,
+ Command command) {
+ CommandInfo info {
+ std::move(argStr),
+ std::move(shortHelp),
+ std::move(fullHelp),
+ std::move(command),
+ po::options_description(folly::sformat("Options for `{}'", name))
+ };
+
+ auto p = commands_.emplace(std::move(name), std::move(info));
+ CHECK(p.second) << "Command already exists";
+
+ return p.first->second.options;
+}
+
+void NestedCommandLineApp::addAlias(std::string newName,
+ std::string oldName) {
+ CHECK(aliases_.count(oldName) || commands_.count(oldName))
+ << "Alias old name does not exist";
+ CHECK(!aliases_.count(newName) && !commands_.count(newName))
+ << "Alias new name already exists";
+ aliases_.emplace(std::move(newName), std::move(oldName));
+}
+
+void NestedCommandLineApp::displayHelp(
+ const po::variables_map& globalOptions,
+ const std::vector<std::string>& args) {
+ if (args.empty()) {
+ // General help
+ printf(
+ "Usage: %s [global_options...] <command> [command_options...] "
+ "[command_args...]\n\n", programName_.c_str());
+ std::cout << globalOptions_;
+ printf("\nAvailable commands:\n");
+
+ size_t maxLen = 0;
+ for (auto& p : commands_) {
+ maxLen = std::max(maxLen, p.first.size());
+ }
+ for (auto& p : aliases_) {
+ maxLen = std::max(maxLen, p.first.size());
+ }
+
+ for (auto& p : commands_) {
+ printf(" %-*s %s\n",
+ int(maxLen), p.first.c_str(), p.second.shortHelp.c_str());
+ }
+
+ if (!aliases_.empty()) {
+ printf("\nAvailable aliases:\n");
+ for (auto& p : aliases_) {
+ printf(" %-*s => %s\n",
+ int(maxLen), p.first.c_str(), resolveAlias(p.second).c_str());
+ }
+ }
+ } else {
+ // Help for a given command
+ auto& p = findCommand(args.front());
+ if (p.first != args.front()) {
+ printf("`%1$s' is an alias for `%2$s'; showing help for `%2$s'\n",
+ args.front().c_str(), p.first.c_str());
+ }
+ auto& info = p.second;
+
+ printf(
+ "Usage: %s [global_options...] %s%s%s%s\n\n",
+ programName_.c_str(),
+ p.first.c_str(),
+ info.options.options().empty() ? "" : " [command_options...]",
+ info.argStr.empty() ? "" : " ",
+ info.argStr.c_str());
+
+ std::cout << globalOptions_;
+
+ if (!info.options.options().empty()) {
+ printf("\n");
+ std::cout << info.options;
+ }
+
+ printf("\n%s\n", info.fullHelp.c_str());
+ }
+}
+
+const std::string& NestedCommandLineApp::resolveAlias(
+ const std::string& name) const {
+ auto dest = &name;
+ for (;;) {
+ auto pos = aliases_.find(*dest);
+ if (pos == aliases_.end()) {
+ break;
+ }
+ dest = &pos->second;
+ }
+ return *dest;
+}
+
+auto NestedCommandLineApp::findCommand(const std::string& name) const
+ -> const std::pair<const std::string, CommandInfo>& {
+ auto pos = commands_.find(resolveAlias(name));
+ if (pos == commands_.end()) {
+ throw ProgramExit(
+ 1,
+ folly::sformat("Command `{}' not found. Run `{} help' for help.",
+ name, programName_));
+ }
+ return *pos;
+}
+
+int NestedCommandLineApp::run(int argc, const char* const argv[]) {
+ if (programName_.empty()) {
+ programName_ = fs::path(argv[0]).filename().native();
+ }
+ return run(std::vector<std::string>(argv + 1, argv + argc));
+}
+
+int NestedCommandLineApp::run(const std::vector<std::string>& args) {
+ int status;
+ try {
+ doRun(args);
+ status = 0;
+ } catch (const ProgramExit& ex) {
+ if (ex.what()[0]) { // if not empty
+ fprintf(stderr, "%s\n", ex.what());
+ }
+ status = ex.status();
+ } catch (const po::error& ex) {
+ fprintf(stderr, "%s. Run `%s help' for help.\n",
+ ex.what(), programName_.c_str());
+ status = 1;
+ }
+
+ if (status == 0) {
+ if (ferror(stdout)) {
+ fprintf(stderr, "error on standard output\n");
+ status = 1;
+ } else if (fflush(stdout)) {
+ fprintf(stderr, "standard output flush failed: %s\n",
+ errnoStr(errno).c_str());
+ status = 1;
+ }
+ }
+
+ return status;
+}
+
+void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
+ if (programName_.empty()) {
+ programName_ = guessProgramName();
+ }
+ auto parsed = parseNestedCommandLine(args, globalOptions_);
+ po::variables_map vm;
+ po::store(parsed.options, vm);
+ if (vm.count("help")) {
+ std::vector<std::string> helpArgs;
+ if (parsed.command) {
+ helpArgs.push_back(*parsed.command);
+ }
+ displayHelp(vm, helpArgs);
+ return;
+ }
+
+ if (vm.count("version")) {
+ printf("%s %s\n", programName_.c_str(), version_.c_str());
+ return;
+ }
+
+ if (!parsed.command) {
+ throw ProgramExit(
+ 1,
+ folly::sformat("Command not specified. Run `{} help' for help.",
+ programName_));
+ }
+
+ auto& p = findCommand(*parsed.command);
+ auto& cmd = p.first;
+ auto& info = p.second;
+
+ auto cmdOptions =
+ po::command_line_parser(parsed.rest).options(info.options).run();
+ po::store(cmdOptions, vm);
+ po::notify(vm);
+
+ auto cmdArgs = po::collect_unrecognized(cmdOptions.options,
+ po::include_positional);
+
+ if (initFunction_) {
+ initFunction_(cmd, vm, cmdArgs);
+ }
+
+ info.command(vm, cmdArgs);
+}
+
+} // namespaces
--- /dev/null
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_NESTEDCOMMANDLINEAPP_H_
+#define FOLLY_NESTEDCOMMANDLINEAPP_H_
+
+#include <functional>
+#include <stdexcept>
+
+#include <folly/experimental/ProgramOptions.h>
+
+namespace folly {
+
+/**
+ * Exception that commands may throw to force the program to exit cleanly
+ * with a non-zero exit code. NestedCommandLineApp::run() catches this and
+ * makes run() print the given message on stderr (followed by a newline, unless
+ * empty), and return the exit code. (Other exceptions will propagate out of
+ * run())
+ */
+class ProgramExit : public std::runtime_error {
+ public:
+ explicit ProgramExit(int status, const std::string& msg = std::string());
+ int status() const { return status_; }
+ private:
+ int status_;
+};
+
+/**
+ * App that uses a nested command line, of the form:
+ *
+ * program [--global_options...] command [--command_options...] command_args...
+ */
+class NestedCommandLineApp {
+ public:
+ typedef std::function<void(
+ const std::string& command,
+ const boost::program_options::variables_map& options,
+ const std::vector<std::string>& args)> InitFunction;
+
+ typedef std::function<void(
+ const boost::program_options::variables_map& options,
+ const std::vector<std::string>&)> Command;
+
+ /**
+ * Initialize the app.
+ *
+ * If programName is not set, we try to guess (readlink("/proc/self/exe")).
+ *
+ * version is the version string printed when given the --version flag.
+ *
+ * initFunction, if specified, is called after parsing the command line,
+ * right before executing the command.
+ */
+ explicit NestedCommandLineApp(
+ std::string programName = std::string(),
+ std::string version = std::string(),
+ InitFunction initFunction = InitFunction());
+
+ /**
+ * Add GFlags to the list of supported options with the given style.
+ */
+ void addGFlags(ProgramOptionsStyle style = ProgramOptionsStyle::GNU) {
+ globalOptions_.add(getGFlags(style));
+ }
+
+ /**
+ * Return the global options object, so you can add options.
+ */
+ boost::program_options::options_description& globalOptions() {
+ return globalOptions_;
+ }
+
+ /**
+ * Add a command.
+ *
+ * name: command name
+ * argStr: description of arguments in help strings
+ * (<filename> <N>)
+ * shortHelp: one-line summary help string
+ * fullHelp: full help string
+ * command: function to run
+ *
+ * Returns a reference to the options_description object that you can
+ * use to add options for this command.
+ */
+ boost::program_options::options_description& addCommand(
+ std::string name,
+ std::string argStr,
+ std::string shortHelp,
+ std::string fullHelp,
+ Command command);
+
+ /**
+ * Add an alias; running the command newName will have the same effect
+ * as running oldName.
+ */
+ void addAlias(std::string newName, std::string oldName);
+
+ /**
+ * Run the command and return; the return code is 0 on success or
+ * non-zero on error, so it is idiomatic to call this at the end of main():
+ * return app.run(argc, argv);
+ */
+ int run(int argc, const char* const argv[]);
+ int run(const std::vector<std::string>& args);
+
+ private:
+ void doRun(const std::vector<std::string>& args);
+ const std::string& resolveAlias(const std::string& name) const;
+
+ struct CommandInfo {
+ std::string argStr;
+ std::string shortHelp;
+ std::string fullHelp;
+ Command command;
+ boost::program_options::options_description options;
+ };
+
+ const std::pair<const std::string, CommandInfo>&
+ findCommand(const std::string& name) const;
+
+ void displayHelp(
+ const boost::program_options::variables_map& options,
+ const std::vector<std::string>& args);
+
+ std::string programName_;
+ std::string version_;
+ InitFunction initFunction_;
+ boost::program_options::options_description globalOptions_;
+ std::map<std::string, CommandInfo> commands_;
+ std::map<std::string, std::string> aliases_;
+};
+
+} // namespaces
+
+#endif /* FOLLY_NESTEDCOMMANDLINEAPP_H_ */
--- /dev/null
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Example application using the nested command line parser.
+//
+// Implements two commands: "cat" and "echo", which behave similarly to their
+// Unix homonyms.
+
+#include <folly/String.h>
+#include <folly/ScopeGuard.h>
+#include <folly/experimental/NestedCommandLineApp.h>
+#include <folly/experimental/ProgramOptions.h>
+
+namespace po = ::boost::program_options;
+
+namespace {
+
+class InputError : public std::runtime_error {
+ public:
+ explicit InputError(const std::string& msg)
+ : std::runtime_error(msg) { }
+};
+
+class OutputError : public std::runtime_error {
+ public:
+ explicit OutputError(const std::string& msg)
+ : std::runtime_error(msg) { }
+};
+
+class Concatenator {
+ public:
+ explicit Concatenator(const po::variables_map& options)
+ : printLineNumbers_(options["number"].as<bool>()) { }
+
+ void cat(const std::string& name);
+ void cat(FILE* file);
+
+ bool printLineNumbers() const { return printLineNumbers_; }
+
+ private:
+ bool printLineNumbers_;
+ size_t lineNumber_ = 0;
+};
+
+FOLLY_NORETURN void throwOutputError() {
+ throw OutputError(folly::errnoStr(errno).toStdString());
+}
+
+FOLLY_NORETURN void throwInputError() {
+ throw InputError(folly::errnoStr(errno).toStdString());
+}
+
+void Concatenator::cat(FILE* file) {
+ char* lineBuf = nullptr;
+ size_t lineBufSize = 0;
+ SCOPE_EXIT {
+ free(lineBuf);
+ };
+
+ ssize_t n;
+ while ((n = getline(&lineBuf, &lineBufSize, file)) >= 0) {
+ ++lineNumber_;
+ if ((printLineNumbers_ && printf("%6zu ", lineNumber_) < 0) ||
+ fwrite(lineBuf, 1, n, stdout) < size_t(n)) {
+ throwOutputError();
+ }
+ }
+
+ if (ferror(file)) {
+ throwInputError();
+ }
+}
+
+void Concatenator::cat(const std::string& name) {
+ auto file = fopen(name.c_str(), "r");
+ if (!file) {
+ throwInputError();
+ }
+
+ // Ignore error, as we might be processing an exception;
+ // during normal operation, we call fclose() directly further below
+ auto guard = folly::makeGuard([file] { fclose(file); });
+
+ cat(file);
+
+ guard.dismiss();
+ if (fclose(file)) {
+ throwInputError();
+ }
+}
+
+void runCat(const po::variables_map& options,
+ const std::vector<std::string>& args) {
+ Concatenator concatenator(options);
+ bool ok = true;
+ auto catFile = [&concatenator, &ok] (const std::string& name) {
+ try {
+ if (name == "-") {
+ concatenator.cat(stdin);
+ } else {
+ concatenator.cat(name);
+ }
+ } catch (const InputError& e) {
+ ok = false;
+ fprintf(stderr, "cat: %s: %s\n", name.c_str(), e.what());
+ }
+ };
+
+ try {
+ if (args.empty()) {
+ catFile("-");
+ } else {
+ for (auto& name : args) {
+ catFile(name);
+ }
+ }
+ } catch (const OutputError& e) {
+ throw folly::ProgramExit(
+ 1, folly::to<std::string>("cat: write error: ", e.what()));
+ }
+ if (!ok) {
+ throw folly::ProgramExit(1);
+ }
+}
+
+void runEcho(const po::variables_map& options,
+ const std::vector<std::string>& args) {
+ try {
+ const char* sep = "";
+ for (auto& arg : args) {
+ if (printf("%s%s", sep, arg.c_str()) < 0) {
+ throw OutputError(folly::errnoStr(errno).toStdString());
+ }
+ sep = " ";
+ }
+ if (!options["-n"].as<bool>()) {
+ if (putchar('\n') == EOF) {
+ throw OutputError(folly::errnoStr(errno).toStdString());
+ }
+ }
+ } catch (const OutputError& e) {
+ throw folly::ProgramExit(
+ 1, folly::to<std::string>("echo: write error: ", e.what()));
+ }
+}
+
+} // namespace
+
+int main(int argc, char *argv[]) {
+ // Initialize a NestedCommandLineApp object.
+ //
+ // The first argument is the program name -- an empty string will cause the
+ // program name to be deduced from the executable name, which is usually
+ // fine. The second argument is a version string.
+ //
+ // You may also add an "initialization function" that is always called
+ // for every valid command before the command is executed.
+ folly::NestedCommandLineApp app("", "0.1");
+
+ // Add any GFlags-defined flags. These are global flags, and so they should
+ // be valid for any command.
+ app.addGFlags();
+
+ // Add any commands. For our example, we'll implement simplified versions
+ // of "cat" and "echo". Note that addCommand() returns a reference to a
+ // boost::program_options object that you may use to add command-specific
+ // options.
+ app.addCommand(
+ // command name
+ "cat",
+
+ // argument description
+ "[file...]",
+
+ // short help string
+ "Concatenate files and print them on standard output",
+
+ // Long help string
+ "Concatenate files and print them on standard output.",
+
+ // Function to execute
+ runCat)
+ .add_options()
+ ("number,n", po::bool_switch(), "number all output lines");
+
+ app.addCommand(
+ "echo",
+ "[string...]",
+ "Display a line of text",
+ "Display a line of text.",
+ runEcho)
+ .add_options()
+ (",n", po::bool_switch(), "do not output the trailing newline");
+
+ // You may also add command aliases -- that is, multiple command names
+ // that do the same thing; see addAlias().
+
+ // app.run returns an appropriate error code
+ return app.run(argc, argv);
+}
--- /dev/null
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/experimental/NestedCommandLineApp.h>
+
+#include <folly/Subprocess.h>
+#include <folly/experimental/io/FsUtil.h>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+namespace folly { namespace test {
+
+namespace {
+
+std::string getHelperPath() {
+ auto path = fs::executable_path();
+ path.remove_filename() /= "nested_command_line_app_test_helper";
+ return path.native();
+}
+
+std::string callHelper(std::initializer_list<std::string> args,
+ int expectedExitCode = 0,
+ int stdoutFd = -1) {
+ static std::string helperPath = getHelperPath();
+
+ std::vector<std::string> allArgs;
+ allArgs.reserve(args.size() + 1);
+ allArgs.push_back(helperPath);
+ allArgs.insert(allArgs.end(), args.begin(), args.end());
+
+ Subprocess::Options options;
+ if (stdoutFd != -1) {
+ options.stdout(stdoutFd);
+ } else {
+ options.pipeStdout();
+ }
+ options.pipeStderr();
+
+ Subprocess proc(allArgs, options);
+ auto p = proc.communicate();
+ EXPECT_EQ(expectedExitCode, proc.wait().exitStatus());
+
+ return p.first;
+}
+
+} // namespace
+
+TEST(ProgramOptionsTest, Errors) {
+ callHelper({}, 1);
+ callHelper({"--wtf", "foo"}, 1);
+ callHelper({"qux"}, 1);
+ callHelper({"--global-foo", "x", "foo"}, 1);
+}
+
+TEST(ProgramOptionsTest, Help) {
+ // Not actually checking help output, just verifying that help doesn't fail
+ callHelper({"--version"});
+ callHelper({"--help"});
+ callHelper({"--help", "foo"});
+ callHelper({"--help", "bar"});
+ callHelper({"help"});
+ callHelper({"help", "foo"});
+ callHelper({"help", "bar"});
+
+ // wrong command name
+ callHelper({"--help", "qux"}, 1);
+ callHelper({"help", "qux"}, 1);
+}
+
+TEST(ProgramOptionsTest, DevFull) {
+ folly::File full("/dev/full", O_RDWR);
+ callHelper({"--help"}, 1, full.fd());
+}
+
+TEST(ProgramOptionsTest, Success) {
+ EXPECT_EQ(
+ "running foo\n"
+ "foo global-foo 42\n"
+ "foo local-foo 42\n",
+ callHelper({"foo"}));
+
+ EXPECT_EQ(
+ "running foo\n"
+ "foo global-foo 43\n"
+ "foo local-foo 44\n"
+ "foo arg a\n"
+ "foo arg b\n",
+ callHelper({"--global-foo", "43", "foo", "--local-foo", "44",
+ "a", "b"}));
+
+ // Check that global flags can still be given after the command
+ EXPECT_EQ(
+ "running foo\n"
+ "foo global-foo 43\n"
+ "foo local-foo 44\n"
+ "foo arg a\n"
+ "foo arg b\n",
+ callHelper({"foo", "--global-foo", "43", "--local-foo", "44",
+ "a", "b"}));
+}
+
+TEST(ProgramOptionsTest, Aliases) {
+ EXPECT_EQ(
+ "running foo\n"
+ "foo global-foo 43\n"
+ "foo local-foo 44\n"
+ "foo arg a\n"
+ "foo arg b\n",
+ callHelper({"--global-foo", "43", "bar", "--local-foo", "44",
+ "a", "b"}));
+}
+
+}} // namespaces
--- /dev/null
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/experimental/NestedCommandLineApp.h>
+#include <gflags/gflags.h>
+
+DEFINE_int32(global_foo, 42, "Global foo");
+
+namespace po = ::boost::program_options;
+
+namespace {
+
+void init(const std::string& cmd,
+ const po::variables_map& options,
+ const std::vector<std::string>& args) {
+ printf("running %s\n", cmd.c_str());
+}
+
+void foo(const po::variables_map& options,
+ const std::vector<std::string>& args) {
+ printf("foo global-foo %d\n", options["global-foo"].as<int32_t>());
+ printf("foo local-foo %d\n", options["local-foo"].as<int32_t>());
+ for (auto& arg : args) {
+ printf("foo arg %s\n", arg.c_str());
+ }
+}
+
+} // namespace
+
+int main(int argc, char *argv[]) {
+ folly::NestedCommandLineApp app("", "0.1", init);
+ app.addGFlags();
+ app.addCommand("foo", "[args...]", "Do some foo", "Does foo", foo)
+ .add_options()
+ ("local-foo", po::value<int32_t>()->default_value(42), "Local foo");
+ app.addAlias("bar", "foo");
+ app.addAlias("baz", "bar");
+ return app.run(argc, argv);
+}