Merge "Fix crashes on leafref value printing"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4b2f0bc..4fd1b0d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -154,6 +154,12 @@
 target_compile_definitions(yang-cli PRIVATE YANG_CLI)
 cli_link_required(yang-cli)
 target_link_libraries(yang-cli yangaccess)
+if(CMAKE_CXX_FLAGS MATCHES "-stdlib=libc\\+\\+" AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0)
+    target_link_libraries(yang-cli c++experimental)
+elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.1)
+    target_link_libraries(yang-cli stdc++fs)
+endif()
+
 
 
 include(CTest)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 54f3f95..4227097 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -224,33 +224,6 @@
     }
 };
 
-struct writable_leaf_path_class {
-    template <typename T, typename Iterator, typename Context>
-    void on_success(Iterator const&, Iterator const&, T&, Context const& context)
-    {
-        auto& parserContext = x3::get<parser_context_tag>(context);
-        if (parserContext.currentSchemaPath().m_nodes.empty()) {
-            parserContext.m_errorMsg = "This is not a path to leaf.";
-            _pass(context) = false;
-            return;
-        }
-
-        try {
-            auto lastNode = parserContext.currentSchemaPath().m_nodes.back();
-            auto leaf = std::get<leaf_>(lastNode.m_suffix);
-            auto location = pathWithoutLastNode(parserContext.currentSchemaPath());
-            ModuleNodePair node{lastNode.m_prefix.flat_map([](const auto& it) { return boost::optional<std::string>{it.m_name}; }), leaf.m_name};
-
-            parserContext.m_tmpListKeyLeafPath.m_location = location;
-            parserContext.m_tmpListKeyLeafPath.m_node = node;
-
-        } catch (std::bad_variant_access&) {
-            parserContext.m_errorMsg = "This is not a path to leaf.";
-            _pass(context) = false;
-        }
-    }
-};
-
 struct set_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/ast_path.hpp b/src/ast_path.hpp
index 83727ff..e097a0c 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -135,6 +135,11 @@
     void pushFragment(const dataNode_& fragment);
 };
 
+enum class WritableOps {
+    Yes,
+    No
+};
+
 std::string nodeToSchemaString(decltype(dataPath_::m_nodes)::value_type node);
 
 std::string pathToDataString(const dataPath_& path, Prefixes prefixes);
diff --git a/src/cli.cpp b/src/cli.cpp
index b036ebc..63ef4c3 100644
--- a/src/cli.cpp
+++ b/src/cli.cpp
@@ -26,18 +26,25 @@
   -d <datastore>   can be "running" or "startup" [default: running])";
 #elif defined(YANG_CLI)
 #include <boost/spirit/home/x3.hpp>
+#include <filesystem>
 #include "yang_access.hpp"
 #define PROGRAM_NAME "yang-cli"
 static const auto usage = R"(CLI interface for creating local YANG data instances
 
+  The <schema_file_or_module_name> argument is treated as a file name if a file
+  with such a path exists, otherwise it's treated as a module name. Search dirs
+  will be used to find a schema for that module.
+
 Usage:
-  yang-cli [-s <search_dir>] [-e enable_features]... <schema_file>...
+  yang-cli [--configonly] [-s <search_dir>] [-e enable_features]... [-i data_file]... <schema_file_or_module_name>...
   yang-cli (-h | --help)
   yang-cli --version
 
 Options:
   -s <search_dir>       Set search for schema lookup
-  -e <enable_features>  Feature to enable after modules are loaded. This option can be supplied more than once. Format: <module_name>:<feature>)";
+  -e <enable_features>  Feature to enable after modules are loaded. This option can be supplied more than once. Format: <module_name>:<feature>
+  -i <data_file>        File to import data from
+  --configonly          Disable editing of operational data)";
 #else
 #error "Unknown CLI backend"
 #endif
@@ -51,6 +58,7 @@
                                true,
                                PROGRAM_NAME " " NETCONF_CLI_VERSION,
                                true);
+    WritableOps writableOps = WritableOps::No;
 
 #if defined(SYSREPO_CLI)
     auto datastoreType = Datastore::Running;
@@ -68,11 +76,23 @@
     std::cout << "Connected to sysrepo [datastore: " << (datastoreType == Datastore::Startup ? "startup" : "running") << "]" << std::endl;
 #elif defined(YANG_CLI)
     YangAccess datastore;
+    if (args["--configonly"].asBool()) {
+        writableOps = WritableOps::No;
+    } else {
+        writableOps = WritableOps::Yes;
+        std::cout << "ops is writable" << std::endl;
+    }
     if (const auto& search_dir = args["-s"]) {
         datastore.addSchemaDir(search_dir.asString());
     }
