Add enum completion

Change-Id: I5ee7d6359812535d0eef9f70e1b66e205d93cf39
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b8ee606..75f0e7d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -182,6 +182,7 @@
     cli_test(utils)
     cli_test(path_completion)
     cli_test(command_completion)
+    cli_test(enum_completion)
 endif()
 
 if(WITH_DOCS)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 943af19..7eed8be 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -569,3 +569,22 @@
             _pass(context) = false;
     }
 };
+
+struct createEnumSuggestions_class {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        parserContext.m_completionIterator = begin;
+        const Schema& schema = parserContext.m_schema;
+
+        boost::optional<std::string> module;
+        if (parserContext.m_curPath.m_nodes.back().m_prefix)
+            module = parserContext.m_curPath.m_nodes.back().m_prefix.value().m_name;
+
+        leaf_ leaf = boost::get<leaf_>(parserContext.m_curPath.m_nodes.back().m_suffix);
+        schemaPath_ location = pathWithoutLastNode(parserContext.m_curPath);
+
+        parserContext.m_suggestions = schema.enumValues(location, {module, leaf.m_name});
+    }
+};
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 3742e35..c12f922 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -59,6 +59,7 @@
 x3::rule<suggestKeysEnd_class, x3::unused_type> const suggestKeysEnd = "suggestKeysEnd";
 x3::rule<createCommandSuggestions_class, x3::unused_type> const createCommandSuggestions = "createCommandSuggestions";
 x3::rule<completing_class, x3::unused_type> const completing = "completing";
+x3::rule<createEnumSuggestions_class, x3::unused_type> const createEnumSuggestions = "createEnumSuggestions";
 
 #if __clang__
 #pragma GCC diagnostic push
@@ -174,8 +175,11 @@
 auto const leafPath_def =
     dataPath;
 
+auto const createEnumSuggestions_def =
+    x3::eps;
+
 auto const leaf_data_enum_def =
-    +char_;
+    createEnumSuggestions >> +char_;
 auto const leaf_data_decimal_def =
     double_;
 
@@ -234,7 +238,7 @@
     get_::name >> -(space_separator >> (dataPathListEnd | dataPath));
 
 auto const set_def =
-    set_::name >> space_separator > leafPath > leaf_data;
+    set_::name >> space_separator > leafPath > space_separator > leaf_data;
 
 auto const commit_def =
     commit_::name >> x3::attr(commit_());
@@ -296,3 +300,4 @@
 BOOST_SPIRIT_DEFINE(suggestKeysEnd)
 BOOST_SPIRIT_DEFINE(createCommandSuggestions)
 BOOST_SPIRIT_DEFINE(completing)
+BOOST_SPIRIT_DEFINE(createEnumSuggestions)
diff --git a/src/schema.hpp b/src/schema.hpp
index e1f019f..c55e659 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -79,6 +79,7 @@
     virtual bool nodeExists(const std::string& location, const std::string& node) const = 0;
     virtual const std::set<std::string> listKeys(const schemaPath_& location, const ModuleNodePair& node) const = 0;
     virtual yang::LeafDataTypes leafType(const schemaPath_& location, const ModuleNodePair& node) const = 0;
+    virtual const std::set<std::string> enumValues(const schemaPath_& location, const ModuleNodePair& node) const = 0;
     virtual std::set<std::string> childNodes(const schemaPath_& path, const Recursion recursion) const = 0;
 
 private:
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index 9be7751..d80ac7b 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -121,12 +121,8 @@
 
 bool StaticSchema::leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const
 {
-    std::string locationString = pathToAbsoluteSchemaString(location);
-    assert(isLeaf(location, node));
-
-    const auto& child = children(locationString).at(fullNodeName(location, node));
-    const auto& list = boost::get<yang::leaf>(child);
-    return list.m_enumValues.find(value) != list.m_enumValues.end();
+    auto enums = enumValues(location, node);
+    return enums.find(value) != enums.end();
 }
 
 bool StaticSchema::isLeaf(const schemaPath_& location, const ModuleNodePair& node) const
@@ -145,6 +141,16 @@
     return boost::get<yang::leaf>(children(locationString).at(fullNodeName(location, node))).m_type;
 }
 
