2 * Copyright 2016 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 InitFunction initFunction)
52 : programName_(std::move(programName)),
53 version_(std::move(version)),
54 initFunction_(std::move(initFunction)),
55 globalOptions_("Global options") {
56 addCommand("help", "[command]",
57 "Display help (globally or for a given command)",
58 "Displays help (globally or for a given command).",
59 [this] (const po::variables_map& vm,
60 const std::vector<std::string>& args) {
61 displayHelp(vm, args);
64 globalOptions_.add_options()
65 ("help,h", "Display help (globally or for a given command)")
66 ("version", "Display version information");
69 po::options_description& NestedCommandLineApp::addCommand(
72 std::string shortHelp,
80 po::options_description(folly::sformat("Options for `{}'", name))
83 auto p = commands_.emplace(std::move(name), std::move(info));
84 CHECK(p.second) << "Command already exists";
86 return p.first->second.options;
89 void NestedCommandLineApp::addAlias(std::string newName,
90 std::string oldName) {
91 CHECK(aliases_.count(oldName) || commands_.count(oldName))
92 << "Alias old name does not exist";
93 CHECK(!aliases_.count(newName) && !commands_.count(newName))
94 << "Alias new name already exists";
95 aliases_.emplace(std::move(newName), std::move(oldName));
98 void NestedCommandLineApp::displayHelp(
99 const po::variables_map& /* globalOptions */,
100 const std::vector<std::string>& args) {
104 "Usage: %s [global_options...] <command> [command_options...] "
105 "[command_args...]\n\n", programName_.c_str());
106 std::cout << globalOptions_;
107 printf("\nAvailable commands:\n");
110 for (auto& p : commands_) {
111 maxLen = std::max(maxLen, p.first.size());
113 for (auto& p : aliases_) {
114 maxLen = std::max(maxLen, p.first.size());
117 for (auto& p : commands_) {
119 int(maxLen), p.first.c_str(), p.second.shortHelp.c_str());
122 if (!aliases_.empty()) {
123 printf("\nAvailable aliases:\n");
124 for (auto& p : aliases_) {
125 printf(" %-*s => %s\n",
126 int(maxLen), p.first.c_str(), resolveAlias(p.second).c_str());
130 // Help for a given command
131 auto& p = findCommand(args.front());
132 if (p.first != args.front()) {
133 printf("`%s' is an alias for `%s'; showing help for `%s'\n",
134 args.front().c_str(), p.first.c_str(), p.first.c_str());
136 auto& info = p.second;
139 "Usage: %s [global_options...] %s%s%s%s\n\n",
140 programName_.c_str(),
142 info.options.options().empty() ? "" : " [command_options...]",
143 info.argStr.empty() ? "" : " ",
144 info.argStr.c_str());
146 std::cout << globalOptions_;
148 if (!info.options.options().empty()) {
150 std::cout << info.options;
153 printf("\n%s\n", info.fullHelp.c_str());
157 const std::string& NestedCommandLineApp::resolveAlias(
158 const std::string& name) const {
161 auto pos = aliases_.find(*dest);
162 if (pos == aliases_.end()) {
170 auto NestedCommandLineApp::findCommand(const std::string& name) const
171 -> const std::pair<const std::string, CommandInfo>& {
172 auto pos = commands_.find(resolveAlias(name));
173 if (pos == commands_.end()) {
176 folly::sformat("Command `{}' not found. Run `{} help' for help.",
177 name, programName_));
182 int NestedCommandLineApp::run(int argc, const char* const argv[]) {
183 if (programName_.empty()) {
184 programName_ = fs::path(argv[0]).filename().string();
186 return run(std::vector<std::string>(argv + 1, argv + argc));
189 int NestedCommandLineApp::run(const std::vector<std::string>& args) {
194 } catch (const ProgramExit& ex) {
195 if (ex.what()[0]) { // if not empty
196 fprintf(stderr, "%s\n", ex.what());
198 status = ex.status();
199 } catch (const po::error& ex) {
200 fprintf(stderr, "%s. Run `%s help' for help.\n",
201 ex.what(), programName_.c_str());
206 if (ferror(stdout)) {
207 fprintf(stderr, "error on standard output\n");
209 } else if (fflush(stdout)) {
210 fprintf(stderr, "standard output flush failed: %s\n",
211 errnoStr(errno).c_str());
219 void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
220 if (programName_.empty()) {
221 programName_ = guessProgramName();
223 auto parsed = parseNestedCommandLine(args, globalOptions_);
224 po::variables_map vm;
225 po::store(parsed.options, vm);
226 if (vm.count("help")) {
227 std::vector<std::string> helpArgs;
228 if (parsed.command) {
229 helpArgs.push_back(*parsed.command);
231 displayHelp(vm, helpArgs);
235 if (vm.count("version")) {
236 printf("%s %s\n", programName_.c_str(), version_.c_str());
240 if (!parsed.command) {
243 folly::sformat("Command not specified. Run `{} help' for help.",
247 auto& p = findCommand(*parsed.command);
249 auto& info = p.second;
252 po::command_line_parser(parsed.rest).options(info.options).run();
253 po::store(cmdOptions, vm);
256 auto cmdArgs = po::collect_unrecognized(cmdOptions.options,
257 po::include_positional);
260 initFunction_(cmd, vm, cmdArgs);
263 info.command(vm, cmdArgs);