2 * Copyright 2017 Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include <folly/experimental/NestedCommandLineApp.h>
21 #include <folly/FileUtil.h>
22 #include <folly/Format.h>
23 #include <folly/experimental/io/FsUtil.h>
25 namespace po = ::boost::program_options;
31 // Guess the program name as basename(executable)
32 std::string guessProgramName() {
34 return fs::executable_path().filename().string();
35 } catch (const std::exception&) {
42 ProgramExit::ProgramExit(int status, const std::string& msg)
43 : std::runtime_error(msg),
45 // Message is only allowed for non-zero exit status
46 CHECK(status_ != 0 || msg.empty());
49 NestedCommandLineApp::NestedCommandLineApp(
50 std::string programName,
52 std::string programHeading,
53 std::string programHelpFooter,
54 InitFunction initFunction)
55 : programName_(std::move(programName)),
56 programHeading_(std::move(programHeading)),
57 programHelpFooter_(std::move(programHelpFooter)),
58 version_(std::move(version)),
59 initFunction_(std::move(initFunction)),
60 globalOptions_("Global options") {
61 addCommand("help", "[command]",
62 "Display help (globally or for a given command)",
63 "Displays help (globally or for a given command).",
64 [this] (const po::variables_map& vm,
65 const std::vector<std::string>& args) {
66 displayHelp(vm, args);
69 globalOptions_.add_options()
70 ("help,h", "Display help (globally or for a given command)")
71 ("version", "Display version information");
74 po::options_description& NestedCommandLineApp::addCommand(
77 std::string shortHelp,
85 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";
91 return p.first->second.options;
94 void NestedCommandLineApp::addAlias(std::string newName,
95 std::string oldName) {
96 CHECK(aliases_.count(oldName) || commands_.count(oldName))
97 << "Alias old name does not exist";
98 CHECK(!aliases_.count(newName) && !commands_.count(newName))
99 << "Alias new name already exists";
100 aliases_.emplace(std::move(newName), std::move(oldName));
103 void NestedCommandLineApp::displayHelp(
104 const po::variables_map& /* globalOptions */,
105 const std::vector<std::string>& args) {
109 "%s\nUsage: %s [global_options...] <command> [command_options...] "
110 "[command_args...]\n\n",
111 programHeading_.c_str(),
112 programName_.c_str());
113 std::cout << globalOptions_;
114 printf("\nAvailable commands:\n");
117 for (auto& p : commands_) {
118 maxLen = std::max(maxLen, p.first.size());
120 for (auto& p : aliases_) {
121 maxLen = std::max(maxLen, p.first.size());
124 for (auto& p : commands_) {
126 int(maxLen), p.first.c_str(), p.second.shortHelp.c_str());
129 if (!aliases_.empty()) {
130 printf("\nAvailable aliases:\n");
131 for (auto& p : aliases_) {
132 printf(" %-*s => %s\n",
133 int(maxLen), p.first.c_str(), resolveAlias(p.second).c_str());
136 std::cout << "\n" << programHelpFooter_ << "\n";
138 // Help for a given command
139 auto& p = findCommand(args.front());
140 if (p.first != args.front()) {
141 printf("`%s' is an alias for `%s'; showing help for `%s'\n",
142 args.front().c_str(), p.first.c_str(), p.first.c_str());
144 auto& info = p.second;
147 "Usage: %s [global_options...] %s%s%s%s\n\n",
148 programName_.c_str(),
150 info.options.options().empty() ? "" : " [command_options...]",
151 info.argStr.empty() ? "" : " ",
152 info.argStr.c_str());
154 printf("%s\n", info.fullHelp.c_str());
156 std::cout << globalOptions_;
158 if (!info.options.options().empty()) {
160 std::cout << info.options;
165 const std::string& NestedCommandLineApp::resolveAlias(
166 const std::string& name) const {
169 auto pos = aliases_.find(*dest);
170 if (pos == aliases_.end()) {
178 auto NestedCommandLineApp::findCommand(const std::string& name) const
179 -> const std::pair<const std::string, CommandInfo>& {
180 auto pos = commands_.find(resolveAlias(name));
181 if (pos == commands_.end()) {
184 folly::sformat("Command `{}' not found. Run `{} help' for help.",
185 name, programName_));
190 int NestedCommandLineApp::run(int argc, const char* const argv[]) {
191 if (programName_.empty()) {
192 programName_ = fs::path(argv[0]).filename().string();
194 return run(std::vector<std::string>(argv + 1, argv + argc));
197 int NestedCommandLineApp::run(const std::vector<std::string>& args) {
202 } catch (const ProgramExit& ex) {
203 if (ex.what()[0]) { // if not empty
204 fprintf(stderr, "%s\n", ex.what());
206 status = ex.status();
207 } catch (const po::error& ex) {
208 fprintf(stderr, "%s. Run `%s help' for help.\n",
209 ex.what(), programName_.c_str());
214 if (ferror(stdout)) {
215 fprintf(stderr, "error on standard output\n");
217 } else if (fflush(stdout)) {
218 fprintf(stderr, "standard output flush failed: %s\n",
219 errnoStr(errno).c_str());
227 void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
228 if (programName_.empty()) {
229 programName_ = guessProgramName();
231 auto parsed = parseNestedCommandLine(args, globalOptions_);
232 po::variables_map vm;
233 po::store(parsed.options, vm);
234 if (vm.count("help")) {
235 std::vector<std::string> helpArgs;
236 if (parsed.command) {
237 helpArgs.push_back(*parsed.command);
239 displayHelp(vm, helpArgs);
243 if (vm.count("version")) {
244 printf("%s %s\n", programName_.c_str(), version_.c_str());
248 if (!parsed.command) {
251 folly::sformat("Command not specified. Run `{} help' for help.",
255 auto& p = findCommand(*parsed.command);
257 auto& info = p.second;
260 po::command_line_parser(parsed.rest).options(info.options).run();
261 po::store(cmdOptions, vm);
264 auto cmdArgs = po::collect_unrecognized(cmdOptions.options,
265 po::include_positional);
268 initFunction_(cmd, vm, cmdArgs);
271 info.command(vm, cmdArgs);