allow command to accept "--" separator
[folly.git] / folly / experimental / NestedCommandLineApp.cpp
index b468f515c5c1b16048a56809a3c2af56a4e3ae80..e694f1f5d545548853db29c6042ab680c96ad4d4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2015-present Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
 #include <folly/experimental/NestedCommandLineApp.h>
 
 #include <iostream>
+
 #include <folly/FileUtil.h>
 #include <folly/Format.h>
 #include <folly/experimental/io/FsUtil.h>
@@ -30,28 +31,33 @@ namespace {
 // Guess the program name as basename(executable)
 std::string guessProgramName() {
   try {
-    return fs::executable_path().filename().native();
+    return fs::executable_path().filename().string();
   } catch (const std::exception&) {
     return "UNKNOWN";
   }
 }
 
-}  // namespace
+} // namespace
 
 ProgramExit::ProgramExit(int status, const std::string& msg)
   : std::runtime_error(msg),
     status_(status) {
-  CHECK_NE(status, 0);
+  // Message is only allowed for non-zero exit status
+  CHECK(status_ != 0 || msg.empty());
 }
 
 NestedCommandLineApp::NestedCommandLineApp(
     std::string programName,
     std::string version,
+    std::string programHeading,
+    std::string programHelpFooter,
     InitFunction initFunction)
-  : programName_(std::move(programName)),
-    version_(std::move(version)),
-    initFunction_(std::move(initFunction)),
-    globalOptions_("Global options") {
+    : programName_(std::move(programName)),
+      programHeading_(std::move(programHeading)),
+      programHelpFooter_(std::move(programHelpFooter)),
+      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).",
@@ -95,13 +101,15 @@ void NestedCommandLineApp::addAlias(std::string newName,
 }
 
 void NestedCommandLineApp::displayHelp(
-    const po::variables_map& globalOptions,
+    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());
+        "%s\nUsage: %s [global_options...] <command> [command_options...] "
+        "[command_args...]\n\n",
+        programHeading_.c_str(),
+        programName_.c_str());
     std::cout << globalOptions_;
     printf("\nAvailable commands:\n");
 
@@ -125,12 +133,13 @@ void NestedCommandLineApp::displayHelp(
                int(maxLen), p.first.c_str(), resolveAlias(p.second).c_str());
       }
     }
+    std::cout << "\n" << programHelpFooter_ << "\n";
   } 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());
+      printf("`%s' is an alias for `%s'; showing help for `%s'\n",
+             args.front().c_str(), p.first.c_str(), p.first.c_str());
     }
     auto& info = p.second;
 
@@ -142,14 +151,14 @@ void NestedCommandLineApp::displayHelp(
         info.argStr.empty() ? "" : " ",
         info.argStr.c_str());
 
+    printf("%s\n", info.fullHelp.c_str());
+
     std::cout << globalOptions_;
 
     if (!info.options.options().empty()) {
       printf("\n");
       std::cout << info.options;
     }
-
-    printf("\n%s\n", info.fullHelp.c_str());
   }
 }
 
@@ -180,7 +189,7 @@ auto NestedCommandLineApp::findCommand(const std::string& name) const
 
 int NestedCommandLineApp::run(int argc, const char* const argv[]) {
   if (programName_.empty()) {
-    programName_ = fs::path(argv[0]).filename().native();
+    programName_ = fs::path(argv[0]).filename().string();
   }
   return run(std::vector<std::string>(argv + 1, argv + argc));
 }
@@ -219,7 +228,22 @@ void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
   if (programName_.empty()) {
     programName_ = guessProgramName();
   }
-  auto parsed = parseNestedCommandLine(args, globalOptions_);
+
+  bool not_clean = false;
+  std::vector<std::string> cleanArgs;
+  std::vector<std::string> endArgs;
+
+  for (auto& na : args) {
+    if (na == "--") {
+      not_clean = true;
+    } else if (not_clean) {
+      endArgs.push_back(na);
+    } else {
+      cleanArgs.push_back(na);
+    }
+  }
+
+  auto parsed = parseNestedCommandLine(cleanArgs, globalOptions_);
   po::variables_map vm;
   po::store(parsed.options, vm);
   if (vm.count("help")) {
@@ -249,12 +273,15 @@ void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
 
   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);
 
+  cmdArgs.insert(cmdArgs.end(), endArgs.begin(), endArgs.end());
+
   if (initFunction_) {
     initFunction_(cmd, vm, cmdArgs);
   }
@@ -262,4 +289,4 @@ void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
   info.command(vm, cmdArgs);
 }
 
-}  // namespaces
+} // namespace folly