ndnpeek: switch from getopt to boost::program_options

Change-Id: Idf47e6b6eb6c082708e423c16f7eb503b81deabd
Refs: #3112
diff --git a/manpages/ndnpeek.rst b/manpages/ndnpeek.rst
index 1f36263..3735337 100644
--- a/manpages/ndnpeek.rst
+++ b/manpages/ndnpeek.rst
@@ -6,7 +6,7 @@
 
 ::
 
-    ndnpeek [-h] [-f] [-r] [-m min] [-M max] [-l lifetime] [-p] [-w timeout] name
+    ndnpeek [-h] [-f] [-r] [-m min] [-M max] [-l lifetime] [-p] [-w timeout] [-v] [-V] name
 
 Description
 -----------
@@ -21,30 +21,48 @@
 Options
 -------
 
-``-h``
-  Print help and exit
+``-h, --help``
+  Print help and exit.
 
-``-f``
+``-f, --fresh``
   If specified, set ``MustBeFresh`` selector in the Interest packet.
 
-``-r``
+``-r, --rightmost``
   Set ``ChildSelector=1`` (the rightmost child) selector.
 
-``-m``
+``-m, --minsuffix min``
   Set ``min`` as the ``MinSuffixComponents`` selector.
 
-``-M``
+``-M, --maxsuffix max``
   Set ``max`` as the ``MaxSuffixComponents`` selector.
 
-``-l``
+``-l, --lifetime lifetime``
   Set ``lifetime`` (in milliseconds) as the ``InterestLifetime``.
 
-``-p``
+``-p, --payload``
   If specified, print the received payload only, not the full packet.
 
-``-w``
+``-w, --timeout timeout``
   Timeout after ``timeout`` milliseconds.
 
+``-v, --verbose``
+  If specified, verbose output.
+
+``-V, --version``
+  Print version and exit.
+
+Exit Codes
+----------
+
+0: Success
+
+1: An unspecified error occurred
+
+2: Malformed command line
+
+3: Network operation timed out
+
+4: Nack received
 
 Examples
 --------
diff --git a/tools/peek/ndn-peek.cpp b/tools/peek/ndn-peek.cpp
index 432042b..2f3bef5 100644
--- a/tools/peek/ndn-peek.cpp
+++ b/tools/peek/ndn-peek.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2015,  Regents of the University of California,
+ * Copyright (c) 2014-2016,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -49,6 +49,9 @@
  */
 
 #include "core/version.hpp"
