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