NestedCommandLineAppExample.cpp 5.7 KB

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