tools: nfdc command line parser

refs #3749

Change-Id: Ief301152212a6d501f0396b2c9834e860ddaf6c5
diff --git a/tests/tools/nfdc/command-definition.t.cpp b/tests/tools/nfdc/command-definition.t.cpp
new file mode 100644
index 0000000..bbefe81
--- /dev/null
+++ b/tests/tools/nfdc/command-definition.t.cpp
@@ -0,0 +1,326 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfdc/command-definition.hpp"
+#include "nfdc/status-report.hpp"
+#include <ndn-cxx/encoding/nfd-constants.hpp>
+#include <ndn-cxx/util/face-uri.hpp>
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+namespace tests {
+
+using namespace nfd::tests;
+
+using ndn::util::FaceUri;
+using ndn::nfd::FacePersistency;
+
+BOOST_AUTO_TEST_SUITE(Nfdc)
+BOOST_FIXTURE_TEST_SUITE(TestCommandDefinition, BaseFixture)
+
+BOOST_AUTO_TEST_SUITE(Arguments)
+
+BOOST_AUTO_TEST_CASE(NoArg)
+{
+  CommandDefinition cs("noun", "verb");
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{});
+  BOOST_CHECK_EQUAL(ca.size(), 0);
+
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"x"}), CommandDefinition::Error);
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"x", "y"}), CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_CASE(NamedArgs)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::UNSIGNED, Required::NO, Positional::NO, "int")
+    .addArg("b", ArgValueType::NAME, Required::NO, Positional::NO, "name");
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{});
+  BOOST_CHECK_EQUAL(ca.size(), 0);
+
+  ca = cs.parse(std::vector<std::string>{"a", "1"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK_EQUAL(ca.get<uint64_t>("a"), 1);
+
+  ca = cs.parse(std::vector<std::string>{"a", "1", "b", "/n"});
+  BOOST_CHECK_EQUAL(ca.size(), 2);
+  BOOST_CHECK_EQUAL(ca.get<uint64_t>("a"), 1);
+  BOOST_CHECK_EQUAL(ca.get<Name>("b"), "/n");
+
+  ca = cs.parse(std::vector<std::string>{"b", "/n", "a", "1"});
+  BOOST_CHECK_EQUAL(ca.size(), 2);
+  BOOST_CHECK_EQUAL(ca.get<uint64_t>("a"), 1);
+  BOOST_CHECK_EQUAL(ca.get<Name>("b"), "/n");
+
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"1"}), CommandDefinition::Error);
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"c", "1"}), CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_CASE(PositionalArgs)
+{
+  CommandDefinition cs("face", "create");
+  cs.addArg("remote", ArgValueType::FACE_URI, Required::YES, Positional::YES)
+    .addArg("persistency", ArgValueType::FACE_PERSISTENCY, Required::NO, Positional::YES);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{"udp4://router.example.com", "persistent"});
+  BOOST_CHECK_EQUAL(ca.size(), 2);
+  BOOST_CHECK_EQUAL(ca.get<FaceUri>("remote"),
+                    FaceUri("udp4://router.example.com"));
+  BOOST_CHECK_EQUAL(ca.get<FacePersistency>("persistency"),
+                    FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  ca = cs.parse(std::vector<std::string>{"udp4://router.example.com"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK_EQUAL(ca.get<FaceUri>("remote"),
+                    FaceUri("udp4://router.example.com"));
+
+  ca = cs.parse(std::vector<std::string>{"remote", "udp4://router.example.com"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK_EQUAL(ca.get<FaceUri>("remote"),
+                    FaceUri("udp4://router.example.com"));
+
+  ca = cs.parse(std::vector<std::string>{
+                "udp4://router.example.com", "persistency", "persistent"});
+  BOOST_CHECK_EQUAL(ca.size(), 2);
+  BOOST_CHECK_EQUAL(ca.get<FaceUri>("remote"),
+                    FaceUri("udp4://router.example.com"));
+  BOOST_CHECK_EQUAL(ca.get<FacePersistency>("persistency"),
+                    FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  ca = cs.parse(std::vector<std::string>{
+                "remote", "udp4://router.example.com", "persistency", "persistent"});
+  BOOST_CHECK_EQUAL(ca.size(), 2);
+  BOOST_CHECK_EQUAL(ca.get<FaceUri>("remote"),
+                    FaceUri("udp4://router.example.com"));
+  BOOST_CHECK_EQUAL(ca.get<FacePersistency>("persistency"),
+                    FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  ca = cs.parse(std::vector<std::string>{
+                "persistency", "persistent", "remote", "udp4://router.example.com"});
+  BOOST_CHECK_EQUAL(ca.size(), 2);
+  BOOST_CHECK_EQUAL(ca.get<FaceUri>("remote"),
+                    FaceUri("udp4://router.example.com"));
+  BOOST_CHECK_EQUAL(ca.get<FacePersistency>("persistency"),
+                    FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{
+    "persistent", "udp4://router.example.com"}), CommandDefinition::Error);
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{
+    "persistency", "persistent", "udp4://router.example.com"}), CommandDefinition::Error);
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{
+    "remote", "udp4://router.example.com", "persistent"}), CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Arguments
+
+BOOST_AUTO_TEST_SUITE(ParseValue)
+
+BOOST_AUTO_TEST_CASE(NoneType)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::NONE, Required::YES, Positional::NO);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{"a"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(bool));
+  BOOST_CHECK_EQUAL(ca.get<bool>("a"), true);
+
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"a", "value"}), CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_CASE(AnyType)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::ANY, Required::NO, Positional::YES);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{});
+  BOOST_CHECK_EQUAL(ca.size(), 0);
+
+  ca = cs.parse(std::vector<std::string>{"a"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(std::vector<std::string>));
+  std::vector<std::string> values = ca.get<std::vector<std::string>>("a");
+  BOOST_CHECK_EQUAL(values.size(), 1);
+  BOOST_CHECK_EQUAL(values.at(0), "a");
+
+  ca = cs.parse(std::vector<std::string>{"b", "c"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(std::vector<std::string>));
+  values = ca.get<std::vector<std::string>>("a");
+  BOOST_CHECK_EQUAL(values.size(), 2);
+  BOOST_CHECK_EQUAL(values.at(0), "b");
+  BOOST_CHECK_EQUAL(values.at(1), "c");
+}
+
+BOOST_AUTO_TEST_CASE(UnsignedType)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::UNSIGNED, Required::YES);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{"a", "0"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(uint64_t));
+  BOOST_CHECK_EQUAL(ca.get<uint64_t>("a"), 0);
+
+  ca = cs.parse(std::vector<std::string>{"a", "12923"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(uint64_t));
+  BOOST_CHECK_EQUAL(ca.get<uint64_t>("a"), 12923);
+
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"a", "-25705"}), CommandDefinition::Error);
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"a", "not-uint"}), CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_CASE(StringType)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::STRING, Required::YES);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{"a", "hello"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(std::string));
+  BOOST_CHECK_EQUAL(ca.get<std::string>("a"), "hello");
+}
+
+BOOST_AUTO_TEST_CASE(ReportFormatType)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::REPORT_FORMAT, Required::YES);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{"a", "xml"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(ReportFormat));
+  BOOST_CHECK_EQUAL(ca.get<ReportFormat>("a"), ReportFormat::XML);
+
+  ca = cs.parse(std::vector<std::string>{"a", "text"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(ReportFormat));
+  BOOST_CHECK_EQUAL(ca.get<ReportFormat>("a"), ReportFormat::TEXT);
+
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"a", "not-fmt"}), CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_CASE(NameType)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::NAME, Required::YES);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{"a", "/n"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(Name));
+  BOOST_CHECK_EQUAL(ca.get<Name>("a"), "/n");
+}
+
+BOOST_AUTO_TEST_CASE(FaceUriType)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::FACE_URI, Required::YES);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{"a", "udp4://192.0.2.1:6363"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(FaceUri));
+  BOOST_CHECK_EQUAL(ca.get<FaceUri>("a"), FaceUri("udp4://192.0.2.1:6363"));
+
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"a", "208"}), CommandDefinition::Error);
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"a", "not-FaceUri"}), CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_CASE(FaceIdOrUriType)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::FACE_ID_OR_URI, Required::YES);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{"a", "208"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(uint64_t));
+  BOOST_CHECK_EQUAL(ca.get<uint64_t>("a"), 208);
+
+  ca = cs.parse(std::vector<std::string>{"a", "udp4://192.0.2.1:6363"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(FaceUri));
+  BOOST_CHECK_EQUAL(ca.get<FaceUri>("a"), FaceUri("udp4://192.0.2.1:6363"));
+
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"a", "not-FaceUri"}), CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_CASE(FacePersistencyType)
+{
+  CommandDefinition cs("noun", "verb");
+  cs.addArg("a", ArgValueType::FACE_PERSISTENCY, Required::YES);
+
+  CommandArguments ca;
+
+  ca = cs.parse(std::vector<std::string>{"a", "persistent"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(FacePersistency));
+  BOOST_CHECK_EQUAL(ca.get<FacePersistency>("a"),
+                    FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  ca = cs.parse(std::vector<std::string>{"a", "permanent"});
+  BOOST_CHECK_EQUAL(ca.size(), 1);
+  BOOST_CHECK(ca.at("a").type() == typeid(FacePersistency));
+  BOOST_CHECK_EQUAL(ca.get<FacePersistency>("a"),
+                    FacePersistency::FACE_PERSISTENCY_PERMANENT);
+
+  // nfdc does not accept "on-demand"
+  BOOST_CHECK_THROW(cs.parse(std::vector<std::string>{"a", "on-demand"}), CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ParseValue
+
+BOOST_AUTO_TEST_SUITE_END() // TestCommandDefinition
+BOOST_AUTO_TEST_SUITE_END() // Nfdc
+
+} // namespace tests
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfdc/command-parser.t.cpp b/tests/tools/nfdc/command-parser.t.cpp
new file mode 100644
index 0000000..435392d
--- /dev/null
+++ b/tests/tools/nfdc/command-parser.t.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfdc/command-parser.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+namespace tests {
+
+using namespace nfd::tests;
+
+BOOST_AUTO_TEST_SUITE(Nfdc)
+BOOST_FIXTURE_TEST_SUITE(TestCommandParser, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  CommandParser parser;
+
+  std::string lastName;
+  auto makeExecute = [&] (const std::string& name) {
+    return [&, name] (const CommandArguments& args) {
+      lastName = name;
+    };
+  };
+
+  CommandDefinition defHelp("help", "");
+  defHelp
+    .addArg("noun", ArgValueType::STRING, Required::NO, Positional::YES)
+    .addArg("verb", ArgValueType::STRING, Required::NO, Positional::YES);
+  parser.addCommand(defHelp, makeExecute("help"), AVAILABLE_IN_ONE_SHOT);
+
+  CommandDefinition defStatusShow("status", "show");
+  parser.addCommand(defStatusShow, makeExecute("status show"));
+  parser.addAlias("status", "show", "list");
+  BOOST_CHECK_THROW(parser.addAlias("status", "show2", "list"), std::out_of_range);
+
+  CommandDefinition defRouteList("route", "list");
+  defRouteList
+    .addArg("nexthop", ArgValueType::FACE_ID_OR_URI, Required::NO, Positional::YES);
+  parser.addCommand(defRouteList, makeExecute("route list"));
+
+  CommandDefinition defRouteAdd("route", "add");
+  defRouteAdd
+    .addArg("prefix", ArgValueType::NAME, Required::YES, Positional::YES)
+    .addArg("nexthop", ArgValueType::FACE_ID_OR_URI, Required::YES, Positional::YES);
+  parser.addCommand(defRouteAdd, makeExecute("route add"));
+  parser.addAlias("route", "add", "add2");
+
+
+  CommandParser::Execute* execute = nullptr;
+  CommandArguments ca;
+
+  std::tie(execute, ca) = parser.parse(std::vector<std::string>{"help"},
+                                       ParseMode::ONE_SHOT);
+  BOOST_REQUIRE(execute != nullptr);
+  (*execute)(ca);
+  BOOST_CHECK_EQUAL(lastName, "help");
+
+  std::tie(execute, ca) = parser.parse(std::vector<std::string>{"status"},
+                                       ParseMode::ONE_SHOT);
+  BOOST_REQUIRE(execute != nullptr);
+  (*execute)(ca);
+  BOOST_CHECK_EQUAL(lastName, "status show");
+
+  std::tie(execute, ca) = parser.parse(std::vector<std::string>{"route", "add", "/n", "300"},
+                                       ParseMode::ONE_SHOT);
+  BOOST_REQUIRE(execute != nullptr);
+  (*execute)(ca);
+  BOOST_CHECK_EQUAL(lastName, "route 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(execute, ca) = parser.parse(std::vector<std::string>{"route", "add2", "/n", "300"},
+                                       ParseMode::ONE_SHOT);
+  BOOST_REQUIRE(execute != nullptr);
+  (*execute)(ca);
+  BOOST_CHECK_EQUAL(lastName, "route add");
+
+  std::tie(execute, ca) = parser.parse(std::vector<std::string>{"route", "list", "400"},
+                                       ParseMode::ONE_SHOT);
+  BOOST_REQUIRE(execute != nullptr);
+  (*execute)(ca);
+  BOOST_CHECK_EQUAL(lastName, "route 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),
+                    CommandDefinition::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCommandDefinition
+BOOST_AUTO_TEST_SUITE_END() // Nfdc
+
+} // namespace tests
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfdc/available-commands.cpp b/tools/nfdc/available-commands.cpp
new file mode 100644
index 0000000..3b6a393
--- /dev/null
+++ b/tools/nfdc/available-commands.cpp
@@ -0,0 +1,99 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "available-commands.hpp"
+#include "status-report.hpp"
+#include "status-main.hpp"
+#include "legacy-nfdc.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+static void
+statusReport(const CommandArguments& ca)
+{
+  int res = 1;
+  ReportFormat fmt = ca.get<ReportFormat>("format", ReportFormat::TEXT);
+  switch (fmt) {
+    case ReportFormat::XML:
+      res = statusMain(std::vector<std::string>{"-x"});
+      break;
+    case ReportFormat::TEXT:
+      res = statusMain(std::vector<std::string>{});
+      break;
+  }
+  exit(res);
+}
+
+static void
+legacyNfdStatus(const CommandArguments& ca)
+{
+  std::vector<std::string> args = ca.get<std::vector<std::string>>("args");
+  int res = statusMain(args);
+  exit(res);
+}
+
+static void
+legacyNfdc(const std::string& subcommand, const CommandArguments& ca)
+{
+  std::vector<std::string> args = ca.get<std::vector<std::string>>("args");
+  int res = legacyNfdcMain(subcommand, args);
+  exit(res);
+}
+
+void
+registerCommands(CommandParser& parser)
+{
+  CommandDefinition defStatusReport("status", "report");
+  defStatusReport
+    .addArg("format", ArgValueType::REPORT_FORMAT, Required::NO, Positional::YES);
+  parser.addCommand(defStatusReport, &statusReport);
+
+  CommandDefinition defLegacyNfdStatus("legacy-nfd-status", "");
+  defLegacyNfdStatus
+    .addArg("args", ArgValueType::ANY, Required::NO, Positional::YES);
+  parser.addCommand(defLegacyNfdStatus, &legacyNfdStatus);
+
+  const std::vector<std::string> legacyNfdcSubcommands{
+    "register",
+    "unregister",
+    "create",
+    "destroy",
+    "set-strategy",
+    "unset-strategy",
+    "add-nexthop",
+    "remove-nexthop"
+  };
+  for (const std::string& subcommand : legacyNfdcSubcommands) {
+    CommandDefinition def(subcommand, "");
+    def.addArg("args", ArgValueType::ANY, Required::NO, Positional::YES);
+    parser.addCommand(def, bind(&legacyNfdc, subcommand, _1));
+  }
+}
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfdc/available-commands.hpp b/tools/nfdc/available-commands.hpp
new file mode 100644
index 0000000..a887eec
--- /dev/null
+++ b/tools/nfdc/available-commands.hpp
@@ -0,0 +1,42 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFDC_AVAILABLE_COMMANDS_HPP
+#define NFD_TOOLS_NFDC_AVAILABLE_COMMANDS_HPP
+
+#include "command-parser.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+void
+registerCommands(CommandParser& parser);
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFDC_AVAILABLE_COMMANDS_HPP
diff --git a/tools/nfdc/command-arguments.hpp b/tools/nfdc/command-arguments.hpp
new file mode 100644
index 0000000..5bf9414
--- /dev/null
+++ b/tools/nfdc/command-arguments.hpp
@@ -0,0 +1,56 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFDC_COMMAND_ARGUMENTS_HPP
+#define NFD_TOOLS_NFDC_COMMAND_ARGUMENTS_HPP
+
+#include "core/common.hpp"
+#include <boost/any.hpp>
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+/** \brief contains named command arguments
+ */
+class CommandArguments : public std::map<std::string, boost::any>
+{
+public:
+  /** \return the argument value, or a default value if the argument is omitted on command line
+   */
+  template<typename T>
+  T
+  get(const std::string& key, const T& defaultValue = T()) const
+  {
+    auto i = find(key);
+    return i == end() ? defaultValue : boost::any_cast<T>(i->second);
+  }
+};
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFDC_COMMAND_ARGUMENTS_HPP
diff --git a/tools/nfdc/command-definition.cpp b/tools/nfdc/command-definition.cpp
new file mode 100644
index 0000000..cf11928
--- /dev/null
+++ b/tools/nfdc/command-definition.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "command-definition.hpp"
+#include "status-report.hpp"
+#include <ndn-cxx/encoding/nfd-constants.hpp>
+#include <ndn-cxx/util/face-uri.hpp>
+#include <ndn-cxx/util/logger.hpp>
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+NDN_LOG_INIT(CommandDefinition);
+
+std::ostream&
+operator<<(std::ostream& os, ArgValueType vt)
+{
+  switch (vt) {
+    case ArgValueType::NONE:
+      return os << "none";
+    case ArgValueType::ANY:
+      return os << "any";
+    case ArgValueType::UNSIGNED:
+      return os << "non-negative integer";
+    case ArgValueType::STRING:
+      return os << "string";
+    case ArgValueType::REPORT_FORMAT:
+      return os << "ReportFormat";
+    case ArgValueType::NAME:
+      return os << "Name";
+    case ArgValueType::FACE_URI:
+      return os << "FaceUri";
+    case ArgValueType::FACE_ID_OR_URI:
+      return os << "FaceId or FaceUri";
+    case ArgValueType::FACE_PERSISTENCY:
+      return os << "FacePersistency";
+  }
+  return os << static_cast<int>(vt);
+}
+
+static std::string
+getMetavarFromType(ArgValueType vt)
+{
+  switch (vt) {
+    case ArgValueType::NONE:
+      return "";
+    case ArgValueType::ANY:
+      return "args";
+    case ArgValueType::UNSIGNED:
+      return "uint";
+    case ArgValueType::STRING:
+      return "str";
+    case ArgValueType::REPORT_FORMAT:
+      return "fmt";
+    case ArgValueType::NAME:
+      return "name";
+    case ArgValueType::FACE_URI:
+      return "uri";
+    case ArgValueType::FACE_ID_OR_URI:
+      return "face";
+    case ArgValueType::FACE_PERSISTENCY:
+      return "persistency";
+  }
+  BOOST_ASSERT(false);
+  return "";
+}
+
+CommandDefinition::CommandDefinition(const std::string& noun, const std::string& verb)
+  : m_noun(noun)
+  , m_verb(verb)
+{
+}
+
+CommandDefinition::~CommandDefinition() = default;
+
+CommandDefinition&
+CommandDefinition::addArg(const std::string& name, ArgValueType valueType,
+                        Required isRequired, Positional allowPositional,
+                        const std::string& metavar)
+{
+  bool isNew = m_args.emplace(name,
+    Arg{name, valueType, static_cast<bool>(isRequired),
+        metavar.empty() ? getMetavarFromType(valueType) : metavar}).second;
+  BOOST_ASSERT(isNew);
+
+  if (static_cast<bool>(isRequired)) {
+    m_requiredArgs.insert(name);
+  }
+
+  if (static_cast<bool>(allowPositional)) {
+    BOOST_ASSERT(valueType != ArgValueType::NONE);
+    m_positionalArgs.push_back(name);
+  }
+  else {
+    BOOST_ASSERT(valueType != ArgValueType::ANY);
+  }
+
+  return *this;
+}
+
+CommandArguments
+CommandDefinition::parse(const std::vector<std::string>& tokens, size_t start) const
+{
+  CommandArguments ca;
+
+  size_t positionalArgIndex = 0;
+  for (size_t i = start; i < tokens.size(); ++i) {
+    const std::string& token = tokens[i];
+
+    // try to parse as named argument
+    auto namedArg = m_args.find(token);
+    if (namedArg != m_args.end() && namedArg->second.valueType != ArgValueType::ANY) {
+      NDN_LOG_TRACE(token << " is a named argument");
+      const Arg& arg = namedArg->second;
+      if (arg.valueType == ArgValueType::NONE) {
+        ca[arg.name] = true;
+        NDN_LOG_TRACE(token << " is a boolean argument");
+      }
+      else if (i + 1 >= tokens.size()) {
+        BOOST_THROW_EXCEPTION(Error(arg.name + ": " + arg.metavar + " is missing"));
+      }
+      else {
+        const std::string& valueToken = tokens[++i];
+        NDN_LOG_TRACE(arg.name << " has value " << valueToken);
+        try {
+          ca[arg.name] = this->parseValue(arg.valueType, valueToken);
+        }
+        catch (const std::exception& e) {
+          NDN_LOG_TRACE(valueToken << " cannot be parsed as " << arg.valueType);
+          BOOST_THROW_EXCEPTION(Error(arg.name + ": cannot parse '" + valueToken + "' as " +
+                                      arg.metavar + " (" + e.what() + ")"));
+        }
+        NDN_LOG_TRACE(valueToken << " is parsed as " << arg.valueType);
+      }
+
+      // disallow positional arguments after named argument
+      positionalArgIndex = m_positionalArgs.size();
+      continue;
+    }
+
+    // try to parse as positional argument
+    for (; positionalArgIndex < m_positionalArgs.size(); ++positionalArgIndex) {
+      const Arg& arg = m_args.at(m_positionalArgs[positionalArgIndex]);
+
+      if (arg.valueType == ArgValueType::ANY) {
+        std::vector<std::string> values;
+        std::copy(tokens.begin() + i, tokens.end(), std::back_inserter(values));
+        ca[arg.name] = values;
+        NDN_LOG_TRACE((tokens.size() - i) << " tokens are consumed for " << arg.name);
+        i = tokens.size();
+        break;
+      }
+
+      try {
+        ca[arg.name] = this->parseValue(arg.valueType, token);
+        NDN_LOG_TRACE(token << " is parsed as value for " << arg.name);
+        break;
+      }
+      catch (const std::exception& e) {
+        if (arg.isRequired) { // the current token must be parsed as the value for arg
+          NDN_LOG_TRACE(token << " cannot be parsed as value for " << arg.name);
+          BOOST_THROW_EXCEPTION(Error("cannot parse '" + token + "' as an argument name or as " +
+                                      arg.metavar + " for " + arg.name + " (" + e.what() + ")"));
+        }
+        else {
+          // the current token may be a value for next positional argument
+          NDN_LOG_TRACE(token << " cannot be parsed as value for " << arg.name);
+        }
+      }
+    }
+
+    if (positionalArgIndex >= m_positionalArgs.size()) {
+      // for loop has reached the end without finding a match,
+      // which means token is not accepted as a value for positional argument
+      BOOST_THROW_EXCEPTION(Error("cannot parse '" + token + "' as an argument name"));
+    }
+
+    // token is accepted; don't parse as the same positional argument again
+    ++positionalArgIndex;
+  }
+
+  for (const std::string& argName : m_requiredArgs) {
+    if (ca.count(argName) == 0) {
+      BOOST_THROW_EXCEPTION(Error(argName + ": required argument is missing"));
+    }
+  }
+
+  return ca;
+}
+
+static ndn::nfd::FacePersistency
+parseFacePersistency(const std::string& s)
+{
+  if (s == "persistent") {
+    return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
+  }
+  if (s == "permanent") {
+    return ndn::nfd::FACE_PERSISTENCY_PERMANENT;
+  }
+  BOOST_THROW_EXCEPTION(std::invalid_argument("unrecognized FacePersistency"));
+}
+
+boost::any
+CommandDefinition::parseValue(ArgValueType valueType, const std::string& token) const
+{
+  switch (valueType) {
+    case ArgValueType::NONE:
+    case ArgValueType::ANY:
+      BOOST_ASSERT(false);
+      return boost::any();
+
+    case ArgValueType::UNSIGNED: {
+      // boost::lexical_cast<uint64_t> will accept negative number
+      int64_t v = boost::lexical_cast<int64_t>(token);
+      if (v < 0) {
+        BOOST_THROW_EXCEPTION(std::out_of_range("value is negative"));
+      }
+      return static_cast<uint64_t>(v);
+    }
+
+    case ArgValueType::STRING:
+      return token;
+
+    case ArgValueType::REPORT_FORMAT:
+      return parseReportFormat(token);
+
+    case ArgValueType::NAME:
+      return Name(token);
+
+    case ArgValueType::FACE_URI:
+      return ndn::util::FaceUri(token);
+
+    case ArgValueType::FACE_ID_OR_URI:
+      try {
+        return boost::lexical_cast<uint64_t>(token);
+      }
+      catch (const boost::bad_lexical_cast&) {
+        return ndn::util::FaceUri(token);
+      }
+
+    case ArgValueType::FACE_PERSISTENCY:
+      return parseFacePersistency(token);
+  }
+
+  BOOST_ASSERT(false);
+  return boost::any();
+}
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfdc/command-definition.hpp b/tools/nfdc/command-definition.hpp
new file mode 100644
index 0000000..136f5c4
--- /dev/null
+++ b/tools/nfdc/command-definition.hpp
@@ -0,0 +1,211 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFDC_COMMAND_DEFINITION_HPP
+#define NFD_TOOLS_NFDC_COMMAND_DEFINITION_HPP
+
+#include "command-arguments.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+/** \brief indicates argument value type
+ */
+enum class ArgValueType {
+  /** \brief boolean argument without value
+   *
+   *  The argument appears in CommandArguments as bool value 'true'.
+   *  It must not be declared as positional.
+   */
+  NONE,
+
+  /** \brief any arguments
+   *
+   *  The argument appears in CommandArguments as std::vector<std::string>.
+   *  It must be declared as positional, and will consume all subsequent tokens.
+   */
+  ANY,
+
+  /** \brief non-negative integer
+   *
+   *  The argument appears in CommandArguments as uint64_t.
+   *  Acceptable input range is [0, std::numeric_limits<int64_t>::max()].
+   */
+  UNSIGNED,
+
+  /** \brief arbitrary string
+   *
+   *  The argument appears in CommandArguments as std::string.
+   */
+  STRING,
+
+  /** \brief report format 'xml' or 'text'
+   *
+   *  The argument appears in CommandArguments as nfd::tools::nfdc::ReportFormat.
+   */
+  REPORT_FORMAT,
+
+  /** \brief Name prefix
+   *
+   *  The argument appears in CommandArguments as ndn::Name.
+   */
+  NAME,
+
+  /** \brief FaceUri
+   *
+   *  The argument appears in CommandArguments as ndn::util::FaceUri.
+   */
+  FACE_URI,
+
+  /** \brief FaceId or FaceUri
+   *
+   *  The argument appears in CommandArguments as either uint64_t or ndn::util::FaceUri.
+   */
+  FACE_ID_OR_URI,
+
+  /** \brief face persistency 'persistent' or 'permanent'
+   *
+   *  The argument appears in CommandArguments as ndn::nfd::FacePersistency.
+   */
+  FACE_PERSISTENCY
+};
+
+std::ostream&
+operator<<(std::ostream& os, ArgValueType vt);
+
+/** \brief indicates whether an argument is required
+ */
+enum class Required {
+  NO = false, ///< argument is optional
+  YES = true  ///< argument is required
+};
+
+/** \brief indicates whether an argument can be specified as positional
+ */
+enum class Positional {
+  NO = false, ///< argument must be named
+  YES = true  ///< argument can be specified as positional
+};
+
+/** \brief declares semantics of a command
+ */
+class CommandDefinition
+{
+public:
+  class Error : public std::invalid_argument
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::invalid_argument(what)
+    {
+    }
+  };
+
+  CommandDefinition(const std::string& noun, const std::string& verb);
+
+  ~CommandDefinition();
+
+  const std::string
+  getNoun() const
+  {
+    return m_noun;
+  }
+
+  const std::string
+  getVerb() const
+  {
+    return m_verb;
+  }
+
+public: // help
+  /** \return one-line synopsis
+   */
+  const std::string&
+  getSynopsis() const
+  {
+    return m_synopsis;
+  }
+
+  /** \brief set one-line synopsis
+   */
+  CommandDefinition&
+  setSynopsis(const std::string& synopsis)
+  {
+    m_synopsis = synopsis;
+    return *this;
+  }
+
+public: // arguments
+  /** \brief declare an argument
+   *  \param name argument name, must be unique
+   *  \param valueType argument value type
+   *  \param isRequired whether the argument is required
+   *  \param allowPositional whether the argument value can be specified as positional
+   *  \param metavar displayed argument value placeholder
+   */
+  CommandDefinition&
+  addArg(const std::string& name, ArgValueType valueType,
+         Required isRequired = Required::NO,
+         Positional allowPositional = Positional::NO,
+         const std::string& metavar = "");
+
+  /** \brief parse a command line
+   *  \param tokens command line tokens
+   *  \param start command line start position, after noun and verb
+   *  \throw Error command line is invalid
+   */
+  CommandArguments
+  parse(const std::vector<std::string>& tokens, size_t start = 0) const;
+
+private:
+  boost::any
+  parseValue(ArgValueType valueType, const std::string& token) const;
+
+private:
+  std::string m_noun;
+  std::string m_verb;
+
+  std::string m_synopsis;
+
+  struct Arg
+  {
+    std::string name;
+    ArgValueType valueType;
+    bool isRequired;
+    std::string metavar;
+  };
+  typedef std::map<std::string, Arg> ArgMap;
+  ArgMap m_args;
+  std::set<std::string> m_requiredArgs;
+  std::vector<std::string> m_positionalArgs;
+};
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFDC_COMMAND_DEFINITION_HPP
diff --git a/tools/nfdc/command-parser.cpp b/tools/nfdc/command-parser.cpp
new file mode 100644
index 0000000..d91cbc3
--- /dev/null
+++ b/tools/nfdc/command-parser.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "command-parser.hpp"
+#include <ndn-cxx/util/logger.hpp>
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+NDN_LOG_INIT(CommandParser);
+
+std::ostream&
+operator<<(std::ostream& os, AvailableIn modes)
+{
+  int count = 0;
+
+#define PRINT_BIT(bit, str) \
+  if ((modes & bit) != 0) { \
+    if (++count > 1) { \
+      os << '|'; \
+    } \
+    os << str; \
+  }
+
+  PRINT_BIT(AVAILABLE_IN_ONE_SHOT, "one-shot")
+  PRINT_BIT(AVAILABLE_IN_BATCH, "batch")
+
+#undef PRINT_BIT
+
+  if (count == 0) {
+    os << "none";
+  }
+  return os;
+}
+
+std::ostream&
+operator<<(std::ostream& os, ParseMode mode)
+{
+  switch (mode) {
+    case ParseMode::ONE_SHOT:
+      return os << "one-shot";
+    case ParseMode::BATCH:
+      return os << "batch";
+  }
+  return os << static_cast<int>(mode);
+}
+
+CommandParser&
+CommandParser::addCommand(const CommandDefinition& def, const Execute& execute, AvailableIn modes)
+{
+  BOOST_ASSERT(modes != AVAILABLE_IN_NONE);
+  m_commands[{def.getNoun(), def.getVerb()}].reset(new Command{def, execute, modes});
+  return *this;
+}
+
+CommandParser&
+CommandParser::addAlias(const std::string& noun, const std::string& verb, const std::string& verb2)
+{
+  m_commands[{noun, verb2}] = m_commands.at({noun, verb});
+  return *this;
+}
+
+std::tuple<CommandParser::Execute*, CommandArguments>
+CommandParser::parse(const std::vector<std::string>& tokens, ParseMode mode) const
+{
+  BOOST_ASSERT(mode == ParseMode::ONE_SHOT);
+
+  const std::string& noun = tokens.size() > 0 ? tokens[0] : "";
+  const std::string& verb = tokens.size() > 1 ? tokens[1] : "";
+  size_t nameLen = std::min<size_t>(2, tokens.size());
+
+  auto i = m_commands.find({noun, verb});
+  if (i == m_commands.end()) {
+    if (verb.empty()) {
+      i = m_commands.find({noun, "list"});
+    }
+    else {
+      // help, exit, quit, legacy nfdc commands
+      i = m_commands.find({noun, ""});
+    }
+    nameLen = std::min<size_t>(1, tokens.size());
+  }
+  if (i == m_commands.end() || (i->second->modes & static_cast<AvailableIn>(mode)) == 0) {
+    BOOST_THROW_EXCEPTION(Error("no such command: " + noun + " " + verb));
+  }
+
+  const CommandDefinition& def = i->second->def;
+  NDN_LOG_TRACE("found command " << def.getNoun() << " " << def.getVerb());
+
+  return std::make_tuple(&i->second->execute, def.parse(tokens, nameLen));
+}
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfdc/command-parser.hpp b/tools/nfdc/command-parser.hpp
new file mode 100644
index 0000000..7a1dbda
--- /dev/null
+++ b/tools/nfdc/command-parser.hpp
@@ -0,0 +1,114 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFDC_COMMAND_PARSER_HPP
+#define NFD_TOOLS_NFDC_COMMAND_PARSER_HPP
+
+#include "command-definition.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+/** \brief indicates which modes is a command allowed
+ */
+enum AvailableIn : uint8_t {
+  AVAILABLE_IN_NONE     = 0,
+  AVAILABLE_IN_ONE_SHOT = 1 << 0,
+  AVAILABLE_IN_BATCH    = 1 << 1,
+  AVAILABLE_IN_ALL      = 0xff
+};
+
+std::ostream&
+operator<<(std::ostream& os, AvailableIn modes);
+
+/** \brief indicates which mode is the parser operated in
+ */
+enum class ParseMode {
+  ONE_SHOT = AVAILABLE_IN_ONE_SHOT, ///< one-shot mode
+  BATCH    = AVAILABLE_IN_BATCH     ///< batch mode
+};
+
+std::ostream&
+operator<<(std::ostream& os, ParseMode mode);
+
+/** \brief parses a command
+ */
+class CommandParser : noncopyable
+{
+public:
+  class Error : public std::invalid_argument
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::invalid_argument(what)
+    {
+    }
+  };
+
+  typedef std::function<void(const CommandArguments&)> Execute;
+
+  /** \brief add an available command
+   *  \param def command semantics definition
+   *  \param execute a function to execute the command
+   *  \param modes parse modes this command should be available in, must not be AVAILABLE_IN_NONE
+   */
+  CommandParser&
+  addCommand(const CommandDefinition& def, const Execute& execute, AvailableIn modes = AVAILABLE_IN_ALL);
+
+  /** \brief add an alias "noun verb2" to existing command "noun verb"
+   *  \throw std::out_of_range "noun verb" does not exist
+   */
+  CommandParser&
+  addAlias(const std::string& noun, const std::string& verb, const std::string& verb2);
+
+  /** \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 CommandDefinition::Error command arguments are invalid
+   */
+  std::tuple<Execute*, CommandArguments>
+  parse(const std::vector<std::string>& tokens, ParseMode mode) const;
+
+private:
+  typedef std::pair<std::string, std::string> CommandName;
+
+  struct Command
+  {
+    CommandDefinition def;
+    Execute execute;
+    AvailableIn modes;
+  };
+
+  std::map<CommandName, shared_ptr<Command>> m_commands;
+};
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFDC_COMMAND_PARSER_HPP
diff --git a/tools/nfdc/main.cpp b/tools/nfdc/main.cpp
index 5bc2596..85fe1e9 100644
--- a/tools/nfdc/main.cpp
+++ b/tools/nfdc/main.cpp
@@ -23,8 +23,8 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include "available-commands.hpp"
 #include "legacy-nfdc.hpp"
-#include "status-main.hpp"
 #include "core/version.hpp"
 
 namespace nfd {
@@ -34,29 +34,35 @@
 static int
 main(int argc, char** argv)
 {
-  if (argc < 2) {
+  std::vector<std::string> args(argv + 1, argv + argc);
+
+  if (args.empty() || args[0] == "-h") {
     legacyNfdcUsage();
     return 0;
   }
 
-  std::string subcommand(argv[1]);
-  std::vector<std::string> args(argv + 2, argv + argc);
-
-  if (subcommand == "-h") {
-    legacyNfdcUsage();
-    return 0;
-  }
-
-  if (subcommand == "-V") {
+  if (args[0] == "-V") {
     std::cout << NFD_VERSION_BUILD_STRING << std::endl;
     return 0;
   }
 
-  if (subcommand == "legacy-nfd-status") {
-    return statusMain(args);
+  CommandParser parser;
+  registerCommands(parser);
+  CommandParser::Execute* execute = nullptr;
+  CommandArguments ca;
+  try {
+    std::tie(execute, ca) = parser.parse(args, ParseMode::ONE_SHOT);
+  }
+  catch (const std::invalid_argument& e) {
+    std::cerr << e.what() << std::endl;
+    return 1;
   }
 
-  return legacyNfdcMain(subcommand, args);
+  ///\todo create Face and KeyChain here
+  (*execute)(ca);
+  ///\todo call processEvents here
+  ///\todo return proper exit code here, instead of using exit() in subcommand
+  return 0;
 }
 
 } // namespace nfdc