2 * Copyright 2017 Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include <folly/experimental/ProgramOptions.h>
19 #include <unordered_map>
20 #include <unordered_set>
22 #include <boost/version.hpp>
23 #include <glog/logging.h>
25 #include <folly/Conv.h>
26 #include <folly/Portability.h>
27 #include <folly/portability/GFlags.h>
29 namespace po = ::boost::program_options;
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.
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.
46 explicit GFlagInfo(gflags::CommandLineFlagInfo info)
47 : info_(std::move(info)),
50 void set(const T& value) {
55 auto strValue = folly::to<std::string>(value);
56 auto msg = gflags::SetCommandLineOption(
60 throw po::invalid_option_value(strValue);
67 CHECK(gflags::GetCommandLineOption(info_.name.c_str(), &str));
68 return folly::to<T>(str);
71 const gflags::CommandLineFlagInfo& info() const { return info_; }
74 gflags::CommandLineFlagInfo info_;
79 class GFlagValueSemanticBase : public po::value_semantic {
81 explicit GFlagValueSemanticBase(std::shared_ptr<GFlagInfo<T>> info)
82 : info_(std::move(info)) { }
84 std::string name() const override { return "arg"; }
85 #if BOOST_VERSION >= 105900
86 bool adjacent_tokens_only() const {
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,
99 auto val = info_->get();
100 this->transform(val);
105 void parse(boost::any& valueStore,
106 const std::vector<std::string>& tokens,
107 bool /* utf8 */) const override;
110 virtual T parseValue(const std::vector<std::string>& tokens) const = 0;
111 virtual void transform(T& /* val */) const {}
113 mutable std::shared_ptr<GFlagInfo<T>> info_;
117 void GFlagValueSemanticBase<T>::parse(boost::any& valueStore,
118 const std::vector<std::string>& tokens,
119 bool /* utf8 */) const {
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());
128 this->info_->set(val);
133 class GFlagValueSemantic : public GFlagValueSemanticBase<T> {
135 explicit GFlagValueSemantic(std::shared_ptr<GFlagInfo<T>> info)
136 : GFlagValueSemanticBase<T>(std::move(info)) { }
138 unsigned min_tokens() const override { return 1; }
139 unsigned max_tokens() const override { return 1; }
141 T parseValue(const std::vector<std::string>& tokens) const override {
142 DCHECK(tokens.size() == 1);
143 return folly::to<T>(tokens.front());
147 class BoolGFlagValueSemantic : public GFlagValueSemanticBase<bool> {
149 explicit BoolGFlagValueSemantic(std::shared_ptr<GFlagInfo<bool>> info)
150 : GFlagValueSemanticBase<bool>(std::move(info)) { }
152 unsigned min_tokens() const override { return 0; }
153 unsigned max_tokens() const override { return 0; }
155 bool parseValue(const std::vector<std::string>& tokens) const override {
156 DCHECK(tokens.empty());
161 class NegativeBoolGFlagValueSemantic : public BoolGFlagValueSemantic {
163 explicit NegativeBoolGFlagValueSemantic(std::shared_ptr<GFlagInfo<bool>> info)
164 : BoolGFlagValueSemantic(std::move(info)) { }
167 void transform(bool& val) const override {
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
177 auto pos = gFlagOverrides.find(name);
178 return pos != gFlagOverrides.end() ? pos->second : name;
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);
190 case ProgramOptionsStyle::GFLAGS:
192 case ProgramOptionsStyle::GNU:
193 std::replace(name.begin(), name.end(), '_', '-');
198 new GFlagValueSemantic<T>(gflagInfo),
199 info.description.c_str());
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;
212 case ProgramOptionsStyle::GFLAGS:
213 negationPrefix = "no";
215 case ProgramOptionsStyle::GNU:
216 std::replace(name.begin(), name.end(), '_', '-');
217 negationPrefix = "no-";
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());
230 typedef void(*FlagAdder)(gflags::CommandLineFlagInfo&&,
231 po::options_description&,
232 ProgramOptionsStyle);
234 const std::unordered_map<std::string, FlagAdder> gFlagAdders = {
235 #define X(NAME, TYPE) \
236 {NAME, addGFlag<TYPE>},
240 X("uint32", uint32_t)
241 X("uint64", uint64_t)
243 X("string", std::string)
249 po::options_description getGFlags(ProgramOptionsStyle style) {
250 static const std::unordered_set<std::string> gSkipFlags{
263 "tab_completion_columns",
264 "tab_completion_word",
267 po::options_description desc("GFlags");
269 std::vector<gflags::CommandLineFlagInfo> allFlags;
270 gflags::GetAllFlags(&allFlags);
272 for (auto& f : allFlags) {
273 if (gSkipFlags.count(f.name)) {
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);
286 NestedCommandLineParseResult doParseNestedCommandLine(
287 po::command_line_parser&& parser,
288 const po::options_description& desc) {
289 NestedCommandLineParseResult result;
291 result.options = parser.options(desc).allow_unregistered().run();
293 bool setCommand = true;
294 for (auto& opt : result.options.options) {
295 auto& tokens = opt.original_tokens;
296 auto tokensStart = tokens.begin();
298 if (setCommand && opt.position_key != -1) {
299 DCHECK(tokensStart != tokens.end());
300 result.command = *(tokensStart++);
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.
308 // program --wtf foo bar
310 // Is "foo" an argument to "--wtf", or the command name?
312 result.rest.insert(result.rest.end(), tokensStart, tokens.end());
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);
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);