add test for basic cd parsing

Change-Id: If35d62e323d48db11dc4128fb5c2898ef4ef63a6
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ad8c3c2..3ac9278 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,15 +47,21 @@
 find_package(spdlog REQUIRED)
 find_package(Boost REQUIRED)
 
-set(netconf-cli_SRCS
-    src/main.cpp
+set(parser_SRCS
     src/CTree.cpp
     src/CParser.cpp
     src/ast.cpp
     )
 
+add_library(parser STATIC ${parser_SRCS})
+target_link_libraries(parser Boost::boost)
+
+set(netconf-cli_SRCS
+    src/main.cpp
+    )
+
 add_executable(netconf-cli ${netconf-cli_SRCS})
-target_link_libraries(netconf-cli docopt Boost::boost)
+target_link_libraries(netconf-cli docopt parser)
 add_dependencies(netconf-cli target-NETCONF_CLI_VERSION)
 target_include_directories(netconf-cli PRIVATE ${PROJECT_BINARY_DIR})
 
@@ -85,7 +91,7 @@
     macro(cli_test fname)
         set(test_${fname}_SOURCES tests/${fname}.cpp)
         add_executable(test_${fname} ${test_${fname}_SOURCES})
-        target_link_libraries(test_${fname} TestCatchIntegration)
+        target_link_libraries(test_${fname} TestCatchIntegration parser)
         if(NOT CMAKE_CROSSCOMPILING)
             add_test(test_${fname} test_${fname})
         endif()
@@ -93,6 +99,8 @@
         target_link_libraries(test_${fname} TestCatchIntegration)
     endmacro()
     cli_test(dummy)
+    cli_test(cd)
+
 endif()
 
 if(WITH_DOCS)
diff --git a/src/CParser.cpp b/src/CParser.cpp
index 75af7c0..618a721 100644
--- a/src/CParser.cpp
+++ b/src/CParser.cpp
@@ -1,3 +1,10 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
 #include "CParser.hpp"
 
 TooManyArgumentsException::~TooManyArgumentsException() = default;
@@ -8,16 +15,19 @@
 }
 
 
-Cmd CParser::parseInput(const std::string& line)
+cd_ CParser::parseCommand(const std::string& line)
 {
-    Cmd args;
+    cd_ parsedCommand;
+    ParserContext ctx(m_tree);
     auto it = line.begin();
-    //bool result = x3::phrase_parse(it, line.end(), command, space, args);
-    //std::cout << "success: " << result << std::endl;
+
+    auto grammar = x3::with<parser_context_tag>(ctx)[cd];
+
+    x3::phrase_parse(it, line.end(), grammar, space, parsedCommand);
     if (it != line.end()) {
         throw TooManyArgumentsException(std::string(it, line.end()));
     }
 
 
-    return args;
+    return parsedCommand;
 }
diff --git a/src/CParser.hpp b/src/CParser.hpp
index c3333af..96d4979 100644
--- a/src/CParser.hpp
+++ b/src/CParser.hpp
@@ -1,13 +1,15 @@
 /*
  * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
  *
  * Written by Václav Kubernát <kubervac@fit.cvut.cz>
  *
- */
+*/
 
 #pragma once
 #include <boost/spirit/home/x3.hpp>
 #include "CTree.hpp"
+#include "ast.hpp"
 namespace x3 = boost::spirit::x3;
 namespace ascii = boost::spirit::x3::ascii;
 using Cmd = std::vector<std::string>;
@@ -27,7 +29,7 @@
 class CParser {
 public:
     CParser(const CTree& tree);
-    Cmd parseInput(const std::string& line);
+    cd_ parseCommand(const std::string& line);
 
 private:
     const CTree& m_tree;
diff --git a/src/CTree.cpp b/src/CTree.cpp
index 994515a..7d89cf3 100644
--- a/src/CTree.cpp
+++ b/src/CTree.cpp
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
  *
  * Written by Václav Kubernát <kubervac@fit.cvut.cz>
  *
@@ -17,21 +18,47 @@
         return prefix + '/' + suffix;
 }
 
