proxygen
NestedCommandLineAppExample.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 
17 // Example application using the nested command line parser.
18 //
19 // Implements two commands: "cat" and "echo", which behave similarly to their
20 // Unix homonyms.
21 
22 #include <folly/ScopeGuard.h>
23 #include <folly/String.h>
26 
27 namespace po = ::boost::program_options;
28 
29 namespace {
30 
31 class InputError : public std::runtime_error {
32  public:
33  explicit InputError(const std::string& msg) : std::runtime_error(msg) {}
34 };
35 
36 class OutputError : public std::runtime_error {
37  public:
38  explicit OutputError(const std::string& msg) : std::runtime_error(msg) {}
39 };
40 
41 class Concatenator {
42  public:
43  explicit Concatenator(const po::variables_map& options)
44  : printLineNumbers_(options["number"].as<bool>()) {}
45 
46  void cat(const std::string& name);
47  void cat(FILE* file);
48 
49  bool printLineNumbers() const {
50  return printLineNumbers_;
51  }
52 
53  private:
54  bool printLineNumbers_;
55  size_t lineNumber_ = 0;
56 };
57 
58 // clang-format off
59 [[noreturn]] void throwOutputError() {
60  throw OutputError(folly::errnoStr(errno).toStdString());
61 }
62 
63 [[noreturn]] void throwInputError() {
64  throw InputError(folly::errnoStr(errno).toStdString());
65 }
66 // clang-format on
67 
68 void Concatenator::cat(FILE* file) {
69  char* lineBuf = nullptr;
70  size_t lineBufSize = 0;
71  SCOPE_EXIT {
72  free(lineBuf);
73  };
74 
75  ssize_t n;
76  while ((n = getline(&lineBuf, &lineBufSize, file)) >= 0) {
77  ++lineNumber_;
78  if ((printLineNumbers_ && printf("%6zu ", lineNumber_) < 0) ||
79  fwrite(lineBuf, 1, n, stdout) < size_t(n)) {
80  throwOutputError();
81  }
82  }
83 
84  if (ferror(file)) {
85  throwInputError();
86  }
87 }
88 
89 void Concatenator::cat(const std::string& name) {
90  auto file = fopen(name.c_str(), "r");
91  if (!file) {
92  throwInputError();
93  }
94 
95  // Ignore error, as we might be processing an exception;
96  // during normal operation, we call fclose() directly further below
97  auto guard = folly::makeGuard([file] { fclose(file); });
98 
99  cat(file);
100 
101  guard.dismiss();
102  if (fclose(file)) {
103  throwInputError();
104  }
105 }
106 
107 void runCat(
108  const po::variables_map& options,
109  const std::vector<std::string>& args) {
110  Concatenator concatenator(options);
111  bool ok = true;
112  auto catFile = [&concatenator, &ok](const std::string& name) {
113  try {
114  if (name == "-") {
115  concatenator.cat(stdin);
116  } else {
117  concatenator.cat(name);
118  }
119  } catch (const InputError& e) {
120  ok = false;
121  fprintf(stderr, "cat: %s: %s\n", name.c_str(), e.what());
122  }
123  };
124 
125  try {
126  if (args.empty()) {
127  catFile("-");
128  } else {
129  for (auto& name : args) {
130  catFile(name);
131  }
132  }
133  } catch (const OutputError& e) {
134  throw folly::ProgramExit(
135  1, folly::to<std::string>("cat: write error: ", e.what()));
136  }
137  if (!ok) {
138  throw folly::ProgramExit(1);
139  }
140 }
141 
142 void runEcho(
143  const po::variables_map& options,
144  const std::vector<std::string>& args) {
145  try {
146  const char* sep = "";
147  for (auto& arg : args) {
148  if (printf("%s%s", sep, arg.c_str()) < 0) {
149  throw OutputError(folly::errnoStr(errno).toStdString());
150  }
151  sep = " ";
152  }
153  if (!options["-n"].as<bool>()) {
154  if (putchar('\n') == EOF) {
155  throw OutputError(folly::errnoStr(errno).toStdString());
156  }
157  }
158  } catch (const OutputError& e) {
159  throw folly::ProgramExit(
160  1, folly::to<std::string>("echo: write error: ", e.what()));
161  }
162 }
163 
164 } // namespace
165 
166 int main(int argc, char* argv[]) {
167  // Initialize a NestedCommandLineApp object.
168  //
169  // The first argument is the program name -- an empty string will cause the
170  // program name to be deduced from the executable name, which is usually
171  // fine. The second argument is a version string.
172  //
173  // You may also add an "initialization function" that is always called
174  // for every valid command before the command is executed.
175  folly::NestedCommandLineApp app("", "0.1");
176 
177  // Add any GFlags-defined flags. These are global flags, and so they should
178  // be valid for any command.
179  app.addGFlags();
180 
181  // Add any commands. For our example, we'll implement simplified versions
182  // of "cat" and "echo". Note that addCommand() returns a reference to a
183  // boost::program_options object that you may use to add command-specific
184  // options.
185  // clang-format off
186  app.addCommand(
187  // command name
188  "cat",
189 
190  // argument description
191  "[file...]",
192 
193  // short help string
194  "Concatenate files and print them on standard output",
195 
196  // Long help string
197  "Concatenate files and print them on standard output.",
198 
199  // Function to execute
200  runCat)
201  .add_options()
202  ("number,n", po::bool_switch(), "number all output lines");
203  // clang-format on
204 
205  // clang-format off
206  app.addCommand(
207  "echo",
208  "[string...]",
209  "Display a line of text",
210  "Display a line of text.",
211  runEcho)
212  .add_options()
213  (",n", po::bool_switch(), "do not output the trailing newline");
214  // clang-format on
215 
216  // You may also add command aliases -- that is, multiple command names
217  // that do the same thing; see addAlias().
218 
219  // app.run returns an appropriate error code
220  return app.run(argc, argv);
221 }
auto cat
int main(int argc, char *argv[])
program_options
Definition: CMakeCache.txt:862
STL namespace.
void BENCHFUN() getline(size_t iters, size_t arg)
#define SCOPE_EXIT
Definition: ScopeGuard.h:274
std::string toStdString(const folly::fbstring &s)
Definition: String.h:41
boost::program_options::options_description & addCommand(std::string name, std::string argStr, std::string shortHelp, std::string fullHelp, Command command)
const char * name
Definition: http_parser.c:437
char ** argv
GuardImpl guard(ErrorHandler &&handler)
Definition: Base.h:840
void free()
fbstring errnoStr(int err)
Definition: String.cpp:463
FOLLY_NODISCARD detail::ScopeGuardImplDecay< F, true > makeGuard(F &&f) noexcept(noexcept(detail::ScopeGuardImplDecay< F, true >(static_cast< F && >(f))))
Definition: ScopeGuard.h:184
void addGFlags(ProgramOptionsStyle style=ProgramOptionsStyle::GNU)
const char * string
Definition: Conv.cpp:212
int run(int argc, const char *const argv[])
Collect as()
Definition: Base.h:811
GMockOutputTest ExpectedCall FILE