add presence containers

Change-Id: Ic8e33d68e496deae9dfe4c3e5ebcecbd45ee31b2
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3819930..dd82c6e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -109,6 +109,7 @@
         target_link_libraries(test_${fname} TestCatchIntegration)
     endmacro()
     cli_test(cd)
+    cli_test(presence_containers)
 
 endif()
 
diff --git a/src/ast.cpp b/src/ast.cpp
index 9345192..0e40b9c 100644
--- a/src/ast.cpp
+++ b/src/ast.cpp
@@ -40,3 +40,13 @@
 {
     return this->m_path == b.m_path;
 }
+
+bool create_::operator==(const create_& b) const
+{
+    return this->m_path == b.m_path;
+}
+
+bool delete_::operator==(const delete_& b) const
+{
+    return this->m_path == b.m_path;
+}
diff --git a/src/ast.hpp b/src/ast.hpp
index 90e007a..417c3d7 100644
--- a/src/ast.hpp
+++ b/src/ast.hpp
@@ -81,7 +81,21 @@
     path_ m_path;
 };
 
+struct create_ : x3::position_tagged {
+    bool operator==(const create_& b) const;
+    path_ m_path;
+};
+
+struct delete_ : x3::position_tagged {
+    bool operator==(const delete_& b) const;
+    path_ m_path;
+};
+
+using command_ = boost::variant<cd_, create_, delete_>;
+
 BOOST_FUSION_ADAPT_STRUCT(container_, m_name)
 BOOST_FUSION_ADAPT_STRUCT(listElement_, m_name, m_keys)
 BOOST_FUSION_ADAPT_STRUCT(path_, m_nodes)
 BOOST_FUSION_ADAPT_STRUCT(cd_, m_path)
+BOOST_FUSION_ADAPT_STRUCT(create_, m_path)
+BOOST_FUSION_ADAPT_STRUCT(delete_, m_path)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 0c7f1c3..1feeeab 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -110,7 +110,6 @@
         } else {
             _pass(context) = false;
         }
-
     }
 };
 
@@ -156,3 +155,61 @@
         return x3::error_handler_result::fail;
     }
 };
+
+struct presenceContainerPathHandler {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        const auto& schema = parserContext.m_schema;
+        try {
+            container_ cont = boost::get<container_>(ast.m_path.m_nodes.back());
+            path_ location{decltype(path_::m_nodes)(parserContext.m_curPath.m_nodes.begin(),
+                                                    parserContext.m_curPath.m_nodes.end() - 1)};
+
+            if (!schema.isPresenceContainer(location, cont.m_name)) {
+                _pass(context) = false;
+                return;
+            }
+        } catch (boost::bad_get&) {
+            _pass(context) = false;
+            return;
+        }
+    }
+
+    template <typename Iterator, typename Exception, typename Context>
+    x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const& x, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        auto& error_handler = x3::get<x3::error_handler_tag>(context).get();
+        std::string message = "This isn't a path to a presence container.";
+        if (parserContext.m_errorHandled) // someone already handled our error
+            return x3::error_handler_result::fail;
+
+        parserContext.m_errorHandled = true;
+        error_handler(x.where(), message);
+        return x3::error_handler_result::fail;
+    }
+};
+
+struct create_class : public presenceContainerPathHandler {
+};
+
+struct delete_class : public presenceContainerPathHandler {
+};
+
+struct command_class {
+    template <typename Iterator, typename Exception, typename Context>
+    x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const& x, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        auto& error_handler = x3::get<x3::error_handler_tag>(context).get();
+        std::string message = "Couldn't parse command.";
+        if (parserContext.m_errorHandled) // someone already handled our error
+            return x3::error_handler_result::fail;
+
+        parserContext.m_errorHandled = true;
+        error_handler(x.where(), message);
+        return x3::error_handler_result::fail;
+    }
+};
diff --git a/src/grammars.hpp b/src/grammars.hpp
index ef6b60f..30f69f1 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -21,6 +21,9 @@
 x3::rule<container_class, container_> const container = "container";
 x3::rule<path_class, path_> const path = "path";
 x3::rule<cd_class, cd_> const cd = "cd";
