Merge "Improve error message for enum values"
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 94121b7..82d0b1d 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -333,6 +333,7 @@
     {
         auto& parserContext = x3::get<parser_context_tag>(context);
         parserContext.m_suggestions.clear();
+        parserContext.m_completionSuffix.clear();
     }
 };
 
@@ -561,7 +562,32 @@
         const auto& schema = parserContext.m_schema;
 
         parserContext.m_completionIterator = begin;
-        parserContext.m_suggestions = schema.childNodes(parserContext.m_curPath, Recursion::NonRecursive);
+        auto suggestions = schema.childNodes(parserContext.m_curPath, Recursion::NonRecursive);
+        std::set<std::string> suffixesAdded;
+        std::transform(suggestions.begin(), suggestions.end(),
+            std::inserter(suffixesAdded, suffixesAdded.end()),
+            [&parserContext, &schema] (auto it) {
+                ModuleNodePair node;
+                if (auto colonPos = it.find(":"); colonPos != it.npos) {
+                    node.first = it.substr(0, colonPos);
+                    node.second = it.substr(colonPos + 1, node.second.npos);
+                } else {
+                    node.first = boost::none;
+                    node.second = it;
+                }
+
+                if (schema.isLeaf(parserContext.m_curPath, node)) {
+                    return it + " ";
+                }
+                if (schema.isContainer(parserContext.m_curPath, node)) {
+                    return it + "/";
+                }
+                if (schema.isList(parserContext.m_curPath, node)) {
+                    return it + "[";
+                }
+                return it;
+        });
+        parserContext.m_suggestions = suffixesAdded;
     }
 };
 
@@ -598,16 +624,6 @@
     }
 };
 
-struct suggestKeysStart_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);
-
-        parserContext.m_completionSuffix = "[";
-    }
-};
-
 struct commandNamesVisitor {
     template <typename T>
     auto operator()(boost::type<T>)
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 4fd7b15..0a259b8 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -71,7 +71,6 @@
 x3::rule<initializePath_class, x3::unused_type> const initializePath = "initializePath";
 x3::rule<createPathSuggestions_class, x3::unused_type> const createPathSuggestions = "createPathSuggestions";
 x3::rule<createKeySuggestions_class, x3::unused_type> const createKeySuggestions = "createKeySuggestions";
-x3::rule<suggestKeysStart_class, x3::unused_type> const suggestKeysStart = "suggestKeysStart";
 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";
@@ -118,9 +117,6 @@
 auto const createKeySuggestions_def =
     x3::eps;
 
-auto const suggestKeysStart_def =
-    x3::eps;
-
 auto const suggestKeysEnd_def =
     x3::eps;
 
@@ -148,7 +144,7 @@
     *keyValueWrapper;
 
 auto const listElement_def =
-    listPrefix >> -(!char_('[') >> suggestKeysStart) >> &char_('[') > listSuffix;
+    listPrefix >> &char_('[') > listSuffix;
 
 auto const list_def =
     node_identifier >> !char_('[');
@@ -405,7 +401,6 @@
 BOOST_SPIRIT_DEFINE(command)
 BOOST_SPIRIT_DEFINE(createPathSuggestions)
 BOOST_SPIRIT_DEFINE(createKeySuggestions)
-BOOST_SPIRIT_DEFINE(suggestKeysStart)
 BOOST_SPIRIT_DEFINE(suggestKeysEnd)
 BOOST_SPIRIT_DEFINE(createCommandSuggestions)
 BOOST_SPIRIT_DEFINE(completing)
diff --git a/src/main.cpp b/src/main.cpp
index f702cb8..ec1b45c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -62,7 +62,14 @@
     while (true) {
         auto line = lineEditor.input(parser.currentNode() + "> ");
         if (!line) {
-            break;
+            // If user pressed CTRL-C to abort the line, errno gets set to EAGAIN.
+            // If user pressed CTRL-D (for EOF), errno doesn't get set to EAGAIN, so we exit the program.
+            // I have no idea why replxx uses errno for this.
+            if (errno == EAGAIN) {
+                continue;
+            } else {
+                break;
+            }
         }
 
         std::locale C_locale("C");
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index a3f59aa..60cddf9 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -107,6 +107,8 @@
 void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataTypes& type)
 {
     m_nodes.at(location).emplace(name, yang::leaf{type, {}, {}, {}});
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
 void StaticSchema::addLeafEnum(const std::string& location, const std::string& name, std::set<std::string> enumValues)
@@ -115,6 +117,8 @@
     toAdd.m_type = yang::LeafDataTypes::Enum;
     toAdd.m_enumValues = enumValues;
     m_nodes.at(location).emplace(name, toAdd);
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
 void StaticSchema::addLeafIdentityRef(const std::string& location, const std::string& name, const ModuleValuePair& base)
@@ -124,6 +128,8 @@
     toAdd.m_type = yang::LeafDataTypes::IdentityRef;
     toAdd.m_identBase = base;
     m_nodes.at(location).emplace(name, toAdd);
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
 void StaticSchema::addLeafRef(const std::string& location, const std::string& name, const std::string& source)
@@ -132,6 +138,8 @@
     toAdd.m_type = yang::LeafDataTypes::LeafRef;
     toAdd.m_leafRefSource = source;
     m_nodes.at(location).emplace(name, toAdd);
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
 void StaticSchema::addModule(const std::string& name)
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 2c80c2d..f84bb91 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -93,8 +93,13 @@
         return fetchSchema(moduleName, revision, submodule);
     });
 
