ProgramOptions.cpp 9.4 KB

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