NestedCommandLineApp.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /*
  2. * Copyright 2015-present Facebook, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include <folly/experimental/NestedCommandLineApp.h>
  17. #include <iostream>
  18. #include <folly/FileUtil.h>
  19. #include <folly/Format.h>
  20. #include <folly/experimental/io/FsUtil.h>
  21. namespace po = ::boost::program_options;
  22. namespace folly {
  23. namespace {
  24. // Guess the program name as basename(executable)
  25. std::string guessProgramName() {
  26. try {
  27. return fs::executable_path().filename().string();
  28. } catch (const std::exception&) {
  29. return "UNKNOWN";
  30. }
  31. }
  32. } // namespace
  33. ProgramExit::ProgramExit(int status, const std::string& msg)
  34. : std::runtime_error(msg), status_(status) {
  35. // Message is only allowed for non-zero exit status
  36. CHECK(status_ != 0 || msg.empty());
  37. }
  38. constexpr StringPiece const NestedCommandLineApp::kHelpCommand;
  39. constexpr StringPiece const NestedCommandLineApp::kVersionCommand;
  40. NestedCommandLineApp::NestedCommandLineApp(
  41. std::string programName,
  42. std::string version,
  43. std::string programHeading,
  44. std::string programHelpFooter,
  45. InitFunction initFunction)
  46. : programName_(std::move(programName)),
  47. programHeading_(std::move(programHeading)),
  48. programHelpFooter_(std::move(programHelpFooter)),
  49. version_(std::move(version)),
  50. initFunction_(std::move(initFunction)),
  51. globalOptions_("Global options") {
  52. addCommand(
  53. kHelpCommand.str(),
  54. "[command]",
  55. "Display help (globally or for a given command)",
  56. "Displays help (globally or for a given command).",
  57. [this](
  58. const po::variables_map& vm, const std::vector<std::string>& args) {
  59. displayHelp(vm, args);
  60. });
  61. builtinCommands_.insert(kHelpCommand);
  62. addCommand(
  63. kVersionCommand.str(),
  64. "[command]",
  65. "Display version information",
  66. "Displays version information.",
  67. [this](const po::variables_map&, const std::vector<std::string>&) {
  68. displayVersion();
  69. });
  70. builtinCommands_.insert(kVersionCommand);
  71. globalOptions_.add_options()(
  72. kHelpCommand.str().c_str(),
  73. "Display help (globally or for a given command)")(
  74. kVersionCommand.str().c_str(), "Display version information");
  75. }
  76. po::options_description& NestedCommandLineApp::addCommand(
  77. std::string name,
  78. std::string argStr,
  79. std::string shortHelp,
  80. std::string fullHelp,
  81. Command command) {
  82. CommandInfo info{
  83. std::move(argStr),
  84. std::move(shortHelp),
  85. std::move(fullHelp),
  86. std::move(command),
  87. po::options_description(folly::sformat("Options for `{}'", name))};
  88. auto p = commands_.emplace(std::move(name), std::move(info));
  89. CHECK(p.second) << "Command already exists";
  90. return p.first->second.options;
  91. }
  92. void NestedCommandLineApp::addAlias(std::string newName, std::string oldName) {
  93. CHECK(aliases_.count(oldName) || commands_.count(oldName))
  94. << "Alias old name does not exist";
  95. CHECK(!aliases_.count(newName) && !commands_.count(newName))
  96. << "Alias new name already exists";
  97. aliases_.emplace(std::move(newName), std::move(oldName));
  98. }
  99. void NestedCommandLineApp::displayHelp(
  100. const po::variables_map& /* globalOptions */,
  101. const std::vector<std::string>& args) const {
  102. if (args.empty()) {
  103. // General help
  104. printf(
  105. "%s\nUsage: %s [global_options...] <command> [command_options...] "
  106. "[command_args...]\n\n",
  107. programHeading_.c_str(),
  108. programName_.c_str());
  109. std::cout << globalOptions_;
  110. printf("\nAvailable commands:\n");
  111. size_t maxLen = 0;
  112. for (auto& p : commands_) {
  113. maxLen = std::max(maxLen, p.first.size());
  114. }
  115. for (auto& p : aliases_) {
  116. maxLen = std::max(maxLen, p.first.size());
  117. }
  118. for (auto& p : commands_) {
  119. printf(
  120. " %-*s %s\n",
  121. int(maxLen),
  122. p.first.c_str(),
  123. p.second.shortHelp.c_str());
  124. }
  125. if (!aliases_.empty()) {
  126. printf("\nAvailable aliases:\n");
  127. for (auto& p : aliases_) {
  128. printf(
  129. " %-*s => %s\n",
  130. int(maxLen),
  131. p.first.c_str(),
  132. resolveAlias(p.second).c_str());
  133. }
  134. }
  135. std::cout << "\n" << programHelpFooter_ << "\n";
  136. } else {
  137. // Help for a given command
  138. auto& p = findCommand(args.front());
  139. if (p.first != args.front()) {
  140. printf(
  141. "`%s' is an alias for `%s'; showing help for `%s'\n",
  142. args.front().c_str(),
  143. p.first.c_str(),
  144. p.first.c_str());
  145. }
  146. auto& info = p.second;
  147. printf(
  148. "Usage: %s [global_options...] %s%s%s%s\n\n",
  149. programName_.c_str(),
  150. p.first.c_str(),
  151. info.options.options().empty() ? "" : " [command_options...]",
  152. info.argStr.empty() ? "" : " ",
  153. info.argStr.c_str());
  154. printf("%s\n", info.fullHelp.c_str());
  155. std::cout << globalOptions_;
  156. if (!info.options.options().empty()) {
  157. printf("\n");
  158. std::cout << info.options;
  159. }
  160. }
  161. }
  162. void NestedCommandLineApp::displayVersion() const {
  163. printf("%s %s\n", programName_.c_str(), version_.c_str());
  164. }
  165. const std::string& NestedCommandLineApp::resolveAlias(
  166. const std::string& name) const {
  167. auto dest = &name;
  168. for (;;) {
  169. auto pos = aliases_.find(*dest);
  170. if (pos == aliases_.end()) {
  171. break;
  172. }
  173. dest = &pos->second;
  174. }
  175. return *dest;
  176. }
  177. auto NestedCommandLineApp::findCommand(const std::string& name) const
  178. -> const std::pair<const std::string, CommandInfo>& {
  179. auto pos = commands_.find(resolveAlias(name));
  180. if (pos == commands_.end()) {
  181. throw ProgramExit(
  182. 1,
  183. folly::sformat(
  184. "Command '{}' not found. Run '{} {}' for help.",
  185. name,
  186. programName_,
  187. kHelpCommand));
  188. }
  189. return *pos;
  190. }
  191. int NestedCommandLineApp::run(int argc, const char* const argv[]) {
  192. if (programName_.empty()) {
  193. programName_ = fs::path(argv[0]).filename().string();
  194. }
  195. return run(std::vector<std::string>(argv + 1, argv + argc));
  196. }
  197. int NestedCommandLineApp::run(const std::vector<std::string>& args) {
  198. int status;
  199. try {
  200. doRun(args);
  201. status = 0;
  202. } catch (const ProgramExit& ex) {
  203. if (ex.what()[0]) { // if not empty
  204. fprintf(stderr, "%s\n", ex.what());
  205. }
  206. status = ex.status();
  207. } catch (const po::error& ex) {
  208. fprintf(
  209. stderr,
  210. "%s",
  211. folly::sformat(
  212. "{}. Run '{} help' for {}.\n",
  213. ex.what(),
  214. programName_,
  215. kHelpCommand)
  216. .c_str());
  217. status = 1;
  218. }
  219. if (status == 0) {
  220. if (ferror(stdout)) {
  221. fprintf(stderr, "error on standard output\n");
  222. status = 1;
  223. } else if (fflush(stdout)) {
  224. fprintf(
  225. stderr,
  226. "standard output flush failed: %s\n",
  227. errnoStr(errno).c_str());
  228. status = 1;
  229. }
  230. }
  231. return status;
  232. }
  233. void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
  234. if (programName_.empty()) {
  235. programName_ = guessProgramName();
  236. }
  237. bool not_clean = false;
  238. std::vector<std::string> cleanArgs;
  239. std::vector<std::string> endArgs;
  240. for (auto& na : args) {
  241. if (na == "--") {
  242. not_clean = true;
  243. } else if (not_clean) {
  244. endArgs.push_back(na);
  245. } else {
  246. cleanArgs.push_back(na);
  247. }
  248. }
  249. auto parsed = parseNestedCommandLine(cleanArgs, globalOptions_);
  250. po::variables_map vm;
  251. po::store(parsed.options, vm);
  252. if (vm.count(kHelpCommand.str())) {
  253. std::vector<std::string> helpArgs;
  254. if (parsed.command) {
  255. helpArgs.push_back(*parsed.command);
  256. }
  257. displayHelp(vm, helpArgs);
  258. return;
  259. }
  260. if (vm.count(kVersionCommand.str())) {
  261. displayVersion();
  262. return;
  263. }
  264. if (!parsed.command) {
  265. throw ProgramExit(
  266. 1,
  267. folly::sformat(
  268. "Command not specified. Run '{} {}' for help.",
  269. programName_,
  270. kHelpCommand));
  271. }
  272. auto& p = findCommand(*parsed.command);
  273. auto& cmd = p.first;
  274. auto& info = p.second;
  275. auto cmdOptions =
  276. po::command_line_parser(parsed.rest).options(info.options).run();
  277. po::store(cmdOptions, vm);
  278. po::notify(vm);
  279. auto cmdArgs =
  280. po::collect_unrecognized(cmdOptions.options, po::include_positional);
  281. cmdArgs.insert(cmdArgs.end(), endArgs.begin(), endArgs.end());
  282. if (initFunction_) {
  283. initFunction_(cmd, vm, cmdArgs);
  284. }
  285. info.command(vm, cmdArgs);
  286. }
  287. bool NestedCommandLineApp::isBuiltinCommand(const std::string& name) const {
  288. return builtinCommands_.count(name);
  289. }
  290. } // namespace folly