-    for (const auto& it : listImplementedSchemas()) {
-        m_schema->loadModule(it);
+    for (const auto& it : listSchemas()) {
+        if (it->implemented()) {
+            m_schema->loadModule(it->module_name());
+            for (unsigned int i = 0; i < it->enabled_feature_cnt(); i++) {
+                m_schema->enableFeature(it->module_name(), it->enabled_features(i));
+            }
+        }
     }
 }
 
@@ -205,9 +210,9 @@
     return schema;
 }
 
-std::vector<std::string> SysrepoAccess::listImplementedSchemas()
+std::vector<std::shared_ptr<sysrepo::Yang_Schema>> SysrepoAccess::listSchemas()
 {
-    std::vector<std::string> res;
+    std::vector<sysrepo::S_Yang_Schema> res;
     std::shared_ptr<sysrepo::Yang_Schemas> schemas;
     try {
         schemas = m_session->list_schemas();
@@ -216,8 +221,7 @@
     }
     for (unsigned int i = 0; i < schemas->schema_cnt(); i++) {
         auto schema = schemas->schema(i);
-        if (schema->implemented())
-            res.push_back(schema->module_name());
+        res.push_back(schema);
     }
     return res;
 }
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index 8df1153..9861ab4 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -18,6 +18,7 @@
 namespace sysrepo {
 class Connection;
 class Session;
+class Yang_Schema;
 }
 
 class Schema;
@@ -43,7 +44,7 @@
     [[noreturn]] void reportErrors();
 
     std::string fetchSchema(const char* module, const char* revision, const char* submodule);
-    std::vector<std::string> listImplementedSchemas();
+    std::vector<std::shared_ptr<sysrepo::Yang_Schema>> listSchemas();
 
     std::shared_ptr<sysrepo::Connection> m_connection;
     std::shared_ptr<sysrepo::Session> m_session;
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 2619943..549393a 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -135,8 +135,14 @@
         enm = type->info()->enums()->enm();
     }
 
+    std::vector<libyang::S_Type_Enum> enabled;
+    std::copy_if(enm.begin(), enm.end(), std::back_inserter(enabled), [] (const libyang::S_Type_Enum& it) {
+        auto iffeatures = it->iffeature();
+        return std::all_of(iffeatures.begin(), iffeatures.end(), [] (auto it) {return it->value();});
+    });
+
     std::set<std::string> enumSet;
-    std::transform(enm.begin(), enm.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return it->name(); });
+    std::transform(enabled.begin(), enabled.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return it->name(); });
     return enumSet;
 }
 
@@ -188,11 +194,8 @@
     return keys.find(key) != keys.end();
 }
 
-libyang::S_Schema_Node YangSchema::getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const
+libyang::S_Schema_Node YangSchema::impl_getSchemaNode(const std::string& node) const
 {
-    std::string absPath = location.m_nodes.empty() ? "" : "/";
-    absPath += pathToAbsoluteSchemaString(location) + "/" + fullNodeName(location, node);
-
     // If no node is found find_path prints an error message, so we have to
     // disable logging
     // https://github.com/CESNET/libyang/issues/753
@@ -205,10 +208,24 @@
             [&oldOptions]() {
                 libyang::set_log_options(oldOptions);
             });