+x3::rule<create_class, create_> const create = "create";
+x3::rule<delete_class, delete_> const delete_rule = "delete_rule";
+x3::rule<command_class, command_> const command = "command";
 
 
 auto const keyValue_def =
@@ -53,6 +56,15 @@
 auto const cd_def =
         lit("cd") > x3::omit[x3::no_skip[space]] > path >> x3::eoi;
 
+auto const create_def =
+        lit("create") > x3::omit[x3::no_skip[space]] > path >> x3::eoi;
+
+auto const delete_rule_def =
+        lit("delete") > x3::omit[x3::no_skip[space]] > path >> x3::eoi;
+
+auto const command_def =
+        cd | create | delete_rule;
+
 BOOST_SPIRIT_DEFINE(keyValue)
 BOOST_SPIRIT_DEFINE(identifier)
 BOOST_SPIRIT_DEFINE(listPrefix)
@@ -62,3 +74,6 @@
 BOOST_SPIRIT_DEFINE(container)
 BOOST_SPIRIT_DEFINE(path)
 BOOST_SPIRIT_DEFINE(cd)
+BOOST_SPIRIT_DEFINE(create)
+BOOST_SPIRIT_DEFINE(delete_rule)
+BOOST_SPIRIT_DEFINE(command)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index ce01c66..5c132d1 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -6,6 +6,7 @@
  *
 */
 
+#include <iostream>
 #include "interpreter.hpp"
 
 void Interpreter::operator()(const cd_& cd) const
@@ -13,7 +14,17 @@
     m_parser.changeNode(cd.m_path);
 }
 
-Interpreter::Interpreter(Parser& parser)
+void Interpreter::operator()(const create_& create) const
+{
+    std::cout << "Presence container " << boost::get<container_>(create.m_path.m_nodes.back()).m_name << " created." << std::endl;
+}
+
+void Interpreter::operator()(const delete_& delet) const
+{
+    std::cout << "Presence container " << boost::get<container_>(delet.m_path.m_nodes.back()).m_name << " deleted." << std::endl;
+}
+
+Interpreter::Interpreter(Parser& parser, Schema&)
     : m_parser(parser)
 {
 }
diff --git a/src/interpreter.hpp b/src/interpreter.hpp
index 72c839e..a4a5cc0 100644
--- a/src/interpreter.hpp
+++ b/src/interpreter.hpp
@@ -12,9 +12,12 @@
 #include "parser.hpp"
 
 struct Interpreter : boost::static_visitor<void> {
-    Interpreter(Parser &m_parser);
+    Interpreter(Parser& parser, Schema&);
 
     void operator()(const cd_&) const;
+    void operator()(const create_&) const;
+    void operator()(const delete_&) const;
+
 private:
     Parser& m_parser;
 };
diff --git a/src/main.cpp b/src/main.cpp
index 98c68ca..4fb9d8d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -36,7 +36,6 @@
 using x3::lexeme;
 using x3::lit;
 
-using command = boost::variant<cd_>;
 
 int main(int argc, char* argv[])
 {
@@ -48,14 +47,14 @@
     std::cout << "Welcome to netconf-cli" << std::endl;
 
     Schema schema;
-    schema.addContainer("", "a");
+    schema.addContainer("", "a", yang::ContainerTraits::Presence);
     schema.addContainer("", "b");
     schema.addContainer("a", "a2");
-    schema.addContainer("b", "b2");
-    schema.addContainer("a/a2", "a3");
+    schema.addContainer("b", "b2", yang::ContainerTraits::Presence);
+    schema.addContainer("a/a2", "a3", yang::ContainerTraits::Presence);
     schema.addContainer("b/b2", "b3");
     schema.addList("", "list", {"number"});
-    schema.addContainer("list", "contInList");
+    schema.addContainer("list", "contInList", yang::ContainerTraits::Presence);
     schema.addList("", "twoKeyList", {"number", "name"});
     Parser parser(schema);
 
@@ -67,8 +66,8 @@
             break;
 
         try {
-            command cmd = parser.parseCommand(input, std::cout);
-            boost::apply_visitor(Interpreter(parser), cmd);
+            command_ cmd = parser.parseCommand(input, std::cout);
+            boost::apply_visitor(Interpreter(parser, schema), cmd);
         } catch (InvalidCommandException& ex) {
             std::cerr << ex.what() << std::endl;
         }
diff --git a/src/parser.cpp b/src/parser.cpp
index b8ac30d..f26b164 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -41,9 +41,9 @@
     }
 };
 
