Add move command for moving (leaf)list instances

Change-Id: I0bff25209f74601a450c12a810200b3c124d65f2
diff --git a/src/ast_commands.cpp b/src/ast_commands.cpp
index c349e8c..1b89a6c 100644
--- a/src/ast_commands.cpp
+++ b/src/ast_commands.cpp
@@ -36,3 +36,8 @@
 {
     return this->m_path == b.m_path;
 }
+
+bool move_::operator==(const move_& other) const
+{
+    return this->m_source == other.m_source && this->m_destination == other.m_destination;
+}
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index 5759eda..9c56d4f 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -11,6 +11,7 @@
 #include <boost/spirit/home/x3/support/ast/position_tagged.hpp>
 #include "ast_path.hpp"
 #include "ast_values.hpp"
+#include "yang_move.hpp"
 
 namespace x3 = boost::spirit::x3;
 
@@ -178,8 +179,35 @@
     Datastore m_destination;
 };
 
+enum class MoveMode {
+    Begin,
+    End,
+    Before,
+    After
+};
+
+struct move_ : x3::position_tagged {
+    static constexpr auto name = "move";
+    static constexpr auto shortHelp = "move - move (leaf)list instances around";
+    static constexpr auto longHelp = R"(
+    move <list-instance-path> begin
+    move <list-instance-path> end
+    move <list-instance-path> before <key>
+    move <list-instance-path> after <key>
+
+    Usage:
+        /> move mod:leaflist['abc'] begin
+        /> move mod:leaflist['def'] after 'abc'
+        /> move mod:interfaces['eth0'] after ['eth1'])";
+    bool operator==(const move_& b) const;
+
+    dataPath_ m_source;
+
+    std::variant<yang::move::Absolute, yang::move::Relative> m_destination;
+};
+
 struct help_;
-using CommandTypes = boost::mpl::vector<cd_, commit_, copy_, create_, delete_, describe_, discard_, get_, help_, ls_, set_>;
+using CommandTypes = boost::mpl::vector<cd_, commit_, copy_, create_, delete_, describe_, discard_, get_, help_, ls_, move_, set_>;
 struct help_ : x3::position_tagged {
     static constexpr auto name = "help";
     static constexpr auto shortHelp = "help - Print help for commands.";
@@ -225,3 +253,4 @@
 BOOST_FUSION_ADAPT_STRUCT(discard_)
 BOOST_FUSION_ADAPT_STRUCT(get_, m_path)
 BOOST_FUSION_ADAPT_STRUCT(copy_, m_source, m_destination)
+BOOST_FUSION_ADAPT_STRUCT(move_, m_source, m_destination)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index f344ac2..1f7dea6 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -94,7 +94,11 @@
             parserContext.m_errorMsg += ".";
 
             _pass(context) = false;
+            return;
         }
+
+        // Clean up after listSuffix, in case someone wants to parse more listSuffixes
+        parserContext.m_tmpListKeys.clear();
     }
 
     template <typename Iterator, typename Exception, typename Context>
@@ -268,6 +272,8 @@
 
 struct copy_class;
 
+struct move_class;
+
 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)
@@ -288,6 +294,7 @@
     {
         auto& parserContext = x3::get<parser_context_tag>(context);
         parserContext.resetPath();
+        parserContext.m_tmpListPath = dataPath_{};
         parserContext.m_tmpListKeys.clear();
         parserContext.m_suggestions.clear();
     }
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index 287ba94..05c1296 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -11,6 +11,7 @@
 #include <map>
 #include <optional>
 #include <string>
+#include "yang_move.hpp"
 #include "ast_values.hpp"
 #include "list_instance.hpp"
 
@@ -37,6 +38,8 @@
 
 class Schema;
 
