tools: make nfdc smarter when the user asks for help

Now 'nfdc foo help', 'nfdc foo --help', and 'nfdc foo -h'
are all accepted as synonyms of 'nfdc help foo'.

Additionally, '--version' can be used in place of '-V'.

Change-Id: I070bb0ea9231a2642c40938377f1e9af2630b34e
Refs: #4503
diff --git a/docs/manpages/nfdc.rst b/docs/manpages/nfdc.rst
index 17824b8..d80d6dd 100644
--- a/docs/manpages/nfdc.rst
+++ b/docs/manpages/nfdc.rst
@@ -3,10 +3,10 @@
 
 SYNOPSIS
 --------
-| nfdc COMMAND ARGUMENTS
-| nfdc [-h]
-| nfdc -V
-| nfdc help COMMAND
+| nfdc COMMAND [ARGUMENTS] ...
+| nfdc help [COMMAND]
+| nfdc [-h|--help]
+| nfdc -V|--version
 
 DESCRIPTION
 -----------
@@ -25,11 +25,11 @@
 <ARGUMENTS>
     Arguments to the subcommand.
 
--h
-    Print a list of available subcommands.
+``-h`` or ``--help``
+    Print a list of available subcommands and exit.
 
--V
-    Print version number.
+``-V`` or ``--version``
+    Show version information and exit.
 
 EXAMPLES
 --------
diff --git a/tests/tools/nfdc/command-parser.t.cpp b/tests/tools/nfdc/command-parser.t.cpp
index 73ad3cc..d55ccec 100644
--- a/tests/tools/nfdc/command-parser.t.cpp
+++ b/tests/tools/nfdc/command-parser.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -37,10 +37,28 @@
 BOOST_AUTO_TEST_SUITE(Nfdc)
 BOOST_FIXTURE_TEST_SUITE(TestCommandParser, BaseFixture)
 
