logging: fix compiler compatibility for one more constexpr function
[folly.git] / folly / experimental / NestedCommandLineApp.cpp
1 /*
2  * Copyright 2017 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
17 #include <folly/experimental/NestedCommandLineApp.h>
18
19 #include <iostream>
20 #include <folly/FileUtil.h>
21 #include <folly/Format.h>
22 #include <folly/experimental/io/FsUtil.h>
23
24 namespace po = ::boost::program_options;
25
26 namespace folly {
27
28 namespace {
29
30 // Guess the program name as basename(executable)
31 std::string guessProgramName() {
32   try {
33     return fs::executable_path().filename().string();
34   } catch (const std::exception&) {
35     return "UNKNOWN";
36   }
37 }
38
39 }  // namespace
40
41 ProgramExit::ProgramExit(int status, const std::string& msg)
42   : std::runtime_error(msg),
43     status_(status) {
44   // Message is only allowed for non-zero exit status
45   CHECK(status_ != 0 || msg.empty());
46 }
47
48 NestedCommandLineApp::NestedCommandLineApp(
49     std::string programName,
50     std::string version,
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);
66              });
67
68   globalOptions_.add_options()
69     ("help,h", "Display help (globally or for a given command)")
70     ("version", "Display version information");
71 }
72
73 po::options_description& NestedCommandLineApp::addCommand(
74     std::string name,
75     std::string argStr,
76     std::string shortHelp,
77     std::string fullHelp,
78     Command command) {
79   CommandInfo info {
80     std::move(argStr),
81     std::move(shortHelp),
82     std::move(fullHelp),
83     std::move(command),
84     po::options_description(folly::sformat("Options for `{}'", name))
85   };
86
87   auto p = commands_.emplace(std::move(name), std::move(info));
88   CHECK(p.second) << "Command already exists";
89
90   return p.first->second.options;
91 }
92
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));
100 }
101
102 void NestedCommandLineApp::displayHelp(
103     const po::variables_map& /* globalOptions */,
104     const std::vector<std::string>& args) {
105   if (args.empty()) {
106     // General help
107     printf(
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");
114
115     size_t maxLen = 0;
116     for (auto& p : commands_) {
117       maxLen = std::max(maxLen, p.first.size());
118     }
119     for (auto& p : aliases_) {
120       maxLen = std::max(maxLen, p.first.size());
121     }
122
123     for (auto& p : commands_) {
124       printf("  %-*s    %s\n",
125              int(maxLen), p.first.c_str(), p.second.shortHelp.c_str());
126     }
127
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());
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("`%s' is an alias for `%s'; showing help for `%s'\n",
141              args.front().c_str(), p.first.c_str(), p.first.c_str());
142     }
143     auto& info = p.second;
144
145     printf(
146         "Usage: %s [global_options...] %s%s%s%s\n\n",
147         programName_.c_str(),
148         p.first.c_str(),
149         info.options.options().empty() ? "" : " [command_options...]",
150         info.argStr.empty() ? "" : " ",
151         info.argStr.c_str());
152
153     printf("%s\n", info.fullHelp.c_str());
154
155     std::cout << globalOptions_;
156
157     if (!info.options.options().empty()) {
158       printf("\n");
159       std::cout << info.options;
160     }
161   }
162 }
163
164 const std::string& NestedCommandLineApp::resolveAlias(
165     const std::string& name) const {
166   auto dest = &name;
167   for (;;) {
168     auto pos = aliases_.find(*dest);
169     if (pos == aliases_.end()) {
170       break;
171     }
172     dest = &pos->second;
173   }
174   return *dest;
175 }
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("Command `{}' not found. Run `{} help' for help.",
184                        name, programName_));
185   }
186   return *pos;
187 }
188
189 int NestedCommandLineApp::run(int argc, const char* const argv[]) {
190   if (programName_.empty()) {
191     programName_ = fs::path(argv[0]).filename().string();
192   }
193   return run(std::vector<std::string>(argv + 1, argv + argc));
194 }
195
196 int NestedCommandLineApp::run(const std::vector<std::string>& args) {
197   int status;
198   try {
199     doRun(args);
200     status = 0;
201   } catch (const ProgramExit& ex) {
202     if (ex.what()[0]) {  // if not empty
203       fprintf(stderr, "%s\n", ex.what());
204     }
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());
209     status = 1;
210   }
211
212   if (status == 0) {
213     if (ferror(stdout)) {
214       fprintf(stderr, "error on standard output\n");
215       status = 1;
216     } else if (fflush(stdout)) {
217       fprintf(stderr, "standard output flush failed: %s\n",
218               errnoStr(errno).c_str());
219       status = 1;
220     }
221   }
222
223   return status;
224 }
225
226 void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
227   if (programName_.empty()) {
228     programName_ = guessProgramName();
229   }
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);
237     }
238     displayHelp(vm, helpArgs);
239     return;
240   }
241
242   if (vm.count("version")) {
243     printf("%s %s\n", programName_.c_str(), version_.c_str());
244     return;
245   }
246
247   if (!parsed.command) {
248     throw ProgramExit(
249         1,
250         folly::sformat("Command not specified. Run `{} help' for help.",
251                        programName_));
252   }
253
254   auto& p = findCommand(*parsed.command);
255   auto& cmd = p.first;
256   auto& info = p.second;
257
258   auto cmdOptions =
259     po::command_line_parser(parsed.rest).options(info.options).run();
260   po::store(cmdOptions, vm);
261   po::notify(vm);
262
263   auto cmdArgs = po::collect_unrecognized(cmdOptions.options,
264                                           po::include_positional);
265
266   if (initFunction_) {
267     initFunction_(cmd, vm, cmdArgs);
268   }
269
270   info.command(vm, cmdArgs);
271 }
272
273 }  // namespaces