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>
20 #include <folly/FileUtil.h>
21 #include <folly/Format.h>
22 #include <folly/experimental/io/FsUtil.h>
24 namespace po = ::boost::program_options;
30 // Guess the program name as basename(executable)
31 std::string guessProgramName() {
33 return fs::executable_path().filename().string();
34 } catch (const std::exception&) {
41 ProgramExit::ProgramExit(int status, const std::string& msg)
42 : std::runtime_error(msg),
44 // Message is only allowed for non-zero exit status
45 CHECK(status_ != 0 || msg.empty());
48 NestedCommandLineApp::NestedCommandLineApp(
49 std::string programName,
51 std::string programHeading,
52 std::string programHelpFooter,
53 InitFunction initFunction)
54 : programName_(std::move(programName)),
55 programHeading_(std::move(programHeading)),
56 programHelpFooter_(std::move(programHelpFooter)),
57 version_(std::move(version)),
58 initFunction_(std::move(initFunction)),
59 globalOptions_("Global options") {
60 addCommand("help", "[command]",
61 "Display help (globally or for a given command)",
62 "Displays help (globally or for a given command).",
63 [this] (const po::variables_map& vm,
64 const std::vector<std::string>& args) {
65 displayHelp(vm, args);
68 globalOptions_.add_options()
69 ("help,h", "Display help (globally or for a given command)")
70 ("version", "Display version information");
73 po::options_description& NestedCommandLineApp::addCommand(
76 std::string shortHelp,
84 po::options_description(folly::sformat("Options for `{}'", name))
87 auto p = commands_.emplace(std::move(name), std::move(info));
88 CHECK(p.second) << "Command already exists";
90 return p.first->second.options;
93 void NestedCommandLineApp::addAlias(std::string newName,
94 std::string oldName) {
95 CHECK(aliases_.count(oldName) || commands_.count(oldName))
96 << "Alias old name does not exist";
97 CHECK(!aliases_.count(newName) && !commands_.count(newName))
98 << "Alias new name already exists";
99 aliases_.emplace(std::move(newName), std::move(oldName));
102 void NestedCommandLineApp::displayHelp(
103 const po::variables_map& /* globalOptions */,
104 const std::vector<std::string>& args) {
108 "%s\nUsage: %s [global_options...] <command> [command_options...] "
109 "[command_args...]\n\n",
110 programHeading_.c_str(),
111 programName_.c_str());
112 std::cout << globalOptions_;
113 printf("\nAvailable commands:\n");
116 for (auto& p : commands_) {
117 maxLen = std::max(maxLen, p.first.size());
119 for (auto& p : aliases_) {
120 maxLen = std::max(maxLen, p.first.size());
123 for (auto& p : commands_) {
125 int(maxLen), p.first.c_str(), p.second.shortHelp.c_str());
128 if (!aliases_.empty()) {
129 printf("\nAvailable aliases:\n");
130 for (auto& p : aliases_) {
131 printf(" %-*s => %s\n",
132 int(maxLen), p.first.c_str(), resolveAlias(p.second).c_str());
135 std::cout << "\n" << programHelpFooter_ << "\n";
137 // Help for a given command
138 auto& p = findCommand(args.front());
139 if (p.first != args.front()) {
140 printf("`%s' is an alias for `%s'; showing help for `%s'\n",
141 args.front().c_str(), p.first.c_str(), p.first.c_str());
143 auto& info = p.second;
146 "Usage: %s [global_options...] %s%s%s%s\n\n",
147 programName_.c_str(),
149 info.options.options().empty() ? "" : " [command_options...]",
150 info.argStr.empty() ? "" : " ",
151 info.argStr.c_str());
153 printf("%s\n", info.fullHelp.c_str());
155 std::cout << globalOptions_;
157 if (!info.options.options().empty()) {
159 std::cout << info.options;
164 const std::string& NestedCommandLineApp::resolveAlias(
165 const std::string& name) const {
168 auto pos = aliases_.find(*dest);
169 if (pos == aliases_.end()) {
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()) {
183 folly::sformat("Command `{}' not found. Run `{} help' for help.",
184 name, programName_));
189 int NestedCommandLineApp::run(int argc, const char* const argv[]) {
190 if (programName_.empty()) {
191 programName_ = fs::path(argv[0]).filename().string();
193 return run(std::vector<std::string>(argv + 1, argv + argc));
196 int NestedCommandLineApp::run(const std::vector<std::string>& args) {
201 } catch (const ProgramExit& ex) {
202 if (ex.what()[0]) { // if not empty
203 fprintf(stderr, "%s\n", ex.what());
205 status = ex.status();
206 } catch (const po::error& ex) {
207 fprintf(stderr, "%s. Run `%s help' for help.\n",
208 ex.what(), programName_.c_str());
213 if (ferror(stdout)) {
214 fprintf(stderr, "error on standard output\n");
216 } else if (fflush(stdout)) {
217 fprintf(stderr, "standard output flush failed: %s\n",
218 errnoStr(errno).c_str());
226 void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
227 if (programName_.empty()) {
228 programName_ = guessProgramName();
230 auto parsed = parseNestedCommandLine(args, globalOptions_);
231 po::variables_map vm;
232 po::store(parsed.options, vm);
233 if (vm.count("help")) {
234 std::vector<std::string> helpArgs;
235 if (parsed.command) {
236 helpArgs.push_back(*parsed.command);
238 displayHelp(vm, helpArgs);
242 if (vm.count("version")) {
243 printf("%s %s\n", programName_.c_str(), version_.c_str());
247 if (!parsed.command) {
250 folly::sformat("Command not specified. Run `{} help' for help.",
254 auto& p = findCommand(*parsed.command);
256 auto& info = p.second;
259 po::command_line_parser(parsed.rest).options(info.options).run();
260 po::store(cmdOptions, vm);
263 auto cmdArgs = po::collect_unrecognized(cmdOptions.options,
264 po::include_positional);
267 initFunction_(cmd, vm, cmdArgs);
270 info.command(vm, cmdArgs);