+struct dataPath_;
+
 class DatastoreAccess {
 public:
     using Tree = std::vector<std::pair<std::string, leaf_data_>>;
@@ -49,6 +52,7 @@
     virtual void deleteListInstance(const std::string& path) = 0;
     virtual void createLeafListInstance(const std::string& path) = 0;
     virtual void deleteLeafListInstance(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 std::shared_ptr<Schema> schema() = 0;
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 253a16a..9f0baf2 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -27,6 +27,7 @@
 x3::rule<describe_class, describe_> const describe = "describe";
 x3::rule<help_class, help_> const help = "help";
 x3::rule<copy_class, copy_> const copy = "copy";
+x3::rule<move_class, move_> const move = "move";
 x3::rule<command_class, command_> const command = "command";
 
 x3::rule<createCommandSuggestions_class, x3::unused_type> const createCommandSuggestions = "createCommandSuggestions";
@@ -134,11 +135,105 @@
 auto const describe_def =
     describe_::name >> space_separator > (dataPathListEnd | anyPath);
 
+struct mode_table : x3::symbols<MoveMode> {
+    mode_table()
+    {
+        add
+            ("after", MoveMode::After)
+            ("before", MoveMode::Before)
+            ("begin", MoveMode::Begin)
+            ("end", MoveMode::End);
+    }
+} const mode_table;
+
+struct move_absolute_table : x3::symbols<yang::move::Absolute> {
+    move_absolute_table()
+    {
+        add
+            ("begin", yang::move::Absolute::Begin)
+            ("end", yang::move::Absolute::End);
+    }
+} const move_absolute_table;
+
+struct move_relative_table : x3::symbols<yang::move::Relative::Position> {
+    move_relative_table()
+    {
+        add
+            ("before", yang::move::Relative::Position::Before)
+            ("after", yang::move::Relative::Position::After);
+    }
+} const move_relative_table;
+
+struct move_args : x3::parser<move_args> {
+    using attribute_type = move_;
+    template <typename It, typename Ctx, typename RCtx>
+    bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, move_& attr) const
+    {
+        ParserContext& parserContext = x3::get<parser_context_tag>(ctx);
+        dataPath_ movePath;
+        auto movePathGrammar = listInstancePath | leafListElementPath;
+        auto res = movePathGrammar.parse(begin, end, ctx, rctx, attr.m_source);
+        if (!res) {
+            parserContext.m_errorMsg = "Expected source path here:";
+            return false;
+        }
+
+        // Try absolute move first.
+        res = (space_separator >> move_absolute_table).parse(begin, end, ctx, rctx, attr.m_destination);
+        if (res) {
+            // Absolute move parsing succeeded, we don't need to parse anything else.
+            return true;
+        }
+
+        // If absolute move didn't succeed, try relative.
+        attr.m_destination = yang::move::Relative{};
+        res = (space_separator >> move_relative_table).parse(begin, end, ctx, rctx, std::get<yang::move::Relative>(attr.m_destination).m_position);
+
+        if (!res) {
+            parserContext.m_errorMsg = "Expected a move position (begin, end, before, after) here:";
+            return false;
+        }
+
+        if (std::holds_alternative<leafListElement_>(attr.m_source.m_nodes.back().m_suffix)) {
+            leaf_data_ value;
+            res = (space_separator >> leaf_data).parse(begin, end, ctx, rctx, value);
+            if (res) {
+                std::get<yang::move::Relative>(attr.m_destination).m_path = {{".", value}};
+            }
+        } else {
+            ListInstance listInstance;
+            // The source list instance will be stored inside the parser context path.
+            // The source list instance will be full data path (with keys included).
+            // However, m_tmpListPath is supposed to store a path with a list without the keys.
+            // So, I pop the last listElement_ (which has the keys) and put in a list_ (which doesn't have the keys).
+            // Example: /mod:cont/protocols[name='ftp'] gets turned into /mod:cont/protocols
+            parserContext.m_tmpListPath = parserContext.currentDataPath();
+            parserContext.m_tmpListPath.m_nodes.pop_back();
+            auto list = list_{std::get<listElement_>(attr.m_source.m_nodes.back().m_suffix).m_name};
+            parserContext.m_tmpListPath.m_nodes.push_back(dataNode_{attr.m_source.m_nodes.back().m_prefix, list});
+
+            res = (space_separator >> listSuffix).parse(begin, end, ctx, rctx, listInstance);
+            if (res) {
+                std::get<yang::move::Relative>(attr.m_destination).m_path = listInstance;
+            }
+        }
+
+        if (!res) {
+            parserContext.m_errorMsg = "Expected a destination here:";
+        }
+
+        return res;
+    }
+} const move_args;
+
+auto const move_def =
+    move_::name >> space_separator >> move_args;
+
 auto const createCommandSuggestions_def =
     x3::eps;
 
 auto const command_def =
-    createCommandSuggestions >> x3::expect[cd | copy | create | delete_rule | set | commit | get | ls | discard | describe | help];
+    createCommandSuggestions >> x3::expect[cd | copy | create | delete_rule | set | commit | get | ls | discard | describe | help | move];
 
 #if __clang__
 #pragma GCC diagnostic pop
@@ -155,5 +250,6 @@
 BOOST_SPIRIT_DEFINE(describe)
 BOOST_SPIRIT_DEFINE(help)
 BOOST_SPIRIT_DEFINE(copy)
+BOOST_SPIRIT_DEFINE(move)
 BOOST_SPIRIT_DEFINE(command)
 BOOST_SPIRIT_DEFINE(createCommandSuggestions)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 2d69e63..80e7a7a 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -195,6 +195,11 @@
     }
 }
 