-    for (const auto& schemaFile : args["<schema_file>"].asStringList()) {
-        datastore.addSchemaFile(schemaFile);
+    for (const auto& schemaFile : args["<schema_file_or_module_name>"].asStringList()) {
+        if (std::filesystem::exists(schemaFile)) {
+            datastore.addSchemaFile(schemaFile);
+        } else if (schemaFile.find('/') == std::string::npos) { // Module names cannot have a slash
+            datastore.loadModule(schemaFile);
+        } else {
+            std::cerr << "Cannot load YANG module " << schemaFile << "\n";
+        }
     }
     if (const auto& enableFeatures = args["-e"]) {
         namespace x3 = boost::spirit::x3;
@@ -94,12 +114,17 @@
 
         }
     }
+    if (const auto& dataFiles = args["-i"]) {
+        for (const auto& dataFile : dataFiles.asStringList()) {
+            datastore.addDataFile(dataFile);
+        }
+    }
 #else
 #error "Unknown CLI backend"
 #endif
 
     auto dataQuery = std::make_shared<DataQuery>(datastore);
-    Parser parser(datastore.schema(), dataQuery);
+    Parser parser(datastore.schema(), writableOps, dataQuery);
 
     using replxx::Replxx;
 
diff --git a/src/parser.cpp b/src/parser.cpp
index bf00a7e..b5878bf 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -14,10 +14,11 @@
 
 InvalidCommandException::~InvalidCommandException() = default;
 
-Parser::Parser(const std::shared_ptr<const Schema> schema, const std::shared_ptr<const DataQuery> dataQuery)
+Parser::Parser(const std::shared_ptr<const Schema> schema, WritableOps writableOps, const std::shared_ptr<const DataQuery> dataQuery)
     : m_schema(schema)
     , m_dataquery(dataQuery)
     , m_curDir({Scope::Absolute, {}})
+    , m_writableOps(writableOps)
 {
 }
 
@@ -36,7 +37,8 @@
 
     auto grammar =
             x3::with<parser_context_tag>(ctx)[
-            x3::with<x3::error_handler_tag>(std::ref(errorHandler))[command]
+            x3::with<x3::error_handler_tag>(std::ref(errorHandler))[
+            x3::with<writableOps_tag>(m_writableOps)[command]]
     ];
     bool result = x3::phrase_parse(it, line.end(), grammar, x3::space, parsedCommand);
 
@@ -58,7 +60,8 @@
 
     auto grammar =
             x3::with<parser_context_tag>(ctx)[
-            x3::with<x3::error_handler_tag>(std::ref(errorHandler))[command]
+            x3::with<x3::error_handler_tag>(std::ref(errorHandler))[
+            x3::with<writableOps_tag>(m_writableOps)[command]]
     ];
     x3::phrase_parse(it, line.end(), grammar, x3::space, parsedCommand);
 
diff --git a/src/parser.hpp b/src/parser.hpp
index 14daa9e..1e8cdbb 100644
--- a/src/parser.hpp
+++ b/src/parser.hpp
@@ -33,7 +33,7 @@
 
 class Parser {
 public:
-    Parser(const std::shared_ptr<const Schema> schema, const std::shared_ptr<const DataQuery> dataQuery = nullptr);
+    Parser(const std::shared_ptr<const Schema> schema, WritableOps writableOps = WritableOps::No, const std::shared_ptr<const DataQuery> dataQuery = nullptr);
     command_ parseCommand(const std::string& line, std::ostream& errorStream);
     void changeNode(const dataPath_& name);
     std::string currentNode() const;
@@ -44,4 +44,5 @@
     const std::shared_ptr<const Schema> m_schema;
     const std::shared_ptr<const DataQuery> m_dataquery;
     dataPath_ m_curDir;
+    const WritableOps m_writableOps;
 };
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index c89b9df..39e2797 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -14,7 +14,6 @@
 
 namespace x3 = boost::spirit::x3;
 
-x3::rule<writable_leaf_path_class, dataPath_> const writableLeafPath = "writableLeafPath";
 x3::rule<presenceContainerPath_class, dataPath_> const presenceContainerPath = "presenceContainerPath";
 x3::rule<listInstancePath_class, dataPath_> const listInstancePath = "listInstancePath";
 x3::rule<leafListElementPath_class, dataPath_> const leafListElementPath = "leafListElementPath";
@@ -389,6 +388,36 @@
     return schema.isConfig(path);
 };
 
+struct writableOps_tag;
+
+PathParser<PathParserMode::DataPath, CompletionMode::Data> const dataPathFilterConfigFalse{filterConfigFalse};
+
+struct WritableLeafPath : x3::parser<WritableLeafPath> {
+    using attribute_type = dataPath_;
+    template <typename It, typename Ctx, typename RCtx, typename Attr>
+    static bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, Attr& attr)
+    {
+        bool res;
+        if (x3::get<writableOps_tag>(ctx) == WritableOps::Yes) {
+            res = dataPath.parse(begin, end, ctx, rctx, attr);
+        } else {
+            res = dataPathFilterConfigFalse.parse(begin, end, ctx, rctx, attr);
+        }
+        if (!res) {
+            return false;
+        }
+
+        if (attr.m_nodes.empty() || !std::holds_alternative<leaf_>(attr.m_nodes.back().m_suffix)) {
+            auto& parserContext = x3::get<parser_context_tag>(ctx);
+            parserContext.m_errorMsg = "This is not a path to leaf.";
+            return false;
+        }
+
+        return true;
+    }
+
+} writableLeafPath;
+
 auto const writableLeafPath_def =
     PathParser<PathParserMode::DataPath, CompletionMode::Data>{filterConfigFalse};
 
