Make gflags and boost::program_options play nice with each other
authorTudor Bosman <tudorb@fb.com>
Thu, 23 Jul 2015 14:30:41 +0000 (07:30 -0700)
committerfacebook-github-bot-1 <folly-bot@fb.com>
Thu, 23 Jul 2015 15:22:14 +0000 (08:22 -0700)
Summary: GFlags is useful for global program options. boost::program_options makes it
easier to write command-line utilities. They don't, unfortunately, play
very nicely with each other.

Add a addGFlags() function to convert all GFlags to boost::program_options
options; you can then use boost::program_options::parse_command_line() to
parse all arguments, GFlags or not.

Also add a helper function to make parsing nested command lines easier.

Reviewed By: @fugalh

Differential Revision: D2215285

folly/Makefile.am
folly/configure.ac
folly/experimental/ProgramOptions.cpp [new file with mode: 0644]
folly/experimental/ProgramOptions.h [new file with mode: 0644]
folly/experimental/test/ProgramOptionsTest.cpp [new file with mode: 0644]
folly/experimental/test/ProgramOptionsTestHelper.cpp [new file with mode: 0644]
folly/m4/ax_boost_program_options.m4 [new file with mode: 0644]

index 1b29351c5f71ddccd1dda6c2689cd663839e1d45..1250f4b715967edd7e9ea12f4ccb4308f7c65bca 100644 (file)
@@ -111,6 +111,7 @@ nobase_follyinclude_HEADERS = \
        experimental/io/FsUtil.h \
        experimental/JSONSchema.h \
        experimental/LockFreeRingBuffer.h \
+       experimental/ProgramOptions.h \
        experimental/Select64.h \
        experimental/StringKeyedCommon.h \
        experimental/StringKeyedUnorderedMap.h \
@@ -371,6 +372,7 @@ libfolly_la_SOURCES = \
        experimental/FunctionScheduler.cpp \
        experimental/io/FsUtil.cpp \
        experimental/JSONSchema.cpp \
+       experimental/ProgramOptions.cpp \
        experimental/Select64.cpp \
        experimental/TestUtil.cpp
 
index b24e575f581a780c130d30a849ae70cf36b877af..97437f63304bce8f08c16ae8b5a303060af5a099 100644 (file)
@@ -107,8 +107,9 @@ AC_CHECK_LIB(ssl,
 
 # check for boost libs
 AX_BOOST_BASE([1.51.0], [], [AC_MSG_ERROR(
-              [Please install boost >= 1.51.0 (context, thread, regex, and system)])])
+              [Please install boost >= 1.51.0 (context, thread, program_options, regex, and system)])])
 AX_BOOST_CONTEXT
+AX_BOOST_PROGRAM_OPTIONS
 AX_BOOST_THREAD
 AX_BOOST_REGEX
 AX_BOOST_SYSTEM
