Merge "Skip rpc nodes in YangSchema::childNodes"
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index c046040..04e890a 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -652,7 +652,7 @@
 
         parserContext.m_suggestions.clear();
         boost::mpl::for_each<CommandTypes, boost::type<boost::mpl::_>>([&parserContext](auto cmd) {
-            parserContext.m_suggestions.insert({commandNamesVisitor()(cmd)});
+            parserContext.m_suggestions.insert({commandNamesVisitor()(cmd), " "});
         });
     }
 };
diff --git a/src/parser.cpp b/src/parser.cpp
index 85793a0..91b00a2 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -69,8 +69,8 @@
 
     auto filtered = filterByPrefix(ctx.m_suggestions, std::string(completionIterator, line.end()));
     if (filtered.size() == 1) {
-        auto suffix = filtered.begin()->m_whenToAdd == Completion::WhenToAdd::IfFullMatch
-                && filtered.begin()->m_value == std::string{completionIterator, line.end()}
+        auto suffix = filtered.begin()->m_whenToAdd == Completion::WhenToAdd::Always
+            || filtered.begin()->m_value == std::string{completionIterator, line.end()}
             ? filtered.begin()->m_suffix
             : "";
         return {{filtered.begin()->m_value + suffix}, completionContext};
diff --git a/src/schema.cpp b/src/schema.cpp
index c390e31..2cfcbf2 100644
--- a/src/schema.cpp
+++ b/src/schema.cpp
@@ -9,3 +9,40 @@
 #include "schema.hpp"
 
 Schema::~Schema() = default;
+
+bool Schema::isList(const schemaPath_& location, const ModuleNodePair& node) const
+{
+    try {
+        return nodeType(location, node) == yang::NodeTypes::List;
+    } catch (InvalidNodeException&) {
+        return false;
+    }
+}
+
+bool Schema::isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const
+{
+    try {
+        return nodeType(location, node) == yang::NodeTypes::PresenceContainer;
+    } catch (InvalidNodeException&) {
+        return false;
+    }
+}
+
+bool Schema::isContainer(const schemaPath_& location, const ModuleNodePair& node) const
+{
+    try {
+        auto type = nodeType(location, node);
+        return type == yang::NodeTypes::Container || type == yang::NodeTypes::PresenceContainer;
+    } catch (InvalidNodeException&) {
+        return false;
+    }
+}
+
+bool Schema::isLeaf(const schemaPath_& location, const ModuleNodePair& node) const
+{
+    try {
+        return nodeType(location, node) == yang::NodeTypes::Leaf;
+    } catch (InvalidNodeException&) {
+        return false;
+    }
+}
diff --git a/src/schema.hpp b/src/schema.hpp
index 8b7031a..a85961b 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -35,6 +35,13 @@
     IdentityRef,
     LeafRef,
 };
+
+enum class NodeTypes {
+    Container,
+    PresenceContainer,
+    List,
+    Leaf
+};
 }
 
 enum class Recursion {
@@ -43,10 +50,7 @@
 };
 
 