-        return m_context->get_node(nullptr, absPath.c_str());
+        return m_context->get_node(nullptr, node.c_str());
     }
 }
 
+
+libyang::S_Schema_Node YangSchema::getSchemaNode(const std::string& node) const
+{
+    return impl_getSchemaNode(node);
+}
+
+libyang::S_Schema_Node YangSchema::getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const
+{
+    std::string absPath = location.m_nodes.empty() ? "" : "/";
+    absPath += pathToAbsoluteSchemaString(location) + "/" + fullNodeName(location, node);
+
+    return impl_getSchemaNode(absPath);
+}
+
 const std::set<std::string> YangSchema::listKeys(const schemaPath_& location, const ModuleNodePair& node) const
 {
     std::set<std::string> keys;
@@ -309,9 +326,8 @@
         nodes = m_context->data_instantiables(0);
     } else {
         const auto absolutePath = "/" + pathToAbsoluteSchemaString(path);
-        const auto set = m_context->find_path(absolutePath.c_str());
-        const auto schemaSet = set->schema();
-        nodes = (*schemaSet.begin())->child_instantiables(0);
+        const auto node = getSchemaNode(absolutePath);
+        nodes = node->child_instantiables(0);
     }
 
     for (const auto node : nodes) {
@@ -360,6 +376,11 @@
     m_context->load_module(moduleName.c_str());
 }
 
+void YangSchema::enableFeature(const std::string& moduleName, const std::string& featureName)
+{
+    m_context->get_module(moduleName.c_str())->feature_enable(featureName.c_str());
+}
+
 void YangSchema::registerModuleCallback(const std::function<std::string(const char*, const char*, const char*)>& clb)
 {
     auto lambda = [clb](const char* mod_name, const char* mod_revision, const char* submod_name, const char* submod_revision) {
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index 12a250c..14b0f1e 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -50,6 +50,9 @@
     /** @short Loads a module called moduleName. */
     void loadModule(const std::string& moduleName);
 
+    /** @short Enables a feature called featureName on a module called moduleName. */
+    void enableFeature(const std::string& moduleName, const std::string& featureName);
+
     /** @short Adds a new module passed as a YANG string. */
     void addSchemaString(const char* schema);
 
@@ -69,6 +72,11 @@
     /** @short Returns a set of nodes, that match the location and name criteria. */
 
     /** @short Returns a single Schema_Node if the criteria matches only one, otherwise nullptr. */
+    std::shared_ptr<libyang::Schema_Node> getSchemaNode(const std::string& node) const;
+
+    /** @short Returns a single Schema_Node if the criteria matches only one, otherwise nullptr. */
     std::shared_ptr<libyang::Schema_Node> getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const;
     std::shared_ptr<libyang::Context> m_context;
+
+    std::shared_ptr<libyang::Schema_Node> impl_getSchemaNode(const std::string& node) const;
 };
diff --git a/submodules/dependencies b/submodules/dependencies
index 7f378d1..2d91a55 160000
--- a/submodules/dependencies
+++ b/submodules/dependencies
@@ -1 +1 @@
-Subproject commit 7f378d15b8194e0c84ed00ea07a5c3e4f9565838
+Subproject commit 2d91a55add68ebcc21322956e51ebf51458fc2c3
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 5adb7ff..c68cca0 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -51,25 +51,25 @@
         SECTION("ls ")
         {
             input = "ls ";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:leafInt", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList", "second:amelie"};
+            expected = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList[", "second:amelie/"};
         }
 
         SECTION("ls e")
         {
             input = "ls e";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:leafInt", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList"};
+            expected = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList["};
         }
 
         SECTION("ls example:ano")
         {
             input = "ls example:ano";
-            expected = {"example:ano", "example:anoda"};
+            expected = {"example:ano/", "example:anoda/"};
         }
 
         SECTION("ls example:ano/example:a")
         {
             input = "ls example:ano/example:a";
-            expected = {"example:a2"};
+            expected = {"example:a2/"};
         }
 
         SECTION("ls x")
@@ -81,32 +81,44 @@
         SECTION("ls /")
         {
             input = "ls /";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:leafInt", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList", "second:amelie"};
+            expected = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList[", "second:amelie/"};
         }
 
         SECTION("ls /e")
         {
             input = "ls /e";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:leafInt", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList"};
+            expected = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList["};
 
         }
 
+        SECTION("ls example:bota")
+        {
+            input = "ls example:bota";
+            expected = {"example:bota/"};
+        }
+
+        SECTION("ls /example:bota")
+        {
+            input = "ls /example:bota";
+            expected = {"example:bota/"};
+        }
+
         SECTION("ls /s")
         {
             input = "ls /s";
-            expected = {"second:amelie"};
+            expected = {"second:amelie/"};
         }
 
         SECTION("ls /example:list[number=3]/")
         {
             input = "ls /example:list[number=3]/";
-            expected = {"example:contInList"};
+            expected = {"example:contInList/"};
         }
 
         SECTION("ls /example:list[number=3]/c")
         {
             input = "ls /example:list[number=3]/e";
-            expected = {"example:contInList"};
+            expected = {"example:contInList/"};
         }
 
         SECTION("ls /example:list[number=3]/a")
@@ -121,7 +133,7 @@
         SECTION("cd example:lis")
         {
             input = "cd example:lis";
-            expected = {"example:list"};
+            expected = {"example:list["};
         }
 
         SECTION("set example:list")
@@ -211,7 +223,7 @@
         SECTION("cd example:ovoce")
         {
             input = "cd example:ovoce";
-            expected = {"example:ovoce", "example:ovocezelenina"};
+            expected = {"example:ovoce[", "example:ovocezelenina["};
         }
     }
 