+void Interpreter::operator()(const move_& move) const
+{
+    m_datastore.moveItem(pathToDataString(move.m_source, Prefixes::WhenNeeded), move.m_destination);
+}
+
 struct commandLongHelpVisitor : boost::static_visitor<const char*> {
     template <typename T>
     auto constexpr operator()(boost::type<T>) const
diff --git a/src/interpreter.hpp b/src/interpreter.hpp
index 0dfe2b6..bd90a60 100644
--- a/src/interpreter.hpp
+++ b/src/interpreter.hpp
@@ -26,6 +26,7 @@
     void operator()(const discard_&) const;
     void operator()(const help_&) const;
     void operator()(const copy_& copy) const;
+    void operator()(const move_& move) const;
 
 private:
     template <typename T>
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 5e2e52a..e4a1859 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -140,6 +140,40 @@
     doEditFromDataNode(node);
 }
 
+struct impl_toYangInsert {
+    std::string operator()(yang::move::Absolute& absolute)
+    {
+        return absolute == yang::move::Absolute::Begin ? "first" : "last";
+    }
+    std::string operator()(yang::move::Relative& relative)
+    {
+        return relative.m_position == yang::move::Relative::Position::After ? "after" : "before";
+    }
+};
+
+std::string toYangInsert(std::variant<yang::move::Absolute, yang::move::Relative> move)
+{
+    return std::visit(impl_toYangInsert{}, move);
+}
+
+void NetconfAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
+{
+    auto node = m_schema->dataNodeFromPath(source);
+    auto sourceNode = *(node->find_path(source.c_str())->data().begin());
+    auto yangModule = m_schema->getYangModule("yang");
+    sourceNode->insert_attr(yangModule, "insert", toYangInsert(move).c_str());
+
+    if (std::holds_alternative<yang::move::Relative>(move)) {
+        auto relative = std::get<yang::move::Relative>(move);
+        if (m_schema->nodeType(source) == yang::NodeTypes::LeafList) {
+            sourceNode->insert_attr(yangModule, "value", leafDataToString(relative.m_path.at(".")).c_str());
+        } else {
+            sourceNode->insert_attr(yangModule, "key", instanceToString(node->node_module()->name(), relative.m_path).c_str());
+        }
+    }
+    doEditFromDataNode(sourceNode);
+}
+
 void NetconfAccess::doEditFromDataNode(std::shared_ptr<libyang::Data_Node> dataNode)
 {
     auto data = dataNode->print_mem(LYD_XML, 0);
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index 0cc4eb4..3875911 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -41,6 +41,7 @@
     void deleteListInstance(const std::string& path) override;
     void createLeafListInstance(const std::string& path) override;
     void deleteLeafListInstance(const std::string& path) override;
+    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;
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 3278766..d8a64da 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -289,6 +289,36 @@
     }
 }
 
