2 * Copyright 2015 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().native();
34 } catch (const std::exception&) {
41 ProgramExit::ProgramExit(int status, const std::string& msg)
42 : std::runtime_error(msg),
47 NestedCommandLineApp::NestedCommandLineApp(
48 std::string programName,
50 InitFunction initFunction)
51 : programName_(std::move(programName)),
52 version_(std::move(version)),
53 initFunction_(std::move(initFunction)),
54 globalOptions_("Global options") {
55 addCommand("help", "[command]",
56 "Display help (globally or for a given command)",
57 "Displays help (globally or for a given command).",
58 [this] (const po::variables_map& vm,
59 const std::vector<std::string>& args) {
60 displayHelp(vm, args);
63 globalOptions_.add_options()
64 ("help,h", "Display help (globally or for a given command)")
65 ("version", "Display version information");
68 po::options_description& NestedCommandLineApp::addCommand(
71 std::string shortHelp,
79 po::options_description(folly::sformat("Options for `{}'", name))
82 auto p = commands_.emplace(std::move(name), std::move(info));
83 CHECK(p.second) << "Command already exists";
85 return p.first->second.options;
88 void NestedCommandLineApp::addAlias(std::string newName,
89 std::string oldName) {
90 CHECK(aliases_.count(oldName) || commands_.count(oldName))
91 << "Alias old name does not exist";
92 CHECK(!aliases_.count(newName) && !commands_.count(newName))
93 << "Alias new name already exists";
94 aliases_.emplace(std::move(newName), std::move(oldName));
97 void NestedCommandLineApp::displayHelp(
98 const po::variables_map& globalOptions,
99 const std::vector<std::string>& args) {
103 "Usage: %s [global_options...] <command> [command_options...] "
104 "[command_args...]\n\n", programName_.c_str());
105 std::cout << globalOptions_;
106 printf("\nAvailable commands:\n");
109 for (auto& p : commands_) {
110 maxLen = std::max(maxLen, p.first.size());
112 for (auto& p : aliases_) {
113 maxLen = std::max(maxLen, p.first.size());
116 for (auto& p : commands_) {
118 int(maxLen), p.first.c_str(), p.second.shortHelp.c_str());
121 if (!aliases_.empty()) {
122 printf("\nAvailable aliases:\n");
123 for (auto& p : aliases_) {
124 printf(" %-*s => %s\n",
125 int(maxLen), p.first.c_str(), resolveAlias(p.second).c_str());
129 // Help for a given command
130 auto& p = findCommand(args.front());
131 if (p.first != args.front()) {
132 printf("`%1$s' is an alias for `%2$s'; showing help for `%2$s'\n",
133 args.front().c_str(), p.first.c_str());
135 auto& info = p.second;
138 "Usage: %s [global_options...] %s%s%s%s\n\n",
139 programName_.c_str(),
141 info.options.options().empty() ? "" : " [command_options...]",
142 info.argStr.empty() ? "" : " ",
143 info.argStr.c_str());
145 std::cout << globalOptions_;
147 if (!info.options.options().empty()) {
149 std::cout << info.options;
152 printf("\n%s\n", info.fullHelp.c_str());
156 const std::string& NestedCommandLineApp::resolveAlias(
157 const std::string& name) const {
160 auto pos = aliases_.find(*dest);
161 if (pos == aliases_.end()) {
169 auto NestedCommandLineApp::findCommand(const std::string& name) const
170 -> const std::pair<const std::string, CommandInfo>& {
171 auto pos = commands_.find(resolveAlias(name));
172 if (pos == commands_.end()) {
175 folly::sformat("Command `{}' not found. Run `{} help' for help.",
176 name, programName_));
181 int NestedCommandLineApp::run(int argc, const char* const argv[]) {
182 if (programName_.empty()) {
183 programName_ = fs::path(argv[0]).filename().native();
185 return run(std::vector<std::string>(argv + 1, argv + argc));
188 int NestedCommandLineApp::run(const std::vector<std::string>& args) {
193 } catch (const ProgramExit& ex) {
194 if (ex.what()[0]) { // if not empty
195 fprintf(stderr, "%s\n", ex.what());
197 status = ex.status();
198 } catch (const po::error& ex) {
199 fprintf(stderr, "%s. Run `%s help' for help.\n",
200 ex.what(), programName_.c_str());
205 if (ferror(stdout)) {
206 fprintf(stderr, "error on standard output\n");
208 } else if (fflush(stdout)) {
209 fprintf(stderr, "standard output flush failed: %s\n",
210 errnoStr(errno).c_str());
218 void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
219 if (programName_.empty()) {
220 programName_ = guessProgramName();
222 auto parsed = parseNestedCommandLine(args, globalOptions_);
223 po::variables_map vm;
224 po::store(parsed.options, vm);
225 if (vm.count("help")) {
226 std::vector<std::string> helpArgs;
227 if (parsed.command) {
228 helpArgs.push_back(*parsed.command);
230 displayHelp(vm, helpArgs);
234 if (vm.count("version")) {
235 printf("%s %s\n", programName_.c_str(), version_.c_str());
239 if (!parsed.command) {
242 folly::sformat("Command not specified. Run `{} help' for help.",
246 auto& p = findCommand(*parsed.command);
248 auto& info = p.second;
251 po::command_line_parser(parsed.rest).options(info.options).run();
252 po::store(cmdOptions, vm);
255 auto cmdArgs = po::collect_unrecognized(cmdOptions.options,
256 po::include_positional);
259 initFunction_(cmd, vm, cmdArgs);
262 info.command(vm, cmdArgs);