-cd_ Parser::parseCommand(const std::string& line, std::ostream& errorStream)
+command_ Parser::parseCommand(const std::string& line, std::ostream& errorStream)
 {
-    cd_ parsedCommand;
+    command_ parsedCommand;
     ParserContext ctx(m_schema, m_curDir);
     auto it = line.begin();
 
@@ -51,7 +51,7 @@
 
     auto grammar =
             x3::with<parser_context_tag>(ctx)[
-            x3::with<x3::error_handler_tag>(std::ref(errorHandler))[cd]
+            x3::with<x3::error_handler_tag>(std::ref(errorHandler))[command]
     ];
     bool result = x3::phrase_parse(it, line.end(), grammar, space, parsedCommand);
 
@@ -81,4 +81,3 @@
 
     return res;
 }
-
diff --git a/src/parser.hpp b/src/parser.hpp
index feb7911..0a253c1 100644
--- a/src/parser.hpp
+++ b/src/parser.hpp
@@ -9,15 +9,7 @@
 #pragma once
 #include <boost/spirit/home/x3.hpp>
 #include "grammars.hpp"
-namespace x3 = boost::spirit::x3;
-namespace ascii = boost::spirit::x3::ascii;
-using Cmd = std::vector<std::string>;
-using ascii::space;
-using x3::_attr;
-using x3::alpha;
-using x3::char_;
-using x3::lexeme;
-using x3::lit;
+
 
 class InvalidCommandException : public std::invalid_argument {
 public:
@@ -35,7 +27,7 @@
 class Parser {
 public:
     Parser(const Schema& schema);
-    cd_ parseCommand(const std::string& line, std::ostream& errorStream);
+    command_ parseCommand(const std::string& line, std::ostream& errorStream);
     void changeNode(const path_& name);
     std::string currentNode() const;
 
diff --git a/src/schema.cpp b/src/schema.cpp
index 1760b5d..fa72f81 100644
--- a/src/schema.cpp
+++ b/src/schema.cpp
@@ -6,7 +6,6 @@
  *
 */
 
-#include <iostream>
 #include "schema.hpp"
 #include "utils.hpp"
 
@@ -60,9 +59,9 @@
     return children(locationString).at(name).type() == typeid(yang::container);
 }
 
-void Schema::addContainer(const std::string& location, const std::string& name)
+void Schema::addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence)
 {
-    m_nodes.at(location).emplace(name, yang::container{});
+    m_nodes.at(location).emplace(name, yang::container{isPresence});
 
     //create a new set of children for the new node
     std::string key = joinPaths(location, name);
@@ -108,3 +107,11 @@
 
     m_nodes.emplace(name, std::unordered_map<std::string, NodeType>());
 }
+
+bool Schema::isPresenceContainer(const path_& location, const std::string& name) const
+{
+    if (!isContainer(location, name))
+        return false;
+    std::string locationString = pathToString(location);
+    return boost::get<yang::container>(children(locationString).at(name)).m_presence == yang::ContainerTraits::Presence;
+}
diff --git a/src/schema.hpp b/src/schema.hpp
index 7d6e486..9c7114b 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -15,7 +15,12 @@
 #include "ast.hpp"
 
 namespace yang {
+enum class ContainerTraits {
+    Presence,
+    None,
+};
 struct container {
+    yang::ContainerTraits m_presence;
 };
 struct list {
     std::set<std::string> m_keys;
@@ -23,7 +28,6 @@
 }
 
 
-
 using NodeType = boost::variant<yang::container, yang::list>;
 
 
@@ -42,11 +46,12 @@
     bool nodeExists(const std::string& location, const std::string& name) const;
 
     bool isContainer(const path_& location, const std::string& name) const;
-    void addContainer(const std::string& location, const std::string& name);
+    void addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence = yang::ContainerTraits::None);
     const std::set<std::string>& listKeys(const path_& location, const std::string& name) const;
     bool listHasKey(const path_& location, const std::string& name, const std::string& key) const;
     bool isList(const path_& location, const std::string& name) const;
     void addList(const std::string& location, const std::string& name, const std::set<std::string>& keys);
