Add dump command
Change-Id: If38d01ebb8788090f91a76daf6b7fc9add0320b7
diff --git a/src/ast_commands.cpp b/src/ast_commands.cpp
index 1b89a6c..425249e 100644
--- a/src/ast_commands.cpp
+++ b/src/ast_commands.cpp
@@ -41,3 +41,8 @@
{
return this->m_source == other.m_source && this->m_destination == other.m_destination;
}
+
+bool dump_::operator==(const dump_& other) const
+{
+ return this->m_format == other.m_format;
+}
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index c4f9f48..99d9da0 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -206,8 +206,22 @@
std::variant<yang::move::Absolute, yang::move::Relative> m_destination;
};
+struct dump_ : x3::position_tagged {
+ static constexpr auto name = "dump";
+ static constexpr auto shortHelp = "dump - dump entire content of the datastore";
+ static constexpr auto longHelp = R"(
+ dump xml|json
+
+ Usage:
+ /> dump xml
+ /> dump json)";
+ bool operator==(const dump_& other) const;
+
+ DataFormat m_format;
+};
+
struct help_;
-using CommandTypes = boost::mpl::vector<cd_, commit_, copy_, create_, delete_, describe_, discard_, get_, help_, ls_, move_, set_>;
+using CommandTypes = boost::mpl::vector<cd_, commit_, copy_, create_, delete_, describe_, discard_, dump_, get_, help_, ls_, move_, set_>;
struct help_ : x3::position_tagged {
static constexpr auto name = "help";
static constexpr auto shortHelp = "help - Print help for commands.";
@@ -254,3 +268,4 @@
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)
+BOOST_FUSION_ADAPT_STRUCT(dump_, m_format)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 1f7dea6..54f3f95 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -274,6 +274,8 @@
struct move_class;
+struct dump_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)
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index b159ab3..5a9cdd1 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -56,6 +56,7 @@
virtual void commitChanges() = 0;
virtual void discardChanges() = 0;
virtual void copyConfig(const Datastore source, const Datastore destination) = 0;
+ virtual std::string dump(const DataFormat format) const = 0;
private:
friend class DataQuery;
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 3cc74f2..8d18340 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -28,6 +28,7 @@
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<dump_class, dump_> const dump = "dump";
x3::rule<command_class, command_> const command = "command";
x3::rule<createCommandSuggestions_class, x3::unused_type> const createCommandSuggestions = "createCommandSuggestions";
@@ -229,11 +230,24 @@
auto const move_def =
move_::name >> space_separator >> move_args;
+struct format_table : x3::symbols<DataFormat> {
+ format_table()
+ {
+ add
+ ("xml", DataFormat::Xml)
+ ("json", DataFormat::Json);
+ }
+} const format_table;
+
+
+auto const dump_def =
+ dump_::name >> space_separator >> format_table;
+
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 | move];
+ createCommandSuggestions >> x3::expect[cd | copy | create | delete_rule | set | commit | get | ls | discard | describe | help | move | dump];
#if __clang__
#pragma GCC diagnostic pop
@@ -251,5 +265,6 @@
BOOST_SPIRIT_DEFINE(help)
BOOST_SPIRIT_DEFINE(copy)
BOOST_SPIRIT_DEFINE(move)
+BOOST_SPIRIT_DEFINE(dump)
BOOST_SPIRIT_DEFINE(command)
BOOST_SPIRIT_DEFINE(createCommandSuggestions)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 8ee2b55..85833f1 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -195,6 +195,11 @@
m_datastore.moveItem(pathToDataString(move.m_source, Prefixes::WhenNeeded), move.m_destination);
}
+void Interpreter::operator()(const dump_& dump) const
+{
+ std::cout << m_datastore.dump(dump.m_format) << "\n";
+}
+
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 e008375..4b4056e 100644
--- a/src/interpreter.hpp
+++ b/src/interpreter.hpp
@@ -28,6 +28,7 @@
void operator()(const help_&) const;
void operator()(const copy_& copy) const;
void operator()(const move_& move) const;
+ void operator()(const dump_& dump) const;
private:
std::string buildTypeInfo(const std::string& path) const;
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index dccec1c..ecdece0 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -215,3 +215,12 @@
return res;
}
+
+std::string NetconfAccess::dump(const DataFormat format) const
+{
+ auto config = m_session->get();
+ if (!config) {
+ return "";
+ }
+ return config->print_mem(format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
+}
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index 924b2c2..d9830bd 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -45,6 +45,8 @@
std::shared_ptr<Schema> schema() override;
+ std::string dump(const DataFormat format) const override;
+
private:
std::vector<ListInstance> listInstances(const std::string& path) override;
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index e95d740..61ff560 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -432,3 +432,25 @@
return res;
}
+
+std::string SysrepoAccess::dump(const DataFormat format) const
+{
+ std::shared_ptr<libyang::Data_Node> root;
+ auto input = getItems("/");
+ if (input.empty()) {
+ return "";
+ }
+ for (const auto& [k, v] : input) {
+ if (v.type() == typeid(special_) && boost::get<special_>(v).m_value != SpecialValue::PresenceContainer) {
+ continue;
+ }
+ if (!root) {
+ root = m_schema->dataNodeFromPath(k, leafDataToString(v));
+ } else {
+ // Using UPDATE here, because in multi-key list, all of the keys get created with the first key (because they are encoded in the path)
+ // and libyang complains if the node already exists.
+ root->new_path(nullptr, k.c_str(), leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_UPDATE);
+ }
+ }
+ return root->print_mem(format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
+}
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index 3150791..016d49c 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -41,6 +41,7 @@
void discardChanges() override;
void copyConfig(const Datastore source, const Datastore destination) override;
+ std::string dump(const DataFormat format) const override;
private:
std::vector<ListInstance> listInstances(const std::string& path) override;
[[noreturn]] void reportErrors() const;
diff --git a/src/yang_access.cpp b/src/yang_access.cpp
index c509231..7e7238e 100644
--- a/src/yang_access.cpp
+++ b/src/yang_access.cpp
@@ -264,10 +264,10 @@
return res;
}
-std::string impl_dumpConfig(const lyd_node* datastore, LYD_FORMAT format)
+std::string YangAccess::dump(const DataFormat format) const
{
char* output;
- lyd_print_mem(&output, datastore, format, LYP_WITHSIBLINGS);
+ lyd_print_mem(&output, m_datastore.get(), format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
if (output) {
std::string res = output;
@@ -278,16 +278,6 @@
return "";
}
-std::string YangAccess::dumpXML() const
-{
- return impl_dumpConfig(m_datastore.get(), LYD_XML);
-}
-
-std::string YangAccess::dumpJSON() const
-{
- return impl_dumpConfig(m_datastore.get(), LYD_JSON);
-}
-
void YangAccess::addSchemaFile(const std::string& path)
{
m_schema->addSchemaFile(path.c_str());
diff --git a/src/yang_access.hpp b/src/yang_access.hpp
index 9b8a443..7fa562c 100644
--- a/src/yang_access.hpp
+++ b/src/yang_access.hpp
@@ -35,8 +35,7 @@
std::shared_ptr<Schema> schema() override;
void enableFeature(const std::string& module, const std::string& feature);
- std::string dumpXML() const;
- std::string dumpJSON() const;
+ std::string dump(const DataFormat format) const override;
void addSchemaFile(const std::string& path);
void addSchemaDir(const std::string& path);
diff --git a/src/yang_operations.hpp b/src/yang_operations.hpp
index c8dc1a0..fdb9c2e 100644
--- a/src/yang_operations.hpp
+++ b/src/yang_operations.hpp
@@ -30,3 +30,8 @@
Running,
Startup
};
+
+enum class DataFormat {
+ Xml,
+ Json
+};
diff --git a/tests/command_completion.cpp b/tests/command_completion.cpp
index 45eb998..06cedd7 100644
--- a/tests/command_completion.cpp
+++ b/tests/command_completion.cpp
@@ -21,7 +21,7 @@
int expectedContextLength;
SECTION("no prefix")
{
- expectedCompletions = {"cd", "copy", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe", "move"};
+ expectedCompletions = {"cd", "copy", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe", "move", "dump"};
expectedContextLength = 0;
SECTION("no space") {
input = "";
@@ -41,7 +41,7 @@
SECTION("d")
{
input = "d";
- expectedCompletions = {"delete", "discard", "describe"};
+ expectedCompletions = {"delete", "discard", "describe", "dump"};
expectedContextLength = 1;
}
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 57ecf24..e505851 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -97,7 +97,7 @@
{
{
std::ofstream of(testConfigFile);
- of << dumpXML();
+ of << dump(DataFormat::Xml);
}
auto command = std::string(sysrepocfgExecutable) + " --import=" + testConfigFile + " --format=xml --datastore=running example-schema";
REQUIRE(std::system(command.c_str()) == 0);
@@ -218,9 +218,11 @@
SECTION("create presence container")
{
+ REQUIRE(datastore.dump(DataFormat::Json).find("example-schema:pContainer") == std::string::npos);
REQUIRE_CALL(mock, write("/example-schema:pContainer", std::nullopt, ""s));
datastore.createItem("/example-schema:pContainer");
datastore.commitChanges();
+ REQUIRE(datastore.dump(DataFormat::Json).find("example-schema:pContainer") != std::string::npos);
}
SECTION("create/delete a list instance")
@@ -813,6 +815,16 @@
datastore.deleteItem("/example-schema:leafInt32");
}
+ SECTION("two key lists")
+ {
+ REQUIRE_CALL(mock, write("/example-schema:point[x='12'][y='10']", std::nullopt, ""s));
+ REQUIRE_CALL(mock, write("/example-schema:point[x='12'][y='10']/x", std::nullopt, "12"s));
+ REQUIRE_CALL(mock, write("/example-schema:point[x='12'][y='10']/y", std::nullopt, "10"s));
+ datastore.createItem("/example-schema:point[x='12'][y='10']");
+ datastore.commitChanges();
+ REQUIRE(datastore.dump(DataFormat::Json).find("example-schema:point") != std::string::npos);
+ }
+
waitForCompletionAndBitMore(seq1);
}
diff --git a/tests/datastoreaccess_mock.hpp b/tests/datastoreaccess_mock.hpp
index a495b99..e1d365b 100644
--- a/tests/datastoreaccess_mock.hpp
+++ b/tests/datastoreaccess_mock.hpp
@@ -35,6 +35,7 @@
IMPLEMENT_MOCK0(commitChanges);
IMPLEMENT_MOCK0(discardChanges);
IMPLEMENT_MOCK2(copyConfig);
+ IMPLEMENT_CONST_MOCK1(dump);
};
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index fdd8daf..792ac42 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -264,4 +264,14 @@
type string;
}
}
+
+ list point {
+ key "x y";
+ leaf x {
+ type int32;
+ }
+ leaf y {
+ type int32;
+ }
+ }
}