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