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;
+};
+}