Request YANG schemas via sysrepo

The std::function that is fed into YangSchema::registerModuleCallback
does not have the same signature that libyang requires, because we would
have to include libyang in the sysrepo_access header file. We don't want this
class to know about libyang, so instead, we use an intermediate function,
that has string arguments and a string return type. In YangSchema, this
function is wrapped with another function, that has the required signature.

Change-Id: Ibd2dcb89344cf1454ba0b3970cac0c8e88ffa20d
diff --git a/src/main.cpp b/src/main.cpp
index a30cec1..f263b42 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -19,7 +19,7 @@
     R"(CLI interface to remote NETCONF hosts
 
 Usage:
-  netconf-cli <path-to-yang-schema>...
+  netconf-cli
   netconf-cli (-h | --help)
   netconf-cli --version
 )";
@@ -34,16 +34,8 @@
                                true);
     std::cout << "Welcome to netconf-cli" << std::endl;
 
-    auto yangschema = std::make_shared<YangSchema>();
-    Parser parser(yangschema);
-
     SysrepoAccess datastore("netconf-cli");
-
-    for (auto it : args.at("<path-to-yang-schema>").asStringList()) {
-        auto dir = std::experimental::filesystem::absolute(it).remove_filename();
-        yangschema->addSchemaDirectory(dir.c_str());
-        yangschema->addSchemaFile(it.c_str());
-    }
+    Parser parser(datastore.schema());
 
     while (true) {
         std::cout << parser.currentNode() << "> ";
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index af553d9..d80f539 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -8,6 +8,7 @@
 
 #include <sysrepo-cpp/Session.h>
 #include "sysrepo_access.hpp"
+#include "yang_schema.hpp"
 
 leaf_data_ leafValueFromVal(const S_Val& value)
 {
@@ -72,8 +73,16 @@
 
 SysrepoAccess::SysrepoAccess(const std::string& appname)
     : m_connection(new Connection(appname.c_str()))
+    , m_schema(new YangSchema())
 {
     m_session = std::make_shared<Session>(m_connection);
+    m_schema->registerModuleCallback([this](const char* moduleName, const char* revision, const char* submodule) {
+        return fetchSchema(moduleName, revision, submodule);
+    });
+
+    for (const auto& it : listImplementedSchemas()) {
+        m_schema->loadModule(it);
+    }
 }
 
 std::map<std::string, leaf_data_> SysrepoAccess::getItems(const std::string& path)
@@ -121,3 +130,29 @@
 {
     m_session->commit();
 }
+
+std::string SysrepoAccess::fetchSchema(const char* module, const char* revision, const char* submodule)
+{
+    auto schema = m_session->get_schema(module, revision, submodule, SR_SCHEMA_YANG); // FIXME: maybe we should use get_submodule_schema for submodules?
+    if (schema.empty())
+        throw std::runtime_error(std::string("Module ") + module + " not available");
+
+    return schema;
+}
+
+std::vector<std::string> SysrepoAccess::listImplementedSchemas()
+{
+    std::vector<std::string> res;
+    auto schemas = m_session->list_schemas();
+    for (unsigned int i = 0; i < schemas->schema_cnt(); i++) {
+        auto schema = schemas->schema(i);
+        if (schema->implemented())
+            res.push_back(schema->module_name());
+    }
+    return res;
+}
+
+std::shared_ptr<Schema> SysrepoAccess::schema()
+{
+    return m_schema;
+}
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index 3630961..1ca813d 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -18,6 +18,8 @@
 
 class Connection;
 class Session;
+class Schema;
+class YangSchema;
 
 class SysrepoAccess : public DatastoreAccess {
 public:
@@ -27,10 +29,15 @@
     void setLeaf(const std::string& path, leaf_data_ value) override;
     void createPresenceContainer(const std::string& path) override;
     void deletePresenceContainer(const std::string& path) override;
+    std::string fetchSchema(const char* module, const char* revision, const char* submodule);
+    std::vector<std::string> listImplementedSchemas();
 
     void commitChanges() override;
 
+    std::shared_ptr<Schema> schema();
+
 private:
     std::shared_ptr<Connection> m_connection;
     std::shared_ptr<Session> m_session;
+    std::shared_ptr<YangSchema> m_schema;
 };
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 68a4cf2..946162b 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -51,7 +51,7 @@
 }
 
 YangSchema::YangSchema()
-    : m_context(std::make_shared<libyang::Context>())
+    : m_context(std::make_shared<libyang::Context>(nullptr, LY_CTX_DISABLE_SEARCHDIRS | LY_CTX_DISABLE_SEARCHDIR_CWD))
 {
 }
 
@@ -223,3 +223,25 @@
     }
     return res;
 }
+
+void YangSchema::loadModule(const std::string& moduleName)
+{
+    m_context->load_module(moduleName.c_str());
+}
+
+void YangSchema::registerModuleCallback(const std::function<std::string(const char*, const char*, const char*)>& clb)
+{
+    auto lambda = [clb](const char* mod_name, const char* mod_revision, const char* submod_name, const char* submod_revision) {
+        (void)submod_revision;
+        auto moduleSource = clb(mod_name, mod_revision, submod_name);
+        if (moduleSource.empty()) {
+            return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, nullptr};
+        }
+        return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, strdup(moduleSource.c_str())};
+    };
+
+    auto deleter = [](void* data) {
+        free(data);
+    };
+    m_context->add_missing_module_callback(lambda, deleter);
+}
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index 51c3be0..d100302 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -8,6 +8,7 @@
 
 #pragma once
 
+#include <functional>
 #include <set>
 #include <stdexcept>
 #include <unordered_map>
@@ -40,6 +41,11 @@
     yang::LeafDataTypes leafType(const path_& location, const ModuleNodePair& node) const override;
     std::set<std::string> childNodes(const path_& path) const override;
 
+    void registerModuleCallback(const std::function<std::string(const char*, const char*, const char*)>& clb);
+
+    /** @short Loads a module called moduleName. */
+    void loadModule(const std::string& moduleName);
+
     /** @short Adds a new module passed as a YANG string. */
     void addSchemaString(const char* schema);