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 | #include <folly/experimental/ProgramOptions.h> |
18 | |
19 | #include <unordered_map> |
20 | #include <unordered_set> |
21 | |
22 | #include <boost/version.hpp> |
23 | #include <glog/logging.h> |
24 | |
25 | #include <folly/Conv.h> |
26 | #include <folly/Portability.h> |
27 | #include <folly/portability/GFlags.h> |
28 | |
29 | namespace po = ::boost::program_options; |
30 | |
31 | namespace folly { |
32 | |
33 | namespace { |
34 | |
35 | // Information about one GFlag. Handled via shared_ptr, as, in the case |
36 | // of boolean flags, two boost::program_options options (--foo and --nofoo) |
37 | // may share the same GFlag underneath. |
38 | // |
39 | // We're slightly abusing the boost::program_options interface; the first |
40 | // time we (successfully) parse a value that matches this GFlag, we'll set |
41 | // it and remember not to set it again; this prevents, for example, the |
42 | // default value of --foo from overwriting the GFlag if --nofoo is set. |
43 | template <class T> |
44 | class GFlagInfo { |
45 | public: |
46 | explicit GFlagInfo(gflags::CommandLineFlagInfo info) |
47 | : info_(std::move(info)), isSet_(false) {} |
48 | |
49 | void set(const T& value) { |
50 | if (isSet_) { |
51 | return; |
52 | } |
53 | |
54 | auto strValue = folly::to<std::string>(value); |
55 | auto msg = |
56 | gflags::SetCommandLineOption(info_.name.c_str(), strValue.c_str()); |
57 | if (msg.empty()) { |
58 | throw po::invalid_option_value(strValue); |
59 | } |
60 | isSet_ = true; |
61 | } |
62 | |
63 | T get() const { |
64 | std::string str; |
65 | CHECK(gflags::GetCommandLineOption(info_.name.c_str(), &str)); |
66 | return folly::to<T>(str); |
67 | } |
68 | |
69 | const gflags::CommandLineFlagInfo& info() const { |
70 | return info_; |
71 | } |
72 | |
73 | private: |
74 | gflags::CommandLineFlagInfo info_; |
75 | bool isSet_; |
76 | }; |
77 | |
78 | template <class T> |
79 | class GFlagValueSemanticBase : public po::value_semantic { |
80 | public: |
81 | explicit GFlagValueSemanticBase(std::shared_ptr<GFlagInfo<T>> info) |
82 | : info_(std::move(info)) {} |
83 | |
84 | std::string name() const override { |
85 | return "arg" ; |
86 | } |
87 | #if BOOST_VERSION >= 105900 && BOOST_VERSION <= 106400 |
88 | bool adjacent_tokens_only() const override { |
89 | return false; |
90 | } |
91 | #endif |
92 | bool is_composing() const override { |
93 | return false; |
94 | } |
95 | bool is_required() const override { |
96 | return false; |
97 | } |
98 | // We handle setting the GFlags from parse(), so notify() does nothing. |
99 | void notify(const boost::any& /* valueStore */) const override {} |
100 | bool apply_default(boost::any& valueStore) const override { |
101 | // We're using the *current* rather than *default* value here, and |
102 | // this is intentional; GFlags-using programs assign to FLAGS_foo |
103 | // before ParseCommandLineFlags() in order to change the default value, |
104 | // and we obey that. |
105 | auto val = info_->get(); |
106 | this->transform(val); |
107 | valueStore = val; |
108 | return true; |
109 | } |
110 | |
111 | void parse( |
112 | boost::any& valueStore, |
113 | const std::vector<std::string>& tokens, |
114 | bool /* utf8 */) const override; |
115 | |
116 | private: |
117 | virtual T parseValue(const std::vector<std::string>& tokens) const = 0; |
118 | virtual void transform(T& /* val */) const {} |
119 | |
120 | mutable std::shared_ptr<GFlagInfo<T>> info_; |
121 | }; |
122 | |
123 | template <class T> |
124 | void GFlagValueSemanticBase<T>::parse( |
125 | boost::any& valueStore, |
126 | const std::vector<std::string>& tokens, |
127 | bool /* utf8 */) const { |
128 | T val; |
129 | try { |
130 | val = this->parseValue(tokens); |
131 | this->transform(val); |
132 | } catch (const std::exception&) { |
133 | throw po::invalid_option_value( |
134 | tokens.empty() ? std::string() : tokens.front()); |
135 | } |
136 | this->info_->set(val); |
137 | valueStore = val; |
138 | } |
139 | |
140 | template <class T> |
141 | class GFlagValueSemantic : public GFlagValueSemanticBase<T> { |
142 | public: |
143 | explicit GFlagValueSemantic(std::shared_ptr<GFlagInfo<T>> info) |
144 | : GFlagValueSemanticBase<T>(std::move(info)) {} |
145 | |
146 | unsigned min_tokens() const override { |
147 | return 1; |
148 | } |
149 | unsigned max_tokens() const override { |
150 | return 1; |
151 | } |
152 | |
153 | T parseValue(const std::vector<std::string>& tokens) const override { |
154 | DCHECK(tokens.size() == 1); |
155 | return folly::to<T>(tokens.front()); |
156 | } |
157 | }; |
158 | |
159 | class BoolGFlagValueSemantic : public GFlagValueSemanticBase<bool> { |
160 | public: |
161 | explicit BoolGFlagValueSemantic(std::shared_ptr<GFlagInfo<bool>> info) |
162 | : GFlagValueSemanticBase<bool>(std::move(info)) {} |
163 | |
164 | unsigned min_tokens() const override { |
165 | return 0; |
166 | } |
167 | unsigned max_tokens() const override { |
168 | return 0; |
169 | } |
170 | |
171 | bool parseValue(const std::vector<std::string>& tokens) const override { |
172 | DCHECK(tokens.empty()); |
173 | return true; |
174 | } |
175 | }; |
176 | |
177 | class NegativeBoolGFlagValueSemantic : public BoolGFlagValueSemantic { |
178 | public: |
179 | explicit NegativeBoolGFlagValueSemantic(std::shared_ptr<GFlagInfo<bool>> info) |
180 | : BoolGFlagValueSemantic(std::move(info)) {} |
181 | |
182 | private: |
183 | void transform(bool& val) const override { |
184 | val = !val; |
185 | } |
186 | }; |
187 | |
188 | const std::string& getName(const std::string& name) { |
189 | static const std::unordered_map<std::string, std::string> gFlagOverrides{ |
190 | // Allow -v in addition to --v |
191 | {"v" , "v,v" }, |
192 | }; |
193 | auto pos = gFlagOverrides.find(name); |
194 | return pos != gFlagOverrides.end() ? pos->second : name; |
195 | } |
196 | |
197 | template <class T> |
198 | void addGFlag( |
199 | gflags::CommandLineFlagInfo&& flag, |
200 | po::options_description& desc, |
201 | ProgramOptionsStyle style) { |
202 | auto gflagInfo = std::make_shared<GFlagInfo<T>>(std::move(flag)); |
203 | auto& info = gflagInfo->info(); |
204 | auto name = getName(info.name); |
205 | |
206 | switch (style) { |
207 | case ProgramOptionsStyle::GFLAGS: |
208 | break; |
209 | case ProgramOptionsStyle::GNU: |
210 | std::replace(name.begin(), name.end(), '_', '-'); |
211 | break; |
212 | } |
213 | desc.add_options()( |
214 | name.c_str(), |
215 | new GFlagValueSemantic<T>(gflagInfo), |
216 | info.description.c_str()); |
217 | } |
218 | |
219 | template <> |
220 | void addGFlag<bool>( |
221 | gflags::CommandLineFlagInfo&& flag, |
222 | po::options_description& desc, |
223 | ProgramOptionsStyle style) { |
224 | auto gflagInfo = std::make_shared<GFlagInfo<bool>>(std::move(flag)); |
225 | auto& info = gflagInfo->info(); |
226 | auto name = getName(info.name); |
227 | std::string negationPrefix; |
228 | |
229 | switch (style) { |
230 | case ProgramOptionsStyle::GFLAGS: |
231 | negationPrefix = "no" ; |
232 | break; |
233 | case ProgramOptionsStyle::GNU: |
234 | std::replace(name.begin(), name.end(), '_', '-'); |
235 | negationPrefix = "no-" ; |
236 | break; |
237 | } |
238 | |
239 | // clang-format off |
240 | desc.add_options() |
241 | (name.c_str(), |
242 | new BoolGFlagValueSemantic(gflagInfo), |
243 | info.description.c_str()) |
244 | ((negationPrefix + name).c_str(), |
245 | new NegativeBoolGFlagValueSemantic(gflagInfo), |
246 | folly::to<std::string>("(no) " , info.description).c_str()); |
247 | // clang-format on |
248 | } |
249 | |
250 | typedef void (*FlagAdder)( |
251 | gflags::CommandLineFlagInfo&&, |
252 | po::options_description&, |
253 | ProgramOptionsStyle); |
254 | |
255 | const std::unordered_map<std::string, FlagAdder> gFlagAdders = { |
256 | #define X(NAME, TYPE) \ |
257 | { NAME, addGFlag<TYPE> } |
258 | X("bool" , bool), |
259 | X("int32" , int32_t), |
260 | X("int64" , int64_t), |
261 | X("uint32" , uint32_t), |
262 | X("uint64" , uint64_t), |
263 | X("double" , double), |
264 | X("string" , std::string), |
265 | #undef X |
266 | }; |
267 | |
268 | } // namespace |
269 | |
270 | po::options_description getGFlags(ProgramOptionsStyle style) { |
271 | static const std::unordered_set<std::string> gSkipFlags{ |
272 | "flagfile" , |
273 | "fromenv" , |
274 | "tryfromenv" , |
275 | "undefok" , |
276 | "help" , |
277 | "helpfull" , |
278 | "helpshort" , |
279 | "helpon" , |
280 | "helpmatch" , |
281 | "helppackage" , |
282 | "helpxml" , |
283 | "version" , |
284 | "tab_completion_columns" , |
285 | "tab_completion_word" , |
286 | }; |
287 | |
288 | po::options_description desc("GFlags" ); |
289 | |
290 | std::vector<gflags::CommandLineFlagInfo> allFlags; |
291 | gflags::GetAllFlags(&allFlags); |
292 | |
293 | for (auto& f : allFlags) { |
294 | if (gSkipFlags.count(f.name)) { |
295 | continue; |
296 | } |
297 | auto pos = gFlagAdders.find(f.type); |
298 | CHECK(pos != gFlagAdders.end()) << "Invalid flag type: " << f.type; |
299 | (*pos->second)(std::move(f), desc, style); |
300 | } |
301 | |
302 | return desc; |
303 | } |
304 | |
305 | namespace { |
306 | |
307 | NestedCommandLineParseResult doParseNestedCommandLine( |
308 | po::command_line_parser&& parser, |
309 | const po::options_description& desc) { |
310 | NestedCommandLineParseResult result; |
311 | |
312 | result.options = parser.options(desc).allow_unregistered().run(); |
313 | |
314 | bool setCommand = true; |
315 | for (auto& opt : result.options.options) { |
316 | auto& tokens = opt.original_tokens; |
317 | auto tokensStart = tokens.begin(); |
318 | |
319 | if (setCommand && opt.position_key != -1) { |
320 | DCHECK(tokensStart != tokens.end()); |
321 | result.command = *(tokensStart++); |
322 | } |
323 | |
324 | if (opt.position_key != -1 || opt.unregistered) { |
325 | // If we see an unrecognized option before the first positional |
326 | // argument, assume we don't have a valid command name, because |
327 | // we don't know how to parse it otherwise. |
328 | // |
329 | // program --wtf foo bar |
330 | // |
331 | // Is "foo" an argument to "--wtf", or the command name? |
332 | setCommand = false; |
333 | result.rest.insert(result.rest.end(), tokensStart, tokens.end()); |
334 | } |
335 | } |
336 | |
337 | return result; |
338 | } |
339 | |
340 | } // namespace |
341 | |
342 | NestedCommandLineParseResult parseNestedCommandLine( |
343 | int argc, |
344 | const char* const argv[], |
345 | const po::options_description& desc) { |
346 | return doParseNestedCommandLine(po::command_line_parser(argc, argv), desc); |
347 | } |
348 | |
349 | NestedCommandLineParseResult parseNestedCommandLine( |
350 | const std::vector<std::string>& cmdline, |
351 | const po::options_description& desc) { |
352 | return doParseNestedCommandLine(po::command_line_parser(cmdline), desc); |
353 | } |
354 | |
355 | } // namespace folly |
356 | |