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
29namespace po = ::boost::program_options;
30
31namespace folly {
32
33namespace {
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.
43template <class T>
44class 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
78template <class T>
79class 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
123template <class T>
124void 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
140template <class T>
141class 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
159class 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
177class 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
188const 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
197template <class T>
198void 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
219template <>
220void 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
250typedef void (*FlagAdder)(
251 gflags::CommandLineFlagInfo&&,
252 po::options_description&,
253 ProgramOptionsStyle);
254
255const 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
270po::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
305namespace {
306
307NestedCommandLineParseResult 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
342NestedCommandLineParseResult 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
349NestedCommandLineParseResult 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