Merge "Add support for yang type descriptions"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 53dda3b..779b968 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -317,7 +317,7 @@
         set_property(TEST ${TESTNAME} ${TESTNAME}_init ${TESTNAME}_cleanup APPEND PROPERTY ENVIRONMENT
             "SYSREPO_REPOSITORY_PATH=${CMAKE_CURRENT_BINARY_DIR}/test_repositories/${TESTNAME}"
             "SYSREPO_SHM_PREFIX=netconf-cli_${TESTNAME}"
-            "NETOPEER_SOCKET=${CMAKE_CURRENT_BINARY_DIR}/test_netopeer_files/${TESTNAME}.sock"
+            "NETOPEER_SOCKET=test_netopeer_files/${TESTNAME}.sock"
             )
 
     endfunction()
@@ -386,7 +386,7 @@
         set_property(TEST test_netconf_cli_py test_netconf_cli_py_init test_netconf_cli_py_cleanup APPEND PROPERTY ENVIRONMENT
             "SYSREPO_REPOSITORY_PATH=${CMAKE_CURRENT_BINARY_DIR}/test_repositories/test_netconf_cli_py"
             "SYSREPO_SHM_PREFIX=netconf-cli_test_netconf_cli_py"
-            "NETOPEER_SOCKET=${CMAKE_CURRENT_BINARY_DIR}/test_netopeer_files/test_netconf_cli_py.sock"
+            "NETOPEER_SOCKET=test_netopeer_files/test_netconf_cli_py.sock"
             )
 
         set(sanitizer_active OFF)
diff --git a/src/cli.cpp b/src/cli.cpp
index a2c9cdc..271d9a5 100644
--- a/src/cli.cpp
+++ b/src/cli.cpp
@@ -63,7 +63,7 @@
 )";
 #include "cli-netconf.hpp"
 #include "netconf_access.hpp"
-#define PROGRAM_NAME "netconf-access"
+#define PROGRAM_NAME "netconf-cli"
 // FIXME: this should be replaced by C++20 std::jthread at some point
 struct PoorMansJThread {
     ~PoorMansJThread()
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index 057c96a..b29e152 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -50,8 +50,7 @@
     virtual void createItem(const std::string& path) = 0;
     virtual void deleteItem(const std::string& path) = 0;
     virtual void moveItem(const std::string& path, std::variant<yang::move::Absolute, yang::move::Relative> move) = 0;
-    virtual Tree executeRpc(const std::string& path, const Tree& input) = 0;
-    virtual Tree executeAction(const std::string& path, const Tree& input) = 0;
+    virtual Tree execute(const std::string& path, const Tree& input) = 0;
 
     virtual std::shared_ptr<Schema> schema() = 0;
 
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index aaa0595..9046b64 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -226,11 +226,7 @@
 
 void Interpreter::operator()(const prepare_& prepare) const
 {
-    if (std::holds_alternative<rpcNode_>(prepare.m_path.m_nodes.back().m_suffix)) {
-        m_datastore.initiateRpc(pathToString(toCanonicalPath(prepare.m_path)));
-    } else {
-        m_datastore.initiateAction(pathToString(toCanonicalPath(prepare.m_path)));
-    }
+    m_datastore.initiate(pathToString(toCanonicalPath(prepare.m_path)));
     m_parser.changeNode(prepare.m_path);
 }
 
diff --git a/src/leaf_data.hpp b/src/leaf_data.hpp
index 7bab723..df0f9b3 100644
--- a/src/leaf_data.hpp
+++ b/src/leaf_data.hpp
@@ -162,6 +162,7 @@
             parser.add(bit, bit);
             parserContext.m_suggestions.insert(Completion{bit});
         }
+        parserContext.m_completionIterator = first;
 
         std::vector<std::string> bitsRes;
 
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 1e56659..4ed29ae 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -132,7 +132,7 @@
     m_session->discard();
 }
 
