Merge changes Ia9ba2193,I736bf655
* changes:
Remove empty SECTION
Fix leafTypeName returning names of primitive types
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index ab9958a..33715a4 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -120,18 +120,6 @@
return x3::error_handler_result::rethrow;
}
};
-struct list_class {
- template <typename T, typename Iterator, typename Context>
- void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
- {
- auto& parserContext = x3::get<parser_context_tag>(context);
- const Schema& schema = parserContext.m_schema;
-
- if (!schema.isList(parserContext.currentSchemaPath(), {parserContext.m_curModule, ast.m_name})) {
- _pass(context) = false;
- }
- }
-};
struct module_class {
template <typename T, typename Iterator, typename Context>
@@ -150,15 +138,6 @@
}
};
-struct dataNodeList_class {
- template <typename T, typename Iterator, typename Context>
- void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
- {
- auto& parserContext = x3::get<parser_context_tag>(context);
- parserContext.pushPathFragment(ast);
- }
-};
-
struct absoluteStart_class {
template <typename T, typename Iterator, typename Context>
void on_success(Iterator const&, Iterator const&, T&, Context const& context)
@@ -168,10 +147,6 @@
}
};
-struct dataNodesListEnd_class;
-
-struct dataPathListEnd_class;
-
struct discard_class;
struct ls_class;
@@ -193,6 +168,12 @@
{
auto& parserContext = x3::get<parser_context_tag>(context);
const auto& schema = parserContext.m_schema;
+ if (ast.m_nodes.empty()) {
+ parserContext.m_errorMsg = "This container is not a presence container.";
+ _pass(context) = false;
+ return;
+ }
+
try {
boost::optional<std::string> module;
if (ast.m_nodes.back().m_prefix)
@@ -216,6 +197,12 @@
void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
{
auto& parserContext = x3::get<parser_context_tag>(context);
+ if (ast.m_nodes.empty()) {
+ parserContext.m_errorMsg = "This is not a list instance.";
+ _pass(context) = false;
+ return;
+ }
+
if (ast.m_nodes.back().m_suffix.type() != typeid(listElement_)) {
parserContext.m_errorMsg = "This is not a list instance.";
_pass(context) = false;
@@ -255,11 +242,17 @@
}
};
-struct leaf_path_class {
+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 = boost::get<leaf_>(lastNode.m_suffix);
@@ -341,36 +334,6 @@
struct trailingSlash_class;
-struct createPathSuggestions_class {
- template <typename T, typename Iterator, typename Context>
- void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
- {
- auto& parserContext = x3::get<parser_context_tag>(context);
- const auto& schema = parserContext.m_schema;
-
- parserContext.m_completionIterator = begin;
- auto suggestions = schema.availableNodes(parserContext.currentSchemaPath(), Recursion::NonRecursive);
- std::set<Completion> suffixesAdded;
- std::transform(suggestions.begin(), suggestions.end(),
- std::inserter(suffixesAdded, suffixesAdded.end()),
- [&parserContext, &schema](const ModuleNodePair& node) {
- std::string completion = (node.first ? *node.first + ":" : "") + node.second;
-
- if (schema.isLeaf(parserContext.currentSchemaPath(), node)) {
- return Completion{completion + " "};
- }
- if (schema.isContainer(parserContext.currentSchemaPath(), node)) {
- return Completion{completion + "/"};
- }
- if (schema.isList(parserContext.currentSchemaPath(), node)) {
- return Completion{completion, "[", Completion::WhenToAdd::IfFullMatch};
- }
- return Completion{completion};
- });
- parserContext.m_suggestions = suffixesAdded;
- }
-};
-
std::set<Completion> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::map<std::string, leaf_data_> currentSet);
struct createKeySuggestions_class {
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 74a6a17..1b4b49d 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -47,7 +47,7 @@
} const ls_options;
auto const ls_def =
- ls_::name >> *(space_separator >> ls_options) >> -(space_separator >> (dataPathListEnd | anyPath | (module >> "*")));
+ ls_::name >> *(space_separator >> ls_options) >> -(space_separator >> (anyPath | (module >> "*")));
auto const cd_def =
cd_::name >> space_separator > dataPath;
@@ -62,7 +62,7 @@
get_::name >> -(space_separator >> ((dataPathListEnd | dataPath) | (module >> "*")));
auto const set_def =
- set_::name >> space_separator > leafPath > space_separator > leaf_data;
+ set_::name >> space_separator > writableLeafPath > space_separator > leaf_data;
auto const commit_def =
commit_::name >> x3::attr(commit_());
diff --git a/src/parser_context.hpp b/src/parser_context.hpp
index f837934..87706a5 100644
--- a/src/parser_context.hpp
+++ b/src/parser_context.hpp
@@ -10,6 +10,7 @@
#include "completion.hpp"
#include "data_query.hpp"
#include "schema.hpp"
+
struct ParserContext {
ParserContext(const Schema& schema, const std::shared_ptr<const DataQuery> dataQuery, const dataPath_& curDir);
schemaPath_ currentSchemaPath();
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index 6360912..15655d7 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -14,48 +14,87 @@
namespace x3 = boost::spirit::x3;
-x3::rule<dataNodeList_class, decltype(dataPath_::m_nodes)::value_type> const dataNodeList = "dataNodeList";
-x3::rule<dataNodesListEnd_class, decltype(dataPath_::m_nodes)> const dataNodesListEnd = "dataNodesListEnd";
-x3::rule<dataPathListEnd_class, dataPath_> const dataPathListEnd = "dataPathListEnd";
-x3::rule<leaf_path_class, dataPath_> const leafPath = "leafPath";
+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<initializePath_class, x3::unused_type> const initializePath = "initializePath";
-x3::rule<createPathSuggestions_class, x3::unused_type> const createPathSuggestions = "createPathSuggestions";
x3::rule<trailingSlash_class, TrailingSlash> const trailingSlash = "trailingSlash";
x3::rule<absoluteStart_class, Scope> const absoluteStart = "absoluteStart";
x3::rule<keyValue_class, keyValue_> const keyValue = "keyValue";
x3::rule<key_identifier_class, std::string> const key_identifier = "key_identifier";
x3::rule<listSuffix_class, std::vector<keyValue_>> const listSuffix = "listSuffix";
-x3::rule<list_class, list_> const list = "list";
x3::rule<createKeySuggestions_class, x3::unused_type> const createKeySuggestions = "createKeySuggestions";
x3::rule<createValueSuggestions_class, x3::unused_type> const createValueSuggestions = "createValueSuggestions";
x3::rule<suggestKeysEnd_class, x3::unused_type> const suggestKeysEnd = "suggestKeysEnd";
-template <typename NodeType>
-struct NodeParser : x3::parser<NodeParser<NodeType>> {
- using attribute_type = NodeType;
+enum class NodeParserMode {
+ CompleteDataNode,
+ IncompleteDataNode,
+ CompletionsOnly,
+ SchemaNode
+};
+
+template <auto>
+struct ModeToAttribute;
+template <>
+struct ModeToAttribute<NodeParserMode::CompleteDataNode> {
+ using type = dataNode_;
+};
+template <>
+struct ModeToAttribute<NodeParserMode::IncompleteDataNode> {
+ using type = dataNode_;
+};
+template <>
+struct ModeToAttribute<NodeParserMode::SchemaNode> {
+ using type = schemaNode_;
+};
+// The CompletionsOnly attribute is dataNode_ only because of convenience:
+// having the same return type means we can get by without a ton of `if constexpr` stanzas.
+// So the code will still "parse data into the target attr" for simplicity.
+template <>
+struct ModeToAttribute<NodeParserMode::CompletionsOnly> {
+ using type = dataNode_;
+};
+
+template <NodeParserMode PARSER_MODE>
+struct NodeParser : x3::parser<NodeParser<PARSER_MODE>> {
+ using attribute_type = typename ModeToAttribute<PARSER_MODE>::type;
+
+ std::function<bool(const Schema&, const std::string& path)> m_filterFunction;
+
+ NodeParser(const std::function<bool(const Schema&, const std::string& path)>& filterFunction)
+ : m_filterFunction(filterFunction)
+ {
+ }
+
+ // GCC complains that `end` isn't used when doing completions only
+ // FIXME: GCC 10.1 doesn't emit a warning here. Remove [[maybe_unused]] when GCC 10 is available
template <typename It, typename Ctx, typename RCtx, typename Attr>
- bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, Attr& attr) const
+ bool parse(It& begin, [[maybe_unused]] It end, Ctx const& ctx, RCtx& rctx, Attr& attr) const
{
std::string tableName;
- if constexpr (std::is_same<NodeType, schemaNode_>()) {
+ if constexpr (std::is_same<attribute_type, schemaNode_>()) {
tableName = "schemaNode";
} else {
tableName = "dataNode";
}
- x3::symbols<NodeType> table(tableName);
+ x3::symbols<attribute_type> table(tableName);
ParserContext& parserContext = x3::get<parser_context_tag>(ctx);
parserContext.m_suggestions.clear();
for (const auto& child : parserContext.m_schema.availableNodes(parserContext.currentSchemaPath(), Recursion::NonRecursive)) {
- NodeType out;
+ attribute_type out;
std::string parseString;
if (child.first) {
out.m_prefix = module_{*child.first};
parseString = *child.first + ":";
}
parseString += child.second;
+
+ if (!m_filterFunction(parserContext.m_schema, joinPaths(pathToSchemaString(parserContext.currentSchemaPath(), Prefixes::Always), parseString))) {
+ continue;
+ }
+
switch (parserContext.m_schema.nodeType(parserContext.currentSchemaPath(), child)) {
case yang::NodeTypes::Container:
case yang::NodeTypes::PresenceContainer:
@@ -67,7 +106,7 @@
parserContext.m_suggestions.emplace(Completion{parseString + " "});
break;
case yang::NodeTypes::List:
- if constexpr (std::is_same<NodeType, schemaNode_>()) {
+ if constexpr (std::is_same<attribute_type, schemaNode_>()) {
out.m_suffix = list_{child.second};
} else {
out.m_suffix = listElement_{child.second, {}};
@@ -82,7 +121,7 @@
continue;
}
table.add(parseString, out);
- table.add("..", NodeType{nodeup_{}});
+ table.add("..", attribute_type{nodeup_{}});
if (!child.first) {
auto topLevelModule = parserContext.currentSchemaPath().m_nodes.begin()->m_prefix;
out.m_prefix = topLevelModule;
@@ -90,73 +129,150 @@
}
}
parserContext.m_completionIterator = begin;
- auto res = table.parse(begin, end, ctx, rctx, attr);
- if (attr.m_prefix) {
- parserContext.m_curModule = attr.m_prefix->m_name;
- }
-
- if (attr.m_suffix.type() == typeid(leaf_)) {
- parserContext.m_tmpListKeyLeafPath.m_location = parserContext.currentSchemaPath();
- ModuleNodePair node{attr.m_prefix.flat_map([](const auto& it) {
- return boost::optional<std::string>{it.m_name};
- }), boost::get<leaf_>(attr.m_suffix).m_name};
- parserContext.m_tmpListKeyLeafPath.m_node = node;
- }
-
- if constexpr (std::is_same<NodeType, dataNode_>()) {
- if (attr.m_suffix.type() == typeid(listElement_)) {
- parserContext.m_tmpListName = boost::get<listElement_>(attr.m_suffix).m_name;
- res = listSuffix.parse(begin, end, ctx, rctx, boost::get<listElement_>(attr.m_suffix).m_keys);
+ if constexpr (PARSER_MODE == NodeParserMode::CompletionsOnly) {
+ return true;
+ } else {
+ It saveIter;
+ // GCC complains that I assign saveIter because I use it only if NodeType is dataNode_
+ // FIXME: GCC 10.1 doesn't emit a warning here. Make this unconditional when GCC 10 is available.
+ if constexpr (std::is_same<attribute_type, dataNode_>()) {
+ saveIter = begin;
}
- }
- if (res) {
- parserContext.pushPathFragment(attr);
- parserContext.m_topLevelModulePresent = true;
- }
+ auto res = table.parse(begin, end, ctx, rctx, attr);
- if (attr.m_prefix) {
- parserContext.m_curModule = boost::none;
+ if (attr.m_prefix) {
+ parserContext.m_curModule = attr.m_prefix->m_name;
+ }
+
+ if (attr.m_suffix.type() == typeid(leaf_)) {
+ parserContext.m_tmpListKeyLeafPath.m_location = parserContext.currentSchemaPath();
+ ModuleNodePair node{attr.m_prefix.flat_map([](const auto& it) {
+ return boost::optional<std::string>{it.m_name};
+ }),
+ boost::get<leaf_>(attr.m_suffix).m_name};
+ parserContext.m_tmpListKeyLeafPath.m_node = node;
+ }
+
+ if constexpr (std::is_same<attribute_type, dataNode_>()) {
+ if (attr.m_suffix.type() == typeid(listElement_)) {
+ parserContext.m_tmpListName = boost::get<listElement_>(attr.m_suffix).m_name;
+ res = listSuffix.parse(begin, end, ctx, rctx, boost::get<listElement_>(attr.m_suffix).m_keys);
+
+ // FIXME: think of a better way to do this, that is, get rid of manual iterator reverting
+ if (!res) {
+ // If listSuffix didn't succeed, we check, if we allow incomplete nodes. If we do, then we replace listElement_ with list_.
+ // If we don't, we fail the whole symbol table.
+ if constexpr (PARSER_MODE == NodeParserMode::IncompleteDataNode) {
+ res = true;
+ attr.m_suffix = list_{boost::get<listElement_>(attr.m_suffix).m_name};
+ } else {
+ begin = saveIter;
+ }
+ }
+ }
+ }
+
+ if (res) {
+ parserContext.pushPathFragment(attr);
+ parserContext.m_topLevelModulePresent = true;
+ }
+
+ if (attr.m_prefix) {
+ parserContext.m_curModule = boost::none;
+ }
+ return res;
}
- return res;
}
};
-NodeParser<schemaNode_> schemaNode;
-NodeParser<dataNode_> dataNode;
+using schemaNode = NodeParser<NodeParserMode::SchemaNode>;
+using dataNode = NodeParser<NodeParserMode::CompleteDataNode>;
+using dataNodeAllowList = NodeParser<NodeParserMode::IncompleteDataNode>;
+using pathCompletions = NodeParser<NodeParserMode::CompletionsOnly>;
using AnyPath = boost::variant<schemaPath_, dataPath_>;
-template <typename PathType>
-struct PathParser : x3::parser<PathParser<PathType>> {
- using attribute_type = PathType;
+enum class PathParserMode {
+ AnyPath,
+ DataPath,
+ DataPathListEnd
+};
+
+template <>
+struct ModeToAttribute<PathParserMode::AnyPath> {
+ using type = AnyPath;
+};
+
+template <>
+struct ModeToAttribute<PathParserMode::DataPath> {
+ using type = dataPath_;
+};
+
+template <>
+struct ModeToAttribute<PathParserMode::DataPathListEnd> {
+ using type = dataPath_;
+};
+
+template <PathParserMode PARSER_MODE>
+struct PathParser : x3::parser<PathParser<PARSER_MODE>> {
+ using attribute_type = ModeToAttribute<PARSER_MODE>;
+ std::function<bool(const Schema&, const std::string& path)> m_filterFunction;
+
+ PathParser(const std::function<bool(const Schema&, const std::string& path)>& filterFunction = [] (const auto&, const auto&) {return true;})
+ : m_filterFunction(filterFunction)
+ {
+ }
+
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, Attr& attr) const
{
initializePath.parse(begin, end, ctx, rctx, x3::unused);
dataPath_ attrData;
+ auto pathEnd = x3::rule<class PathEnd>{"pathEnd"} = &space_separator | x3::eoi;
// absoluteStart has to be separate from the dataPath parser,
// otherwise, if the "dataNode % '/'" parser fails, the begin iterator
// gets reverted to before the starting slash.
- auto res = -absoluteStart.parse(begin, end, ctx, rctx, attrData.m_scope);
- auto dataPath = x3::attr(attrData.m_scope) >> dataNode % '/' >> -trailingSlash;
+ auto res = (-absoluteStart).parse(begin, end, ctx, rctx, attrData.m_scope);
+ auto dataPath = x3::attr(attrData.m_scope)
+ >> (dataNode{m_filterFunction} % '/' | pathEnd >> x3::attr(std::vector<dataNode_>{}))
+ >> -trailingSlash;
res = dataPath.parse(begin, end, ctx, rctx, attrData);
- attr = attrData;
- if constexpr (std::is_same<Attr, AnyPath>()) {
- auto pathEnd = x3::rule<class PathEnd>{"pathEnd"} = &space_separator | x3::eoi;
- // If parsing failed, or if there's more input we try parsing schema nodes
+ // If we allow data paths with a list at the end, we just try to parse that separately.
+ if constexpr (PARSER_MODE == PathParserMode::DataPathListEnd || PARSER_MODE == PathParserMode::AnyPath) {
if (!res || !pathEnd.parse(begin, end, ctx, rctx, x3::unused)) {
- // If dataPath parsed some nodes, they will be saved in `attrData`. We have to keep these.
- schemaPath_ attrSchema = dataPathToSchemaPath(attrData);
- auto schemaPath = schemaNode % '/';
- // The schemaPath parser continues where the dataPath parser ended.
- res = schemaPath.parse(begin, end, ctx, rctx, attrSchema.m_nodes);
- auto trailing = -trailingSlash >> pathEnd;
- res = trailing.parse(begin, end, ctx, rctx, attrSchema.m_trailingSlash);
- attr = attrSchema;
+ dataNode_ attrNodeList;
+ res = dataNodeAllowList{m_filterFunction}.parse(begin, end, ctx, rctx, attrNodeList);
+ if (res) {
+ attrData.m_nodes.push_back(attrNodeList);
+ // If the trailing slash matches, no more nodes are parsed.
+ // That means no more completion. So, I generate them
+ // manually.
+ res = (-(trailingSlash >> x3::omit[pathCompletions{m_filterFunction}])).parse(begin, end, ctx, rctx, attrData.m_trailingSlash);
+ }
+ }
+ }
+
+ attr = attrData;
+ if constexpr (PARSER_MODE == PathParserMode::AnyPath) {
+ // If our data path already has some listElement_ fragments, we can't parse rest of the path as a schema path
+ auto hasLists = std::any_of(attrData.m_nodes.begin(), attrData.m_nodes.end(),
+ [] (const auto& node) { return node.m_suffix.type() == typeid(listElement_); });
+ // If parsing failed, or if there's more input we try parsing schema nodes.
+ if (!hasLists) {
+ if (!res || !pathEnd.parse(begin, end, ctx, rctx, x3::unused)) {
+ // If dataPath parsed some nodes, they will be saved in `attrData`. We have to keep these.
+ schemaPath_ attrSchema = dataPathToSchemaPath(attrData);
+ auto schemaPath = schemaNode{m_filterFunction} % '/';
+ // The schemaPath parser continues where the dataPath parser ended.
+ res = schemaPath.parse(begin, end, ctx, rctx, attrSchema.m_nodes);
+ auto trailing = -trailingSlash >> pathEnd;
+ res = trailing.parse(begin, end, ctx, rctx, attrSchema.m_trailingSlash);
+ attr = attrSchema;
+ }
}
}
return res;
@@ -170,8 +286,9 @@
// The PathParser class would get a boost::variant as the attribute, but I
// don't want to deal with that, so I use these wrappers to ensure the
// attribute I want (and let Spirit deal with boost::variant).
-auto const anyPath = x3::rule<class anyPath_class, AnyPath>{"anyPath"} = PathParser<AnyPath>{};
-auto const dataPath = x3::rule<class dataPath_class, dataPath_>{"dataPath"} = PathParser<dataPath_>{};
+auto const anyPath = x3::rule<class anyPath_class, AnyPath>{"anyPath"} = PathParser<PathParserMode::AnyPath>{};
+auto const dataPath = x3::rule<class dataPath_class, dataPath_>{"dataPath"} = PathParser<PathParserMode::DataPath>{};
+auto const dataPathListEnd = x3::rule<class dataPath_class, dataPath_>{"dataPath"} = PathParser<PathParserMode::DataPathListEnd>{};
#if __clang__
#pragma GCC diagnostic push
@@ -214,24 +331,12 @@
auto const trailingSlash_def =
x3::omit['/'] >> x3::attr(TrailingSlash::Present);
-auto const createPathSuggestions_def =
- x3::eps;
+auto const filterConfigFalse = [] (const Schema& schema, const std::string& path) {
+ return schema.isConfig(path);
+};
-auto const dataNodeList_def =
- createPathSuggestions >> -(module) >> list;
-
-// This intermediate rule is mandatory, because we need the first alternative
-// to be collapsed to a vector. If we didn't use the intermediate rule,
-// Spirit wouldn't know we want it to collapse.
-// https://github.com/boostorg/spirit/issues/408
-auto const dataNodesListEnd_def =
- dataNode % '/' >> '/' >> dataNodeList >> -(&char_('/') >> createPathSuggestions) |
- x3::attr(decltype(dataPath_::m_nodes)()) >> dataNodeList;
-
-auto const dataPathListEnd_def = initializePath >> absoluteStart >> createPathSuggestions >> x3::attr(decltype(dataPath_::m_nodes)()) >> x3::attr(TrailingSlash::NonPresent) >> x3::eoi | initializePath >> -(absoluteStart >> createPathSuggestions) >> dataNodesListEnd >> (-(trailingSlash >> createPathSuggestions) >> -(completing >> rest) >> (&space_separator | x3::eoi));
-
-auto const leafPath_def =
- dataPath;
+auto const writableLeafPath_def =
+ PathParser<PathParserMode::DataPath>{filterConfigFalse};
auto const presenceContainerPath_def =
dataPath;
@@ -252,16 +357,11 @@
BOOST_SPIRIT_DEFINE(keyValue)
BOOST_SPIRIT_DEFINE(key_identifier)
BOOST_SPIRIT_DEFINE(listSuffix)
-BOOST_SPIRIT_DEFINE(list)
-BOOST_SPIRIT_DEFINE(dataNodeList)
-BOOST_SPIRIT_DEFINE(dataNodesListEnd)
-BOOST_SPIRIT_DEFINE(leafPath)
+BOOST_SPIRIT_DEFINE(writableLeafPath)
BOOST_SPIRIT_DEFINE(presenceContainerPath)
BOOST_SPIRIT_DEFINE(listInstancePath)
-BOOST_SPIRIT_DEFINE(dataPathListEnd)
BOOST_SPIRIT_DEFINE(initializePath)
BOOST_SPIRIT_DEFINE(createKeySuggestions)
-BOOST_SPIRIT_DEFINE(createPathSuggestions)
BOOST_SPIRIT_DEFINE(createValueSuggestions)
BOOST_SPIRIT_DEFINE(suggestKeysEnd)
BOOST_SPIRIT_DEFINE(absoluteStart)
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index aecad5a..738ce49 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -12,10 +12,10 @@
StaticSchema::StaticSchema()
{
- m_nodes.emplace("/", std::unordered_map<std::string, NodeType>());
+ m_nodes.emplace("/", std::unordered_map<std::string, NodeInfo>());
}
-const std::unordered_map<std::string, NodeType>& StaticSchema::children(const std::string& name) const
+const std::unordered_map<std::string, NodeInfo>& StaticSchema::children(const std::string& name) const
{
return m_nodes.at(name);
}
@@ -36,11 +36,11 @@
void StaticSchema::addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence)
{
- m_nodes.at(location).emplace(name, yang::container{isPresence});
+ m_nodes.at(location).emplace(name, NodeInfo{yang::container{isPresence}, yang::AccessType::Writable});
//create a new set of children for the new node
std::string key = joinPaths(location, name);
- m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
+ m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
}
bool StaticSchema::listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const
@@ -49,7 +49,7 @@
assert(isList(location, node));
const auto& child = children(locationString).at(fullNodeName(location, node));
- const auto& list = boost::get<yang::list>(child);
+ const auto& list = boost::get<yang::list>(child.m_nodeType);
return list.m_keys.find(key) != list.m_keys.end();
}
@@ -59,16 +59,16 @@
assert(isList(location, node));
const auto& child = children(locationString).at(fullNodeName(location, node));
- const auto& list = boost::get<yang::list>(child);
+ const auto& list = boost::get<yang::list>(child.m_nodeType);
return list.m_keys;
}
void StaticSchema::addList(const std::string& location, const std::string& name, const std::set<std::string>& keys)
{
- m_nodes.at(location).emplace(name, yang::list{keys});
+ m_nodes.at(location).emplace(name, NodeInfo{yang::list{keys}, yang::AccessType::Writable});
std::string key = joinPaths(location, name);
- m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
+ m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
}
std::set<identityRef_> StaticSchema::validIdentities(std::string_view module, std::string_view value)
@@ -79,11 +79,11 @@
return identities;
}
-void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type)
+void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type, const yang::AccessType accessType)
{
- m_nodes.at(location).emplace(name, yang::leaf{yang::TypeInfo{type, std::nullopt}});
+ m_nodes.at(location).emplace(name, NodeInfo{yang::leaf{yang::TypeInfo{type, std::nullopt}}, accessType});
std::string key = joinPaths(location, name);
- m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
+ m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
}
void StaticSchema::addModule(const std::string& name)
@@ -120,14 +120,14 @@
yang::TypeInfo StaticSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
{
std::string locationString = pathToSchemaString(location, Prefixes::Always);
- return boost::get<yang::leaf>(children(locationString).at(fullNodeName(location, node))).m_type;
+ return boost::get<yang::leaf>(children(locationString).at(fullNodeName(location, node)).m_nodeType).m_type;
}
yang::TypeInfo StaticSchema::leafType(const std::string& path) const
{
auto locationString = stripLastNodeFromPath(path);
auto node = lastNodeOfSchemaPath(path);
- return boost::get<yang::leaf>(children(locationString).at(node)).m_type;
+ return boost::get<yang::leaf>(children(locationString).at(node).m_nodeType).m_type;
}
std::set<ModuleNodePair> StaticSchema::availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const
@@ -185,18 +185,18 @@
try {
auto targetNode = children(locationString).at(fullName);
- if (targetNode.type() == typeid(yang::container)) {
- if (boost::get<yang::container>(targetNode).m_presence == yang::ContainerTraits::Presence) {
+ if (targetNode.m_nodeType.type() == typeid(yang::container)) {
+ if (boost::get<yang::container>(targetNode.m_nodeType).m_presence == yang::ContainerTraits::Presence) {
return yang::NodeTypes::PresenceContainer;
}
return yang::NodeTypes::Container;
}
- if (targetNode.type() == typeid(yang::list)) {
+ if (targetNode.m_nodeType.type() == typeid(yang::list)) {
return yang::NodeTypes::List;
}
- if (targetNode.type() == typeid(yang::leaf)) {
+ if (targetNode.m_nodeType.type() == typeid(yang::leaf)) {
return yang::NodeTypes::Leaf;
}
@@ -207,6 +207,25 @@
}
}
+std::string fullNodeName(const std::string& location, const std::string& node)
+{
+ // If the node already contains a module name, just return it.
+ if (node.find_first_of(':') != std::string::npos) {
+ return node;
+ }
+
+ // Otherwise take the module name from the first node of location.
+ return location.substr(location.find_first_not_of('/'), location.find_first_of(':') - 1) + ":" + node;
+}
+
+bool StaticSchema::isConfig(const std::string& leafPath) const
+{
+ auto locationString = stripLastNodeFromPath(leafPath);
+
+ auto node = fullNodeName(locationString, lastNodeOfSchemaPath(leafPath));
+ return children(locationString).at(node).m_configType == yang::AccessType::Writable;
+}
+
std::optional<std::string> StaticSchema::description([[maybe_unused]] const std::string& path) const
{
throw std::runtime_error{"StaticSchema::description not implemented"};
@@ -237,11 +256,6 @@
throw std::runtime_error{"Internal error: StaticSchema::leafTypeName(std::string) not implemented. The tests should not have called this overload."};
}
-bool StaticSchema::isConfig([[maybe_unused]] const std::string& leafPath) const
-{
- throw std::runtime_error{"Internal error: StaticSchema::isConfigLeaf(std::string) not implemented. The tests should not have called this overload."};
-}
-
std::optional<std::string> StaticSchema::defaultValue([[maybe_unused]] const std::string& leafPath) const
{
throw std::runtime_error{"Internal error: StaticSchema::defaultValue(std::string) not implemented. The tests should not have called this overload."};
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 5184ae6..b94dfa3 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -32,10 +32,20 @@
struct module {
};
+
+enum class AccessType {
+ Writable,
+ ReadOnly
+};
}
using NodeType = boost::variant<yang::container, yang::list, yang::leaf, yang::module>;
+struct NodeInfo {
+ NodeType m_nodeType;
+ yang::AccessType m_configType;
+};
+
/*! \class StaticSchema
* \brief Static schema, used mainly for testing
@@ -66,17 +76,17 @@
* used in addLeaf for the `type` argument */
std::set<identityRef_> validIdentities(std::string_view module, std::string_view value);
void addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence = yang::ContainerTraits::None);
- void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type);
+ void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type, const yang::AccessType accessType = yang::AccessType::Writable);
void addList(const std::string& location, const std::string& name, const std::set<std::string>& keys);
void addModule(const std::string& name);
void addIdentity(const std::optional<identityRef_>& base, const identityRef_& name);
private:
- const std::unordered_map<std::string, NodeType>& children(const std::string& name) const;
+ const std::unordered_map<std::string, NodeInfo>& children(const std::string& name) const;
void getIdentSet(const identityRef_& ident, std::set<identityRef_>& res) const;
bool nodeExists(const std::string& location, const std::string& node) const;
- std::unordered_map<std::string, std::unordered_map<std::string, NodeType>> m_nodes;
+ std::unordered_map<std::string, std::unordered_map<std::string, NodeInfo>> m_nodes;
std::set<std::string> m_modules;
std::map<identityRef_, std::set<identityRef_>> m_identities;
diff --git a/tests/leaf_editing.cpp b/tests/leaf_editing.cpp
index 75433e0..6da7868 100644
--- a/tests/leaf_editing.cpp
+++ b/tests/leaf_editing.cpp
@@ -73,6 +73,7 @@
yang::TypeInfo{yang::Empty{}},
}});
schema->addLeaf("/", "mod:dummy", yang::Empty{});
+ schema->addLeaf("/", "mod:readonly", yang::Int32{}, yang::AccessType::ReadOnly);
Parser parser(schema);
std::string input;
@@ -601,6 +602,16 @@
input = "set mod:dummy";
}
+ SECTION("empty path")
+ {
+ input = "set ";
+ }
+
+ SECTION("setting readonly data")
+ {
+ input = "set mod:readonly 123";
+ }
+
REQUIRE_THROWS_AS(parser.parseCommand(input, errorStream), InvalidCommandException);
REQUIRE(errorStream.str().find(expectedError) != std::string::npos);
}
diff --git a/tests/ls.cpp b/tests/ls.cpp
index 93ec75b..6d70414 100644
--- a/tests/ls.cpp
+++ b/tests/ls.cpp
@@ -9,6 +9,7 @@
#include "trompeloeil_doctest.hpp"
#include "ast_commands.hpp"
#include "parser.hpp"
+#include "pretty_printers.hpp"
#include "static_schema.hpp"
TEST_CASE("ls")
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index bfc83d2..71fc917 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -37,6 +37,7 @@
schema->addLeaf("/example:twoKeyList", "example:name", yang::String{});
schema->addLeaf("/example:twoKeyList", "example:number", yang::Int32{});
schema->addLeaf("/", "example:leafInt", yang::Int32{});
+ schema->addLeaf("/", "example:readonly", yang::Int32{}, yang::AccessType::ReadOnly);
auto mockDatastore = std::make_shared<MockDatastoreAccess>();
// The parser will use DataQuery for key value completion, but I'm not testing that here, so I don't return anything.
@@ -66,14 +67,14 @@
SECTION("ls ")
{
input = "ls ";
- expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList", "second:amelie/"};
+ expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:readonly ", "example:ovocezelenina", "example:twoKeyList", "second:amelie/"};
expectedContextLength = 0;
}
SECTION("ls e")
{
input = "ls e";
- expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList"};
+ expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:readonly ", "example:ovocezelenina", "example:twoKeyList"};
expectedContextLength = 1;
}
@@ -101,14 +102,14 @@
SECTION("ls /")
{
input = "ls /";
- expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList", "second:amelie/"};
+ expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:readonly ", "example:ovocezelenina", "example:twoKeyList", "second:amelie/"};
expectedContextLength = 0;
}
SECTION("ls /e")
{
input = "ls /e";
- expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList"};
+ expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:readonly ", "example:ovocezelenina", "example:twoKeyList"};
expectedContextLength = 1;
}
@@ -312,6 +313,12 @@
}
}
+ SECTION("no readonly data leafs in set")
+ {
+ input = "set example:read";
+ expectedContextLength = 12;
+ }
+
SECTION("clear completions when no longer inputting path")
{
input = "set example:leafInt ";
diff --git a/tests/presence_containers.cpp b/tests/presence_containers.cpp
index 7c6397d..f248597 100644
--- a/tests/presence_containers.cpp
+++ b/tests/presence_containers.cpp
@@ -107,6 +107,11 @@
input = "list[quote='lol']";
}
+ SECTION("no path")
+ {
+ input = " ";
+ }
+
REQUIRE_THROWS_AS(parser.parseCommand("create " + input, errorStream), InvalidCommandException);
REQUIRE_THROWS_AS(parser.parseCommand("delete " + input, errorStream), InvalidCommandException);
}
diff --git a/tests/pretty_printers.hpp b/tests/pretty_printers.hpp
index fb8aebf..7f59adc 100644
--- a/tests/pretty_printers.hpp
+++ b/tests/pretty_printers.hpp
@@ -110,3 +110,21 @@
}
return s;
}
+
+std::ostream& operator<<(std::ostream& s, const boost::optional<boost::variant<dataPath_, schemaPath_, module_>>& path)
+{
+ if (path) {
+ s << *path;
+ } else {
+ s << "boost::none";
+ }
+
+ return s;
+}
+
+
+std::ostream& operator<<(std::ostream& s, const ls_& ls)
+{
+ s << "\nls_ {\n " << ls.m_path << "}\n";
+ return s;
+}