+    bool isPresenceContainer(const path_& location, const std::string& name) const;
 
 private:
     const std::unordered_map<std::string, NodeType>& children(const std::string& name) const;
diff --git a/tests/cd.cpp b/tests/cd.cpp
index 83a1f98..2cef615 100644
--- a/tests/cd.cpp
+++ b/tests/cd.cpp
@@ -125,8 +125,9 @@
             }
         }
 
-        cd_ command = parser.parseCommand(input, errorStream);
-        REQUIRE(command == expected);
+        command_ command = parser.parseCommand(input, errorStream);
+        REQUIRE(command.type() == typeid(cd_));
+        REQUIRE(boost::get<cd_>(command) == expected);
     }
     SECTION("invalid input")
     {
diff --git a/tests/presence_containers.cpp b/tests/presence_containers.cpp
new file mode 100644
index 0000000..a4050f8
--- /dev/null
+++ b/tests/presence_containers.cpp
@@ -0,0 +1,93 @@
+
+/*
+ * 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 "ast.hpp"
+#include "parser.hpp"
+#include "schema.hpp"
+
+TEST_CASE("presence containers")
+{
+    Schema schema;
+    schema.addContainer("", "a", yang::ContainerTraits::Presence);
+    schema.addContainer("", "b");
+    schema.addContainer("a", "a2");
+    schema.addContainer("a/a2", "a3", yang::ContainerTraits::Presence);
+    schema.addContainer("b", "b2", yang::ContainerTraits::Presence);
+    schema.addList("", "list", {"quote"});
+    schema.addContainer("list", "contInList", yang::ContainerTraits::Presence);
+    Parser parser(schema);
+    std::string input;
+    std::ostringstream errorStream;
+
+    SECTION("valid input")
+    {
+        path_ expectedPath;
+
+        SECTION("a")
+        {
+            input = "a";
+            expectedPath.m_nodes = { container_("a") };
+        }
+
+        SECTION("b/b2")
+        {
+            input = "b/b2";
+            expectedPath.m_nodes = { container_("b"), container_("b2") };
+        }
+
+        SECTION("a/a2/a3")
+        {
+            input = "a/a2/a3";
+            expectedPath.m_nodes = { container_("a"), container_("a2"), container_("a3") };
+        }
+
+        SECTION("list[quote=lol]/contInList")
+        {
+            input = "list[quote=lol]/contInList";
+            auto keys = std::map<std::string, std::string>{
+                {"quote", "lol"}};
+            expectedPath.m_nodes = { listElement_("list", keys), container_("contInList") };
+        }
+
+        create_ expectedCreate;
+        expectedCreate.m_path = expectedPath;
+        command_ commandCreate = parser.parseCommand("create " + input, errorStream);
+        REQUIRE(commandCreate.type() == typeid(create_));
+        create_ create = boost::get<create_>(commandCreate);
+        REQUIRE(create == expectedCreate);
+
+        delete_ expectedDelete;
+        expectedDelete.m_path = expectedPath;
+        command_ commandDelete = parser.parseCommand("delete " + input, errorStream);
+        REQUIRE(commandDelete.type() == typeid(delete_));
+        delete_ delet = boost::get<delete_>(commandDelete);
+        REQUIRE(delet == expectedDelete);
+    }
+    SECTION("invalid input")
+    {
+        SECTION("c")
+        {
+            input = "c";
+        }
+
+        SECTION("a/a2")
+        {
+            input = "a/a2";
+        }
+
+        SECTION("list[quote=lol]")
+        {
+            input = "list[quote=lol]";
+        }
+
+        REQUIRE_THROWS(parser.parseCommand("create " + input, errorStream));
+        REQUIRE_THROWS(parser.parseCommand("delete " + input, errorStream));
+    }
+}