+BOOST_AUTO_TEST_CASE(PrintAvailableIn)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(AVAILABLE_IN_NONE), "hidden");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(AVAILABLE_IN_ONE_SHOT), "one-shot|hidden");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(AVAILABLE_IN_BATCH), "batch|hidden");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(AVAILABLE_IN_HELP), "none");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(AVAILABLE_IN_ALL), "one-shot|batch");
+}
+
+BOOST_AUTO_TEST_CASE(PrintParseMode)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ParseMode::ONE_SHOT), "one-shot");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ParseMode::BATCH), "batch");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<ParseMode>(0xFF)), "255");
+}
+
 BOOST_AUTO_TEST_CASE(Basic)
 {
   CommandParser parser;
-  ExecuteCommand dummyExecute = [] (ExecuteContext&) { return 0; };
+  ExecuteCommand dummyExecute = [] (ExecuteContext&) { BOOST_ERROR("should not be called"); };
+
+  BOOST_CHECK(parser.listCommands("", ParseMode::ONE_SHOT).empty());
 
   CommandDefinition defHelp("help", "");
   defHelp
@@ -65,48 +83,59 @@
   parser.addCommand(defRouteAdd, dummyExecute);
   parser.addAlias("route", "add", "add2");
 
+  BOOST_CHECK_EQUAL(parser.listCommands("", ParseMode::ONE_SHOT).size(), 3);
+  BOOST_CHECK_EQUAL(parser.listCommands("", ParseMode::BATCH).size(), 3);
+  BOOST_CHECK_EQUAL(parser.listCommands("route", ParseMode::ONE_SHOT).size(), 2);
+  BOOST_CHECK_EQUAL(parser.listCommands("unknown", ParseMode::ONE_SHOT).size(), 0);
 
   std::string noun, verb;
   CommandArguments ca;
   ExecuteCommand execute;
 
-  std::tie(noun, verb, ca, execute) = parser.parse(
-    std::vector<std::string>{"help"}, ParseMode::ONE_SHOT);
+  std::tie(noun, verb, ca, execute) = parser.parse({"help"}, ParseMode::ONE_SHOT);
   BOOST_CHECK_EQUAL(noun, "help");
   BOOST_CHECK_EQUAL(verb, "");
 
-  std::tie(noun, verb, ca, execute) = parser.parse(
-    std::vector<std::string>{"status"}, ParseMode::ONE_SHOT);
+  std::tie(noun, verb, ca, execute) = parser.parse({"help", "foo"}, ParseMode::ONE_SHOT);
+  BOOST_CHECK_EQUAL(noun, "help");
+  BOOST_CHECK_EQUAL(verb, "");
+
+  std::tie(noun, verb, ca, execute) = parser.parse({"foo", "help"}, ParseMode::ONE_SHOT);
+  BOOST_CHECK_EQUAL(noun, "help");
+  BOOST_CHECK_EQUAL(verb, "");
+
+  std::tie(noun, verb, ca, execute) = parser.parse({"foo", "bar", "-h"}, ParseMode::ONE_SHOT);
+  BOOST_CHECK_EQUAL(noun, "help");
+  BOOST_CHECK_EQUAL(verb, "");
+
+  std::tie(noun, verb, ca, execute) = parser.parse({"status"}, ParseMode::ONE_SHOT);
   BOOST_CHECK_EQUAL(noun, "status");
   BOOST_CHECK_EQUAL(verb, "show");
 
-  std::tie(noun, verb, ca, execute) = parser.parse(
-    std::vector<std::string>{"route", "add", "/n", "300"}, ParseMode::ONE_SHOT);
+  std::tie(noun, verb, ca, execute) = parser.parse({"route", "add", "/n", "300"}, ParseMode::ONE_SHOT);
   BOOST_CHECK_EQUAL(noun, "route");
   BOOST_CHECK_EQUAL(verb, "add");
   BOOST_CHECK_EQUAL(boost::any_cast<Name>(ca.at("prefix")), "/n");
   BOOST_CHECK_EQUAL(boost::any_cast<uint64_t>(ca.at("nexthop")), 300);
 
-  std::tie(noun, verb, ca, execute) = parser.parse(
-    std::vector<std::string>{"route", "add2", "/n", "300"}, ParseMode::ONE_SHOT);
+  std::tie(noun, verb, ca, execute) = parser.parse({"route", "add2", "/n", "300"}, ParseMode::ONE_SHOT);
   BOOST_CHECK_EQUAL(noun, "route");
   BOOST_CHECK_EQUAL(verb, "add");
 
-  std::tie(noun, verb, ca, execute) = parser.parse(
-    std::vector<std::string>{"route", "list", "400"}, ParseMode::ONE_SHOT);
+  std::tie(noun, verb, ca, execute) = parser.parse({"route", "list", "400"}, ParseMode::ONE_SHOT);
   BOOST_CHECK_EQUAL(noun, "route");
   BOOST_CHECK_EQUAL(verb, "list");
   BOOST_CHECK_EQUAL(boost::any_cast<uint64_t>(ca.at("nexthop")), 400);
 
-  BOOST_CHECK_THROW(parser.parse(std::vector<std::string>{}, ParseMode::ONE_SHOT),
-                    CommandParser::Error);
-  BOOST_CHECK_THROW(parser.parse(std::vector<std::string>{"cant-help"}, ParseMode::ONE_SHOT),
-                    CommandParser::Error);
-  BOOST_CHECK_THROW(parser.parse(std::vector<std::string>{"status", "hide"}, ParseMode::ONE_SHOT),
-                    CommandParser::Error);
-  BOOST_CHECK_THROW(parser.parse(std::vector<std::string>{"route", "400"}, ParseMode::ONE_SHOT),
-                    CommandParser::Error);
-  BOOST_CHECK_THROW(parser.parse(std::vector<std::string>{"route", "add"}, ParseMode::ONE_SHOT),
+  BOOST_CHECK_THROW(parser.parse({}, ParseMode::ONE_SHOT),
+                    CommandParser::NoSuchCommandError);
+  BOOST_CHECK_THROW(parser.parse({"cant-help"}, ParseMode::ONE_SHOT),
+                    CommandParser::NoSuchCommandError);
+  BOOST_CHECK_THROW(parser.parse({"status", "hide"}, ParseMode::ONE_SHOT),
+                    CommandParser::NoSuchCommandError);
+  BOOST_CHECK_THROW(parser.parse({"route", "66"}, ParseMode::ONE_SHOT),
+                    CommandParser::NoSuchCommandError);
+  BOOST_CHECK_THROW(parser.parse({"route", "add"}, ParseMode::ONE_SHOT),
                     CommandDefinition::Error);
 }
 
diff --git a/tools/nfdc/command-parser.cpp b/tools/nfdc/command-parser.cpp
index 74c5202..74ecf36 100644
--- a/tools/nfdc/command-parser.cpp
+++ b/tools/nfdc/command-parser.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -25,6 +25,7 @@
 
 #include "command-parser.hpp"
 #include "format-helpers.hpp"
+
 #include <ndn-cxx/util/logger.hpp>
 
 namespace nfd {
@@ -74,6 +75,7 @@
                           std::underlying_type<AvailableIn>::type modes)
 {
   BOOST_ASSERT(modes != AVAILABLE_IN_NONE);
+
   m_commands[{def.getNoun(), def.getVerb()}].reset(
     new Command{def, execute, static_cast<AvailableIn>(modes)});
 
@@ -95,7 +97,7 @@
 CommandParser::listCommands(const std::string& noun, ParseMode mode) const
 {
   std::vector<const CommandDefinition*> results;
-  for (CommandContainer::const_iterator i : m_commandOrder) {
+  for (auto i : m_commandOrder) {
     const Command& command = *i->second;
     if ((command.modes & static_cast<AvailableIn>(mode)) != 0 &&
         (noun.empty() || noun == command.def.getNoun())) {
@@ -106,7 +108,7 @@
 }
 
 std::tuple<std::string, std::string, CommandArguments, ExecuteCommand>
-CommandParser::parse(const std::vector<std::string>& tokens, ParseMode mode) const
+CommandParser::parse(std::vector<std::string> tokens, ParseMode mode) const
 {
   BOOST_ASSERT(mode == ParseMode::ONE_SHOT);
 
@@ -114,23 +116,42 @@
   const std::string& verb = tokens.size() > 1 ? tokens[1] : "";
   size_t nameLen = std::min<size_t>(2, tokens.size());
 
+  NDN_LOG_TRACE("parse mode=" << mode << " noun=" << noun << " verb=" << verb);
+
   auto i = m_commands.find({noun, verb});
   if (i == m_commands.end()) {
     if (verb.empty()) {
+      NDN_LOG_TRACE("fallback to noun=" << noun << " verb=list");
       i = m_commands.find({noun, "list"});
     }
     else {
       // help, exit, quit commands
+      NDN_LOG_TRACE("fallback to noun=" << noun << " verb=");
       i = m_commands.find({noun, ""});
     }
     nameLen = std::min<size_t>(1, tokens.size());
+
+    if (i == m_commands.end()) {
+      const auto helpStrings = {"help", "--help", "-h"};
+      auto helpIt = std::find_first_of(tokens.begin(), tokens.end(),
+                                       helpStrings.begin(), helpStrings.end());
+      if (helpIt != tokens.end()) {
+        NDN_LOG_TRACE("fallback to noun=help verb=");
+        i = m_commands.find({"help", ""});
+        if (i != m_commands.end()) {
+          tokens.erase(helpIt);
+          nameLen = 0;
+        }
+      }
+    }
   }
+
   if (i == m_commands.end() || (i->second->modes & static_cast<AvailableIn>(mode)) == 0) {
-    BOOST_THROW_EXCEPTION(Error("no such command: " + noun + " " + verb));
+    BOOST_THROW_EXCEPTION(NoSuchCommandError(noun, verb));
   }
 
   const CommandDefinition& def = i->second->def;
-  NDN_LOG_TRACE("found command " << def.getNoun() << " " << def.getVerb());
+  NDN_LOG_TRACE("found command noun=" << def.getNoun() << " verb=" << def.getVerb());
 
   return std::make_tuple(def.getNoun(), def.getVerb(), def.parse(tokens, nameLen), i->second->execute);
 }
diff --git a/tools/nfdc/command-parser.hpp b/tools/nfdc/command-parser.hpp
index 551f5e3..5ff5217 100644
--- a/tools/nfdc/command-parser.hpp
+++ b/tools/nfdc/command-parser.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -28,6 +28,7 @@
 
 #include "command-definition.hpp"
 #include "execute-command.hpp"
+
 #include <type_traits>
 
 namespace nfd {
@@ -62,12 +63,11 @@
 class CommandParser : noncopyable
 {
 public:
-  class Error : public std::invalid_argument
+  class NoSuchCommandError : public std::invalid_argument
   {
   public:
-    explicit
-    Error(const std::string& what)
-      : std::invalid_argument(what)
+    NoSuchCommandError(const std::string& noun, const std::string& verb)
+      : std::invalid_argument("No such command: " + noun + " " + verb)
     {
     }
   };
@@ -98,12 +98,12 @@
   /** \brief parse a command line
    *  \param tokens command line
    *  \param mode parser mode, must be ParseMode::ONE_SHOT, other modes are not implemented
-   *  \throw Error command is not found
+   *  \throw NoSuchCommandError command not found
    *  \throw CommandDefinition::Error command arguments are invalid
    *  \return noun, verb, arguments, execute function
    */
   std::tuple<std::string, std::string, CommandArguments, ExecuteCommand>
-  parse(const std::vector<std::string>& tokens, ParseMode mode) const;
+  parse(std::vector<std::string> tokens, ParseMode mode) const;
 
 private:
   typedef std::pair<std::string, std::string> CommandName;
diff --git a/tools/nfdc/help.cpp b/tools/nfdc/help.cpp
index 902bf67..e67992c 100644
--- a/tools/nfdc/help.cpp
+++ b/tools/nfdc/help.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017,  Regents of the University of California,
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -25,7 +25,9 @@
 
 #include "help.hpp"
 #include "format-helpers.hpp"
+
 #include <ndn-cxx/util/logger.hpp>
+
 #include <unistd.h>
 
 namespace nfd {
@@ -39,7 +41,7 @@
 void
 helpList(std::ostream& os, const CommandParser& parser, ParseMode mode, const std::string& noun)
 {
-  os << "nfdc [-h] [-V] <command> [<args>]\n\n";
+  os << "nfdc [-h|--help] [-V|--version] <command> [<args>]\n\n";
   if (noun.empty()) {
     os << "All subcommands:\n";
   }
@@ -64,25 +66,25 @@
 }
 
 static void
-helpSingle(const std::string& noun, const std::string& verb)
+helpCommand(const std::string& noun, const std::string& verb)
 {
   std::string manpage = "nfdc-" + noun;
 
-  execlp("man", "man", manpage.data(), nullptr);
+  ::execlp("man", "man", manpage.data(), nullptr);
   NDN_LOG_FATAL("Error opening man page for " << manpage);
 }
 
 void
 help(ExecuteContext& ctx, const CommandParser& parser)
 {
-  std::string noun = ctx.args.get<std::string>("noun", "");
-  std::string verb = ctx.args.get<std::string>("verb", "");
+  auto noun = ctx.args.get<std::string>("noun", "");
+  auto verb = ctx.args.get<std::string>("verb", "");
 
   if (noun.empty()) {
-    helpList(ctx.out, parser, ParseMode::ONE_SHOT, noun);
+    helpList(ctx.out, parser);
   }
   else {
-    helpSingle(noun, verb); // should not return
+    helpCommand(noun, verb); // should not return
     ctx.exitCode = 1;
   }
 }
diff --git a/tools/nfdc/main.cpp b/tools/nfdc/main.cpp
index dfbc05c..a164a8e 100644
--- a/tools/nfdc/main.cpp
+++ b/tools/nfdc/main.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -41,12 +41,12 @@
   CommandParser parser;
   registerCommands(parser);
 
-  if (args.empty() || args[0] == "-h") {
+  if (args.empty() || args[0] == "-h" || args[0] == "--help") {
     helpList(std::cout, parser);
     return 0;
   }
 
-  if (args[0] == "-V") {
+  if (args[0] == "-V" || args[0] == "--version") {
     std::cout << NFD_VERSION_BUILD_STRING << std::endl;
     return 0;
   }
@@ -55,7 +55,7 @@
   CommandArguments ca;
   ExecuteCommand execute;
   try {
-    std::tie(noun, verb, ca, execute) = parser.parse(args, ParseMode::ONE_SHOT);
+    std::tie(noun, verb, ca, execute) = parser.parse(std::move(args), ParseMode::ONE_SHOT);
   }
   catch (const std::invalid_argument& e) {
     std::cerr << e.what() << std::endl;