-class InvalidNodeException : public std::invalid_argument {
-public:
-    using std::invalid_argument::invalid_argument;
-    ~InvalidNodeException() override;
+class InvalidNodeException {
 };
 
 /*! \class Schema
@@ -59,11 +63,13 @@
 public:
     virtual ~Schema();
 
-    virtual bool isContainer(const schemaPath_& location, const ModuleNodePair& node) const = 0;
-    virtual bool isLeaf(const schemaPath_& location, const ModuleNodePair& node) const = 0;
+    bool isContainer(const schemaPath_& location, const ModuleNodePair& node) const;
+    bool isLeaf(const schemaPath_& location, const ModuleNodePair& node) const;
+    bool isList(const schemaPath_& location, const ModuleNodePair& node) const;
+    bool isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const;
+    virtual yang::NodeTypes nodeType(const std::string& path) const = 0;
+    virtual yang::NodeTypes nodeType(const schemaPath_& location, const ModuleNodePair& node) const = 0;
     virtual bool isModule(const std::string& name) const = 0;
-    virtual bool isList(const schemaPath_& location, const ModuleNodePair& node) const = 0;
-    virtual bool isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const = 0;
     virtual bool leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const = 0;
     virtual bool leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const = 0;
     virtual bool listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const = 0;
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index 47e0884..d2287dc 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -10,8 +10,6 @@
 #include "static_schema.hpp"
 #include "utils.hpp"
 
-InvalidNodeException::~InvalidNodeException() = default;
-
 StaticSchema::StaticSchema()
 {
     m_nodes.emplace("/", std::unordered_map<std::string, NodeType>());
@@ -36,16 +34,6 @@
     return m_modules.find(name) != m_modules.end();
 }
 
-bool StaticSchema::isContainer(const schemaPath_& location, const ModuleNodePair& node) const
-{
-    std::string locationString = pathToSchemaString(location, Prefixes::Always);
-    auto fullName = fullNodeName(location, node);
-    if (!nodeExists(locationString, fullName))
-        return false;
-
-    return children(locationString).at(fullName).type() == typeid(yang::container);
-}
-
 void StaticSchema::addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence)
 {
     m_nodes.at(location).emplace(name, yang::container{isPresence});
@@ -75,19 +63,6 @@
     return list.m_keys;
 }
 
-bool StaticSchema::isList(const schemaPath_& location, const ModuleNodePair& node) const
-{
-    std::string locationString = pathToSchemaString(location, Prefixes::Always);
-    auto fullName = fullNodeName(location, node);
-    if (!nodeExists(locationString, fullName))
-        return false;
-    const auto& child = children(locationString).at(fullName);
-    if (child.type() != typeid(yang::list))
-        return false;
-
-    return true;
-}
-
 void StaticSchema::addList(const std::string& location, const std::string& name, const std::set<std::string>& keys)
 {
     m_nodes.at(location).emplace(name, yang::list{keys});
@@ -96,14 +71,6 @@
     m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
-bool StaticSchema::isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const
-{
-    if (!isContainer(location, node))
-        return false;
-    std::string locationString = pathToSchemaString(location, Prefixes::Always);
-    return boost::get<yang::container>(children(locationString).at(fullNodeName(location, node))).m_presence == yang::ContainerTraits::Presence;
-}
-
 void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataTypes& type)
 {
     m_nodes.at(location).emplace(name, yang::leaf{type, {}, {}, {}});
@@ -205,16 +172,6 @@
     return std::any_of(identities.begin(), identities.end(), [toFind = identModule + ":" + value.second](const auto& x) { return x == toFind; });
 }
 
-bool StaticSchema::isLeaf(const schemaPath_& location, const ModuleNodePair& node) const
-{
-    std::string locationString = pathToSchemaString(location, Prefixes::Always);
-    auto fullName = fullNodeName(location, node);
-    if (!nodeExists(locationString, fullName))
-        return false;
-
-    return children(locationString).at(fullName).type() == typeid(yang::leaf);
-}
-
 std::string lastNodeOfSchemaPath(const std::string& path)
 {
     std::string res = path;
@@ -284,3 +241,37 @@
     }
     return res;
 }
+
+yang::NodeTypes StaticSchema::nodeType(const schemaPath_& location, const ModuleNodePair& node) const
+{
+    std::string locationString = pathToSchemaString(location, Prefixes::Always);
+    auto fullName = fullNodeName(location, node);
+    try {
+        auto targetNode = children(locationString).at(fullName);
+
+        if (targetNode.type() == typeid(yang::container)) {
+            if (boost::get<yang::container>(targetNode).m_presence == yang::ContainerTraits::Presence) {
+                return yang::NodeTypes::PresenceContainer;
+            }
+            return yang::NodeTypes::Container;
+        }
+
+        if (targetNode.type() == typeid(yang::list)) {
+            return yang::NodeTypes::List;
+        }
+
+        if (targetNode.type() == typeid(yang::leaf)) {
+            return yang::NodeTypes::Leaf;
+        }
+
+        throw std::runtime_error{"YangSchema::nodeType: unsupported type"};
+
+    } catch (std::out_of_range&) {
+        throw InvalidNodeException();
+    }
+}
+
+yang::NodeTypes StaticSchema::nodeType([[maybe_unused]] const std::string& path) const
+{
+    throw std::runtime_error{"Internal error: StaticSchema::nodeType(std::string) not implemented. The tests should not have called this overload."};
+}
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 742c53d..c2623a5 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -48,11 +48,9 @@
 public:
     StaticSchema();
 
-    bool isContainer(const schemaPath_& location, const ModuleNodePair& node) const override;
+    yang::NodeTypes nodeType(const std::string& path) const override;
+    yang::NodeTypes nodeType(const schemaPath_& location, const ModuleNodePair& node) const override;
     bool isModule(const std::string& name) const override;
-    bool isLeaf(const schemaPath_& location, const ModuleNodePair& node) const override;
-    bool isList(const schemaPath_& location, const ModuleNodePair& node) const override;
-    bool isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const override;
     bool leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const override;
     bool leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const override;
     bool listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const override;
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 4627bd2..88f0154 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -93,31 +93,6 @@
     return set.find(name) != set.end();
 }
 
-bool YangSchema::isContainer(const schemaPath_& location, const ModuleNodePair& node) const
-{
-    const auto schemaNode = getSchemaNode(location, node);
-    return schemaNode && schemaNode->nodetype() == LYS_CONTAINER;
-}
-
-bool YangSchema::isLeaf(const schemaPath_& location, const ModuleNodePair& node) const
-{
-    const auto schemaNode = getSchemaNode(location, node);
-    return schemaNode && schemaNode->nodetype() == LYS_LEAF;
-}
-
-bool YangSchema::isList(const schemaPath_& location, const ModuleNodePair& node) const
-{
-    const auto schemaNode = getSchemaNode(location, node);
-    return schemaNode && schemaNode->nodetype() == LYS_LIST;
-}
-
-bool YangSchema::isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const
-{
-    if (!isContainer(location, node))
-        return false;
-    return libyang::Schema_Node_Container(getSchemaNode(location, node)).presence();
-}
-
 bool YangSchema::leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const
 {
     auto enums = enumValues(location, node);
@@ -417,3 +392,32 @@
 {
     return m_context->get_module(name.c_str(), nullptr, 0);
 }
+
+namespace {
+yang::NodeTypes impl_nodeType(const libyang::S_Schema_Node& node)
+{
+    if (!node) {
+        throw InvalidNodeException();
+    }
+    switch (node->nodetype()) {
+    case LYS_CONTAINER:
+        return libyang::Schema_Node_Container{node}.presence() ? yang::NodeTypes::PresenceContainer : yang::NodeTypes::Container;
+    case LYS_LEAF:
+        return yang::NodeTypes::Leaf;
+    case LYS_LIST:
+        return yang::NodeTypes::List;
+    default:
+        throw InvalidNodeException(); // FIXME: Implement all types.
+    }
+}
+}
+
+yang::NodeTypes YangSchema::nodeType(const schemaPath_& location, const ModuleNodePair& node) const
+{
+    return impl_nodeType(getSchemaNode(location, node));
+}
+
+yang::NodeTypes YangSchema::nodeType(const std::string& path) const
+{
+    return impl_nodeType(getSchemaNode(path));
+}
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index 1ff5144..ea204e2 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -30,11 +30,9 @@
     YangSchema(std::shared_ptr<libyang::Context> lyCtx);
     ~YangSchema() override;
 
-    bool isContainer(const schemaPath_& location, const ModuleNodePair& node) const override;
-    bool isLeaf(const schemaPath_& location, const ModuleNodePair& node) const override;
+    yang::NodeTypes nodeType(const std::string& path) const override;
+    yang::NodeTypes nodeType(const schemaPath_& location, const ModuleNodePair& node) const override;
     bool isModule(const std::string& name) const override;
-    bool isList(const schemaPath_& location, const ModuleNodePair& node) const override;
-    bool isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const override;
     bool leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const override;
     bool leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const override;
     bool listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const override;
diff --git a/tests/command_completion.cpp b/tests/command_completion.cpp
index 9ab146e..1d44646 100644
--- a/tests/command_completion.cpp
+++ b/tests/command_completion.cpp
@@ -57,15 +57,14 @@
     SECTION("cd")
     {
         input = "cd";
-        // TODO: depending on how Readline works, this will have to be changed to include a space
-        expectedCompletions = {"cd"};
+        expectedCompletions = {"cd "};
         expectedContextLength = 2;
     }
 
     SECTION("create")
     {
         input = "create";
-        expectedCompletions = {"create"};
+        expectedCompletions = {"create "};
         expectedContextLength = 6;
     }
 
diff --git a/tests/yang.cpp b/tests/yang.cpp
index a90cb8e..9af8b47 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -751,6 +751,37 @@
 
             REQUIRE(ys.childNodes(path, Recursion::NonRecursive) == set);
         }
+        SECTION("nodeType")
+        {
+            yang::NodeTypes expected;
+            SECTION("leafInt32")
+            {
+                path.m_nodes.push_back(schemaNode_(module_{"example-schema"}, leaf_("leafInt32")));
+                expected = yang::NodeTypes::Leaf;
+            }
+
+            SECTION("a")
+            {
+                path.m_nodes.push_back(schemaNode_(module_{"example-schema"}, container_("a")));
+                expected = yang::NodeTypes::Container;
+            }
+
+            SECTION("a/a2/a3")
+            {
+                path.m_nodes.push_back(schemaNode_(module_{"example-schema"}, container_("a")));
+                path.m_nodes.push_back(schemaNode_(container_("a2")));
+                path.m_nodes.push_back(schemaNode_(container_("a3")));
+                expected = yang::NodeTypes::PresenceContainer;
+            }
+
+            SECTION("_list")
+            {
+                path.m_nodes.push_back(schemaNode_(module_{"example-schema"}, list_("_list")));
+                expected = yang::NodeTypes::List;
+            }
+
+            REQUIRE(ys.nodeType(pathToSchemaString(path, Prefixes::WhenNeeded)) == expected);
+        }
     }
 
     SECTION("negative")