+#include "core/common.hpp"
+
+#include <ndn-cxx/util/io.hpp>
 
 namespace ndn {
 namespace peek {
@@ -58,63 +61,34 @@
 public:
   explicit
   NdnPeek(char* programName)
-    : m_programName(programName)
-    , m_isVerbose(false)
-    , m_mustBeFresh(false)
-    , m_isChildSelectorRightmost(false)
+    : isVerbose(false)
+    , mustBeFresh(false)
+    , wantRightmostChild(false)
+    , wantPayloadOnly(false)
+    , m_programName(programName)
     , m_minSuffixComponents(-1)
     , m_maxSuffixComponents(-1)
     , m_interestLifetime(-1)
-    , m_isPayloadOnlySet(false)
     , m_timeout(-1)
     , m_prefixName("")
-    , m_isDataReceived(false)
+    , m_didReceiveData(false)
   {
   }
 
   void
-  usage()
+  usage(std::ostream& os ,const boost::program_options::options_description& options) const
   {
-    std::cout << "\n Usage:\n " << m_programName << " "
-      "[-f] [-r] [-m min] [-M max] [-l lifetime] [-p] [-w timeout] ndn:/name\n"
-      "   Get one data item matching the name prefix and write it to stdout\n"
-      "   [-f]          - set MustBeFresh\n"
-      "   [-r]          - set ChildSelector to select rightmost child\n"
-      "   [-m min]      - set MinSuffixComponents\n"
-      "   [-M max]      - set MaxSuffixComponents\n"
-      "   [-l lifetime] - set InterestLifetime in time::milliseconds\n"
-      "   [-p]          - print payload only, not full packet\n"
-      "   [-w timeout]  - set Timeout in time::milliseconds\n"
-      "   [-v]          - verbose output\n"
-      "   [-h]          - print help and exit\n"
-      "   [-V]          - print version and exit\n"
-      "\n";
-    exit(1);
-  }
-
-  void
-  setVerbose()
-  {
-    m_isVerbose = true;
-  }
-
-  void
-  setMustBeFresh()
-  {
-    m_mustBeFresh = true;
-  }
-
-  void
-  setRightmostChildSelector()
-  {
-    m_isChildSelectorRightmost = true;
+    os << "Usage: " << m_programName << " [options] ndn:/name\n"
+          "   Fetch one data item matching the name prefix and write it to standard output\n"
+          "\n"
+       << options;
   }
 
   void
   setMinSuffixComponents(int minSuffixComponents)
   {
     if (minSuffixComponents < 0)
-      usage();
+      throw std::out_of_range("'minSuffixComponents' must be a non-negative integer");
 
     m_minSuffixComponents = minSuffixComponents;
   }
@@ -123,7 +97,7 @@
   setMaxSuffixComponents(int maxSuffixComponents)
   {
     if (maxSuffixComponents < 0)
-      usage();
+      throw std::out_of_range("'maxSuffixComponents' must be a non-negative integer");
 
     m_maxSuffixComponents = maxSuffixComponents;
   }
@@ -132,33 +106,24 @@
   setInterestLifetime(int interestLifetime)
   {
     if (interestLifetime < 0)
-      usage();
+      throw std::out_of_range("'lifetime' must be a non-negative integer");
 
     m_interestLifetime = time::milliseconds(interestLifetime);
   }
 
   void
-  setPayloadOnly()
-  {
-    m_isPayloadOnlySet = true;
-  }
-
-  void
   setTimeout(int timeout)
   {
     if (timeout < 0)
-      usage();
+      throw std::out_of_range("'timeout' must be a non-negative integer");
 
     m_timeout = time::milliseconds(timeout);
   }
 
   void
-  setPrefixName(char* prefixName)
+  setPrefixName(const std::string& prefixName)
   {
     m_prefixName = prefixName;
-
-    if (m_prefixName.length() == 0)
-      usage();
   }
 
   time::milliseconds
@@ -173,10 +138,10 @@
     Name interestName(m_prefixName);
     Interest interestPacket(interestName);
 
-    if (m_mustBeFresh)
+    if (mustBeFresh)
       interestPacket.setMustBeFresh(true);
 
-    if (m_isChildSelectorRightmost)
+    if (wantRightmostChild)
       interestPacket.setChildSelector(1);
 
     if (m_minSuffixComponents >= 0)
@@ -190,7 +155,7 @@
     else
       interestPacket.setInterestLifetime(m_interestLifetime);
 
-    if (m_isVerbose) {
+    if (isVerbose) {
       std::cerr << "INTEREST: " << interestPacket << std::endl;
     }
 
@@ -200,15 +165,15 @@
   void
   onData(const Interest& interest, Data& data)
   {
-    m_isDataReceived = true;
+    m_didReceiveData = true;
 
-    if (m_isVerbose) {
+    if (isVerbose) {
       std::cerr << "DATA, RTT: "
                 << time::duration_cast<time::milliseconds>(time::steady_clock::now() - m_expressInterestTime).count()
                 << "ms" << std::endl;
     }
 
-    if (m_isPayloadOnlySet) {
+    if (wantPayloadOnly) {
       const Block& block = data.getContent();
       std::cout.write(reinterpret_cast<const char*>(block.value()), block.value_size());
     }
@@ -223,7 +188,7 @@
   {
   }
 
-  void
+  int
   run()
   {
     try {
@@ -237,34 +202,34 @@
       }
       m_face.processEvents(m_timeout);
     }
-    catch (std::exception& e) {
+    catch (const std::exception& e) {
       std::cerr << "ERROR: " << e.what() << std::endl;
-      exit(1);
+      return 1;
     }
-    if (m_isVerbose && !m_isDataReceived) {
+
+    if (isVerbose && !m_didReceiveData) {
       std::cerr << "TIMEOUT" << std::endl;
+      return 3;
     }
+
+    return 0;
   }
 
-  bool
-  isDataReceived() const
-  {
-    return m_isDataReceived;
-  }
+public:
+  bool isVerbose;
+  bool mustBeFresh;
+  bool wantRightmostChild;
+  bool wantPayloadOnly;
 
 private:
   std::string m_programName;
-  bool m_isVerbose;
-  bool m_mustBeFresh;
-  bool m_isChildSelectorRightmost;
   int m_minSuffixComponents;
   int m_maxSuffixComponents;
   time::milliseconds m_interestLifetime;
-  bool m_isPayloadOnlySet;
   time::milliseconds m_timeout;
   std::string m_prefixName;
   time::steady_clock::TimePoint m_expressInterestTime;
-  bool m_isDataReceived;
+  bool m_didReceiveData;
   Face m_face;
 };
 
@@ -272,58 +237,71 @@
 main(int argc, char* argv[])
 {
   NdnPeek program(argv[0]);
-  int option;
-  while ((option = getopt(argc, argv, "hvfrm:M:l:pw:V")) != -1) {
-    switch (option) {
-    case 'h':
-      program.usage();
-      break;
-    case 'v':
-      program.setVerbose();
-      break;
-    case 'f':
-      program.setMustBeFresh();
-      break;
-    case 'r':
-      program.setRightmostChildSelector();
-      break;
-    case 'm':
-      program.setMinSuffixComponents(atoi(optarg));
-      break;
-    case 'M':
-      program.setMaxSuffixComponents(atoi(optarg));
-      break;
-    case 'l':
-      program.setInterestLifetime(atoi(optarg));
-      break;
-    case 'p':
-      program.setPayloadOnly();
-      break;
-    case 'w':
-      program.setTimeout(atoi(optarg));
-      break;
-    case 'V':
+
+  namespace po = boost::program_options;
+
+  po::options_description visibleOptDesc("Allowed options");
+  visibleOptDesc.add_options()
+    ("help,h", "print help and exit")
+    ("version,V", "print version and exit")
+    ("fresh,f", po::bool_switch(&program.mustBeFresh),
+        "set MustBeFresh")
+    ("rightmost,r", po::bool_switch(&program.wantRightmostChild),
+        "set ChildSelector to rightmost")
+    ("minsuffix,m", po::value<int>()->notifier(bind(&NdnPeek::setMinSuffixComponents, &program, _1)),
+        "set MinSuffixComponents")
+    ("maxsuffix,M", po::value<int>()->notifier(bind(&NdnPeek::setMaxSuffixComponents, &program, _1)),
+        "set MaxSuffixComponents")
+    ("lifetime,l", po::value<int>()->notifier(bind(&NdnPeek::setInterestLifetime, &program, _1)),
+        "set InterestLifetime (in milliseconds)")
+    ("payload,p", po::bool_switch(&program.wantPayloadOnly),
+        "print payload only, instead of full packet")
+    ("timeout,w", po::value<int>()->notifier(bind(&NdnPeek::setTimeout, &program, _1)),
+        "set timeout (in milliseconds)")
+    ("verbose,v", po::bool_switch(&program.isVerbose),
+        "turn on verbose output")
+  ;
+
+  po::options_description hiddenOptDesc("Hidden options");
+  hiddenOptDesc.add_options()
+    ("prefix", po::value<std::string>(), "Interest name");
+
+  po::options_description optDesc("Allowed options");
+  optDesc.add(visibleOptDesc).add(hiddenOptDesc);
+
+  po::positional_options_description optPos;
+  optPos.add("prefix", -1);
+
+  try {
+    po::variables_map vm;
+    po::store(po::command_line_parser(argc, argv).options(optDesc).positional(optPos).run(), vm);
+    po::notify(vm);
+
+    if (vm.count("help") > 0) {
+      program.usage(std::cout, visibleOptDesc);
+      return 0;
+    }
+
+    if (vm.count("version") > 0) {
       std::cout << "ndnpeek " << tools::VERSION << std::endl;
       return 0;
-    default:
-      program.usage();
-      break;
+    }
+
+    if (vm.count("prefix") > 0) {
+      std::string prefixName = vm["prefix"].as<std::string>();
+      program.setPrefixName(prefixName);
+    }
+    else {
+      throw std::runtime_error("Required argument 'prefix' is missing");
     }
   }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    program.usage(std::cerr, visibleOptDesc);
+    return 2;
+  }
 
-  argc -= optind;
-  argv += optind;
-
-  if (argv[0] == 0)
-    program.usage();
-
-  program.setPrefixName(argv[0]);
-  program.run();
-
-  if (program.isDataReceived())
-    return 0;
-  else
-    return 1;
+  return program.run();
 }
 
 } // namespace peek