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