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