diff --git a/folly/experimental/ProgramOptions.cpp b/folly/experimental/ProgramOptions.cpp
new file mode 100644 (file)
index 0000000..c5d3715
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/experimental/ProgramOptions.h>
+
+#include <unordered_map>
+#include <unordered_set>
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+#include <folly/Conv.h>
+#include <folly/Portability.h>
+
+namespace po = ::boost::program_options;
+
+namespace folly {
+
+namespace {
+
+// Information about one GFlag. Handled via shared_ptr, as, in the case
+// of boolean flags, two boost::program_options options (--foo and --nofoo)
+// may share the same GFlag underneath.
+//
+// We're slightly abusing the boost::program_options interface; the first
+// time we (successfully) parse a value that matches this GFlag, we'll set
+// it and remember not to set it again; this prevents, for example, the
+// default value of --foo from overwriting the GFlag if --nofoo is set.
+template <class T>
+class GFlagInfo {
+ public:
+  explicit GFlagInfo(gflags::CommandLineFlagInfo info)
+    : info_(std::move(info)),
+      isSet_(false) { }
+
+  void set(const T& value) {
+    if (isSet_) {
+      return;
+    }
+
+    auto strValue = folly::to<std::string>(value);
+    auto msg = gflags::SetCommandLineOption(
+        info_.name.c_str(),
+        strValue.c_str());
+    if (msg.empty()) {
+      throw po::invalid_option_value(strValue);
+    }
+    isSet_ = true;
+  }
+
+  T get() const {
+    std::string str;
+    CHECK(gflags::GetCommandLineOption(info_.name.c_str(), &str));
+    return folly::to<T>(str);
+  }
+
+  const gflags::CommandLineFlagInfo& info() const { return info_; }
+
+ private:
+  gflags::CommandLineFlagInfo info_;
+  bool isSet_;
+};
+
+template <class T>
+class GFlagValueSemanticBase : public po::value_semantic {
+ public:
+  explicit GFlagValueSemanticBase(std::shared_ptr<GFlagInfo<T>> info)
+    : info_(std::move(info)) { }
+
+  std::string name() const override { return "arg"; }
+  bool is_composing() const override { return false; }
+  bool is_required() const override { return false; }
+  // We handle setting the GFlags from parse(), so notify() does nothing.
+  void notify(const boost::any& valueStore) const override { }
+  bool apply_default(boost::any& valueStore) const override {
+    // We're using the *current* rather than *default* value here, and
+    // this is intentional; GFlags-using programs assign to FLAGS_foo
+    // before ParseCommandLineFlags() in order to change the default value,
+    // and we obey that.
+    auto val = info_->get();
+    this->transform(val);
+    valueStore = val;
+    return true;
+  }
+
+  void parse(boost::any& valueStore,
+             const std::vector<std::string>& tokens,
+             bool utf8) const override;
+
+ private:
+  virtual T parseValue(const std::vector<std::string>& tokens) const = 0;
+  virtual void transform(T& val) const { }
+
+  mutable std::shared_ptr<GFlagInfo<T>> info_;
+};
+
+template <class T>
+void GFlagValueSemanticBase<T>::parse(boost::any& valueStore,
+                                      const std::vector<std::string>& tokens,
+                                      bool utf8) const {
+  T val;
+  try {
+    val = this->parseValue(tokens);
+    this->transform(val);
+  } catch (const std::exception& e) {
+    throw po::invalid_option_value(
+        tokens.empty() ? std::string() : tokens.front());
+  }
+  this->info_->set(val);
+  valueStore = val;
+}
+
+template <class T>
+class GFlagValueSemantic : public GFlagValueSemanticBase<T> {
+ public:
+  explicit GFlagValueSemantic(std::shared_ptr<GFlagInfo<T>> info)
+    : GFlagValueSemanticBase<T>(std::move(info)) { }
+
+  unsigned min_tokens() const override { return 1; }
+  unsigned max_tokens() const override { return 1; }
+
+  T parseValue(const std::vector<std::string>& tokens) const override {
+    DCHECK(tokens.size() == 1);
+    return folly::to<T>(tokens.front());
+  }
+};
+
+class BoolGFlagValueSemantic : public GFlagValueSemanticBase<bool> {
+ public:
+  explicit BoolGFlagValueSemantic(std::shared_ptr<GFlagInfo<bool>> info)
+    : GFlagValueSemanticBase<bool>(std::move(info)) { }
+
+  unsigned min_tokens() const override { return 0; }
+  unsigned max_tokens() const override { return 0; }
+
+  bool parseValue(const std::vector<std::string>& tokens) const override {
+    DCHECK(tokens.empty());
+    return true;
+  }
+};
+
+class NegativeBoolGFlagValueSemantic : public BoolGFlagValueSemantic {
+ public:
+  explicit NegativeBoolGFlagValueSemantic(std::shared_ptr<GFlagInfo<bool>> info)
+    : BoolGFlagValueSemantic(std::move(info)) { }
+
+ private:
+  void transform(bool& val) const override {
+    val = !val;
+  }
+};
+
+static const std::unordered_set<std::string> gSkipFlags {
+  "flagfile",
+  "fromenv",
+  "tryfromenv",
+  "undefok",
+  "help",
+  "helpfull",
+  "helpshort",
+  "helpon",
+  "helpmatch",
+  "helppackage",
+  "helpxml",
+  "version",
+  "tab_completion_columns",
+  "tab_completion_word",
+};
+
+static const std::unordered_map<std::string, std::string> gFlagOverrides {
+  // Allow -v in addition to --v
+  {"v", "v,v"},
+};
+
+const std::string& getName(const std::string& name) {
+  auto pos = gFlagOverrides.find(name);
+  return pos != gFlagOverrides.end() ? pos->second : name;
+}
+
+template <class T>
+void addGFlag(gflags::CommandLineFlagInfo&& flag,
+              po::options_description& desc,
+              ProgramOptionsStyle style) {
+  auto gflagInfo = std::make_shared<GFlagInfo<T>>(std::move(flag));
+  auto& info = gflagInfo->info();
+  auto name = getName(info.name);
+
+  switch (style) {
+  case ProgramOptionsStyle::GFLAGS:
+    break;
+  case ProgramOptionsStyle::GNU:
+    std::replace(name.begin(), name.end(), '_', '-');
+    break;
+  }
+  desc.add_options()
+    (name.c_str(),
+     new GFlagValueSemantic<T>(gflagInfo),
+     info.description.c_str());
+}
+
+template <>
+void addGFlag<bool>(gflags::CommandLineFlagInfo&& flag,
+                    po::options_description& desc,
+                    ProgramOptionsStyle style) {
+  auto gflagInfo = std::make_shared<GFlagInfo<bool>>(std::move(flag));
+  auto& info = gflagInfo->info();
+  auto name = getName(info.name);
+  std::string negationPrefix;
+
+  switch (style) {
+  case ProgramOptionsStyle::GFLAGS:
+    negationPrefix = "no";
+    break;
+  case ProgramOptionsStyle::GNU:
+    std::replace(name.begin(), name.end(), '_', '-');
+    negationPrefix = "no-";
+    break;
+  }
+
+  desc.add_options()
+    (name.c_str(),
+     new BoolGFlagValueSemantic(gflagInfo),
+     info.description.c_str())
+    ((negationPrefix + name).c_str(),
+     new NegativeBoolGFlagValueSemantic(gflagInfo),
+     folly::to<std::string>("(no) ", info.description).c_str());
+}
+
+typedef void(*FlagAdder)(gflags::CommandLineFlagInfo&&,
+                         po::options_description&,
+                         ProgramOptionsStyle);
+
+const std::unordered_map<std::string, FlagAdder> gFlagAdders = {
+#define X(NAME, TYPE) \
+  {NAME, addGFlag<TYPE>},
+  X("bool",   bool)
+  X("int32",  int32_t)
+  X("int64",  int64_t)
+  X("uint64", uint64_t)
+  X("double", double)
+  X("string", std::string)
+#undef X
+};
+
+}  // namespace
+
+po::options_description getGFlags(ProgramOptionsStyle style) {
+  po::options_description desc("GFlags");
+
+  std::vector<gflags::CommandLineFlagInfo> allFlags;
+  gflags::GetAllFlags(&allFlags);
+
+  for (auto& f : allFlags) {
+    if (gSkipFlags.count(f.name)) {
+      continue;
+    }
+    auto pos = gFlagAdders.find(f.type);
+    CHECK(pos != gFlagAdders.end()) << "Invalid flag type: " << f.type;
+    (*pos->second)(std::move(f), desc, style);
+  }
+
+  return desc;
+}
+
+namespace {
+
+NestedCommandLineParseResult doParseNestedCommandLine(
+    po::command_line_parser&& parser,
+    const po::options_description& desc) {
+  NestedCommandLineParseResult result;
+
+  result.options = parser.options(desc).allow_unregistered().run();
+
+  bool setCommand = true;
+  for (auto& opt : result.options.options) {
+    auto& tokens = opt.original_tokens;
+    auto tokensStart = tokens.begin();
+
+    if (setCommand && opt.position_key != -1) {
+      DCHECK(tokensStart != tokens.end());
+      result.command = *(tokensStart++);
+    }
+
+    if (opt.position_key != -1 || opt.unregistered) {
+      // If we see an unrecognized option before the first positional
+      // argument, assume we don't have a valid command name, because
+      // we don't know how to parse it otherwise.
+      //
+      // program --wtf foo bar
+      //
+      // Is "foo" an argument to "--wtf", or the command name?
+      setCommand = false;
+      result.rest.insert(result.rest.end(), tokensStart, tokens.end());
+    }
+  }
+
+  return result;
+}
+
+}  // namespace
+
+NestedCommandLineParseResult parseNestedCommandLine(
+    int argc, const char* const argv[],
+    const po::options_description& desc) {
+  return doParseNestedCommandLine(po::command_line_parser(argc, argv), desc);
+}
+
+NestedCommandLineParseResult parseNestedCommandLine(
+    const std::vector<std::string>& cmdline,
+    const po::options_description& desc) {
+  return doParseNestedCommandLine(po::command_line_parser(cmdline), desc);
+}
+
+}  // namespaces
diff --git a/folly/experimental/ProgramOptions.h b/folly/experimental/ProgramOptions.h
new file mode 100644 (file)
index 0000000..14abe9a
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_PROGRAMOPTIONS_H_
+#define FOLLY_PROGRAMOPTIONS_H_
+
+#include <boost/program_options.hpp>
+#include <folly/Optional.h>
+#include <gflags/gflags.h>
+
+namespace folly {
+
+enum class ProgramOptionsStyle {
+  GFLAGS,
+  GNU
+};
+
+// Add all GFlags to the given options_description.
+// Use this *instead of* gflags::ParseCommandLineFlags().
+//
+// in GFLAGS style, the flags are named as per gflags conventions:
+//   names_with_underscores
+//   boolean flags have a "no" prefix
+//
+// in GNU style, the flags are named as per GNU conventions:
+//   names-with-dashes
+//   boolean flags have a "no-" prefix
+//
+// Consider (for example) a boolean flag:
+//   DEFINE_bool(flying_pigs, false, "...");
+//
+// In GFLAGS style, the corresponding flags are named
+//   flying_pigs
+//   noflying_pigs
+//
+// In GNU style, the corresponding flags are named
+//   flying-pigs
+//   no-flying-pigs
+//
+// You may not pass arguments to boolean flags, so you must use the
+// "no" / "no-" prefix to set them to false; "--flying_pigs false"
+// and "--flying_pigs=false" are not allowed, to prevent ambiguity.
+boost::program_options::options_description getGFlags(
+    ProgramOptionsStyle style = ProgramOptionsStyle::GNU);
+
+// Helper when parsing nested command lines:
+//
+// program [--common_options...] command [--command_options...] args
+//
+// The result has "command" set to the first positional argument, if any,
+// and "rest" set to the remaining options and arguments. Note that any
+// unrecognized flags must appear after the command name.
+//
+// You may pass "rest" to parseNestedCommandLine again, etc.
+struct NestedCommandLineParseResult {
+  NestedCommandLineParseResult() { }
+
+  boost::program_options::parsed_options options {nullptr};
+
+  Optional<std::string> command;
+  std::vector<std::string> rest;
+};
+
+NestedCommandLineParseResult parseNestedCommandLine(
+    int argc, const char* const argv[],
+    const boost::program_options::options_description& desc);
+
+NestedCommandLineParseResult parseNestedCommandLine(
+    const std::vector<std::string>& cmdline,
+    const boost::program_options::options_description& desc);
+
+}  // namespaces
+
+#endif /* FOLLY_PROGRAMOPTIONS_H_ */
diff --git a/folly/experimental/test/ProgramOptionsTest.cpp b/folly/experimental/test/ProgramOptionsTest.cpp
new file mode 100644 (file)
index 0000000..3a0f4ca
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/experimental/ProgramOptions.h>
+
+#include <folly/FileUtil.h>
+#include <folly/Subprocess.h>
+#include <folly/experimental/io/FsUtil.h>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+namespace folly { namespace test {
+
+namespace {
+
+std::string getHelperPath() {
+  auto path = fs::executable_path();
+  path.remove_filename() /= "program_options_test_helper";
+  return path.native();
+}
+
+std::string callHelper(ProgramOptionsStyle style,
+                       std::initializer_list<std::string> args) {
+  static std::string helperPath = getHelperPath();
+
+  std::vector<std::string> allArgs;
+  allArgs.reserve(args.size() + 1);
+  allArgs.push_back(helperPath);
+  allArgs.insert(allArgs.end(), args.begin(), args.end());
+
+  std::vector<std::string> env;
+  switch (style) {
+  case ProgramOptionsStyle::GNU:
+    env.push_back("PROGRAM_OPTIONS_TEST_STYLE=GNU");
+    break;
+  case ProgramOptionsStyle::GFLAGS:
+    env.push_back("PROGRAM_OPTIONS_TEST_STYLE=GFLAGS");
+    break;
+  }
+
+  Subprocess proc(allArgs, Subprocess::pipeStdout(), nullptr, &env);
+  auto p = proc.communicate();
+  EXPECT_EQ(0, proc.wait().exitStatus());
+
+  return p.first;
+}
+
+}  // namespace
+
+// name value
+
+TEST(ProgramOptionsTest, GFlagsStyleDefaultValues) {
+  EXPECT_EQ(
+      "flag_bool_true 1\n"
+      "flag_bool_false 0\n"
+      "flag_int 42\n"
+      "flag_string foo\n",
+      callHelper(ProgramOptionsStyle::GFLAGS, {}));
+}
+
+TEST(ProgramOptionsTest, GFlagsStyleFlagsSet) {
+  EXPECT_EQ(
+      "flag_bool_true 1\n"
+      "flag_bool_false 1\n"
+      "flag_int 43\n"
+      "flag_string bar\n",
+      callHelper(ProgramOptionsStyle::GFLAGS, {
+          "--flag_bool_true",
+          "--flag_bool_false",
+          "--flag_int", "43",
+          "--flag_string", "bar"}));
+}
+
+TEST(ProgramOptionsTest, GFlagsStyleBoolFlagsNegation) {
+  EXPECT_EQ(
+      "flag_bool_true 0\n"
+      "flag_bool_false 0\n"
+      "flag_int 42\n"
+      "flag_string foo\n",
+      callHelper(ProgramOptionsStyle::GFLAGS, {
+          "--noflag_bool_true",
+          "--noflag_bool_false"}));
+}
+
+TEST(ProgramOptionsTest, GNUStyleDefaultValues) {
+  EXPECT_EQ(
+      "flag-bool-true 1\n"
+      "flag-bool-false 0\n"
+      "flag-int 42\n"
+      "flag-string foo\n",
+      callHelper(ProgramOptionsStyle::GNU, {}));
+}
+
+TEST(ProgramOptionsTest, GNUStyleFlagsSet) {
+  EXPECT_EQ(
+      "flag-bool-true 1\n"
+      "flag-bool-false 1\n"
+      "flag-int 43\n"
+      "flag-string bar\n",
+      callHelper(ProgramOptionsStyle::GNU, {
+          "--flag-bool-true",
+          "--flag-bool-false",
+          "--flag-int", "43",
+          "--flag-string", "bar"}));
+}
+
+TEST(ProgramOptionsTest, GNUStyleBoolFlagsNegation) {
+  EXPECT_EQ(
+      "flag-bool-true 0\n"
+      "flag-bool-false 0\n"
+      "flag-int 42\n"
+      "flag-string foo\n",
+      callHelper(ProgramOptionsStyle::GNU, {
+          "--no-flag-bool-true",
+          "--no-flag-bool-false"}));
+}
+
+TEST(ProgramOptionsTest, GNUStyleSubCommand) {
+  EXPECT_EQ(
+      "flag-bool-true 1\n"
+      "flag-bool-false 1\n"
+      "flag-int 43\n"
+      "flag-string foo\n"
+      "command hello\n"
+      "arg --wtf\n"
+      "arg 100\n"
+      "arg -x\n"
+      "arg -xy\n",
+      callHelper(ProgramOptionsStyle::GNU, {
+          "--flag-bool-false",
+          "hello",
+          "--wtf",
+          "--flag-int", "43",
+          "100",
+          "-x",
+          "-xy"}));
+}
+
+TEST(ProgramOptionsTest, GNUStyleSubCommandUnrecognizedOptionFirst) {
+  EXPECT_EQ(
+      "flag-bool-true 1\n"
+      "flag-bool-false 1\n"
+      "flag-int 43\n"
+      "flag-string foo\n"
+      "arg --wtf\n"
+      "arg hello\n"
+      "arg 100\n"
+      "arg -x\n"
+      "arg -xy\n",
+      callHelper(ProgramOptionsStyle::GNU, {
+          "--flag-bool-false",
+          "--wtf",
+          "hello",
+          "--flag-int", "43",
+          "100",
+          "-x",
+          "-xy"}));
+}
+
+}}  // namespaces
diff --git a/folly/experimental/test/ProgramOptionsTestHelper.cpp b/folly/experimental/test/ProgramOptionsTestHelper.cpp
new file mode 100644 (file)
index 0000000..c683e9a
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <folly/Conv.h>
+#include <folly/experimental/ProgramOptions.h>
+#include <glog/logging.h>
+
+DEFINE_bool(flag_bool_true, true, "Bool with true default value");
+DEFINE_bool(flag_bool_false, false, "Bool with false default value");
+DEFINE_int32(flag_int, 42, "Integer flag");
+DEFINE_string(flag_string, "foo", "String flag");
+
+namespace po = ::boost::program_options;
+
+namespace {
+template <class T>
+void print(const po::variables_map& vm, const std::string& name) {
+  auto& v = vm[name];
+  printf("%s %s\n",
+         name.c_str(),
+         folly::to<std::string>(v.as<T>()).c_str());
+}
+}  // namespace
+
+int main(int argc, char *argv[]) {
+  po::options_description desc;
+  auto styleEnv = getenv("PROGRAM_OPTIONS_TEST_STYLE");
+
+  CHECK(styleEnv) << "PROGRAM_OPTIONS_TEST_STYLE is required";
+  bool gnuStyle = !strcmp(styleEnv, "GNU");
+  CHECK(gnuStyle || !strcmp(styleEnv, "GFLAGS"))
+    << "Invalid value for PROGRAM_OPTIONS_TEST_STYLE";
+
+  desc.add(getGFlags(
+      gnuStyle ? folly::ProgramOptionsStyle::GNU :
+      folly::ProgramOptionsStyle::GFLAGS));
+  desc.add_options()
+    ("help,h", "help");
+
+  po::variables_map vm;
+  auto result = folly::parseNestedCommandLine(argc, argv, desc);
+  po::store(result.options, vm);
+  po::notify(vm);
+
+  if (vm.count("help")) {
+    std::cout << desc;
+    return 1;
+  }
+
+  print<bool>(vm, gnuStyle ? "flag-bool-true" : "flag_bool_true");
+  print<bool>(vm, gnuStyle ? "flag-bool-false" : "flag_bool_false");
+  print<int32_t>(vm, gnuStyle ? "flag-int" : "flag_int");
+  print<std::string>(vm, gnuStyle ? "flag-string" : "flag_string");
+
+  if (result.command) {
+    printf("command %s\n", result.command->c_str());
+  }
+
+  for (auto& arg : result.rest) {
+    printf("arg %s\n", arg.c_str());
+  }
+
+  return 0;
+}
diff --git a/folly/m4/ax_boost_program_options.m4 b/folly/m4/ax_boost_program_options.m4
new file mode 100644 (file)
index 0000000..30913f6
--- /dev/null
@@ -0,0 +1,108 @@
+# ============================================================================
+#  http://www.gnu.org/software/autoconf-archive/ax_boost_program_options.html
+# ============================================================================
+#
+# SYNOPSIS
+#
+#   AX_BOOST_PROGRAM_OPTIONS
+#
+# DESCRIPTION
+#
+#   Test for program options library from the Boost C++ libraries. The macro
+#   requires a preceding call to AX_BOOST_BASE. Further documentation is
+#   available at <http://randspringer.de/boost/index.html>.
+#
+#   This macro calls:
+#
+#     AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB)
+#
+#   And sets:
+#
+#     HAVE_BOOST_PROGRAM_OPTIONS
+#
+# LICENSE
+#
+#   Copyright (c) 2009 Thomas Porschberg <thomas@randspringer.de>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 24
+
+AC_DEFUN([AX_BOOST_PROGRAM_OPTIONS],
+[
+       AC_ARG_WITH([boost-program-options],
+               AS_HELP_STRING([--with-boost-program-options@<:@=special-lib@:>@],
+                       [use the program options library from boost - it is possible to specify a certain library for the linker
+                        e.g. --with-boost-program-options=boost_program_options-gcc-mt-1_33_1 ]),
+        [
+        if test "$withval" = "no"; then
+                       want_boost="no"
+        elif test "$withval" = "yes"; then
+            want_boost="yes"
+            ax_boost_user_program_options_lib=""
+        else
+                   want_boost="yes"
+               ax_boost_user_program_options_lib="$withval"
+               fi
+        ],
+        [want_boost="yes"]
+       )
+
+       if test "x$want_boost" = "xyes"; then
+        AC_REQUIRE([AC_PROG_CC])
+           export want_boost
+               CPPFLAGS_SAVED="$CPPFLAGS"
+               CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+               export CPPFLAGS
+               LDFLAGS_SAVED="$LDFLAGS"
+               LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+               export LDFLAGS
+               AC_CACHE_CHECK([whether the Boost::Program_Options library is available],
+                                          ax_cv_boost_program_options,
+                                          [AC_LANG_PUSH(C++)
+                               AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <boost/program_options/errors.hpp>
+                                                          ]],
+                                  [[boost::program_options::error err("Error message");
+                                   return 0;]])],
+                           ax_cv_boost_program_options=yes, ax_cv_boost_program_options=no)
+                                       AC_LANG_POP([C++])
+               ])
+               if test "$ax_cv_boost_program_options" = yes; then
+                               AC_DEFINE(HAVE_BOOST_PROGRAM_OPTIONS,,[define if the Boost::PROGRAM_OPTIONS library is available])
+                  BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'`
+                if test "x$ax_boost_user_program_options_lib" = "x"; then
+                for libextension in `ls $BOOSTLIBDIR/libboost_program_options*.so* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.so.*$;\1;'` `ls $BOOSTLIBDIR/libboost_program_options*.dylib* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.dylib.*$;\1;'` `ls $BOOSTLIBDIR/libboost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.a.*$;\1;'` ; do
+                     ax_lib=${libextension}
+                                   AC_CHECK_LIB($ax_lib, exit,
+                                 [BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) link_program_options="yes"; break],
+                                 [link_program_options="no"])
+                               done
+                if test "x$link_program_options" != "xyes"; then
+                for libextension in `ls $BOOSTLIBDIR/boost_program_options*.dll* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.dll.*$;\1;'` `ls $BOOSTLIBDIR/boost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.a.*$;\1;'` ; do
+                     ax_lib=${libextension}
+                                   AC_CHECK_LIB($ax_lib, exit,
+                                 [BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) link_program_options="yes"; break],
+                                 [link_program_options="no"])
+                               done
+                fi
+                else
+                  for ax_lib in $ax_boost_user_program_options_lib boost_program_options-$ax_boost_user_program_options_lib; do
+                                     AC_CHECK_LIB($ax_lib, main,
+                                   [BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) link_program_options="yes"; break],
+                                   [link_program_options="no"])
+                  done
+                fi
+            if test "x$ax_lib" = "x"; then
+                AC_MSG_ERROR(Could not find a version of the library!)
+            fi
+                               if test "x$link_program_options" != "xyes"; then
+                                       AC_MSG_ERROR([Could not link against [$ax_lib] !])
+                               fi
+               fi
+               CPPFLAGS="$CPPFLAGS_SAVED"
+       LDFLAGS="$LDFLAGS_SAVED"
+       fi
+])