-DatastoreAccess::Tree NetconfAccess::impl_execute(const std::string& path, const Tree& input)
+DatastoreAccess::Tree NetconfAccess::execute(const std::string& path, const Tree& input)
 {
     auto root = m_schema->dataNodeFromPath(path);
     for (const auto& [k, v] : input) {
@@ -149,16 +149,6 @@
     return res;
 }
 
-DatastoreAccess::Tree NetconfAccess::executeRpc(const std::string& path, const Tree& input)
-{
-    return impl_execute(path, input);
-}
-
-DatastoreAccess::Tree NetconfAccess::executeAction(const std::string& path, const Tree& input)
-{
-    return impl_execute(path, input);
-}
-
 NC_DATASTORE toNcDatastore(Datastore datastore)
 {
     switch (datastore) {
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index 45973a6..c54434d 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -47,8 +47,7 @@
     void moveItem(const std::string& path, std::variant<yang::move::Absolute, yang::move::Relative> move) override;
     void commitChanges() override;
     void discardChanges() override;
-    Tree executeRpc(const std::string& path, const Tree& input) override;
-    Tree executeAction(const std::string& path, const Tree& input) override;
+    Tree execute(const std::string& path, const Tree& input) override;
     void copyConfig(const Datastore source, const Datastore destination) override;
 
     std::shared_ptr<Schema> schema() override;
@@ -57,14 +56,12 @@
 
 private:
     std::vector<ListInstance> listInstances(const std::string& path) override;
-    DatastoreAccess::Tree impl_execute(const std::string& path, const Tree& input);
 
     std::string fetchSchema(const std::string_view module, const
             std::optional<std::string_view> revision, const
             std::optional<std::string_view> submodule, const
             std::optional<std::string_view> submoduleRevision);
     std::vector<std::string> listImplementedSchemas();
-    void datastoreInit();
     void doEditFromDataNode(std::shared_ptr<libyang::Data_Node> dataNode);
 
     std::unique_ptr<libnetconf::client::Session> m_session;
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index fca760d..58d174e 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -76,10 +76,8 @@
     {
     }
 
-    // GCC complains that `end` isn't used when doing completions only
-    // FIXME: GCC 10.1 doesn't emit a warning here. Remove [[maybe_unused]] when GCC 10 is available
     template <typename It, typename Ctx, typename RCtx, typename Attr>
-    bool parse(It& begin, [[maybe_unused]] It end, Ctx const& ctx, RCtx& rctx, Attr& attr) const
+    bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, Attr& attr) const
     {
         std::string tableName;
         if constexpr (std::is_same<attribute_type, schemaNode_>()) {
@@ -165,12 +163,7 @@
         if constexpr (PARSER_MODE == NodeParserMode::CompletionsOnly) {
             return true;
         } else {
-            It saveIter;
-            // GCC complains that I assign saveIter because I use it only if NodeType is dataNode_
-            // FIXME: GCC 10.1 doesn't emit a warning here. Make this unconditional when GCC 10 is available.
-            if constexpr (std::is_same<attribute_type, dataNode_>()) {
-                saveIter = begin;
-            }
+            It saveIter = begin;
 
             auto res = table.parse(begin, end, ctx, rctx, attr);
 
diff --git a/src/proxy_datastore.cpp b/src/proxy_datastore.cpp
index 113a923..ef3c5db 100644
--- a/src/proxy_datastore.cpp
+++ b/src/proxy_datastore.cpp
@@ -59,34 +59,14 @@
     return m_datastore->dump(format);
 }
 
-namespace {
-struct getInputPath {
-    template <typename InputType>
-    auto operator()(const InputType& input)
-    {
-        return input.m_path;
-    }
-};
-}
-
-void ProxyDatastore::initiateRpc(const std::string& rpcPath)
+void ProxyDatastore::initiate(const std::string& path)
 {
     if (m_inputDatastore) {
-        throw std::runtime_error("RPC/action input already in progress (" + std::visit(getInputPath{}, m_inputPath) + ")");
+        throw std::runtime_error("RPC/action input already in progress (" + m_inputPath + ")");
     }
     m_inputDatastore = m_createTemporaryDatastore(m_datastore);
-    m_inputPath = RpcInput{rpcPath};
-    m_inputDatastore->createItem(rpcPath);
-}
-
-void ProxyDatastore::initiateAction(const std::string& actionPath)
-{
-    if (m_inputDatastore) {
-        throw std::runtime_error("RPC/action input already in progress (" + std::visit(getInputPath{}, m_inputPath) + ")");
-    }
-    m_inputDatastore = m_createTemporaryDatastore(m_datastore);
-    m_inputPath = ActionInput{actionPath};
-    m_inputDatastore->createItem(actionPath);
+    m_inputPath = path;
+    m_inputDatastore->createItem(path);
 }
 
 DatastoreAccess::Tree ProxyDatastore::execute()
@@ -96,11 +76,7 @@
     }
     auto inputData = m_inputDatastore->getItems("/");
     m_inputDatastore = nullptr;
-    if (std::holds_alternative<RpcInput>(m_inputPath)) {
-        return m_datastore->executeRpc(std::visit(getInputPath{}, m_inputPath), inputData);
-    } else {
-        return m_datastore->executeAction(std::visit(getInputPath{}, m_inputPath), inputData);
-    }
+    return m_datastore->execute(m_inputPath, inputData);
 }
 
 void ProxyDatastore::cancel()
@@ -115,7 +91,7 @@
 
 std::shared_ptr<DatastoreAccess> ProxyDatastore::pickDatastore(const std::string& path) const
 {
-    if (!m_inputDatastore || !boost::starts_with(path, std::visit(getInputPath{}, m_inputPath))) {
+    if (!m_inputDatastore || !boost::starts_with(path, m_inputPath)) {
         return m_datastore;
     } else {
         return m_inputDatastore;
diff --git a/src/proxy_datastore.hpp b/src/proxy_datastore.hpp
index e7650cd..ab7e3a2 100644
--- a/src/proxy_datastore.hpp
+++ b/src/proxy_datastore.hpp
@@ -29,8 +29,7 @@
     void copyConfig(const Datastore source, const Datastore destination);
     [[nodiscard]] std::string dump(const DataFormat format) const;
 
-    void initiateRpc(const std::string& rpcPath);
-    void initiateAction(const std::string& actionPath);
+    void initiate(const std::string& path);
     [[nodiscard]] DatastoreAccess::Tree execute();
     void cancel();
 
@@ -48,14 +47,5 @@
     std::function<std::shared_ptr<DatastoreAccess>(const std::shared_ptr<DatastoreAccess>&)> m_createTemporaryDatastore;
     std::shared_ptr<DatastoreAccess> m_inputDatastore;
 
-    struct RpcInput {
-        std::string m_path;
-    };
-
-    struct ActionInput {
-        std::string m_path;
-    };
-    // This variant is needed, so that I know whether to call executeRpc or executeAction
-    // TODO: get rid of this variant with sysrepo2 because the method for RPC/action is the same there
-    std::variant<ActionInput, RpcInput> m_inputPath;
+    std::string m_inputPath;
 };
diff --git a/src/python_netconf.cpp b/src/python_netconf.cpp
index 7a0e4d3..c6e3c44 100644
--- a/src/python_netconf.cpp
+++ b/src/python_netconf.cpp
@@ -70,7 +70,7 @@
             .def("setLeaf", &NetconfAccess::setLeaf, "xpath"_a, "value"_a)
             .def("createItem", &NetconfAccess::createItem, "xpath"_a)
             .def("deleteItem", &NetconfAccess::deleteItem, "xpath"_a)
-            .def("executeRpc", &NetconfAccess::executeRpc, "rpc"_a, "input"_a=DatastoreAccess::Tree{})
+            .def("execute", &NetconfAccess::execute, "rpc"_a, "input"_a=DatastoreAccess::Tree{})
             .def("commitChanges", &NetconfAccess::commitChanges)
             ;
 }
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 7c21cad..21eca7e 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -329,15 +329,7 @@
 }
 }
 
-// TODO: merge this with executeAction
-DatastoreAccess::Tree SysrepoAccess::executeRpc(const std::string& path, const Tree& input)
-{
-    auto srInput = toSrVals(path, input);
-    auto output = m_session->rpc_send(path.c_str(), srInput);
-    return toTree(path, output);
-}
-
-DatastoreAccess::Tree SysrepoAccess::executeAction(const std::string& path, const Tree& input)
+DatastoreAccess::Tree SysrepoAccess::execute(const std::string& path, const Tree& input)
 {
     auto srInput = toSrVals(path, input);
     auto output = m_session->rpc_send(path.c_str(), srInput);
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index d0ad869..55e8ac6 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -33,8 +33,7 @@
     void createItem(const std::string& path) override;
     void deleteItem(const std::string& path) override;
     void moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move) override;
-    Tree executeRpc(const std::string& path, const Tree& input) override;
-    Tree executeAction(const std::string& path, const Tree& input) override;
+    Tree execute(const std::string& path, const Tree& input) override;
 
     std::shared_ptr<Schema> schema() override;
 
diff --git a/src/yang_access.cpp b/src/yang_access.cpp
index a460150..7a43507 100644
--- a/src/yang_access.cpp
+++ b/src/yang_access.cpp
@@ -229,7 +229,7 @@
 {
 }
 
-[[noreturn]] void YangAccess::impl_execute(const std::string& type, const std::string& path, const Tree& input)
+[[noreturn]] DatastoreAccess::Tree YangAccess::execute(const std::string& path, const Tree& input)
 {
     auto root = lyWrap(lyd_new_path(nullptr, m_ctx.get(), path.c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0));
     if (!root) {
@@ -244,17 +244,7 @@
             getErrorsAndThrow();
         }
     }
-    throw std::logic_error("in-memory datastore doesn't support executing " + type + "s");
-}
-
-DatastoreAccess::Tree YangAccess::executeRpc(const std::string& path, const Tree& input)
-{
-    impl_execute("RPC", path, input);
-}
-
-DatastoreAccess::Tree YangAccess::executeAction(const std::string& path, const Tree& input)
-{
-    impl_execute("action", path, input);
+    throw std::logic_error("in-memory datastore doesn't support executing RPC/action");
 }
 
 void YangAccess::copyConfig(const Datastore source, const Datastore dest)