-const std::unordered_set<std::string>& CTree::children(const std::string& node) const
+bool TreeNode::operator<(const TreeNode& b) const
+{
+    return this->m_name < b.m_name;
+}
+
+CTree::CTree()
+{
+    m_nodes.emplace("", std::unordered_map<std::string, NODE_TYPE>());
+}
+
+const std::unordered_map<std::string, NODE_TYPE>& CTree::children(const std::string& node) const
 {
     return m_nodes.at(node);
 }
 
-bool CTree::checkNode(const std::string& location, const std::string& node) const
+bool CTree::nodeExists(const std::string& location, const std::string& node) const
 {
-    if (node == ".." || node.empty())
+    if (node.empty())
         return true;
-    const auto& childrenRef = children(location); //first, get a reference to all children
-    if (childrenRef.find(node) == childrenRef.end()) { //find the desired node, if it isn't present throw an exception
-        throw InvalidNodeException(node);
-    }
-    return true;
+    const auto& childrenRef = children(location);
+
+    return childrenRef.find(node) != childrenRef.end();
 }
+
+bool CTree::isContainer(const std::string& location, const std::string& node) const
+{
+    if (!nodeExists(location, node))
+        return false;
+    return children(location).at(node) == TYPE_CONTAINER;
+}
+
+void CTree::addContainer(const std::string& location, const std::string& name)
+{
+    m_nodes.at(location).emplace(name, TYPE_CONTAINER);
+
+    //create a new set of children for the new node
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NODE_TYPE>());
+}
+
+
 void CTree::changeNode(const std::string& node)
 {
     if (node.empty()) {
@@ -44,21 +71,3 @@
 {
     return m_curDir;
 }
-
-void CTree::addNode(const std::string& location, const std::string& name)
-{
-    m_nodes.at(location).insert(name);
-
-    //create a new set of children for the new node
-    m_nodes.emplace(joinPaths(location, name), std::unordered_set<std::string>());
-}
-void CTree::initDefault()
-{
-    m_nodes.emplace("", std::unordered_set<std::string>());
-    addNode("", "aaa");
-    addNode("", "bbb");
-    addNode("", "ccc");
-    addNode("aaa", "aaabbb");
-    addNode("aaa", "aaauuu");
-    addNode("bbb", "bbbuuu");
-}
diff --git a/src/CTree.hpp b/src/CTree.hpp
index 88bccae..fa99df6 100644
--- a/src/CTree.hpp
+++ b/src/CTree.hpp
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
  *
  * Written by Václav Kubernát <kubervac@fit.cvut.cz>
  *
@@ -9,13 +10,23 @@
 
 #include <stdexcept>
 #include <unordered_map>
-#include <unordered_set>
+
+enum NODE_TYPE {
+    TYPE_CONTAINER,
+    TYPE_LIST,
+    TYPE_LIST_ELEMENT
+};
+
+struct TreeNode {
+    bool operator<(const TreeNode& b) const;
+    std::string m_name;
+    NODE_TYPE m_type;
+};
 
 class InvalidNodeException : public std::invalid_argument {
 public:
     using std::invalid_argument::invalid_argument;
     ~InvalidNodeException() override;
-
 };
 
 /*! \class CTree
@@ -26,15 +37,17 @@
  *         */
 class CTree {
 public:
-    bool checkNode(const std::string& location, const std::string& node) const;
+    CTree();
+    bool nodeExists(const std::string& location, const std::string& node) const;
+
+    bool isContainer(const std::string& location, const std::string& node) const;
+    void addContainer(const std::string& location, const std::string& node);
     void changeNode(const std::string& node);
-    void initDefault();
     std::string currentNode() const;
 
 private:
-    void addNode(const std::string& location, const std::string& node);
-    const std::unordered_set<std::string>& children(const std::string& node) const;
+    const std::unordered_map<std::string, NODE_TYPE>& children(const std::string& node) const;
 
-    std::unordered_map<std::string, std::unordered_set<std::string>> m_nodes;
+    std::unordered_map<std::string, std::unordered_map<std::string, NODE_TYPE>> m_nodes;
     std::string m_curDir;
 };
diff --git a/src/ast.cpp b/src/ast.cpp
index d9964ea..762c7cc 100644
--- a/src/ast.cpp
+++ b/src/ast.cpp
@@ -1,35 +1,36 @@
 /*
  * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
  *
  * Written by Václav Kubernát <kubervac@fit.cvut.cz>
  *
 */
 #include "ast.hpp"
-
-
-template <typename T, typename Iterator, typename Context>
-inline void container_class::on_success(Iterator const& first, Iterator const& last
-    , T& ast, Context const& context)
+container_::container_(const std::string& name)
+    : m_name(name)
 {
-    ast.m_name = ast.m_first + ast.m_name;
-    //std::cout <<"parsed " << ast.m_name << "(container)\n";
 }
 
-template <typename T, typename Iterator, typename Context>
-inline void path_class::on_success(Iterator const& first, Iterator const& last
-    , T& ast, Context const& context)
+bool container_::operator==(const container_& b) const
 {
-    //std::cout << "parsed path:" << std::endl;
-    //for (auto it : ast.m_nodes)
-    //std::cout << it.m_name << std::endl;
+    return this->m_name == b.m_name;
 }
 
-template <typename T, typename Iterator, typename Context>
-inline void cd_class::on_success(Iterator const& first, Iterator const& last
-    , T& ast, Context const& context)
+bool path_::operator==(const path_& b) const
 {
-    //std::cout << "parsed cd! final path:" << std::endl;
-    //for (auto it : ast.m_path.m_nodes)
-    //std::cout << it.m_name << std::endl;
+    if (this->m_nodes.size() != b.m_nodes.size())
+        return false;
+    return this->m_nodes == b.m_nodes;
+}
 
+bool cd_::operator==(const cd_& b) const
+{
+    return this->m_path == b.m_path;
+}
+
+
+ParserContext::ParserContext(const CTree& tree)
+    : m_tree(tree)
+{
+    m_curPath = m_tree.currentNode();
 }
diff --git a/src/ast.hpp b/src/ast.hpp
index 6c60dc9..f55ab39 100644
--- a/src/ast.hpp
+++ b/src/ast.hpp
@@ -1,15 +1,18 @@
 /*
  * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
  *
  * Written by Václav Kubernát <kubervac@fit.cvut.cz>
  *
 */
 #pragma once
 #include <boost/spirit/home/x3.hpp>
-#include <vector>
 #include <boost/spirit/home/x3/support/ast/position_tagged.hpp>
+
 #include <boost/fusion/adapted/struct/adapt_struct.hpp>
 #include <boost/fusion/include/adapt_struct.hpp>
+#include <vector>
+
 #include "CTree.hpp"
 namespace x3 = boost::spirit::x3;
 namespace ascii = boost::spirit::x3::ascii;
@@ -22,76 +25,88 @@
 using x3::lexeme;
 using ascii::space;
 
-using nodeString = std::string;
-
-struct ParserContext
-{
+struct ParserContext {
     ParserContext(const CTree& tree);
     const CTree& m_tree;
-    std::string m_currentContext;
+    std::string m_curPath;
 };
 
 struct parser_context_tag;
 
-struct container_
-{
-    char m_first;
+struct container_ {
+    container_() {}
+    container_(const std::string& name);
+
+    bool operator==(const container_& b) const;
+
+    char m_first = ' ';
     std::string m_name;
 };
 
 BOOST_FUSION_ADAPT_STRUCT(container_, m_first, m_name)
 
-struct path_
-{
+struct path_ {
+    bool operator==(const path_& b) const;
     std::vector<container_> m_nodes;
 };
 
 BOOST_FUSION_ADAPT_STRUCT(path_, m_nodes)
 
-struct cd_
-{
+struct cd_ {
+    bool operator==(const cd_& b) const;
     path_ m_path;
 };
 
 BOOST_FUSION_ADAPT_STRUCT(cd_, m_path)
 
 
-struct container_class
-{
+struct container_class {
     template <typename T, typename Iterator, typename Context>
-        inline void on_success(Iterator const& first, Iterator const& last
-                , T& ast, Context const& context);
+    void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
+    {
+        ast.m_name = ast.m_first + ast.m_name;
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        const auto& tree = parserContext.m_tree;
+
+        if (tree.isContainer(parserContext.m_curPath, ast.m_name)) {
+            if (!parserContext.m_curPath.empty()) {
+                parserContext.m_curPath += '/';
+            }
+            parserContext.m_curPath += ast.m_name;
+        } else {
+            throw InvalidNodeException("No container with the name \"" + ast.m_name + "\" in \"" + parserContext.m_curPath + "\"");
+        }
+    }
 };
 
-struct path_class
-{
+struct path_class {
     template <typename T, typename Iterator, typename Context>
-    inline void on_success(Iterator const& first, Iterator const& last
-    , T& ast, Context const& context);
+    void on_success(Iterator const&, Iterator const&, T&, Context const&)
+    {
+    }
 };
 
-struct cd_class
-{
+struct cd_class {
     template <typename T, typename Iterator, typename Context>
-    inline void on_success(Iterator const& first, Iterator const& last
-    , T& ast, Context const& context);
-
+    void on_success(Iterator const&, Iterator const&, T&, Context const&)
+    {
+    }
 };
 
 
-typedef x3::rule<container_class, container_> container_type;
-typedef x3::rule<path_class, path_> path_type;
-typedef x3::rule<cd_class, cd_> cd_type;
+x3::rule<container_class, container_> const container = "container";
+x3::rule<path_class, path_> const path = "path";
+x3::rule<cd_class, cd_> const cd = "cd";
 
-container_type const container = "container";
-path_type const path = "path";
-cd_type const cd = "cd";
 
+auto const identifier =
+    lexeme[
+        ((alpha | char_("_")) >> *(alnum | char_("_") | char_("-") | char_(".")))
+    ];
 
 auto const container_def =
-    lexeme[
-        ((alpha | x3::string("_")) >> *(alnum | x3::string("_") | x3::string("-") | x3::string(".")))
-    ];
+    identifier;
+
 auto const path_def =
     container % '/';
 
diff --git a/tests/cd.cpp b/tests/cd.cpp
new file mode 100644
index 0000000..39487a3
--- /dev/null
+++ b/tests/cd.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include "trompeloeil_catch.h"
+#include "CParser.hpp"
+#include "CTree.hpp"
+#include "ast.hpp"
+
+TEST_CASE("cd")
+{
+    CTree tree;
+    tree.addContainer("", "a");
+    tree.addContainer("", "b");
+    tree.addContainer("a", "a2");
+    tree.addContainer("b", "b2");
+    tree.addContainer("a/a2", "a3");
+    tree.addContainer("b/b2", "b3");
+
+    CParser parser(tree);
+    cd_ expected;
+
+    std::string input;
+
+    SECTION("basic cd parsing")
+    {
+        SECTION("a")
+        {
+            input = "cd a";
+            expected.m_path.m_nodes.push_back(container_("a"));
+        }
+
+        SECTION("b")
+        {
+            input = "cd b";
+            expected.m_path.m_nodes.push_back(container_("b"));
+        }
+
+
+        SECTION("a/a2")
+        {
+            input = "cd a/a2";
+            expected.m_path.m_nodes.push_back(container_("a"));
+            expected.m_path.m_nodes.push_back(container_("a2"));
+        }
+
+        SECTION("b/b2")
+        {
+            input = "cd b/b2";
+            expected.m_path.m_nodes.push_back(container_("b"));
+            expected.m_path.m_nodes.push_back(container_("b2"));
+        }
+
+        cd_ command = parser.parseCommand(input);
+        REQUIRE(command == expected);
+    }
+
+    SECTION("InvalidNodeException")
+    {
+        SECTION("x")
+        {
+            input = "cd x";
+        }
+
+        SECTION("a/x")
+        {
+            input = "cd a/x";
+        }
+        REQUIRE_THROWS_AS(parser.parseCommand(input), InvalidNodeException);
+    }
+}