diff --git a/tests/yang.cpp b/tests/yang.cpp
index 156d94b..2ca1704 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -6,6 +6,7 @@
  *
 */
 
+#include <experimental/iterator>
 #include "trompeloeil_doctest.h"
 #include "yang_schema.hpp"
 
@@ -249,8 +250,30 @@
         }
     }
 
+    feature bigPizzas;
+
+    leaf pizzaSize {
+        type enumeration {
+            enum large {
+                if-feature "bigPizzas";
+            }
+            enum medium;
+            enum small;
+        }
+    }
+
 })";
 
+namespace std {
+std::ostream& operator<<(std::ostream& s, const std::set<std::string> set)
+{
+    s << std::endl << "{";
+    std::copy(set.begin(), set.end(), std::experimental::make_ostream_joiner(s, ", "));
+    s << "}" << std::endl;
+    return s;
+}
+}
+
 TEST_CASE("yangschema")
 {
     using namespace std::string_view_literals;
@@ -423,6 +446,28 @@
                     value = "data";
             }
 
+            SECTION("pizzaSize")
+            {
+                node.first = "example-schema";
+                node.second = "pizzaSize";
+
+                SECTION("small")
+                {
+                    value = "small";
+
+                }
+                SECTION("medium")
+                {
+                    value = "medium";
+                }
+
+                SECTION("large")
+                {
+                    ys.enableFeature("example-schema", "bigPizzas");
+                    value = "large";
+                }
+            }
+
             REQUIRE(ys.leafEnumHasValue(path, node, value));
         }
         SECTION("leafIdentityIsValid")
@@ -681,7 +726,8 @@
                        "example-schema:_list", "example-schema:twoKeyList", "second-schema:bla",
                        "example-schema:carry", "example-schema:zero", "example-schema:direction",
                        "example-schema:interrupt",
-                       "example-schema:ethernet", "example-schema:loopback"};
+                       "example-schema:ethernet", "example-schema:loopback",
+                       "example-schema:pizzaSize"};
             }
 
             SECTION("example-schema:a")
@@ -696,6 +742,12 @@
                 set = {"bla2"};
             }
 
+            SECTION("example-schema:ethernet")
+            {
+                path.m_nodes.push_back(schemaNode_(module_{"example-schema"}, container_("ethernet")));
+                set = {"ip"};
+            }
+
             REQUIRE(ys.childNodes(path, Recursion::NonRecursive) == set);
         }
     }
@@ -852,5 +904,15 @@
             REQUIRE(!ys.isLeaf(path, node));
             REQUIRE(!ys.isContainer(path, node));
         }
+
+        SECTION("enum is disabled by if-feature if feature is not enabled")
+        {
+            node.first = "example-schema";
+            node.second = "pizzaSize";
+
+            std::string value = "large";
+
+            REQUIRE(!ys.leafEnumHasValue(path, node, value));
+        }
     }
 }