diff --git a/src/yang_access.hpp b/src/yang_access.hpp
index 62c43d6..c009420 100644
--- a/src/yang_access.hpp
+++ b/src/yang_access.hpp
@@ -30,8 +30,7 @@
     void moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move) override;
     void commitChanges() override;
     void discardChanges() override;
-    Tree executeRpc(const std::string& path, const Tree& input) override;
-    Tree executeAction(const std::string& path, const Tree& input) override;
+    [[noreturn]] Tree execute(const std::string& path, const Tree& input) override;
     void copyConfig(const Datastore source, const Datastore destination) override;
 
     std::shared_ptr<Schema> schema() override;
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 43d6038..ddfe288 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -951,7 +951,7 @@
             SECTION("noop")
             {
                 rpc = "/example-schema:noop";
-                proxyDatastore.initiateRpc(rpc);
+                proxyDatastore.initiate(rpc);
             }
 
             SECTION("small nuke")
@@ -961,7 +961,7 @@
                     {"description", "dummy"s},
                     {"payload/kilotons", uint64_t{333'666}},
                 };
-                proxyDatastore.initiateRpc(rpc);
+                proxyDatastore.initiate(rpc);
                 proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{333'666});
                 // no data are returned
             }
@@ -973,7 +973,7 @@
                     {"description", "dummy"s},
                     {"payload/kilotons", uint64_t{4}},
                 };