@@ -414,7 +443,6 @@
 BOOST_SPIRIT_DEFINE(keyValue)
 BOOST_SPIRIT_DEFINE(key_identifier)
 BOOST_SPIRIT_DEFINE(listSuffix)
-BOOST_SPIRIT_DEFINE(writableLeafPath)
 BOOST_SPIRIT_DEFINE(presenceContainerPath)
 BOOST_SPIRIT_DEFINE(listInstancePath)
 BOOST_SPIRIT_DEFINE(leafListElementPath)
diff --git a/src/yang_access.cpp b/src/yang_access.cpp
index 7e7238e..d5c0ade 100644
--- a/src/yang_access.cpp
+++ b/src/yang_access.cpp
@@ -1,5 +1,6 @@
 #include <boost/algorithm/string/predicate.hpp>
 #include <experimental/iterator>
+#include <fstream>
 #include <iostream>
 #include <libyang/Tree_Data.hpp>
 #include <libyang/libyang.h>
@@ -278,6 +279,11 @@
     return "";
 }
 
+void YangAccess::loadModule(const std::string& name)
+{
+    m_schema->loadModule(name);
+}
+
 void YangAccess::addSchemaFile(const std::string& path)
 {
     m_schema->addSchemaFile(path.c_str());
@@ -292,3 +298,25 @@
 {
     m_schema->enableFeature(module, feature);
 }
+
+void YangAccess::addDataFile(const std::string& path)
+{
+    std::ifstream fs(path);
+    char firstChar;
+    fs >> firstChar;
+
+    std::cout << "Parsing \"" << path << "\" as " << (firstChar == '{' ? "JSON" : "XML") << "...\n";
+    auto dataNode = lyd_parse_path(m_ctx.get(), path.c_str(), firstChar == '{' ? LYD_JSON : LYD_XML, LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB | LYD_OPT_TRUSTED);
+
+    if (!dataNode) {
+        throw std::runtime_error("Supplied data file " + path + " couldn't be parsed.");
+    }
+
+    if (!m_datastore) {
+        m_datastore = lyWrap(dataNode);
+    } else {
+        lyd_merge(m_datastore.get(), dataNode, LYD_OPT_DESTRUCT);
+    }
+
+    validate();
+}
diff --git a/src/yang_access.hpp b/src/yang_access.hpp
index 7fa562c..aaabd4b 100644
--- a/src/yang_access.hpp
+++ b/src/yang_access.hpp
@@ -37,8 +37,10 @@
     void enableFeature(const std::string& module, const std::string& feature);
     std::string dump(const DataFormat format) const override;
 
+    void loadModule(const std::string& name);
     void addSchemaFile(const std::string& path);
     void addSchemaDir(const std::string& path);
+    void addDataFile(const std::string& path);
 
 private:
     std::vector<ListInstance> listInstances(const std::string& path) override;
diff --git a/tests/keyvalue_completion.cpp b/tests/keyvalue_completion.cpp
index 3a1656d..dd826f4 100644
--- a/tests/keyvalue_completion.cpp
+++ b/tests/keyvalue_completion.cpp
@@ -33,7 +33,7 @@
         .RETURN(schema);
     auto dataQuery = std::make_shared<DataQuery>(*mockDatastore);
     expectation.reset();
-    Parser parser(schema, dataQuery);
+    Parser parser(schema, WritableOps::No, dataQuery);
     std::string input;
     std::ostringstream errorStream;
 
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 3002ed7..249dc8c 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -52,7 +52,7 @@
         .RETURN(schema);
     auto dataQuery = std::make_shared<DataQuery>(*mockDatastore);
     expectation.reset();
-    Parser parser(schema, dataQuery);
+    Parser parser(schema, WritableOps::No, dataQuery);
     std::string input;
     std::ostringstream errorStream;
 
diff --git a/tests/set_value_completion.cpp b/tests/set_value_completion.cpp
index c3ad64a..dd36e34 100644
--- a/tests/set_value_completion.cpp
+++ b/tests/set_value_completion.cpp
@@ -38,7 +38,7 @@
     auto expectation = NAMED_REQUIRE_CALL(*mockDatastore, schema())
         .RETURN(schema);
     auto dataQuery = std::make_shared<DataQuery>(*mockDatastore);
-    Parser parser(schema, dataQuery);
+    Parser parser(schema, WritableOps::No, dataQuery);
     std::string input;
     std::ostringstream errorStream;