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;