-                proxyDatastore.initiateRpc(rpc);
+                proxyDatastore.initiate(rpc);
                 proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{4});
 
                 output = {
@@ -989,7 +989,7 @@
                     {"payload/kilotons", uint64_t{6}},
                     {"cities/targets[city='Prague']/city", "Prague"s},
                 };
-                proxyDatastore.initiateRpc(rpc);
+                proxyDatastore.initiate(rpc);
                 proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{6});
                 proxyDatastore.createItem("/example-schema:launch-nukes/example-schema:cities/example-schema:targets[city='Prague']");
                 output = {
@@ -1012,17 +1012,17 @@
                 input = {
                     {"whom", "Colton"s}
                 };
-                proxyDatastore.initiateRpc(rpc);
+                proxyDatastore.initiate(rpc);
                 proxyDatastore.setLeaf("/example-schema:fire/example-schema:whom", "Colton"s);
             }
 
-            catching<OnExec>([&] { REQUIRE(datastore->executeRpc(rpc, input) == output); });
+            catching<OnExec>([&] { REQUIRE(datastore->execute(rpc, input) == output); });
             catching<OnExec>([&] { REQUIRE(proxyDatastore.execute() == output); });
         }
 
         SECTION("non-existing RPC")
         {
-            catching<OnInvalidRpcPath>([&] { datastore->executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{}); });
+            catching<OnInvalidRpcPath>([&] { datastore->execute("/example-schema:non-existing", DatastoreAccess::Tree{}); });
         }
     }
 
@@ -1045,7 +1045,7 @@
             path = "/example-schema:ports[name='A']/shutdown";
         }
 
-        catching<OnExec>([&] { REQUIRE(datastore->executeAction(path, input) == output); });
+        catching<OnExec>([&] { REQUIRE(datastore->execute(path, input) == output); });
     }
 
     waitForCompletionAndBitMore(seq1);
