tools: legacy nfdc parses command with Boost.Program_options

refs #3749

Change-Id: I5274e0a2594323729834cd8001308ec699fbe6d6
diff --git a/tools/nfdc/legacy-nfdc.cpp b/tools/nfdc/legacy-nfdc.cpp
index 3e4c0e6..5b7ff45 100644
--- a/tools/nfdc/legacy-nfdc.cpp
+++ b/tools/nfdc/legacy-nfdc.cpp
@@ -26,6 +26,7 @@
 #include "legacy-nfdc.hpp"
 #include "face-id-fetcher.hpp"
 
+#include <boost/program_options.hpp>
 #include <boost/regex.hpp>
 
 namespace nfd {
@@ -52,42 +53,42 @@
 LegacyNfdc::dispatch(const std::string& command)
 {
   if (command == "add-nexthop") {
-    if (m_nOptions != 2)
+    if (m_commandLineArguments.size() != 2)
       return false;
     fibAddNextHop();
   }
   else if (command == "remove-nexthop") {
-    if (m_nOptions != 2)
+    if (m_commandLineArguments.size() != 2)
       return false;
     fibRemoveNextHop();
   }
   else if (command == "register") {
-    if (m_nOptions != 2)
+    if (m_commandLineArguments.size() != 2)
       return false;
     ribRegisterPrefix();
   }
   else if (command == "unregister") {
-    if (m_nOptions != 2)
+    if (m_commandLineArguments.size() != 2)
       return false;
     ribUnregisterPrefix();
   }
   else if (command == "create") {
-    if (m_nOptions != 1)
+    if (m_commandLineArguments.size() != 1)
       return false;
     faceCreate();
   }
   else if (command == "destroy") {
-    if (m_nOptions != 1)
+    if (m_commandLineArguments.size() != 1)
       return false;
     faceDestroy();
   }
   else if (command == "set-strategy") {
-    if (m_nOptions != 2)
+    if (m_commandLineArguments.size() != 2)
       return false;
     strategyChoiceSet();
   }
   else if (command == "unset-strategy") {
-    if (m_nOptions != 1)
+    if (m_commandLineArguments.size() != 1)
       return false;
     strategyChoiceUnset();
   }
@@ -285,6 +286,116 @@
   BOOST_THROW_EXCEPTION(Error(os.str()));
 }
 
+void
+legacyNfdcUsage()
+{
+  std::cout << "Usage:\n"
+    "nfdc [-h] [-V] COMMAND [<Command Options>]\n"
+    "       -h print usage and exit\n"
+    "       -V print version and exit\n"
+    "\n"
+    "   COMMAND can be one of the following:\n"
+    "       register [-I] [-C] [-c cost] [-e expiration time] [-o origin] name <faceId | faceUri>\n"
+    "           register name to the given faceId or faceUri\n"
+    "           -I: unset CHILD_INHERIT flag\n"
+    "           -C: set CAPTURE flag\n"
+    "           -c: specify cost (default 0)\n"
+    "           -e: specify expiration time in ms\n"
+    "               (by default the entry remains in FIB for the lifetime of the associated face)\n"
+    "           -o: specify origin\n"
+    "               0 for Local producer applications, 128 for NLSR, 255(default) for static routes\n"
+    "       unregister [-o origin] name <faceId | faceUri>\n"
+    "           unregister name from the given faceId\n"
+    "       create [-P] <faceUri> \n"
+    "           Create a face in one of the following formats:\n"
+    "           UDP unicast:    udp[4|6]://<remote-IP-or-host>[:<remote-port>]\n"
+    "           TCP:            tcp[4|6]://<remote-IP-or-host>[:<remote-port>] \n"
+    "           -P: create permanent (instead of persistent) face\n"
+    "       destroy <faceId | faceUri> \n"
+    "           Destroy a face\n"
+    "       set-strategy <name> <strategy> \n"
+    "           Set the strategy for a namespace \n"
+    "       unset-strategy <name> \n"
+    "           Unset the strategy for a namespace \n"
+    "       add-nexthop [-c <cost>] <name> <faceId | faceUri>\n"
+    "           Add a nexthop to a FIB entry\n"
+    "           -c: specify cost (default 0)\n"
+    "       remove-nexthop <name> <faceId | faceUri> \n"
+    "           Remove a nexthop from a FIB entry\n"
+    << std::endl;
+}
+
+int
+legacyNfdcMain(const std::string& subcommand, const std::vector<std::string>& args)
+{
+  ndn::Face face;
+  LegacyNfdc p(face);
+
+  bool wantUnsetChildInherit = false;
+  bool wantCapture = false;
+  bool wantPermanentFace = false;
+  int64_t expires = -1;
+
+  namespace po = boost::program_options;
+  po::options_description options;
+  options.add_options()
+    (",I", po::bool_switch(&wantUnsetChildInherit))
+    (",C", po::bool_switch(&wantCapture))
+    (",c", po::value<uint64_t>(&p.m_cost))
+    (",e", po::value<int64_t>(&expires))
+    (",o", po::value<uint64_t>(&p.m_origin))
+    (",P", po::bool_switch(&wantPermanentFace));
+  po::variables_map vm;
+  std::vector<std::string> unparsed;
+  try {
+    po::parsed_options parsed = po::command_line_parser(args).options(options).allow_unregistered().run();
+    unparsed = po::collect_unrecognized(parsed.options, po::include_positional);
+    po::store(parsed, vm);
+    po::notify(vm);
+  }
+  catch (const po::error& e) {
+    std::cerr << e.what() << std::endl;
+    legacyNfdcUsage();
+    return 1;
+  }
+
+  if (wantUnsetChildInherit) {
+    p.m_flags &= ~(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
+  }
+  if (wantCapture) {
+    p.m_flags |= ndn::nfd::ROUTE_FLAG_CAPTURE;
+  }
+  if (expires >= 0) {
+    // accept negative values as no expiration
+    p.m_expires = time::milliseconds(expires);
+  }
+  if (wantPermanentFace) {
+    p.m_facePersistency = ndn::nfd::FACE_PERSISTENCY_PERMANENT;
+  }
+
+  if (std::any_of(unparsed.begin(), unparsed.end(),
+      [] (const std::string& s) { return s.empty() || s[0] == '-'; })) {
+    // unrecognized -option
+    legacyNfdcUsage();
+    return 1;
+  }
+  p.m_commandLineArguments = unparsed;
+
+  try {
+    bool isOk = p.dispatch(subcommand);
+    if (!isOk) {
+      legacyNfdcUsage();
+      return 1;
+    }
+    face.processEvents();
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+  return 0;
+}
+
 } // namespace nfdc
 } // namespace tools
 } // namespace nfd
