proxygen
NestedCommandLineApp.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2015-present 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 
18 
19 #include <iostream>
20 
21 #include <folly/FileUtil.h>
22 #include <folly/Format.h>
24 
25 namespace po = ::boost::program_options;
26 
27 namespace folly {
28 
29 namespace {
30 
31 // Guess the program name as basename(executable)
32 std::string guessProgramName() {
33  try {
34  return fs::executable_path().filename().string();
35  } catch (const std::exception&) {
36  return "UNKNOWN";
37  }
38 }
39 
40 } // namespace
41 
42 ProgramExit::ProgramExit(int status, const std::string& msg)
43  : std::runtime_error(msg), status_(status) {
44  // Message is only allowed for non-zero exit status
45  CHECK(status_ != 0 || msg.empty());
46 }
47 
50 
52  std::string programName,
54  std::string programHeading,
55  std::string programHelpFooter,
56  InitFunction initFunction)
57  : programName_(std::move(programName)),
58  programHeading_(std::move(programHeading)),
59  programHelpFooter_(std::move(programHelpFooter)),
60  version_(std::move(version)),
61  initFunction_(std::move(initFunction)),
62  globalOptions_("Global options") {
63  addCommand(
64  kHelpCommand.str(),
65  "[command]",
66  "Display help (globally or for a given command)",
67  "Displays help (globally or for a given command).",
68  [this](
69  const po::variables_map& vm, const std::vector<std::string>& args) {
70  displayHelp(vm, args);
71  });
73 
74  addCommand(
76  "[command]",
77  "Display version information",
78  "Displays version information.",
79  [this](const po::variables_map&, const std::vector<std::string>&) {
81  });
83 
84  globalOptions_.add_options()(
85  kHelpCommand.str().c_str(),
86  "Display help (globally or for a given command)")(
87  kVersionCommand.str().c_str(), "Display version information");
88 }
89 
90 po::options_description& NestedCommandLineApp::addCommand(
92  std::string argStr,
93  std::string shortHelp,
94  std::string fullHelp,
95  Command command) {
97  std::move(argStr),
98  std::move(shortHelp),
99  std::move(fullHelp),
100  std::move(command),
101  po::options_description(folly::sformat("Options for `{}'", name))};
102 
103  auto p = commands_.emplace(std::move(name), std::move(info));
104  CHECK(p.second) << "Command already exists";
105 
106  return p.first->second.options;
107 }
108 
110  CHECK(aliases_.count(oldName) || commands_.count(oldName))
111  << "Alias old name does not exist";
112  CHECK(!aliases_.count(newName) && !commands_.count(newName))
113  << "Alias new name already exists";
114  aliases_.emplace(std::move(newName), std::move(oldName));
115 }
116 
118  const po::variables_map& /* globalOptions */,
119  const std::vector<std::string>& args) const {
120  if (args.empty()) {
121  // General help
122  printf(
123  "%s\nUsage: %s [global_options...] <command> [command_options...] "
124  "[command_args...]\n\n",
125  programHeading_.c_str(),
126  programName_.c_str());
127  std::cout << globalOptions_;
128  printf("\nAvailable commands:\n");
129 
130  size_t maxLen = 0;
131  for (auto& p : commands_) {
132  maxLen = std::max(maxLen, p.first.size());
133  }
134  for (auto& p : aliases_) {
135  maxLen = std::max(maxLen, p.first.size());
136  }
137 
138  for (auto& p : commands_) {
139  printf(
140  " %-*s %s\n",
141  int(maxLen),
142  p.first.c_str(),
143  p.second.shortHelp.c_str());
144  }
145 
146  if (!aliases_.empty()) {
147  printf("\nAvailable aliases:\n");
148  for (auto& p : aliases_) {
149  printf(
150  " %-*s => %s\n",
151  int(maxLen),
152  p.first.c_str(),
153  resolveAlias(p.second).c_str());
154  }
155  }
156  std::cout << "\n" << programHelpFooter_ << "\n";
157  } else {
158  // Help for a given command
159  auto& p = findCommand(args.front());
160  if (p.first != args.front()) {
161  printf(
162  "`%s' is an alias for `%s'; showing help for `%s'\n",
163  args.front().c_str(),
164  p.first.c_str(),
165  p.first.c_str());
166  }
167  auto& info = p.second;
168 
169  printf(
170  "Usage: %s [global_options...] %s%s%s%s\n\n",
171  programName_.c_str(),
172  p.first.c_str(),
173  info.options.options().empty() ? "" : " [command_options...]",
174  info.argStr.empty() ? "" : " ",
175  info.argStr.c_str());
176 
177  printf("%s\n", info.fullHelp.c_str());
178 
179  std::cout << globalOptions_;
180 
181  if (!info.options.options().empty()) {
182  printf("\n");
183  std::cout << info.options;
184  }
185  }
186 }
187 
189  printf("%s %s\n", programName_.c_str(), version_.c_str());
190 }
191 
193  const std::string& name) const {
194  auto dest = &name;
195  for (;;) {
196  auto pos = aliases_.find(*dest);
197  if (pos == aliases_.end()) {
198  break;
199  }
200  dest = &pos->second;
201  }
202  return *dest;
203 }
204 
206  -> const std::pair<const std::string, CommandInfo>& {
207  auto pos = commands_.find(resolveAlias(name));
208  if (pos == commands_.end()) {
209  throw ProgramExit(
210  1,
212  "Command '{}' not found. Run '{} {}' for help.",
213  name,
214  programName_,
215  kHelpCommand));
216  }
217  return *pos;
218 }
219 
220 int NestedCommandLineApp::run(int argc, const char* const argv[]) {
221  if (programName_.empty()) {
222  programName_ = fs::path(argv[0]).filename().string();
223  }
224  return run(std::vector<std::string>(argv + 1, argv + argc));
225 }
226 
227 int NestedCommandLineApp::run(const std::vector<std::string>& args) {
228  int status;
229  try {
230  doRun(args);
231  status = 0;
232  } catch (const ProgramExit& ex) {
233  if (ex.what()[0]) { // if not empty
234  fprintf(stderr, "%s\n", ex.what());
235  }
236  status = ex.status();
237  } catch (const po::error& ex) {
238  fprintf(
239  stderr,
240  "%s",
242  "{}. Run '{} help' for {}.\n",
243  ex.what(),
244  programName_,
245  kHelpCommand)
246  .c_str());
247  status = 1;
248  }
249 
250  if (status == 0) {
251  if (ferror(stdout)) {
252  fprintf(stderr, "error on standard output\n");
253  status = 1;
254  } else if (fflush(stdout)) {
255  fprintf(
256  stderr,
257  "standard output flush failed: %s\n",
258  errnoStr(errno).c_str());
259  status = 1;
260  }
261  }
262 
263  return status;
264 }
265 
266 void NestedCommandLineApp::doRun(const std::vector<std::string>& args) {
267  if (programName_.empty()) {
268  programName_ = guessProgramName();
269  }
270 
271  bool not_clean = false;
272  std::vector<std::string> cleanArgs;
273  std::vector<std::string> endArgs;
274 
275  for (auto& na : args) {
276  if (na == "--") {
277  not_clean = true;
278  } else if (not_clean) {
279  endArgs.push_back(na);
280  } else {
281  cleanArgs.push_back(na);
282  }
283  }
284 
285  auto parsed = parseNestedCommandLine(cleanArgs, globalOptions_);
286  po::variables_map vm;
287  po::store(parsed.options, vm);
288  if (vm.count(kHelpCommand.str())) {
289  std::vector<std::string> helpArgs;
290  if (parsed.command) {
291  helpArgs.push_back(*parsed.command);
292  }
293  displayHelp(vm, helpArgs);
294  return;
295  }
296 
297  if (vm.count(kVersionCommand.str())) {
298  displayVersion();
299  return;
300  }
301 
302  if (!parsed.command) {
303  throw ProgramExit(
304  1,
306  "Command not specified. Run '{} {}' for help.",
307  programName_,
308  kHelpCommand));
309  }
310 
311  auto& p = findCommand(*parsed.command);
312  auto& cmd = p.first;
313  auto& info = p.second;
314 
315  auto cmdOptions =
316  po::command_line_parser(parsed.rest).options(info.options).run();
317 
318  po::store(cmdOptions, vm);
319  po::notify(vm);
320 
321  auto cmdArgs =
322  po::collect_unrecognized(cmdOptions.options, po::include_positional);
323 
324  cmdArgs.insert(cmdArgs.end(), endArgs.begin(), endArgs.end());
325 
326  if (initFunction_) {
327  initFunction_(cmd, vm, cmdArgs);
328  }
329 
330  info.command(vm, cmdArgs);
331 }
332 
334  return builtinCommands_.count(name);
335 }
336 
337 } // namespace folly
std::function< void(const std::string &command, const boost::program_options::variables_map &options, const std::vector< std::string > &args)> InitFunction
def info()
Definition: deadlock.py:447
std::string str() const
Definition: Range.h:591
path executable_path()
Definition: FsUtil.cpp:72
NestedCommandLineApp(std::string programName=std::string(), std::string version=std::string(), std::string programHeading=std::string(), std::string programHelpFooter=std::string(), InitFunction initFunction=InitFunction())
LogLevel max
Definition: LogLevel.cpp:31
std::map< std::string, std::string > aliases_
std::string sformat(StringPiece fmt, Args &&...args)
Definition: Format.h:280
void addAlias(std::string newName, std::string oldName)
void displayHelp(const boost::program_options::variables_map &options, const std::vector< std::string > &args) const
NestedCommandLineParseResult parseNestedCommandLine(int argc, const char *const argv[], const po::options_description &desc)
program_options
Definition: CMakeCache.txt:862
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
dest
Definition: upload.py:394
STL namespace.
const std::pair< const std::string, CommandInfo > & findCommand(const std::string &name) const
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
requires And< SemiMovable< VN >... > &&SemiMovable< E > auto error(E e)
Definition: error.h:48
boost::program_options::options_description & addCommand(std::string name, std::string argStr, std::string shortHelp, std::string fullHelp, Command command)
boost::program_options::options_description globalOptions_
ProtocolVersion version
const char * name
Definition: http_parser.c:437
char ** argv
static constexpr StringPiece const kVersionCommand
bool isBuiltinCommand(const std::string &name) const
std::function< void(const boost::program_options::variables_map &options, const std::vector< std::string > &)> Command
void doRun(const std::vector< std::string > &args)
fbstring errnoStr(int err)
Definition: String.cpp:463
cmd
Definition: gtest-cfgcmd.txt:1
std::map< std::string, CommandInfo > commands_
const std::string & resolveAlias(const std::string &name) const
const char * string
Definition: Conv.cpp:212
int run(int argc, const char *const argv[])
std::set< folly::StringPiece > builtinCommands_
static constexpr StringPiece const kHelpCommand
ProgramExit(int status, const std::string &msg=std::string())