diff --git a/tests/datastoreaccess_mock.hpp b/tests/datastoreaccess_mock.hpp
index 751cdad..e708149 100644
--- a/tests/datastoreaccess_mock.hpp
+++ b/tests/datastoreaccess_mock.hpp
@@ -24,8 +24,7 @@
     IMPLEMENT_MOCK1(createItem);
     IMPLEMENT_MOCK1(deleteItem);
     IMPLEMENT_MOCK2(moveItem);
-    IMPLEMENT_MOCK2(executeRpc);
-    IMPLEMENT_MOCK2(executeAction);
+    IMPLEMENT_MOCK2(execute);
 
     // Can't use IMPLEMENT_MOCK for private methods - IMPLEMENT_MOCK needs full visibility of the method
     MAKE_MOCK1(listInstances, std::vector<ListInstance>(const std::string&), override);
diff --git a/tests/interpreter.cpp b/tests/interpreter.cpp
index d57ad64..c6d7c9c 100644
--- a/tests/interpreter.cpp
+++ b/tests/interpreter.cpp
@@ -454,7 +454,7 @@
         SECTION("exec")
         {
             REQUIRE_CALL(*input_datastore, getItems("/")).RETURN(DatastoreAccess::Tree{});
-            REQUIRE_CALL(*datastore, executeRpc("/example:launch-nukes", DatastoreAccess::Tree{})).RETURN(DatastoreAccess::Tree{});
+            REQUIRE_CALL(*datastore, execute("/example:launch-nukes", DatastoreAccess::Tree{})).RETURN(DatastoreAccess::Tree{});
             boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{exec_{}});
         }
 
diff --git a/tests/prepare.cpp b/tests/prepare.cpp
index 139ef4b..d56a488 100644
--- a/tests/prepare.cpp
+++ b/tests/prepare.cpp
@@ -28,14 +28,14 @@
     SECTION("rpc")
     {
         input = "prepare example:fire";
-        expected.m_path.m_nodes.push_back({module_{"example"}, rpcNode_{"fire"}});
+        expected.m_path.m_nodes.emplace_back(module_{"example"}, rpcNode_{"fire"});
     }
 
     SECTION("action")
     {
         input = "prepare example:port[name='eth0']/shutdown";
-        expected.m_path.m_nodes.push_back({module_{"example"}, listElement_{"port", {{"name", std::string{"eth0"}}}}});
-        expected.m_path.m_nodes.push_back({actionNode_{"shutdown"}});
+        expected.m_path.m_nodes.emplace_back(module_{"example"}, listElement_{"port", {{"name", std::string{"eth0"}}}});
+        expected.m_path.m_nodes.emplace_back(actionNode_{"shutdown"});
     }
 
     command_ command = parser.parseCommand(input, errorStream);
diff --git a/tests/set_value_completion.cpp b/tests/set_value_completion.cpp
index dd36e34..f4d8124 100644
--- a/tests/set_value_completion.cpp
+++ b/tests/set_value_completion.cpp
@@ -29,6 +29,7 @@
     schema->addIdentity(identityRef_{"mod", "food"}, identityRef_{"mod", "spaghetti"});
     schema->addIdentity(identityRef_{"mod", "pizza"}, identityRef_{"pizza-module", "hawaii"});
     schema->addLeaf("/", "mod:foodIdentRef", yang::IdentityRef{schema->validIdentities("mod", "food")});
+    schema->addLeaf("/", "mod:flags", yang::Bits{{"parity", "zero", "carry", "sign"}});
     auto mockDatastore = std::make_shared<MockDatastoreAccess>();
     // The parser will use DataQuery for key value completion, but I'm not testing that here, so I don't return anything.
     ALLOW_CALL(*mockDatastore, listInstances("/mod:list"))
@@ -88,5 +89,19 @@
         expectedContextLength = 0;
     }
 
+    SECTION("set mod:flags ")
+    {
+        input = "set mod:flags ";
+        expectedCompletions = {"carry", "sign", "parity", "zero"};
+        expectedContextLength = 0;
+    }
+
+    SECTION("set mod:flags ze")
+    {
+        input = "set mod:flags ze";
+        expectedCompletions = {"zero"};
+        expectedContextLength = 2;
+    }
+
     REQUIRE(parser.completeCommand(input, errorStream) == (Completions{expectedCompletions, expectedContextLength}));
 }