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;