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#pragma once
17
18#include <functional>
19#include <set>
20#include <stdexcept>
21
22#include <folly/CPortability.h>
23#include <folly/String.h>
24#include <folly/experimental/ProgramOptions.h>
25
26namespace folly {
27
28/**
29 * Exception that commands may throw to force the program to exit cleanly
30 * with a given exit code. NestedCommandLineApp::run() catches this and
31 * makes run() print the given message on stderr (followed by a newline, unless
32 * empty; the message is only allowed when exiting with a non-zero status), and
33 * return the exit code. (Other exceptions will propagate out of run())
34 */
35class FOLLY_EXPORT ProgramExit : public std::runtime_error {
36 public:
37 explicit ProgramExit(int status, const std::string& msg = std::string());
38 int status() const {
39 return status_;
40 }
41
42 private:
43 int status_;
44};
45
46/**
47 * App that uses a nested command line, of the form:
48 *
49 * program [--global_options...] command [--command_options...] command_args...
50 */
51class NestedCommandLineApp {
52 public:
53 typedef std::function<void(
54 const std::string& command,
55 const boost::program_options::variables_map& options,
56 const std::vector<std::string>& args)>
57 InitFunction;
58
59 typedef std::function<void(
60 const boost::program_options::variables_map& options,
61 const std::vector<std::string>&)>
62 Command;
63
64 static constexpr StringPiece const kHelpCommand = "help";
65 static constexpr StringPiece const kVersionCommand = "version";
66 /**
67 * Initialize the app.
68 *
69 * If programName is not set, we try to guess (readlink("/proc/self/exe")).
70 *
71 * version is the version string printed when given the --version flag.
72 *
73 * initFunction, if specified, is called after parsing the command line,
74 * right before executing the command.
75 */
76 explicit NestedCommandLineApp(
77 std::string programName = std::string(),
78 std::string version = std::string(),
79 std::string programHeading = std::string(),
80 std::string programHelpFooter = std::string(),
81 InitFunction initFunction = InitFunction());
82
83 /**
84 * Add GFlags to the list of supported options with the given style.
85 */
86 void addGFlags(ProgramOptionsStyle style = ProgramOptionsStyle::GNU) {
87 globalOptions_.add(getGFlags(style));
88 }
89
90 /**
91 * Return the global options object, so you can add options.
92 */
93 boost::program_options::options_description& globalOptions() {
94 return globalOptions_;
95 }
96
97 /**
98 * Add a command.
99 *
100 * name: command name
101 * argStr: description of arguments in help strings
102 * (<filename> <N>)
103 * shortHelp: one-line summary help string
104 * fullHelp: full help string
105 * command: function to run
106 *
107 * Returns a reference to the options_description object that you can
108 * use to add options for this command.
109 */
110 boost::program_options::options_description& addCommand(
111 std::string name,
112 std::string argStr,
113 std::string shortHelp,
114 std::string fullHelp,
115 Command command);
116
117 /**
118 * Add an alias; running the command newName will have the same effect
119 * as running oldName.
120 */
121 void addAlias(std::string newName, std::string oldName);
122
123 /**
124 * Run the command and return; the return code is 0 on success or
125 * non-zero on error, so it is idiomatic to call this at the end of main():
126 * return app.run(argc, argv);
127 *
128 * On successful exit, run() will check for errors on stdout (and flush
129 * it) to help command-line applications that need to write to stdout
130 * (failing to write to stdout is an error). If there is an error on stdout,
131 * we'll print a helpful message on stderr and return an error status (1).
132 */
133 int run(int argc, const char* const argv[]);
134 int run(const std::vector<std::string>& args);
135
136 /**
137 * Return true if name represent known built-in command (help, version)
138 */
139 bool isBuiltinCommand(const std::string& name) const;
140
141 private:
142 void doRun(const std::vector<std::string>& args);
143 const std::string& resolveAlias(const std::string& name) const;
144
145 struct CommandInfo {
146 std::string argStr;
147 std::string shortHelp;
148 std::string fullHelp;
149 Command command;
150 boost::program_options::options_description options;
151 };
152
153 const std::pair<const std::string, CommandInfo>& findCommand(
154 const std::string& name) const;
155
156 void displayHelp(
157 const boost::program_options::variables_map& options,
158 const std::vector<std::string>& args) const;
159
160 void displayVersion() const;
161
162 std::string programName_;
163 std::string programHeading_;
164 std::string programHelpFooter_;
165 std::string version_;
166 InitFunction initFunction_;
167 boost::program_options::options_description globalOptions_;
168 std::map<std::string, CommandInfo> commands_;
169 std::map<std::string, std::string> aliases_;
170 std::set<folly::StringPiece> builtinCommands_;
171};
172
173} // namespace folly
174