+const std::set<std::string> StaticSchema::enumValues(const schemaPath_& location, const ModuleNodePair& node) const
+{
+    std::string locationString = pathToAbsoluteSchemaString(location);
+    assert(isLeaf(location, node));
+
+    const auto& child = children(locationString).at(fullNodeName(location, node));
+    const auto& leaf = boost::get<yang::leaf>(child);
+    return leaf.m_enumValues;
+}
+
 // We do not test StaticSchema, so we don't need to implement recursive childNodes
 // for this class.
 std::set<std::string> StaticSchema::childNodes(const schemaPath_& path, const Recursion) const
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 7326120..d07852e 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -33,6 +33,7 @@
     bool nodeExists(const std::string& location, const std::string& node) const override;
     const std::set<std::string> listKeys(const schemaPath_& location, const ModuleNodePair& node) const override;
     yang::LeafDataTypes leafType(const schemaPath_& location, const ModuleNodePair& node) const override;
+    const std::set<std::string> enumValues(const schemaPath_& location, const ModuleNodePair& node) const override;
     std::set<std::string> childNodes(const schemaPath_& path, const Recursion) const override;
 
     void addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence = yang::ContainerTraits::None);
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 522e58a..8b870fc 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -112,8 +112,15 @@
 
 bool YangSchema::leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const
 {
+    auto enums = enumValues(location, node);
+
+    return std::any_of(enums.begin(), enums.end(), [=](const auto& x) { return x == value; });
+}
+
+const std::set<std::string> YangSchema::enumValues(const schemaPath_& location, const ModuleNodePair& node) const
+{
     if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::Enum)
-        return false;
+        return {};
 
     libyang::Schema_Node_Leaf leaf(getSchemaNode(location, node));
     auto type = leaf.type();
@@ -126,7 +133,9 @@
         enm = type->info()->enums()->enm();
     }
 
-    return std::any_of(enm.begin(), enm.end(), [=](const auto& x) { return x->name() == value; });
+    std::set<std::string> enumSet;
+    std::transform(enm.begin(), enm.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return it->name(); });
+    return enumSet;
 }
 
 bool YangSchema::listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index e4d6d5d..e56c853 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -39,6 +39,7 @@
     bool nodeExists(const std::string& location, const std::string& node) const override;
     const std::set<std::string> listKeys(const schemaPath_& location, const ModuleNodePair& node) const override;
     yang::LeafDataTypes leafType(const schemaPath_& location, const ModuleNodePair& node) const override;
+    const std::set<std::string> enumValues(const schemaPath_& location, const ModuleNodePair& node) const override;
     std::set<std::string> childNodes(const schemaPath_& path, const Recursion recursion) const override;
 
     void registerModuleCallback(const std::function<std::string(const char*, const char*, const char*)>& clb);
diff --git a/tests/enum_completion.cpp b/tests/enum_completion.cpp
new file mode 100644
index 0000000..e352c7f
--- /dev/null
+++ b/tests/enum_completion.cpp
@@ -0,0 +1,62 @@
+
+/*
+ * 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_commands.hpp"
+#include "parser.hpp"
+#include "static_schema.hpp"
+
+TEST_CASE("enum completion")
+{
+    auto schema = std::make_shared<StaticSchema>();
+    schema->addModule("mod");
+    schema->addContainer("", "mod:contA");
+    schema->addLeafEnum("", "mod:leafEnum", {"lala", "lol", "data", "coze"});
+    schema->addLeafEnum("mod:contA", "mod:leafInCont", {"abc", "def"});
+    schema->addList("", "mod:list", {"number"});
+    schema->addLeafEnum("mod:list", "mod:leafInList", {"ano", "anoda", "ne", "katoda"});
+    Parser parser(schema);
+    std::string input;
+    std::ostringstream errorStream;
+
+    std::set<std::string> expected;
+
+    SECTION("set mod:leafEnum ")
+    {
+        input = "set mod:leafEnum ";
+        expected = {"lala", "lol", "data", "coze"};
+    }
+
+    SECTION("set mod:leafEnum c")
+    {
+        input = "set mod:leafEnum c";
+        expected = {"coze"};
+    }
+
+    SECTION("set mod:leafEnum l")
+    {
+        input = "set mod:leafEnum l";
+        expected = {"lala", "lol"};
+    }
+
+
+    SECTION("set mod:contA/leafInCont ")
+    {
+        input = "set mod:contA/leafInCont ";
+        expected = {"abc", "def"};
+    }
+
+    SECTION("set mod:list[number=42]/leafInList ")
+    {
+        input = "set mod:list[number=42]/leafInList ";
+        expected = {"ano", "anoda", "ne", "katoda"};
+    }
+
+    REQUIRE(parser.completeCommand(input, errorStream) == expected);
+}