allow moving up a node with cd

Change-Id: Id1419fddda4aa2d2c16b5775a43c8df58db98c73
diff --git a/src/CTree.cpp b/src/CTree.cpp
index 5d0cf7e..69afb58 100644
--- a/src/CTree.cpp
+++ b/src/CTree.cpp
@@ -13,6 +13,10 @@
 InvalidNodeException::~InvalidNodeException() = default;
 
 struct nodeToString : public boost::static_visitor<std::string> {
+    std::string operator()(const nodeup_&) const
+    {
+        return "..";
+    }
     template <class T>
     std::string operator()(const T& node) const
     {
@@ -94,7 +98,6 @@
     m_nodes.emplace(name, std::unordered_map<std::string, NodeType>());
 }
 
-
 void CTree::changeNode(const path_& name)
 {
     if (name.m_nodes.empty()) {
@@ -102,7 +105,13 @@
         return;
     }
     for (const auto& it : name.m_nodes) {
-        m_curDir = joinPaths(m_curDir, boost::apply_visitor(nodeToString(), it));
+        const std::string node = boost::apply_visitor(nodeToString(), it);
+        if (node == "..") {
+            m_curDir = stripLastNodeFromPath(m_curDir);
+        } else {
+            m_curDir = joinPaths(m_curDir, boost::apply_visitor(nodeToString(), it));
+        }
+
     }
 }
 std::string CTree::currentNode() const
diff --git a/src/ast.hpp b/src/ast.hpp
index 8e71476..0c257d0 100644
--- a/src/ast.hpp
+++ b/src/ast.hpp
@@ -37,6 +37,14 @@
 
 struct parser_context_tag;
 
+
+struct nodeup_ {
+    bool operator==(const nodeup_&) const
+    {
+       return true;
+    }
+};
+
 struct container_ {
     container_() = default;
     container_(const std::string& name);
@@ -64,7 +72,7 @@
 
 struct path_ {
     bool operator==(const path_& b) const;
-    std::vector<boost::variant<container_, listElement_>> m_nodes;
+    std::vector<boost::variant<container_, listElement_, nodeup_>> m_nodes;
 };
 
 
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 746116e..9ab4738 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -11,7 +11,6 @@
 #include "CTree.hpp"
 #include "parser_context.hpp"
 
-
 struct keyValue_class {
     template <typename T, typename Iterator, typename Context>
     void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
@@ -99,6 +98,22 @@
     }
 };
 
+
+struct nodeup_class {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const&, Iterator const&, T&, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+
+        if (!parserContext.m_curPath.empty()) {
+            parserContext.m_curPath = stripLastNodeFromPath(parserContext.m_curPath);
+        } else {
+            _pass(context) = false;
+        }
+
+    }
+};
+
 struct container_class {
     template <typename T, typename Iterator, typename Context>
     void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
diff --git a/src/grammars.hpp b/src/grammars.hpp
index c6f3ab5..ef6b60f 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -11,11 +11,13 @@
 #include "ast.hpp"
 #include "ast_handlers.hpp"
 
+
 x3::rule<keyValue_class, keyValue_> const keyValue = "keyValue";
 x3::rule<identifier_class, std::string> const identifier = "identifier";
 x3::rule<listPrefix_class, std::string> const listPrefix = "listPrefix";
 x3::rule<listSuffix_class, std::vector<keyValue_>> const listSuffix = "listSuffix";
 x3::rule<listElement_class, listElement_> const listElement = "listElement";
+x3::rule<nodeup_class, nodeup_> const nodeup = "nodeup";
 x3::rule<container_class, container_> const container = "container";
 x3::rule<path_class, path_> const path = "path";
 x3::rule<cd_class, cd_> const cd = "cd";
@@ -39,20 +41,24 @@
 auto const listElement_def =
         listPrefix > listSuffix;
 
+auto const nodeup_def =
+        lit("..") > x3::attr(nodeup_());
+
 auto const container_def =
         identifier;
 
 auto const path_def =
-        (container | listElement) % '/';
+        (container | listElement | nodeup) % '/';
 
 auto const cd_def =
-      lit("cd") > x3::omit[x3::no_skip[space]] > path >> x3::eoi;
+        lit("cd") > x3::omit[x3::no_skip[space]] > path >> x3::eoi;
 
 BOOST_SPIRIT_DEFINE(keyValue)
 BOOST_SPIRIT_DEFINE(identifier)
 BOOST_SPIRIT_DEFINE(listPrefix)
 BOOST_SPIRIT_DEFINE(listSuffix)
 BOOST_SPIRIT_DEFINE(listElement)
+BOOST_SPIRIT_DEFINE(nodeup)
 BOOST_SPIRIT_DEFINE(container)
 BOOST_SPIRIT_DEFINE(path)
 BOOST_SPIRIT_DEFINE(cd)
diff --git a/src/utils.cpp b/src/utils.cpp
index 38bfe7d..77466c4 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -14,3 +14,14 @@
     else
         return prefix + '/' + suffix;
 }
+
+std::string stripLastNodeFromPath(const std::string& path)
+{
+    std::string res = path;
+    auto pos = res.find_last_of('/');
+    if (pos == res.npos)
+        res.clear();
+    else
+        res.erase(pos);
+    return res;
+}
diff --git a/src/utils.hpp b/src/utils.hpp
index 54c455a..4f10102 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -8,3 +8,4 @@
 #include <string>
 
 std::string joinPaths(const std::string& prefix, const std::string& suffix);
+std::string stripLastNodeFromPath(const std::string& path);
diff --git a/tests/cd.cpp b/tests/cd.cpp
index d6079fb..8ab4c40 100644
--- a/tests/cd.cpp
+++ b/tests/cd.cpp
@@ -101,6 +101,33 @@
             }
         }
 
+        SECTION("moving up")
+        {
+            SECTION("a/..")
+            {
+                input = "cd a/..";
+                expected.m_path.m_nodes.push_back(container_("a"));
+                expected.m_path.m_nodes.push_back(nodeup_());
+            }
+
+            SECTION("a/../a")
+            {
+                input = "cd a/../a";
+                expected.m_path.m_nodes.push_back(container_("a"));
+                expected.m_path.m_nodes.push_back(nodeup_());
+                expected.m_path.m_nodes.push_back(container_("a"));
+            }
+
+            SECTION("a/../a/a2")
+            {
+                input = "cd a/../a/a2";
+                expected.m_path.m_nodes.push_back(container_("a"));
+                expected.m_path.m_nodes.push_back(nodeup_());
+                expected.m_path.m_nodes.push_back(container_("a"));
+                expected.m_path.m_nodes.push_back(container_("a2"));
+            }
+        }
+
         cd_ command = parser.parseCommand(input, errorStream);
         REQUIRE(command == expected);
     }