diff --git a/tools/nfdc/legacy-nfdc.hpp b/tools/nfdc/legacy-nfdc.hpp
index 5a804a1..8828afb 100644
--- a/tools/nfdc/legacy-nfdc.hpp
+++ b/tools/nfdc/legacy-nfdc.hpp
@@ -159,11 +159,7 @@
   onObtainFaceIdFailure(const std::string& message);
 
 public:
-  const char* m_programName;
-
-  // command parameters without leading 'cmd' component
-  const char* const* m_commandLineArguments;
-  int m_nOptions;
+  std::vector<std::string> m_commandLineArguments; // positional arguments
   uint64_t m_flags;
   uint64_t m_cost;
   uint64_t m_faceId;
@@ -178,6 +174,12 @@
   ndn::nfd::Controller m_controller;
 };
 
+void
+legacyNfdcUsage();
+
+int
+legacyNfdcMain(const std::string& subcommand, const std::vector<std::string>& args);
+
 } // namespace nfdc
 } // namespace tools
 } // namespace nfd
diff --git a/tools/nfdc/main.cpp b/tools/nfdc/main.cpp
index 74ef47d..5bc2596 100644
--- a/tools/nfdc/main.cpp
+++ b/tools/nfdc/main.cpp
@@ -27,153 +27,36 @@
 #include "status-main.hpp"
 #include "core/version.hpp"
 