+struct impl_toSrMoveOp {
+    sr_move_position_t operator()(yang::move::Absolute& absolute)
+    {
+        return absolute == yang::move::Absolute::Begin ? SR_MOVE_FIRST : SR_MOVE_LAST;
+    }
+    sr_move_position_t operator()(yang::move::Relative& relative)
+    {
+        return relative.m_position == yang::move::Relative::Position::After ? SR_MOVE_AFTER : SR_MOVE_BEFORE;
+    }
+};
+
+sr_move_position_t toSrMoveOp(std::variant<yang::move::Absolute, yang::move::Relative> move)
+{
+    return std::visit(impl_toSrMoveOp{}, move);
+}
+
+void SysrepoAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
+{
+    std::string destPathStr;
+    if (std::holds_alternative<yang::move::Relative>(move)) {
+        auto relative = std::get<yang::move::Relative>(move);
+        if (m_schema->nodeType(source) == yang::NodeTypes::LeafList) {
+            destPathStr = stripLeafListValueFromPath(source) + "[.='" + leafDataToString(relative.m_path.at(".")) + "']";
+        } else {
+            destPathStr = stripLastListInstanceFromPath(source) + instanceToString(m_schema->dataNodeFromPath(source)->node_module()->name(), relative.m_path);
+        }
+    }
+    m_session->move_item(source.c_str(), toSrMoveOp(move), destPathStr.c_str());
+}
+
 void SysrepoAccess::commitChanges()
 {
     try {
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index 0ca7587..6e66eea 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -36,6 +36,7 @@
     void deleteListInstance(const std::string& path) override;
     void createLeafListInstance(const std::string& path) override;
     void deleteLeafListInstance(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;
 
     std::shared_ptr<Schema> schema() override;
diff --git a/src/utils.cpp b/src/utils.cpp
index 941fa4e..cf789b7 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -235,3 +235,20 @@
     res.erase(res.find_last_of('['));
     return res;
 }
+
+std::string stripLastListInstanceFromPath(const std::string& path)
+{
+    auto res = path;
+    res.erase(res.find_first_of('[', res.find_last_of('/')));
+    return res;
+}
+
+std::string instanceToString(const std::string& modName, const ListInstance& instance)
+{
+    std::string instanceStr;
+    for (const auto& [key, value] : instance) {
+        using namespace std::string_literals;
+        instanceStr += "[" + modName + ":" + key + "=" + escapeListKeyString(leafDataToString(value)) + "]";
+    }
+    return instanceStr;
+}
diff --git a/src/utils.hpp b/src/utils.hpp
index ecbf919..0196ff8 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -26,3 +26,6 @@
 std::string leafDataToString(const leaf_data_ value);
 schemaPath_ anyPathToSchemaPath(const boost::variant<dataPath_, schemaPath_, module_>& path);
 std::string stripLeafListValueFromPath(const std::string& path);
+std::string stripLastListInstanceFromPath(const std::string& path);
+// The string includes module name prefixes.
+std::string instanceToString(const std::string& modName, const ListInstance& instance);
diff --git a/src/yang_move.hpp b/src/yang_move.hpp
new file mode 100644
index 0000000..0b1310c
--- /dev/null
+++ b/src/yang_move.hpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+#pragma once
+#include <variant>
+#include "list_instance.hpp"
+
+namespace yang::move {
+enum class Absolute {
+    Begin,
+    End
+};
+struct Relative {
+    bool operator==(const yang::move::Relative& other) const
+    {
+        return this->m_position == other.m_position && this->m_path == other.m_path;
+    }
+    enum class Position {
+        Before,
+        After
+    } m_position;
+    ListInstance m_path;
+};
+}