-#include <boost/lexical_cast.hpp>
-
 namespace nfd {
 namespace tools {
 namespace nfdc {
 
-static void
-usage(const char* programName)
-{
-  std::cout << "Usage:\n" << programName  << " [-h] [-V] COMMAND [<Command Options>]\n"
-    "       -h print usage and exit\n"
-    "       -V print version and exit\n"
-    "\n"
-    "   COMMAND can be one of the following:\n"
-    "       register [-I] [-C] [-c cost] [-e expiration time] [-o origin] name <faceId | faceUri>\n"
-    "           register name to the given faceId or faceUri\n"
-    "           -I: unset CHILD_INHERIT flag\n"
-    "           -C: set CAPTURE flag\n"
-    "           -c: specify cost (default 0)\n"
-    "           -e: specify expiration time in ms\n"
-    "               (by default the entry remains in FIB for the lifetime of the associated face)\n"
-    "           -o: specify origin\n"
-    "               0 for Local producer applications, 128 for NLSR, 255(default) for static routes\n"
-    "       unregister [-o origin] name <faceId | faceUri>\n"
-    "           unregister name from the given faceId\n"
-    "       create [-P] <faceUri> \n"
-    "           Create a face in one of the following formats:\n"
-    "           UDP unicast:    udp[4|6]://<remote-IP-or-host>[:<remote-port>]\n"
-    "           TCP:            tcp[4|6]://<remote-IP-or-host>[:<remote-port>] \n"
-    "           -P: create permanent (instead of persistent) face\n"
-    "       destroy <faceId | faceUri> \n"
-    "           Destroy a face\n"
-    "       set-strategy <name> <strategy> \n"
-    "           Set the strategy for a namespace \n"
-    "       unset-strategy <name> \n"
-    "           Unset the strategy for a namespace \n"
-    "       add-nexthop [-c <cost>] <name> <faceId | faceUri>\n"
-    "           Add a nexthop to a FIB entry\n"
-    "           -c: specify cost (default 0)\n"
-    "       remove-nexthop <name> <faceId | faceUri> \n"
-    "           Remove a nexthop from a FIB entry\n"
-    << std::endl;
-}
-
 static int
 main(int argc, char** argv)
 {
-  ndn::Face face;
-  LegacyNfdc p(face);
-
-  p.m_programName = argv[0];
-
   if (argc < 2) {
-    usage(p.m_programName);
+    legacyNfdcUsage();
     return 0;
   }
 
-  if (strcmp(argv[1], "-h") == 0) {
-    usage(p.m_programName);
+  std::string subcommand(argv[1]);
+  std::vector<std::string> args(argv + 2, argv + argc);
+
+  if (subcommand == "-h") {
+    legacyNfdcUsage();
     return 0;
   }
 
-  if (strcmp(argv[1], "-V") == 0) {
+  if (subcommand == "-V") {
     std::cout << NFD_VERSION_BUILD_STRING << std::endl;
     return 0;
   }
 
-  if (strcmp(argv[1], "legacy-nfd-status") == 0) {
-    return statusMain(argc - 1, argv + 1);
+  if (subcommand == "legacy-nfd-status") {
+    return statusMain(args);
   }
 
-  ::optind = 2; //start reading options from 2nd argument i.e. Command
-  int opt;
-  while ((opt = ::getopt(argc, argv, "ICc:e:o:P")) != -1) {
-    switch (opt) {
-      case 'I':
-        p.m_flags = p.m_flags & ~(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
-        break;
-
-      case 'C':
-        p.m_flags = p.m_flags | ndn::nfd::ROUTE_FLAG_CAPTURE;
-        break;
-
-      case 'c':
-        try {
-          p.m_cost = boost::lexical_cast<uint64_t>(::optarg);
-        }
-        catch (const boost::bad_lexical_cast&) {
-          std::cerr << "Error: cost must be in unsigned integer format" << std::endl;
-          return 1;
-        }
-        break;
-
-      case 'e':
-        uint64_t expires;
-        try {
-          expires = boost::lexical_cast<uint64_t>(::optarg);
-        }
-        catch (const boost::bad_lexical_cast&) {
-          std::cerr << "Error: expiration time must be in unsigned integer format" << std::endl;
-          return 1;
-        }
-        p.m_expires = ndn::time::milliseconds(expires);
-        break;
-
-      case 'o':
-        try {
-          p.m_origin = boost::lexical_cast<uint64_t>(::optarg);
-        }
-        catch (const boost::bad_lexical_cast&) {
-          std::cerr << "Error: origin must be in unsigned integer format" << std::endl;
-          return 1;
-        }
-        break;
-
-      case 'P':
-        p.m_facePersistency = ndn::nfd::FACE_PERSISTENCY_PERMANENT;
-        break;
-
-      default:
-        usage(p.m_programName);
-        return 1;
-    }
-  }
-
-  if (argc == ::optind) {
-    usage(p.m_programName);
-    return 1;
-  }
-
-  try {
-    p.m_commandLineArguments = argv + ::optind;
-    p.m_nOptions = argc - ::optind;
-
-    //argv[1] points to the command, so pass it to the dispatch
-    bool isOk = p.dispatch(argv[1]);
-    if (!isOk) {
-      usage(p.m_programName);
-      return 1;
-    }
-    face.processEvents();
-  }
-  catch (const std::exception& e) {
-    std::cerr << "ERROR: " << e.what() << std::endl;
-    return 2;
-  }
-  return 0;
+  return legacyNfdcMain(subcommand, args);
 }
 
 } // namespace nfdc
diff --git a/tools/nfdc/status-main.cpp b/tools/nfdc/status-main.cpp
index e268e40..0f87ab1 100644
--- a/tools/nfdc/status-main.cpp
+++ b/tools/nfdc/status-main.cpp
@@ -64,7 +64,7 @@
  *          otherwise, caller should immediately exit with the specified exit code
  */
 static std::tuple<int, Options>
-parseCommandLine(int argc, const char* const* argv)
+parseCommandLine(const std::vector<std::string>& args)
 {
   Options options;
 
@@ -82,14 +82,14 @@
     ("xml,x", "output as XML instead of text (implies -vcfbrs)");
   po::variables_map vm;
   try {
-    po::store(po::parse_command_line(argc, argv, cmdOptions), vm);
+    po::store(po::command_line_parser(args).options(cmdOptions).run(), vm);
+    po::notify(vm);
   }
   catch (const po::error& e) {
     std::cerr << e.what() << "\n";
     showUsage(std::cerr, cmdOptions);
     return std::make_tuple(2, options);
   }
-  po::notify(vm);
 
   if (vm.count("help") > 0) {
     showUsage(std::cout, cmdOptions);
@@ -114,11 +114,11 @@
 }
 
 int
-statusMain(int argc, char** argv)
+statusMain(const std::vector<std::string>& args)
 {
   int exitCode = -1;
   Options options;
-  std::tie(exitCode, options) = parseCommandLine(argc, argv);
+  std::tie(exitCode, options) = parseCommandLine(args);
   if (exitCode >= 0) {
     return exitCode;
   }
diff --git a/tools/nfdc/status-main.hpp b/tools/nfdc/status-main.hpp
index 910e4ac..62b58a6 100644
--- a/tools/nfdc/status-main.hpp
+++ b/tools/nfdc/status-main.hpp
@@ -26,12 +26,14 @@
 #ifndef NFD_TOOLS_NFDC_STATUS_MAIN_HPP
 #define NFD_TOOLS_NFDC_STATUS_MAIN_HPP
 
+#include "core/common.hpp"
+
 namespace nfd {
 namespace tools {
 namespace nfdc {
 
 int
-statusMain(int argc, char** argv);
+statusMain(const std::vector<std::string>& args);
 
 } // namespace nfdc
 } // namespace tools