Merge pull request #2074 from hugeping/printer_tree-segfault
printer_tree: avoid NULL pointer dereference in trp_ext_is_present()
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6bdabce..8ed9695 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -61,12 +61,12 @@
# set version of the project
set(LIBYANG_MAJOR_VERSION 2)
set(LIBYANG_MINOR_VERSION 1)
-set(LIBYANG_MICRO_VERSION 80)
+set(LIBYANG_MICRO_VERSION 101)
set(LIBYANG_VERSION ${LIBYANG_MAJOR_VERSION}.${LIBYANG_MINOR_VERSION}.${LIBYANG_MICRO_VERSION})
# set version of the library
set(LIBYANG_MAJOR_SOVERSION 2)
-set(LIBYANG_MINOR_SOVERSION 36)
-set(LIBYANG_MICRO_SOVERSION 1)
+set(LIBYANG_MINOR_SOVERSION 38)
+set(LIBYANG_MICRO_SOVERSION 4)
set(LIBYANG_SOVERSION_FULL ${LIBYANG_MAJOR_SOVERSION}.${LIBYANG_MINOR_SOVERSION}.${LIBYANG_MICRO_SOVERSION})
set(LIBYANG_SOVERSION ${LIBYANG_MAJOR_SOVERSION})
diff --git a/CMakeModules/FindPCRE2.cmake b/CMakeModules/FindPCRE2.cmake
index 19af7b7..bb44f78 100644
--- a/CMakeModules/FindPCRE2.cmake
+++ b/CMakeModules/FindPCRE2.cmake
@@ -22,19 +22,24 @@
${CMAKE_INSTALL_PREFIX}/include)
# Look for the library.
- find_library(PCRE2_LIBRARY
- NAMES
- libpcre2.a
- pcre2-8
- PATHS
- /usr/lib
- /usr/lib64
- /usr/local/lib
- /usr/local/lib64
- /opt/local/lib
- /sw/lib
- ${CMAKE_LIBRARY_PATH}
- ${CMAKE_INSTALL_PREFIX}/lib)
+ if (WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+ # For the Debug build, the pcre2 library is called pcre2-8d. The Release build should be pcre2-8.
+ find_library(PCRE2_LIBRARY pcre2-8d)
+ else()
+ find_library(PCRE2_LIBRARY
+ NAMES
+ libpcre2.a
+ pcre2-8
+ PATHS
+ /usr/lib
+ /usr/lib64
+ /usr/local/lib
+ /usr/local/lib64
+ /opt/local/lib
+ /sw/lib
+ ${CMAKE_LIBRARY_PATH}
+ ${CMAKE_INSTALL_PREFIX}/lib)
+ endif()
if(PCRE2_INCLUDE_DIR AND PCRE2_LIBRARY)
# learn pcre2 version
diff --git a/CMakeModules/UseCompat.cmake b/CMakeModules/UseCompat.cmake
index 34a9741..ef3df89 100644
--- a/CMakeModules/UseCompat.cmake
+++ b/CMakeModules/UseCompat.cmake
@@ -54,7 +54,6 @@
check_function_exists(timegm HAVE_TIMEGM)
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
check_symbol_exists(mmap "sys/mman.h" HAVE_MMAP)
- check_symbol_exists(dirname "libgen.h" HAVE_DIRNAME)
check_symbol_exists(setenv "stdlib.h" HAVE_SETENV)
unset(CMAKE_REQUIRED_DEFINITIONS)
diff --git a/compat/compat.c b/compat/compat.c
index 5fb2be8..24f235c 100644
--- a/compat/compat.c
+++ b/compat/compat.c
@@ -86,9 +86,9 @@
ssize_t
getline(char **lineptr, size_t *n, FILE *stream)
{
- static char line[256];
+ static char chunk[256];
char *ptr;
- ssize_t len;
+ ssize_t len, written;
if (!lineptr || !n) {
errno = EINVAL;
@@ -99,28 +99,31 @@
return -1;
}
- if (!fgets(line, 256, stream)) {
- return -1;
- }
-
- ptr = strchr(line, '\n');
- if (ptr) {
- *ptr = '\0';
- }
-
- len = strlen(line);
-
- if (len + 1 < 256) {
- ptr = realloc(*lineptr, 256);
- if (!ptr) {
- return -1;
+ *n = *lineptr ? *n : 0;
+ written = 0;
+ while (fgets(chunk, sizeof(chunk), stream)) {
+ len = strlen(chunk);
+ if (written + len > *n) {
+ ptr = realloc(*lineptr, *n + sizeof(chunk));
+ if (!ptr) {
+ return -1;
+ }
+ *lineptr = ptr;
+ *n = *n + sizeof(chunk);
}
- *lineptr = ptr;
- *n = 256;
+ memcpy(*lineptr + written, &chunk, len);
+ written += len;
+ if ((*lineptr)[written - 1] == '\n') {
+ break;
+ }
+ }
+ if (written) {
+ (*lineptr)[written] = '\0';
+ } else {
+ written = -1;
}
- strcpy(*lineptr, line);
- return len;
+ return written;
}
#endif
@@ -322,21 +325,6 @@
#endif
#endif
-#ifndef HAVE_DIRNAME
-#ifdef _WIN32
-#include <shlwapi.h>
-char *
-dirname(char *path)
-{
- PathRemoveFileSpecA(path);
- return path;
-}
-
-#else
-#error No dirname() implementation for this platform is available.
-#endif
-#endif
-
#ifndef HAVE_SETENV
#ifdef _WIN32
int
diff --git a/compat/compat.h.in b/compat/compat.h.in
index 0fa2fe7..3baa489 100644
--- a/compat/compat.h.in
+++ b/compat/compat.h.in
@@ -71,7 +71,6 @@
#cmakedefine HAVE_TIMEGM
#cmakedefine HAVE_STRPTIME
#cmakedefine HAVE_MMAP
-#cmakedefine HAVE_DIRNAME
#cmakedefine HAVE_STRCASECMP
#cmakedefine HAVE_SETENV
diff --git a/compat/posix-shims/strings.h b/compat/posix-shims/strings.h
index c917a66..1ac0519 100644
--- a/compat/posix-shims/strings.h
+++ b/compat/posix-shims/strings.h
@@ -7,3 +7,11 @@
#error No strcasecmp() implementation for this platform is available.
#endif
#endif
+
+#ifndef HAVE_STRNCASECMP
+#ifdef _MSC_VER
+#define strncasecmp _strnicmp
+#else
+#error No strncasecmp() implementation for this platform is available.
+#endif
+#endif
diff --git a/distro/pkg/rpm/libyang.spec b/distro/pkg/rpm/libyang.spec
index eccc4a5..f57ef91 100644
--- a/distro/pkg/rpm/libyang.spec
+++ b/distro/pkg/rpm/libyang.spec
@@ -47,27 +47,46 @@
%prep
%autosetup -p1
+%if 0%{?rhel} && 0%{?rhel} < 8
+ mkdir build
+%endif
%build
-%cmake -DCMAKE_BUILD_TYPE=RELWITHDEBINFO
-%cmake_build
-
-%if "x%{?suse_version}" == "x"
-cd %{__cmake_builddir}
+%if 0%{?rhel} && 0%{?rhel} < 8
+ cd build
+ cmake \
+ -DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \
+ -DCMAKE_BUILD_TYPE:String="Release" \
+ -DCMAKE_C_FLAGS="${RPM_OPT_FLAGS}" \
+ -DCMAKE_CXX_FLAGS="${RPM_OPT_FLAGS}" \
+ ..
+ make
+%else
+ %cmake -DCMAKE_BUILD_TYPE=RELWITHDEBINFO
+ %cmake_build
+ %if "x%{?suse_version}" == "x"
+ cd %{__cmake_builddir}
+ %endif
%endif
make doc
%check
-%if "x%{?suse_version}" == "x"
-cd %{__cmake_builddir}
+%if ( 0%{?rhel} == 0 ) || 0%{?rhel} > 7
+ %if "x%{?suse_version}" == "x"
+ cd %{__cmake_builddir}
+ %endif
%endif
ctest --output-on-failure -V %{?_smp_mflags}
%install
-%cmake_install
-
mkdir -m0755 -p %{buildroot}/%{_docdir}/libyang
-cp -a doc/html %{buildroot}/%{_docdir}/libyang/html
+%if 0%{?rhel} && 0%{?rhel} < 8
+ cd build
+ make DESTDIR=%{buildroot} install
+%else
+ %cmake_install
+ cp -a doc/html %{buildroot}/%{_docdir}/libyang/html
+%endif
%files
%license LICENSE
diff --git a/src/common.c b/src/common.c
index 38f51ea..f3b1d6b 100644
--- a/src/common.c
+++ b/src/common.c
@@ -258,6 +258,7 @@
(value != 0x09) &&
(value != 0x0a) &&
(value != 0x0d)) {
+ /* valid UTF8 but not YANG string character */
return LY_EINVAL;
}
diff --git a/src/context.h b/src/context.h
index 10bbef4..a6367f5 100644
--- a/src/context.h
+++ b/src/context.h
@@ -197,6 +197,8 @@
#define LY_CTX_ENABLE_IMP_FEATURES 0x0100 /**< By default, all features of newly implemented imported modules of
a module that is being loaded are disabled. With this flag they all become
enabled. */
+#define LY_CTX_LEAFREF_EXTENDED 0x0200 /**< By default, path attribute of leafref accepts only path as defined in RFC 7950.
+ By using this option, the path attribute will also allow using XPath functions as deref() */
/** @} contextoptions */
diff --git a/src/diff.c b/src/diff.c
index 51189d2..0625bc9 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -161,6 +161,127 @@
return rc;
}
+/**
+ * @brief Find metadata/an attribute of a node.
+ *
+ * @param[in] node Node to search.
+ * @param[in] name Metadata/attribute name.
+ * @param[out] meta Metadata found, NULL if not found.
+ * @param[out] attr Attribute found, NULL if not found.
+ */
+static void
+lyd_diff_find_meta(const struct lyd_node *node, const char *name, struct lyd_meta **meta, struct lyd_attr **attr)
+{
+ struct lyd_meta *m;
+ struct lyd_attr *a;
+
+ if (meta) {
+ *meta = NULL;
+ }
+ if (attr) {
+ *attr = NULL;
+ }
+
+ if (node->schema) {
+ assert(meta);
+
+ LY_LIST_FOR(node->meta, m) {
+ if (!strcmp(m->name, name) && !strcmp(m->annotation->module->name, "yang")) {
+ *meta = m;
+ break;
+ }
+ }
+ } else {
+ assert(attr);
+
+ LY_LIST_FOR(((struct lyd_node_opaq *)node)->attr, a) {
+ /* name */
+ if (strcmp(a->name.name, name)) {
+ continue;
+ }
+
+ /* module */
+ switch (a->format) {
+ case LY_VALUE_JSON:
+ if (strcmp(a->name.module_name, "yang")) {
+ continue;
+ }
+ break;
+ case LY_VALUE_XML:
+ if (strcmp(a->name.module_ns, "urn:ietf:params:xml:ns:yang:1")) {
+ continue;
+ }
+ break;
+ default:
+ LOGINT(LYD_CTX(node));
+ return;
+ }
+
+ *attr = a;
+ break;
+ }
+ }
+}
+
+/**
+ * @brief Learn operation of a diff node.
+ *
+ * @param[in] diff_node Diff node.
+ * @param[out] op Operation.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op)
+{
+ struct lyd_meta *meta = NULL;
+ struct lyd_attr *attr = NULL;
+ const struct lyd_node *diff_parent;
+ const char *str;
+ char *path;
+
+ for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) {
+ lyd_diff_find_meta(diff_parent, "operation", &meta, &attr);
+ if (!meta && !attr) {
+ continue;
+ }
+
+ str = meta ? lyd_get_meta_value(meta) : attr->value;
+ if ((str[0] == 'r') && (diff_parent != diff_node)) {
+ /* we do not care about this operation if it's in our parent */
+ continue;
+ }
+ *op = lyd_diff_str2op(str);
+ return LY_SUCCESS;
+ }
+
+ /* operation not found */
+ path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0);
+ LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path);
+ free(path);
+ return LY_EINT;
+}
+
+/**
+ * @brief Remove metadata/an attribute from a node.
+ *
+ * @param[in] node Node to update.
+ * @param[in] name Metadata/attribute name.
+ */
+static void
+lyd_diff_del_meta(struct lyd_node *node, const char *name)
+{
+ struct lyd_meta *meta;
+ struct lyd_attr *attr;
+
+ lyd_diff_find_meta(node, name, &meta, &attr);
+
+ if (meta) {
+ lyd_free_meta_single(meta);
+ } else if (attr) {
+ lyd_free_attr_single(LYD_CTX(node), attr);
+ }
+}
+
LY_ERR
lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_default, const char *orig_value,
const char *key, const char *value, const char *position, const char *orig_key, const char *orig_position,
@@ -168,6 +289,9 @@
{
struct lyd_node *dup, *siblings, *match = NULL, *diff_parent = NULL, *elem;
const struct lyd_node *parent = NULL;
+ enum lyd_diff_op cur_op;
+ struct lyd_meta *meta;
+ uint32_t diff_opts;
assert(diff);
@@ -189,14 +313,16 @@
/* find the first existing parent */
siblings = *diff;
- while (1) {
+ do {
/* find next node parent */
parent = node;
while (parent->parent && (!diff_parent || (parent->parent->schema != diff_parent->schema))) {
parent = lyd_parent(parent);
}
- if (parent == node) {
- /* no more parents to find */
+
+ if (lysc_is_dup_inst_list(parent->schema)) {
+ /* assume it never exists, we are not able to distinguish whether it does or not */
+ match = NULL;
break;
}
@@ -210,41 +336,75 @@
/* move down in the diff */
siblings = lyd_child_no_keys(match);
- }
+ } while (parent != node);
- /* duplicate the subtree (and connect to the diff if possible) */
- if (diff_parent) {
- LY_CHECK_RET(lyd_dup_single_to_ctx(node, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent,
- LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup));
- } else {
- LY_CHECK_RET(lyd_dup_single(node, NULL,
- LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup));
- }
+ if (match && (parent == node)) {
+ /* special case when there is already an operation on our descendant */
+ assert(!lyd_diff_get_op(diff_parent, &cur_op) && (cur_op == LYD_DIFF_OP_NONE));
+ (void)cur_op;
- /* find the first duplicated parent */
- if (!diff_parent) {
- diff_parent = lyd_parent(dup);
- while (diff_parent && diff_parent->parent) {
- diff_parent = lyd_parent(diff_parent);
+ /* move it to the end where it is expected (matters for user-ordered lists) */
+ if (lysc_is_userordered(diff_parent->schema)) {
+ for (elem = diff_parent; elem->next && (elem->next->schema == elem->schema); elem = elem->next) {}
+ if (elem != diff_parent) {
+ LY_CHECK_RET(lyd_insert_after(elem, diff_parent));
+ }
}
- } else {
- diff_parent = dup;
- while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) {
- diff_parent = lyd_parent(diff_parent);
+
+ /* will be replaced by the new operation but keep the current op for descendants */
+ lyd_diff_del_meta(diff_parent, "operation");
+ LY_LIST_FOR(lyd_child_no_keys(diff_parent), elem) {
+ lyd_diff_find_meta(elem, "operation", &meta, NULL);
+ if (meta) {
+ /* explicit operation, fine */
+ continue;
+ }
+
+ /* set the none operation */
+ LY_CHECK_RET(lyd_new_meta(NULL, elem, NULL, "yang:operation", "none", 0, NULL));
}
- }
- /* no parent existed, must be manually connected */
- if (!diff_parent) {
- /* there actually was no parent to duplicate */
- lyd_insert_sibling(*diff, dup, diff);
- } else if (!diff_parent->parent) {
- lyd_insert_sibling(*diff, diff_parent, diff);
- }
+ dup = diff_parent;
+ } else {
+ diff_opts = LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS;
+ if ((op != LYD_DIFF_OP_REPLACE) || !lysc_is_userordered(node->schema) || (node->schema->flags & LYS_CONFIG_R)) {
+ /* move applies only to the user-ordered list, no descendants */
+ diff_opts |= LYD_DUP_RECURSIVE;
+ }
- /* add parent operation, if any */
- if (diff_parent && (diff_parent != dup)) {
- LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", 0, NULL));
+ /* duplicate the subtree (and connect to the diff if possible) */
+ if (diff_parent) {
+ LY_CHECK_RET(lyd_dup_single_to_ctx(node, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent,
+ diff_opts, &dup));
+ } else {
+ LY_CHECK_RET(lyd_dup_single(node, NULL, diff_opts, &dup));
+ }
+
+ /* find the first duplicated parent */
+ if (!diff_parent) {
+ diff_parent = lyd_parent(dup);
+ while (diff_parent && diff_parent->parent) {
+ diff_parent = lyd_parent(diff_parent);
+ }
+ } else {
+ diff_parent = dup;
+ while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) {
+ diff_parent = lyd_parent(diff_parent);
+ }
+ }
+
+ /* no parent existed, must be manually connected */
+ if (!diff_parent) {
+ /* there actually was no parent to duplicate */
+ lyd_insert_sibling(*diff, dup, diff);
+ } else if (!diff_parent->parent) {
+ lyd_insert_sibling(*diff, diff_parent, diff);
+ }
+
+ /* add parent operation, if any */
+ if (diff_parent && (diff_parent != dup)) {
+ LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", 0, NULL));
+ }
}
/* add subtree operation */
@@ -366,7 +526,7 @@
LY_ERR rc = LY_SUCCESS;
const struct lysc_node *schema;
size_t buflen, bufused;
- uint32_t first_pos, second_pos;
+ uint32_t first_pos, second_pos, comp_opts;
assert(first || second);
@@ -402,7 +562,8 @@
} else if (!first) {
*op = LYD_DIFF_OP_CREATE;
} else {
- if (lyd_compare_single(second, userord_item->inst[second_pos], 0)) {
+ comp_opts = lysc_is_dup_inst_list(second->schema) ? LYD_COMPARE_FULL_RECURSION : 0;
+ if (lyd_compare_single(second, userord_item->inst[second_pos], comp_opts)) {
/* in first, there is a different instance on the second position, we are going to move 'first' node */
*op = LYD_DIFF_OP_REPLACE;
} else if ((options & LYD_DIFF_DEFAULTS) && ((first->flags & LYD_DEFAULT) != (second->flags & LYD_DEFAULT))) {
@@ -928,98 +1089,6 @@
}
/**
- * @brief Find metadata/an attribute of a node.
- *
- * @param[in] node Node to search.
- * @param[in] name Metadata/attribute name.
- * @param[out] meta Metadata found, NULL if not found.
- * @param[out] attr Attribute found, NULL if not found.
- */
-static void
-lyd_diff_find_meta(const struct lyd_node *node, const char *name, struct lyd_meta **meta, struct lyd_attr **attr)
-{
- struct lyd_meta *m;
- struct lyd_attr *a;
-
- *meta = NULL;
- *attr = NULL;
-
- if (node->schema) {
- LY_LIST_FOR(node->meta, m) {
- if (!strcmp(m->name, name) && !strcmp(m->annotation->module->name, "yang")) {
- *meta = m;
- break;
- }
- }
- } else {
- LY_LIST_FOR(((struct lyd_node_opaq *)node)->attr, a) {
- /* name */
- if (strcmp(a->name.name, name)) {
- continue;
- }
-
- /* module */
- switch (a->format) {
- case LY_VALUE_JSON:
- if (strcmp(a->name.module_name, "yang")) {
- continue;
- }
- break;
- case LY_VALUE_XML:
- if (strcmp(a->name.module_ns, "urn:ietf:params:xml:ns:yang:1")) {
- continue;
- }
- break;
- default:
- LOGINT(LYD_CTX(node));
- return;
- }
-
- *attr = a;
- break;
- }
- }
-}
-
-/**
- * @brief Learn operation of a diff node.
- *
- * @param[in] diff_node Diff node.
- * @param[out] op Operation.
- * @return LY_ERR value.
- */
-static LY_ERR
-lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op)
-{
- struct lyd_meta *meta = NULL;
- struct lyd_attr *attr = NULL;
- const struct lyd_node *diff_parent;
- const char *str;
- char *path;
-
- for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) {
- lyd_diff_find_meta(diff_parent, "operation", &meta, &attr);
- if (!meta && !attr) {
- continue;
- }
-
- str = meta ? lyd_get_meta_value(meta) : attr->value;
- if ((str[0] == 'r') && (diff_parent != diff_node)) {
- /* we do not care about this operation if it's in our parent */
- continue;
- }
- *op = lyd_diff_str2op(str);
- return LY_SUCCESS;
- }
-
- /* operation not found */
- path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0);
- LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path);
- free(path);
- return LY_EINT;
-}
-
-/**
* @brief Insert a diff node into a data tree.
*
* @param[in,out] first_node First sibling of the data tree.
@@ -1357,27 +1426,6 @@
}
/**
- * @brief Remove metadata/an attribute from a node.
- *
- * @param[in] node Node to update.
- * @param[in] name Metadata/attribute name.
- */
-static void
-lyd_diff_del_meta(struct lyd_node *node, const char *name)
-{
- struct lyd_meta *meta;
- struct lyd_attr *attr;
-
- lyd_diff_find_meta(node, name, &meta, &attr);
-
- if (meta) {
- lyd_free_meta_single(meta);
- } else if (attr) {
- lyd_free_attr_single(LYD_CTX(node), attr);
- }
-}
-
-/**
* @brief Set a specific operation of a node. Delete the previous operation, if any.
* Does not change the default flag.
*
diff --git a/src/hash_table.c b/src/hash_table.c
index 0523d8e..246fb34 100644
--- a/src/hash_table.c
+++ b/src/hash_table.c
@@ -537,7 +537,7 @@
int32_t i;
ly_bool first_matched = 0;
LY_ERR r, ret = LY_SUCCESS;
- lyht_value_equal_cb old_val_equal;
+ lyht_value_equal_cb old_val_equal = NULL;
LY_CHECK_ERR_RET(lyht_find_first(ht, hash, &rec), LOGARG(NULL, hash), LY_ENOTFOUND); /* hash not found */
diff --git a/src/hash_table.h b/src/hash_table.h
index c45a6da..13c6645 100644
--- a/src/hash_table.h
+++ b/src/hash_table.h
@@ -16,7 +16,6 @@
#ifndef LY_HASH_TABLE_H_
#define LY_HASH_TABLE_H_
-#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
@@ -24,10 +23,15 @@
extern "C" {
#endif
-#include "compat.h"
#include "log.h"
/**
+ * @struct ly_ht
+ * @brief libyang hash table.
+ */
+struct ly_ht;
+
+/**
* @brief Compute hash from (several) string(s).
*
* Usage:
diff --git a/src/log.c b/src/log.c
index 1a75dec..92f7c86 100644
--- a/src/log.c
+++ b/src/log.c
@@ -4,7 +4,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief Logger routines implementations
*
- * Copyright (c) 2015 - 2022 CESNET, z.s.p.o.
+ * Copyright (c) 2015 - 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -48,12 +48,6 @@
THREAD_LOCAL struct ly_log_location_s log_location = {0};
-LIBYANG_API_DEF const char *
-ly_last_errmsg(void)
-{
- return last_msg;
-}
-
LIBYANG_API_DEF LY_ERR
ly_errcode(const struct ly_ctx *ctx)
{
@@ -67,6 +61,47 @@
return LY_SUCCESS;
}
+LIBYANG_API_DEF const char *
+ly_strerrcode(LY_ERR err)
+{
+ /* ignore plugin flag */
+ err &= ~LY_EPLUGIN;
+
+ switch (err) {
+ case LY_SUCCESS:
+ return "Success";
+ case LY_EMEM:
+ return "Out of memory";
+ case LY_ESYS:
+ return "System call failed";
+ case LY_EINVAL:
+ return "Invalid value";
+ case LY_EEXIST:
+ return "Already exists";
+ case LY_ENOTFOUND:
+ return "Not found";
+ case LY_EINT:
+ return "Internal error";
+ case LY_EVALID:
+ return "Validation failed";
+ case LY_EDENIED:
+ return "Operation denied";
+ case LY_EINCOMPLETE:
+ return "Operation incomplete";
+ case LY_ERECOMPILE:
+ return "Recompilation required";
+ case LY_ENOT:
+ return "Negative result";
+ case LY_EOTHER:
+ return "Another failure reason";
+ case LY_EPLUGIN:
+ break;
+ }
+
+ /* unreachable */
+ return "Unknown";
+}
+
LIBYANG_API_DEF LY_VECODE
ly_vecode(const struct ly_ctx *ctx)
{
@@ -81,6 +116,38 @@
}
LIBYANG_API_DEF const char *
+ly_strvecode(LY_VECODE vecode)
+{
+ switch (vecode) {
+ case LYVE_SUCCESS:
+ return "Success";
+ case LYVE_SYNTAX:
+ return "General syntax error";
+ case LYVE_SYNTAX_YANG:
+ return "YANG syntax error";
+ case LYVE_SYNTAX_YIN:
+ return "YIN syntax error";
+ case LYVE_REFERENCE:
+ return "Reference error";
+ case LYVE_XPATH:
+ return "XPath error";
+ case LYVE_SEMANTICS:
+ return "Semantic error";
+ case LYVE_SYNTAX_XML:
+ return "XML syntax error";
+ case LYVE_SYNTAX_JSON:
+ return "JSON syntax error";
+ case LYVE_DATA:
+ return "YANG data error";
+ case LYVE_OTHER:
+ return "Another error";
+ }
+
+ /* unreachable */
+ return "Unknown";
+}
+
+LIBYANG_API_DEF const char *
ly_errmsg(const struct ly_ctx *ctx)
{
struct ly_err_item *i;
@@ -96,6 +163,12 @@
}
LIBYANG_API_DEF const char *
+ly_last_errmsg(void)
+{
+ return last_msg;
+}
+
+LIBYANG_API_DEF const char *
ly_errpath(const struct ly_ctx *ctx)
{
struct ly_err_item *i;
@@ -634,6 +707,8 @@
va_start(ap, format);
log_vprintf(NULL, LY_LLDBG, 0, 0, NULL, NULL, dbg_format, ap);
va_end(ap);
+
+ free(dbg_format);
}
#endif
diff --git a/src/log.h b/src/log.h
index 0d4a5d8..021ab89 100644
--- a/src/log.h
+++ b/src/log.h
@@ -170,7 +170,7 @@
* @param[in] dbg_groups Bitfield of enabled debug message groups (see @ref dbggroup).
* @return Previous options bitfield.
*/
-uint32_t ly_log_dbg_groups(uint32_t dbg_groups);
+LIBYANG_API_DECL uint32_t ly_log_dbg_groups(uint32_t dbg_groups);
#endif
@@ -294,25 +294,31 @@
* @brief Libyang full error structure.
*/
struct ly_err_item {
- LY_LOG_LEVEL level;
- LY_ERR no;
- LY_VECODE vecode;
- char *msg;
- char *path;
- char *apptag;
- struct ly_err_item *next;
- struct ly_err_item *prev; /* first item's prev points to the last item */
+ LY_LOG_LEVEL level; /**< error (message) log level */
+ LY_ERR no; /**< error code */
+ LY_VECODE vecode; /**< validation error code, if any */
+ char *msg; /**< error message */
+ char *path; /**< error path that caused the error, if any */
+ char *apptag; /**< error-app-tag, if any */
+ struct ly_err_item *next; /**< next error item */
+ struct ly_err_item *prev; /**< previous error item, points to the last item for the ifrst item */
};
/**
- * @brief Get the last (thread-specific) error message.
+ * @brief Get the last (thread, context-specific) error code.
*
- * ::ly_errmsg() should be used instead of this function but this one is useful for getting
- * errors from functions that do not have any context accessible. Or as a simple unified logging API.
- *
- * @return Last generated error message.
+ * @param[in] ctx Relative context.
+ * @return LY_ERR value of the last error code.
*/
-LIBYANG_API_DECL const char *ly_last_errmsg(void);
+LIBYANG_API_DECL LY_ERR ly_errcode(const struct ly_ctx *ctx);
+
+/**
+ * @brief Get human-readable error message for an error code.
+ *
+ * @param[in] err Error code.
+ * @return String error message.
+ */
+LIBYANG_API_DECL const char *ly_strerrcode(LY_ERR err);
/**
* @brief Get the last (thread, context-specific) validation error code.
@@ -325,12 +331,12 @@
LIBYANG_API_DECL LY_VECODE ly_vecode(const struct ly_ctx *ctx);
/**
- * @brief Get the last (thread, context-specific) error code.
+ * @brief Get human-readable error message for a validation error code.
*
- * @param[in] ctx Relative context.
- * @return LY_ERR value of the last error code.
+ * @param[in] vecode Validation error code.
+ * @return String error message.
*/
-LIBYANG_API_DECL LY_ERR ly_errcode(const struct ly_ctx *ctx);
+LIBYANG_API_DECL const char *ly_strvecode(LY_VECODE vecode);
/**
* @brief Get the last (thread, context-specific) error message. If the coresponding module defined
@@ -345,6 +351,16 @@
LIBYANG_API_DECL const char *ly_errmsg(const struct ly_ctx *ctx);
/**
+ * @brief Get the last (thread-specific) error message.
+ *
+ * ::ly_errmsg() should be used instead of this function but this one is useful for getting
+ * errors from functions that do not have any context accessible. Or as a simple unified logging API.
+ *
+ * @return Last generated error message.
+ */
+LIBYANG_API_DECL const char *ly_last_errmsg(void);
+
+/**
* @brief Get the last (thread, context-specific) path of the element where was an error.
*
* The path always corresponds to the error message available via ::ly_errmsg(), so
diff --git a/src/parser_data.h b/src/parser_data.h
index 1bad5b2..537d6d4 100644
--- a/src/parser_data.h
+++ b/src/parser_data.h
@@ -312,7 +312,7 @@
enum lyd_type {
LYD_TYPE_DATA_YANG = 0, /* generic YANG instance data */
LYD_TYPE_RPC_YANG, /* instance of a YANG RPC/action request with only "input" data children,
- including all parents in case of an action */
+ including all parents and optional top-level "action" element in case of an action */
LYD_TYPE_NOTIF_YANG, /* instance of a YANG notification, including all parents in case of a nested one */
LYD_TYPE_REPLY_YANG, /* instance of a YANG RPC/action reply with only "output" data children,
including all parents in case of an action */
diff --git a/src/parser_json.c b/src/parser_json.c
index 9cbd5d8..5143fae 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -338,6 +338,55 @@
}
/**
+ * @brief Get the hint for the data type parsers according to the current JSON parser context.
+ *
+ * @param[in] jsonctx JSON parser context. The context is supposed to be on a value.
+ * @param[in,out] status Pointer to the current context status,
+ * in some circumstances the function manipulates with the context so the status is updated.
+ * @param[out] type_hint_p Pointer to the variable to store the result.
+ * @return LY_SUCCESS in case of success.
+ * @return LY_EINVAL in case of invalid context status not referring to a value.
+ */
+static LY_ERR
+lydjson_value_type_hint(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status_p, uint32_t *type_hint_p)
+{
+ *type_hint_p = 0;
+
+ if (*status_p == LYJSON_ARRAY) {
+ /* only [null] */
+ LY_CHECK_RET(lyjson_ctx_next(jsonctx, status_p));
+ if (*status_p != LYJSON_NULL) {
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX_JSON,
+ "Expected JSON name/value or special name/[null], but input data contains name/[%s].",
+ lyjson_token2str(*status_p));
+ return LY_EINVAL;
+ }
+
+ LY_CHECK_RET(lyjson_ctx_next(jsonctx, NULL));
+ if (lyjson_ctx_status(jsonctx) != LYJSON_ARRAY_CLOSED) {
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX_JSON, "Expected array end, but input data contains %s.",
+ lyjson_token2str(*status_p));
+ return LY_EINVAL;
+ }
+
+ *type_hint_p = LYD_VALHINT_EMPTY;
+ } else if (*status_p == LYJSON_STRING) {
+ *type_hint_p = LYD_VALHINT_STRING | LYD_VALHINT_NUM64;
+ } else if (*status_p == LYJSON_NUMBER) {
+ *type_hint_p = LYD_VALHINT_DECNUM;
+ } else if ((*status_p == LYJSON_FALSE) || (*status_p == LYJSON_TRUE)) {
+ *type_hint_p = LYD_VALHINT_BOOLEAN;
+ } else if (*status_p == LYJSON_NULL) {
+ *type_hint_p = 0;
+ } else {
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX_JSON, "Unexpected input data %s.", lyjson_token2str(*status_p));
+ return LY_EINVAL;
+ }
+
+ return LY_SUCCESS;
+}
+
+/**
* @brief Check that the input data are parseable as the @p list.
*
* Checks for all the list's keys. Function does not revert the context state.
@@ -354,7 +403,7 @@
enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(jsonctx);
struct ly_set key_set = {0};
const struct lysc_node *snode;
- uint32_t i;
+ uint32_t i, hints;
assert(list && (list->nodetype == LYS_LIST));
@@ -402,7 +451,9 @@
goto cleanup;
}
- rc = lys_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL);
+ rc = lydjson_value_type_hint(jsonctx, &status, &hints);
+ LY_CHECK_GOTO(rc, cleanup);
+ rc = ly_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL, hints);
LY_CHECK_GOTO(rc, cleanup);
/* key with a valid value, remove from the set */
@@ -430,55 +481,6 @@
}
/**
- * @brief Get the hint for the data type parsers according to the current JSON parser context.
- *
- * @param[in] lydctx JSON data parser context. The context is supposed to be on a value.
- * @param[in,out] status Pointer to the current context status,
- * in some circumstances the function manipulates with the context so the status is updated.
- * @param[out] type_hint_p Pointer to the variable to store the result.
- * @return LY_SUCCESS in case of success.
- * @return LY_EINVAL in case of invalid context status not referring to a value.
- */
-static LY_ERR
-lydjson_value_type_hint(struct lyd_json_ctx *lydctx, enum LYJSON_PARSER_STATUS *status_p, uint32_t *type_hint_p)
-{
- *type_hint_p = 0;
-
- if (*status_p == LYJSON_ARRAY) {
- /* only [null] */
- LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, status_p));
- if (*status_p != LYJSON_NULL) {
- LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON,
- "Expected JSON name/value or special name/[null], but input data contains name/[%s].",
- lyjson_token2str(*status_p));
- return LY_EINVAL;
- }
-
- LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, NULL));
- if (lyjson_ctx_status(lydctx->jsonctx) != LYJSON_ARRAY_CLOSED) {
- LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Expected array end, but input data contains %s.",
- lyjson_token2str(*status_p));
- return LY_EINVAL;
- }
-
- *type_hint_p = LYD_VALHINT_EMPTY;
- } else if (*status_p == LYJSON_STRING) {
- *type_hint_p = LYD_VALHINT_STRING | LYD_VALHINT_NUM64;
- } else if (*status_p == LYJSON_NUMBER) {
- *type_hint_p = LYD_VALHINT_DECNUM;
- } else if ((*status_p == LYJSON_FALSE) || (*status_p == LYJSON_TRUE)) {
- *type_hint_p = LYD_VALHINT_BOOLEAN;
- } else if (*status_p == LYJSON_NULL) {
- *type_hint_p = 0;
- } else {
- LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Unexpected input data %s.", lyjson_token2str(*status_p));
- return LY_EINVAL;
- }
-
- return LY_SUCCESS;
-}
-
-/**
* @brief Check in advance if the input data are parsable according to the provided @p snode.
*
* Note that the checks are done only in case the LYD_PARSE_OPAQ is allowed. Otherwise the same checking
@@ -518,12 +520,10 @@
case LYS_LEAFLIST:
case LYS_LEAF:
/* value may not be valid in which case we parse it as an opaque node */
- ret = lydjson_value_type_hint(lydctx, &status, type_hint_p);
- if (ret) {
+ if ((ret = lydjson_value_type_hint(jsonctx, &status, type_hint_p))) {
break;
}
-
- if (lys_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL)) {
+ if (ly_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL, *type_hint_p)) {
ret = LY_ENOT;
}
break;
@@ -536,7 +536,7 @@
break;
}
} else if (snode->nodetype & LYD_NODE_TERM) {
- ret = lydjson_value_type_hint(lydctx, &status, type_hint_p);
+ ret = lydjson_value_type_hint(jsonctx, &status, type_hint_p);
}
/* restore parser */
@@ -846,7 +846,7 @@
LY_CHECK_GOTO(rc = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup);
/* get value hints */
- LY_CHECK_GOTO(rc = lydjson_value_type_hint(lydctx, &status, &val_hints), cleanup);
+ LY_CHECK_GOTO(rc = lydjson_value_type_hint(lydctx->jsonctx, &status, &val_hints), cleanup);
if (node->schema) {
/* create metadata */
@@ -969,7 +969,7 @@
dynamic = lydctx->jsonctx->dynamic;
lydctx->jsonctx->dynamic = 0;
- LY_CHECK_RET(lydjson_value_type_hint(lydctx, status_inner_p, &type_hint));
+ LY_CHECK_RET(lydjson_value_type_hint(lydctx->jsonctx, status_inner_p, &type_hint));
}
/* get the module name */
@@ -1236,14 +1236,17 @@
enum LYJSON_PARSER_STATUS *status, struct lyd_node **node)
{
LY_ERR r, rc = LY_SUCCESS;
- uint32_t prev_parse_opts, prev_int_opts;
+ uint32_t prev_parse_opts = lydctx->parse_opts, prev_int_opts = lydctx->int_opts;
struct ly_in in_start;
char *val = NULL;
const char *end;
- struct lyd_node *tree = NULL;
+ struct lyd_node *child = NULL;
+ ly_bool log_node = 0;
assert(snode->nodetype & LYD_NODE_ANY);
+ *node = NULL;
+
/* status check according to allowed JSON types */
if (snode->nodetype == LYS_ANYXML) {
LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_ARRAY) && (*status != LYJSON_NUMBER) &&
@@ -1256,39 +1259,41 @@
/* create any node */
switch (*status) {
case LYJSON_OBJECT:
+ /* create node */
+ r = lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node);
+ LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+ assert(*node);
+ LOG_LOCSET(NULL, *node, NULL, NULL);
+ log_node = 1;
+
/* parse any data tree with correct options, first backup the current options and then make the parser
* process data as opaq nodes */
- prev_parse_opts = lydctx->parse_opts;
lydctx->parse_opts &= ~LYD_PARSE_STRICT;
lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0);
- prev_int_opts = lydctx->int_opts;
lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS;
lydctx->any_schema = snode;
/* process the anydata content */
do {
- r = lydjson_subtree_r(lydctx, NULL, &tree, NULL);
+ r = lydjson_subtree_r(lydctx, NULL, &child, NULL);
LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
*status = lyjson_ctx_status(lydctx->jsonctx);
} while (*status == LYJSON_OBJECT_NEXT);
- /* restore parser options */
- lydctx->parse_opts = prev_parse_opts;
- lydctx->int_opts = prev_int_opts;
- lydctx->any_schema = NULL;
-
/* finish linking metadata */
- r = lydjson_metadata_finish(lydctx, &tree);
+ r = lydjson_metadata_finish(lydctx, &child);
LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
- r = lyd_create_any(snode, tree, LYD_ANYDATA_DATATREE, 1, node);
- LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+ /* assign the data tree */
+ ((struct lyd_node_any *)*node)->value.tree = child;
+ child = NULL;
break;
case LYJSON_ARRAY:
/* skip until the array end */
in_start = *lydctx->jsonctx->in;
- LY_CHECK_RET(lydjson_data_skip(lydctx->jsonctx));
+ LY_CHECK_GOTO(rc = lydjson_data_skip(lydctx->jsonctx), cleanup);
/* return back by all the WS */
end = lydctx->jsonctx->in->current;
@@ -1299,22 +1304,25 @@
/* make a copy of the whole array and store it */
if (asprintf(&val, "[%.*s", (int)(end - in_start.current), in_start.current) == -1) {
LOGMEM(lydctx->jsonctx->ctx);
- return LY_EMEM;
+ rc = LY_EMEM;
+ goto cleanup;
}
r = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node);
- LY_CHECK_ERR_GOTO(r, rc = r, val_err);
+ LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+ val = NULL;
break;
case LYJSON_STRING:
/* string value */
if (lydctx->jsonctx->dynamic) {
- LY_CHECK_RET(lyd_create_any(snode, lydctx->jsonctx->value, LYD_ANYDATA_STRING, 1, node));
+ LY_CHECK_GOTO(rc = lyd_create_any(snode, lydctx->jsonctx->value, LYD_ANYDATA_STRING, 1, node), cleanup);
lydctx->jsonctx->dynamic = 0;
} else {
val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len);
- LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM);
+ LY_CHECK_ERR_GOTO(!val, LOGMEM(lydctx->jsonctx->ctx); rc = LY_EMEM, cleanup);
r = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, node);
- LY_CHECK_ERR_GOTO(r, rc = r, val_err);
+ LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+ val = NULL;
}
break;
case LYJSON_NUMBER:
@@ -1323,10 +1331,11 @@
/* JSON value */
assert(!lydctx->jsonctx->dynamic);
val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len);
- LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM);
+ LY_CHECK_ERR_GOTO(!val, LOGMEM(lydctx->jsonctx->ctx); rc = LY_EMEM, cleanup);
r = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node);
- LY_CHECK_ERR_GOTO(r, rc = r, val_err);
+ LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+ val = NULL;
break;
case LYJSON_NULL:
/* no value */
@@ -1334,14 +1343,20 @@
LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
break;
default:
- LOGINT_RET(lydctx->jsonctx->ctx);
+ LOGINT(lydctx->jsonctx->ctx);
+ rc = LY_EINT;
+ goto cleanup;
}
cleanup:
- return rc;
-
-val_err:
+ if (log_node) {
+ LOG_LOCBACK(0, 1, 0, 0);
+ }
+ lydctx->parse_opts = prev_parse_opts;
+ lydctx->int_opts = prev_int_opts;
+ lydctx->any_schema = NULL;
free(val);
+ lyd_free_tree(child);
return rc;
}
@@ -1531,7 +1546,7 @@
size_t name_len, prefix_len = 0;
ly_bool is_meta = 0, parse_subtree;
const struct lysc_node *snode = NULL;
- struct lysc_ext_instance *ext;
+ struct lysc_ext_instance *ext = NULL;
struct lyd_node *node = NULL, *attr_node = NULL;
const struct ly_ctx *ctx = lydctx->jsonctx->ctx;
char *value = NULL;
diff --git a/src/parser_lyb.c b/src/parser_lyb.c
index 9ea6397..2e3d0c5 100644
--- a/src/parser_lyb.c
+++ b/src/parser_lyb.c
@@ -1168,7 +1168,7 @@
/* create node */
ret = lyd_create_opaq(ctx, name, strlen(name), prefix, ly_strlen(prefix), module_key, ly_strlen(module_key),
- value, strlen(value), &dynamic, format, val_prefix_data, 0, &node);
+ value, strlen(value), &dynamic, format, val_prefix_data, LYD_HINT_DATA, &node);
LY_CHECK_GOTO(ret, cleanup);
/* process children */
diff --git a/src/parser_xml.c b/src/parser_xml.c
index 54e6c8b..1914e76 100644
--- a/src/parser_xml.c
+++ b/src/parser_xml.c
@@ -75,6 +75,8 @@
*meta = NULL;
+ LOG_LOCSET(sparent, NULL, NULL, NULL);
+
/* check for NETCONF filter unqualified attributes */
if (!strcmp(sparent->module->name, "notifications")) {
/* ancient module that does not even use the extension */
@@ -163,6 +165,7 @@
}
cleanup:
+ LOG_LOCBACK(1, 0, 0, 0);
if (ret) {
lyd_free_meta_siblings(*meta);
*meta = NULL;
@@ -286,7 +289,7 @@
assert(xmlctx->status == LYXML_ELEM_CONTENT);
if (i < key_set.count) {
/* validate the value */
- r = lys_value_validate(NULL, snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns);
+ r = ly_value_validate(NULL, snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA);
if (!r) {
/* key with a valid value, remove from the set */
ly_set_rm_index(&key_set, i, NULL);
@@ -392,7 +395,7 @@
if ((*snode)->nodetype & LYD_NODE_TERM) {
/* value may not be valid in which case we parse it as an opaque node */
- if (lys_value_validate(NULL, *snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns)) {
+ if (ly_value_validate(NULL, *snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA)) {
LOGVRB("Parsing opaque term node \"%s\" with invalid value \"%.*s\".", (*snode)->name, xmlctx->value_len,
xmlctx->value);
*snode = NULL;
@@ -853,6 +856,7 @@
uint32_t prev_parse_opts = lydctx->parse_opts, prev_int_opts = lydctx->int_opts;
struct lyd_node *child = NULL;
char *val = NULL;
+ ly_bool log_node = 0;
*node = NULL;
@@ -878,6 +882,14 @@
LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
val = NULL;
} else {
+ /* create node */
+ r = lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node);
+ LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+ assert(*node);
+ LOG_LOCSET(NULL, *node, NULL, NULL);
+ log_node = 1;
+
/* parser next */
r = lyxml_ctx_next(xmlctx);
LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
@@ -893,17 +905,15 @@
LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
}
- /* restore options */
- lydctx->parse_opts = prev_parse_opts;
- lydctx->int_opts = prev_int_opts;
-
- /* create node */
- r = lyd_create_any(snode, child, LYD_ANYDATA_DATATREE, 1, node);
- LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+ /* assign the data tree */
+ ((struct lyd_node_any *)*node)->value.tree = child;
child = NULL;
}
cleanup:
+ if (log_node) {
+ LOG_LOCBACK(0, 1, 0, 0);
+ }
lydctx->parse_opts = prev_parse_opts;
lydctx->int_opts = prev_int_opts;
free(val);
@@ -1087,7 +1097,11 @@
const char *prefix;
size_t prefix_len;
- assert(xmlctx->status == LYXML_ELEMENT);
+ if (xmlctx->status != LYXML_ELEMENT) {
+ /* nothing to parse */
+ return LY_ENOT;
+ }
+
if (ly_strncmp(name, xmlctx->name, xmlctx->name_len)) {
/* not the expected element */
return LY_ENOT;
@@ -1147,7 +1161,8 @@
{
LY_ERR r, rc = LY_SUCCESS;
struct lyd_xml_ctx *lydctx;
- ly_bool parsed_data_nodes = 0;
+ ly_bool parsed_data_nodes = 0, close_elem = 0;
+ struct lyd_node *act = NULL;
enum LYXML_PARSER_STATUS status;
assert(ctx && in && lydctx_p);
@@ -1167,6 +1182,14 @@
/* find the operation node if it exists already */
LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup);
+ if ((int_opts & LYD_INTOPT_RPC) && (int_opts & LYD_INTOPT_ACTION)) {
+ /* can be either, try to parse "action" */
+ if (!lydxml_envelope(lydctx->xmlctx, "action", "urn:ietf:params:xml:ns:yang:1", 0, &act)) {
+ close_elem = 1;
+ int_opts &= ~LYD_INTOPT_RPC;
+ }
+ }
+
/* parse XML data */
while (lydctx->xmlctx->status == LYXML_ELEMENT) {
r = lydxml_subtree_r(lydctx, parent, first_p, parsed);
@@ -1179,6 +1202,19 @@
}
}
+ /* close an opened element */
+ if (close_elem) {
+ if (lydctx->xmlctx->status != LYXML_ELEM_CLOSE) {
+ assert(lydctx->xmlctx->status == LYXML_ELEMENT);
+ LOGVAL(lydctx->xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\".",
+ (int)lydctx->xmlctx->name_len, lydctx->xmlctx->name);
+ rc = LY_EVALID;
+ goto cleanup;
+ }
+
+ LY_CHECK_GOTO(rc = lyxml_ctx_next(lydctx->xmlctx), cleanup);
+ }
+
/* check final state */
if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && (lydctx->xmlctx->status == LYXML_ELEMENT)) {
LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node.");
@@ -1211,6 +1247,7 @@
assert(!(parse_opts & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count &&
!lydctx->node_when.count));
+ lyd_free_tree(act);
if (rc && (!(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) {
lyd_xml_ctx_free((struct lyd_ctx *)lydctx);
} else {
@@ -1241,12 +1278,6 @@
assert(envp && !*envp);
- if (xmlctx->status != LYXML_ELEMENT) {
- /* nothing to parse */
- assert(xmlctx->status == LYXML_END);
- goto cleanup;
- }
-
/* parse "rpc" */
r = lydxml_envelope(xmlctx, "rpc", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp);
LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
@@ -1295,12 +1326,6 @@
assert(envp && !*envp);
- if (xmlctx->status != LYXML_ELEMENT) {
- /* nothing to parse */
- assert(xmlctx->status == LYXML_END);
- goto cleanup;
- }
-
/* parse "notification" */
r = lydxml_envelope(xmlctx, "notification", "urn:ietf:params:xml:ns:netconf:notification:1.0", 0, envp);
LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
@@ -1716,12 +1741,6 @@
assert(envp && !*envp);
- if (xmlctx->status != LYXML_ELEMENT) {
- /* nothing to parse */
- assert(xmlctx->status == LYXML_END);
- goto cleanup;
- }
-
/* parse "rpc-reply" */
r = lydxml_envelope(xmlctx, "rpc-reply", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp);
LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
diff --git a/src/parser_yang.c b/src/parser_yang.c
index dd84480..b81cf60 100644
--- a/src/parser_yang.c
+++ b/src/parser_yang.c
@@ -804,7 +804,8 @@
MOVE_INPUT(ctx, 1);
goto extension;
case '{':
- /* allowed only for input and output statements which can be without arguments */
+ case ';':
+ /* allowed only for input and output statements which are without arguments */
if ((*kw == LY_STMT_INPUT) || (*kw == LY_STMT_OUTPUT)) {
break;
}
@@ -2860,11 +2861,6 @@
YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, inout_p->exts, ret, cleanup);
}
- if (!inout_p->child) {
- LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "data-def-stmt", lyplg_ext_stmt2str(inout_kw));
- return LY_EVALID;
- }
-
cleanup:
return ret;
}
diff --git a/src/parser_yin.c b/src/parser_yin.c
index fa44968..088e79d 100644
--- a/src/parser_yin.c
+++ b/src/parser_yin.c
@@ -3718,7 +3718,6 @@
ret = LY_EINT;
}
LY_CHECK_GOTO(ret, cleanup);
- subelem = NULL;
LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup);
}
diff --git a/src/path.c b/src/path.c
index cfa934f..d691696 100644
--- a/src/path.c
+++ b/src/path.c
@@ -264,6 +264,60 @@
return LY_EVALID;
}
+/**
+ * @brief Parse deref XPath function and perform all additional checks.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] ctx_node Optional context node, used for logging.
+ * @param[in] exp Parsed path.
+ * @param[in,out] tok_idx Index in @p exp, is adjusted.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_parse_deref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const struct lyxp_expr *exp,
+ uint32_t *tok_idx)
+{
+ size_t arg_len;
+ uint32_t begin_token, end_token;
+ struct lyxp_expr *arg_expr = NULL;
+
+ /* mandatory FunctionName */
+ LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_FUNCNAME), LY_EVALID);
+ if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "deref", 5)) {
+ LOGVAL(ctx, LYVE_XPATH, "Unexpected XPath function \"%.*s\" in path, expected \"deref(...)\"",
+ exp->tok_len[*tok_idx], exp->tok_pos[*tok_idx]);
+ return LY_EVALID;
+ }
+
+ /* mandatory '(' */
+ LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR1), LY_EVALID);
+ begin_token = *tok_idx;
+
+ /* count tokens till ')' */
+ while (lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_PAR2) && *tok_idx < exp->used) {
+ /* emebedded functions are not allowed */
+ if (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_FUNCNAME)) {
+ LOGVAL(ctx, LYVE_XPATH, "Embedded function XPath function inside deref function within the path"
+ "is not allowed");
+ return LY_EVALID;
+ }
+
+ (*tok_idx)++;
+ }
+
+ /* mandatory ')' */
+ LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR2), LY_EVALID);
+ end_token = *tok_idx - 1;
+
+ /* parse the path of deref argument */
+ arg_len = exp->tok_pos[end_token] - exp->tok_pos[begin_token];
+ LY_CHECK_RET(ly_path_parse(ctx, ctx_node, &exp->expr[exp->tok_pos[begin_token]], arg_len, 1,
+ LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &arg_expr), LY_EVALID);
+ lyxp_expr_free(ctx, arg_expr);
+
+ return LY_SUCCESS;
+}
+
LY_ERR
ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *str_path, size_t path_len,
ly_bool lref, uint16_t begin, uint16_t prefix, uint16_t pred, struct lyxp_expr **expr)
@@ -294,12 +348,23 @@
if (lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_OPER_PATH)) {
/* relative path check specific to leafref */
if (lref) {
+ /* optional function 'deref..' */
+ if ((ly_ctx_get_options(ctx) & LY_CTX_LEAFREF_EXTENDED) &&
+ !lyxp_check_token(NULL, exp, tok_idx, LYXP_TOKEN_FUNCNAME)) {
+ LY_CHECK_ERR_GOTO(ly_path_parse_deref(ctx, ctx_node, exp, &tok_idx), ret = LY_EVALID, error);
+
+ /* '/' */
+ LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID,
+ error);
+ }
+
/* mandatory '..' */
LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_DDOT), ret = LY_EVALID, error);
do {
/* '/' */
- LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID, error);
+ LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID,
+ error);
/* optional '..' */
} while (!lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_DDOT));
@@ -866,6 +931,187 @@
}
/**
+ * @brief Duplicate ly_path_predicate structure.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] pred The array of path predicates.
+ * @param[out] dup Duplicated predicates.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_dup_predicates(const struct ly_ctx *ctx, const struct ly_path_predicate *pred, struct ly_path_predicate **dup)
+{
+ LY_ARRAY_COUNT_TYPE u;
+
+ if (!pred) {
+ return LY_SUCCESS;
+ }
+
+ LY_ARRAY_CREATE_RET(ctx, *dup, LY_ARRAY_COUNT(pred), LY_EMEM);
+ LY_ARRAY_FOR(pred, u) {
+ LY_ARRAY_INCREMENT(*dup);
+ (*dup)[u].type = pred->type;
+
+ switch (pred->type) {
+ case LY_PATH_PREDTYPE_POSITION:
+ /* position-predicate */
+ (*dup)[u].position = pred->position;
+ break;
+ case LY_PATH_PREDTYPE_LIST:
+ case LY_PATH_PREDTYPE_LEAFLIST:
+ /* key-predicate or leaf-list-predicate */
+ (*dup)[u].key = pred->key;
+ pred->value.realtype->plugin->duplicate(ctx, &pred->value, &(*dup)[u].value);
+ LY_ATOMIC_INC_BARRIER(((struct lysc_type *)pred->value.realtype)->refcount);
+ break;
+ case LY_PATH_PREDTYPE_LIST_VAR:
+ /* key-predicate with a variable */
+ (*dup)[u].key = pred->key;
+ (*dup)[u].variable = strdup(pred->variable);
+ break;
+ }
+ }
+
+ return LY_SUCCESS;
+}
+
+/**
+ * @brief Appends path elements from source to destination array
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] src The source path
+ * @param[in,out] dst The destination path
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_append(const struct ly_ctx *ctx, const struct ly_path *src, struct ly_path **dst)
+{
+ LY_ERR ret = LY_SUCCESS;
+ LY_ARRAY_COUNT_TYPE u;
+ struct ly_path *p;
+
+ if (!src) {
+ return LY_SUCCESS;
+ }
+
+ LY_ARRAY_CREATE_RET(ctx, *dst, LY_ARRAY_COUNT(src), LY_EMEM);
+ LY_ARRAY_FOR(src, u) {
+ LY_ARRAY_NEW_GOTO(ctx, *dst, p, ret, cleanup);
+ p->node = src[u].node;
+ p->ext = src[u].ext;
+ LY_CHECK_GOTO(ret = ly_path_dup_predicates(ctx, src[u].predicates, &p->predicates), cleanup);
+ }
+
+cleanup:
+ return ret;
+}
+
+/**
+ * @brief Compile deref XPath function into ly_path structure.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] ctx_node Optional context node, mandatory of @p lref.
+ * @param[in] top_ext Extension instance containing the definition of the data being created. It is used to find
+ * the top-level node inside the extension instance instead of a module. Note that this is the case not only if
+ * the @p ctx_node is NULL, but also if the relative path starting in @p ctx_node reaches the document root
+ * via double dots.
+ * @param[in] expr Parsed path.
+ * @param[in] oper Oper option (@ref path_oper_options).
+ * @param[in] target Target option (@ref path_target_options).
+ * @param[in] format Format of the path.
+ * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix).
+ * @param[in,out] tok_idx Index in @p exp, is adjusted.
+ * @param[out] path Compiled path.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_compile_deref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node,
+ const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint16_t oper, uint16_t target,
+ LY_VALUE_FORMAT format, void *prefix_data, uint32_t *tok_idx, struct ly_path **path)
+{
+ LY_ERR ret = LY_SUCCESS;
+ struct lyxp_expr expr2;
+ struct ly_path *path2 = NULL;
+ const struct lysc_node *node2;
+ const struct lysc_node_leaf *deref_leaf_node;
+ const struct lysc_type_leafref *lref;
+ uint32_t begin_token;
+
+ *path = NULL;
+
+ /* properly parsed path must always starts with 'deref' and '(' */
+ assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_FUNCNAME));
+ assert(!strncmp(&expr->expr[expr->tok_pos[*tok_idx]], "deref", 5));
+ (*tok_idx)++;
+ assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR1));
+ (*tok_idx)++;
+ begin_token = *tok_idx;
+
+ /* emebedded functions were already identified count tokens till ')' */
+ while (lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2) && (*tok_idx < expr->used)) {
+ (*tok_idx)++;
+ }
+
+ /* properly parsed path must have ')' within the tokens */
+ assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2));
+
+ /* prepare expr representing just deref arg */
+ expr2.tokens = &expr->tokens[begin_token];
+ expr2.tok_pos = &expr->tok_pos[begin_token];
+ expr2.tok_len = &expr->tok_len[begin_token];
+ expr2.repeat = &expr->repeat[begin_token];
+ expr2.used = *tok_idx - begin_token;
+ expr2.size = expr->size - begin_token;
+ expr2.expr = expr->expr;
+
+ /* compile just deref arg, append it to the path and find dereferenced lref for next operations */
+ LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, ctx_node, top_ext, &expr2, oper, target, format, prefix_data,
+ &path2), cleanup);
+ node2 = path2[LY_ARRAY_COUNT(path2) - 1].node;
+ deref_leaf_node = (const struct lysc_node_leaf *)node2;
+ lref = (const struct lysc_type_leafref *)deref_leaf_node->type;
+ LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup);
+ ly_path_free(ctx, path2);
+ path2 = NULL;
+
+ /* compile dereferenced leafref expression and append it to the path */
+ LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, lref->path, oper, target, format, prefix_data,
+ &path2), cleanup);
+ node2 = path2[LY_ARRAY_COUNT(path2) - 1].node;
+ LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup);
+ ly_path_free(ctx, path2);
+ path2 = NULL;
+
+ /* properly parsed path must always continue with ')' and '/' */
+ assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2));
+ (*tok_idx)++;
+ assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_OPER_PATH));
+ (*tok_idx)++;
+
+ /* prepare expr representing rest of the path after deref */
+ expr2.tokens = &expr->tokens[*tok_idx];
+ expr2.tok_pos = &expr->tok_pos[*tok_idx];
+ expr2.tok_len = &expr->tok_len[*tok_idx];
+ expr2.repeat = &expr->repeat[*tok_idx];
+ expr2.used = expr->used - *tok_idx;
+ expr2.size = expr->size - *tok_idx;
+ expr2.expr = expr->expr;
+
+ /* compile rest of the path and append it to the path */
+ LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, &expr2, oper, target, format, prefix_data, &path2),
+ cleanup);
+ LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup);
+
+cleanup:
+ ly_path_free(ctx, path2);
+ if (ret) {
+ ly_path_free(ctx, *path);
+ *path = NULL;
+ }
+ return ret;
+}
+
+/**
* @brief Compile path into ly_path structure. Any predicates of a leafref are only checked, not compiled.
*
* @param[in] ctx libyang context.
@@ -922,7 +1168,13 @@
getnext_opts = 0;
}
- if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) {
+ if (lref && (ly_ctx_get_options(ctx) & LY_CTX_LEAFREF_EXTENDED) &&
+ (expr->tokens[tok_idx] == LYXP_TOKEN_FUNCNAME)) {
+ /* deref function */
+ ret = ly_path_compile_deref(ctx, ctx_node, top_ext, expr, oper, target, format, prefix_data, &tok_idx, path);
+ ctx_node = (*path)[LY_ARRAY_COUNT(*path) - 1].node;
+ goto cleanup;
+ } else if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) {
/* absolute path */
ctx_node = NULL;
@@ -1158,7 +1410,8 @@
LY_ERR
ly_path_dup(const struct ly_ctx *ctx, const struct ly_path *path, struct ly_path **dup)
{
- LY_ARRAY_COUNT_TYPE u, v;
+ LY_ERR ret = LY_SUCCESS;
+ LY_ARRAY_COUNT_TYPE u;
if (!path) {
return LY_SUCCESS;
@@ -1168,34 +1421,8 @@
LY_ARRAY_FOR(path, u) {
LY_ARRAY_INCREMENT(*dup);
(*dup)[u].node = path[u].node;
- if (path[u].predicates) {
- LY_ARRAY_CREATE_RET(ctx, (*dup)[u].predicates, LY_ARRAY_COUNT(path[u].predicates), LY_EMEM);
- LY_ARRAY_FOR(path[u].predicates, v) {
- struct ly_path_predicate *pred = &path[u].predicates[v];
-
- LY_ARRAY_INCREMENT((*dup)[u].predicates);
- (*dup)[u].predicates[v].type = pred->type;
-
- switch (pred->type) {
- case LY_PATH_PREDTYPE_POSITION:
- /* position-predicate */
- (*dup)[u].predicates[v].position = pred->position;
- break;
- case LY_PATH_PREDTYPE_LIST:
- case LY_PATH_PREDTYPE_LEAFLIST:
- /* key-predicate or leaf-list-predicate */
- (*dup)[u].predicates[v].key = pred->key;
- pred->value.realtype->plugin->duplicate(ctx, &pred->value, &(*dup)[u].predicates[v].value);
- LY_ATOMIC_INC_BARRIER(((struct lysc_type *)pred->value.realtype)->refcount);
- break;
- case LY_PATH_PREDTYPE_LIST_VAR:
- /* key-predicate with a variable */
- (*dup)[u].predicates[v].key = pred->key;
- (*dup)[u].predicates[v].variable = strdup(pred->variable);
- break;
- }
- }
- }
+ (*dup)[u].ext = path[u].ext;
+ LY_CHECK_RET(ret = ly_path_dup_predicates(ctx, path[u].predicates, &(*dup)[u].predicates), ret);
}
return LY_SUCCESS;
diff --git a/src/plugins_exts/nacm.c b/src/plugins_exts/nacm.c
index 5ab8daa..df49721 100644
--- a/src/plugins_exts/nacm.c
+++ b/src/plugins_exts/nacm.c
@@ -101,7 +101,7 @@
/* check for duplication */
LY_ARRAY_FOR(parent->exts, u) {
- if ((&parent->exts[u] != ext) && parent->exts[u].record && (parent->exts[u].record->plugin.id == ext->record->plugin.id)) {
+ if ((&parent->exts[u] != ext) && parent->exts[u].record && !strcmp(parent->exts[u].record->plugin.id, ext->record->plugin.id)) {
/* duplication of a NACM extension on a single node
* We check for all NACM plugins since we want to catch even the situation that there is default-deny-all
* AND default-deny-write */
diff --git a/src/plugins_types.c b/src/plugins_types.c
index cb4b896..7f62be8 100644
--- a/src/plugins_types.c
+++ b/src/plugins_types.c
@@ -224,24 +224,52 @@
}
/**
- * @brief Simply return module local prefix. Also, store the module in a set.
+ * @brief Get prefix for XML print.
+ *
+ * @param[in] mod Module whose prefix to get.
+ * @param[in,out] prefix_data Set of used modules in the print. If @p mod is found in this set, no string (prefix) is
+ * returned.
+ * @return Prefix to print, may be NULL if the default namespace should be used.
*/
static const char *
ly_xml_get_prefix(const struct lys_module *mod, void *prefix_data)
{
- struct ly_set *ns_list = prefix_data;
+ struct ly_set *mods = prefix_data;
+ uint32_t i;
- LY_CHECK_RET(ly_set_add(ns_list, (void *)mod, 0, NULL), NULL);
+ /* first is the local module */
+ assert(mods->count);
+ if (mods->objs[0] == mod) {
+ return NULL;
+ }
+
+ /* check for duplicates in the rest of the modules and add there */
+ for (i = 1; i < mods->count; ++i) {
+ if (mods->objs[i] == mod) {
+ break;
+ }
+ }
+ if (i == mods->count) {
+ LY_CHECK_RET(ly_set_add(mods, (void *)mod, 1, NULL), NULL);
+ }
+
+ /* return the prefix */
return mod->prefix;
}
/**
- * @brief Simply return module name.
+ * @brief Get prefix for JSON print.
+ *
+ * @param[in] mod Module whose prefix to get.
+ * @param[in] prefix_data Current local module, may be NULL. If it matches @p mod, no string (preifx) is returned.
+ * @return Prefix (module name) to print, may be NULL if the default module should be used.
*/
static const char *
-ly_json_get_prefix(const struct lys_module *mod, void *UNUSED(prefix_data))
+ly_json_get_prefix(const struct lys_module *mod, void *prefix_data)
{
- return mod->name;
+ const struct lys_module *local_mod = prefix_data;
+
+ return (local_mod == mod) ? NULL : mod->name;
}
const char *
diff --git a/src/plugins_types.h b/src/plugins_types.h
index 3ec1d3b..8490e3a 100644
--- a/src/plugins_types.h
+++ b/src/plugins_types.h
@@ -367,7 +367,7 @@
* @param[in] format Format of the prefix (::lyplg_type_print_clb's format parameter).
* @param[in] prefix_data Format-specific data (::lyplg_type_print_clb's prefix_data parameter).
* @return Module prefix to print.
- * @return NULL on error.
+ * @return NULL on using the current module/namespace.
*/
LIBYANG_API_DECL const char *lyplg_type_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, void *prefix_data);
diff --git a/src/plugins_types/identityref.c b/src/plugins_types/identityref.c
index 8b7985d..e33b012 100644
--- a/src/plugins_types/identityref.c
+++ b/src/plugins_types/identityref.c
@@ -50,8 +50,16 @@
identityref_ident2str(const struct lysc_ident *ident, LY_VALUE_FORMAT format, void *prefix_data, char **str, size_t *str_len)
{
int len;
+ const char *prefix;
- len = asprintf(str, "%s:%s", lyplg_type_get_prefix(ident->module, format, prefix_data), ident->name);
+ /* get the prefix, may be NULL for no prefix and the default namespace */
+ prefix = lyplg_type_get_prefix(ident->module, format, prefix_data);
+
+ if (prefix) {
+ len = asprintf(str, "%s:%s", prefix, ident->name);
+ } else {
+ len = asprintf(str, "%s", ident->name);
+ }
if (len == -1) {
return LY_EMEM;
}
@@ -269,8 +277,8 @@
LY_CHECK_GOTO(ret, cleanup);
}
} else {
- /* JSON format with prefix is the canonical one */
- ret = identityref_ident2str(ident, LY_VALUE_JSON, NULL, &canon, NULL);
+ /* JSON format is the canonical one */
+ ret = identityref_ident2str(ident, LY_VALUE_JSON, ctx_node ? ctx_node->module : NULL, &canon, NULL);
LY_CHECK_GOTO(ret, cleanup);
ret = lydict_insert_zc(ctx, canon, &storage->_canonical);
diff --git a/src/plugins_types/instanceid.c b/src/plugins_types/instanceid.c
index 0eef9cc..53a1de1 100644
--- a/src/plugins_types/instanceid.c
+++ b/src/plugins_types/instanceid.c
@@ -50,12 +50,19 @@
LY_ERR ret = LY_SUCCESS;
LY_ARRAY_COUNT_TYPE u, v;
char *result = NULL, quot;
- const struct lys_module *mod = NULL;
+ const struct lys_module *mod = NULL, *local_mod = NULL;
+ struct ly_set *mods;
ly_bool inherit_prefix = 0, d;
const char *strval;
switch (format) {
case LY_VALUE_XML:
+ /* null the local module so that all the prefixes are printed */
+ mods = prefix_data;
+ local_mod = mods->objs[0];
+ mods->objs[0] = NULL;
+
+ /* fallthrough */
case LY_VALUE_SCHEMA:
case LY_VALUE_SCHEMA_RESOLVED:
/* everything is prefixed */
@@ -136,6 +143,9 @@
}
cleanup:
+ if (local_mod) {
+ mods->objs[0] = (void *)local_mod;
+ }
if (ret) {
free(result);
} else {
diff --git a/src/plugins_types/instanceid_keys.c b/src/plugins_types/instanceid_keys.c
index 163bf19..ab7751c 100644
--- a/src/plugins_types/instanceid_keys.c
+++ b/src/plugins_types/instanceid_keys.c
@@ -67,10 +67,18 @@
void *mem;
const char *cur_exp_ptr;
ly_bool is_nt;
- const struct lys_module *context_mod = NULL;
+ const struct lys_module *context_mod = NULL, *local_mod = NULL;
+ struct ly_set *mods;
*str_value = NULL;
+ if (format == LY_VALUE_XML) {
+ /* null the local module so that all the prefixes are printed */
+ mods = prefix_data;
+ local_mod = mods->objs[0];
+ mods->objs[0] = NULL;
+ }
+
while (cur_idx < val->keys->used) {
cur_tok = val->keys->tokens[cur_idx];
cur_exp_ptr = val->keys->expr + val->keys->tok_pos[cur_idx];
@@ -79,11 +87,15 @@
/* tokens that may include prefixes, get them in the target format */
is_nt = (cur_tok == LYXP_TOKEN_NAMETEST) ? 1 : 0;
LY_CHECK_GOTO(ret = lyplg_type_xpath10_print_token(cur_exp_ptr, val->keys->tok_len[cur_idx], is_nt, &context_mod,
- val->ctx, val->format, val->prefix_data, format, prefix_data, &str_tok, err), error);
+ val->ctx, val->format, val->prefix_data, format, prefix_data, &str_tok, err), cleanup);
/* append the converted token */
mem = realloc(*str_value, str_len + strlen(str_tok) + 1);
- LY_CHECK_ERR_GOTO(!mem, free(str_tok), error_mem);
+ if (!mem) {
+ free(str_tok);
+ ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory.");
+ goto cleanup;
+ }
*str_value = mem;
str_len += sprintf(*str_value + str_len, "%s", str_tok);
free(str_tok);
@@ -93,7 +105,10 @@
} else {
/* just copy the token */
mem = realloc(*str_value, str_len + val->keys->tok_len[cur_idx] + 1);
- LY_CHECK_GOTO(!mem, error_mem);
+ if (!mem) {
+ ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory.");
+ goto cleanup;
+ }
*str_value = mem;
str_len += sprintf(*str_value + str_len, "%.*s", (int)val->keys->tok_len[cur_idx], cur_exp_ptr);
@@ -102,13 +117,14 @@
}
}
- return LY_SUCCESS;
-
-error_mem:
- ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory.");
-
-error:
- free(*str_value);
+cleanup:
+ if (local_mod) {
+ mods->objs[0] = (void *)local_mod;
+ }
+ if (ret) {
+ free(*str_value);
+ *str_value = NULL;
+ }
return ret;
}
diff --git a/src/plugins_types/node_instanceid.c b/src/plugins_types/node_instanceid.c
index 00cf207..7833263 100644
--- a/src/plugins_types/node_instanceid.c
+++ b/src/plugins_types/node_instanceid.c
@@ -50,7 +50,8 @@
LY_ERR ret = LY_SUCCESS;
LY_ARRAY_COUNT_TYPE u, v;
char *result = NULL, quot;
- const struct lys_module *mod = NULL;
+ const struct lys_module *mod = NULL, *local_mod = NULL;
+ struct ly_set *mods;
ly_bool inherit_prefix = 0, d;
const char *strval;
@@ -62,6 +63,12 @@
switch (format) {
case LY_VALUE_XML:
+ /* null the local module so that all the prefixes are printed */
+ mods = prefix_data;
+ local_mod = mods->objs[0];
+ mods->objs[0] = NULL;
+
+ /* fallthrough */
case LY_VALUE_SCHEMA:
case LY_VALUE_SCHEMA_RESOLVED:
/* everything is prefixed */
@@ -148,6 +155,9 @@
}
cleanup:
+ if (local_mod) {
+ mods->objs[0] = (void *)local_mod;
+ }
if (ret) {
free(result);
} else {
diff --git a/src/plugins_types/xpath1.0.c b/src/plugins_types/xpath1.0.c
index 6f05bd2..fdfc2a9 100644
--- a/src/plugins_types/xpath1.0.c
+++ b/src/plugins_types/xpath1.0.c
@@ -3,7 +3,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief ietf-yang-types xpath1.0 type plugin.
*
- * Copyright (c) 2021 CESNET, z.s.p.o.
+ * Copyright (c) 2021 - 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -57,12 +57,9 @@
while (!(ret = ly_value_prefix_next(str_begin, token + tok_len, &len, &is_prefix, &str_next)) && len) {
if (!is_prefix) {
if (!has_prefix && is_nametest && (get_format == LY_VALUE_XML) && *context_mod) {
- /* prefix is always needed, get it in the target format */
+ /* get the prefix */
prefix = lyplg_type_get_prefix(*context_mod, get_format, get_prefix_data);
- if (!prefix) {
- ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Internal error.");
- goto cleanup;
- }
+ assert(prefix);
/* append the nametest and prefix */
mem = realloc(str, str_len + strlen(prefix) + 1 + len + 1);
@@ -94,10 +91,7 @@
if (mod) {
/* get the prefix in the target format */
prefix = lyplg_type_get_prefix(mod, get_format, get_prefix_data);
- if (!prefix) {
- ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Internal error.");
- goto cleanup;
- }
+ assert(prefix);
pref_len = strlen(prefix);
} else {
/* invalid prefix, just copy it */
@@ -223,13 +217,25 @@
LY_ERR ret = LY_SUCCESS;
uint16_t expr_idx = 0;
uint32_t str_len = 0;
+ const struct lys_module *local_mod = NULL;
+ struct ly_set *mods;
*str_value = NULL;
*err = NULL;
+ if (format == LY_VALUE_XML) {
+ /* null the local module so that all the prefixes are printed */
+ mods = prefix_data;
+ local_mod = mods->objs[0];
+ mods->objs[0] = NULL;
+ }
+
/* recursively print the expression */
ret = xpath10_print_subexpr_r(&expr_idx, 0, NULL, xp_val, format, prefix_data, str_value, &str_len, err);
+ if (local_mod) {
+ mods->objs[0] = (void *)local_mod;
+ }
if (ret) {
free(*str_value);
*str_value = NULL;
diff --git a/src/printer_json.c b/src/printer_json.c
index a9dc2da..165dfab 100644
--- a/src/printer_json.c
+++ b/src/printer_json.c
@@ -14,6 +14,7 @@
*/
#include <assert.h>
+#include <ctype.h>
#include <stdint.h>
#include <stdlib.h>
@@ -219,31 +220,38 @@
static LY_ERR
json_print_string(struct ly_out *out, const char *text)
{
- uint64_t i, n;
+ uint64_t i;
if (!text) {
return LY_SUCCESS;
}
ly_write_(out, "\"", 1);
- for (i = n = 0; text[i]; i++) {
- const unsigned char ascii = text[i];
+ for (i = 0; text[i]; i++) {
+ const unsigned char byte = text[i];
- if (ascii < 0x20) {
- /* control character */
- ly_print_(out, "\\u%.4X", ascii);
- } else {
- switch (ascii) {
- case '"':
- ly_print_(out, "\\\"");
- break;
- case '\\':
- ly_print_(out, "\\\\");
- break;
- default:
+ switch (byte) {
+ case '"':
+ ly_print_(out, "\\\"");
+ break;
+ case '\\':
+ ly_print_(out, "\\\\");
+ break;
+ case '\r':
+ ly_print_(out, "\\r");
+ break;
+ case '\t':
+ ly_print_(out, "\\t");
+ break;
+ default:
+ if (iscntrl(byte)) {
+ /* control character */
+ ly_print_(out, "\\u%.4X", byte);
+ } else {
+ /* printable character (even non-ASCII UTF8) */
ly_write_(out, &text[i], 1);
- n++;
}
+ break;
}
}
ly_write_(out, "\"", 1);
@@ -335,15 +343,19 @@
* @param[in] pctx JSON printer context.
* @param[in] ctx Context used to print the value.
* @param[in] val Data value to be printed.
+ * @param[in] local_mod Module of the current node.
* @return LY_ERR value.
*/
static LY_ERR
-json_print_value(struct jsonpr_ctx *pctx, const struct ly_ctx *ctx, const struct lyd_value *val)
+json_print_value(struct jsonpr_ctx *pctx, const struct ly_ctx *ctx, const struct lyd_value *val,
+ const struct lys_module *local_mod)
{
ly_bool dynamic;
LY_DATA_TYPE basetype;
- const char *value = val->realtype->plugin->print(ctx, val, LY_VALUE_JSON, NULL, &dynamic, NULL);
+ const char *value;
+ value = val->realtype->plugin->print(ctx, val, LY_VALUE_JSON, (void *)local_mod, &dynamic, NULL);
+ LY_CHECK_RET(!value, LY_EINVAL);
basetype = val->realtype->basetype;
print_val:
@@ -406,7 +418,7 @@
struct lyd_attr *attr;
if (wdmod) {
- ly_print_(pctx->out, "%*s\"%s:default\":true", INDENT, wdmod->name);
+ ly_print_(pctx->out, "%*s\"%s:default\":%strue", INDENT, wdmod->name, DO_FORMAT ? " " : "");
LEVEL_PRINTED;
}
@@ -443,14 +455,14 @@
struct lyd_meta *meta;
if (wdmod) {
- ly_print_(pctx->out, "%*s\"%s:default\":true", INDENT, wdmod->name);
+ ly_print_(pctx->out, "%*s\"%s:default\":%strue", INDENT, wdmod->name, DO_FORMAT ? " " : "");
LEVEL_PRINTED;
}
for (meta = node->meta; meta; meta = meta->next) {
PRINT_COMMA;
ly_print_(pctx->out, "%*s\"%s:%s\":%s", INDENT, meta->annotation->module->name, meta->name, DO_FORMAT ? " " : "");
- LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &meta->value));
+ LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &meta->value, NULL));
LEVEL_PRINTED;
}
@@ -518,7 +530,7 @@
json_print_leaf(struct jsonpr_ctx *pctx, const struct lyd_node *node)
{
LY_CHECK_RET(json_print_member(pctx, node, 0));
- LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value));
+ LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, node->schema->module));
LEVEL_PRINTED;
/* print attributes as sibling */
@@ -783,7 +795,7 @@
} else {
assert(node->schema->nodetype == LYS_LEAFLIST);
- LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value));
+ LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, node->schema->module));
if (!pctx->print_sibling_metadata) {
if ((node->flags & LYD_DEFAULT) && (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG))) {
@@ -900,7 +912,7 @@
ly_print_(pctx->out, "%s", node->value);
} else {
/* string or a large number */
- ly_print_(pctx->out, "\"%s\"", node->value);
+ json_print_string(pctx->out, node->value);
}
LEVEL_PRINTED;
diff --git a/src/printer_tree.c b/src/printer_tree.c
index d18485a..6aa2814 100644
--- a/src/printer_tree.c
+++ b/src/printer_tree.c
@@ -3426,7 +3426,7 @@
/* current node is top-node */
switch (tc->section) {
case TRD_SECT_MODULE:
- tc->cn = tc->cmod->data;
+ tc->cn = tc->cn->module->compiled->data;
break;
case TRD_SECT_RPCS:
tc->cn = (const struct lysc_node *)tc->cmod->rpcs;
@@ -3486,7 +3486,8 @@
}
if (node->name.module_prefix) {
- len += strlen(node->name.module_prefix);
+ /* prefix_name and ':' */
+ len += strlen(node->name.module_prefix) + 1;
}
if (node->name.str) {
len += strlen(node->name.str);
@@ -3753,7 +3754,10 @@
/* print node */
ly_print_(pc->out, "\n");
print_node = pc->fp.read.node(TRP_EMPTY_PARENT_CACHE, tc);
+ /* siblings do not print, so the node is always considered the last */
+ print_node.last_one = 1;
max_gap_before_type = trb_max_gap_to_type(TRP_EMPTY_PARENT_CACHE, pc, tc);
+ tc->cn = node;
trb_print_entire_node(&print_node, max_gap_before_type, wr, pc, tc);
}
@@ -3856,25 +3860,33 @@
* @param[in] compiled if @p ext is lysc structure.
* @param[in] ext current processed extension.
* @param[out] plug_ctx is plugin context which will be initialized.
+ * @param[out] ignore plugin callback is NULL.
* @return LY_ERR value.
*/
static LY_ERR
-tro_ext_printer_tree(ly_bool compiled, void *ext, const struct lyspr_tree_ctx *plug_ctx)
+tro_ext_printer_tree(ly_bool compiled, void *ext, const struct lyspr_tree_ctx *plug_ctx, ly_bool *ignore)
{
struct lysc_ext_instance *ext_comp;
struct lysp_ext_instance *ext_pars;
+ const struct lyplg_ext *plugin;
const char *flags = NULL, *add_opts = NULL;
if (compiled) {
ext_comp = ext;
- if (ext_comp->def->plugin->printer_ctree) {
- return ext_comp->def->plugin->printer_ctree(ext, plug_ctx, &flags, &add_opts);
+ plugin = ext_comp->def->plugin;
+ if (!plugin->printer_ctree) {
+ *ignore = 1;
+ return LY_SUCCESS;
}
+ return plugin->printer_ctree(ext, plug_ctx, &flags, &add_opts);
} else {
ext_pars = ext;
- if (ext_pars->record->plugin.printer_ptree) {
- return ext_pars->record->plugin.printer_ptree(ext, plug_ctx, &flags, &add_opts);
+ plugin = &ext_pars->record->plugin;
+ if (!plugin->printer_ptree) {
+ *ignore = 1;
+ return LY_SUCCESS;
}
+ return plugin->printer_ptree(ext, plug_ctx, &flags, &add_opts);
}
return LY_SUCCESS;
@@ -3992,7 +4004,7 @@
LY_ARRAY_COUNT_TYPE i;
uint64_t last_instance = UINT64_MAX;
void *ext;
- ly_bool child_exists;
+ ly_bool child_exists, ignore = 0;
uint32_t max, max_gap_before_type = 0;
ca = tro_parent_cache_for_child(ca, tc);
@@ -4010,8 +4022,12 @@
while ((ext = trb_ext_iter(tc, &i))) {
struct lyspr_tree_ctx plug_ctx = {0};
- rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx);
+ rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx, &ignore);
LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end);
+ if (ignore) {
+ ignore = 0;
+ continue;
+ }
trb_ext_try_unified_indent(&plug_ctx, ca, &max_gap_before_type, pc, tc);
if (plug_ctx.schemas) {
last_instance = i;
@@ -4030,8 +4046,12 @@
while ((ext = trb_ext_iter(tc, &i))) {
struct lyspr_tree_ctx plug_ctx = {0};
- rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx);
+ rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx, &ignore);
LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end);
+ if (ignore) {
+ ignore = 0;
+ continue;
+ }
if (!child_exists && (last_instance == i)) {
trb_ext_print_schemas(&plug_ctx, 1, max_gap_before_type, wr, ca, pc, tc);
} else {
@@ -4497,6 +4517,7 @@
struct trt_printer_ctx pc_dupl;
struct trt_tree_ctx tc_dupl;
struct trt_node node;
+ ly_bool ignore = 0;
uint32_t max_gap_before_type;
void *ext;
@@ -4510,9 +4531,10 @@
while ((ext = trb_mod_ext_iter(tc, &i))) {
struct lyspr_tree_ctx plug_ctx = {0};
- rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx);
+ rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx, &ignore);
LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end);
- if (!plug_ctx.schemas) {
+ if (!plug_ctx.schemas || ignore) {
+ ignore = 0;
continue;
}
diff --git a/src/printer_xml.c b/src/printer_xml.c
index a7f4c73..47b0acc 100644
--- a/src/printer_xml.c
+++ b/src/printer_xml.c
@@ -178,6 +178,8 @@
struct ly_set ns_list = {0};
LY_ARRAY_COUNT_TYPE u;
ly_bool dynamic, filter_attrs = 0;
+ const char *value;
+ uint32_t i;
/* with-defaults */
if (node->schema->nodetype & LYD_NODE_TERM) {
@@ -205,11 +207,14 @@
}
for (meta = node->meta; meta; meta = meta->next) {
- const char *value = meta->value.realtype->plugin->print(LYD_CTX(node), &meta->value, LY_VALUE_XML, &ns_list,
- &dynamic, NULL);
+ /* store the module of the default namespace, NULL because there is none */
+ ly_set_add(&ns_list, NULL, 0, NULL);
+
+ /* print the value */
+ value = meta->value.realtype->plugin->print(LYD_CTX(node), &meta->value, LY_VALUE_XML, &ns_list, &dynamic, NULL);
/* print namespaces connected with the value's prefixes */
- for (uint32_t i = 0; i < ns_list.count; ++i) {
+ for (i = 1; i < ns_list.count; ++i) {
mod = ns_list.objs[i];
xml_print_ns(pctx, mod->ns, mod->prefix, 1);
}
@@ -314,22 +319,32 @@
static LY_ERR
xml_print_term(struct xmlpr_ctx *pctx, const struct lyd_node_term *node)
{
+ LY_ERR rc = LY_SUCCESS;
struct ly_set ns_list = {0};
- ly_bool dynamic;
- const char *value;
+ ly_bool dynamic = 0;
+ const char *value = NULL;
+ const struct lys_module *mod;
+ uint32_t i;
- xml_print_node_open(pctx, &node->node);
+ /* store the module of the default namespace */
+ if ((rc = ly_set_add(&ns_list, node->schema->module, 0, NULL))) {
+ LOGMEM(pctx->ctx);
+ goto cleanup;
+ }
+
+ /* print the value */
value = ((struct lysc_node_leaf *)node->schema)->type->plugin->print(LYD_CTX(node), &node->value, LY_VALUE_XML,
&ns_list, &dynamic, NULL);
- LY_CHECK_RET(!value, LY_EINVAL);
+ LY_CHECK_ERR_GOTO(!value, rc = LY_EINVAL, cleanup);
+
+ /* print node opening */
+ xml_print_node_open(pctx, &node->node);
/* print namespaces connected with the values's prefixes */
- for (uint32_t u = 0; u < ns_list.count; ++u) {
- const struct lys_module *mod = (const struct lys_module *)ns_list.objs[u];
-
+ for (i = 1; i < ns_list.count; ++i) {
+ mod = ns_list.objs[i];
ly_print_(pctx->out, " xmlns:%s=\"%s\"", mod->prefix, mod->ns);
}
- ly_set_erase(&ns_list, NULL);
if (!value[0]) {
ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
@@ -338,11 +353,13 @@
lyxml_dump_text(pctx->out, value, 0);
ly_print_(pctx->out, "</%s>%s", node->schema->name, DO_FORMAT ? "\n" : "");
}
+
+cleanup:
+ ly_set_erase(&ns_list, NULL);
if (dynamic) {
free((void *)value);
}
-
- return LY_SUCCESS;
+ return rc;
}
/**
diff --git a/src/schema_compile.c b/src/schema_compile.c
index 001a3af..9d77a1f 100644
--- a/src/schema_compile.c
+++ b/src/schema_compile.c
@@ -437,6 +437,10 @@
assert(implement || mod_p);
+ if (mod_p) {
+ *mod_p = NULL;
+ }
+
for (i = 0; i < expr->used; ++i) {
if ((expr->tokens[i] != LYXP_TOKEN_NAMETEST) && (expr->tokens[i] != LYXP_TOKEN_LITERAL)) {
/* token cannot have a prefix */
@@ -476,38 +480,6 @@
}
/**
- * @brief Check and optionally implement modules referenced by a when expression.
- *
- * @param[in] ctx Compile context.
- * @param[in] when When to check.
- * @param[in,out] unres Global unres structure.
- * @return LY_ERECOMPILE if the whole dep set needs to be recompiled for these whens to evaluate.
- * @return LY_ENOT if full check of this when should be skipped.
- * @return LY_ERR value on error.
- */
-static LY_ERR
-lys_compile_unres_when_implement(struct lysc_ctx *ctx, const struct lysc_when *when, struct lys_glob_unres *unres)
-{
- LY_ERR rc = LY_SUCCESS;
- const struct lys_module *mod = NULL;
-
- /* check whether all the referenced modules are implemented */
- rc = lys_compile_expr_implement(ctx->ctx, when->cond, LY_VALUE_SCHEMA_RESOLVED, when->prefixes,
- ctx->ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod);
- if (rc) {
- goto cleanup;
- } else if (mod) {
- LOGWRN(ctx->ctx, "When condition \"%s\" check skipped because referenced module \"%s\" is not implemented.",
- when->cond->expr, mod->name);
- rc = LY_ENOT;
- goto cleanup;
- }
-
-cleanup:
- return rc;
-}
-
-/**
* @brief Check when for cyclic dependencies.
*
* @param[in] set Set with all the referenced nodes.
@@ -519,7 +491,7 @@
{
struct lyxp_set tmp_set;
struct lyxp_set_scnode *xp_scnode;
- uint32_t i, j;
+ uint32_t i, j, idx;
LY_ARRAY_COUNT_TYPE u;
LY_ERR ret = LY_SUCCESS;
@@ -565,36 +537,46 @@
}
for (j = 0; j < tmp_set.used; ++j) {
- /* skip roots'n'stuff */
- if (tmp_set.val.scnodes[j].type == LYXP_NODE_ELEM) {
- /* try to find this node in our set */
- uint32_t idx;
-
- if (lyxp_set_scnode_contains(set, tmp_set.val.scnodes[j].scnode, LYXP_NODE_ELEM, -1, &idx) &&
- (set->val.scnodes[idx].in_ctx == LYXP_SET_SCNODE_START_USED)) {
- LOGVAL(set->ctx, LYVE_SEMANTICS, "When condition cyclic dependency on the node \"%s\".",
- tmp_set.val.scnodes[j].scnode->name);
- ret = LY_EVALID;
- LOG_LOCBACK(1, 0, 0, 0);
- goto cleanup;
- }
-
- /* needs to be checked, if in both sets, will be ignored */
- tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
- } else {
- /* no when, nothing to check */
+ if (tmp_set.val.scnodes[j].type != LYXP_NODE_ELEM) {
+ /* skip roots'n'stuff, no when, nothing to check */
tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_NODE;
+ continue;
+ }
+
+ /* try to find this node in our set */
+ if (lyxp_set_scnode_contains(set, tmp_set.val.scnodes[j].scnode, LYXP_NODE_ELEM, -1, &idx) &&
+ (set->val.scnodes[idx].in_ctx == LYXP_SET_SCNODE_START_USED)) {
+ LOGVAL(set->ctx, LYVE_SEMANTICS, "When condition cyclic dependency on the node \"%s\".",
+ tmp_set.val.scnodes[j].scnode->name);
+ ret = LY_EVALID;
+ LOG_LOCBACK(1, 0, 0, 0);
+ goto cleanup;
+ }
+
+ /* needs to be checked, if in both sets, will be ignored */
+ tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
+ }
+
+ if (when->context != node) {
+ /* node actually depends on this "when", not the context node */
+ assert(tmp_set.val.scnodes[0].scnode == when->context);
+ if (tmp_set.val.scnodes[0].in_ctx == LYXP_SET_SCNODE_START_USED) {
+ /* replace the non-traversed context node with the dependent node */
+ tmp_set.val.scnodes[0].scnode = (struct lysc_node *)node;
+ } else {
+ /* context node was traversed, so just add the dependent node */
+ ret = lyxp_set_scnode_insert_node(&tmp_set, node, LYXP_SET_SCNODE_START_USED, LYXP_AXIS_CHILD, NULL);
+ LY_CHECK_ERR_GOTO(ret, LOG_LOCBACK(1, 0, 0, 0), cleanup);
}
}
/* merge this set into the global when set */
lyxp_set_scnode_merge(set, &tmp_set);
}
+ LOG_LOCBACK(1, 0, 0, 0);
/* check when of non-data parents as well */
node = node->parent;
-
- LOG_LOCBACK(1, 0, 0, 0);
} while (node && (node->nodetype & (LYS_CASE | LYS_CHOICE)));
/* this node when was checked (xp_scnode could have been reallocd) */
@@ -640,6 +622,7 @@
{
struct lyxp_set tmp_set = {0};
uint32_t i, opts;
+ struct lysc_node *schema;
LY_ERR ret = LY_SUCCESS;
opts = LYXP_SCNODE_SCHEMA | ((node->flags & LYS_IS_OUTPUT) ? LYXP_SCNODE_OUTPUT : 0);
@@ -655,28 +638,45 @@
ctx->path[0] = '\0';
lysc_path(node, LYSC_PATH_LOG, ctx->path, LYSC_CTX_BUFSIZE);
for (i = 0; i < tmp_set.used; ++i) {
- /* skip roots'n'stuff */
- if ((tmp_set.val.scnodes[i].type == LYXP_NODE_ELEM) &&
- (tmp_set.val.scnodes[i].in_ctx != LYXP_SET_SCNODE_START_USED)) {
- struct lysc_node *schema = tmp_set.val.scnodes[i].scnode;
+ if (tmp_set.val.scnodes[i].type != LYXP_NODE_ELEM) {
+ /* skip roots'n'stuff */
+ continue;
+ } else if (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_START_USED) {
+ /* context node not actually traversed */
+ continue;
+ }
- /* XPath expression cannot reference "lower" status than the node that has the definition */
- if (lysc_check_status(NULL, when->flags, node->module, node->name, schema->flags, schema->module,
- schema->name)) {
- LOGWRN(ctx->ctx, "When condition \"%s\" may be referencing %s node \"%s\".", when->cond->expr,
- (schema->flags == LYS_STATUS_OBSLT) ? "obsolete" : "deprecated", schema->name);
- }
+ schema = tmp_set.val.scnodes[i].scnode;
- /* check dummy node children/value accessing */
- if (lysc_data_parent(schema) == node) {
- LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node children.");
- ret = LY_EVALID;
- goto cleanup;
- } else if ((schema == node) && (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_VAL)) {
- LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node value.");
- ret = LY_EVALID;
- goto cleanup;
- }
+ /* XPath expression cannot reference "lower" status than the node that has the definition */
+ if (lysc_check_status(NULL, when->flags, node->module, node->name, schema->flags, schema->module,
+ schema->name)) {
+ LOGWRN(ctx->ctx, "When condition \"%s\" may be referencing %s node \"%s\".", when->cond->expr,
+ (schema->flags == LYS_STATUS_OBSLT) ? "obsolete" : "deprecated", schema->name);
+ }
+
+ /* check dummy node children/value accessing */
+ if (lysc_data_parent(schema) == node) {
+ LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node children.");
+ ret = LY_EVALID;
+ goto cleanup;
+ } else if ((schema == node) && (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_VAL)) {
+ LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node value.");
+ ret = LY_EVALID;
+ goto cleanup;
+ }
+ }
+
+ if (when->context != node) {
+ /* node actually depends on this "when", not the context node */
+ assert(tmp_set.val.scnodes[0].scnode == when->context);
+ if (tmp_set.val.scnodes[0].in_ctx == LYXP_SET_SCNODE_START_USED) {
+ /* replace the non-traversed context node with the dependent node */
+ tmp_set.val.scnodes[0].scnode = (struct lysc_node *)node;
+ } else {
+ /* context node was traversed, so just add the dependent node */
+ ret = lyxp_set_scnode_insert_node(&tmp_set, node, LYXP_SET_SCNODE_START_USED, LYXP_AXIS_CHILD, NULL);
+ LY_CHECK_GOTO(ret, cleanup);
}
}
@@ -695,20 +695,16 @@
* @param[in] ctx Compile context.
* @param[in] node Node to check.
* @param[in] local_mods Sized array of local modules for musts of @p node at the same index.
- * @param[in,out] unres Global unres structure.
- * @return LY_ERECOMPILE
- * @return LY_ERR value
+ * @return LY_ERR value.
*/
static LY_ERR
-lys_compile_unres_must(struct lysc_ctx *ctx, const struct lysc_node *node, const struct lysp_module **local_mods,
- struct lys_glob_unres *unres)
+lys_compile_unres_must(struct lysc_ctx *ctx, const struct lysc_node *node, const struct lysp_module **local_mods)
{
struct lyxp_set tmp_set;
uint32_t i, opts;
LY_ARRAY_COUNT_TYPE u;
- struct lysc_must *musts = NULL;
+ struct lysc_must *musts;
LY_ERR ret = LY_SUCCESS;
- const struct lys_module *mod;
uint16_t flg;
LOG_LOCSET(node, NULL, NULL, NULL);
@@ -718,18 +714,6 @@
musts = lysc_node_musts(node);
LY_ARRAY_FOR(musts, u) {
- /* first check whether all the referenced modules are implemented */
- mod = NULL;
- ret = lys_compile_expr_implement(ctx->ctx, musts[u].cond, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes,
- ctx->ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod);
- if (ret) {
- goto cleanup;
- } else if (mod) {
- LOGWRN(ctx->ctx, "Must condition \"%s\" check skipped because referenced module \"%s\" is not implemented.",
- musts[u].cond->expr, mod->name);
- continue;
- }
-
/* check "must" */
ret = lyxp_atomize(ctx->ctx, musts[u].cond, node->module, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes, node,
node, &tmp_set, opts);
@@ -853,13 +837,11 @@
* @param[in] node Context node for the leafref.
* @param[in] lref Leafref to check/resolve.
* @param[in] local_mod Local module for the leafref type.
- * @param[in,out] unres Global unres structure.
- * @return LY_ERECOMPILE if context recompilation is needed,
* @return LY_ERR value.
*/
static LY_ERR
lys_compile_unres_leafref(struct lysc_ctx *ctx, const struct lysc_node *node, struct lysc_type_leafref *lref,
- const struct lysp_module *local_mod, struct lys_glob_unres *unres)
+ const struct lysp_module *local_mod)
{
const struct lysc_node *target = NULL;
struct ly_path *p;
@@ -873,9 +855,6 @@
return LY_SUCCESS;
}
- /* first implement all the modules in the path */
- LY_CHECK_RET(lys_compile_expr_implement(ctx->ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL));
-
/* try to find the target, current module is that of the context node (RFC 7950 6.4.1 second bullet) */
LY_CHECK_RET(ly_path_compile_leafref(ctx->ctx, node, ctx->ext, lref->path,
(node->flags & LYS_IS_OUTPUT) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY,
@@ -945,6 +924,7 @@
* @param[in] dflt_pmod Parsed module of the @p dflt to resolve possible prefixes.
* @param[in,out] storage Storage for the compiled default value.
* @param[in,out] unres Global unres structure for newly implemented modules.
+ * @return LY_ERECOMPILE if the whole dep set needs to be recompiled for the value to be checked.
* @return LY_ERR value.
*/
static LY_ERR
@@ -1125,6 +1105,108 @@
}
/**
+ * @brief Implement all referenced modules by leafrefs, when and must conditions.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] unres Global unres structure with the sets to resolve.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERECOMPILE if the whole dep set needs to be recompiled with the new implemented modules.
+ * @return LY_ERR value on error.
+ */
+static LY_ERR
+lys_compile_unres_depset_implement(struct ly_ctx *ctx, struct lys_glob_unres *unres)
+{
+ struct lys_depset_unres *ds_unres = &unres->ds_unres;
+ struct lysc_type_leafref *lref;
+ const struct lys_module *mod;
+ LY_ARRAY_COUNT_TYPE u;
+ struct lysc_unres_leafref *l;
+ struct lysc_unres_when *w;
+ struct lysc_unres_must *m;
+ struct lysc_must *musts;
+ ly_bool not_implemented;
+ uint32_t di = 0, li = 0, wi = 0, mi = 0;
+
+implement_all:
+ /* disabled leafrefs - even those because we need to check their target exists */
+ while (di < ds_unres->disabled_leafrefs.count) {
+ l = ds_unres->disabled_leafrefs.objs[di];
+
+ u = 0;
+ while ((lref = lys_type_leafref_next(l->node, &u))) {
+ LY_CHECK_RET(lys_compile_expr_implement(ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL));
+ }
+
+ ++di;
+ }
+
+ /* leafrefs */
+ while (li < ds_unres->leafrefs.count) {
+ l = ds_unres->leafrefs.objs[li];
+
+ u = 0;
+ while ((lref = lys_type_leafref_next(l->node, &u))) {
+ LY_CHECK_RET(lys_compile_expr_implement(ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL));
+ }
+
+ ++li;
+ }
+
+ /* when conditions */
+ while (wi < ds_unres->whens.count) {
+ w = ds_unres->whens.objs[wi];
+
+ LY_CHECK_RET(lys_compile_expr_implement(ctx, w->when->cond, LY_VALUE_SCHEMA_RESOLVED, w->when->prefixes,
+ ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod));
+ if (mod) {
+ LOGWRN(ctx, "When condition \"%s\" check skipped because referenced module \"%s\" is not implemented.",
+ w->when->cond->expr, mod->name);
+
+ /* remove from the set to skip the check */
+ ly_set_rm_index(&ds_unres->whens, wi, free);
+ continue;
+ }
+
+ ++wi;
+ }
+
+ /* must conditions */
+ while (mi < ds_unres->musts.count) {
+ m = ds_unres->musts.objs[mi];
+
+ not_implemented = 0;
+ musts = lysc_node_musts(m->node);
+ LY_ARRAY_FOR(musts, u) {
+ LY_CHECK_RET(lys_compile_expr_implement(ctx, musts[u].cond, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes,
+ ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod));
+ if (mod) {
+ LOGWRN(ctx, "Must condition \"%s\" check skipped because referenced module \"%s\" is not implemented.",
+ musts[u].cond->expr, mod->name);
+
+ /* need to implement modules from all the expressions */
+ not_implemented = 1;
+ }
+ }
+
+ if (not_implemented) {
+ /* remove from the set to skip the check */
+ lysc_unres_must_free(m);
+ ly_set_rm_index(&ds_unres->musts, mi, NULL);
+ continue;
+ }
+
+ ++mi;
+ }
+
+ if ((di < ds_unres->disabled_leafrefs.count) || (li < ds_unres->leafrefs.count) || (wi < ds_unres->whens.count)) {
+ /* new items in the sets */
+ goto implement_all;
+ }
+
+ return LY_SUCCESS;
+}
+
+/**
* @brief Finish dependency set compilation by resolving all the unres sets.
*
* @param[in] ctx libyang context.
@@ -1136,7 +1218,7 @@
static LY_ERR
lys_compile_unres_depset(struct ly_ctx *ctx, struct lys_glob_unres *unres)
{
- LY_ERR ret = LY_SUCCESS, r;
+ LY_ERR ret = LY_SUCCESS;
struct lysc_node *node;
struct lysc_type *typeiter;
struct lysc_type_leafref *lref;
@@ -1151,7 +1233,12 @@
uint32_t i, processed_leafrefs = 0;
resolve_all:
- /* check disabled leafrefs first */
+ /* implement all referenced modules to get final ds_unres set */
+ if ((ret = lys_compile_unres_depset_implement(ctx, unres))) {
+ goto cleanup;
+ }
+
+ /* check disabled leafrefs */
while (ds_unres->disabled_leafrefs.count) {
/* remember index, it can change before we get to free this item */
i = ds_unres->disabled_leafrefs.count - 1;
@@ -1161,7 +1248,7 @@
LOG_LOCSET(l->node, NULL, NULL, NULL);
v = 0;
while ((ret == LY_SUCCESS) && (lref = lys_type_leafref_next(l->node, &v))) {
- ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod, unres);
+ ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod);
}
LOG_LOCBACK(1, 0, 0, 0);
LY_CHECK_GOTO(ret, cleanup);
@@ -1180,7 +1267,7 @@
LOG_LOCSET(l->node, NULL, NULL, NULL);
v = 0;
while ((ret == LY_SUCCESS) && (lref = lys_type_leafref_next(l->node, &v))) {
- ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod, unres);
+ ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod);
}
LOG_LOCBACK(1, 0, 0, 0);
LY_CHECK_GOTO(ret, cleanup);
@@ -1204,29 +1291,7 @@
processed_leafrefs++;
}
- /* check when, first implement all the referenced modules (for the cyclic check in the next loop to work) */
- i = 0;
- while (i < ds_unres->whens.count) {
- w = ds_unres->whens.objs[i];
- LYSC_CTX_INIT_PMOD(cctx, w->node->module->parsed, NULL);
-
- LOG_LOCSET(w->node, NULL, NULL, NULL);
- r = lys_compile_unres_when_implement(&cctx, w->when, unres);
- LOG_LOCBACK(w->node ? 1 : 0, 0, 0, 0);
-
- if (r == LY_ENOT) {
- /* skip full when check, remove from the set */
- free(w);
- ly_set_rm_index(&ds_unres->whens, i, NULL);
- continue;
- } else if (r) {
- /* error */
- ret = r;
- goto cleanup;
- }
-
- ++i;
- }
+ /* check when, the referenced modules must be implemented now */
while (ds_unres->whens.count) {
i = ds_unres->whens.count - 1;
w = ds_unres->whens.objs[i];
@@ -1248,7 +1313,7 @@
LYSC_CTX_INIT_PMOD(cctx, m->node->module->parsed, m->ext);
LOG_LOCSET(m->node, NULL, NULL, NULL);
- ret = lys_compile_unres_must(&cctx, m->node, m->local_mods, unres);
+ ret = lys_compile_unres_must(&cctx, m->node, m->local_mods);
LOG_LOCBACK(1, 0, 0, 0);
LY_CHECK_GOTO(ret, cleanup);
@@ -1289,7 +1354,7 @@
ly_set_rm_index(&ds_unres->dflts, i, NULL);
}
- /* some unres items may have been added */
+ /* some unres items may have been added by the default values */
if ((processed_leafrefs != ds_unres->leafrefs.count) || ds_unres->disabled_leafrefs.count ||
ds_unres->whens.count || ds_unres->musts.count || ds_unres->dflts.count) {
goto resolve_all;
diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c
index 8c5a9e7..2723716 100644
--- a/src/schema_compile_node.c
+++ b/src/schema_compile_node.c
@@ -3417,7 +3417,7 @@
/* YANG 1.0 denies key to be of empty type */
if (key->type->basetype == LY_TYPE_EMPTY) {
LOGVAL(ctx->ctx, LYVE_SEMANTICS,
- "List's key cannot be of \"empty\" type until it is in YANG 1.1 module.");
+ "List key of the \"empty\" type is allowed only in YANG 1.1 modules.");
return LY_EVALID;
}
} else {
diff --git a/src/tree_data.c b/src/tree_data.c
index d38acf2..c7fb06e 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -99,7 +99,7 @@
lyd_parse(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, struct lyd_node **first_p,
struct ly_in *in, LYD_FORMAT format, uint32_t parse_opts, uint32_t val_opts, struct lyd_node **op)
{
- LY_ERR r, rc = LY_SUCCESS;
+ LY_ERR r = LY_SUCCESS, rc = LY_SUCCESS;
struct lyd_ctx *lydctx = NULL;
struct ly_set parsed = {0};
struct lyd_node *first;
@@ -1372,28 +1372,30 @@
iter1 = lyd_child(node1);
iter2 = lyd_child(node2);
- if (!(node1->schema->flags & LYS_KEYLESS) && !(options & LYD_COMPARE_FULL_RECURSION)) {
- /* lists with keys, their equivalence is based on their keys */
- for (const struct lysc_node *key = lysc_node_child(node1->schema);
- key && (key->flags & LYS_KEY);
- key = key->next) {
- if (!iter1 || !iter2) {
- return (iter1 == iter2) ? LY_SUCCESS : LY_ENOT;
- }
- r = lyd_compare_single_schema(iter1, iter2, options, 1);
- LY_CHECK_RET(r);
- r = lyd_compare_single_data(iter1, iter2, options);
- LY_CHECK_RET(r);
-
- iter1 = iter1->next;
- iter2 = iter2->next;
- }
-
- return LY_SUCCESS;
- } else {
- /* lists without keys, their equivalence is based on equivalence of all the children (both direct and indirect) */
+ if (options & LYD_COMPARE_FULL_RECURSION) {
return lyd_compare_siblings_(iter1, iter2, options, 1);
+ } else if (node1->schema->flags & LYS_KEYLESS) {
+ /* always equal */
+ return LY_SUCCESS;
}
+
+ /* lists with keys, their equivalence is based on their keys */
+ for (const struct lysc_node *key = lysc_node_child(node1->schema);
+ key && (key->flags & LYS_KEY);
+ key = key->next) {
+ if (!iter1 || !iter2) {
+ return (iter1 == iter2) ? LY_SUCCESS : LY_ENOT;
+ }
+ r = lyd_compare_single_schema(iter1, iter2, options, 1);
+ LY_CHECK_RET(r);
+ r = lyd_compare_single_data(iter1, iter2, options);
+ LY_CHECK_RET(r);
+
+ iter1 = iter1->next;
+ iter2 = iter2->next;
+ }
+
+ return LY_SUCCESS;
case LYS_ANYXML:
case LYS_ANYDATA:
any1 = (struct lyd_node_any *)node1;
@@ -1834,10 +1836,11 @@
* @return LY_ERR value.
*/
static LY_ERR
-lyd_dup_get_local_parent(const struct lyd_node *node, const struct ly_ctx *trg_ctx, const struct lyd_node_inner *parent,
- uint32_t options, struct lyd_node **dup_parent, struct lyd_node_inner **local_parent)
+lyd_dup_get_local_parent(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node *parent,
+ uint32_t options, struct lyd_node **dup_parent, struct lyd_node **local_parent)
{
- const struct lyd_node_inner *orig_parent, *iter;
+ const struct lyd_node *orig_parent;
+ struct lyd_node *iter;
ly_bool repeat = 1, ext_parent = 0;
*dup_parent = NULL;
@@ -1846,7 +1849,7 @@
if (node->flags & LYD_EXT) {
ext_parent = 1;
}
- for (orig_parent = node->parent; repeat && orig_parent; orig_parent = orig_parent->parent) {
+ for (orig_parent = lyd_parent(node); repeat && orig_parent; orig_parent = lyd_parent(orig_parent)) {
if (ext_parent) {
/* use the standard context */
trg_ctx = LYD_CTX(orig_parent);
@@ -1857,32 +1860,23 @@
repeat = 0;
} else if (parent && (LYD_CTX(parent) != LYD_CTX(orig_parent)) &&
lyd_compare_schema_equal(parent->schema, orig_parent->schema) &&
- lyd_compare_schema_parents_equal(&parent->node, &orig_parent->node)) {
+ lyd_compare_schema_parents_equal(parent, orig_parent)) {
iter = parent;
repeat = 0;
} else {
iter = NULL;
- LY_CHECK_RET(lyd_dup_r((struct lyd_node *)orig_parent, trg_ctx, NULL, 0, (struct lyd_node **)&iter, options,
- (struct lyd_node **)&iter));
+ LY_CHECK_RET(lyd_dup_r(orig_parent, trg_ctx, NULL, 0, &iter, options, &iter));
}
+
if (!*local_parent) {
- *local_parent = (struct lyd_node_inner *)iter;
+ /* update local parent (created parent) */
+ *local_parent = iter;
}
- if (iter->child) {
- /* 1) list - add after keys
- * 2) provided parent with some children */
- iter->child->prev->next = *dup_parent;
- if (*dup_parent) {
- (*dup_parent)->prev = iter->child->prev;
- iter->child->prev = *dup_parent;
- }
- } else {
- ((struct lyd_node_inner *)iter)->child = *dup_parent;
- }
+
if (*dup_parent) {
- (*dup_parent)->parent = (struct lyd_node_inner *)iter;
+ lyd_insert_node(iter, NULL, *dup_parent, 0);
}
- *dup_parent = (struct lyd_node *)iter;
+ *dup_parent = iter;
if (orig_parent->flags & LYD_EXT) {
ext_parent = 1;
}
@@ -1890,8 +1884,8 @@
if (repeat && parent) {
/* given parent and created parents chain actually do not interconnect */
- LOGERR(trg_ctx, LY_EINVAL,
- "Invalid argument parent (%s()) - does not interconnect with the created node's parents chain.", __func__);
+ LOGERR(trg_ctx, LY_EINVAL, "None of the duplicated node \"%s\" schema parents match the provided parent \"%s\".",
+ LYD_NAME(node), LYD_NAME(parent));
return LY_EINVAL;
}
@@ -1899,14 +1893,14 @@
}
static LY_ERR
-lyd_dup(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node_inner *parent, uint32_t options,
+lyd_dup(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node *parent, uint32_t options,
ly_bool nosiblings, struct lyd_node **dup)
{
LY_ERR rc;
const struct lyd_node *orig; /* original node to be duplicated */
struct lyd_node *first = NULL; /* the first duplicated node, this is returned */
struct lyd_node *top = NULL; /* the most higher created node */
- struct lyd_node_inner *local_parent = NULL; /* the direct parent node for the duplicated node(s) */
+ struct lyd_node *local_parent = NULL; /* the direct parent node for the duplicated node(s) */
assert(node && trg_ctx);
@@ -1921,7 +1915,7 @@
if (lysc_is_key(orig->schema)) {
if (local_parent) {
/* the key must already exist in the parent */
- rc = lyd_find_sibling_schema(local_parent->child, orig->schema, first ? NULL : &first);
+ rc = lyd_find_sibling_schema(lyd_child(local_parent), orig->schema, first ? NULL : &first);
LY_CHECK_ERR_GOTO(rc, LOGINT(trg_ctx), error);
} else {
assert(!(options & LYD_DUP_WITH_PARENTS));
@@ -1931,8 +1925,7 @@
}
} else {
/* if there is no local parent, it will be inserted into first */
- rc = lyd_dup_r(orig, trg_ctx, local_parent ? &local_parent->node : NULL, 0, &first, options,
- first ? NULL : &first);
+ rc = lyd_dup_r(orig, trg_ctx, local_parent, 0, &first, options, first ? NULL : &first);
LY_CHECK_GOTO(rc, error);
}
if (nosiblings) {
@@ -1989,7 +1982,7 @@
LY_CHECK_ARG_RET(NULL, node, LY_EINVAL);
LY_CHECK_RET(lyd_dup_ctx_check(node, parent));
- return lyd_dup(node, LYD_CTX(node), parent, options, 1, dup);
+ return lyd_dup(node, LYD_CTX(node), (struct lyd_node *)parent, options, 1, dup);
}
LIBYANG_API_DEF LY_ERR
@@ -1998,7 +1991,7 @@
{
LY_CHECK_ARG_RET(trg_ctx, node, trg_ctx, LY_EINVAL);
- return lyd_dup(node, trg_ctx, parent, options, 1, dup);
+ return lyd_dup(node, trg_ctx, (struct lyd_node *)parent, options, 1, dup);
}
LIBYANG_API_DEF LY_ERR
@@ -2007,7 +2000,7 @@
LY_CHECK_ARG_RET(NULL, node, LY_EINVAL);
LY_CHECK_RET(lyd_dup_ctx_check(node, parent));
- return lyd_dup(node, LYD_CTX(node), parent, options, 0, dup);
+ return lyd_dup(node, LYD_CTX(node), (struct lyd_node *)parent, options, 0, dup);
}
LIBYANG_API_DEF LY_ERR
@@ -2016,7 +2009,7 @@
{
LY_CHECK_ARG_RET(trg_ctx, node, trg_ctx, LY_EINVAL);
- return lyd_dup(node, trg_ctx, parent, options, 0, dup);
+ return lyd_dup(node, trg_ctx, (struct lyd_node *)parent, options, 0, dup);
}
LIBYANG_API_DEF LY_ERR
@@ -2485,7 +2478,8 @@
/* parent */
parent = (depth > 1) ? dnodes->dnodes[depth - 2] : NULL;
- assert(!parent || !iter->schema || !parent->schema || (lysc_data_parent(iter->schema) == parent->schema) ||
+ assert(!parent || !iter->schema || !parent->schema || (parent->schema->nodetype & LYD_NODE_ANY) ||
+ (lysc_data_parent(iter->schema) == parent->schema) ||
(!lysc_data_parent(iter->schema) && (LYD_CTX(iter) != LYD_CTX(parent))));
/* get module to print, if any */
@@ -2619,14 +2613,14 @@
siblings = lyd_first_sibling(siblings);
parent = siblings->parent;
- if (parent && parent->schema && parent->children_ht) {
+ if (target->schema && parent && parent->schema && parent->children_ht) {
assert(target->hash);
if (lysc_is_dup_inst_list(target->schema)) {
/* we must search the instances from beginning to find the first matching one */
found = 0;
LYD_LIST_FOR_INST(siblings, target->schema, iter) {
- if (!lyd_compare_single(target, iter, 0)) {
+ if (!lyd_compare_single(target, iter, LYD_COMPARE_FULL_RECURSION)) {
found = 1;
break;
}
@@ -2646,10 +2640,16 @@
}
}
} else {
- /* no children hash table */
+ /* no children hash table or cannot be used */
for ( ; siblings; siblings = siblings->next) {
- if (!lyd_compare_single(siblings, target, LYD_COMPARE_OPAQ)) {
- break;
+ if (lysc_is_dup_inst_list(target->schema)) {
+ if (!lyd_compare_single(siblings, target, LYD_COMPARE_FULL_RECURSION)) {
+ break;
+ }
+ } else {
+ if (!lyd_compare_single(siblings, target, LYD_COMPARE_OPAQ)) {
+ break;
+ }
}
}
}
@@ -2736,6 +2736,7 @@
{
struct lyd_node **match_p, *first, *iter;
struct lyd_node_inner *parent;
+ uint32_t comp_opts;
LY_CHECK_ARG_RET(NULL, target, set, LY_EINVAL);
LY_CHECK_CTX_EQUAL_RET(siblings ? LYD_CTX(siblings) : NULL, LYD_CTX(target), LY_EINVAL);
@@ -2747,6 +2748,9 @@
return LY_ENOTFOUND;
}
+ /* set options */
+ comp_opts = LYD_COMPARE_OPAQ | (lysc_is_dup_inst_list(target->schema) ? LYD_COMPARE_FULL_RECURSION : 0);
+
/* get first sibling */
siblings = lyd_first_sibling(siblings);
@@ -2771,7 +2775,7 @@
}
while (iter) {
/* add all found nodes into the set */
- if ((iter != first) && !lyd_compare_single(iter, target, 0) && ly_set_add(*set, iter, 1, NULL)) {
+ if ((iter != first) && !lyd_compare_single(iter, target, comp_opts) && ly_set_add(*set, iter, 1, NULL)) {
goto error;
}
@@ -2786,7 +2790,7 @@
} else {
/* no children hash table */
LY_LIST_FOR(siblings, siblings) {
- if (!lyd_compare_single(target, siblings, LYD_COMPARE_OPAQ)) {
+ if (!lyd_compare_single(target, siblings, comp_opts)) {
ly_set_add(*set, (void *)siblings, 1, NULL);
}
}
diff --git a/src/tree_data.h b/src/tree_data.h
index dddeb6c..2c3066d 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -2144,7 +2144,7 @@
* @param[in] first First data tree.
* @param[in] second Second data tree.
* @param[in] options Bitmask of options flags, see @ref diffoptions.
- * @param[out] diff Generated diff.
+ * @param[out] diff Generated diff, NULL if there are no differences.
* @return LY_SUCCESS on success,
* @return LY_ERR on error.
*/
@@ -2159,7 +2159,7 @@
* @param[in] first First data tree.
* @param[in] second Second data tree.
* @param[in] options Bitmask of options flags, see @ref diffoptions.
- * @param[out] diff Generated diff.
+ * @param[out] diff Generated diff, NULL if there are no differences.
* @return LY_SUCCESS on success,
* @return LY_ERR on error.
*/
diff --git a/src/tree_data_common.c b/src/tree_data_common.c
index 69c3851..4602e98 100644
--- a/src/tree_data_common.c
+++ b/src/tree_data_common.c
@@ -499,8 +499,8 @@
}
LY_ERR
-lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
- LY_VALUE_FORMAT format, void *prefix_data)
+ly_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
+ LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints)
{
LY_ERR rc = LY_SUCCESS;
struct ly_err_item *err = NULL;
@@ -515,8 +515,8 @@
}
type = ((struct lysc_node_leaf *)node)->type;
- rc = type->plugin->store(ctx ? ctx : node->module->ctx, type, value, value_len, 0, format, prefix_data,
- LYD_HINT_SCHEMA, node, &storage, NULL, &err);
+ rc = type->plugin->store(ctx ? ctx : node->module->ctx, type, value, value_len, 0, format, prefix_data, hints, node,
+ &storage, NULL, &err);
if (rc == LY_EINCOMPLETE) {
/* actually success since we do not provide the context tree and call validation with
* LY_TYPE_OPTS_INCOMPLETE_DATA */
@@ -748,8 +748,8 @@
assert(!node->schema);
/* get all keys into a set */
- while ((key = lys_getnext(key, snode, NULL, 0)) && (snode->flags & LYS_KEY)) {
- LY_CHECK_GOTO(ret = ly_set_add(&key_set, (void *)snode, 1, NULL), cleanup);
+ while ((key = lys_getnext(key, snode, NULL, 0)) && (key->flags & LYS_KEY)) {
+ LY_CHECK_GOTO(ret = ly_set_add(&key_set, (void *)key, 1, NULL), cleanup);
}
LY_LIST_FOR(lyd_child(node), child) {
@@ -778,8 +778,8 @@
ly_set_rm_index(&key_set, i, NULL);
/* check value */
- ret = lys_value_validate(LYD_CTX(node), key, opaq_k->value, strlen(opaq_k->value), opaq_k->format,
- opaq_k->val_prefix_data);
+ ret = ly_value_validate(LYD_CTX(node), key, opaq_k->value, strlen(opaq_k->value), opaq_k->format,
+ opaq_k->val_prefix_data, opaq_k->hints);
LY_CHECK_GOTO(ret, cleanup);
}
@@ -889,7 +889,7 @@
if (snode->nodetype & LYD_NODE_TERM) {
/* leaf / leaf-list */
- rc = lys_value_validate(ctx, snode, opaq->value, strlen(opaq->value), opaq->format, opaq->val_prefix_data);
+ rc = ly_value_validate(ctx, snode, opaq->value, strlen(opaq->value), opaq->format, opaq->val_prefix_data, opaq->hints);
LY_CHECK_GOTO(rc, cleanup);
} else if (snode->nodetype == LYS_LIST) {
/* list */
diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h
index e87723b..c7449ce 100644
--- a/src/tree_data_internal.h
+++ b/src/tree_data_internal.h
@@ -506,8 +506,7 @@
const struct lyd_node *ctx_node, const struct lyd_node *tree);
/**
- * @brief Check type restrictions applicable to the particular leaf/leaf-list with the given string @p value coming
- * from a schema.
+ * @brief Check type restrictions applicable to the particular leaf/leaf-list with the given string @p value.
*
* This function check just the type's restriction, if you want to check also the data tree context (e.g. in case of
* require-instance restriction), use ::lyd_value_validate().
@@ -518,11 +517,12 @@
* @param[in] value_len Length of the given @p value (mandatory).
* @param[in] format Value prefix format.
* @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix).
+ * @param[in] hints Value encoding hints.
* @return LY_SUCCESS on success
* @return LY_ERR value if an error occurred.
*/
-LY_ERR lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
- LY_VALUE_FORMAT format, void *prefix_data);
+LY_ERR ly_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
+ LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints);
/**
* @defgroup datahash Data nodes hash manipulation
diff --git a/src/tree_data_new.c b/src/tree_data_new.c
index 2f92c7e..090e7ac 100644
--- a/src/tree_data_new.c
+++ b/src/tree_data_new.c
@@ -1527,7 +1527,7 @@
}
/* parse path */
- LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL,
+ LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_FIRST,
LY_PATH_PRED_SIMPLE, &exp), cleanup);
/* compile path */
@@ -1903,9 +1903,7 @@
}
LYD_TREE_DFS_BEGIN(tree, node) {
- /* skip added default nodes */
- if (((node->flags & (LYD_DEFAULT | LYD_NEW)) != (LYD_DEFAULT | LYD_NEW)) &&
- (node->schema->nodetype & LYD_NODE_INNER)) {
+ if (node->schema->nodetype & LYD_NODE_INNER) {
LY_CHECK_GOTO(ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &node_when, NULL,
NULL, implicit_options, diff), cleanup);
}
@@ -1990,16 +1988,12 @@
/* process nested nodes */
LY_LIST_FOR(*tree, root) {
- /* skip added default nodes */
- if ((root->flags & (LYD_DEFAULT | LYD_NEW)) != (LYD_DEFAULT | LYD_NEW)) {
- LY_CHECK_GOTO(ret = lyd_new_implicit_tree(root, implicit_options, diff ? &d : NULL), cleanup);
+ LY_CHECK_GOTO(ret = lyd_new_implicit_tree(root, implicit_options, diff ? &d : NULL), cleanup);
- if (d) {
- /* merge into one diff */
- lyd_insert_sibling(*diff, d, diff);
-
- d = NULL;
- }
+ if (d) {
+ /* merge into one diff */
+ lyd_insert_sibling(*diff, d, diff);
+ d = NULL;
}
}
diff --git a/src/tree_schema.c b/src/tree_schema.c
index 487a473..7a119b9 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -442,7 +442,7 @@
struct ly_set **set)
{
LY_ERR ret = LY_SUCCESS;
- struct lyxp_set xp_set;
+ struct lyxp_set xp_set = {0};
struct lyxp_expr *exp = NULL;
uint32_t i;
@@ -455,7 +455,9 @@
ctx = ctx_node->module->ctx;
}
- memset(&xp_set, 0, sizeof xp_set);
+ /* allocate return set */
+ ret = ly_set_new(set);
+ LY_CHECK_GOTO(ret, cleanup);
/* compile expression */
ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp);
@@ -465,10 +467,6 @@
ret = lyxp_atomize(ctx, exp, NULL, LY_VALUE_JSON, NULL, ctx_node, ctx_node, &xp_set, options);
LY_CHECK_GOTO(ret, cleanup);
- /* allocate return set */
- ret = ly_set_new(set);
- LY_CHECK_GOTO(ret, cleanup);
-
/* transform into ly_set */
(*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs);
LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(ctx); ret = LY_EMEM, cleanup);
@@ -501,15 +499,15 @@
options = LYXP_SCNODE;
}
+ /* allocate return set */
+ ret = ly_set_new(set);
+ LY_CHECK_GOTO(ret, cleanup);
+
/* atomize expression */
ret = lyxp_atomize(cur_mod->ctx, expr, cur_mod, LY_VALUE_SCHEMA_RESOLVED, (void *)prefixes, ctx_node, ctx_node,
&xp_set, options);
LY_CHECK_GOTO(ret, cleanup);
- /* allocate return set */
- ret = ly_set_new(set);
- LY_CHECK_GOTO(ret, cleanup);
-
/* transform into ly_set */
(*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs);
LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(cur_mod->ctx); ret = LY_EMEM, cleanup);
@@ -552,6 +550,10 @@
ctx = ctx_node->module->ctx;
}
+ /* allocate return set */
+ ret = ly_set_new(set);
+ LY_CHECK_GOTO(ret, cleanup);
+
/* compile expression */
ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp);
LY_CHECK_GOTO(ret, cleanup);
@@ -560,10 +562,6 @@
ret = lyxp_atomize(ctx, exp, NULL, LY_VALUE_JSON, NULL, ctx_node, ctx_node, &xp_set, options);
LY_CHECK_GOTO(ret, cleanup);
- /* allocate return set */
- ret = ly_set_new(set);
- LY_CHECK_GOTO(ret, cleanup);
-
/* transform into ly_set */
(*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs);
LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(ctx); ret = LY_EMEM, cleanup);
diff --git a/src/xpath.c b/src/xpath.c
index 3184350..259fd9c 100644
--- a/src/xpath.c
+++ b/src/xpath.c
@@ -43,8 +43,6 @@
#include "tree_schema_internal.h"
#include "xml.h"
-static LY_ERR set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type,
- enum lyxp_axis axis, uint32_t *index_p);
static LY_ERR reparse_or_expr(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth);
static LY_ERR eval_expr_select(const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_expr_type etype,
struct lyxp_set *set, uint32_t options);
@@ -703,14 +701,14 @@
assert(!r);
(void)r;
- if (hnode.node == node) {
+ if ((hnode.node == node) && (hnode.type == type)) {
/* it was just added, do not add it twice */
- node = NULL;
+ return;
}
}
}
- if (set->ht && node) {
+ if (set->ht) {
/* add the new node into hash table */
hnode.node = node;
hnode.type = type;
@@ -893,7 +891,7 @@
(set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_START)) {
uint32_t idx;
- LY_CHECK_ERR_RET(set_scnode_insert_node(ret, set->val.scnodes[i].scnode, set->val.scnodes[i].type,
+ LY_CHECK_ERR_RET(lyxp_set_scnode_insert_node(ret, set->val.scnodes[i].scnode, set->val.scnodes[i].type,
set->val.scnodes[i].axis, &idx), lyxp_set_free(ret), NULL);
/* coverity seems to think scnodes can be NULL */
if (!ret->val.scnodes) {
@@ -993,11 +991,7 @@
return;
}
- if (trg->type == LYXP_SET_NODE_SET) {
- free(trg->val.nodes);
- } else if (trg->type == LYXP_SET_STRING) {
- free(trg->val.str);
- }
+ lyxp_set_free_content(trg);
set_init(trg, src);
if (src->type == LYXP_SET_SCNODE_SET) {
@@ -1318,19 +1312,8 @@
set_insert_node_hash(set, (struct lyd_node *)node, node_type);
}
-/**
- * @brief Insert schema node into set.
- *
- * @param[in] set Set to insert into.
- * @param[in] node Node to insert.
- * @param[in] node_type Node type of @p node.
- * @param[in] axis Axis that @p node was reached on.
- * @param[out] index_p Optional pointer to store index if the inserted @p node.
- * @return LY_SUCCESS on success.
- * @return LY_EMEM on memory allocation failure.
- */
-static LY_ERR
-set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type,
+LY_ERR
+lyxp_set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type,
enum lyxp_axis axis, uint32_t *index_p)
{
uint32_t index;
@@ -1350,7 +1333,7 @@
}
if (lyxp_set_scnode_contains(set, node, node_type, -1, &index)) {
- /* BUG if axes differs, this new one is thrown away */
+ /* BUG if axes differ, this new one is thrown away */
set->val.scnodes[index].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
} else {
if (set->used == set->size) {
@@ -3938,10 +3921,10 @@
set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE);
if (set->cur_scnode) {
- LY_CHECK_RET(set_scnode_insert_node(set, set->cur_scnode, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL));
+ LY_CHECK_RET(lyxp_set_scnode_insert_node(set, set->cur_scnode, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL));
} else {
/* root node */
- LY_CHECK_RET(set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL));
+ LY_CHECK_RET(lyxp_set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL));
}
} else {
lyxp_set_free_content(set);
@@ -4007,7 +3990,7 @@
target = p[LY_ARRAY_COUNT(p) - 1].node;
ly_path_free(set->ctx, p);
- LY_CHECK_RET(set_scnode_insert_node(set, target, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL));
+ LY_CHECK_RET(lyxp_set_scnode_insert_node(set, target, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL));
} /* else the target was found before but is disabled so it was removed */
}
@@ -5661,7 +5644,7 @@
if (options & LYXP_SCNODE_ALL) {
set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE);
- LY_CHECK_RET(set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL));
+ LY_CHECK_RET(lyxp_set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL));
} else {
set->type = LYXP_SET_NODE_SET;
set->used = 0;
@@ -6675,7 +6658,7 @@
{
ly_bool temp_ctx = 0;
uint32_t getnext_opts, orig_used, i, mod_idx, idx;
- const struct lys_module *mod;
+ const struct lys_module *mod = NULL;
const struct lysc_node *iter;
enum lyxp_node_type iter_type;
@@ -6714,7 +6697,7 @@
}
/* insert */
- LY_CHECK_RET(set_scnode_insert_node(set, iter, iter_type, axis, &idx));
+ LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, iter_type, axis, &idx));
/* we need to prevent these nodes from being considered in this moveto */
if ((idx < orig_used) && (idx > i)) {
@@ -6727,7 +6710,7 @@
(set->val.scnodes[i].type == LYXP_NODE_ELEM) && !ly_nested_ext_schema(NULL, set->val.scnodes[i].scnode,
moveto_mod->name, strlen(moveto_mod->name), LY_VALUE_JSON, NULL, ncname, strlen(ncname), &iter, NULL)) {
/* there is a matching node from an extension, use it */
- LY_CHECK_RET(set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, axis, &idx));
+ LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, axis, &idx));
if ((idx < orig_used) && (idx > i)) {
set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_NEW_CTX;
temp_ctx = 1;
@@ -6871,7 +6854,7 @@
goto skip_children;
}
} else {
- LY_CHECK_RET(set_scnode_insert_node(set, elem, LYXP_NODE_ELEM, LYXP_AXIS_DESCENDANT, NULL));
+ LY_CHECK_RET(lyxp_set_scnode_insert_node(set, elem, LYXP_NODE_ELEM, LYXP_AXIS_DESCENDANT, NULL));
}
} else if (rc == LY_EINVAL) {
goto skip_children;
@@ -9876,7 +9859,7 @@
memset(set, 0, sizeof *set);
set->type = LYXP_SET_SCNODE_SET;
set->root_type = lyxp_get_root_type(NULL, ctx_scnode, options);
- LY_CHECK_RET(set_scnode_insert_node(set, ctx_scnode, ctx_scnode ? LYXP_NODE_ELEM : set->root_type, LYXP_AXIS_SELF, NULL));
+ LY_CHECK_RET(lyxp_set_scnode_insert_node(set, ctx_scnode, ctx_scnode ? LYXP_NODE_ELEM : set->root_type, LYXP_AXIS_SELF, NULL));
set->val.scnodes[0].in_ctx = LYXP_SET_SCNODE_START;
set->ctx = (struct ly_ctx *)ctx;
diff --git a/src/xpath.h b/src/xpath.h
index cfa1fe6..96f7419 100644
--- a/src/xpath.h
+++ b/src/xpath.h
@@ -422,6 +422,20 @@
void lyxp_set_scnode_merge(struct lyxp_set *set1, struct lyxp_set *set2);
/**
+ * @brief Insert schema node into set.
+ *
+ * @param[in] set Set to insert into.
+ * @param[in] node Node to insert.
+ * @param[in] node_type Node type of @p node.
+ * @param[in] axis Axis that @p node was reached on.
+ * @param[out] index_p Optional pointer to store the index of the inserted @p node.
+ * @return LY_SUCCESS on success.
+ * @return LY_EMEM on memory allocation failure.
+ */
+LY_ERR lyxp_set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type,
+ enum lyxp_axis axis, uint32_t *index_p);
+
+/**
* @brief Parse an XPath expression into a structure of tokens.
* Logs directly.
*
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 6f36f31..259ef34 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -45,6 +45,8 @@
add_subdirectory(style)
add_subdirectory(fuzz)
endif()
+ add_subdirectory(yanglint)
+ add_subdirectory(yangre)
endif()
if(ENABLE_PERF_TESTS)
add_subdirectory(perf)
diff --git a/tests/tool_i.tcl b/tests/tool_i.tcl
new file mode 100644
index 0000000..d0f3d4b
--- /dev/null
+++ b/tests/tool_i.tcl
@@ -0,0 +1,156 @@
+# @brief Common functions and variables for Tool Under Test (TUT).
+#
+# The script requires variables:
+# TUT_PATH - Assumed absolute path to the directory in which the TUT is located.
+# TUT_NAME - TUT name (without path).
+#
+# The script sets the variables:
+# TUT - The path (including the name) of the executable TUT.
+# error_prompt - Delimiter on error.
+# error_head - Header on error.
+
+package require Expect
+
+# Complete the path for Tool Under Test (TUT). For example, on Windows, TUT can be located in the Debug or Release
+# subdirectory. Note that Release build takes precedence over Debug.
+set conftypes {{} Release Debug}
+foreach i $conftypes {
+ if { [file executable "$TUT_PATH/$i/$TUT_NAME"] || [file executable "$TUT_PATH/$i/$TUT_NAME.exe"] } {
+ set TUT "$TUT_PATH/$i/$TUT_NAME"
+ break
+ }
+}
+if {![info exists TUT]} {
+ error "$TUT_NAME executable not found"
+}
+
+# prompt of error message
+set error_prompt ">>>"
+# the beginning of error message
+set error_head "$error_prompt Check-failed"
+
+# detection on eof and timeout will be on every expect command
+expect_after {
+ eof {
+ global error_head
+ error "$error_head unexpected termination"
+ } timeout {
+ global error_head
+ error "$error_head timeout"
+ }
+}
+
+# Run commands from command line
+tcltest::loadTestedCommands
+
+# namespace of internal functions
+namespace eval ly::private {}
+
+# Send command 'cmd' to the process, then check output string by 'pattern'.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex or an exact string to match. If is not specified, only prompt assumed afterwards.
+# It must not contain a prompt. There can be an '$' character at the end of the pattern, in which case the regex
+# matches the characters before the prompt.
+# Parameter 'opt' can contain:
+# -ex has a similar meaning to the expect command. The 'pattern' parameter is used as a simple string
+# for exact matching of the output. So 'pattern' is not a regular expression but some characters
+# must still be escaped, eg ][.
+proc ly_cmd {cmd {pattern ""} {opt ""}} {
+ global prompt
+
+ send -- "${cmd}\r"
+ expect -- "${cmd}\r\n"
+
+ if { $pattern eq "" } {
+ # command without output
+ expect ^$prompt
+ return
+ }
+
+ # definition of an expression that matches failure
+ set failure_pattern "\r\n${prompt}$"
+
+ if { $opt eq "" && [string index $pattern end] eq "$"} {
+ # check output by regular expression
+ # It was explicitly specified how the expression should end.
+ set pattern [string replace $pattern end end]
+ expect {
+ -re "${pattern}\r\n${prompt}$" {}
+ -re $failure_pattern {
+ error "unexpected output:\n$expect_out(buffer)"
+ }
+ }
+ } elseif { $opt eq "" } {
+ # check output by regular expression
+ expect {
+ -re "${pattern}.*\r\n${prompt}$" {}
+ -re $failure_pattern {
+ error "unexpected output:\n$expect_out(buffer)"
+ }
+ }
+ } elseif { $opt eq "-ex" } {
+ # check output by exact matching
+ expect {
+ -ex "${pattern}\r\n${prompt}" {}
+ -re $failure_pattern {
+ error "unexpected output:\n$expect_out(buffer)"
+ }
+ }
+ } else {
+ global error_head
+ error "$error_head unrecognized value of parameter 'opt'"
+ }
+}
+
+# Send command 'cmd' to the process, expect some header and then check output string by 'pattern'.
+# This function is useful for checking an error that appears in the form of a header.
+# Parameter header is the expected header on the output.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex. It must not contain a prompt.
+proc ly_cmd_header {cmd header pattern} {
+ global prompt
+
+ send -- "${cmd}\r"
+ expect -- "${cmd}\r\n"
+
+ expect {
+ -re "$header .*${pattern}.*\r\n${prompt}$" {}
+ -re "\r\n${prompt}$" {
+ error "unexpected output:\n$expect_out(buffer)"
+ }
+ }
+}
+
+# Whatever is written is sent, output is ignored and then another prompt is expected.
+# Parameter cmd is optional and any output is ignored.
+proc ly_ignore {{cmd ""}} {
+ global prompt
+
+ send "${cmd}\r"
+ expect -re "$prompt$"
+}
+
+# Send a completion request and check if the anchored regex output matches.
+proc ly_completion {input output} {
+ global prompt
+
+ send -- "${input}\t"
+ # expecting echoing input, output and 10 terminal control characters
+ expect -re "^${input}\r${prompt}${output}.*\r.*$"
+}
+
+# Send a completion request and check if the anchored regex hint options match.
+proc ly_hint {input prev_input hints} {
+ global prompt
+
+ set output {}
+ foreach i $hints {
+ # each element might have some number of spaces and CRLF around it
+ append output "${i} *(?:\\r\\n)?"
+ }
+
+ send -- "${input}\t"
+ # expecting the hints, previous input from which the hints were generated
+ # and some number of terminal control characters
+ expect -re "${output}\r${prompt}${prev_input}.*\r.*$"
+}
diff --git a/tests/tool_ni.tcl b/tests/tool_ni.tcl
new file mode 100644
index 0000000..7282d35
--- /dev/null
+++ b/tests/tool_ni.tcl
@@ -0,0 +1,141 @@
+# @brief Common functions and variables for Tool Under Test (TUT).
+#
+# The script requires variables:
+# TUT_PATH - Assumed absolute path to the directory in which the TUT is located.
+# TUT_NAME - TUT name (without path).
+#
+# The script sets the variables:
+# TUT - The path (including the name) of the executable TUT.
+# error_prompt - Delimiter on error.
+# error_head - Header on error.
+
+# Complete the path for Tool Under Test (TUT). For example, on Windows, TUT can be located in the Debug or Release
+# subdirectory. Note that Release build takes precedence over Debug.
+set conftypes {{} Release Debug}
+foreach i $conftypes {
+ if { [file executable "$TUT_PATH/$i/$TUT_NAME"] || [file executable "$TUT_PATH/$i/$TUT_NAME.exe"] } {
+ set TUT "$TUT_PATH/$i/$TUT_NAME"
+ break
+ }
+}
+if {![info exists TUT]} {
+ error "$TUT_NAME executable not found"
+}
+
+# prompt of error message
+set error_prompt ">>>"
+# the beginning of error message
+set error_head "$error_prompt Check-failed"
+
+# Run commands from command line
+tcltest::loadTestedCommands
+
+# namespace of internal functions
+namespace eval ly::private {
+ namespace export *
+}
+
+# Run the process with arguments.
+# Parameter cmd is a string with arguments.
+# Parameter wrn is a flag. Set to 1 if stderr should be ignored.
+# Returns a pair where the first is the return code and the second is the output.
+proc ly::private::ly_exec {cmd {wrn ""}} {
+ global TUT
+ try {
+ set results [exec -- $TUT {*}$cmd]
+ set status 0
+ } trap CHILDSTATUS {results options} {
+ # return code is not 0
+ set status [lindex [dict get $options -errorcode] 2]
+ } trap NONE results {
+ if { $wrn == 1 } {
+ set status 0
+ } else {
+ error "return code is 0 but something was written to stderr:\n$results\n"
+ }
+ } trap CHILDKILLED {results options} {
+ set status [lindex [dict get $options -errorcode] 2]
+ error "process was killed: $status"
+ }
+ list $status $results
+}
+
+# Internal function.
+# Check the output with pattern.
+# Parameter pattern is a regex or an exact string to match.
+# Parameter msg is the output to check.
+# Parameter 'opt' is optional. If contains '-ex', then the 'pattern' parameter is
+# used as a simple string for exact matching of the output.
+proc ly::private::output_check {pattern msg {opt ""}} {
+ if { $opt eq "" } {
+ expr {![regexp -- $pattern $msg]}
+ } elseif { $opt eq "-ex" } {
+ expr {![string equal "$pattern" $msg]}
+ } else {
+ global error_head
+ error "$error_head unrecognized value of parameter 'opt'"
+ }
+}
+
+# Execute yanglint with arguments and expect success.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex or an exact string to match.
+# Parameter 'opt' is optional. If contains '-ex', then the 'pattern' parameter is
+# used as a simple string for exact matching of the output.
+proc ly_cmd {cmd {pattern ""} {opt ""}} {
+ namespace import ly::private::*
+ lassign [ly_exec $cmd] rc msg
+ if { $rc != 0 } {
+ error "unexpected return code $rc:\n$msg\n"
+ }
+ if { $pattern ne "" && [output_check $pattern $msg $opt] } {
+ error "unexpected output:\n$msg\n"
+ }
+ return
+}
+
+# Execute yanglint with arguments and expect error.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex.
+proc ly_cmd_err {cmd pattern} {
+ namespace import ly::private::*
+ lassign [ly_exec $cmd] rc msg
+ if { $rc == 0 } {
+ error "unexpected return code $rc"
+ }
+ if { [output_check $pattern $msg] } {
+ error "unexpected output:\n$msg\n"
+ }
+ return
+}
+
+# Execute yanglint with arguments, expect warning in stderr but success.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex.
+proc ly_cmd_wrn {cmd pattern} {
+ namespace import ly::private::*
+ lassign [ly_exec $cmd 1] rc msg
+ if { $rc != 0 } {
+ error "unexpected return code $rc:\n$msg\n"
+ }
+ if { [output_check $pattern $msg] } {
+ error "unexpected output:\n$msg\n"
+ }
+ return
+}
+
+# Check if yanglint supports the specified option.
+# Parameter opt is a option to be found.
+# Return true if option is found otherwise false.
+proc ly_opt_exists {opt} {
+ namespace import ly::private::*
+ lassign [ly_exec "--help"] rc msg
+ if { $rc != 0 } {
+ error "unexpected return code $rc:\n$msg\n"
+ }
+ if { [output_check $opt $msg] } {
+ return false
+ } else {
+ return true
+ }
+}
diff --git a/tests/utests/data/test_diff.c b/tests/utests/data/test_diff.c
index a84c3f6..4400b5d 100644
--- a/tests/utests/data/test_diff.c
+++ b/tests/utests/data/test_diff.c
@@ -4,7 +4,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief tests for lyd_diff()
*
- * Copyright (c) 2020 CESNET, z.s.p.o.
+ * Copyright (c) 2020 - 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -17,15 +17,15 @@
#include "libyang.h"
-#define CHECK_PARSE_LYD(INPUT, MODEL) \
- CHECK_PARSE_LYD_PARAM(INPUT, LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, MODEL)
+#define CHECK_PARSE_LYD(INPUT, OUTPUT) \
+ CHECK_PARSE_LYD_PARAM(INPUT, LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, OUTPUT)
-#define CHECK_LYD_STRING(IN_MODEL, TEXT) \
- CHECK_LYD_STRING_PARAM(IN_MODEL, TEXT, LYD_XML, LYD_PRINT_WITHSIBLINGS)
+#define CHECK_LYD_STRING(INPUT, TEXT) \
+ CHECK_LYD_STRING_PARAM(INPUT, TEXT, LYD_XML, LYD_PRINT_WITHSIBLINGS)
-#define CHECK_PARSE_LYD_DIFF(INPUT_1, INPUT_2, OUT_MODEL) \
- assert_int_equal(LY_SUCCESS, lyd_diff_siblings(INPUT_1, INPUT_2, 0, &OUT_MODEL));\
- assert_non_null(OUT_MODEL)
+#define CHECK_PARSE_LYD_DIFF(INPUT_1, INPUT_2, OUT_DIFF) \
+ assert_int_equal(LY_SUCCESS, lyd_diff_siblings(INPUT_1, INPUT_2, 0, &OUT_DIFF));\
+ assert_non_null(OUT_DIFF)
#define TEST_DIFF_3(XML1, XML2, XML3, DIFF1, DIFF2, MERGE) \
{ \
@@ -117,6 +117,12 @@
" leaf l2 {\n"
" type int32;\n"
" }\n"
+ ""
+ " container cont {\n"
+ " leaf l3 {\n"
+ " type string;\n"
+ " }\n"
+ " }\n"
" }\n"
""
" leaf-list dllist {\n"
@@ -639,6 +645,126 @@
}
static void
+test_nested_list(void **state)
+{
+ struct lyd_node *data1, *data2, *diff;
+ const char *xml1, *xml2;
+
+ (void) state;
+
+ xml1 =
+ "<df xmlns=\"urn:libyang:tests:defaults\">"
+ " <list>"
+ " <name>n1</name>"
+ " <value>25</value>"
+ " <list2>"
+ " <name2>n22</name2>"
+ " <value2>26</value2>"
+ " </list2>"
+ " </list>"
+ " <list>"
+ " <name>n2</name>"
+ " <value>25</value>"
+ " <list2>"
+ " <name2>n22</name2>"
+ " <value2>26</value2>"
+ " </list2>"
+ " </list>"
+ " <list>"
+ " <name>n3</name>"
+ " <value>25</value>"
+ " <list2>"
+ " <name2>n22</name2>"
+ " <value2>26</value2>"
+ " </list2>"
+ " </list>"
+ " <list>"
+ " <name>n4</name>"
+ " <value>25</value>"
+ " <list2>"
+ " <name2>n22</name2>"
+ " <value2>26</value2>"
+ " </list2>"
+ " </list>"
+ " <list>"
+ " <name>n0</name>"
+ " <value>26</value>"
+ " <list2>"
+ " <name2>n22</name2>"
+ " <value2>26</value2>"
+ " </list2>"
+ " <list2>"
+ " <name2>n23</name2>"
+ " <value2>26</value2>"
+ " </list2>"
+ " </list>"
+ "</df>";
+ xml2 =
+ "<df xmlns=\"urn:libyang:tests:defaults\">"
+ " <list>"
+ " <name>n0</name>"
+ " <value>30</value>"
+ " <list2>"
+ " <name2>n23</name2>"
+ " <value2>26</value2>"
+ " </list2>"
+ " </list>"
+ "</df>";
+
+ CHECK_PARSE_LYD(xml1, data1);
+ CHECK_PARSE_LYD(xml2, data2);
+ CHECK_PARSE_LYD_DIFF(data1, data2, diff);
+
+ CHECK_LYD_STRING(diff,
+ "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+ " <list yang:operation=\"delete\">\n"
+ " <name>n1</name>\n"
+ " <value>25</value>\n"
+ " <list2>\n"
+ " <name2>n22</name2>\n"
+ " <value2>26</value2>\n"
+ " </list2>\n"
+ " </list>\n"
+ " <list yang:operation=\"delete\">\n"
+ " <name>n2</name>\n"
+ " <value>25</value>\n"
+ " <list2>\n"
+ " <name2>n22</name2>\n"
+ " <value2>26</value2>\n"
+ " </list2>\n"
+ " </list>\n"
+ " <list yang:operation=\"delete\">\n"
+ " <name>n3</name>\n"
+ " <value>25</value>\n"
+ " <list2>\n"
+ " <name2>n22</name2>\n"
+ " <value2>26</value2>\n"
+ " </list2>\n"
+ " </list>\n"
+ " <list yang:operation=\"delete\">\n"
+ " <name>n4</name>\n"
+ " <value>25</value>\n"
+ " <list2>\n"
+ " <name2>n22</name2>\n"
+ " <value2>26</value2>\n"
+ " </list2>\n"
+ " </list>\n"
+ " <list yang:operation=\"none\">\n"
+ " <name>n0</name>\n"
+ " <value yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"26\">30</value>\n"
+ " <list2 yang:operation=\"delete\">\n"
+ " <name2>n22</name2>\n"
+ " <value2>26</value2>\n"
+ " </list2>\n"
+ " </list>\n"
+ "</df>\n");
+
+ lyd_free_all(data1);
+ lyd_free_all(data2);
+ lyd_free_all(diff);
+}
+
+static void
test_userord_llist(void **state)
{
(void) state;
@@ -942,6 +1068,118 @@
}
static void
+test_userord_list3(void **state)
+{
+ (void) state;
+ const char *xml1 =
+ "<df xmlns=\"urn:libyang:tests:defaults\">\n"
+ " <ul>\n"
+ " <l1>a</l1>\n"
+ " <l2>1</l2>\n"
+ " </ul>\n"
+ " <ul>\n"
+ " <l1>b</l1>\n"
+ " <l2>2</l2>\n"
+ " </ul>\n"
+ " <ul>\n"
+ " <l1>c</l1>\n"
+ " <cont>\n"
+ " <l3>val1</l3>\n"
+ " </cont>\n"
+ " </ul>\n"
+ " <ul>\n"
+ " <l1>d</l1>\n"
+ " <l2>4</l2>\n"
+ " </ul>\n"
+ "</df>\n";
+ const char *xml2 =
+ "<df xmlns=\"urn:libyang:tests:defaults\">\n"
+ " <ul>\n"
+ " <l1>c</l1>\n"
+ " <l2>3</l2>\n"
+ " <cont>\n"
+ " <l3>val2</l3>\n"
+ " </cont>\n"
+ " </ul>\n"
+ " <ul>\n"
+ " <l1>a</l1>\n"
+ " <l2>1</l2>\n"
+ " </ul>\n"
+ " <ul>\n"
+ " <l1>d</l1>\n"
+ " <l2>44</l2>\n"
+ " </ul>\n"
+ " <ul>\n"
+ " <l1>b</l1>\n"
+ " <l2>2</l2>\n"
+ " </ul>\n"
+ "</df>\n";
+ const char *xml3 =
+ "<df xmlns=\"urn:libyang:tests:defaults\">\n"
+ " <ul>\n"
+ " <l1>a</l1>\n"
+ " </ul>\n"
+ " <ul>\n"
+ " <l1>c</l1>\n"
+ " <l2>3</l2>\n"
+ " <cont>\n"
+ " <l3>val2</l3>\n"
+ " </cont>\n"
+ " </ul>\n"
+ " <ul>\n"
+ " <l1>d</l1>\n"
+ " <l2>44</l2>\n"
+ " </ul>\n"
+ " <ul>\n"
+ " <l1>b</l1>\n"
+ " <l2>2</l2>\n"
+ " </ul>\n"
+ "</df>\n";
+
+ const char *out_diff_1 =
+ "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+ " <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='b']\">\n"
+ " <l1>c</l1>\n"
+ " <l2 yang:operation=\"create\">3</l2>\n"
+ " <cont yang:operation=\"none\">\n"
+ " <l3 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"val1\">val2</l3>\n"
+ " </cont>\n"
+ " </ul>\n"
+ " <ul yang:operation=\"replace\" yang:key=\"[l1='a']\" yang:orig-key=\"[l1='b']\">\n"
+ " <l1>d</l1>\n"
+ " <l2 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"4\">44</l2>\n"
+ " </ul>\n"
+ "</df>\n";
+ const char *out_diff_2 =
+ "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+ " <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='c']\">\n"
+ " <l1>a</l1>\n"
+ " <l2 yang:operation=\"delete\">1</l2>\n"
+ " </ul>\n"
+ "</df>\n";
+ const char *out_merge =
+ "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+ " <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='b']\">\n"
+ " <l1>c</l1>\n"
+ " <l2 yang:operation=\"create\">3</l2>\n"
+ " <cont yang:operation=\"none\">\n"
+ " <l3 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"val1\">val2</l3>\n"
+ " </cont>\n"
+ " </ul>\n"
+ " <ul yang:operation=\"replace\" yang:key=\"[l1='a']\" yang:orig-key=\"[l1='b']\">\n"
+ " <l1>d</l1>\n"
+ " <l2 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"4\">44</l2>\n"
+ " </ul>\n"
+ " <ul yang:key=\"\" yang:orig-key=\"[l1='c']\" yang:operation=\"replace\">\n"
+ " <l1>a</l1>\n"
+ " <l2 yang:operation=\"delete\">1</l2>\n"
+ " </ul>\n"
+ "</df>\n";
+
+ TEST_DIFF_3(xml1, xml2, xml3, out_diff_1, out_diff_2, out_merge);
+}
+
+static void
test_keyless_list(void **state)
{
(void) state;
@@ -1209,11 +1447,13 @@
UTEST(test_delete_merge, setup),
UTEST(test_leaf, setup),
UTEST(test_list, setup),
+ UTEST(test_nested_list, setup),
UTEST(test_userord_llist, setup),
UTEST(test_userord_llist2, setup),
UTEST(test_userord_mix, setup),
UTEST(test_userord_list, setup),
UTEST(test_userord_list2, setup),
+ UTEST(test_userord_list3, setup),
UTEST(test_keyless_list, setup),
UTEST(test_state_llist, setup),
UTEST(test_wd, setup),
diff --git a/tests/utests/data/test_parser_json.c b/tests/utests/data/test_parser_json.c
index d2c40e6..e5513da 100644
--- a/tests/utests/data/test_parser_json.c
+++ b/tests/utests/data/test_parser_json.c
@@ -45,6 +45,7 @@
"leaf-list ll1 { type uint8; }"
"leaf foo2 { type string; default \"default-val\"; }"
"leaf foo3 { type uint32; }"
+ "leaf foo4 { type uint64; }"
"notification n2;}";
UTEST_SETUP;
@@ -520,6 +521,25 @@
CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data);
lyd_free_all(tree);
+ /* special chars */
+ data = "{\"a:foo3\":\"ab\\\"\\\\\\r\\t\"}";
+ CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree);
+ CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data);
+ lyd_free_all(tree);
+
+ /* wrong encoding */
+ data = "{\"a:foo3\":\"25\"}";
+ CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree);
+ CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_JSON, "foo3", 0, 0, NULL, 0, "25");
+ CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data);
+ lyd_free_all(tree);
+
+ data = "{\"a:foo4\":25}";
+ CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree);
+ CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_JSON, "foo4", 0, 0, NULL, 0, "25");
+ CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data);
+ lyd_free_all(tree);
+
/* missing key, no flags */
data = "{\"a:l1\":[{\"a\":\"val_a\",\"b\":\"val_b\",\"d\":\"val_d\"}]}";
PARSER_CHECK_ERROR(data, 0, LYD_VALIDATE_PRESENT, tree, LY_EVALID,
diff --git a/tests/utests/data/test_parser_xml.c b/tests/utests/data/test_parser_xml.c
index 04397ae..01cf7f4 100644
--- a/tests/utests/data/test_parser_xml.c
+++ b/tests/utests/data/test_parser_xml.c
@@ -631,8 +631,33 @@
lyd_free_all(tree);
lyd_free_all(op);
- /* wrong namespace, element name, whatever... */
- /* TODO */
+ /* invalid anyxml nested metadata value */
+ data = "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"1\" pid=\"4114692032\">\n"
+ " <copy-config>\n"
+ " <target>\n"
+ " <running/>\n"
+ " </target>\n"
+ " <source>\n"
+ " <config>\n"
+ " <l1 xmlns=\"urn:tests:a\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ " <a>val_a</a>\n"
+ " <b>val_b</b>\n"
+ " <c>5</c>\n"
+ " <cont nc:operation=\"merge\">\n"
+ " <e nc:operation=\"merge2\">false</e>\n"
+ " </cont>\n"
+ " </l1>\n"
+ " </config>\n"
+ " </source>\n"
+ " </copy-config>\n"
+ "</rpc>\n";
+ assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+ assert_int_equal(LY_EVALID, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_RPC_NETCONF, &tree, &op));
+ ly_in_free(in, 0);
+ CHECK_LOG_CTX("Invalid enumeration value \"merge2\".",
+ "Data location \"/ietf-netconf:copy-config/source/config/a:l1[a='val_a'][b='val_b'][c='5']/cont/e\", line number 13.");
+ lyd_free_all(tree);
+ assert_null(op);
}
static void
diff --git a/tests/utests/data/test_tree_data.c b/tests/utests/data/test_tree_data.c
index 7b17e50..462913b 100644
--- a/tests/utests/data/test_tree_data.c
+++ b/tests/utests/data/test_tree_data.c
@@ -1,9 +1,9 @@
/**
* @file test_tree_data.c
- * @author: Radek Krejci <rkrejci@cesnet.cz>
- * @brief unit tests for functions from tress_data.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief unit tests for functions from tree_data.c
*
- * Copyright (c) 2018-2019 CESNET, z.s.p.o.
+ * Copyright (c) 2018-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -100,7 +100,7 @@
data2 = "<l2 xmlns=\"urn:tests:a\"><c><x>b</x></c></l2>";
CHECK_PARSE_LYD(data1, 0, LYD_VALIDATE_PRESENT, tree1);
CHECK_PARSE_LYD(data2, 0, LYD_VALIDATE_PRESENT, tree2);
- assert_int_equal(LY_ENOT, lyd_compare_single(tree1->next, tree2->next, 0));
+ assert_int_equal(LY_ENOT, lyd_compare_single(tree1->next, tree2->next, LYD_COMPARE_FULL_RECURSION));
assert_int_equal(LY_SUCCESS, lyd_compare_single(tree1->next->next, tree2->next, 0));
lyd_free_all(tree1);
lyd_free_all(tree2);
@@ -371,7 +371,7 @@
CHECK_PARSE_LYD(data, 0, LYD_VALIDATE_PRESENT, tree1);
assert_int_equal(LY_EINVAL, lyd_dup_single(((struct lyd_node_inner *)tree1)->child->prev,
(struct lyd_node_inner *)tree1->next, LYD_DUP_WITH_PARENTS, NULL));
- CHECK_LOG_CTX("Invalid argument parent (lyd_dup_get_local_parent()) - does not interconnect with the created node's parents chain.",
+ CHECK_LOG_CTX("None of the duplicated node \"c\" schema parents match the provided parent \"c\".",
NULL);
lyd_free_all(tree1);
}
diff --git a/tests/utests/schema/test_printer_tree.c b/tests/utests/schema/test_printer_tree.c
index c076ece..40fb15f 100644
--- a/tests/utests/schema/test_printer_tree.c
+++ b/tests/utests/schema/test_printer_tree.c
@@ -1574,6 +1574,7 @@
" yang-version 1.1;\n"
" namespace \"x:y\";\n"
" prefix x;\n"
+ "\n"
" container g {\n"
" leaf a {\n"
" type string;\n"
@@ -1586,6 +1587,12 @@
" leaf c {\n"
" type string;\n"
" }\n"
+ " list l {\n"
+ " key \"ip\";\n"
+ " leaf ip {\n"
+ " type string;\n"
+ " }\n"
+ " }\n"
" }\n"
" }\n"
"}\n";
@@ -1610,13 +1617,31 @@
ly_out_reset(UTEST_OUT);
+ /* pyang -f tree --tree-path /g/h/l */
+ expect =
+ "module: a26\n"
+ " +--rw g\n"
+ " +--rw h\n"
+ " +--rw l* [ip]\n"
+ " +--rw ip string\n";
+
+ node = lys_find_path(UTEST_LYCTX, NULL, "/a26:g/h/l", 0);
+ CHECK_POINTER(node, 1);
+ assert_int_equal(LY_SUCCESS, lys_print_node(UTEST_OUT, node, LYS_OUT_TREE, 72, 0));
+ assert_int_equal(strlen(expect), ly_out_printed(UTEST_OUT));
+ assert_string_equal(printed, expect);
+
+ ly_out_reset(UTEST_OUT);
+
/* pyang -f tree --tree-path /g/h */
expect =
"module: a26\n"
" +--rw g\n"
" +--rw h\n"
" +--rw b string\n"
- " +--rw c? string\n";
+ " +--rw c? string\n"
+ " +--rw l* [ip]\n"
+ " +--rw ip string\n";
node = lys_find_path(UTEST_LYCTX, NULL, "/a26:g/h", 0);
CHECK_POINTER(node, 1);
@@ -1643,6 +1668,59 @@
TEST_LOCAL_TEARDOWN;
}
+static void
+print_compiled_node_augment(void **state)
+{
+ TEST_LOCAL_SETUP;
+ const struct lysc_node *node;
+
+ orig =
+ "module b26xx {\n"
+ " yang-version 1.1;\n"
+ " namespace \"xx:y\";\n"
+ " prefix xx;\n"
+ " container c;\n"
+ "}\n";
+
+ UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod);
+
+ /* module with import statement */
+ orig =
+ "module b26 {\n"
+ " yang-version 1.1;\n"
+ " namespace \"x:y\";\n"
+ " prefix x;\n"
+ "\n"
+ " import b26xx {\n"
+ " prefix xx;\n"
+ " }\n"
+ "\n"
+ " augment \"/xx:c\" {\n"
+ " container e;\n"
+ " }\n"
+ "}\n";
+
+ UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod);
+
+ /* pyang -f tree --tree-path /c/e ... but prefixes modified */
+ expect =
+ "module: b26\n"
+ " +--rw xx:c\n"
+ " +--rw e\n";
+
+ /* using lysc tree */
+ ly_ctx_set_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED);
+ node = lys_find_path(UTEST_LYCTX, NULL, "/b26xx:c/b26:e", 0);
+ CHECK_POINTER(node, 1);
+ assert_int_equal(LY_SUCCESS, lys_print_node(UTEST_OUT, node, LYS_OUT_TREE, 72, 0));
+ assert_int_equal(strlen(expect), ly_out_printed(UTEST_OUT));
+ assert_string_equal(printed, expect);
+ ly_out_reset(UTEST_OUT);
+ ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED);
+
+ TEST_LOCAL_TEARDOWN;
+}
+
static LY_ERR
local_imp_clb(const char *UNUSED(mod_name), const char *UNUSED(mod_rev), const char *UNUSED(submod_name),
const char *UNUSED(sub_rev), void *user_data, LYS_INFORMAT *format,
@@ -2365,6 +2443,43 @@
TEST_LOCAL_TEARDOWN;
}
+static void
+annotation(void **state)
+{
+ TEST_LOCAL_SETUP;
+
+ orig =
+ "module ann {\n"
+ " yang-version 1.1;\n"
+ " namespace \"urn:example:ann\";\n"
+ " prefix an;\n"
+ "\n"
+ " import ietf-yang-metadata {\n"
+ " prefix md;\n"
+ " }\n"
+ "\n"
+ " leaf lf1 {\n"
+ " type string;\n"
+ " }\n"
+ " md:annotation avalue {\n"
+ " type string;\n"
+ " }\n"
+ "}\n";
+
+ expect =
+ "module: ann\n"
+ " +--rw lf1? string\n";
+
+ /* annotation is ignored without error message */
+ UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod);
+ TEST_LOCAL_PRINT(mod, 72);
+ assert_int_equal(strlen(expect), ly_out_printed(UTEST_OUT));
+ assert_string_equal(printed, expect);
+ ly_out_reset(UTEST_OUT);
+
+ TEST_LOCAL_TEARDOWN;
+}
+
int
main(void)
{
@@ -2395,10 +2510,12 @@
UTEST(transition_between_rpc_and_notif),
UTEST(local_augment),
UTEST(print_compiled_node),
+ UTEST(print_compiled_node_augment),
UTEST(print_parsed_submodule),
UTEST(yang_data),
UTEST(mount_point),
UTEST(structure),
+ UTEST(annotation),
};
return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/utests/schema/test_tree_schema_compile.c b/tests/utests/schema/test_tree_schema_compile.c
index 09c63a3..077faf0 100644
--- a/tests/utests/schema/test_tree_schema_compile.c
+++ b/tests/utests/schema/test_tree_schema_compile.c
@@ -454,7 +454,7 @@
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm {namespace urn:mm;prefix mm;"
"list l {key x;leaf x {type empty;}}}", LYS_IN_YANG, NULL));
- CHECK_LOG_CTX("List's key cannot be of \"empty\" type until it is in YANG 1.1 module.", "Path \"/mm:l/x\".");
+ CHECK_LOG_CTX("List key of the \"empty\" type is allowed only in YANG 1.1 modules.", "Path \"/mm:l/x\".");
}
static void
@@ -3901,6 +3901,55 @@
" }"
"}",
LYS_IN_YANG, NULL));
+
+ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb,
+ "module d1 {"
+ " namespace urn:d1;"
+ " prefix d1;"
+ " container ifm {"
+ " container interfaces {"
+ " list interface {"
+ " key \"name\";"
+ " leaf name {"
+ " type string;"
+ " }"
+ " container ethernet {"
+ " container main-interface {"
+ " container l2-attribute {"
+ " when \"not(/d1:ifm/d1:interfaces/d1:interface/d1:trunk/d1:members/d1:member[d1:name=current()/../../../d1:name])\";"
+ " presence \"\";"
+ " }"
+ " }"
+ " }"
+ " container trunk {"
+ " container members {"
+ " list member {"
+ " key \"name\";"
+ " leaf name {"
+ " type string;"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ "}");
+ assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX,
+ "module d2 {"
+ " namespace \"urn:d2\";"
+ " prefix d2;"
+ " import d1 {"
+ " prefix d1;"
+ " }"
+ " augment \"/d1:ifm/d1:interfaces/d1:interface/d1:ethernet/d1:main-interface\" {"
+ " when \"not(d1:l2-attribute)\";"
+ " container extra-attribute {"
+ " presence \"\";"
+ " }"
+ " }"
+ "}",
+ LYS_IN_YANG, NULL));
}
static void
@@ -3942,6 +3991,68 @@
LYS_IN_YANG, NULL));
/* no warnings */
CHECK_LOG_CTX(NULL, NULL);
+
+ /* must referencing disabled leafref in another module */
+ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb,
+ "module b-imp {"
+ " yang-version 1.1;"
+ " namespace \"urn:b-imp\";"
+ " prefix \"bi\";"
+ ""
+ " feature feat;"
+ ""
+ " grouping band-capabilities {"
+ " leaf band-number {"
+ " type uint16;"
+ " }"
+ ""
+ " container sub-band-info {"
+ " when \"../band-number = '46'\";"
+ " if-feature \"bi:feat\";"
+ " leaf number-of-laa-scarriers {"
+ " type uint8;"
+ " }"
+ " }"
+ " }"
+ ""
+ " container module-capability {"
+ " list band-capabilities {"
+ " key band-number;"
+ " config false;"
+ " uses band-capabilities;"
+ " }"
+ " container rw-sub-band-info {"
+ " if-feature \"bi:feat\";"
+ " leaf rw-number-of-laa-scarriers {"
+ " type leafref {"
+ " path \"/module-capability/band-capabilities/sub-band-info/number-of-laa-scarriers\";"
+ " require-instance false;"
+ " }"
+ " }"
+ " }"
+ " }"
+ "}");
+
+ ly_ctx_set_options(UTEST_LYCTX, LY_CTX_REF_IMPLEMENTED);
+ assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX,
+ "module b {"
+ " yang-version 1.1;"
+ " namespace \"urn:b\";"
+ " prefix \"b\";"
+ ""
+ " import b-imp {"
+ " prefix \"bi\";"
+ " }"
+ ""
+ " container laa-config {"
+ " must \"number-of-laa-scarriers <= /bi:module-capability/bi:rw-sub-band-info/bi:rw-number-of-laa-scarriers\";"
+ " }"
+ "}",
+ LYS_IN_YANG, NULL));
+ ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_REF_IMPLEMENTED);
+
+ CHECK_LOG_CTX("Schema node \"number-of-laa-scarriers\" not found; in expr \"number-of-laa-scarriers\" "
+ "with context node \"/b:laa-config\".", NULL);
}
int
diff --git a/tests/utests/types/identityref.c b/tests/utests/types/identityref.c
index cdfe057..b69299e 100644
--- a/tests/utests/types/identityref.c
+++ b/tests/utests/types/identityref.c
@@ -27,15 +27,6 @@
NODES \
"}\n"
-#define TEST_SUCCESS_XML(MOD_NAME, NAMESPACES, NODE_NAME, DATA, TYPE, ...) \
- { \
- struct lyd_node *tree; \
- const char *data = "<" NODE_NAME " xmlns=\"urn:tests:" MOD_NAME "\" " NAMESPACES ">" DATA "</" NODE_NAME ">"; \
- CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); \
- CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, TYPE, __VA_ARGS__); \
- lyd_free_all(tree); \
- }
-
#define TEST_ERROR_XML(MOD_NAME, NAMESPACES, NODE_NAME, DATA) \
{\
struct lyd_node *tree; \
@@ -64,21 +55,44 @@
test_data_xml(void **state)
{
const char *schema, *schema2;
+ struct lyd_node *tree;
+ const char *data;
/* xml test */
- schema = MODULE_CREATE_YANG("ident-base", "identity ident-base;"
- "identity ident-imp {base ident-base;}");
+ schema = "module ident-base {"
+ " yang-version 1.1;"
+ " namespace \"urn:tests:ident-base\";"
+ " prefix ib;"
+ " identity ident-base;"
+ " identity ident-imp {base ident-base;}"
+ "}";
UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL);
- schema2 = MODULE_CREATE_YANG("defs", "import ident-base {prefix ib;}"
- "identity ident1 {base ib:ident-base;}"
- "leaf l1 {type identityref {base ib:ident-base;}}");
+ schema2 = "module defs {"
+ " yang-version 1.1;"
+ " namespace \"urn:tests:defs\";"
+ " prefix d;"
+ " import ident-base {prefix ib;}"
+ " identity ident1 {base ib:ident-base;}"
+ " leaf l1 {type identityref {base ib:ident-base;}}"
+ "}";
UTEST_ADD_MODULE(schema2, LYS_IN_YANG, NULL, NULL);
- TEST_SUCCESS_XML("defs", "", "l1", "ident1", IDENT, "defs:ident1", "ident1");
+ /* local ident, XML/JSON print */
+ data = "<l1 xmlns=\"urn:tests:defs\">ident1</l1>";
+ CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+ CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, IDENT, "ident1", "ident1");
+ CHECK_LYD_STRING_PARAM(tree, data, LYD_XML, LYD_PRINT_SHRINK);
+ CHECK_LYD_STRING_PARAM(tree, "{\"defs:l1\":\"ident1\"}", LYD_JSON, LYD_PRINT_SHRINK);
+ lyd_free_all(tree);
- TEST_SUCCESS_XML("defs", "xmlns:i=\"urn:tests:ident-base\"", "l1", "i:ident-imp", IDENT, "ident-base:ident-imp",
- "ident-imp");
+ /* foreign ident, XML/JSON print */
+ data = "<l1 xmlns=\"urn:tests:defs\" xmlns:ib=\"urn:tests:ident-base\">ib:ident-imp</l1>";
+ CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+ CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, IDENT, "ident-base:ident-imp", "ident-imp");
+ CHECK_LYD_STRING_PARAM(tree, data, LYD_XML, LYD_PRINT_SHRINK);
+ CHECK_LYD_STRING_PARAM(tree, "{\"defs:l1\":\"ident-base:ident-imp\"}", LYD_JSON, LYD_PRINT_SHRINK);
+ lyd_free_all(tree);
/* invalid value */
TEST_ERROR_XML("defs", "", "l1", "fast-ethernet");
diff --git a/tests/utests/types/instanceid.c b/tests/utests/types/instanceid.c
index 4174e4d..efc2b44 100644
--- a/tests/utests/types/instanceid.c
+++ b/tests/utests/types/instanceid.c
@@ -125,7 +125,7 @@
"<list-ident xmlns=\"urn:tests:defs\"><id xmlns:b=\"urn:tests:defs\">b:ident-der2</id>"
"<value>y</value></list-ident>",
"defs", "xmlns:a=\"urn:tests:defs\"", "a:l1", "/a:list-ident[a:id='a:ident-der1']/a:value",
- INST, "/defs:list-ident[id='defs:ident-der1']/value", val5);
+ INST, "/defs:list-ident[id='ident-der1']/value", val5);
TEST_SUCCESS_XML2("<list2 xmlns=\"urn:tests:defs\"><id>defs:xxx</id><id2>x</id2></list2>"
"<list2 xmlns=\"urn:tests:defs\"><id>a:xxx</id><id2>y</id2></list2>",
diff --git a/tests/utests/types/leafref.c b/tests/utests/types/leafref.c
index c8d0cb6..7005d08 100644
--- a/tests/utests/types/leafref.c
+++ b/tests/utests/types/leafref.c
@@ -209,6 +209,38 @@
TEST_SUCCESS_LYB("lyb", "lst", "key_str", "lref", "key_str");
}
+static void
+test_data_xpath_json(void **state)
+{
+ const char *schema, *data;
+ struct lyd_node *tree;
+
+ ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED);
+
+ /* json xpath test */
+ schema = MODULE_CREATE_YANG("xp_test",
+ "list l1 {key t1;"
+ "leaf t1 {type uint8;}"
+ "list l2 {key t2;"
+ "leaf t2 {type uint8;}"
+ "leaf-list l3 {type uint8;}"
+ "}}"
+ "leaf r1 {type leafref {path \"../l1/t1\";}}"
+ "leaf r2 {type leafref {path \"deref(../r1)/../l2/t2\";}}"
+ "leaf r3 {type leafref {path \"deref(../r2)/../l3\";}}");
+
+ UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL);
+
+ data = "{"
+ " \"xp_test:l1\":[{\"t1\": 1,\"l2\":[{\"t2\": 2,\"l3\":[3]}]}],"
+ " \"xp_test:r1\": 1,"
+ " \"xp_test:r2\": 2,"
+ " \"xp_test:r3\": 3"
+ "}";
+ CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+ lyd_free_all(tree);
+}
+
int
main(void)
{
@@ -216,6 +248,7 @@
UTEST(test_data_xml),
UTEST(test_data_json),
UTEST(test_plugin_lyb),
+ UTEST(test_data_xpath_json),
};
return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/utests/types/union.c b/tests/utests/types/union.c
index 3522795..c4c282c 100644
--- a/tests/utests/types/union.c
+++ b/tests/utests/types/union.c
@@ -86,7 +86,7 @@
"defs", "", "un1", "2", UNION, "2", STRING, "2");
TEST_SUCCESS_XML2("<int8 xmlns=\"urn:tests:defs\">10</int8>",
- "defs", "xmlns:x=\"urn:tests:defs\"", "un1", "x:ident2", UNION, "defs:ident2", IDENT, "defs:ident2", "ident2");
+ "defs", "xmlns:x=\"urn:tests:defs\"", "un1", "x:ident2", UNION, "ident2", IDENT, "ident2", "ident2");
TEST_SUCCESS_XML2("<int8 xmlns=\"urn:tests:defs\">10</int8>",
"defs", "xmlns:x=\"urn:tests:defs\"", "un1", "x:ident55", UNION, "x:ident55", STRING, "x:ident55");
diff --git a/tests/yanglint/CMakeLists.txt b/tests/yanglint/CMakeLists.txt
new file mode 100644
index 0000000..c1e081a
--- /dev/null
+++ b/tests/yanglint/CMakeLists.txt
@@ -0,0 +1,36 @@
+if(WIN32)
+ set(YANGLINT_INTERACTIVE OFF)
+else()
+ set(YANGLINT_INTERACTIVE ON)
+endif()
+
+function(add_yanglint_test)
+ cmake_parse_arguments(ADDTEST "" "NAME;VIA;SCRIPT" "" ${ARGN})
+ set(TEST_NAME yanglint_${ADDTEST_NAME})
+
+ if(${ADDTEST_VIA} STREQUAL "tclsh")
+ set(WRAPPER ${PATH_TCLSH})
+ else()
+ message(FATAL_ERROR "build: unexpected wrapper '${ADDTEST_VIA}'")
+ endif()
+
+ add_test(NAME ${TEST_NAME} COMMAND ${WRAPPER} ${CMAKE_CURRENT_SOURCE_DIR}/${ADDTEST_SCRIPT})
+ set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "TESTS_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
+ set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANG_MODULES_DIR=${CMAKE_CURRENT_SOURCE_DIR}/modules")
+ set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANGLINT=${PROJECT_BINARY_DIR}")
+endfunction(add_yanglint_test)
+
+if(ENABLE_TESTS)
+ # tests of interactive mode using tclsh
+ find_program(PATH_TCLSH NAMES tclsh)
+ if(NOT PATH_TCLSH)
+ message(WARNING "'tclsh' not found! The yanglint(1) interactive tests will not be available.")
+ else()
+ if(YANGLINT_INTERACTIVE)
+ add_yanglint_test(NAME interactive VIA tclsh SCRIPT interactive/all.tcl)
+ add_yanglint_test(NAME non-interactive VIA tclsh SCRIPT non-interactive/all.tcl)
+ else()
+ add_yanglint_test(NAME non-interactive VIA tclsh SCRIPT non-interactive/all.tcl)
+ endif()
+ endif()
+endif()
diff --git a/tests/yanglint/README.md b/tests/yanglint/README.md
new file mode 100644
index 0000000..6c51d89
--- /dev/null
+++ b/tests/yanglint/README.md
@@ -0,0 +1,107 @@
+# yanglint testing
+
+Testing yanglint is divided into two ways.
+It is either tested in interactive mode using the tcl command 'expect' or non-interactively, classically from the command line.
+For both modes, unit testing was used using the tcl package tcltest.
+
+## How to
+
+The sample commands in this chapter using `tclsh` are called in the `interactive` or `non-interactive` directories.
+
+### How to run all yanglint tests?
+
+In the build directory designated for cmake, enter:
+
+```
+ctest -R yanglint
+```
+
+### How to run all yanglint tests that are in interactive mode?
+
+In the interactive directory, run:
+
+```
+tclsh all.tcl
+```
+
+### How to run all yanglint tests that are in non-interactive mode?
+
+In the non-interactive directory, run:
+
+```
+tclsh all.tcl
+```
+
+### How to run all unit-tests from .test file?
+
+```
+tclsh clear.test
+```
+
+or alternatively:
+
+```
+tclsh all.tcl -file clear.test
+```
+
+### How to run one unit-test?
+
+```
+tclsh clear.test -match clear_ietf_yang_library
+```
+
+or alternatively:
+
+```
+tclsh all.tcl -file clear.test -match clear_ietf_yang_library
+```
+
+### How to run unit-tests for a certain yanglint command?
+
+Test names are assumed to consist of the command name:
+
+```
+tclsh all.tcl -match clear*
+```
+
+### How do I get more detailed information about 'expect' for a certain test?
+
+In the interactive directory, run:
+
+```
+tclsh clear.test -match clear_ietf_yang_library -load "exp_internal 1"
+```
+
+### How do I get more detailed dialog between 'expect' and yanglint for a certain test?
+
+In the interactive directory, run:
+
+```
+tclsh clear.test -match clear_ietf_yang_library -load "log_user 1"
+```
+
+### How do I suppress error message from tcltest?
+
+Probably only possible to do via `-verbose ""`
+
+### How can I also debug?
+
+You can write commands `interact` and `interpreter` from 'Expect' package into some test.
+However, the most useful are the `exp_internal` and `log_user`, which can also be written directly into the test.
+See also the rlwrap tool.
+You can also use other debugging methods used in tcl programming.
+
+### Are the tests between interactive mode and non-interactive mode similar?
+
+Sort of...
+- regex \n must be changed to \r\n in the tests for interactive yanglint
+
+### I would like to add a new "ly_" function.
+
+Add it to the ly.tcl file.
+If you need to call other subfunctions in it, add them to namespace ly::private.
+
+### I would like to use function other than those prefixed with "ly_".
+
+Look in the common.tcl file in the "uti" namespace,
+which contains general tcl functions that can be used in both interactive and non-interactive tests.
diff --git a/tests/yanglint/common.tcl b/tests/yanglint/common.tcl
new file mode 100644
index 0000000..d186282
--- /dev/null
+++ b/tests/yanglint/common.tcl
@@ -0,0 +1,114 @@
+# @brief Common functions and variables for yanglint-interactive and yanglint-non-interactive.
+#
+# The script requires variables:
+# ::env(TESTS_DIR) - Main test directory. Must be set if the script is run via ctest.
+#
+# The script sets the variables:
+# ::env(TESTS_DIR) - Main test directory. It is set by default if not defined.
+# ::env(YANG_MODULES_DIR) - Directory of YANG modules.
+# TUT_PATH - Assumed absolute path to the directory in which the TUT is located.
+# TUT_NAME - TUT name (without path).
+# ::tcltest::testConstraint ctest - A tcltest variable that is set to true if the script is run via ctest. Causes tests
+# to be a skipped.
+
+package require tcltest
+namespace import ::tcltest::test ::tcltest::cleanupTests
+
+# Set directory paths for testing yanglint.
+if { ![info exists ::env(TESTS_DIR)] } {
+ # the script is not run via 'ctest' so paths must be set
+ set ::env(TESTS_DIR) "../"
+ set ::env(YANG_MODULES_DIR) "../modules"
+ set TUT_PATH "../../../build"
+ ::tcltest::testConstraint ctest false
+} else {
+ # cmake (ctest) already sets ::env variables
+ set TUT_PATH $::env(YANGLINT)
+ ::tcltest::testConstraint ctest true
+}
+
+set TUT_NAME "yanglint"
+
+# The script continues by defining functions specific to the yanglint tool.
+
+namespace eval uti {
+ namespace export *
+}
+
+# Iterate through the items in the list 'lst' and return a new list where
+# the items will have the form: <prefix><item><suffix>.
+# Parameter 'index' determines at which index it will start wrapping.
+# Parameter 'step' specifies how far the iterator must move to wrap the next item.
+proc uti::wrap_list_items {lst {prefix ""} {suffix ""} {index 0} {step 1}} {
+ # counter to track when to insert wrapper
+ set cnt $step
+ set len [llength $lst]
+
+ if {$index > 0} {
+ # copy list from interval <0;$index)
+ set ret [lrange $lst 0 [expr {$index - 1}]]
+ } else {
+ set ret {}
+ }
+
+ for {set i $index} {$i < $len} {incr i} {
+ incr cnt
+ set item [lindex $lst $i]
+ if {$cnt >= $step} {
+ # insert wrapper for item
+ set cnt 0
+ lappend ret [string cat $prefix $item $suffix]
+ } else {
+ # just copy item
+ lappend ret $item
+ }
+ }
+
+ return $ret
+}
+
+# Wrap list items with xml tags.
+# The element format is: <tag>value</tag>
+# Parameter 'values' is list of values.
+# Parameter 'tag' is the name of the searched tag.
+proc uti::wrap_to_xml {values tag {index 0} {step 1}} {
+ return [wrap_list_items $values "<$tag>" "</$tag>" $index $step]
+}
+
+# Wrap list items with json attributes.
+# The pair format is: "attribute": "value"
+# Parameter 'values' is list of values.
+# Parameter 'attribute' is the name of the searched attribute.
+proc uti::wrap_to_json {values attribute {index 0} {step 1}} {
+ return [wrap_list_items $values "\"$attribute\": \"" "\"" $index $step]
+}
+
+# Convert list to a regex (which is just a string) so that 'delim' is between items,
+# 'begin' is at the beginning of the expression and 'end' is at the end.
+proc uti::list_to_regex {lst {delim ".*"} {begin ".*"} {end ".*"}} {
+ return [string cat $begin [join $lst $delim] $end]
+}
+
+# Merge two lists into one such that the nth items are merged into one separated by a delimiter.
+# Returns a list that is the same length as 'lst1' and 'lst2'
+proc uti::blend_lists {lst1 lst2 {delim ".*"}} {
+ return [lmap a $lst1 b $lst2 {string cat $a $delim $b}]
+}
+
+# Create regex to find xml elements.
+# The element format is: <tag>value</tag>
+# Parameter 'values' is list of values.
+# Parameter 'tag' is the name of the searched tag.
+# The resulting expression looks like: ".*<tag>value1</tag>.*<tag>value2</tag>.*..."
+proc uti::regex_xml_elements {values tag} {
+ return [list_to_regex [wrap_to_xml $values $tag]]
+}
+
+# Create regex to find json pairs.
+# The pair format is: "attribute": "value"
+# Parameter 'values' is list of values.
+# Parameter 'attribute' is the name of the searched attribute.
+# The resulting expression looks like: ".*\"attribute\": \"value1\".*\"attribute\": \"value2\".*..."
+proc uti::regex_json_pairs {values attribute} {
+ return [list_to_regex [wrap_to_json $values $attribute]]
+}
diff --git a/tests/yanglint/data/modaction.xml b/tests/yanglint/data/modaction.xml
new file mode 100644
index 0000000..37faa2d
--- /dev/null
+++ b/tests/yanglint/data/modaction.xml
@@ -0,0 +1,8 @@
+<con xmlns="urn:yanglint:modaction">
+ <ls>
+ <lfkey>kv</lfkey>
+ <act>
+ <lfi>some_input</lfi>
+ </act>
+ </ls>
+</con>
diff --git a/tests/yanglint/data/modaction_ds.xml b/tests/yanglint/data/modaction_ds.xml
new file mode 100644
index 0000000..a5a1727
--- /dev/null
+++ b/tests/yanglint/data/modaction_ds.xml
@@ -0,0 +1,5 @@
+<con xmlns="urn:yanglint:modaction">
+ <ls>
+ <lfkey>kv</lfkey>
+ </ls>
+</con>
diff --git a/tests/yanglint/data/modaction_nc.xml b/tests/yanglint/data/modaction_nc.xml
new file mode 100644
index 0000000..a74b6bf
--- /dev/null
+++ b/tests/yanglint/data/modaction_nc.xml
@@ -0,0 +1,13 @@
+<rpc message-id="101"
+ xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <action xmlns="urn:ietf:params:xml:ns:yang:1">
+ <con xmlns="urn:yanglint:modaction">
+ <ls>
+ <lfkey>kv</lfkey>
+ <act>
+ <lfi>some_input</lfi>
+ </act>
+ </ls>
+ </con>
+ </action>
+</rpc>
diff --git a/tests/yanglint/data/modaction_reply.xml b/tests/yanglint/data/modaction_reply.xml
new file mode 100644
index 0000000..7d6532d
--- /dev/null
+++ b/tests/yanglint/data/modaction_reply.xml
@@ -0,0 +1,8 @@
+<con xmlns="urn:yanglint:modaction">
+ <ls>
+ <lfkey>kv</lfkey>
+ <act>
+ <lfo>-56</lfo>
+ </act>
+ </ls>
+</con>
diff --git a/tests/yanglint/data/modaction_reply_nc.xml b/tests/yanglint/data/modaction_reply_nc.xml
new file mode 100644
index 0000000..f7c3b8f
--- /dev/null
+++ b/tests/yanglint/data/modaction_reply_nc.xml
@@ -0,0 +1,4 @@
+<rpc-reply message-id="101"
+ xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <lfo xmlns="urn:yanglint:modaction">-56</lfo>
+</rpc-reply>
diff --git a/tests/yanglint/data/modconfig.xml b/tests/yanglint/data/modconfig.xml
new file mode 100644
index 0000000..f8a03a9
--- /dev/null
+++ b/tests/yanglint/data/modconfig.xml
@@ -0,0 +1,4 @@
+<mcc xmlns="urn:yanglint:modconfig">
+ <lft>rw</lft>
+ <lff>ro</lff>
+</mcc>
diff --git a/tests/yanglint/data/modconfig2.xml b/tests/yanglint/data/modconfig2.xml
new file mode 100644
index 0000000..c96e344
--- /dev/null
+++ b/tests/yanglint/data/modconfig2.xml
@@ -0,0 +1,3 @@
+<mcc xmlns="urn:yanglint:modconfig">
+ <lft>rw</lft>
+</mcc>
diff --git a/tests/yanglint/data/modconfig_ctx.xml b/tests/yanglint/data/modconfig_ctx.xml
new file mode 100644
index 0000000..124989c
--- /dev/null
+++ b/tests/yanglint/data/modconfig_ctx.xml
@@ -0,0 +1,13 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set>
+ <name>main-set</name>
+ <module>
+ <name>modconfig</name>
+ <namespace>urn:yanglint:modconfig</namespace>
+ </module>
+ </module-set>
+ <content-id>1</content-id>
+</yang-library>
+<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set-id>1</module-set-id>
+</modules-state>
diff --git a/tests/yanglint/data/moddatanodes.xml b/tests/yanglint/data/moddatanodes.xml
new file mode 100644
index 0000000..8ae6e97
--- /dev/null
+++ b/tests/yanglint/data/moddatanodes.xml
@@ -0,0 +1,17 @@
+<dnc xmlns="urn:yanglint:moddatanodes">
+ <lf>x</lf>
+ <lfl>1</lfl>
+ <lfl>2</lfl>
+ <con>
+ <lt>
+ <kalf>ka1</kalf>
+ <kblf>kb1</kblf>
+ <vlf>v1</vlf>
+ </lt>
+ <lt>
+ <kalf>ka2</kalf>
+ <kblf>kb2</kblf>
+ <vlf>v2</vlf>
+ </lt>
+ </con>
+</dnc>
diff --git a/tests/yanglint/data/moddefault.xml b/tests/yanglint/data/moddefault.xml
new file mode 100644
index 0000000..00f3a9d
--- /dev/null
+++ b/tests/yanglint/data/moddefault.xml
@@ -0,0 +1,4 @@
+<mdc xmlns="urn:yanglint:moddefault">
+ <lf>0</lf>
+ <di>5</di>
+</mdc>
diff --git a/tests/yanglint/data/modimp_type_ctx.xml b/tests/yanglint/data/modimp_type_ctx.xml
new file mode 100644
index 0000000..e6d158a
--- /dev/null
+++ b/tests/yanglint/data/modimp_type_ctx.xml
@@ -0,0 +1,13 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set>
+ <name>main-set</name>
+ <module>
+ <name>modimp-type</name>
+ <namespace>urn:yanglint:modimp-type</namespace>
+ </module>
+ </module-set>
+ <content-id>1</content-id>
+</yang-library>
+<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set-id>1</module-set-id>
+</modules-state>
diff --git a/tests/yanglint/data/modleaf.djson b/tests/yanglint/data/modleaf.djson
new file mode 100644
index 0000000..25af218
--- /dev/null
+++ b/tests/yanglint/data/modleaf.djson
@@ -0,0 +1,3 @@
+{
+ "modleaf:lfl": 7
+}
diff --git a/tests/yanglint/data/modleaf.dxml b/tests/yanglint/data/modleaf.dxml
new file mode 100644
index 0000000..408936a
--- /dev/null
+++ b/tests/yanglint/data/modleaf.dxml
@@ -0,0 +1 @@
+<lfl xmlns="urn:yanglint:modleaf">7</lfl>
diff --git a/tests/yanglint/data/modleaf.xml b/tests/yanglint/data/modleaf.xml
new file mode 100644
index 0000000..408936a
--- /dev/null
+++ b/tests/yanglint/data/modleaf.xml
@@ -0,0 +1 @@
+<lfl xmlns="urn:yanglint:modleaf">7</lfl>
diff --git a/tests/yanglint/data/modleafref.xml b/tests/yanglint/data/modleafref.xml
new file mode 100644
index 0000000..c9fb147
--- /dev/null
+++ b/tests/yanglint/data/modleafref.xml
@@ -0,0 +1,2 @@
+<lfl xmlns="urn:yanglint:modleaf">7</lfl>
+<lfr xmlns="urn:yanglint:modleafref">7</lfr>
diff --git a/tests/yanglint/data/modleafref2.xml b/tests/yanglint/data/modleafref2.xml
new file mode 100644
index 0000000..3946daf
--- /dev/null
+++ b/tests/yanglint/data/modleafref2.xml
@@ -0,0 +1,2 @@
+<lfl xmlns="urn:yanglint:modleaf">7</lfl>
+<lfr xmlns="urn:yanglint:modleafref">10</lfr>
diff --git a/tests/yanglint/data/modmandatory.xml b/tests/yanglint/data/modmandatory.xml
new file mode 100644
index 0000000..108cb2a
--- /dev/null
+++ b/tests/yanglint/data/modmandatory.xml
@@ -0,0 +1,3 @@
+<mmc xmlns="urn:yanglint:modmandatory">
+ <lft>9</lft>
+</mmc>
diff --git a/tests/yanglint/data/modmandatory_invalid.xml b/tests/yanglint/data/modmandatory_invalid.xml
new file mode 100644
index 0000000..de71895
--- /dev/null
+++ b/tests/yanglint/data/modmandatory_invalid.xml
@@ -0,0 +1,3 @@
+<mmc xmlns="urn:yanglint:modmandatory">
+ <lff>9</lff>
+</mmc>
diff --git a/tests/yanglint/data/modmerge.xml b/tests/yanglint/data/modmerge.xml
new file mode 100644
index 0000000..b52eff5
--- /dev/null
+++ b/tests/yanglint/data/modmerge.xml
@@ -0,0 +1,4 @@
+<mmc xmlns="urn:yanglint:modmerge">
+ <en>one</en>
+ <lm>4</lm>
+</mmc>
diff --git a/tests/yanglint/data/modmerge2.xml b/tests/yanglint/data/modmerge2.xml
new file mode 100644
index 0000000..e7f17c4
--- /dev/null
+++ b/tests/yanglint/data/modmerge2.xml
@@ -0,0 +1,3 @@
+<mmc xmlns="urn:yanglint:modmerge">
+ <en>zero</en>
+</mmc>
diff --git a/tests/yanglint/data/modmerge3.xml b/tests/yanglint/data/modmerge3.xml
new file mode 100644
index 0000000..6ef857e
--- /dev/null
+++ b/tests/yanglint/data/modmerge3.xml
@@ -0,0 +1,3 @@
+<mmc xmlns="urn:yanglint:modmerge">
+ <lf>str</lf>
+</mmc>
diff --git a/tests/yanglint/data/modnotif.xml b/tests/yanglint/data/modnotif.xml
new file mode 100644
index 0000000..81cab21
--- /dev/null
+++ b/tests/yanglint/data/modnotif.xml
@@ -0,0 +1,5 @@
+<con xmlns="urn:yanglint:modnotif">
+ <nfn>
+ <lf>nested</lf>
+ </nfn>
+</con>
diff --git a/tests/yanglint/data/modnotif2.xml b/tests/yanglint/data/modnotif2.xml
new file mode 100644
index 0000000..fc75b57
--- /dev/null
+++ b/tests/yanglint/data/modnotif2.xml
@@ -0,0 +1,3 @@
+<nfg xmlns="urn:yanglint:modnotif">
+ <lf>top</lf>
+</nfg>
diff --git a/tests/yanglint/data/modnotif2_nc.xml b/tests/yanglint/data/modnotif2_nc.xml
new file mode 100644
index 0000000..c87cfa0
--- /dev/null
+++ b/tests/yanglint/data/modnotif2_nc.xml
@@ -0,0 +1,6 @@
+<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
+ <eventTime>2010-12-06T08:00:01Z</eventTime>
+ <nfg xmlns="urn:yanglint:modnotif">
+ <lf>top</lf>
+ </nfg>
+</notification>
diff --git a/tests/yanglint/data/modnotif_ds.xml b/tests/yanglint/data/modnotif_ds.xml
new file mode 100644
index 0000000..efd835b
--- /dev/null
+++ b/tests/yanglint/data/modnotif_ds.xml
@@ -0,0 +1 @@
+<con xmlns="urn:yanglint:modnotif"></con>
diff --git a/tests/yanglint/data/modnotif_nc.xml b/tests/yanglint/data/modnotif_nc.xml
new file mode 100644
index 0000000..39a3440
--- /dev/null
+++ b/tests/yanglint/data/modnotif_nc.xml
@@ -0,0 +1,8 @@
+<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
+ <eventTime>2010-12-06T08:00:01Z</eventTime>
+ <con xmlns="urn:yanglint:modnotif">
+ <nfn>
+ <lf>nested</lf>
+ </nfn>
+ </con>
+</notification>
diff --git a/tests/yanglint/data/modoper_leafref_action.xml b/tests/yanglint/data/modoper_leafref_action.xml
new file mode 100644
index 0000000..7ccf29f
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_action.xml
@@ -0,0 +1,8 @@
+<cond xmlns="urn:yanglint:modoper-leafref">
+ <list>
+ <klf>key_val</klf>
+ <act>
+ <lfi>rw</lfi>
+ </act>
+ </list>
+</cond>
diff --git a/tests/yanglint/data/modoper_leafref_action_reply.xml b/tests/yanglint/data/modoper_leafref_action_reply.xml
new file mode 100644
index 0000000..39ec672
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_action_reply.xml
@@ -0,0 +1,8 @@
+<cond xmlns="urn:yanglint:modoper-leafref">
+ <list>
+ <klf>key_val</klf>
+ <act>
+ <lfo>rw</lfo>
+ </act>
+ </list>
+</cond>
diff --git a/tests/yanglint/data/modoper_leafref_ds.xml b/tests/yanglint/data/modoper_leafref_ds.xml
new file mode 100644
index 0000000..f934b9b
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_ds.xml
@@ -0,0 +1,9 @@
+<mcc xmlns="urn:yanglint:modconfig">
+ <lft>rw</lft>
+ <lff>ro</lff>
+</mcc>
+<cond xmlns="urn:yanglint:modoper-leafref">
+ <list>
+ <klf>key_val</klf>
+ </list>
+</cond>
diff --git a/tests/yanglint/data/modoper_leafref_notif.xml b/tests/yanglint/data/modoper_leafref_notif.xml
new file mode 100644
index 0000000..2c56b67
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_notif.xml
@@ -0,0 +1,3 @@
+<notifg xmlns="urn:yanglint:modoper-leafref">
+ <lfr>rw</lfr>
+</notifg>
diff --git a/tests/yanglint/data/modoper_leafref_notif2.xml b/tests/yanglint/data/modoper_leafref_notif2.xml
new file mode 100644
index 0000000..466697c
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_notif2.xml
@@ -0,0 +1,8 @@
+<cond xmlns="urn:yanglint:modoper-leafref">
+ <list>
+ <klf>key_val</klf>
+ <notif>
+ <lfn>rw</lfn>
+ </notif>
+ </list>
+</cond>
diff --git a/tests/yanglint/data/modoper_leafref_notif_err.xml b/tests/yanglint/data/modoper_leafref_notif_err.xml
new file mode 100644
index 0000000..1622ded
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_notif_err.xml
@@ -0,0 +1,7 @@
+<mcc xmlns="urn:yanglint:modconfig">
+ <lft>rw</lft>
+ <lff>ro</lff>
+</mcc>
+<notifg xmlns="urn:yanglint:modoper-leafref">
+ <lf>rw</lf>
+</notifg>
diff --git a/tests/yanglint/data/modoper_leafref_rpc.xml b/tests/yanglint/data/modoper_leafref_rpc.xml
new file mode 100644
index 0000000..b294544
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_rpc.xml
@@ -0,0 +1,3 @@
+<rpcg xmlns="urn:yanglint:modoper-leafref">
+ <lfi>rw</lfi>
+</rpcg>
diff --git a/tests/yanglint/data/modoper_leafref_rpc_reply.xml b/tests/yanglint/data/modoper_leafref_rpc_reply.xml
new file mode 100644
index 0000000..e8f7af3
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_rpc_reply.xml
@@ -0,0 +1,5 @@
+<rpcg xmlns="urn:yanglint:modoper-leafref">
+ <cono>
+ <lfo>rw</lfo>
+ </cono>
+</rpcg>
diff --git a/tests/yanglint/data/modrpc.xml b/tests/yanglint/data/modrpc.xml
new file mode 100644
index 0000000..a4f924d
--- /dev/null
+++ b/tests/yanglint/data/modrpc.xml
@@ -0,0 +1,3 @@
+<rpc xmlns="urn:yanglint:modrpc">
+ <lfi>some_input</lfi>
+</rpc>
diff --git a/tests/yanglint/data/modrpc_nc.xml b/tests/yanglint/data/modrpc_nc.xml
new file mode 100644
index 0000000..78d3149
--- /dev/null
+++ b/tests/yanglint/data/modrpc_nc.xml
@@ -0,0 +1,6 @@
+<rpc message-id="101"
+ xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <rpc xmlns="urn:yanglint:modrpc">
+ <lfi>some_input</lfi>
+ </rpc>
+</rpc>
diff --git a/tests/yanglint/data/modrpc_reply.xml b/tests/yanglint/data/modrpc_reply.xml
new file mode 100644
index 0000000..632971c
--- /dev/null
+++ b/tests/yanglint/data/modrpc_reply.xml
@@ -0,0 +1,5 @@
+<rpc xmlns="urn:yanglint:modrpc">
+ <con>
+ <lfo>-56</lfo>
+ </con>
+</rpc>
diff --git a/tests/yanglint/data/modrpc_reply_nc.xml b/tests/yanglint/data/modrpc_reply_nc.xml
new file mode 100644
index 0000000..da2a01c
--- /dev/null
+++ b/tests/yanglint/data/modrpc_reply_nc.xml
@@ -0,0 +1,6 @@
+<rpc-reply message-id="101"
+ xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <con xmlns="urn:yanglint:modrpc">
+ <lfo>-56</lfo>
+ </con>
+</rpc-reply>
diff --git a/tests/yanglint/data/modsm.xml b/tests/yanglint/data/modsm.xml
new file mode 100644
index 0000000..bb0793c
--- /dev/null
+++ b/tests/yanglint/data/modsm.xml
@@ -0,0 +1,3 @@
+<root xmlns="urn:yanglint:modsm">
+ <lfl xmlns="urn:yanglint:modleaf">7</lfl>
+</root>
diff --git a/tests/yanglint/data/modsm2.xml b/tests/yanglint/data/modsm2.xml
new file mode 100644
index 0000000..ff6f103
--- /dev/null
+++ b/tests/yanglint/data/modsm2.xml
@@ -0,0 +1,4 @@
+<root xmlns="urn:yanglint:modsm">
+ <lfl xmlns="urn:yanglint:modleaf">7</lfl>
+ <alf xmlns="urn:yanglint:modsm-augment">str</alf>
+</root>
diff --git a/tests/yanglint/data/modsm_ctx_ext.xml b/tests/yanglint/data/modsm_ctx_ext.xml
new file mode 100644
index 0000000..e80141a
--- /dev/null
+++ b/tests/yanglint/data/modsm_ctx_ext.xml
@@ -0,0 +1,20 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set>
+ <name>test-set</name>
+ <module>
+ <name>modleaf</name>
+ <namespace>urn:yanglint:modleaf</namespace>
+ </module>
+ </module-set>
+ <content-id>1</content-id>
+</yang-library>
+<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set-id>1</module-set-id>
+</modules-state>
+<schema-mounts xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount">
+ <mount-point>
+ <module>modsm</module>
+ <label>root</label>
+ <inline></inline>
+ </mount-point>
+</schema-mounts>
diff --git a/tests/yanglint/data/modsm_ctx_main.xml b/tests/yanglint/data/modsm_ctx_main.xml
new file mode 100644
index 0000000..5405d4d
--- /dev/null
+++ b/tests/yanglint/data/modsm_ctx_main.xml
@@ -0,0 +1,17 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set>
+ <name>main-set</name>
+ <module>
+ <name>modsm</name>
+ <namespace>urn:yanglint:modsm</namespace>
+ </module>
+ <module>
+ <name>modsm-augment</name>
+ <namespace>urn:yanglint:modsm-augment</namespace>
+ </module>
+ </module-set>
+ <content-id>1</content-id>
+</yang-library>
+<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set-id>1</module-set-id>
+</modules-state>
diff --git a/tests/yanglint/interactive/add.test b/tests/yanglint/interactive/add.test
new file mode 100644
index 0000000..d1cacc1
--- /dev/null
+++ b/tests/yanglint/interactive/add.test
@@ -0,0 +1,59 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test add_basic {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "add $mdir/modleafref.yang"
+ ly_cmd "list" "I modleafref\r.*I modleaf"
+}}
+
+test add_disable_searchdir_once {add --disable-searchdir} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {!ctest} -body {
+ ly_cmd "add $mdir/modimp-cwd.yang"
+ ly_cmd "clear"
+ ly_cmd_err "add -D $mdir/modimp-cwd.yang" "not found in local searchdirs"
+}}
+
+test add_disable_searchdir_twice {add -D -D} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {!ctest} -body {
+ ly_cmd "add $mdir/ietf-ip.yang"
+ ly_cmd "clear"
+ ly_cmd_err "add -D -D $mdir/ietf-ip.yang" "Loading \"ietf-interfaces\" module failed."
+}}
+
+test add_with_feature {Add module with feature} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "add --feature modfeature:ftr2 $mdir/modfeature.yang"
+ ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(off\\)\r\n\tftr2 \\(on\\)"
+}}
+
+test add_make_implemented_once {add --make-implemented} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_ignore "add $mdir/modmust.yang"
+ ly_cmd "list" "I modmust\r.*i modleaf"
+ ly_cmd "clear"
+ ly_ignore "add -i $mdir/modmust.yang"
+ ly_cmd "list" "I modmust\r.*I modleaf"
+}}
+
+test add_make_implemented_twice {add -i -i} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "add $mdir/modimp-type.yang"
+ ly_cmd "list" "I modimp-type\r.*i modtypedef"
+ ly_cmd "clear"
+ ly_cmd "add -i -i $mdir/modimp-type.yang"
+ ly_cmd "list" "I modimp-type\r.*I modtypedef"
+}}
+
+test add_extended_leafref_enabled {Valid module with --extended-leafref option} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "add -X $mdir/modextleafref.yang"
+}}
+
+test add_extended_leafref_disabled {Expected error if --extended-leafref is not set} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd_err "add $mdir/modextleafref.yang" "Unexpected XPath token \"FunctionName\""
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/all.tcl b/tests/yanglint/interactive/all.tcl
new file mode 100644
index 0000000..b22a5ab
--- /dev/null
+++ b/tests/yanglint/interactive/all.tcl
@@ -0,0 +1,15 @@
+package require tcltest
+
+# Hook to determine if any of the tests failed.
+# Sets a global variable exitCode to 1 if any test fails otherwise it is set to 0.
+proc tcltest::cleanupTestsHook {} {
+ variable numTests
+ set ::exitCode [expr {$numTests(Failed) > 0}]
+}
+
+if {[info exists ::env(TESTS_DIR)]} {
+ tcltest::configure -testdir "$env(TESTS_DIR)/interactive"
+}
+
+tcltest::runAllTests
+exit $exitCode
diff --git a/tests/yanglint/interactive/clear.test b/tests/yanglint/interactive/clear.test
new file mode 100644
index 0000000..cac0810
--- /dev/null
+++ b/tests/yanglint/interactive/clear.test
@@ -0,0 +1,53 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test clear_searchpath {searchpath is also deleted} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "searchpath ./"
+ ly_cmd "clear"
+ ly_cmd "searchpath" "List of the searchpaths:" -ex
+}}
+
+test clear_make_implemented_once {clear --make-implemented} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -i"
+ ly_cmd "add $mdir/modmust.yang"
+ ly_cmd "list" "I modmust\r.*I modleaf"
+}}
+
+test clear_make_implemented_twice {clear -i -i} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -i -i"
+ ly_cmd "add $mdir/modmust.yang"
+ ly_cmd "list" "I modmust\r.*I modleaf"
+}}
+
+test clear_ietf_yang_library {clear --yang-library} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ # add models
+ ly_cmd "clear -y"
+ ly_cmd "list" "I ietf-yang-library"
+}}
+
+test clear_ylf_list {apply --yang-library-file and check result by --list} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -Y $ddir/modimp_type_ctx.xml"
+ ly_cmd "list" "I modimp-type.*i modtypedef"
+}}
+
+test clear_ylf_make_implemented {apply --yang-library-file and --make-implemented} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -Y $ddir/modimp_type_ctx.xml -i -i"
+ ly_cmd "list" "I modimp-type.*I modtypedef"
+}}
+
+test clear_ylf_augment_ctx {Setup context by yang-library-file and augment module} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -Y $ddir/modconfig_ctx.xml"
+ ly_cmd "add $mdir/modconfig-augment.yang"
+ ly_cmd "print -f tree modconfig" "mca:alf"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/completion.test b/tests/yanglint/interactive/completion.test
new file mode 100644
index 0000000..86ded1f
--- /dev/null
+++ b/tests/yanglint/interactive/completion.test
@@ -0,0 +1,69 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+
+variable ly_cleanup {
+ ly_ignore
+ ly_exit
+}
+
+test completion_hints_ietf_ip {Completion and hints for ietf-ip.yang} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "add $mdir/ietf-ip.yang"
+
+ # completion and hint
+ ly_completion "print -f info -P " "print -f info -P /ietf-"
+
+ set hints {"/ietf-yang-schema-mount:schema-mounts" "/ietf-interfaces:interfaces" "/ietf-interfaces:interfaces-state"}
+ ly_hint "" "print -f info -P /ietf-" $hints
+
+ # double completion
+ ly_completion "i" "print -f info -P /ietf-interfaces:interfaces"
+ ly_completion "/" "print -f info -P /ietf-interfaces:interfaces/interface"
+
+ # a lot of hints
+ set hints {"/ietf-interfaces:interfaces/interface"
+ "/ietf-interfaces:interfaces/interface/name" "/ietf-interfaces:interfaces/interface/description"
+ "/ietf-interfaces:interfaces/interface/type" "/ietf-interfaces:interfaces/interface/enabled"
+ "/ietf-interfaces:interfaces/interface/link-up-down-trap-enable"
+ "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6"
+ }
+ ly_hint "" "print -f info -P /ietf-interfaces:interfaces/interface" $hints
+
+ # double tab
+ ly_completion "/i" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv"
+ ly_completion "4" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4"
+ set hints { "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled"
+ "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/forwarding" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/mtu"
+ "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/neighbor"
+ }
+ ly_hint "\t" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" $hints
+
+ # no more completion
+ ly_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled "
+}}
+
+# Note that somehow a command is automatically sent again (\t\t replaced by \r) after the hints.
+# But that doesn't affect the test because the tests only focus on the word in the hint.
+
+test hint_data_file {Show file hints for command data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_hint "data $mdir\t\t" "data $mdir" "modleaf.yang.*"
+}}
+
+test hint_data_format {Show print hints for command data --format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_hint "data -f \t\t" "data -f " "xml.*"
+}}
+
+test hint_data_file_after_opt {Show file hints after option with argument} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_hint "data -f xml $mdir\t\t" "data -f xml $mdir" "modleaf.yang.*"
+}}
+
+test hint_data_file_after_opt2 {Show file hints after option without argument} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_hint "data -m $mdir\t\t" "data -m $mdir" "modleaf.yang.*"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_default.test b/tests/yanglint/interactive/data_default.test
new file mode 100644
index 0000000..1953acc
--- /dev/null
+++ b/tests/yanglint/interactive/data_default.test
@@ -0,0 +1,41 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mods "ietf-netconf-with-defaults moddefault"
+set data "$::env(TESTS_DIR)/data/moddefault.xml"
+
+test data_default_not_set {Print data without --default parameter} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load $mods"
+ ly_cmd "data -f xml $data" "</lf>.*</di>\r\n</mdc>"
+ ly_cmd "data -f json $data" "lf\".*di\"\[^\"]*"
+}}
+
+test data_default_all {data --default all} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load $mods"
+ ly_cmd "data -d all -f xml $data" "</lf>.*</di>.*</ds>\r\n</mdc>"
+ ly_cmd "data -d all -f json $data" "lf\".*di\".*ds\"\[^\"]*"
+}}
+
+test data_default_all_tagged {data --default all-tagged} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load $mods"
+ ly_cmd "data -d all-tagged -f xml $data" "</lf>.*<di.*default.*</di>.*<ds.*default.*</ds>\r\n</mdc>"
+ ly_cmd "data -d all-tagged -f json $data" "lf\".*di\".*ds\".*@ds\".*default\"\[^\"]*"
+}}
+
+test data_default_trim {data --default trim} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load $mods"
+ ly_cmd "data -d trim -f xml $data" "</lf>\r\n</mdc>"
+ ly_cmd "data -d trim -f json $data" "lf\"\[^\"]*"
+}}
+
+test data_default_implicit_tagged {data --default implicit-tagged} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load $mods"
+ ly_cmd "data -d implicit-tagged -f xml $data" "</lf>.*<di>5</di>.*<ds.*default.*</ds>\r\n</mdc>"
+ ly_cmd "data -d implicit-tagged -f json $data" "lf\".*di\"\[^@]*ds\".*default\"\[^\"]*"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_format.test b/tests/yanglint/interactive/data_format.test
new file mode 100644
index 0000000..dc4b7e0
--- /dev/null
+++ b/tests/yanglint/interactive/data_format.test
@@ -0,0 +1,23 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_format_xml {Print data in xml format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd "data -f xml $ddir/modleaf.xml" "<lfl xmlns=\"urn:yanglint:modleaf\">7</lfl>"
+}}
+
+test data_format_json {Print data in json format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd "data -f json $ddir/modleaf.xml" "{\r\n \"modleaf:lfl\": 7\r\n}"
+}}
+
+test data_format_lyb_err {Print data in lyb format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd_err "data -f lyb $ddir/modleaf.xml" "The LYB format requires the -o"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_in_format.test b/tests/yanglint/interactive/data_in_format.test
new file mode 100644
index 0000000..cc5f37e
--- /dev/null
+++ b/tests/yanglint/interactive/data_in_format.test
@@ -0,0 +1,21 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_in_format_xml {--in-format xml} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd "data -F xml $ddir/modleaf.dxml"
+ ly_cmd_err "data -F json $ddir/modleaf.dxml" "Failed to parse"
+ ly_cmd_err "data -F lyb $ddir/modleaf.dxml" "Failed to parse"
+}}
+
+test data_in_format_json {--in-format json} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd "data -F json $ddir/modleaf.djson"
+ ly_cmd_err "data -F xml $ddir/modleaf.djson" "Failed to parse"
+ ly_cmd_err "data -F lyb $ddir/modleaf.djson" "Failed to parse"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_merge.test b/tests/yanglint/interactive/data_merge.test
new file mode 100644
index 0000000..38754c7
--- /dev/null
+++ b/tests/yanglint/interactive/data_merge.test
@@ -0,0 +1,33 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_merge_basic {Data is merged and the node is added} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modmerge"
+ ly_cmd "data -m -f xml $ddir/modmerge.xml $ddir/modmerge3.xml" "<en>.*<lm>.*<lf>"
+}}
+
+test data_merge_validation_failed {Data is merged but validation failed.} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modmerge"
+ ly_cmd "data $ddir/modmerge.xml"
+ ly_cmd "data $ddir/modmerge2.xml"
+ ly_cmd "data -m $ddir/modmerge2.xml $ddir/modmerge.xml"
+ ly_cmd_err "data -m $ddir/modmerge.xml $ddir/modmerge2.xml" "Merged data are not valid"
+}}
+
+test data_merge_dataconfig {The merge option has effect only for 'data' and 'config' TYPEs} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modrpc modnotif modconfig modleaf"
+ set wrn1 "option has effect only for"
+ ly_cmd_wrn "data -m -t rpc $ddir/modrpc.xml $ddir/modrpc.xml" $wrn1
+ ly_cmd_wrn "data -m -t notif $ddir/modnotif2.xml $ddir/modnotif2.xml" $wrn1
+ ly_cmd_wrn "data -m -t get $ddir/modleaf.xml $ddir/modconfig.xml" $wrn1
+ ly_cmd_wrn "data -m -t getconfig $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1
+ ly_cmd_wrn "data -m -t edit $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1
+ ly_cmd "data -m -t config $ddir/modleaf.xml $ddir/modconfig2.xml"
+ ly_cmd "data -m -t data $ddir/modleaf.xml $ddir/modconfig.xml"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_not_strict.test b/tests/yanglint/interactive/data_not_strict.test
new file mode 100644
index 0000000..201a5a9
--- /dev/null
+++ b/tests/yanglint/interactive/data_not_strict.test
@@ -0,0 +1,25 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir $::env(TESTS_DIR)/data
+
+test data_no_strict_basic {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd_err "data $ddir/modmandatory.xml" "No module with namespace \"urn:yanglint:modmandatory\" in the context."
+ ly_cmd "data -n $ddir/modmandatory.xml"
+}}
+
+test data_no_strict_invalid_data {validation with --no-strict but data are invalid} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ set errmsg "Mandatory node \"lft\" instance does not exist."
+ ly_cmd "load modmandatory"
+ ly_cmd_err "data -n $ddir/modmandatory_invalid.xml" $errmsg
+}}
+
+test data_no_strict_ignore_invalid_data {--no-strict ignore invalid data if no schema is provided} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd "data -f xml -n $ddir/modmandatory_invalid.xml $ddir/modleaf.xml" "modleaf.*</lfl>$"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_operational.test b/tests/yanglint/interactive/data_operational.test
new file mode 100644
index 0000000..c0c7b1c
--- /dev/null
+++ b/tests/yanglint/interactive/data_operational.test
@@ -0,0 +1,86 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types"
+
+test data_operational_twice {it is not allowed to specify more than one --operational parameter} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ ly_cmd "data -t notif -O $ddir/modconfig.xml -O $ddir/modleaf.xml" "cannot be set multiple times"
+}}
+
+test data_operational_no_type {--operational should be with parameter --type} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ ly_cmd_wrn "data -O $ddir/modconfig.xml $ddir/modoper_leafref_notif.xml" $err1
+}}
+
+test data_operational_missing {--operational is omitted and the datastore contents is in the data file} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ ly_cmd_err "data $ddir/modoper_leafref_notif_err.xml" "Failed to parse input data file"
+}}
+
+test data_operational_wrong_type {data are not defined as an operation} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd_wrn "data -t data -O $ddir/modconfig.xml $ddir/modleaf.xml" $err1
+}}
+
+test data_operational_datastore_with_unknown_data {unknown data are ignored} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modrpc"
+ ly_cmd "data -t rpc -O $ddir/modmandatory_invalid.xml $ddir/modrpc.xml"
+}}
+
+test data_operational_empty_datastore {datastore is considered empty because it contains unknown data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modrpc modnotif"
+ ly_cmd "data -t rpc -O $ddir/modmandatory_invalid.xml $ddir/modrpc.xml"
+ set msg "parent \"/modnotif:con\" not found in the operational data"
+ ly_cmd_err "data -t notif -O $ddir/modmandatory_invalid.xml $ddir/modnotif.xml" $msg
+}}
+
+test data_operational_notif_leafref {--operational data is referenced from notification-leafref} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ ly_cmd "data -t notif -O $ddir/modconfig.xml $ddir/modoper_leafref_notif.xml"
+}}
+
+test data_operational_nested_notif_leafref {--operational data is referenced from nested-notification-leafref} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ ly_cmd "data -t notif -O $ddir/modoper_leafref_ds.xml $ddir/modoper_leafref_notif2.xml"
+}}
+
+test data_operational_nested_notif_parent_missing {--operational data are invalid due to missing parent node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ set msg "klf='key_val']\" not found in the operational data"
+ ly_cmd_err "data -t notif -O $ddir/modconfig.xml $ddir/modoper_leafref_notif2.xml" $msg
+}}
+
+test data_operational_action_leafref {--operational data is referenced from action-leafref} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ ly_cmd "data -t rpc -O $ddir/modoper_leafref_ds.xml $ddir/modoper_leafref_action.xml"
+}}
+
+test data_operational_action_reply_leafref {--operational data is referenced from action-leafref output} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ ly_cmd "data -t reply -O $ddir/modoper_leafref_ds.xml $ddir/modoper_leafref_action_reply.xml"
+}}
+
+test data_operational_rpc_leafref {--operational data is referenced from rpc-leafref} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ ly_cmd "data -t rpc -O $ddir/modconfig.xml $ddir/modoper_leafref_rpc.xml"
+}}
+
+test data_operational_rpc_reply_leafref {--operational data is referenced from rpc-leafref output} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modoper-leafref"
+ ly_cmd "data -t reply -O $ddir/modconfig.xml $ddir/modoper_leafref_rpc_reply.xml"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_present.test b/tests/yanglint/interactive/data_present.test
new file mode 100644
index 0000000..4bba596
--- /dev/null
+++ b/tests/yanglint/interactive/data_present.test
@@ -0,0 +1,25 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_present_via_mandatory {validation of mandatory-stmt will pass only with the --present} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf modmandatory"
+ ly_cmd_err "data $ddir/modleaf.xml" "Mandatory node \"lft\" instance does not exist."
+ ly_cmd "data -e $ddir/modleaf.xml"
+}}
+
+test data_present_merge {validation with --present and --merge} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf modmandatory moddefault"
+ ly_cmd_err "data -m $ddir/modleaf.xml $ddir/moddefault.xml" "Mandatory node \"lft\" instance does not exist."
+ ly_cmd "data -e -m $ddir/modleaf.xml $ddir/moddefault.xml"
+}}
+
+test data_present_merge_invalid {using --present and --merge but data are invalid} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf modmandatory"
+ ly_cmd_err "data -e -m $ddir/modleaf.xml $ddir/modmandatory_invalid.xml" "Mandatory node \"lft\" instance does not exist."
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_type.test b/tests/yanglint/interactive/data_type.test
new file mode 100644
index 0000000..a442813
--- /dev/null
+++ b/tests/yanglint/interactive/data_type.test
@@ -0,0 +1,140 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_type_data {data --type data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modconfig"
+ ly_cmd "data -t data $ddir/modconfig.xml"
+}}
+
+test data_type_config {data --type config} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modconfig"
+ ly_cmd_err "data -t config $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+ ly_cmd "data -t config $ddir/modconfig2.xml"
+}}
+
+test data_type_get {data --type get} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleafref"
+ ly_cmd_err "data -t data $ddir/modleafref2.xml" "Invalid leafref value"
+ ly_cmd "data -t get $ddir/modleafref2.xml"
+}}
+
+test data_type_getconfig_no_state {No state node for data --type getconfig} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modconfig"
+ ly_cmd_err "data -t getconfig $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+ ly_cmd "data -t getconfig $ddir/modconfig2.xml"
+}}
+
+test data_type_getconfig_parse_only {No validation performed for data --type getconfig} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleafref"
+ ly_cmd_err "data -t data $ddir/modleafref2.xml" "Invalid leafref value"
+ ly_cmd "data -t getconfig $ddir/modleafref2.xml"
+}}
+
+test data_type_edit_no_state {No state node for data --type edit} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modconfig"
+ ly_cmd_err "data -t edit $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+ ly_cmd "data -t edit $ddir/modconfig2.xml"
+}}
+
+test data_type_edit_parse_only {No validation performed for data --type edit} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleafref"
+ ly_cmd_err "data -t data $ddir/modleafref2.xml" "Invalid leafref value"
+ ly_cmd "data -t edit $ddir/modleafref2.xml"
+}}
+
+test data_type_rpc {Validation of rpc-statement by data --type rpc} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modrpc modleaf"
+ ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd "data -t rpc $ddir/modrpc.xml"
+}}
+
+test data_type_rpc_nc {Validation of rpc-statement by data --type nc-rpc} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modrpc modleaf ietf-netconf"
+ ly_cmd_err "data -t nc-rpc $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope"
+ ly_cmd "data -t nc-rpc $ddir/modrpc_nc.xml"
+}}
+
+test data_type_rpc_reply {Validation of rpc-reply by data --type reply} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modrpc modleaf"
+ ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd_wrn "data -t reply -R $ddir/modrpc.xml $ddir/modrpc_reply.xml" "needed only for NETCONF"
+ ly_cmd "data -t reply $ddir/modrpc_reply.xml"
+}}
+
+test data_type_rpc_reply_nc {Validation of rpc-reply by data --type nc-reply} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modrpc modleaf"
+ ly_cmd_err "data -t nc-reply -R $ddir/modrpc_nc.xml $ddir/modleaf.xml" "Missing NETCONF <rpc-reply> envelope"
+ ly_cmd_err "data -t nc-reply $ddir/modrpc_reply_nc.xml" "Missing source RPC"
+ ly_cmd "data -t nc-reply -R $ddir/modrpc_nc.xml $ddir/modrpc_reply_nc.xml"
+}}
+
+test data_type_rpc_action {Validation of action-statement by data --type rpc} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modaction modleaf"
+ ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd "data -t rpc -O $ddir/modaction_ds.xml $ddir/modaction.xml"
+}}
+
+test data_type_rpc_action_nc {Validation of action-statement by data --type nc-rpc} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modaction modleaf"
+ ly_cmd_err "data -t nc-rpc $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope"
+ ly_cmd "data -t nc-rpc -O $ddir/modaction_ds.xml $ddir/modaction_nc.xml"
+}}
+
+test data_type_rpc_action_reply {Validation of action-reply by data --type reply} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modaction modleaf"
+ ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd "data -t reply -O $ddir/modaction_ds.xml $ddir/modaction_reply.xml"
+}}
+
+test data_type_rpc_action_reply_nc {Validation of action-reply by data --type nc-reply} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modaction modleaf"
+ ly_cmd_err "data -t nc-reply -R $ddir/modaction_nc.xml $ddir/modleaf.xml" "Missing NETCONF <rpc-reply> envelope"
+ ly_cmd_err "data -t nc-reply $ddir/modaction_reply_nc.xml" "Missing source RPC"
+ ly_cmd_err "data -t nc-reply -R $ddir/modaction_nc.xml $ddir/modaction_reply_nc.xml" "operational parameter needed"
+ ly_cmd "data -t nc-reply -O $ddir/modaction_ds.xml -R $ddir/modaction_nc.xml $ddir/modaction_reply_nc.xml"
+}}
+
+test data_type_notif {Validation of notification-statement by data --type notif} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modnotif modleaf"
+ ly_cmd_err "data -t notif $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd "data -t notif $ddir/modnotif2.xml"
+}}
+
+test data_type_notif_nc {Validation of notification-statement by data --type nc-notif} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modnotif modleaf ietf-netconf"
+ ly_cmd_err "data -t nc-notif $ddir/modleaf.xml" "Missing NETCONF <notification> envelope"
+ ly_cmd "data -t nc-notif $ddir/modnotif2_nc.xml"
+}}
+
+test data_type_notif_nested {Validation of nested-notification-statement by data --type notif} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modnotif modleaf"
+ ly_cmd "data -t notif -O $ddir/modnotif_ds.xml $ddir/modnotif.xml"
+}}
+
+test data_type_notif_nested_nc {Validation of nested-notification-statement by data --type nc-notif} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modnotif modleaf ietf-netconf"
+ ly_cmd_err "data -t nc-notif $ddir/modleaf.xml" "Missing NETCONF <notification> envelope"
+ ly_cmd "data -t nc-notif -O $ddir/modnotif_ds.xml $ddir/modnotif_nc.xml"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_xpath.test b/tests/yanglint/interactive/data_xpath.test
new file mode 100644
index 0000000..398cb9f
--- /dev/null
+++ b/tests/yanglint/interactive/data_xpath.test
@@ -0,0 +1,57 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set data "$::env(TESTS_DIR)/data/moddatanodes.xml"
+
+test data_xpath_empty {--xpath to missing node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load moddatanodes"
+ ly_cmd "data -x /moddatanodes:dnc/mis $data" "Empty"
+}}
+
+test data_xpath_leaf {--xpath to leaf node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load moddatanodes"
+ ly_cmd "data -x /moddatanodes:dnc/lf $data" "leaf \"lf\" \\(value: \"x\"\\)"
+}}
+
+test data_xpath_leaflist {--xpath to leaf-list node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load moddatanodes"
+ set r1 "leaf-list \"lfl\" \\(value: \"1\"\\)"
+ set r2 "leaf-list \"lfl\" \\(value: \"2\"\\)"
+ ly_cmd "data -x /moddatanodes:dnc/lfl $data" "$r1\r\n $r2"
+}}
+
+test data_xpath_list {--xpath to list} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load moddatanodes"
+ set r1 "list \"lt\" \\(\"kalf\": \"ka1\"; \"kblf\": \"kb1\";\\)"
+ set r2 "list \"lt\" \\(\"kalf\": \"ka2\"; \"kblf\": \"kb2\";\\)"
+ ly_cmd "data -x /moddatanodes:dnc/con/lt $data" "$r1\r\n $r2"
+}}
+
+test data_xpath_container {--xpath to container} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load moddatanodes"
+ ly_cmd "data -x /moddatanodes:dnc/con $data" "container \"con\""
+}}
+
+test data_xpath_wrong_path {--xpath to a non-existent node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load moddatanodes"
+ ly_cmd_err "data -x /moddatanodes:dnc/wrng $data" "xpath failed"
+}}
+
+test data_xpath_err_format {--xpath cannot be combined with --format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load moddatanodes"
+ ly_cmd_err "data -f xml -x /moddatanodes:dnc/lf $data" "option cannot be combined"
+}}
+
+test data_xpath_err_default {--xpath cannot be combined with --default} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load moddatanodes ietf-netconf-with-defaults"
+ ly_cmd_err "data -d all -x /moddatanodes:dnc/lf $data" "option cannot be combined"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/debug.test b/tests/yanglint/interactive/debug.test
new file mode 100644
index 0000000..8a64c92
--- /dev/null
+++ b/tests/yanglint/interactive/debug.test
@@ -0,0 +1,33 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test debug_dict {Check debug message DICT} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body {
+ ly_cmd "verb debug"
+ ly_cmd "debug dict"
+ ly_cmd "load modleaf" "DICT"
+}}
+
+test debug_xpath {Check debug message XPATH} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body {
+ ly_cmd "verb debug"
+ ly_cmd "debug xpath"
+ ly_cmd "load modmust" "XPATH"
+}}
+
+test debug_dep_sets {Check debug message DEPSETS} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body {
+ ly_cmd "verb debug"
+ ly_cmd "debug dep-sets"
+ ly_cmd "load modleaf" "DEPSETS"
+}}
+
+test debug_depsets_xpath {Check debug message DEPSETS and XPATH} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body {
+ ly_cmd "verb debug"
+ ly_cmd "debug dep-sets xpath"
+ ly_cmd "load modmust" "DEPSETS.*XPATH"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/extdata.test b/tests/yanglint/interactive/extdata.test
new file mode 100644
index 0000000..e253d1a
--- /dev/null
+++ b/tests/yanglint/interactive/extdata.test
@@ -0,0 +1,63 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+
+test extdata_set_clear {Set and clear extdata file} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "extdata" "No file set"
+ ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+ ly_cmd "extdata" "$ddir/modsm_ctx_ext.xml"
+ ly_cmd "extdata -c"
+ ly_cmd "extdata" "No file set"
+}}
+
+test extdata_clear_cmd {Clear extdata file by 'clear' command} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+ ly_cmd "clear"
+ ly_cmd "extdata" "No file set"
+}}
+
+test extdata_one_only {Only one file for extdata} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd_err "extdata $ddir/modsm_ctx_ext.xml $ddir/modsm_ctx_ext.xml" "Only one file must be entered"
+}}
+
+test extdata_schema_mount_tree {Print tree output of a model with Schema Mount} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -y"
+ ly_cmd "searchpath $mdir"
+ ly_cmd "load modsm"
+ ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+ ly_cmd "print -f tree modsm" "--mp root.*--rw lfl/"
+}}
+
+test ext_data_schema_mount_tree_yanglibfile {Print tree output of a model with Schema Mount and --yang-library-file} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -Y $ddir/modsm_ctx_main.xml"
+ ly_cmd "searchpath $mdir"
+ ly_cmd "load modsm"
+ ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+ ly_cmd "print -f tree modsm" "--mp root.*--rw lfl/.*--rw msa:alf?"
+}}
+
+test ext_data_schema_mount_xml {Validating and printing mounted data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -y"
+ ly_cmd "searchpath $mdir"
+ ly_cmd "load modsm"
+ ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+ ly_cmd "data -f xml -t config $ddir/modsm.xml" "</lfl>"
+}}
+
+test ext_data_schema_mount_xml_yanglibfile {Validating and printing mounted data with --yang-library-file} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -Y $ddir/modsm_ctx_main.xml"
+ ly_cmd "searchpath $mdir"
+ ly_cmd "load modsm"
+ ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+ ly_cmd "data -f xml -t config $ddir/modsm2.xml" "</lfl>.*</alf>"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/feature.test b/tests/yanglint/interactive/feature.test
new file mode 100644
index 0000000..84bfa8e
--- /dev/null
+++ b/tests/yanglint/interactive/feature.test
@@ -0,0 +1,37 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+test feature_all_default {Default output of feature --all} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "feature -a" "yang:\r\n\t(none)\r\n\r\nietf-yang-schema-mount:\r\n\t(none)\r\n" -ex
+}}
+
+test feature_all_add_module {Add module with only one feature and call feature --all} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load --feature modfeature:ftr1 modfeature"
+ ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(on\\)\r\n\tftr2 \\(off\\)"
+}}
+
+test feature_all_on {Add module with all enabled features and call feature --all} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load --feature modfeature:* modfeature"
+ ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(on\\)\r\n\tftr2 \\(on\\)"
+}}
+
+test feature_one_module {Show features for one module} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load ietf-ip"
+ ly_cmd "feature -f ietf-ip" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" -ex
+}}
+
+test feature_more_modules {Show a mix of modules with and without features} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+
+ set features " -F modfeature:ftr1,ftr2\
+-F modleaf:\
+-F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf"
+
+ ly_cmd "load ietf-ip modleaf modfeature"
+ ly_cmd "feature -f modfeature modleaf ietf-ip" $features -ex
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/list.test b/tests/yanglint/interactive/list.test
new file mode 100644
index 0000000..ab59a32
--- /dev/null
+++ b/tests/yanglint/interactive/list.test
@@ -0,0 +1,34 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+namespace import uti::regex_xml_elements uti::regex_json_pairs
+
+set modules {ietf-yang-library ietf-inet-types}
+
+test list_basic {basic test} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "list" "ietf-yang-types"
+}}
+
+test list_format_xml {list --format xml} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -y"
+ ly_cmd "list -f xml" [regex_xml_elements $modules "name"]
+}}
+
+test list_format_json {list --format json} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -y"
+ ly_cmd "list -f json" [regex_json_pairs $modules "name"]
+}}
+
+test list_ietf_yang_library {Error due to missing ietf-yang-library} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd_err "list -f xml" "Module \"ietf-yang-library\" is not implemented."
+}}
+
+test list_bad_format {Error due to bad format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "clear -y"
+ ly_cmd_err "list -f csv" "Unknown output format csv"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/load.test b/tests/yanglint/interactive/load.test
new file mode 100644
index 0000000..a95d044
--- /dev/null
+++ b/tests/yanglint/interactive/load.test
@@ -0,0 +1,45 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+test load_basic {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleafref"
+ ly_cmd "list" "I modleafref\r.*I modleaf"
+}}
+
+test load_with_feature {Load module with feature} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load --feature modfeature:ftr2 modfeature"
+ ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(off\\)\r\n\tftr2 \\(on\\)"
+}}
+
+test load_make_implemented_once {load --make-implemented} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_ignore "load modmust"
+ ly_cmd "list" "I modmust\r.*i modleaf"
+ ly_cmd "clear"
+ ly_cmd "searchpath $::env(YANG_MODULES_DIR)"
+ ly_cmd "load -i modmust"
+ ly_cmd "list" "I modmust\r.*I modleaf"
+}}
+
+test load_make_implemented_twice {load -i -i} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modimp-type"
+ ly_cmd "list" "I modimp-type\r.*i modtypedef"
+ ly_cmd "clear"
+ ly_cmd "searchpath $::env(YANG_MODULES_DIR)"
+ ly_cmd "load -i -i modimp-type"
+ ly_cmd "list" "I modimp-type\r.*I modtypedef"
+}}
+
+test load_extended_leafref_enabled {Valid module with --extended-leafref option} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load -X modextleafref"
+}}
+
+test load_extended_leafref_disabled {Expected error if --extended-leafref is not set} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd_err "load modextleafref" "Unexpected XPath token \"FunctionName\""
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/ly.tcl b/tests/yanglint/interactive/ly.tcl
new file mode 100644
index 0000000..4c56be4
--- /dev/null
+++ b/tests/yanglint/interactive/ly.tcl
@@ -0,0 +1,81 @@
+# @brief The main source of functions and variables for testing yanglint in the interactive mode.
+
+# For testing yanglint.
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/common.tcl" : "../common.tcl"}]
+# For testing any interactive tool.
+source "$::env(TESTS_DIR)/../tool_i.tcl"
+
+# The script continues by defining variables and functions specific to the interactive yanglint tool.
+
+# set the timeout to 5 seconds
+set timeout 5
+# prompt of yanglint
+set prompt "> "
+# turn off dialog between expect and yanglint
+log_user 0
+# setting some large terminal width
+stty columns 720
+
+# default setup for every unit test
+variable ly_setup {
+ spawn $TUT
+ ly_skip_warnings
+ # Searchpath is set, so modules can be loaded via the 'load' command.
+ ly_cmd "searchpath $::env(YANG_MODULES_DIR)"
+}
+
+# default cleanup for every unit test
+variable ly_cleanup {
+ ly_exit
+}
+
+# Skip no dir and/or no history warnings and prompt.
+proc ly_skip_warnings {} {
+ global prompt
+ expect -re "(YANGLINT.*)*$prompt" {}
+}
+
+# Send command 'cmd' to the process, expect error header and then check output string by 'pattern'.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex. It must not contain a prompt.
+proc ly_cmd_err {cmd pattern} {
+ global prompt
+
+ send -- "${cmd}\r"
+ expect -- "${cmd}\r\n"
+
+ expect {
+ -re "YANGLINT\\\[E\\\]: .*${pattern}.*\r\n${prompt}$" {}
+ -re "libyang\\\[\[0-9]+\\\]: .*${pattern}.*\r\n${prompt}$" {}
+ -re "\r\n${prompt}$" {
+ error "unexpected output:\n$expect_out(buffer)"
+ }
+ }
+}
+
+# Send command 'cmd' to the process, expect warning header and then check output string by 'pattern'.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex. It must not contain a prompt.
+proc ly_cmd_wrn {cmd pattern} {
+ ly_cmd_header $cmd "YANGLINT\\\[W\\\]:" $pattern
+}
+
+# Send 'exit' and wait for eof.
+proc ly_exit {} {
+ send "exit\r"
+ expect eof
+}
+
+# Check if yanglint is configured as DEBUG.
+# Return 1 on success.
+proc yanglint_debug {} {
+ global TUT
+ # Call non-interactive yanglint with --help.
+ set output [exec -- $TUT "-h"]
+ # Find option --debug.
+ if { [regexp -- "--debug=GROUPS" $output] } {
+ return 1
+ } else {
+ return 0
+ }
+}
diff --git a/tests/yanglint/interactive/modcwd.yang b/tests/yanglint/interactive/modcwd.yang
new file mode 100644
index 0000000..db33e73
--- /dev/null
+++ b/tests/yanglint/interactive/modcwd.yang
@@ -0,0 +1,4 @@
+module modcwd {
+ namespace "urn:yanglint:modcwd";
+ prefix mc;
+}
diff --git a/tests/yanglint/interactive/print.test b/tests/yanglint/interactive/print.test
new file mode 100644
index 0000000..8b9d740
--- /dev/null
+++ b/tests/yanglint/interactive/print.test
@@ -0,0 +1,77 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ipv6_path "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address"
+
+test print_yang {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd "print -f yang modleaf" "leaf lfl"
+}}
+
+test print_yang_submodule {Print submodule in yang format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modinclude"
+ ly_cmd "print -f yang modsub" "submodule modsub"
+}}
+
+test print_yin {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd "print -f yin modleaf" "<leaf name=\"lfl\">"
+}}
+
+test print_yin_submodule {Print submodule in yin format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modinclude"
+ ly_cmd "print -f yin modsub" "<submodule name=\"modsub\""
+}}
+
+test print_info {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd "print -f info modleaf" "status current"
+}}
+
+test print_info_path {Print subtree in info format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load ietf-ip"
+ ly_cmd "print -f info -P $ipv6_path" "^list address .* leaf prefix-length"
+}}
+
+test print_info_path_single_node {Print node in info format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load ietf-ip"
+ ly_cmd "print -f info -q -P $ipv6_path" "^list address .* IPv6 addresses on the interface.\";\r\n\}$"
+}}
+
+test print_tree {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modleaf"
+ ly_cmd "print -f tree modleaf" "\\+--rw lfl"
+}}
+
+test print_tree_submodule {Print submodule in tree format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load modinclude"
+ ly_cmd "print -f tree modsub" "submodule: modsub"
+}}
+
+test print_tree_path {Print subtree in tree format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load ietf-ip"
+ ly_cmd "print -f tree -P $ipv6_path" "\\+--rw address.*\\+--rw prefix-length"
+}}
+
+test print_tree_path_single_node {Print node in tree format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load ietf-ip"
+ ly_cmd "print -f tree -q -P $ipv6_path" "\\+--rw address\\* \\\[ip\\\]$"
+}}
+
+test print_tree_path_single_node_line_length {Print node in the tree format and limit row size} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "load ietf-ip"
+ ly_cmd "print -f tree -L 20 -q -P $ipv6_path" "\\+--rw address\\*\r\n *\\\[ip\\\]$"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/searchpath.test b/tests/yanglint/interactive/searchpath.test
new file mode 100644
index 0000000..3bd6796
--- /dev/null
+++ b/tests/yanglint/interactive/searchpath.test
@@ -0,0 +1,24 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+variable ly_setup {
+ spawn $TUT
+ ly_skip_warnings
+}
+
+test searchpath_basic {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "searchpath $mdir"
+ ly_cmd "searchpath" "$mdir"
+ ly_cmd "load modleaf"
+}}
+
+test searchpath_clear {searchpath --clear} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_cmd "searchpath $mdir"
+ ly_cmd "searchpath --clear"
+ ly_cmd_err "load modleaf" "Data model \"modleaf\" not found in local searchdirs"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/modules/ietf-interfaces.yang b/tests/yanglint/modules/ietf-interfaces.yang
new file mode 100644
index 0000000..ad64425
--- /dev/null
+++ b/tests/yanglint/modules/ietf-interfaces.yang
@@ -0,0 +1,725 @@
+module ietf-interfaces {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces";
+ prefix if;
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ organization
+ "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netmod/>
+ WG List: <mailto:netmod@ietf.org>
+
+ WG Chair: Thomas Nadeau
+ <mailto:tnadeau@lucidvision.com>
+
+ WG Chair: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>";
+
+ description
+ "This module contains a collection of YANG definitions for
+ managing network interfaces.
+
+ Copyright (c) 2014 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 7223; see
+ the RFC itself for full legal notices.";
+
+ revision 2014-05-08 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 7223: A YANG Data Model for Interface Management";
+ }
+
+ /*
+ * Typedefs
+ */
+
+ typedef interface-ref {
+ type leafref {
+ path "/if:interfaces/if:interface/if:name";
+ }
+ description
+ "This type is used by data models that need to reference
+ configured interfaces.";
+ }
+
+ typedef interface-state-ref {
+ type leafref {
+ path "/if:interfaces-state/if:interface/if:name";
+ }
+ description
+ "This type is used by data models that need to reference
+ the operationally present interfaces.";
+ }
+
+ /*
+ * Identities
+ */
+
+ identity interface-type {
+ description
+ "Base identity from which specific interface types are
+ derived.";
+ }
+
+ /*
+ * Features
+ */
+
+ feature arbitrary-names {
+ description
+ "This feature indicates that the device allows user-controlled
+ interfaces to be named arbitrarily.";
+ }
+ feature pre-provisioning {
+ description
+ "This feature indicates that the device supports
+ pre-provisioning of interface configuration, i.e., it is
+ possible to configure an interface whose physical interface
+ hardware is not present on the device.";
+ }
+
+ feature if-mib {
+ description
+ "This feature indicates that the device implements
+ the IF-MIB.";
+ reference
+ "RFC 2863: The Interfaces Group MIB";
+ }
+
+ /*
+ * Configuration data nodes
+ */
+
+ container interfaces {
+ description
+ "Interface configuration parameters.";
+
+ list interface {
+ key "name";
+
+ description
+ "The list of configured interfaces on the device.
+
+ The operational state of an interface is available in the
+ /interfaces-state/interface list. If the configuration of a
+ system-controlled interface cannot be used by the system
+ (e.g., the interface hardware present does not match the
+ interface type), then the configuration is not applied to
+ the system-controlled interface shown in the
+ /interfaces-state/interface list. If the configuration
+ of a user-controlled interface cannot be used by the system,
+ the configured interface is not instantiated in the
+ /interfaces-state/interface list.";
+
+ leaf name {
+ type string;
+ description
+ "The name of the interface.
+
+ A device MAY restrict the allowed values for this leaf,
+ possibly depending on the type of the interface.
+ For system-controlled interfaces, this leaf is the
+ device-specific name of the interface. The 'config false'
+ list /interfaces-state/interface contains the currently
+ existing interfaces on the device.
+
+ If a client tries to create configuration for a
+ system-controlled interface that is not present in the
+ /interfaces-state/interface list, the server MAY reject
+ the request if the implementation does not support
+ pre-provisioning of interfaces or if the name refers to
+ an interface that can never exist in the system. A
+ NETCONF server MUST reply with an rpc-error with the
+ error-tag 'invalid-value' in this case.
+
+ If the device supports pre-provisioning of interface
+ configuration, the 'pre-provisioning' feature is
+ advertised.
+
+ If the device allows arbitrarily named user-controlled
+ interfaces, the 'arbitrary-names' feature is advertised.
+
+ When a configured user-controlled interface is created by
+ the system, it is instantiated with the same name in the
+ /interface-state/interface list.";
+ }
+
+ leaf description {
+ type string;
+ description
+ "A textual description of the interface.
+
+ A server implementation MAY map this leaf to the ifAlias
+ MIB object. Such an implementation needs to use some
+ mechanism to handle the differences in size and characters
+ allowed between this leaf and ifAlias. The definition of
+ such a mechanism is outside the scope of this document.
+
+ Since ifAlias is defined to be stored in non-volatile
+ storage, the MIB implementation MUST map ifAlias to the
+ value of 'description' in the persistently stored
+ datastore.
+
+ Specifically, if the device supports ':startup', when
+ ifAlias is read the device MUST return the value of
+ 'description' in the 'startup' datastore, and when it is
+ written, it MUST be written to the 'running' and 'startup'
+ datastores. Note that it is up to the implementation to
+
+ decide whether to modify this single leaf in 'startup' or
+ perform an implicit copy-config from 'running' to
+ 'startup'.
+
+ If the device does not support ':startup', ifAlias MUST
+ be mapped to the 'description' leaf in the 'running'
+ datastore.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAlias";
+ }
+
+ leaf type {
+ type identityref {
+ base interface-type;
+ }
+ mandatory true;
+ description
+ "The type of the interface.
+
+ When an interface entry is created, a server MAY
+ initialize the type leaf with a valid value, e.g., if it
+ is possible to derive the type from the name of the
+ interface.
+
+ If a client tries to set the type of an interface to a
+ value that can never be used by the system, e.g., if the
+ type is not supported or if the type does not match the
+ name of the interface, the server MUST reject the request.
+ A NETCONF server MUST reply with an rpc-error with the
+ error-tag 'invalid-value' in this case.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifType";
+ }
+
+ leaf enabled {
+ type boolean;
+ default "true";
+ description
+ "This leaf contains the configured, desired state of the
+ interface.
+
+ Systems that implement the IF-MIB use the value of this
+ leaf in the 'running' datastore to set
+ IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry
+ has been initialized, as described in RFC 2863.
+
+
+
+ Changes in this leaf in the 'running' datastore are
+ reflected in ifAdminStatus, but if ifAdminStatus is
+ changed over SNMP, this leaf is not affected.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+ }
+
+ leaf link-up-down-trap-enable {
+ if-feature if-mib;
+ type enumeration {
+ enum enabled {
+ value 1;
+ }
+ enum disabled {
+ value 2;
+ }
+ }
+ description
+ "Controls whether linkUp/linkDown SNMP notifications
+ should be generated for this interface.
+
+ If this node is not configured, the value 'enabled' is
+ operationally used by the server for interfaces that do
+ not operate on top of any other interface (i.e., there are
+ no 'lower-layer-if' entries), and 'disabled' otherwise.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifLinkUpDownTrapEnable";
+ }
+ }
+ }
+
+ /*
+ * Operational state data nodes
+ */
+
+ container interfaces-state {
+ config false;
+ description
+ "Data nodes for the operational state of interfaces.";
+
+ list interface {
+ key "name";
+
+
+
+
+
+ description
+ "The list of interfaces on the device.
+
+ System-controlled interfaces created by the system are
+ always present in this list, whether they are configured or
+ not.";
+
+ leaf name {
+ type string;
+ description
+ "The name of the interface.
+
+ A server implementation MAY map this leaf to the ifName
+ MIB object. Such an implementation needs to use some
+ mechanism to handle the differences in size and characters
+ allowed between this leaf and ifName. The definition of
+ such a mechanism is outside the scope of this document.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifName";
+ }
+
+ leaf type {
+ type identityref {
+ base interface-type;
+ }
+ mandatory true;
+ description
+ "The type of the interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifType";
+ }
+
+ leaf admin-status {
+ if-feature if-mib;
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+ description
+ "Not ready to pass packets and not in some test mode.";
+ }
+
+
+
+ enum testing {
+ value 3;
+ description
+ "In some test mode.";
+ }
+ }
+ mandatory true;
+ description
+ "The desired state of the interface.
+
+ This leaf has the same read semantics as ifAdminStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+ }
+
+ leaf oper-status {
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+ description
+ "The interface does not pass any packets.";
+ }
+ enum testing {
+ value 3;
+ description
+ "In some test mode. No operational packets can
+ be passed.";
+ }
+ enum unknown {
+ value 4;
+ description
+ "Status cannot be determined for some reason.";
+ }
+ enum dormant {
+ value 5;
+ description
+ "Waiting for some external event.";
+ }
+ enum not-present {
+ value 6;
+ description
+ "Some component (typically hardware) is missing.";
+ }
+ enum lower-layer-down {
+ value 7;
+ description
+ "Down due to state of lower-layer interface(s).";
+ }
+ }
+ mandatory true;
+ description
+ "The current operational state of the interface.
+
+ This leaf has the same semantics as ifOperStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOperStatus";
+ }
+
+ leaf last-change {
+ type yang:date-and-time;
+ description
+ "The time the interface entered its current operational
+ state. If the current state was entered prior to the
+ last re-initialization of the local network management
+ subsystem, then this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifLastChange";
+ }
+
+ leaf if-index {
+ if-feature if-mib;
+ type int32 {
+ range "1..2147483647";
+ }
+ mandatory true;
+ description
+ "The ifIndex value for the ifEntry represented by this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifIndex";
+ }
+
+ leaf phys-address {
+ type yang:phys-address;
+ description
+ "The interface's address at its protocol sub-layer. For
+ example, for an 802.x interface, this object normally
+ contains a Media Access Control (MAC) address. The
+ interface's media-specific modules must define the bit
+
+
+ and byte ordering and the format of the value of this
+ object. For interfaces that do not have such an address
+ (e.g., a serial line), this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifPhysAddress";
+ }
+
+ leaf-list higher-layer-if {
+ type interface-state-ref;
+ description
+ "A list of references to interfaces layered on top of this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf-list lower-layer-if {
+ type interface-state-ref;
+ description
+ "A list of references to interfaces layered underneath this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf speed {
+ type yang:gauge64;
+ units "bits/second";
+ description
+ "An estimate of the interface's current bandwidth in bits
+ per second. For interfaces that do not vary in
+ bandwidth or for those where no accurate estimation can
+ be made, this node should contain the nominal bandwidth.
+ For interfaces that have no concept of bandwidth, this
+ node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifSpeed, ifHighSpeed";
+ }
+
+
+
+
+
+
+
+
+
+ container statistics {
+ description
+ "A collection of interface-related statistics objects.";
+
+ leaf discontinuity-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "The time on the most recent occasion at which any one or
+ more of this interface's counters suffered a
+ discontinuity. If no such discontinuities have occurred
+ since the last re-initialization of the local management
+ subsystem, then this node contains the time the local
+ management subsystem re-initialized itself.";
+ }
+
+ leaf in-octets {
+ type yang:counter64;
+ description
+ "The total number of octets received on the interface,
+ including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInOctets";
+ }
+
+ leaf in-unicast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were not addressed to a
+ multicast or broadcast address at this sub-layer.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts";
+ }
+
+
+
+
+ leaf in-broadcast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a broadcast
+ address at this sub-layer.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInBroadcastPkts";
+ }
+
+ leaf in-multicast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a multicast
+ address at this sub-layer. For a MAC-layer protocol,
+ this includes both Group and Functional addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInMulticastPkts";
+ }
+
+ leaf in-discards {
+ type yang:counter32;
+ description
+ "The number of inbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being deliverable to a higher-layer
+ protocol. One possible reason for discarding such a
+ packet could be to free up buffer space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+
+
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInDiscards";
+ }
+
+ leaf in-errors {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of inbound
+ packets that contained errors preventing them from being
+ deliverable to a higher-layer protocol. For character-
+ oriented or fixed-length interfaces, the number of
+ inbound transmission units that contained errors
+ preventing them from being deliverable to a higher-layer
+ protocol.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInErrors";
+ }
+
+ leaf in-unknown-protos {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of packets
+ received via the interface that were discarded because
+ of an unknown or unsupported protocol. For
+ character-oriented or fixed-length interfaces that
+ support protocol multiplexing, the number of
+ transmission units received via the interface that were
+ discarded because of an unknown or unsupported protocol.
+ For any interface that does not support protocol
+ multiplexing, this counter is not present.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos";
+ }
+
+
+
+
+
+ leaf out-octets {
+ type yang:counter64;
+ description
+ "The total number of octets transmitted out of the
+ interface, including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutOctets";
+ }
+
+ leaf out-unicast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted, and that were not addressed
+ to a multicast or broadcast address at this sub-layer,
+ including those that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts";
+ }
+
+ leaf out-broadcast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted, and that were addressed to a
+ broadcast address at this sub-layer, including those
+ that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutBroadcastPkts";
+ }
+
+
+ leaf out-multicast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted, and that were addressed to a
+ multicast address at this sub-layer, including those
+ that were discarded or not sent. For a MAC-layer
+ protocol, this includes both Group and Functional
+ addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutMulticastPkts";
+ }
+
+ leaf out-discards {
+ type yang:counter32;
+ description
+ "The number of outbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being transmitted. One possible reason
+ for discarding such a packet could be to free up buffer
+ space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutDiscards";
+ }
+
+ leaf out-errors {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of outbound
+ packets that could not be transmitted because of errors.
+ For character-oriented or fixed-length interfaces, the
+ number of outbound transmission units that could not be
+ transmitted because of errors.
+
+
+
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutErrors";
+ }
+ }
+ }
+ }
+}
diff --git a/tests/yanglint/modules/ietf-ip.yang b/tests/yanglint/modules/ietf-ip.yang
new file mode 100644
index 0000000..1499120
--- /dev/null
+++ b/tests/yanglint/modules/ietf-ip.yang
@@ -0,0 +1,758 @@
+module ietf-ip {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-ip";
+ prefix ip;
+
+ import ietf-interfaces {
+ prefix if;
+ }
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ organization
+ "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netmod/>
+ WG List: <mailto:netmod@ietf.org>
+
+ WG Chair: Thomas Nadeau
+ <mailto:tnadeau@lucidvision.com>
+
+ WG Chair: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>";
+
+
+
+
+
+
+
+
+
+
+ description
+ "This module contains a collection of YANG definitions for
+ configuring IP implementations.
+
+ Copyright (c) 2014 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 7277; see
+ the RFC itself for full legal notices.";
+
+ revision 2014-06-16 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 7277: A YANG Data Model for IP Management";
+ }
+
+ /*
+
+ * Features
+ */
+
+ feature ipv4-non-contiguous-netmasks {
+ description
+ "Indicates support for configuring non-contiguous
+ subnet masks.";
+ }
+
+ feature ipv6-privacy-autoconf {
+ description
+ "Indicates support for Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6";
+ }
+
+
+
+
+
+ /*
+ * Typedefs
+ */
+
+ typedef ip-address-origin {
+ type enumeration {
+ enum other {
+ description
+ "None of the following.";
+ }
+ enum static {
+ description
+ "Indicates that the address has been statically
+ configured - for example, using NETCONF or a Command Line
+ Interface.";
+ }
+ enum dhcp {
+ description
+ "Indicates an address that has been assigned to this
+ system by a DHCP server.";
+ }
+ enum link-layer {
+ description
+ "Indicates an address created by IPv6 stateless
+ autoconfiguration that embeds a link-layer address in its
+ interface identifier.";
+ }
+ enum random {
+ description
+ "Indicates an address chosen by the system at
+
+ random, e.g., an IPv4 address within 169.254/16, an
+ RFC 4941 temporary address, or an RFC 7217 semantically
+ opaque address.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6
+ RFC 7217: A Method for Generating Semantically Opaque
+ Interface Identifiers with IPv6 Stateless
+ Address Autoconfiguration (SLAAC)";
+ }
+ }
+ description
+ "The origin of an address.";
+ }
+
+
+
+ typedef neighbor-origin {
+ type enumeration {
+ enum other {
+ description
+ "None of the following.";
+ }
+ enum static {
+ description
+ "Indicates that the mapping has been statically
+ configured - for example, using NETCONF or a Command Line
+ Interface.";
+ }
+ enum dynamic {
+ description
+ "Indicates that the mapping has been dynamically resolved
+ using, e.g., IPv4 ARP or the IPv6 Neighbor Discovery
+ protocol.";
+ }
+ }
+ description
+ "The origin of a neighbor entry.";
+ }
+
+ /*
+ * Configuration data nodes
+ */
+
+ augment "/if:interfaces/if:interface" {
+ description
+ "Parameters for configuring IP on interfaces.
+
+ If an interface is not capable of running IP, the server
+ must not allow the client to configure these parameters.";
+
+ container ipv4 {
+ presence
+ "Enables IPv4 unless the 'enabled' leaf
+ (which defaults to 'true') is set to 'false'";
+ description
+ "Parameters for the IPv4 address family.";
+
+
+
+
+
+
+
+
+ leaf enabled {
+ type boolean;
+ default true;
+ description
+ "Controls whether IPv4 is enabled or disabled on this
+ interface. When IPv4 is enabled, this interface is
+ connected to an IPv4 stack, and the interface can send
+ and receive IPv4 packets.";
+ }
+ leaf forwarding {
+ type boolean;
+ default false;
+ description
+ "Controls IPv4 packet forwarding of datagrams received by,
+ but not addressed to, this interface. IPv4 routers
+ forward datagrams. IPv4 hosts do not (except those
+ source-routed via the host).";
+ }
+ leaf mtu {
+ type uint16 {
+ range "68..max";
+ }
+ units octets;
+ description
+ "The size, in octets, of the largest IPv4 packet that the
+ interface will send and receive.
+
+ The server may restrict the allowed values for this leaf,
+ depending on the interface's type.
+
+ If this leaf is not configured, the operationally used MTU
+ depends on the interface's type.";
+ reference
+ "RFC 791: Internet Protocol";
+ }
+ list address {
+ key "ip";
+ description
+ "The list of configured IPv4 addresses on the interface.";
+
+ leaf ip {
+ type inet:ipv4-address-no-zone;
+ description
+ "The IPv4 address on the interface.";
+ }
+
+
+
+ choice subnet {
+ mandatory true;
+ description
+ "The subnet can be specified as a prefix-length, or,
+ if the server supports non-contiguous netmasks, as
+ a netmask.";
+ leaf prefix-length {
+ type uint8 {
+ range "0..32";
+ }
+ description
+ "The length of the subnet prefix.";
+ }
+ leaf netmask {
+ if-feature ipv4-non-contiguous-netmasks;
+ type yang:dotted-quad;
+ description
+ "The subnet specified as a netmask.";
+ }
+ }
+ }
+ list neighbor {
+ key "ip";
+ description
+ "A list of mappings from IPv4 addresses to
+ link-layer addresses.
+
+ Entries in this list are used as static entries in the
+ ARP Cache.";
+ reference
+ "RFC 826: An Ethernet Address Resolution Protocol";
+
+ leaf ip {
+ type inet:ipv4-address-no-zone;
+ description
+ "The IPv4 address of the neighbor node.";
+ }
+ leaf link-layer-address {
+ type yang:phys-address;
+ mandatory true;
+ description
+ "The link-layer address of the neighbor node.";
+ }
+ }
+
+ }
+
+
+ container ipv6 {
+ presence
+ "Enables IPv6 unless the 'enabled' leaf
+ (which defaults to 'true') is set to 'false'";
+ description
+ "Parameters for the IPv6 address family.";
+
+ leaf enabled {
+ type boolean;
+ default true;
+ description
+ "Controls whether IPv6 is enabled or disabled on this
+ interface. When IPv6 is enabled, this interface is
+ connected to an IPv6 stack, and the interface can send
+ and receive IPv6 packets.";
+ }
+ leaf forwarding {
+ type boolean;
+ default false;
+ description
+ "Controls IPv6 packet forwarding of datagrams received by,
+ but not addressed to, this interface. IPv6 routers
+ forward datagrams. IPv6 hosts do not (except those
+ source-routed via the host).";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)
+ Section 6.2.1, IsRouter";
+ }
+ leaf mtu {
+ type uint32 {
+ range "1280..max";
+ }
+ units octets;
+ description
+ "The size, in octets, of the largest IPv6 packet that the
+ interface will send and receive.
+
+ The server may restrict the allowed values for this leaf,
+ depending on the interface's type.
+
+ If this leaf is not configured, the operationally used MTU
+ depends on the interface's type.";
+ reference
+ "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification
+ Section 5";
+ }
+
+
+ list address {
+ key "ip";
+ description
+ "The list of configured IPv6 addresses on the interface.";
+
+ leaf ip {
+ type inet:ipv6-address-no-zone;
+ description
+ "The IPv6 address on the interface.";
+ }
+ leaf prefix-length {
+ type uint8 {
+ range "0..128";
+ }
+ mandatory true;
+ description
+ "The length of the subnet prefix.";
+ }
+ }
+ list neighbor {
+ key "ip";
+ description
+ "A list of mappings from IPv6 addresses to
+ link-layer addresses.
+
+ Entries in this list are used as static entries in the
+ Neighbor Cache.";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)";
+
+ leaf ip {
+ type inet:ipv6-address-no-zone;
+ description
+ "The IPv6 address of the neighbor node.";
+ }
+ leaf link-layer-address {
+ type yang:phys-address;
+ mandatory true;
+ description
+ "The link-layer address of the neighbor node.";
+ }
+ }
+
+
+
+
+
+
+ leaf dup-addr-detect-transmits {
+ type uint32;
+ default 1;
+ description
+ "The number of consecutive Neighbor Solicitation messages
+ sent while performing Duplicate Address Detection on a
+ tentative address. A value of zero indicates that
+ Duplicate Address Detection is not performed on
+ tentative addresses. A value of one indicates a single
+ transmission with no follow-up retransmissions.";
+ reference
+ "RFC 4862: IPv6 Stateless Address Autoconfiguration";
+ }
+ container autoconf {
+ description
+ "Parameters to control the autoconfiguration of IPv6
+ addresses, as described in RFC 4862.";
+ reference
+ "RFC 4862: IPv6 Stateless Address Autoconfiguration";
+
+ leaf create-global-addresses {
+ type boolean;
+ default true;
+ description
+ "If enabled, the host creates global addresses as
+ described in RFC 4862.";
+ reference
+ "RFC 4862: IPv6 Stateless Address Autoconfiguration
+ Section 5.5";
+ }
+ leaf create-temporary-addresses {
+ if-feature ipv6-privacy-autoconf;
+ type boolean;
+ default false;
+ description
+ "If enabled, the host creates temporary addresses as
+ described in RFC 4941.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6";
+ }
+
+
+
+
+
+
+
+ leaf temporary-valid-lifetime {
+ if-feature ipv6-privacy-autoconf;
+ type uint32;
+ units "seconds";
+ default 604800;
+ description
+ "The time period during which the temporary address
+ is valid.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6
+ - TEMP_VALID_LIFETIME";
+ }
+ leaf temporary-preferred-lifetime {
+ if-feature ipv6-privacy-autoconf;
+ type uint32;
+ units "seconds";
+ default 86400;
+ description
+ "The time period during which the temporary address is
+ preferred.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6
+ - TEMP_PREFERRED_LIFETIME";
+ }
+ }
+ }
+ }
+
+ /*
+ * Operational state data nodes
+ */
+
+ augment "/if:interfaces-state/if:interface" {
+ description
+ "Data nodes for the operational state of IP on interfaces.";
+
+ container ipv4 {
+ presence "Present if IPv4 is enabled on this interface";
+ config false;
+ description
+ "Interface-specific parameters for the IPv4 address family.";
+
+
+
+
+
+ leaf forwarding {
+ type boolean;
+ description
+ "Indicates whether IPv4 packet forwarding is enabled or
+ disabled on this interface.";
+ }
+ leaf mtu {
+ type uint16 {
+ range "68..max";
+ }
+ units octets;
+ description
+ "The size, in octets, of the largest IPv4 packet that the
+ interface will send and receive.";
+ reference
+ "RFC 791: Internet Protocol";
+ }
+ list address {
+ key "ip";
+ description
+ "The list of IPv4 addresses on the interface.";
+
+ leaf ip {
+ type inet:ipv4-address-no-zone;
+ description
+ "The IPv4 address on the interface.";
+ }
+ choice subnet {
+ description
+ "The subnet can be specified as a prefix-length, or,
+ if the server supports non-contiguous netmasks, as
+ a netmask.";
+ leaf prefix-length {
+ type uint8 {
+ range "0..32";
+ }
+ description
+ "The length of the subnet prefix.";
+ }
+ leaf netmask {
+ if-feature ipv4-non-contiguous-netmasks;
+ type yang:dotted-quad;
+ description
+ "The subnet specified as a netmask.";
+ }
+ }
+
+
+ leaf origin {
+ type ip-address-origin;
+ description
+ "The origin of this address.";
+ }
+ }
+ list neighbor {
+ key "ip";
+ description
+ "A list of mappings from IPv4 addresses to
+ link-layer addresses.
+
+ This list represents the ARP Cache.";
+ reference
+ "RFC 826: An Ethernet Address Resolution Protocol";
+
+ leaf ip {
+ type inet:ipv4-address-no-zone;
+ description
+ "The IPv4 address of the neighbor node.";
+ }
+ leaf link-layer-address {
+ type yang:phys-address;
+ description
+ "The link-layer address of the neighbor node.";
+ }
+ leaf origin {
+ type neighbor-origin;
+ description
+ "The origin of this neighbor entry.";
+ }
+ }
+
+ }
+
+ container ipv6 {
+ presence "Present if IPv6 is enabled on this interface";
+ config false;
+ description
+ "Parameters for the IPv6 address family.";
+
+
+
+
+
+
+
+
+ leaf forwarding {
+ type boolean;
+ default false;
+ description
+ "Indicates whether IPv6 packet forwarding is enabled or
+ disabled on this interface.";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)
+ Section 6.2.1, IsRouter";
+ }
+ leaf mtu {
+ type uint32 {
+ range "1280..max";
+ }
+ units octets;
+ description
+ "The size, in octets, of the largest IPv6 packet that the
+ interface will send and receive.";
+ reference
+ "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification
+ Section 5";
+ }
+ list address {
+ key "ip";
+ description
+ "The list of IPv6 addresses on the interface.";
+
+ leaf ip {
+ type inet:ipv6-address-no-zone;
+ description
+ "The IPv6 address on the interface.";
+ }
+ leaf prefix-length {
+ type uint8 {
+ range "0..128";
+ }
+ mandatory true;
+ description
+ "The length of the subnet prefix.";
+ }
+ leaf origin {
+ type ip-address-origin;
+ description
+ "The origin of this address.";
+ }
+
+
+
+ leaf status {
+ type enumeration {
+ enum preferred {
+ description
+ "This is a valid address that can appear as the
+ destination or source address of a packet.";
+ }
+ enum deprecated {
+ description
+ "This is a valid but deprecated address that should
+ no longer be used as a source address in new
+ communications, but packets addressed to such an
+ address are processed as expected.";
+ }
+ enum invalid {
+ description
+ "This isn't a valid address, and it shouldn't appear
+ as the destination or source address of a packet.";
+ }
+ enum inaccessible {
+ description
+ "The address is not accessible because the interface
+ to which this address is assigned is not
+ operational.";
+ }
+ enum unknown {
+ description
+ "The status cannot be determined for some reason.";
+ }
+ enum tentative {
+ description
+ "The uniqueness of the address on the link is being
+ verified. Addresses in this state should not be
+ used for general communication and should only be
+ used to determine the uniqueness of the address.";
+ }
+ enum duplicate {
+ description
+ "The address has been determined to be non-unique on
+ the link and so must not be used.";
+ }
+
+
+
+
+
+
+
+ enum optimistic {
+ description
+ "The address is available for use, subject to
+ restrictions, while its uniqueness on a link is
+ being verified.";
+ }
+ }
+ description
+ "The status of an address. Most of the states correspond
+ to states from the IPv6 Stateless Address
+ Autoconfiguration protocol.";
+ reference
+ "RFC 4293: Management Information Base for the
+ Internet Protocol (IP)
+ - IpAddressStatusTC
+ RFC 4862: IPv6 Stateless Address Autoconfiguration";
+ }
+ }
+ list neighbor {
+ key "ip";
+ description
+ "A list of mappings from IPv6 addresses to
+ link-layer addresses.
+
+ This list represents the Neighbor Cache.";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)";
+
+ leaf ip {
+ type inet:ipv6-address-no-zone;
+ description
+ "The IPv6 address of the neighbor node.";
+ }
+ leaf link-layer-address {
+ type yang:phys-address;
+ description
+ "The link-layer address of the neighbor node.";
+ }
+ leaf origin {
+ type neighbor-origin;
+ description
+ "The origin of this neighbor entry.";
+ }
+ leaf is-router {
+ type empty;
+ description
+ "Indicates that the neighbor node acts as a router.";
+ }
+ leaf state {
+ type enumeration {
+ enum incomplete {
+ description
+ "Address resolution is in progress, and the link-layer
+ address of the neighbor has not yet been
+ determined.";
+ }
+ enum reachable {
+ description
+ "Roughly speaking, the neighbor is known to have been
+ reachable recently (within tens of seconds ago).";
+ }
+ enum stale {
+ description
+ "The neighbor is no longer known to be reachable, but
+ until traffic is sent to the neighbor no attempt
+ should be made to verify its reachability.";
+ }
+ enum delay {
+ description
+ "The neighbor is no longer known to be reachable, and
+ traffic has recently been sent to the neighbor.
+ Rather than probe the neighbor immediately, however,
+ delay sending probes for a short while in order to
+ give upper-layer protocols a chance to provide
+ reachability confirmation.";
+ }
+ enum probe {
+ description
+ "The neighbor is no longer known to be reachable, and
+ unicast Neighbor Solicitation probes are being sent
+ to verify reachability.";
+ }
+ }
+ description
+ "The Neighbor Unreachability Detection state of this
+ entry.";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)
+ Section 7.3.2";
+ }
+ }
+ }
+ }
+}
diff --git a/tests/yanglint/modules/ietf-netconf-acm.yang b/tests/yanglint/modules/ietf-netconf-acm.yang
new file mode 100644
index 0000000..d372fa0
--- /dev/null
+++ b/tests/yanglint/modules/ietf-netconf-acm.yang
@@ -0,0 +1,411 @@
+module ietf-netconf-acm {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm";
+ prefix nacm;
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netconf/>
+ WG List: <mailto:netconf@ietf.org>
+
+ WG Chair: Mehmet Ersue
+ <mailto:mehmet.ersue@nsn.com>
+
+ WG Chair: Bert Wijnen
+ <mailto:bertietf@bwijnen.net>
+
+ Editor: Andy Bierman
+ <mailto:andy@yumaworks.com>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>";
+ description
+ "NETCONF Access Control Model.
+
+ Copyright (c) 2012 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD
+ License set forth in Section 4.c of the IETF Trust's
+ Legal Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6536; see
+ the RFC itself for full legal notices.";
+
+ revision 2012-02-22 {
+ description
+ "Initial version";
+ reference
+ "RFC 6536: Network Configuration Protocol (NETCONF)
+ Access Control Model";
+ }
+
+ extension default-deny-write {
+ description
+ "Used to indicate that the data model node
+ represents a sensitive security system parameter.
+
+ If present, and the NACM module is enabled (i.e.,
+ /nacm/enable-nacm object equals 'true'), the NETCONF server
+ will only allow the designated 'recovery session' to have
+ write access to the node. An explicit access control rule is
+ required for all other users.
+
+ The 'default-deny-write' extension MAY appear within a data
+ definition statement. It is ignored otherwise.";
+ }
+
+ extension default-deny-all {
+ description
+ "Used to indicate that the data model node
+ controls a very sensitive security system parameter.
+
+ If present, and the NACM module is enabled (i.e.,
+ /nacm/enable-nacm object equals 'true'), the NETCONF server
+ will only allow the designated 'recovery session' to have
+ read, write, or execute access to the node. An explicit
+ access control rule is required for all other users.
+
+ The 'default-deny-all' extension MAY appear within a data
+ definition statement, 'rpc' statement, or 'notification'
+ statement. It is ignored otherwise.";
+ }
+
+ typedef user-name-type {
+ type string {
+ length "1..max";
+ }
+ description
+ "General Purpose Username string.";
+ }
+
+ typedef matchall-string-type {
+ type string {
+ pattern "\\*";
+ }
+ description
+ "The string containing a single asterisk '*' is used
+ to conceptually represent all possible values
+ for the particular leaf using this data type.";
+ }
+
+ typedef access-operations-type {
+ type bits {
+ bit create {
+ description
+ "Any protocol operation that creates a
+ new data node.";
+ }
+ bit read {
+ description
+ "Any protocol operation or notification that
+ returns the value of a data node.";
+ }
+ bit update {
+ description
+ "Any protocol operation that alters an existing
+ data node.";
+ }
+ bit delete {
+ description
+ "Any protocol operation that removes a data node.";
+ }
+ bit exec {
+ description
+ "Execution access to the specified protocol operation.";
+ }
+ }
+ description
+ "NETCONF Access Operation.";
+ }
+
+ typedef group-name-type {
+ type string {
+ length "1..max";
+ pattern "[^\\*].*";
+ }
+ description
+ "Name of administrative group to which
+ users can be assigned.";
+ }
+
+ typedef action-type {
+ type enumeration {
+ enum "permit" {
+ description
+ "Requested action is permitted.";
+ }
+ enum "deny" {
+ description
+ "Requested action is denied.";
+ }
+ }
+ description
+ "Action taken by the server when a particular
+ rule matches.";
+ }
+
+ typedef node-instance-identifier {
+ type yang:xpath1.0;
+ description
+ "Path expression used to represent a special
+ data node instance identifier string.
+
+ A node-instance-identifier value is an
+ unrestricted YANG instance-identifier expression.
+ All the same rules as an instance-identifier apply
+ except predicates for keys are optional. If a key
+ predicate is missing, then the node-instance-identifier
+ represents all possible server instances for that key.
+
+ This XPath expression is evaluated in the following context:
+
+ o The set of namespace declarations are those in scope on
+ the leaf element where this type is used.
+
+ o The set of variable bindings contains one variable,
+ 'USER', which contains the name of the user of the current
+ session.
+
+ o The function library is the core function library, but
+ note that due to the syntax restrictions of an
+ instance-identifier, no functions are allowed.
+
+ o The context node is the root node in the data tree.";
+ }
+
+ container nacm {
+ nacm:default-deny-all;
+ description
+ "Parameters for NETCONF Access Control Model.";
+ leaf enable-nacm {
+ type boolean;
+ default "true";
+ description
+ "Enables or disables all NETCONF access control
+ enforcement. If 'true', then enforcement
+ is enabled. If 'false', then enforcement
+ is disabled.";
+ }
+ leaf read-default {
+ type action-type;
+ default "permit";
+ description
+ "Controls whether read access is granted if
+ no appropriate rule is found for a
+ particular read request.";
+ }
+ leaf write-default {
+ type action-type;
+ default "deny";
+ description
+ "Controls whether create, update, or delete access
+ is granted if no appropriate rule is found for a
+ particular write request.";
+ }
+ leaf exec-default {
+ type action-type;
+ default "permit";
+ description
+ "Controls whether exec access is granted if no appropriate
+ rule is found for a particular protocol operation request.";
+ }
+ leaf enable-external-groups {
+ type boolean;
+ default "true";
+ description
+ "Controls whether the server uses the groups reported by the
+ NETCONF transport layer when it assigns the user to a set of
+ NACM groups. If this leaf has the value 'false', any group
+ names reported by the transport layer are ignored by the
+ server.";
+ }
+ leaf denied-operations {
+ type yang:zero-based-counter32;
+ config false;
+ mandatory true;
+ description
+ "Number of times since the server last restarted that a
+ protocol operation request was denied.";
+ }
+ leaf denied-data-writes {
+ type yang:zero-based-counter32;
+ config false;
+ mandatory true;
+ description
+ "Number of times since the server last restarted that a
+ protocol operation request to alter
+ a configuration datastore was denied.";
+ }
+ leaf denied-notifications {
+ type yang:zero-based-counter32;
+ config false;
+ mandatory true;
+ description
+ "Number of times since the server last restarted that
+ a notification was dropped for a subscription because
+ access to the event type was denied.";
+ }
+ container groups {
+ description
+ "NETCONF Access Control Groups.";
+ list group {
+ key "name";
+ description
+ "One NACM Group Entry. This list will only contain
+ configured entries, not any entries learned from
+ any transport protocols.";
+ leaf name {
+ type group-name-type;
+ description
+ "Group name associated with this entry.";
+ }
+ leaf-list user-name {
+ type user-name-type;
+ description
+ "Each entry identifies the username of
+ a member of the group associated with
+ this entry.";
+ }
+ }
+ }
+ list rule-list {
+ key "name";
+ ordered-by user;
+ description
+ "An ordered collection of access control rules.";
+ leaf name {
+ type string {
+ length "1..max";
+ }
+ description
+ "Arbitrary name assigned to the rule-list.";
+ }
+ leaf-list group {
+ type union {
+ type matchall-string-type;
+ type group-name-type;
+ }
+ description
+ "List of administrative groups that will be
+ assigned the associated access rights
+ defined by the 'rule' list.
+
+ The string '*' indicates that all groups apply to the
+ entry.";
+ }
+ list rule {
+ key "name";
+ ordered-by user;
+ description
+ "One access control rule.
+
+ Rules are processed in user-defined order until a match is
+ found. A rule matches if 'module-name', 'rule-type', and
+ 'access-operations' match the request. If a rule
+ matches, the 'action' leaf determines if access is granted
+ or not.";
+ leaf name {
+ type string {
+ length "1..max";
+ }
+ description
+ "Arbitrary name assigned to the rule.";
+ }
+ leaf module-name {
+ type union {
+ type matchall-string-type;
+ type string;
+ }
+ default "*";
+ description
+ "Name of the module associated with this rule.
+
+ This leaf matches if it has the value '*' or if the
+ object being accessed is defined in the module with the
+ specified module name.";
+ }
+ choice rule-type {
+ description
+ "This choice matches if all leafs present in the rule
+ match the request. If no leafs are present, the
+ choice matches all requests.";
+ case protocol-operation {
+ leaf rpc-name {
+ type union {
+ type matchall-string-type;
+ type string;
+ }
+ description
+ "This leaf matches if it has the value '*' or if
+ its value equals the requested protocol operation
+ name.";
+ }
+ }
+ case notification {
+ leaf notification-name {
+ type union {
+ type matchall-string-type;
+ type string;
+ }
+ description
+ "This leaf matches if it has the value '*' or if its
+ value equals the requested notification name.";
+ }
+ }
+ case data-node {
+ leaf path {
+ type node-instance-identifier;
+ mandatory true;
+ description
+ "Data Node Instance Identifier associated with the
+ data node controlled by this rule.
+
+ Configuration data or state data instance
+ identifiers start with a top-level data node. A
+ complete instance identifier is required for this
+ type of path value.
+
+ The special value '/' refers to all possible
+ datastore contents.";
+ }
+ }
+ }
+ leaf access-operations {
+ type union {
+ type matchall-string-type;
+ type access-operations-type;
+ }
+ default "*";
+ description
+ "Access operations associated with this rule.
+
+ This leaf matches if it has the value '*' or if the
+ bit corresponding to the requested operation is set.";
+ }
+ leaf action {
+ type action-type;
+ mandatory true;
+ description
+ "The access control action associated with the
+ rule. If a rule is determined to match a
+ particular request, then this object is used
+ to determine whether to permit or deny the
+ request.";
+ }
+ leaf comment {
+ type string;
+ description
+ "A textual description of the access rule.";
+ }
+ }
+ }
+ }
+}
diff --git a/tests/yanglint/modules/ietf-netconf-with-defaults@2011-06-01.yang b/tests/yanglint/modules/ietf-netconf-with-defaults@2011-06-01.yang
new file mode 100644
index 0000000..e19d2b3
--- /dev/null
+++ b/tests/yanglint/modules/ietf-netconf-with-defaults@2011-06-01.yang
@@ -0,0 +1,140 @@
+module ietf-netconf-with-defaults {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults";
+
+ prefix ncwd;
+
+ import ietf-netconf { prefix nc; }
+
+ organization
+ "IETF NETCONF (Network Configuration Protocol) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netconf/>
+
+ WG List: <netconf@ietf.org>
+
+ WG Chair: Bert Wijnen
+ <bertietf@bwijnen.net>
+
+ WG Chair: Mehmet Ersue
+ <mehmet.ersue@nsn.com>
+
+ Editor: Andy Bierman
+ <andy.bierman@brocade.com>
+
+ Editor: Balazs Lengyel
+ <balazs.lengyel@ericsson.com>";
+
+ description
+ "This module defines an extension to the NETCONF protocol
+ that allows the NETCONF client to control how default
+ values are handled by the server in particular NETCONF
+ operations.
+
+ Copyright (c) 2011 IETF Trust and the persons identified as
+ the document authors. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6243; see
+ the RFC itself for full legal notices.";
+
+ revision 2011-06-01 {
+ description
+ "Initial version.";
+ reference
+ "RFC 6243: With-defaults Capability for NETCONF";
+ }
+
+ typedef with-defaults-mode {
+ description
+ "Possible modes to report default data.";
+ reference
+ "RFC 6243; Section 3.";
+ type enumeration {
+ enum report-all {
+ description
+ "All default data is reported.";
+ reference
+ "RFC 6243; Section 3.1";
+ }
+ enum report-all-tagged {
+ description
+ "All default data is reported.
+ Any nodes considered to be default data
+ will contain a 'default' XML attribute,
+ set to 'true' or '1'.";
+ reference
+ "RFC 6243; Section 3.4";
+ }
+ enum trim {
+ description
+ "Values are not reported if they contain the default.";
+ reference
+ "RFC 6243; Section 3.2";
+ }
+ enum explicit {
+ description
+ "Report values that contain the definition of
+ explicitly set data.";
+ reference
+ "RFC 6243; Section 3.3";
+ }
+ }
+ }
+
+ grouping with-defaults-parameters {
+ description
+ "Contains the <with-defaults> parameter for control
+ of defaults in NETCONF retrieval operations.";
+
+ leaf with-defaults {
+ description
+ "The explicit defaults processing mode requested.";
+ reference
+ "RFC 6243; Section 4.5.1";
+
+ type with-defaults-mode;
+ }
+ }
+
+ // extending the get-config operation
+ augment /nc:get-config/nc:input {
+ description
+ "Adds the <with-defaults> parameter to the
+ input of the NETCONF <get-config> operation.";
+ reference
+ "RFC 6243; Section 4.5.1";
+
+ uses with-defaults-parameters;
+ }
+
+ // extending the get operation
+ augment /nc:get/nc:input {
+ description
+ "Adds the <with-defaults> parameter to
+ the input of the NETCONF <get> operation.";
+ reference
+ "RFC 6243; Section 4.5.1";
+
+ uses with-defaults-parameters;
+ }
+
+ // extending the copy-config operation
+ augment /nc:copy-config/nc:input {
+ description
+ "Adds the <with-defaults> parameter to
+ the input of the NETCONF <copy-config> operation.";
+ reference
+ "RFC 6243; Section 4.5.1";
+
+ uses with-defaults-parameters;
+ }
+
+}
diff --git a/tests/yanglint/modules/ietf-netconf@2011-06-01.yang b/tests/yanglint/modules/ietf-netconf@2011-06-01.yang
new file mode 100644
index 0000000..3053db2
--- /dev/null
+++ b/tests/yanglint/modules/ietf-netconf@2011-06-01.yang
@@ -0,0 +1,934 @@
+module ietf-netconf {
+
+ // the namespace for NETCONF XML definitions is unchanged
+ // from RFC 4741, which this document replaces
+ namespace "urn:ietf:params:xml:ns:netconf:base:1.0";
+
+ prefix nc;
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+
+ import ietf-netconf-acm { prefix nacm; }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netconf/>
+ WG List: <netconf@ietf.org>
+
+ WG Chair: Bert Wijnen
+ <bertietf@bwijnen.net>
+
+ WG Chair: Mehmet Ersue
+ <mehmet.ersue@nsn.com>
+
+ Editor: Martin Bjorklund
+ <mbj@tail-f.com>
+
+ Editor: Juergen Schoenwaelder
+ <j.schoenwaelder@jacobs-university.de>
+
+ Editor: Andy Bierman
+ <andy.bierman@brocade.com>";
+ description
+ "NETCONF Protocol Data Types and Protocol Operations.
+
+ Copyright (c) 2011 IETF Trust and the persons identified as
+ the document authors. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6241; see
+ the RFC itself for full legal notices.";
+
+ revision 2011-06-01 {
+ description
+ "Initial revision;
+ 2013-09-29: Updated to include NACM attributes,
+ as specified in RFC 6536: sec 3.2.5 and 3.2.8";
+ reference
+ "RFC 6241: Network Configuration Protocol";
+ }
+
+ extension get-filter-element-attributes {
+ description
+ "If this extension is present within an 'anyxml'
+ statement named 'filter', which must be conceptually
+ defined within the RPC input section for the <get>
+ and <get-config> protocol operations, then the
+ following unqualified XML attribute is supported
+ within the <filter> element, within a <get> or
+ <get-config> protocol operation:
+
+ type : optional attribute with allowed
+ value strings 'subtree' and 'xpath'.
+ If missing, the default value is 'subtree'.
+
+ If the 'xpath' feature is supported, then the
+ following unqualified XML attribute is
+ also supported:
+
+ select: optional attribute containing a
+ string representing an XPath expression.
+ The 'type' attribute must be equal to 'xpath'
+ if this attribute is present.";
+ }
+
+ // NETCONF capabilities defined as features
+ feature writable-running {
+ description
+ "NETCONF :writable-running capability;
+ If the server advertises the :writable-running
+ capability for a session, then this feature must
+ also be enabled for that session. Otherwise,
+ this feature must not be enabled.";
+ reference "RFC 6241, Section 8.2";
+ }
+
+ feature candidate {
+ description
+ "NETCONF :candidate capability;
+ If the server advertises the :candidate
+ capability for a session, then this feature must
+ also be enabled for that session. Otherwise,
+ this feature must not be enabled.";
+ reference "RFC 6241, Section 8.3";
+ }
+
+ feature confirmed-commit {
+ if-feature candidate;
+ description
+ "NETCONF :confirmed-commit:1.1 capability;
+ If the server advertises the :confirmed-commit:1.1
+ capability for a session, then this feature must
+ also be enabled for that session. Otherwise,
+ this feature must not be enabled.";
+
+ reference "RFC 6241, Section 8.4";
+ }
+
+ feature rollback-on-error {
+ description
+ "NETCONF :rollback-on-error capability;
+ If the server advertises the :rollback-on-error
+ capability for a session, then this feature must
+ also be enabled for that session. Otherwise,
+ this feature must not be enabled.";
+ reference "RFC 6241, Section 8.5";
+ }
+
+ feature validate {
+ description
+ "NETCONF :validate:1.1 capability;
+ If the server advertises the :validate:1.1
+ capability for a session, then this feature must
+ also be enabled for that session. Otherwise,
+ this feature must not be enabled.";
+ reference "RFC 6241, Section 8.6";
+ }
+
+ feature startup {
+ description
+ "NETCONF :startup capability;
+ If the server advertises the :startup
+ capability for a session, then this feature must
+ also be enabled for that session. Otherwise,
+ this feature must not be enabled.";
+ reference "RFC 6241, Section 8.7";
+ }
+
+ feature url {
+ description
+ "NETCONF :url capability;
+ If the server advertises the :url
+ capability for a session, then this feature must
+ also be enabled for that session. Otherwise,
+ this feature must not be enabled.";
+ reference "RFC 6241, Section 8.8";
+ }
+
+ feature xpath {
+ description
+ "NETCONF :xpath capability;
+ If the server advertises the :xpath
+ capability for a session, then this feature must
+ also be enabled for that session. Otherwise,
+ this feature must not be enabled.";
+ reference "RFC 6241, Section 8.9";
+ }
+
+ // NETCONF Simple Types
+
+ typedef session-id-type {
+ type uint32 {
+ range "1..max";
+ }
+ description
+ "NETCONF Session Id";
+ }
+
+ typedef session-id-or-zero-type {
+ type uint32;
+ description
+ "NETCONF Session Id or Zero to indicate none";
+ }
+ typedef error-tag-type {
+ type enumeration {
+ enum in-use {
+ description
+ "The request requires a resource that
+ already is in use.";
+ }
+ enum invalid-value {
+ description
+ "The request specifies an unacceptable value for one
+ or more parameters.";
+ }
+ enum too-big {
+ description
+ "The request or response (that would be generated) is
+ too large for the implementation to handle.";
+ }
+ enum missing-attribute {
+ description
+ "An expected attribute is missing.";
+ }
+ enum bad-attribute {
+ description
+ "An attribute value is not correct; e.g., wrong type,
+ out of range, pattern mismatch.";
+ }
+ enum unknown-attribute {
+ description
+ "An unexpected attribute is present.";
+ }
+ enum missing-element {
+ description
+ "An expected element is missing.";
+ }
+ enum bad-element {
+ description
+ "An element value is not correct; e.g., wrong type,
+ out of range, pattern mismatch.";
+ }
+ enum unknown-element {
+ description
+ "An unexpected element is present.";
+ }
+ enum unknown-namespace {
+ description
+ "An unexpected namespace is present.";
+ }
+ enum access-denied {
+ description
+ "Access to the requested protocol operation or
+ data model is denied because authorization failed.";
+ }
+ enum lock-denied {
+ description
+ "Access to the requested lock is denied because the
+ lock is currently held by another entity.";
+ }
+ enum resource-denied {
+ description
+ "Request could not be completed because of
+ insufficient resources.";
+ }
+ enum rollback-failed {
+ description
+ "Request to roll back some configuration change (via
+ rollback-on-error or <discard-changes> operations)
+ was not completed for some reason.";
+
+ }
+ enum data-exists {
+ description
+ "Request could not be completed because the relevant
+ data model content already exists. For example,
+ a 'create' operation was attempted on data that
+ already exists.";
+ }
+ enum data-missing {
+ description
+ "Request could not be completed because the relevant
+ data model content does not exist. For example,
+ a 'delete' operation was attempted on
+ data that does not exist.";
+ }
+ enum operation-not-supported {
+ description
+ "Request could not be completed because the requested
+ operation is not supported by this implementation.";
+ }
+ enum operation-failed {
+ description
+ "Request could not be completed because the requested
+ operation failed for some reason not covered by
+ any other error condition.";
+ }
+ enum partial-operation {
+ description
+ "This error-tag is obsolete, and SHOULD NOT be sent
+ by servers conforming to this document.";
+ }
+ enum malformed-message {
+ description
+ "A message could not be handled because it failed to
+ be parsed correctly. For example, the message is not
+ well-formed XML or it uses an invalid character set.";
+ }
+ }
+ description "NETCONF Error Tag";
+ reference "RFC 6241, Appendix A";
+ }
+
+ typedef error-severity-type {
+ type enumeration {
+ enum error {
+ description "Error severity";
+ }
+ enum warning {
+ description "Warning severity";
+ }
+ }
+ description "NETCONF Error Severity";
+ reference "RFC 6241, Section 4.3";
+ }
+
+ typedef edit-operation-type {
+ type enumeration {
+ enum merge {
+ description
+ "The configuration data identified by the
+ element containing this attribute is merged
+ with the configuration at the corresponding
+ level in the configuration datastore identified
+ by the target parameter.";
+ }
+ enum replace {
+ description
+ "The configuration data identified by the element
+ containing this attribute replaces any related
+ configuration in the configuration datastore
+ identified by the target parameter. If no such
+ configuration data exists in the configuration
+ datastore, it is created. Unlike a
+ <copy-config> operation, which replaces the
+ entire target configuration, only the configuration
+ actually present in the config parameter is affected.";
+ }
+ enum create {
+ description
+ "The configuration data identified by the element
+ containing this attribute is added to the
+ configuration if and only if the configuration
+ data does not already exist in the configuration
+ datastore. If the configuration data exists, an
+ <rpc-error> element is returned with an
+ <error-tag> value of 'data-exists'.";
+ }
+ enum delete {
+ description
+ "The configuration data identified by the element
+ containing this attribute is deleted from the
+ configuration if and only if the configuration
+ data currently exists in the configuration
+ datastore. If the configuration data does not
+ exist, an <rpc-error> element is returned with
+ an <error-tag> value of 'data-missing'.";
+ }
+ enum remove {
+ description
+ "The configuration data identified by the element
+ containing this attribute is deleted from the
+ configuration if the configuration
+ data currently exists in the configuration
+ datastore. If the configuration data does not
+ exist, the 'remove' operation is silently ignored
+ by the server.";
+ }
+ }
+ default "merge";
+ description "NETCONF 'operation' attribute values";
+ reference "RFC 6241, Section 7.2";
+ }
+
+ // NETCONF Standard Protocol Operations
+
+ rpc get-config {
+ description
+ "Retrieve all or part of a specified configuration.";
+
+ reference "RFC 6241, Section 7.1";
+
+ input {
+ container source {
+ description
+ "Particular configuration to retrieve.";
+
+ choice config-source {
+ mandatory true;
+ description
+ "The configuration to retrieve.";
+ leaf candidate {
+ if-feature candidate;
+ type empty;
+ description
+ "The candidate configuration is the config source.";
+ }
+ leaf running {
+ type empty;
+ description
+ "The running configuration is the config source.";
+ }
+ leaf startup {
+ if-feature startup;
+ type empty;
+ description
+ "The startup configuration is the config source.
+ This is optional-to-implement on the server because
+ not all servers will support filtering for this
+ datastore.";
+ }
+ }
+ }
+
+ anyxml filter {
+ description
+ "Subtree or XPath filter to use.";
+ nc:get-filter-element-attributes;
+ }
+ }
+
+ output {
+ anyxml data {
+ description
+ "Copy of the source datastore subset that matched
+ the filter criteria (if any). An empty data container
+ indicates that the request did not produce any results.";
+ }
+ }
+ }
+
+ rpc edit-config {
+ description
+ "The <edit-config> operation loads all or part of a specified
+ configuration to the specified target configuration.";
+
+ reference "RFC 6241, Section 7.2";
+
+ input {
+ container target {
+ description
+ "Particular configuration to edit.";
+
+ choice config-target {
+ mandatory true;
+ description
+ "The configuration target.";
+
+ leaf candidate {
+ if-feature candidate;
+ type empty;
+ description
+ "The candidate configuration is the config target.";
+ }
+ leaf running {
+ if-feature writable-running;
+ type empty;
+ description
+ "The running configuration is the config source.";
+ }
+ }
+ }
+
+ leaf default-operation {
+ type enumeration {
+ enum merge {
+ description
+ "The default operation is merge.";
+ }
+ enum replace {
+ description
+ "The default operation is replace.";
+ }
+ enum none {
+ description
+ "There is no default operation.";
+ }
+ }
+ default "merge";
+ description
+ "The default operation to use.";
+ }
+
+ leaf test-option {
+ if-feature validate;
+ type enumeration {
+ enum test-then-set {
+ description
+ "The server will test and then set if no errors.";
+ }
+ enum set {
+ description
+ "The server will set without a test first.";
+ }
+
+ enum test-only {
+ description
+ "The server will only test and not set, even
+ if there are no errors.";
+ }
+ }
+ default "test-then-set";
+ description
+ "The test option to use.";
+ }
+
+ leaf error-option {
+ type enumeration {
+ enum stop-on-error {
+ description
+ "The server will stop on errors.";
+ }
+ enum continue-on-error {
+ description
+ "The server may continue on errors.";
+ }
+ enum rollback-on-error {
+ description
+ "The server will roll back on errors.
+ This value can only be used if the 'rollback-on-error'
+ feature is supported.";
+ }
+ }
+ default "stop-on-error";
+ description
+ "The error option to use.";
+ }
+
+ choice edit-content {
+ mandatory true;
+ description
+ "The content for the edit operation.";
+
+ anyxml config {
+ description
+ "Inline Config content.";
+ }
+ leaf url {
+ if-feature url;
+ type inet:uri;
+ description
+ "URL-based config content.";
+ }
+ }
+ }
+ }
+
+ rpc copy-config {
+ description
+ "Create or replace an entire configuration datastore with the
+ contents of another complete configuration datastore.";
+
+ reference "RFC 6241, Section 7.3";
+
+ input {
+ container target {
+ description
+ "Particular configuration to copy to.";
+
+ choice config-target {
+ mandatory true;
+ description
+ "The configuration target of the copy operation.";
+
+ leaf candidate {
+ if-feature candidate;
+ type empty;
+ description
+ "The candidate configuration is the config target.";
+ }
+ leaf running {
+ if-feature writable-running;
+ type empty;
+ description
+ "The running configuration is the config target.
+ This is optional-to-implement on the server.";
+ }
+ leaf startup {
+ if-feature startup;
+ type empty;
+ description
+ "The startup configuration is the config target.";
+ }
+ leaf url {
+ if-feature url;
+ type inet:uri;
+ description
+ "The URL-based configuration is the config target.";
+ }
+ }
+ }
+
+ container source {
+ description
+ "Particular configuration to copy from.";
+
+ choice config-source {
+ mandatory true;
+ description
+ "The configuration source for the copy operation.";
+
+ leaf candidate {
+ if-feature candidate;
+ type empty;
+ description
+ "The candidate configuration is the config source.";
+ }
+ leaf running {
+ type empty;
+ description
+ "The running configuration is the config source.";
+ }
+ leaf startup {
+ if-feature startup;
+ type empty;
+ description
+ "The startup configuration is the config source.";
+ }
+ leaf url {
+ if-feature url;
+ type inet:uri;
+ description
+ "The URL-based configuration is the config source.";
+ }
+ anyxml config {
+ description
+ "Inline Config content: <config> element. Represents
+ an entire configuration datastore, not
+ a subset of the running datastore.";
+ }
+ }
+ }
+ }
+ }
+
+ rpc delete-config {
+ nacm:default-deny-all;
+ description
+ "Delete a configuration datastore.";
+
+ reference "RFC 6241, Section 7.4";
+
+ input {
+ container target {
+ description
+ "Particular configuration to delete.";
+
+ choice config-target {
+ mandatory true;
+ description
+ "The configuration target to delete.";
+
+ leaf startup {
+ if-feature startup;
+ type empty;
+ description
+ "The startup configuration is the config target.";
+ }
+ leaf url {
+ if-feature url;
+ type inet:uri;
+ description
+ "The URL-based configuration is the config target.";
+ }
+ }
+ }
+ }
+ }
+
+ rpc lock {
+ description
+ "The lock operation allows the client to lock the configuration
+ system of a device.";
+
+ reference "RFC 6241, Section 7.5";
+
+ input {
+ container target {
+ description
+ "Particular configuration to lock.";
+
+ choice config-target {
+ mandatory true;
+ description
+ "The configuration target to lock.";
+
+ leaf candidate {
+ if-feature candidate;
+ type empty;
+ description
+ "The candidate configuration is the config target.";
+ }
+ leaf running {
+ type empty;
+ description
+ "The running configuration is the config target.";
+ }
+ leaf startup {
+ if-feature startup;
+ type empty;
+ description
+ "The startup configuration is the config target.";
+ }
+ }
+ }
+ }
+ }
+
+ rpc unlock {
+ description
+ "The unlock operation is used to release a configuration lock,
+ previously obtained with the 'lock' operation.";
+
+ reference "RFC 6241, Section 7.6";
+
+ input {
+ container target {
+ description
+ "Particular configuration to unlock.";
+
+ choice config-target {
+ mandatory true;
+ description
+ "The configuration target to unlock.";
+
+ leaf candidate {
+ if-feature candidate;
+ type empty;
+ description
+ "The candidate configuration is the config target.";
+ }
+ leaf running {
+ type empty;
+ description
+ "The running configuration is the config target.";
+ }
+ leaf startup {
+ if-feature startup;
+ type empty;
+ description
+ "The startup configuration is the config target.";
+ }
+ }
+ }
+ }
+ }
+
+ rpc get {
+ description
+ "Retrieve running configuration and device state information.";
+
+ reference "RFC 6241, Section 7.7";
+
+ input {
+ anyxml filter {
+ description
+ "This parameter specifies the portion of the system
+ configuration and state data to retrieve.";
+ nc:get-filter-element-attributes;
+ }
+ }
+
+ output {
+ anyxml data {
+ description
+ "Copy of the running datastore subset and/or state
+ data that matched the filter criteria (if any).
+ An empty data container indicates that the request did not
+ produce any results.";
+ }
+ }
+ }
+
+ rpc close-session {
+ description
+ "Request graceful termination of a NETCONF session.";
+
+ reference "RFC 6241, Section 7.8";
+ }
+
+ rpc kill-session {
+ nacm:default-deny-all;
+ description
+ "Force the termination of a NETCONF session.";
+
+ reference "RFC 6241, Section 7.9";
+
+ input {
+ leaf session-id {
+ type session-id-type;
+ mandatory true;
+ description
+ "Particular session to kill.";
+ }
+ }
+ }
+
+ rpc commit {
+ if-feature candidate;
+
+ description
+ "Commit the candidate configuration as the device's new
+ current configuration.";
+
+ reference "RFC 6241, Section 8.3.4.1";
+
+ input {
+ leaf confirmed {
+ if-feature confirmed-commit;
+ type empty;
+ description
+ "Requests a confirmed commit.";
+ reference "RFC 6241, Section 8.3.4.1";
+ }
+
+ leaf confirm-timeout {
+ if-feature confirmed-commit;
+ type uint32 {
+ range "1..max";
+ }
+ units "seconds";
+ default "600"; // 10 minutes
+ description
+ "The timeout interval for a confirmed commit.";
+ reference "RFC 6241, Section 8.3.4.1";
+ }
+
+ leaf persist {
+ if-feature confirmed-commit;
+ type string;
+ description
+ "This parameter is used to make a confirmed commit
+ persistent. A persistent confirmed commit is not aborted
+ if the NETCONF session terminates. The only way to abort
+ a persistent confirmed commit is to let the timer expire,
+ or to use the <cancel-commit> operation.
+
+ The value of this parameter is a token that must be given
+ in the 'persist-id' parameter of <commit> or
+ <cancel-commit> operations in order to confirm or cancel
+ the persistent confirmed commit.
+
+ The token should be a random string.";
+ reference "RFC 6241, Section 8.3.4.1";
+ }
+
+ leaf persist-id {
+ if-feature confirmed-commit;
+ type string;
+ description
+ "This parameter is given in order to commit a persistent
+ confirmed commit. The value must be equal to the value
+ given in the 'persist' parameter to the <commit> operation.
+ If it does not match, the operation fails with an
+ 'invalid-value' error.";
+ reference "RFC 6241, Section 8.3.4.1";
+ }
+
+ }
+ }
+
+ rpc discard-changes {
+ if-feature candidate;
+
+ description
+ "Revert the candidate configuration to the current
+ running configuration.";
+ reference "RFC 6241, Section 8.3.4.2";
+ }
+
+ rpc cancel-commit {
+ if-feature confirmed-commit;
+ description
+ "This operation is used to cancel an ongoing confirmed commit.
+ If the confirmed commit is persistent, the parameter
+ 'persist-id' must be given, and it must match the value of the
+ 'persist' parameter.";
+ reference "RFC 6241, Section 8.4.4.1";
+
+ input {
+ leaf persist-id {
+ type string;
+ description
+ "This parameter is given in order to cancel a persistent
+ confirmed commit. The value must be equal to the value
+ given in the 'persist' parameter to the <commit> operation.
+ If it does not match, the operation fails with an
+ 'invalid-value' error.";
+ }
+ }
+ }
+
+ rpc validate {
+ if-feature validate;
+
+ description
+ "Validates the contents of the specified configuration.";
+
+ reference "RFC 6241, Section 8.6.4.1";
+
+ input {
+ container source {
+ description
+ "Particular configuration to validate.";
+
+ choice config-source {
+ mandatory true;
+ description
+ "The configuration source to validate.";
+
+ leaf candidate {
+ if-feature candidate;
+ type empty;
+ description
+ "The candidate configuration is the config source.";
+ }
+ leaf running {
+ type empty;
+ description
+ "The running configuration is the config source.";
+ }
+ leaf startup {
+ if-feature startup;
+ type empty;
+ description
+ "The startup configuration is the config source.";
+ }
+ leaf url {
+ if-feature url;
+ type inet:uri;
+ description
+ "The URL-based configuration is the config source.";
+ }
+ anyxml config {
+ description
+ "Inline Config content: <config> element. Represents
+ an entire configuration datastore, not
+ a subset of the running datastore.";
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/tests/yanglint/modules/modaction.yang b/tests/yanglint/modules/modaction.yang
new file mode 100644
index 0000000..5a3f92f
--- /dev/null
+++ b/tests/yanglint/modules/modaction.yang
@@ -0,0 +1,26 @@
+module modaction {
+ yang-version 1.1;
+ namespace "urn:yanglint:modaction";
+ prefix ma;
+
+ container con {
+ list ls {
+ key "lfkey";
+ leaf lfkey {
+ type string;
+ }
+ action act {
+ input {
+ leaf lfi {
+ type string;
+ }
+ }
+ output {
+ leaf lfo {
+ type int16;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/yanglint/modules/modconfig-augment.yang b/tests/yanglint/modules/modconfig-augment.yang
new file mode 100644
index 0000000..d94b366
--- /dev/null
+++ b/tests/yanglint/modules/modconfig-augment.yang
@@ -0,0 +1,15 @@
+module modconfig-augment {
+ yang-version 1.1;
+ namespace "urn:yanglint:modconfig-augment";
+ prefix "mca";
+
+ import modconfig {
+ prefix mc;
+ }
+
+ augment "/mc:mcc" {
+ leaf alf {
+ type string;
+ }
+ }
+}
diff --git a/tests/yanglint/modules/modconfig.yang b/tests/yanglint/modules/modconfig.yang
new file mode 100644
index 0000000..1d12ca6
--- /dev/null
+++ b/tests/yanglint/modules/modconfig.yang
@@ -0,0 +1,17 @@
+module modconfig {
+ namespace "urn:yanglint:modconfig";
+ prefix mc;
+
+ container mcc {
+ leaf lft {
+ type string;
+ config true;
+ mandatory true;
+ }
+ leaf lff {
+ type string;
+ config false;
+ mandatory true;
+ }
+ }
+}
diff --git a/tests/yanglint/modules/moddatanodes.yang b/tests/yanglint/modules/moddatanodes.yang
new file mode 100644
index 0000000..ae4ab20
--- /dev/null
+++ b/tests/yanglint/modules/moddatanodes.yang
@@ -0,0 +1,31 @@
+module moddatanodes {
+ yang-version 1.1;
+ namespace "urn:yanglint:moddatanodes";
+ prefix mdn;
+
+ container dnc {
+ leaf lf {
+ type string;
+ }
+ leaf-list lfl {
+ type string;
+ }
+ leaf mis {
+ type string;
+ }
+ container con {
+ list lt {
+ key "kalf kblf";
+ leaf kalf {
+ type string;
+ }
+ leaf kblf {
+ type string;
+ }
+ leaf vlf {
+ type string;
+ }
+ }
+ }
+ }
+}
diff --git a/tests/yanglint/modules/moddefault.yang b/tests/yanglint/modules/moddefault.yang
new file mode 100644
index 0000000..26570c3
--- /dev/null
+++ b/tests/yanglint/modules/moddefault.yang
@@ -0,0 +1,19 @@
+module moddefault {
+ namespace "urn:yanglint:moddefault";
+ prefix md;
+
+ container mdc {
+ leaf lf {
+ type uint16;
+ }
+ leaf di {
+ type int16;
+ default "5";
+ }
+ leaf ds {
+ type string;
+ default "str";
+ }
+ }
+
+}
diff --git a/tests/yanglint/modules/modextleafref.yang b/tests/yanglint/modules/modextleafref.yang
new file mode 100644
index 0000000..d45ec71
--- /dev/null
+++ b/tests/yanglint/modules/modextleafref.yang
@@ -0,0 +1,24 @@
+module modextleafref {
+ namespace "urn:yanglint:modextleafref";
+ prefix mel;
+
+ list ls {
+ key k;
+ leaf k {
+ type string;
+ }
+ leaf lf {
+ type uint8;
+ }
+ }
+ leaf lfr {
+ type leafref {
+ path "../ls/k";
+ }
+ }
+ leaf lfrderef {
+ type leafref {
+ path "deref(../lfr)/../lf";
+ }
+ }
+}
diff --git a/tests/yanglint/modules/modfeature.yang b/tests/yanglint/modules/modfeature.yang
new file mode 100644
index 0000000..f59d4c8
--- /dev/null
+++ b/tests/yanglint/modules/modfeature.yang
@@ -0,0 +1,7 @@
+module modfeature {
+ namespace "urn:yanglint:modfeature";
+ prefix l;
+
+ feature ftr1;
+ feature ftr2;
+}
diff --git a/tests/yanglint/modules/modimp-cwd.yang b/tests/yanglint/modules/modimp-cwd.yang
new file mode 100644
index 0000000..3249462
--- /dev/null
+++ b/tests/yanglint/modules/modimp-cwd.yang
@@ -0,0 +1,8 @@
+module modimp-cwd {
+ namespace "urn:yanglint:modimp-cwd";
+ prefix ic;
+
+ import modcwd {
+ prefix mc;
+ }
+}
diff --git a/tests/yanglint/modules/modimp-path.yang b/tests/yanglint/modules/modimp-path.yang
new file mode 100644
index 0000000..d9dbb9b
--- /dev/null
+++ b/tests/yanglint/modules/modimp-path.yang
@@ -0,0 +1,8 @@
+module modimp-path {
+ namespace "urn:yanglint:modimp-path";
+ prefix ip;
+
+ import modpath {
+ prefix mp;
+ }
+}
diff --git a/tests/yanglint/modules/modimp-type.yang b/tests/yanglint/modules/modimp-type.yang
new file mode 100644
index 0000000..ec21d31
--- /dev/null
+++ b/tests/yanglint/modules/modimp-type.yang
@@ -0,0 +1,12 @@
+module modimp-type {
+ namespace "urn:yanglint:modimp-type";
+ prefix mit;
+
+ import modtypedef {
+ prefix mtd;
+ }
+
+ leaf lf {
+ type mtd:mui8;
+ }
+}
diff --git a/tests/yanglint/modules/modinclude.yang b/tests/yanglint/modules/modinclude.yang
new file mode 100644
index 0000000..849d43f
--- /dev/null
+++ b/tests/yanglint/modules/modinclude.yang
@@ -0,0 +1,9 @@
+module modinclude {
+ yang-version 1.1;
+ namespace "urn:yanglint:modinclude";
+ prefix mi;
+
+ include "modsub";
+
+ container mic;
+}
diff --git a/tests/yanglint/modules/modleaf.yang b/tests/yanglint/modules/modleaf.yang
new file mode 100644
index 0000000..48ce786
--- /dev/null
+++ b/tests/yanglint/modules/modleaf.yang
@@ -0,0 +1,8 @@
+module modleaf {
+ namespace "urn:yanglint:modleaf";
+ prefix l;
+
+ leaf lfl {
+ type uint16;
+ }
+}
diff --git a/tests/yanglint/modules/modleafref.yang b/tests/yanglint/modules/modleafref.yang
new file mode 100644
index 0000000..f86fb3f
--- /dev/null
+++ b/tests/yanglint/modules/modleafref.yang
@@ -0,0 +1,14 @@
+module modleafref {
+ namespace "urn:yanglint:modleafref";
+ prefix m;
+
+ import modleaf {
+ prefix ml;
+ }
+
+ leaf lfr {
+ type leafref {
+ path "/ml:lfl";
+ }
+ }
+}
diff --git a/tests/yanglint/modules/modmandatory.yang b/tests/yanglint/modules/modmandatory.yang
new file mode 100644
index 0000000..4d48540
--- /dev/null
+++ b/tests/yanglint/modules/modmandatory.yang
@@ -0,0 +1,14 @@
+module modmandatory {
+ namespace "urn:yanglint:modmandatory";
+ prefix mm;
+
+ container mmc {
+ leaf lft {
+ type int16;
+ mandatory true;
+ }
+ leaf lff {
+ type int16;
+ }
+ }
+}
diff --git a/tests/yanglint/modules/modmerge.yang b/tests/yanglint/modules/modmerge.yang
new file mode 100644
index 0000000..60fd75c
--- /dev/null
+++ b/tests/yanglint/modules/modmerge.yang
@@ -0,0 +1,21 @@
+module modmerge {
+ namespace "urn:yanglint:modmerge";
+ prefix mm;
+
+ container mmc {
+ leaf en {
+ type enumeration {
+ enum zero;
+ enum one;
+ }
+ }
+ leaf lm {
+ type int16;
+ must "../en != 'zero'";
+ }
+ leaf lf {
+ type string;
+ }
+ }
+
+}
diff --git a/tests/yanglint/modules/modmust.yang b/tests/yanglint/modules/modmust.yang
new file mode 100644
index 0000000..99971bd
--- /dev/null
+++ b/tests/yanglint/modules/modmust.yang
@@ -0,0 +1,13 @@
+module modmust {
+ namespace "urn:yanglint:modmust";
+ prefix m;
+
+ import modleaf {
+ prefix ml;
+ }
+
+ leaf lfm {
+ type string;
+ must "/ml:lfl > 0";
+ }
+}
diff --git a/tests/yanglint/modules/modnotif.yang b/tests/yanglint/modules/modnotif.yang
new file mode 100644
index 0000000..a2155a0
--- /dev/null
+++ b/tests/yanglint/modules/modnotif.yang
@@ -0,0 +1,19 @@
+module modnotif {
+ yang-version 1.1;
+ namespace "urn:yanglint:modnotif";
+ prefix mn;
+
+ container con {
+ notification nfn {
+ leaf lf {
+ type string;
+ }
+ }
+ }
+
+ notification nfg {
+ leaf lf {
+ type string;
+ }
+ }
+}
diff --git a/tests/yanglint/modules/modoper-leafref.yang b/tests/yanglint/modules/modoper-leafref.yang
new file mode 100644
index 0000000..36a1124
--- /dev/null
+++ b/tests/yanglint/modules/modoper-leafref.yang
@@ -0,0 +1,68 @@
+module modoper-leafref {
+ yang-version 1.1;
+ namespace "urn:yanglint:modoper-leafref";
+ prefix mol;
+
+ import modconfig {
+ prefix mc;
+ }
+
+ container cond {
+ list list {
+ key "klf";
+ leaf klf {
+ type string;
+ }
+ action act {
+ input {
+ leaf lfi {
+ type leafref {
+ path "/mc:mcc/mc:lft";
+ }
+ }
+ }
+ output {
+ leaf lfo {
+ type leafref {
+ path "/mc:mcc/mc:lft";
+ }
+ }
+ }
+ }
+ notification notif {
+ leaf lfn {
+ type leafref {
+ path "/mc:mcc/mc:lft";
+ }
+ }
+ }
+ }
+ }
+
+ rpc rpcg {
+ input {
+ leaf lfi {
+ type leafref {
+ path "/mc:mcc/mc:lft";
+ }
+ }
+ }
+ output {
+ container cono {
+ leaf lfo {
+ type leafref {
+ path "/mc:mcc/mc:lft";
+ }
+ }
+ }
+ }
+ }
+
+ notification notifg {
+ leaf lfr {
+ type leafref {
+ path "/mc:mcc/mc:lft";
+ }
+ }
+ }
+}
diff --git a/tests/yanglint/modules/modpath.yang b/tests/yanglint/modules/modpath.yang
new file mode 100644
index 0000000..da099a2
--- /dev/null
+++ b/tests/yanglint/modules/modpath.yang
@@ -0,0 +1,4 @@
+module modpath {
+ namespace "urn:yanglint:modpath";
+ prefix mp;
+}
diff --git a/tests/yanglint/modules/modrpc.yang b/tests/yanglint/modules/modrpc.yang
new file mode 100644
index 0000000..dc0cced
--- /dev/null
+++ b/tests/yanglint/modules/modrpc.yang
@@ -0,0 +1,19 @@
+module modrpc {
+ namespace "urn:yanglint:modrpc";
+ prefix mr;
+
+ rpc rpc {
+ input {
+ leaf lfi {
+ type string;
+ }
+ }
+ output {
+ container con {
+ leaf lfo {
+ type int16;
+ }
+ }
+ }
+ }
+}
diff --git a/tests/yanglint/modules/modsm-augment.yang b/tests/yanglint/modules/modsm-augment.yang
new file mode 100644
index 0000000..5d16fbd
--- /dev/null
+++ b/tests/yanglint/modules/modsm-augment.yang
@@ -0,0 +1,15 @@
+module modsm-augment {
+ yang-version 1.1;
+ namespace "urn:yanglint:modsm-augment";
+ prefix "msa";
+
+ import modsm {
+ prefix msm;
+ }
+
+ augment "/msm:root" {
+ leaf alf {
+ type string;
+ }
+ }
+}
diff --git a/tests/yanglint/modules/modsm.yang b/tests/yanglint/modules/modsm.yang
new file mode 100644
index 0000000..dfe8830
--- /dev/null
+++ b/tests/yanglint/modules/modsm.yang
@@ -0,0 +1,13 @@
+module modsm {
+ yang-version 1.1;
+ namespace "urn:yanglint:modsm";
+ prefix "msm";
+
+ import ietf-yang-schema-mount {
+ prefix sm;
+ }
+
+ container root {
+ sm:mount-point "root";
+ }
+}
diff --git a/tests/yanglint/modules/modsub.yang b/tests/yanglint/modules/modsub.yang
new file mode 100644
index 0000000..79d9286
--- /dev/null
+++ b/tests/yanglint/modules/modsub.yang
@@ -0,0 +1,8 @@
+submodule modsub {
+ yang-version 1.1;
+ belongs-to modinclude {
+ prefix mi;
+ }
+
+ container msc;
+}
diff --git a/tests/yanglint/modules/modtypedef.yang b/tests/yanglint/modules/modtypedef.yang
new file mode 100644
index 0000000..ea09c95
--- /dev/null
+++ b/tests/yanglint/modules/modtypedef.yang
@@ -0,0 +1,8 @@
+module modtypedef {
+ namespace "urn:yanglint:typedef";
+ prefix mt;
+
+ typedef mui8 {
+ type uint8;
+ }
+}
diff --git a/tests/yanglint/non-interactive/all.tcl b/tests/yanglint/non-interactive/all.tcl
new file mode 100644
index 0000000..998c03a
--- /dev/null
+++ b/tests/yanglint/non-interactive/all.tcl
@@ -0,0 +1,15 @@
+package require tcltest
+
+# Hook to determine if any of the tests failed.
+# Sets a global variable exitCode to 1 if any test fails otherwise it is set to 0.
+proc tcltest::cleanupTestsHook {} {
+ variable numTests
+ set ::exitCode [expr {$numTests(Failed) > 0}]
+}
+
+if {[info exists ::env(TESTS_DIR)]} {
+ tcltest::configure -testdir "$env(TESTS_DIR)/non-interactive"
+}
+
+tcltest::runAllTests
+exit $exitCode
diff --git a/tests/yanglint/non-interactive/data_default.test b/tests/yanglint/non-interactive/data_default.test
new file mode 100644
index 0000000..be19d72
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_default.test
@@ -0,0 +1,31 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mods "$::env(YANG_MODULES_DIR)/ietf-netconf-with-defaults@2011-06-01.yang $::env(YANG_MODULES_DIR)/moddefault.yang"
+set data "$::env(TESTS_DIR)/data/moddefault.xml"
+
+test data_default_not_set {Print data without --default parameter} {
+ ly_cmd "-f xml $mods $data" "</lf>.*</di>\n</mdc>"
+ ly_cmd "-f json $mods $data" "lf\".*di\"\[^\"]*"
+} {}
+
+test data_default_all {data --default all} {
+ ly_cmd "-d all -f xml $mods $data" "</lf>.*</di>.*</ds>\n</mdc>"
+ ly_cmd "-d all -f json $mods $data" "lf\".*di\".*ds\"\[^\"]*"
+} {}
+
+test data_default_all_tagged {data --default all-tagged} {
+ ly_cmd "-d all-tagged -f xml $mods $data" "</lf>.*<di.*default.*</di>.*<ds.*default.*</ds>\n</mdc>"
+ ly_cmd "-d all-tagged -f json $mods $data" "lf\".*di\".*ds\".*@ds\".*default\"\[^\"]*"
+} {}
+
+test data_default_trim {data --default trim} {
+ ly_cmd "-d trim -f xml $mods $data" "</lf>\n</mdc>"
+ ly_cmd "-d trim -f json $mods $data" "lf\"\[^\"]*"
+} {}
+
+test data_default_implicit_tagged {data --default implicit-tagged} {
+ ly_cmd "-d implicit-tagged -f xml $mods $data" "</lf>.*<di>5</di>.*<ds.*default.*</ds>\n</mdc>"
+ ly_cmd "-d implicit-tagged -f json $mods $data" "lf\".*di\"\[^@]*ds\".*default\"\[^\"]*"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_in_format.test b/tests/yanglint/non-interactive/data_in_format.test
new file mode 100644
index 0000000..f1336dd
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_in_format.test
@@ -0,0 +1,18 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test data_in_format_xml {--in-format xml} {
+ ly_cmd "-I xml $mdir/modleaf.yang $ddir/modleaf.dxml"
+ ly_cmd_err "-I json $mdir/modleaf.yang $ddir/modleaf.dxml" "Failed to parse"
+ ly_cmd_err "-I lyb $mdir/modleaf.yang $ddir/modleaf.dxml" "Failed to parse"
+} {}
+
+test data_in_format_json {--in-format json} {
+ ly_cmd "-I json $mdir/modleaf.yang $ddir/modleaf.djson"
+ ly_cmd_err "-I xml $mdir/modleaf.yang $ddir/modleaf.djson" "Failed to parse"
+ ly_cmd_err "-I lyb $mdir/modleaf.yang $ddir/modleaf.djson" "Failed to parse"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_merge.test b/tests/yanglint/non-interactive/data_merge.test
new file mode 100644
index 0000000..4ecfcee
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_merge.test
@@ -0,0 +1,28 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test data_merge_basic {Data is merged and the node is added} {
+ ly_cmd "-m -f xml $mdir/modmerge.yang $ddir/modmerge.xml $ddir/modmerge3.xml" "<en>.*<lm>.*<lf>"
+} {}
+
+test data_merge_validation_failed {Data is merged but validation failed.} {
+ ly_cmd "$mdir/modmerge.yang $ddir/modmerge.xml"
+ ly_cmd "$mdir/modmerge.yang $ddir/modmerge2.xml"
+ ly_cmd "-m $mdir/modmerge.yang $ddir/modmerge2.xml $ddir/modmerge.xml"
+ ly_cmd_err "-m $mdir/modmerge.yang $ddir/modmerge.xml $ddir/modmerge2.xml" "Merged data are not valid"
+} {}
+
+test data_merge_dataconfig {The merge option has effect only for 'data' and 'config' TYPEs} {
+ set wrn1 "option has effect only for"
+ ly_cmd_wrn "-m -t rpc $mdir/modrpc.yang $ddir/modrpc.xml $ddir/modrpc.xml" $wrn1
+ ly_cmd_wrn "-m -t notif $mdir/modnotif.yang $ddir/modnotif2.xml $ddir/modnotif2.xml" $wrn1
+ ly_cmd_wrn "-m -t get $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig.xml" $wrn1
+ ly_cmd_wrn "-m -t getconfig $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1
+ ly_cmd_wrn "-m -t edit $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1
+ ly_cmd "-m -t config $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig2.xml"
+ ly_cmd "-m -t data $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig.xml"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_not_strict.test b/tests/yanglint/non-interactive/data_not_strict.test
new file mode 100644
index 0000000..b91eed8
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_not_strict.test
@@ -0,0 +1,20 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test data_no_strict_basic {} {
+ ly_cmd_err "$ddir/modmandatory.xml $mdir/modleaf.yang" "No module with namespace \"urn:yanglint:modmandatory\" in the context."
+ ly_cmd "-n $ddir/modmandatory.xml $mdir/modleaf.yang"
+} {}
+
+test data_no_strict_invalid_data {validation with --no-strict but data are invalid} {
+ set errmsg "Mandatory node \"lft\" instance does not exist."
+ ly_cmd_err "-n $ddir/modmandatory_invalid.xml $mdir/modmandatory.yang" $errmsg
+} {}
+
+test data_no_strict_ignore_invalid_data {--no-strict ignore invalid data if no schema is provided} {
+ ly_cmd "-f xml -n $ddir/modmandatory_invalid.xml $ddir/modleaf.xml $mdir/modleaf.yang" "modleaf.*</lfl>$"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_operational.test b/tests/yanglint/non-interactive/data_operational.test
new file mode 100644
index 0000000..82e861e
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_operational.test
@@ -0,0 +1,62 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types"
+
+test data_operational_twice {it is not allowed to specify more than one --operational parameter} {
+ ly_cmd_err "-t notif -O $ddir/modconfig.xml -O $ddir/modleaf.xml" "cannot be set multiple times"
+} {}
+
+test data_operational_no_type {--operational should be with parameter --type} {
+ ly_cmd_err "-O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif.xml" $err1
+} {}
+
+test data_operational_missing {--operational is omitted and the datastore contents is in the data file} {
+ ly_cmd_err "$mdir/modoper-leafref.yang $ddir/modoper_leafref_notif_err.xml" "Failed to parse input data file"
+} {}
+
+test data_operational_wrong_type {data are not defined as an operation} {
+ ly_cmd_wrn "-t data -O $ddir/modconfig.xml $mdir/modleaf.yang $ddir/modleaf.xml" $err1
+} {}
+
+test data_operational_datastore_with_unknown_data {unknown data are ignored} {
+ ly_cmd "-t rpc -O $ddir/modmandatory_invalid.xml $mdir/modrpc.yang $ddir/modrpc.xml"
+} {}
+
+test data_operational_empty_datastore {datastore is considered empty because it contains unknown data} {
+ ly_cmd "-t rpc -O $ddir/modmandatory_invalid.xml $mdir/modrpc.yang $ddir/modrpc.xml"
+ set msg "parent \"/modnotif:con\" not found in the operational data"
+ ly_cmd_err "-t notif -O $ddir/modmandatory_invalid.xml $mdir/modnotif.yang $ddir/modnotif.xml" $msg
+} {}
+
+test data_operational_notif_leafref {--operational data is referenced from notification-leafref} {
+ ly_cmd "-t notif -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif.xml"
+} {}
+
+test data_operational_nested_notif_leafref {--operational data is referenced from nested-notification-leafref} {
+ ly_cmd "-t notif -O $ddir/modoper_leafref_ds.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif2.xml"
+} {}
+
+test data_operational_nested_notif_parent_missing {--operational data are invalid due to missing parent node} {
+ set msg "klf='key_val']\" not found in the operational data"
+ ly_cmd_err "-t notif -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif2.xml" $msg
+} {}
+
+test data_operational_action_leafref {--operational data is referenced from action-leafref} {
+ ly_cmd "-t rpc -O $ddir/modoper_leafref_ds.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_action.xml"
+} {}
+
+test data_operational_action_reply_leafref {--operational data is referenced from action-leafref output} {
+ ly_cmd "-t reply -O $ddir/modoper_leafref_ds.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_action_reply.xml"
+} {}
+
+test data_operational_rpc_leafref {--operational data is referenced from rpc-leafref} {
+ ly_cmd "-t rpc -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_rpc.xml"
+} {}
+
+test data_operational_rpc_reply_leafref {--operational data is referenced from rpc-leafref output} {
+ ly_cmd "-t reply -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_rpc_reply.xml"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_present.test b/tests/yanglint/non-interactive/data_present.test
new file mode 100644
index 0000000..81aac14
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_present.test
@@ -0,0 +1,25 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test data_present_via_mandatory {validation of mandatory-stmt will pass only with the --present} {
+ set mods "$mdir/modleaf.yang $mdir/modmandatory.yang"
+ ly_cmd_err "$ddir/modleaf.xml $mods" "Mandatory node \"lft\" instance does not exist."
+ ly_cmd "-e $ddir/modleaf.xml $mods"
+} {}
+
+test data_present_merge {validation with --present and --merge} {
+ set mods "$mdir/modleaf.yang $mdir/modmandatory.yang $mdir/moddefault.yang"
+ set data "$ddir/modleaf.xml $ddir/moddefault.xml"
+ ly_cmd_err "-m $data $mods" "Mandatory node \"lft\" instance does not exist."
+ ly_cmd "-m -e $data $mods"
+} {}
+
+test data_present_merge_invalid {using --present and --merge but data are invalid} {
+ set mods "$mdir/modleaf.yang $mdir/modmandatory.yang"
+ set data "$ddir/modleaf.xml $ddir/modmandatory_invalid.xml"
+ ly_cmd_err "-e -m $data $mods" "Mandatory node \"lft\" instance does not exist."
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_type.test b/tests/yanglint/non-interactive/data_type.test
new file mode 100644
index 0000000..e3691d7
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_type.test
@@ -0,0 +1,107 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+set modnc "$mdir/ietf-netconf@2011-06-01.yang"
+
+test data_type_data {data --type data} {
+ ly_cmd "-t data $mdir/modconfig.yang $ddir/modconfig.xml"
+} {}
+
+test data_type_config {data --type config} {
+ ly_cmd_err "-t config $mdir/modconfig.yang $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+ ly_cmd "-t config $mdir/modconfig.yang $ddir/modconfig2.xml"
+} {}
+
+test data_type_get {data --type get} {
+ ly_cmd_err "-t data $mdir/modleafref.yang $ddir/modleafref2.xml" "Invalid leafref value"
+ ly_cmd "-t get $mdir/modleafref.yang $ddir/modleafref2.xml"
+} {}
+
+test data_type_getconfig_no_state {No state node for data --type getconfig} {
+ ly_cmd_err "-t getconfig $mdir/modconfig.yang $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+ ly_cmd "-t getconfig $mdir/modconfig.yang $ddir/modconfig2.xml"
+} {}
+
+test data_type_getconfig_parse_only {No validation performed for data --type getconfig} {
+ ly_cmd_err "-t data $mdir/modleafref.yang $ddir/modleafref2.xml" "Invalid leafref value"
+ ly_cmd "-t getconfig $mdir/modleafref.yang $ddir/modleafref2.xml"
+} {}
+
+test data_type_edit_no_state {No state node for data --type edit} {
+ ly_cmd_err "-t edit $mdir/modconfig.yang $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+ ly_cmd "-t edit $mdir/modconfig.yang $ddir/modconfig2.xml"
+} {}
+
+test data_type_edit_parse_only {No validation performed for data --type edit} {
+ ly_cmd_err "-t data $mdir/modleafref.yang $ddir/modleafref2.xml" "Invalid leafref value"
+ ly_cmd "-t edit $mdir/modleafref.yang $ddir/modleafref2.xml"
+} {}
+
+test data_type_rpc {Validation of rpc-statement by data --type rpc} {
+ ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd "-t rpc $mdir/modrpc.yang $ddir/modrpc.xml"
+} {}
+
+test data_type_rpc_nc {Validation of rpc-statement by data --type nc-rpc} {
+ ly_cmd_err "-t nc-rpc $modnc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope"
+ ly_cmd "-t nc-rpc $modnc $mdir/modrpc.yang $ddir/modrpc_nc.xml"
+} {}
+
+test data_type_rpc_reply {Validation of rpc-reply by data --type reply} {
+ ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd_wrn "-t reply -R $ddir/modrpc.xml $mdir/modrpc.yang $ddir/modrpc_reply.xml" "needed only for NETCONF"
+ ly_cmd "-t reply $mdir/modrpc.yang $ddir/modrpc_reply.xml"
+} {}
+
+test data_type_rpc_reply_nc {Validation of rpc-reply by data --type nc-reply} {
+ set err1 "Missing NETCONF <rpc-reply> envelope"
+ ly_cmd_err "-t nc-reply -R $ddir/modrpc_nc.xml $mdir/modrpc.yang $mdir/modleaf.yang $ddir/modleaf.xml" $err1
+ ly_cmd_err "-t nc-reply $mdir/modrpc.yang $ddir/modrpc_reply_nc.xml" "Missing source RPC"
+ ly_cmd "-t nc-reply -R $ddir/modrpc_nc.xml $mdir/modrpc.yang $ddir/modrpc_reply_nc.xml"
+} {}
+
+test data_type_rpc_action {Validation of action-statement by data --type rpc} {
+ ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd "-t rpc -O $ddir/modaction_ds.xml $mdir/modaction.yang $ddir/modaction.xml"
+} {}
+
+test data_type_rpc_action_nc {Validation of action-statement by data --type nc-rpc} {
+ ly_cmd_err "-t nc-rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope"
+ ly_cmd "-t nc-rpc -O $ddir/modaction_ds.xml $mdir/modaction.yang $ddir/modaction_nc.xml"
+} {}
+
+test data_type_rpc_action_reply {Validation of action-reply by data --type reply} {
+ ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd "-t reply -O $ddir/modaction_ds.xml $mdir/modaction.yang $ddir/modaction_reply.xml"
+} {}
+
+test data_type_rpc_action_reply_nc {Validation of action-reply by data --type nc-reply} {
+ set err1 "Missing NETCONF <rpc-reply> envelope"
+ set err2 "operational parameter needed"
+ ly_cmd_err "-t nc-reply -R $ddir/modaction_nc.xml $mdir/modaction.yang $mdir/modleaf.yang $ddir/modleaf.xml" $err1
+ ly_cmd_err "-t nc-reply $mdir/modaction.yang $ddir/modaction_reply_nc.xml" "Missing source RPC"
+ ly_cmd_err "-t nc-reply -R $ddir/modaction_nc.xml $mdir/modaction.yang $ddir/modaction_reply_nc.xml" $err2
+ ly_cmd "-t nc-reply -O $ddir/modaction_ds.xml -R $ddir/modaction_nc.xml $mdir/modaction.yang $ddir/modaction_reply_nc.xml"
+} {}
+
+test data_type_notif {Validation of notification-statement by data --type notif} {
+ ly_cmd_err "-t notif $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+ ly_cmd "-t notif $mdir/modnotif.yang $ddir/modnotif2.xml"
+} {}
+
+test data_type_notif_nc {Validation of notification-statement by data --type nc-notif} {
+ ly_cmd_err "-t nc-notif $modnc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <notification> envelope"
+ ly_cmd "-t nc-notif $modnc $mdir/modnotif.yang $ddir/modnotif2_nc.xml"
+} {}
+
+test data_type_notif_nested {Validation of nested-notification-statement by data --type notif} {
+ ly_cmd "-t notif -O $ddir/modnotif_ds.xml $mdir/modnotif.yang $ddir/modnotif.xml"
+} {}
+
+test data_type_notif_nested_nc {Validation of nested-notification-statement by data --type nc-notif} {
+ ly_cmd_err "-t nc-notif $modnc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <notification> envelope"
+ ly_cmd "-t nc-notif -O $ddir/modnotif_ds.xml $modnc $mdir/modnotif.yang $ddir/modnotif_nc.xml"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_xpath.test b/tests/yanglint/non-interactive/data_xpath.test
new file mode 100644
index 0000000..1d96106
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_xpath.test
@@ -0,0 +1,42 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mod "$::env(YANG_MODULES_DIR)/moddatanodes.yang"
+set data "$::env(TESTS_DIR)/data/moddatanodes.xml"
+
+test data_xpath_empty {--data-path to missing node} {
+ ly_cmd "-E /moddatanodes:dnc/mis $mod $data" "Empty"
+} {}
+
+test data_xpath_leaf {--xpath to leaf node} {
+ ly_cmd "-E /moddatanodes:dnc/lf $mod $data" "leaf \"lf\" \\(value: \"x\"\\)"
+} {}
+
+test data_xpath_leaflist {--xpath to leaf-list node} {
+ set r1 "leaf-list \"lfl\" \\(value: \"1\"\\)"
+ set r2 "leaf-list \"lfl\" \\(value: \"2\"\\)"
+ ly_cmd "-E /moddatanodes:dnc/lfl $mod $data" "$r1\n $r2"
+} {}
+
+test data_xpath_list {--xpath to list} {
+ set r1 "list \"lt\" \\(\"kalf\": \"ka1\"; \"kblf\": \"kb1\";\\)"
+ set r2 "list \"lt\" \\(\"kalf\": \"ka2\"; \"kblf\": \"kb2\";\\)"
+ ly_cmd "-E /moddatanodes:dnc/con/lt $mod $data" "$r1\n $r2"
+} {}
+
+test data_xpath_container {--xpath to container} {
+ ly_cmd "-E /moddatanodes:dnc/con $mod $data" "container \"con\""
+} {}
+
+test data_xpath_wrong_path {--xpath to a non-existent node} {
+ ly_cmd_err "-E /moddatanodes:dnc/wrng $mod $data" "xpath failed"
+} {}
+
+test data_xpath_err_format {--xpath cannot be combined with --format} {
+ ly_cmd_err "-f xml -E /moddatanodes:dnc/lf $mod $data" "option cannot be combined"
+} {}
+
+test data_xpath_err_default {--xpath cannot be combined with --default} {
+ ly_cmd_err "-d all -E /moddatanodes:dnc/lf $mod $data" "option cannot be combined"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/debug.test b/tests/yanglint/non-interactive/debug.test
new file mode 100644
index 0000000..4543acb
--- /dev/null
+++ b/tests/yanglint/non-interactive/debug.test
@@ -0,0 +1,25 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test debug_dict {Check debug message DICT} {
+-constraints {[ly_opt_exists "-G"]} -body {
+ ly_cmd_wrn "-V -V -G dict $mdir/modleaf.yang" "DICT"
+}}
+
+test debug_xpath {Check debug message XPATH} {
+-constraints {[ly_opt_exists "-G"]} -body {
+ ly_cmd_wrn "-V -V -G xpath $mdir/modmust.yang" "XPATH"
+}}
+
+test debug_dep_sets {Check debug message DEPSETS} {
+-constraints {[ly_opt_exists "-G"]} -body {
+ ly_cmd_wrn "-V -V -G dep-sets $mdir/modleaf.yang" "DEPSETS"
+}}
+
+test debug_depsets_xpath {Check debug message DEPSETS and XPATH} {
+-constraints {[ly_opt_exists "-G"]} -body {
+ ly_cmd_wrn "-V -V -G dep-sets,xpath $mdir/modmust.yang" "DEPSETS.*XPATH"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/disabled_searchdir.test b/tests/yanglint/non-interactive/disabled_searchdir.test
new file mode 100644
index 0000000..49fe13e
--- /dev/null
+++ b/tests/yanglint/non-interactive/disabled_searchdir.test
@@ -0,0 +1,18 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $env(YANG_MODULES_DIR)
+
+# Test should be skipped if called by ctest.
+test disable_searchdir_once {Unsuccessfully imports module due to disabled cwd searching} {
+-constraints {!ctest} -body {
+ ly_cmd "$mdir/modimp-cwd.yang"
+ ly_cmd_err "-D $mdir/modimp-cwd.yang" "not found in local searchdirs"
+}}
+
+test disable_searchdir_twice {Unsuccessfully imports module due to -D -D} {
+ ly_cmd "$mdir/ietf-ip.yang"
+ ly_cmd_err "-D -D $mdir/ietf-ip.yang" "Loading \"ietf-interfaces\" module failed."
+} {}
+
+cleanupTests
+
diff --git a/tests/yanglint/non-interactive/ext_data.test b/tests/yanglint/non-interactive/ext_data.test
new file mode 100644
index 0000000..d4e3c44
--- /dev/null
+++ b/tests/yanglint/non-interactive/ext_data.test
@@ -0,0 +1,29 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+
+test ext_data_schema_mount_tree {Print tree output of a model with Schema Mount} {
+ # mounting node lfl from modleaf.yang into modsm.yang
+ set out1 "--mp root.*--rw lfl/"
+ ly_cmd "-f tree -p $mdir -y -x $ddir/modsm_ctx_ext.xml $mdir/modsm.yang" $out1
+} {}
+
+test ext_data_schema_mount_tree_yanglibfile {Print tree output of a model with Schema Mount and --yang-library-file} {
+ # yang-library-file context contains an augment node 'alf' for modsm
+ set out1 "--mp root.*--rw lfl/.*--rw msa:alf?"
+ ly_cmd "-f tree -p $mdir -Y $ddir/modsm_ctx_main.xml -x $ddir/modsm_ctx_ext.xml $mdir/modsm.yang" $out1
+} {}
+
+test ext_data_schema_mount_xml {Validating and printing mounted data} {
+ ly_cmd "-f xml -t config -p $mdir -y -x $ddir/modsm_ctx_ext.xml $mdir/modsm.yang $ddir/modsm.xml" "</lfl>"
+} {}
+
+test ext_data_schema_mount_xml_yanglibfile {Validating and printing mounted data with --yang-library-file} {
+ set yanglibfile "$ddir/modsm_ctx_main.xml"
+ set extdata "$ddir/modsm_ctx_ext.xml"
+ set out1 "</lfl>.*</alf>"
+ ly_cmd "-f xml -t config -p $mdir -Y $yanglibfile -x $extdata $mdir/modsm.yang $ddir/modsm2.xml" $out1
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/extended_leafref.test b/tests/yanglint/non-interactive/extended_leafref.test
new file mode 100644
index 0000000..5e1a90e
--- /dev/null
+++ b/tests/yanglint/non-interactive/extended_leafref.test
@@ -0,0 +1,13 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test extended_leafref_enabled {Valid module with --extended-leafref option} {
+ ly_cmd "-X $mdir/modextleafref.yang"
+} {}
+
+test extended_leafref_disabled {Expected error if --extended-leafref is not set} {
+ ly_cmd_err "$mdir/modextleafref.yang" "Unexpected XPath token \"FunctionName\""
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/format.test b/tests/yanglint/non-interactive/format.test
new file mode 100644
index 0000000..8df5544
--- /dev/null
+++ b/tests/yanglint/non-interactive/format.test
@@ -0,0 +1,72 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+set ipv6_path "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address"
+
+test format_yang {} {
+ ly_cmd "-f yang $mdir/modleaf.yang" "leaf lfl"
+} {}
+
+test format_yang_submodule {Print submodule in yang format} {
+ ly_cmd "-s modsub -f yang $mdir/modinclude.yang" "submodule modsub"
+} {}
+
+test format_yin {} {
+ ly_cmd "-f yin $mdir/modleaf.yang" "<leaf name=\"lfl\">"
+} {}
+
+test format_yin_submodule {Print submodule in yin format} {
+ ly_cmd "-s modsub -f yin $mdir/modinclude.yang" "<submodule name=\"modsub\""
+} {}
+
+test format_info {} {
+ ly_cmd "-f info $mdir/modleaf.yang" "status current"
+} {}
+
+test format_tree {} {
+ ly_cmd "-f tree $mdir/modleaf.yang" "\\+--rw lfl"
+} {}
+
+test format_data_xml {Print data in xml format} {
+ ly_cmd "-f xml $mdir/modleaf.yang $ddir/modleaf.xml" "<lfl xmlns=\"urn:yanglint:modleaf\">7</lfl>"
+} {}
+
+test format_data_json {Print data in json format} {
+ ly_cmd "-f json $mdir/modleaf.yang $ddir/modleaf.xml" "{\n \"modleaf:lfl\": 7\n}"
+} {}
+
+test format_data_lyb_err {Printing in LYB format: expect error due to missing parameter} {
+ ly_cmd_err "-f lyb $mdir/modleaf.yang $ddir/modleaf.xml" "The LYB format requires the -o"
+} {}
+
+test format_tree_submodule {Print submodule in tree format} {
+ ly_cmd "-s modsub -f tree $mdir/modinclude.yang" "submodule: modsub"
+} {}
+
+test format_tree_path {Print subtree in tree format} {
+ ly_cmd "-f tree -P $ipv6_path $mdir/ietf-ip.yang" "\\+--rw address.*\\+--rw prefix-length"
+} {}
+
+test format_tree_path_single_node {Print node in tree format} {
+ ly_cmd "-f tree -q -P $ipv6_path $mdir/ietf-ip.yang" "\\+--rw address\\* \\\[ip\\\]$"
+} {}
+
+test format_tree_path_single_node_line_length {Print node in the tree format and limit row size} {
+ ly_cmd "-f tree -L 20 -q -P $ipv6_path $mdir/ietf-ip.yang" "\\+--rw address\\*\n *\\\[ip\\\]$"
+} {}
+
+test format_feature_param_one_module {Show features for one module} {
+ ly_cmd "-f feature-param $mdir/ietf-ip.yang" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" -ex
+} {}
+
+test format_feature_param_more_modules {Show a mix of modules with and without features} {
+
+ set features " -F modfeature:ftr1,ftr2\
+-F modleaf:\
+-F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf"
+
+ ly_cmd "-f feature-param $mdir/modfeature.yang $mdir/modleaf.yang $mdir/ietf-ip.yang" $features -ex
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/list.test b/tests/yanglint/non-interactive/list.test
new file mode 100644
index 0000000..626d9a1
--- /dev/null
+++ b/tests/yanglint/non-interactive/list.test
@@ -0,0 +1,26 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+namespace import uti::regex_xml_elements uti::regex_json_pairs
+
+set modules {ietf-yang-library ietf-inet-types}
+
+test list_basic {} {
+ ly_cmd "-l" "ietf-yang-types"
+} {}
+
+test list_format_xml {list --format xml} {
+ ly_cmd "-y -f xml -l" [regex_xml_elements $modules "name"]
+} {}
+
+test list_format_json {list --format json} {
+ ly_cmd "-y -f json -l" [regex_json_pairs $modules "name"]
+} {}
+
+test list_ietf_yang_library {Error due to missing ietf-yang-library} {
+ ly_cmd_err "-f xml -l" "Module \"ietf-yang-library\" is not implemented."
+} {}
+
+test list_bad_format {Error due to bad format} {
+ ly_cmd_err "-f csv -l" "Unknown output format csv"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/ly.tcl b/tests/yanglint/non-interactive/ly.tcl
new file mode 100644
index 0000000..f6bb2c7
--- /dev/null
+++ b/tests/yanglint/non-interactive/ly.tcl
@@ -0,0 +1,8 @@
+# @brief The main source of functions and variables for testing yanglint in the non-interactive mode.
+
+# For testing yanglint.
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/common.tcl" : "../common.tcl"}]
+# For testing any non-interactive tool.
+source "$::env(TESTS_DIR)/../tool_ni.tcl"
+
+# The script continues by defining variables and functions specific to the non-interactive yanglint tool.
diff --git a/tests/yanglint/non-interactive/make_implemented.test b/tests/yanglint/non-interactive/make_implemented.test
new file mode 100644
index 0000000..40cead9
--- /dev/null
+++ b/tests/yanglint/non-interactive/make_implemented.test
@@ -0,0 +1,17 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test make_impl_no_set {Import while --make-implemented is not set} {
+ ly_cmd "-l $mdir/modleafref.yang" "I modleafref\n.*I modleaf"
+} {}
+
+test make_impl_set_once {--make-implemented} {
+ ly_cmd "-l -i $mdir/modmust.yang" "I modmust\n.*I modleaf"
+} {}
+
+test make_impl_set_twice {-i -i} {
+ ly_cmd "-l -i -i $mdir/modimp-type.yang" "I modimp-type\n.*I modtypedef"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/modcwd.yang b/tests/yanglint/non-interactive/modcwd.yang
new file mode 100644
index 0000000..db33e73
--- /dev/null
+++ b/tests/yanglint/non-interactive/modcwd.yang
@@ -0,0 +1,4 @@
+module modcwd {
+ namespace "urn:yanglint:modcwd";
+ prefix mc;
+}
diff --git a/tests/yanglint/non-interactive/path.test b/tests/yanglint/non-interactive/path.test
new file mode 100644
index 0000000..bf915ff
--- /dev/null
+++ b/tests/yanglint/non-interactive/path.test
@@ -0,0 +1,9 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $env(YANG_MODULES_DIR)
+
+test path_basic {} {
+ ly_cmd "-p $::env(TESTS_DIR)/data $::env(YANG_MODULES_DIR)/modimp-path.yang"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/yang_library_file.test b/tests/yanglint/non-interactive/yang_library_file.test
new file mode 100644
index 0000000..bd95978
--- /dev/null
+++ b/tests/yanglint/non-interactive/yang_library_file.test
@@ -0,0 +1,18 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+
+test ylf_list {apply --yang-library-file and check result by --list} {
+ ly_cmd "-Y $ddir/modimp_type_ctx.xml -p $mdir -l" "I modimp-type.*i modtypedef"
+} {}
+
+test ylf_make_implemented {apply --yang-library-file and --make-implemented} {
+ ly_cmd "-Y $ddir/modimp_type_ctx.xml -p $mdir -i -i -l" "I modimp-type.*I modtypedef"
+} {}
+
+test ylf_augment_ctx {Setup context by yang-library-file and augment module} {
+ ly_cmd "-Y $ddir/modconfig_ctx.xml -p $mdir -f tree $mdir/modconfig.yang $mdir/modconfig-augment.yang" "mca:alf"
+} {}
+
+cleanupTests
diff --git a/tests/yangre/CMakeLists.txt b/tests/yangre/CMakeLists.txt
new file mode 100644
index 0000000..ce5b39b
--- /dev/null
+++ b/tests/yangre/CMakeLists.txt
@@ -0,0 +1,8 @@
+find_program(PATH_TCLSH NAMES tclsh)
+if(NOT PATH_TCLSH)
+ message(WARNING "'tclsh' not found! The yangre test will not be available.")
+else()
+ add_test(NAME "yangre" COMMAND "tclsh" "${CMAKE_CURRENT_SOURCE_DIR}/all.tcl")
+ set_property(TEST "yangre" APPEND PROPERTY ENVIRONMENT "TESTS_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
+ set_property(TEST "yangre" APPEND PROPERTY ENVIRONMENT "YANGRE=${PROJECT_BINARY_DIR}")
+endif()
diff --git a/tests/yangre/all.tcl b/tests/yangre/all.tcl
new file mode 100644
index 0000000..f00563f
--- /dev/null
+++ b/tests/yangre/all.tcl
@@ -0,0 +1,15 @@
+package require tcltest
+
+# Hook to determine if any of the tests failed.
+# Sets a global variable exitCode to 1 if any test fails otherwise it is set to 0.
+proc tcltest::cleanupTestsHook {} {
+ variable numTests
+ set ::exitCode [expr {$numTests(Failed) > 0}]
+}
+
+if {[info exists ::env(TESTS_DIR)]} {
+ tcltest::configure -testdir "$env(TESTS_DIR)"
+}
+
+tcltest::runAllTests
+exit $exitCode
diff --git a/tests/yangre/arg.test b/tests/yangre/arg.test
new file mode 100644
index 0000000..821aad1
--- /dev/null
+++ b/tests/yangre/arg.test
@@ -0,0 +1,19 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}]
+
+test arg_empty {Missing arguments} {
+ ly_cmd_err "" "missing <string> parameter to process"
+} {}
+
+test arg_wrong {Wrong argument} {
+ ly_cmd_err "-j" "invalid option"
+} {}
+
+test arg_help {Print help} {
+ ly_cmd "-h" "Usage:"
+} {}
+
+test arg_version {Print version} {
+ ly_cmd "-v" "yangre"
+} {}
+
+cleanupTests
diff --git a/tests/yangre/file.test b/tests/yangre/file.test
new file mode 100644
index 0000000..80ea3f6
--- /dev/null
+++ b/tests/yangre/file.test
@@ -0,0 +1,37 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}]
+
+set fdir "$::env(TESTS_DIR)/files"
+
+test file_empty {file is empty} {
+ ly_cmd "-f $fdir/empty.txt"
+} {}
+
+test file_empty_str {<string> is empty} {
+ ly_cmd "-f $fdir/empty_str.txt"
+} {}
+
+test file_empty_str_err {empty <string> is not allowed} {
+ ly_cmd_err "-f $fdir/empty_str_err.txt" "does not conform"
+} {}
+
+test file_one_pattern {one pattern in the file} {
+ ly_cmd "-f $fdir/one_pattern.txt"
+} {}
+
+test file_two_patterns {two patterns in the file} {
+ ly_cmd "-f $fdir/two_patterns.txt"
+} {}
+
+test file_two_patterns_err {two patterns and the <string> is wrong} {
+ ly_cmd_err "-f $fdir/two_patterns_err.txt" "does not conform"
+} {}
+
+test file_two_patterns_invert_match {one pattern is inverted} {
+ ly_cmd "-f $fdir/two_patterns_invert_match.txt"
+} {}
+
+test file_two_patterns_invert_match_err {one pattern is inverted and the <string> is wrong} {
+ ly_cmd_err "-f $fdir/two_patterns_invert_match_err.txt" "does not conform to inverted"
+} {}
+
+cleanupTests
diff --git a/tests/yangre/files/empty.txt b/tests/yangre/files/empty.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/yangre/files/empty.txt
diff --git a/tests/yangre/files/empty_str.txt b/tests/yangre/files/empty_str.txt
new file mode 100644
index 0000000..bf9b1b5
--- /dev/null
+++ b/tests/yangre/files/empty_str.txt
@@ -0,0 +1,2 @@
+"[0-9a-fA-F]*"
+
diff --git a/tests/yangre/files/empty_str_err.txt b/tests/yangre/files/empty_str_err.txt
new file mode 100644
index 0000000..f48a15d
--- /dev/null
+++ b/tests/yangre/files/empty_str_err.txt
@@ -0,0 +1,2 @@
+"[0-9a-fA-F]+"
+
diff --git a/tests/yangre/files/one_pattern.txt b/tests/yangre/files/one_pattern.txt
new file mode 100644
index 0000000..cf9acc5
--- /dev/null
+++ b/tests/yangre/files/one_pattern.txt
@@ -0,0 +1,3 @@
+"[0-9a-fA-F]*"
+
+1F
diff --git a/tests/yangre/files/two_patterns.txt b/tests/yangre/files/two_patterns.txt
new file mode 100644
index 0000000..7d04b2c
--- /dev/null
+++ b/tests/yangre/files/two_patterns.txt
@@ -0,0 +1,4 @@
+"[0-9a-fA-F]*"
+'[a-zA-Z0-9\-_.]*'
+
+1F
diff --git a/tests/yangre/files/two_patterns_err.txt b/tests/yangre/files/two_patterns_err.txt
new file mode 100644
index 0000000..78f9878
--- /dev/null
+++ b/tests/yangre/files/two_patterns_err.txt
@@ -0,0 +1,4 @@
+"[0-9a-fA-F]*"
+'[a-zA-Z0-9\-_.]*'
+
+@!@
diff --git a/tests/yangre/files/two_patterns_invert_match.txt b/tests/yangre/files/two_patterns_invert_match.txt
new file mode 100644
index 0000000..ffbd835
--- /dev/null
+++ b/tests/yangre/files/two_patterns_invert_match.txt
@@ -0,0 +1,4 @@
+"[a-z]*"
+ '[a-f]*'
+
+gh
diff --git a/tests/yangre/files/two_patterns_invert_match_err.txt b/tests/yangre/files/two_patterns_invert_match_err.txt
new file mode 100644
index 0000000..f182aab
--- /dev/null
+++ b/tests/yangre/files/two_patterns_invert_match_err.txt
@@ -0,0 +1,4 @@
+"[a-z]*"
+ '[a-f]*'
+
+ab
diff --git a/tests/yangre/invert_match.test b/tests/yangre/invert_match.test
new file mode 100644
index 0000000..707ca9d
--- /dev/null
+++ b/tests/yangre/invert_match.test
@@ -0,0 +1,28 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}]
+
+test invert_match_from_help1 {Test the first pattern from help via invert match} {
+ ly_cmd_err {-p {"[0-9a-fA-F]*"} -i {1F}} "not conform to inverted"
+ ly_cmd {-p {"[0-9a-fA-F]*"} -i {GUN}}
+} {}
+
+test invert_match_from_help2 {Test the second pattern from help via invert match} {
+ ly_cmd_err {-p {'[a-zA-Z0-9\-_.]*'} -i {a-b}} "not conform to inverted"
+ ly_cmd {-p {'[a-zA-Z0-9\-_.]*'} -i {%@}}
+} {}
+
+test invert_match_from_help3 {Test the third pattern from help via invert match} {
+ ly_cmd_err {-p {[xX][mM][lL].*} -i {xml-encoding}} "not conform to inverted"
+ ly_cmd {-p {[xX][mM][lL].*} -i {json}}
+} {}
+
+test invert_match_three_at_once {Test three inverted patterns and once} {
+ ly_cmd_err {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -i -p {[xX][mM][lL].*} -i {xml}} "not conform to inverted"
+ ly_cmd {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -i -p {[xX][mM][lL].*} -i {%@}}
+} {}
+
+test invert_match_second_is_not {Test three patterns but the second one is not inverted} {
+ ly_cmd_err {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -i -p {[xX][mM][lL].*} -i {o_O}} "not conform to inverted"
+ ly_cmd {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -p {[xX][mM][lL].*} -i {o_O}}
+} {}
+
+cleanupTests
diff --git a/tests/yangre/ly.tcl b/tests/yangre/ly.tcl
new file mode 100644
index 0000000..3bb62b5
--- /dev/null
+++ b/tests/yangre/ly.tcl
@@ -0,0 +1,17 @@
+# @brief The main source of functions and variables for testing yangre.
+
+package require tcltest
+namespace import ::tcltest::test ::tcltest::cleanupTests
+
+if { ![info exists ::env(TESTS_DIR)] } {
+ # the script is not run via 'ctest' so paths must be set
+ set ::env(TESTS_DIR) "./"
+ set TUT_PATH "../../build"
+} else {
+ # cmake (ctest) already sets ::env variables
+ set TUT_PATH $::env(YANGRE)
+}
+set TUT_NAME "yangre"
+source "$::env(TESTS_DIR)/../tool_ni.tcl"
+
+# The script continues by defining variables and functions specific to the yangre tool.
diff --git a/tests/yangre/pattern.test b/tests/yangre/pattern.test
new file mode 100644
index 0000000..45b7e3b
--- /dev/null
+++ b/tests/yangre/pattern.test
@@ -0,0 +1,19 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}]
+
+test pattern_from_help1 {Test the first pattern from help} {
+ ly_cmd {-p {"[0-9a-fA-F]*"} {1F}}
+} {}
+
+test pattern_from_help2 {Test the second pattern from help} {
+ ly_cmd {-p {'[a-zA-Z0-9\-_.]*'} {a-b}}
+} {}
+
+test pattern_from_help3 {Test the third pattern from help} {
+ ly_cmd {-p {[xX][mM][lL].*} {xml-encoding}}
+} {}
+
+test pattern_three_at_once {Test three patterns and once} {
+ ly_cmd {-p {"[0-9a-zA-Z]*"} -p {'[a-zA-Z0-9\-_.]*'} -p {[xX][mM][lL].*} {xml}}
+} {}
+
+cleanupTests
diff --git a/tools/lint/CMakeLists.txt b/tools/lint/CMakeLists.txt
index 32cdcbf..14f8b76 100644
--- a/tools/lint/CMakeLists.txt
+++ b/tools/lint/CMakeLists.txt
@@ -17,6 +17,12 @@
cmd_load.c
cmd_print.c
cmd_searchpath.c
+ cmd_extdata.c
+ cmd_help.c
+ cmd_verb.c
+ cmd_debug.c
+ yl_opt.c
+ yl_schema_features.c
common.c
)
if(YANGLINT_INTERACTIVE)
@@ -46,45 +52,3 @@
target_include_directories(yanglint PRIVATE ${GETOPT_INCLUDE_DIR})
target_link_libraries(yanglint ${GETOPT_LIBRARY})
endif()
-
-#
-# tests
-#
-function(add_yanglint_test)
- cmake_parse_arguments(ADDTEST "" "NAME;VIA;SCRIPT" "" ${ARGN})
- set(TEST_NAME yanglint_${ADDTEST_NAME})
-
- if(${ADDTEST_VIA} STREQUAL "bash")
- set(WRAPPER /usr/bin/env bash)
- elseif(${ADDTEST_VIA} STREQUAL "expect")
- set(WRAPPER ${PATH_EXPECT})
- else()
- message(FATAL_ERROR "build: unexpected wrapper '${ADDTEST_VIA}'")
- endif()
-
- add_test(NAME ${TEST_NAME} COMMAND ${WRAPPER} ${CMAKE_CURRENT_SOURCE_DIR}/tests/${ADDTEST_SCRIPT})
- set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANGLINT=${PROJECT_BINARY_DIR}/yanglint")
- set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANG_MODULES_DIR=${PROJECT_SOURCE_DIR}/tests/modules/yang")
- set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
-endfunction(add_yanglint_test)
-
-if(ENABLE_TESTS)
- # tests of non-interactive mode using shunit2
- find_program(PATH_SHUNIT NAMES shunit2)
- if(NOT PATH_SHUNIT)
- message(WARNING "'shunit2' not found! The yanglint(1) non-interactive tests will not be available.")
- else()
- add_yanglint_test(NAME ni_list VIA bash SCRIPT shunit2/list.sh)
- add_yanglint_test(NAME ni_feature VIA bash SCRIPT shunit2/feature.sh)
- endif()
-
- # tests of interactive mode using expect
- find_program(PATH_EXPECT NAMES expect)
- if(NOT PATH_EXPECT)
- message(WARNING "'expect' not found! The yanglint(1) interactive tests will not be available.")
- elseif(YANGLINT_INTERACTIVE)
- add_yanglint_test(NAME in_list VIA expect SCRIPT expect/list.exp)
- add_yanglint_test(NAME in_feature VIA expect SCRIPT expect/feature.exp)
- add_yanglint_test(NAME in_completion VIA expect SCRIPT expect/completion.exp)
- endif()
-endif()
diff --git a/tools/lint/cmd.c b/tools/lint/cmd.c
index 10e7446..344900d 100644
--- a/tools/lint/cmd.c
+++ b/tools/lint/cmd.c
@@ -2,9 +2,10 @@
* @file cmd.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief libyang's yanglint tool general commands
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -30,230 +31,80 @@
COMMAND commands[];
extern int done;
-#ifndef NDEBUG
-
void
-cmd_debug_help(void)
+cmd_free(void)
{
- printf("Usage: debug (dict | xpath)+\n");
-}
+ uint16_t i;
-void
-cmd_debug(struct ly_ctx **UNUSED(ctx), const char *cmdline)
-{
- int argc = 0;
- char **argv = NULL;
- int opt, opt_index;
- struct option options[] = {
- {"help", no_argument, NULL, 'h'},
- {NULL, 0, NULL, 0}
- };
- uint32_t dbg_groups = 0;
-
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
- }
-
- while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
- switch (opt) {
- case 'h':
- cmd_debug_help();
- goto cleanup;
- default:
- YLMSG_E("Unknown option.\n");
- goto cleanup;
+ for (i = 0; commands[i].name; i++) {
+ if (commands[i].free_func) {
+ commands[i].free_func();
}
}
- if (argc == optind) {
- /* no argument */
- cmd_debug_help();
- goto cleanup;
- }
-
- for (int i = 0; i < argc - optind; i++) {
- if (!strcasecmp("dict", argv[optind + i])) {
- dbg_groups |= LY_LDGDICT;
- } else if (!strcasecmp("xpath", argv[optind + i])) {
- dbg_groups |= LY_LDGXPATH;
- } else {
- YLMSG_E("Unknown debug group \"%s\"\n", argv[optind + 1]);
- goto cleanup;
- }
- }
-
- ly_log_dbg_groups(dbg_groups);
-
-cleanup:
- free_cmdline(argv);
}
-#endif
-
-void
-cmd_verb_help(void)
-{
- printf("Usage: verb (error | warning | verbose | debug)\n");
-}
-
-void
-cmd_verb(struct ly_ctx **UNUSED(ctx), const char *cmdline)
-{
- int argc = 0;
- char **argv = NULL;
- int opt, opt_index;
- struct option options[] = {
- {"help", no_argument, NULL, 'h'},
- {NULL, 0, NULL, 0}
- };
-
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
- }
-
- while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
- switch (opt) {
- case 'h':
- cmd_verb_help();
- goto cleanup;
- default:
- YLMSG_E("Unknown option.\n");
- goto cleanup;
- }
- }
-
- if (argc - optind > 1) {
- YLMSG_E("Only a single verbosity level can be set.\n");
- cmd_verb_help();
- goto cleanup;
- } else if (argc == optind) {
- /* no argument - print current value */
- LY_LOG_LEVEL level = ly_log_level(LY_LLERR);
-
- ly_log_level(level);
- printf("Current verbosity level: ");
- if (level == LY_LLERR) {
- printf("error\n");
- } else if (level == LY_LLWRN) {
- printf("warning\n");
- } else if (level == LY_LLVRB) {
- printf("verbose\n");
- } else if (level == LY_LLDBG) {
- printf("debug\n");
- }
- goto cleanup;
- }
-
- if (!strcasecmp("error", argv[optind]) || !strcmp("0", argv[optind])) {
- ly_log_level(LY_LLERR);
- } else if (!strcasecmp("warning", argv[optind]) || !strcmp("1", argv[optind])) {
- ly_log_level(LY_LLWRN);
- } else if (!strcasecmp("verbose", argv[optind]) || !strcmp("2", argv[optind])) {
- ly_log_level(LY_LLVRB);
- } else if (!strcasecmp("debug", argv[optind]) || !strcmp("3", argv[optind])) {
- ly_log_level(LY_LLDBG);
- } else {
- YLMSG_E("Unknown verbosity \"%s\"\n", argv[optind]);
- goto cleanup;
- }
-
-cleanup:
- free_cmdline(argv);
-}
-
-void
-cmd_quit(struct ly_ctx **UNUSED(ctx), const char *UNUSED(cmdline))
+int
+cmd_quit_exec(struct ly_ctx **UNUSED(ctx), struct yl_opt *UNUSED(yo), const char *UNUSED(posv))
{
done = 1;
- return;
+ return 0;
}
-void
-cmd_help_help(void)
-{
- printf("Usage: help [cmd ...]\n");
-}
-
-void
-cmd_help(struct ly_ctx **UNUSED(ctx), const char *cmdline)
-{
- int argc = 0;
- char **argv = NULL;
- int opt, opt_index;
- struct option options[] = {
- {"help", no_argument, NULL, 'h'},
- {NULL, 0, NULL, 0}
- };
-
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
- }
-
- while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
- switch (opt) {
- case 'h':
- cmd_help_help();
- goto cleanup;
- default:
- YLMSG_E("Unknown option.\n");
- goto cleanup;
- }
- }
-
- if (argc == optind) {
-generic_help:
- printf("Available commands:\n");
- for (uint16_t i = 0; commands[i].name; i++) {
- if (commands[i].helpstring != NULL) {
- printf(" %-15s %s\n", commands[i].name, commands[i].helpstring);
- }
- }
- } else {
- /* print specific help for the selected command(s) */
-
- for (int c = 0; c < argc - optind; ++c) {
- int8_t match = 0;
-
- /* get the command of the specified name */
- for (uint16_t i = 0; commands[i].name; i++) {
- if (strcmp(argv[optind + c], commands[i].name) == 0) {
- match = 1;
- if (commands[i].help_func != NULL) {
- commands[i].help_func();
- } else {
- printf("%s: %s\n", argv[optind + c], commands[i].helpstring);
- }
- break;
- }
- }
- if (!match) {
- /* if unknown command specified, print the list of commands */
- printf("Unknown command \'%s\'\n", argv[optind + c]);
- goto generic_help;
- }
- }
- }
-
-cleanup:
- free_cmdline(argv);
-}
-
+/* Also keep enum COMMAND_INDEX updated. */
COMMAND commands[] = {
- {"help", cmd_help, cmd_help_help, "Display commands description"},
- {"add", cmd_add, cmd_add_help, "Add a new module from a specific file"},
- {"load", cmd_load, cmd_load_help, "Load a new schema from the searchdirs"},
- {"print", cmd_print, cmd_print_help, "Print a module"},
- {"data", cmd_data, cmd_data_help, "Load, validate and optionally print instance data"},
- {"list", cmd_list, cmd_list_help, "List all the loaded modules"},
- {"feature", cmd_feature, cmd_feature_help, "Print all features of module(s) with their state"},
- {"searchpath", cmd_searchpath, cmd_searchpath_help, "Print/set the search path(s) for schemas"},
- {"clear", cmd_clear, cmd_clear_help, "Clear the context - remove all the loaded modules"},
- {"verb", cmd_verb, cmd_verb_help, "Change verbosity"},
+ {
+ "help", cmd_help_opt, NULL, cmd_help_exec, NULL, cmd_help_help, NULL,
+ "Display commands description", "h"
+ },
+ {
+ "add", cmd_add_opt, cmd_add_dep, cmd_add_exec, NULL, cmd_add_help, NULL,
+ "Add a new module from a specific file", "DF:hiX"
+ },
+ {
+ "load", cmd_load_opt, cmd_load_dep, cmd_load_exec, NULL, cmd_load_help, NULL,
+ "Load a new schema from the searchdirs", "F:hiX"
+ },
+ {
+ "print", cmd_print_opt, cmd_print_dep, cmd_print_exec, NULL, cmd_print_help, NULL,
+ "Print a module", "f:hL:o:P:q"
+ },
+ {
+ "data", cmd_data_opt, cmd_data_dep, cmd_data_store, cmd_data_process, cmd_data_help, NULL,
+ "Load, validate and optionally print instance data", "d:ef:F:hmo:O:R:r:nt:x:"
+ },
+ {
+ "list", cmd_list_opt, cmd_list_dep, cmd_list_exec, NULL, cmd_list_help, NULL,
+ "List all the loaded modules", "f:h"
+ },
+ {
+ "feature", cmd_feature_opt, cmd_feature_dep, cmd_feature_exec, cmd_feature_fin, cmd_feature_help, NULL,
+ "Print all features of module(s) with their state", "haf"
+ },
+ {
+ "searchpath", cmd_searchpath_opt, NULL, cmd_searchpath_exec, NULL, cmd_searchpath_help, NULL,
+ "Print/set the search path(s) for schemas", "ch"
+ },
+ {
+ "extdata", cmd_extdata_opt, cmd_extdata_dep, cmd_extdata_exec, NULL, cmd_extdata_help, cmd_extdata_free,
+ "Set the specific data required by an extension", "ch"
+ },
+ {
+ "clear", cmd_clear_opt, cmd_clear_dep, cmd_clear_exec, NULL, cmd_clear_help, NULL,
+ "Clear the context - remove all the loaded modules", "iyhY:"
+ },
+ {
+ "verb", cmd_verb_opt, cmd_verb_dep, cmd_verb_exec, NULL, cmd_verb_help, NULL,
+ "Change verbosity", "h"
+ },
#ifndef NDEBUG
- {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups"},
+ {
+ "debug", cmd_debug_opt, cmd_debug_dep, cmd_debug_store, cmd_debug_setlog, cmd_debug_help, NULL,
+ "Display specific debug message groups", "h"
+ },
#endif
- {"quit", cmd_quit, NULL, "Quit the program"},
+ {"quit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"},
/* synonyms for previous commands */
- {"?", cmd_help, NULL, "Display commands description"},
- {"exit", cmd_quit, NULL, "Quit the program"},
- {NULL, NULL, NULL, NULL}
+ {"?", NULL, NULL, cmd_help_exec, NULL, NULL, NULL, "Display commands description", "h"},
+ {"exit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"},
+ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
};
diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h
index 9f6f88d..bd2f2f2 100644
--- a/tools/lint/cmd.h
+++ b/tools/lint/cmd.h
@@ -2,9 +2,10 @@
* @file cmd.h
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief libyang's yanglint tool commands header
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -18,15 +19,44 @@
#include "libyang.h"
+struct yl_opt;
+
/**
* @brief command information
+ *
+ * Callback functions are in the order they should be called.
+ * First, the 'opt_func' should be called, which parses arguments from the command line and sets flags or pointers in
+ * the struct yl_opt. This type of function is for interactive mode and is optional.
+ * Then the 'dep_func' callback can check the struct yl_opt settings. Other items that depend on them can also be
+ * set. There is also an possibility for controlling the number of positional arguments and its implications.
+ * The most important callback is 'exec_func' where the command itself is executed. This function can even replace the
+ * entire libyang context. The function parameters are mainly found in the yl_opt structure. Optionally, the function
+ * can be called with a positional argument obtained from the command line. Some 'exec_func' are adapted to be called
+ * from non-interactive yanglint mode.
+ * The 'fun_func' complements the 'exec_func'. In some cases, the command execution must be divided into two stages.
+ * For example, the 'exec_func' is used to fill some items in the yl_opt structure from the positional
+ * arguments and then the 'fin_func' is used to perform the final action.
*/
typedef struct {
- char *name; /* User printable name of the function. */
+ /* User printable name of the function. */
+ char *name;
- void (*func)(struct ly_ctx **ctx, const char *); /* Function to call to do the command. */
- void (*help_func)(void); /* Display command help. */
- char *helpstring; /* Documentation for this function. */
+ /* Convert command line options to the data struct yl_opt. */
+ int (*opt_func)(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+ /* Additionally set dependent items and perform error checking. */
+ int (*dep_func)(struct yl_opt *yo, int posc);
+ /* Execute command. */
+ int (*exec_func)(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+ /* Finish execution of command. */
+ int (*fin_func)(struct ly_ctx *ctx, struct yl_opt *yo);
+ /* Display command help. */
+ void (*help_func)(void);
+ /* Freeing global variables allocated by the command. */
+ void (*free_func)(void);
+ /* Documentation for this function. */
+ char *helpstring;
+ /* Option characters used in function getopt_long. */
+ char *optstring;
} COMMAND;
/**
@@ -34,36 +64,333 @@
*/
extern COMMAND commands[];
+/**
+ * @brief Index for global variable ::commands.
+ */
+enum COMMAND_INDEX {
+ CMD_HELP = 0,
+ CMD_ADD,
+ CMD_LOAD,
+ CMD_PRINT,
+ CMD_DATA,
+ CMD_LIST,
+ CMD_FEATURE,
+ CMD_SEARCHPATH,
+ CMD_EXTDATA,
+ CMD_CLEAR,
+ CMD_VERB,
+#ifndef NDEBUG
+ CMD_DEBUG,
+#endif
+};
+
+/**
+ * @brief For each cmd, call the COMMAND.free_func in the variable 'commands'.
+ */
+void cmd_free(void);
+
/* cmd_add.c */
-void cmd_add(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @brief Parse the arguments of an interactive command.
+ *
+ * @param[out] yo Context for yanglint.
+ * @param[in] cmdline String containing command line arguments.
+ * @param[out] posv Pointer to argv to a section of positional arguments.
+ * @param[out] posc Number of positional arguments.
+ * @return 0 on success.
+ */
+int cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @brief Check the options and set dependent items in @p yo.
+ *
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posc number of positional arguments.
+ * @return 0 on success.
+ */
+int cmd_add_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Parse and compile a new module using filepath.
+ *
+ * @param[in,out] ctx Context for libyang.
+ * @param[in,out] yo Context for yanglint.
+ * @param[in] posv Path to the file where the new module is located.
+ * @return 0 on success.
+ */
+int cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
void cmd_add_help(void);
/* cmd_clear.c */
-void cmd_clear(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_clear_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Clear libyang context.
+ *
+ * @param[in,out] ctx context for libyang that will be replaced with an empty one.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv not used.
+ * @return 0 on success.
+ */
+int cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
void cmd_clear_help(void);
/* cmd_data.c */
-void cmd_data(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_data_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Store data file for later processing.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Path to the file where the data is located.
+ * @return 0 on success.
+ */
+int cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+
+/**
+ * @brief Parse, validate and optionally print data instances.
+ *
+ * @param[in] ctx Context for libyang.
+ * @param[in] yo Context of yanglint. All necessary parameters should already be set.
+ * @return 0 on success.
+ */
+int cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo);
void cmd_data_help(void);
/* cmd_list.c */
-void cmd_list(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_list_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Print the list of modules in the current context.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Not used.
+ * @return 0 on success.
+ */
+int cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
void cmd_list_help(void);
/* cmd_feature.c */
-void cmd_feature(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_feature_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Print the features the modules.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the module which features are to be printed.
+ * @return 0 on success.
+ */
+int cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+
+/**
+ * @brief Printing of features ends.
+ *
+ * @param[in] ctx context for libyang. Not used.
+ * @param[in] yo context for yanglint.
+ * @return 0 on success.
+ */
+int cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo);
void cmd_feature_help(void);
/* cmd_load.c */
-void cmd_load(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_load_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Parse and compile a new module using module name.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the module to be loaded into the context.
+ * @return 0 on success.
+ */
+int cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
void cmd_load_help(void);
/* cmd_print.c */
-void cmd_print(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_print_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Print a schema module.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the module to be printed. Can be NULL in the case of printing a node.
+ * @return 0 on success.
+ */
+int cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
void cmd_print_help(void);
/* cmd_searchpath.c */
-void cmd_searchpath(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @brief Set the paths of directories where to search schema modules.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Path to the directory. Can be NULL in the case of printing a current searchdirs.
+ * @return 0 on success.
+ */
+int cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
void cmd_searchpath_help(void);
+/* cmd_extdata.c */
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_extdata_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Set path to the file required by the extension.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Path to the directory. Can be NULL in the case of printing a current path.
+ * @return 0 on success.
+ */
+int cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+void cmd_extdata_help(void);
+void cmd_extdata_free(void);
+
+/* cmd_help.c */
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @brief Print help.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the command which help message is to be printed. Can be NULL.
+ * @return 0 on success.
+ */
+int cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+void cmd_help_help(void);
+
+/* cmd_verb.c */
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_verb_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Set the verbose level.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the verbose level to be set.
+ * @return 0 on success.
+ */
+int cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+void cmd_verb_help(void);
+
+/* cmd_debug.c */
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_debug_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Store the type of debug messages for later processing.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the debug type to be set.
+ * @return 0 on success.
+ */
+int cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+
+/**
+ * @brief Set debug logging.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint. All necessary parameters should already be set.
+ * @return 0 on success.
+ */
+int cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo);
+void cmd_debug_help(void);
+
#endif /* COMMANDS_H_ */
diff --git a/tools/lint/cmd_add.c b/tools/lint/cmd_add.c
index bbfdfd6..6a9af8d 100644
--- a/tools/lint/cmd_add.c
+++ b/tools/lint/cmd_add.c
@@ -2,9 +2,10 @@
* @file cmd_add.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief 'add' command of the libyang's yanglint tool.
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -17,8 +18,8 @@
#include "cmd.h"
+#include <assert.h>
#include <getopt.h>
-#include <libgen.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -26,6 +27,8 @@
#include "libyang.h"
#include "common.h"
+#include "yl_opt.h"
+#include "yl_schema_features.h"
void
cmd_add_help(void)
@@ -44,133 +47,164 @@
" -i, --make-implemented\n"
" Make the imported modules \"referenced\" from any loaded\n"
" <schema> module also implemented. If specified a second time,\n"
- " all the modules are set implemented.\n");
+ " all the modules are set implemented.\n"
+ " -X, --extended-leafref\n"
+ " Allow usage of deref() XPath function within leafref.\n");
}
-void
-cmd_add(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
{
- int argc = 0;
- char **argv = NULL;
+ int rc = 0, argc = 0;
int opt, opt_index;
struct option options[] = {
{"disable-searchdir", no_argument, NULL, 'D'},
{"features", required_argument, NULL, 'F'},
{"help", no_argument, NULL, 'h'},
{"make-implemented", no_argument, NULL, 'i'},
+ {"extended-leafref", no_argument, NULL, 'X'},
{NULL, 0, NULL, 0}
};
- uint16_t options_ctx = 0;
- const char *all_features[] = {"*", NULL};
- struct ly_set fset = {0};
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
}
- while ((opt = getopt_long(argc, argv, "D:F:hi", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_ADD].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'D': /* --disable--search */
- if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) {
+ if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) {
YLMSG_W("The -D option specified too many times.\n");
}
- if (options_ctx & LY_CTX_DISABLE_SEARCHDIR_CWD) {
- options_ctx &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
- options_ctx |= LY_CTX_DISABLE_SEARCHDIRS;
- } else {
- options_ctx |= LY_CTX_DISABLE_SEARCHDIR_CWD;
- }
+ yo_opt_update_disable_searchdir(yo);
break;
case 'F': /* --features */
- if (parse_features(optarg, &fset)) {
- goto cleanup;
+ if (parse_features(optarg, &yo->schema_features)) {
+ return 1;
}
break;
case 'h':
cmd_add_help();
- goto cleanup;
+ return 1;
case 'i': /* --make-implemented */
- if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
- options_ctx &= ~LY_CTX_REF_IMPLEMENTED;
- options_ctx |= LY_CTX_ALL_IMPLEMENTED;
- } else {
- options_ctx |= LY_CTX_REF_IMPLEMENTED;
- }
+ yo_opt_update_make_implemented(yo);
+ break;
+
+ case 'X': /* --extended-leafref */
+ yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED;
break;
default:
YLMSG_E("Unknown option.\n");
- goto cleanup;
+ return 1;
}
}
- if (argc == optind) {
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return 0;
+}
+
+int
+cmd_add_dep(struct yl_opt *yo, int posc)
+{
+ if (yo->interactive && !posc) {
/* no argument */
cmd_add_help();
+ return 1;
+ }
+ if (!yo->schema_features.count) {
+ /* no features, enable all of them */
+ yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES;
+ }
+
+ return 0;
+}
+
+static int
+store_parsed_module(const char *filepath, struct lys_module *mod, struct yl_opt *yo)
+{
+ assert(!yo->interactive);
+
+ if (yo->schema_out_format || yo->feature_param_format) {
+ if (ly_set_add(&yo->schema_modules, (void *)mod, 1, NULL)) {
+ YLMSG_E("Storing parsed schema module (%s) for print failed.\n", filepath);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int
+cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ const char *all_features[] = {"*", NULL};
+ LY_ERR ret;
+ uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */
+ char *dir, *modname = NULL;
+ const char **features = NULL;
+ struct ly_in *in = NULL;
+ struct lys_module *mod;
+
+ assert(posv);
+
+ if (yo->ctx_options) {
+ ly_ctx_set_options(*ctx, yo->ctx_options);
+ }
+
+ if (parse_schema_path(posv, &dir, &modname)) {
+ return 1;
+ }
+
+ if (!yo->interactive) {
+ /* The module should already be parsed if yang_lib_file was set. */
+ if (yo->yang_lib_file && (mod = ly_ctx_get_module_implemented(*ctx, modname))) {
+ ret = store_parsed_module(posv, mod, yo);
+ goto cleanup;
+ }
+ /* parse module */
+ }
+
+ /* add temporarily also the path of the module itself */
+ if (ly_ctx_set_searchdir(*ctx, dir) == LY_EEXIST) {
+ path_unset = 0;
+ }
+
+ /* get features list for this module */
+ if (!yo->schema_features.count) {
+ features = all_features;
+ } else {
+ get_features(&yo->schema_features, modname, &features);
+ }
+
+ /* prepare input handler */
+ ret = ly_in_new_filepath(posv, 0, &in);
+ if (ret) {
goto cleanup;
}
- if (!fset.count) {
- /* no features, enable all of them */
- options_ctx |= LY_CTX_ENABLE_IMP_FEATURES;
+ /* parse the file */
+ ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, &mod);
+ ly_in_free(in, 1);
+ ly_ctx_unset_searchdir_last(*ctx, path_unset);
+ if (ret) {
+ goto cleanup;
}
- if (options_ctx) {
- ly_ctx_set_options(*ctx, options_ctx);
- }
-
- for (int i = 0; i < argc - optind; i++) {
- /* process the schema module files */
- LY_ERR ret;
- uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */
- char *dir, *module;
- const char **features = NULL;
- struct ly_in *in = NULL;
-
- if (parse_schema_path(argv[optind + i], &dir, &module)) {
- goto cleanup;
- }
-
- /* add temporarily also the path of the module itself */
- if (ly_ctx_set_searchdir(*ctx, dirname(dir)) == LY_EEXIST) {
- path_unset = 0;
- }
-
- /* get features list for this module */
- if (!fset.count) {
- features = all_features;
- } else {
- get_features(&fset, module, &features);
- }
-
- /* temporary cleanup */
- free(dir);
- free(module);
-
- /* prepare input handler */
- ret = ly_in_new_filepath(argv[optind + i], 0, &in);
- if (ret) {
- goto cleanup;
- }
-
- /* parse the file */
- ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, NULL);
- ly_in_free(in, 1);
- ly_ctx_unset_searchdir_last(*ctx, path_unset);
-
- if (ret) {
- /* libyang printed the error messages */
+ if (!yo->interactive) {
+ if ((ret = store_parsed_module(posv, mod, yo))) {
goto cleanup;
}
}
cleanup:
- if (options_ctx) {
- ly_ctx_unset_options(*ctx, options_ctx);
- }
- ly_set_erase(&fset, free_features);
- free_cmdline(argv);
+ free(dir);
+ free(modname);
+
+ return ret;
}
diff --git a/tools/lint/cmd_clear.c b/tools/lint/cmd_clear.c
index 5eed6ff..233cc92 100644
--- a/tools/lint/cmd_clear.c
+++ b/tools/lint/cmd_clear.c
@@ -2,9 +2,10 @@
* @file cmd_clear.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief 'clear' command of the libyang's yanglint tool.
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -24,6 +25,7 @@
#include "libyang.h"
#include "common.h"
+#include "yl_opt.h"
void
cmd_clear_help(void)
@@ -32,68 +34,139 @@
" Replace the current context with an empty one, searchpaths\n"
" are not kept.\n\n"
" -i, --make-implemented\n"
- " Make the imported modules \"referenced\" from any loaded\n"
- " module also implemented. If specified a second time, all the\n"
- " modules are set implemented.\n"
+ " When loading a module into the context, the imported 'referenced'\n"
+ " modules will also be implemented. If specified a second time,\n"
+ " all the modules will be implemented.\n"
" -y, --yang-library\n"
" Load and implement internal \"ietf-yang-library\" YANG module.\n"
" Note that this module includes definitions of mandatory state\n"
- " data that can result in unexpected data validation errors.\n");
-#if 0
- " If <yang-library-data> path specified, load the modules\n"
- " according to the provided yang library data.\n"
-#endif
+ " data that can result in unexpected data validation errors.\n"
+ " -Y FILE, --yang-library-file=FILE\n"
+ " Parse FILE with \"ietf-yang-library\" data and use them to\n"
+ " create an exact YANG schema context. Searchpaths defined so far\n"
+ " are used, but then deleted.\n");
}
-void
-cmd_clear(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
{
- int argc = 0;
- char **argv = NULL;
+ int rc = 0, argc = 0;
int opt, opt_index;
struct option options[] = {
- {"make-implemented", no_argument, NULL, 'i'},
- {"yang-library", no_argument, NULL, 'y'},
+ {"make-implemented", no_argument, NULL, 'i'},
+ {"yang-library", no_argument, NULL, 'y'},
+ {"yang-library-file", no_argument, NULL, 'Y'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
- uint16_t options_ctx = LY_CTX_NO_YANGLIBRARY;
- struct ly_ctx *ctx_new;
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
+ yo->ctx_options = LY_CTX_NO_YANGLIBRARY;
+
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
}
- while ((opt = getopt_long(argc, argv, "iyh", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_CLEAR].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'i':
- if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
- options_ctx &= ~LY_CTX_REF_IMPLEMENTED;
- options_ctx |= LY_CTX_ALL_IMPLEMENTED;
+ if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) {
+ yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED;
+ yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED;
} else {
- options_ctx |= LY_CTX_REF_IMPLEMENTED;
+ yo->ctx_options |= LY_CTX_REF_IMPLEMENTED;
}
break;
case 'y':
- options_ctx &= ~LY_CTX_NO_YANGLIBRARY;
+ yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+ break;
+ case 'Y':
+ yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+ yo->yang_lib_file = optarg;
+ if (!yo->yang_lib_file) {
+ YLMSG_E("Memory allocation error.\n");
+ return 1;
+ }
break;
case 'h':
cmd_clear_help();
- goto cleanup;
+ return 1;
default:
YLMSG_E("Unknown option.\n");
- goto cleanup;
+ return 1;
}
}
- if (ly_ctx_new(NULL, options_ctx, &ctx_new)) {
- YLMSG_W("Failed to create context.\n");
- goto cleanup;
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return rc;
+}
+
+int
+cmd_clear_dep(struct yl_opt *yo, int posc)
+{
+ (void) yo;
+
+ if (posc) {
+ YLMSG_E("No positional arguments are allowed.\n");
+ return 1;
}
+ return 0;
+}
+
+/**
+ * @brief Convert searchpaths into single string.
+ *
+ * @param[in] ctx Context with searchpaths.
+ * @param[out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":"
+ * (on Windows, used semicolon ";" instead).
+ * @return LY_ERR value.
+ */
+static LY_ERR
+searchpaths_to_str(const struct ly_ctx *ctx, char **searchpaths)
+{
+ uint32_t i;
+ int rc = 0;
+ const char * const *dirs = ly_ctx_get_searchdirs(ctx);
+
+ for (i = 0; dirs[i]; ++i) {
+ rc = searchpath_strcat(searchpaths, dirs[i]);
+ if (!rc) {
+ break;
+ }
+ }
+
+ return rc;
+}
+
+int
+cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ (void) posv;
+ struct ly_ctx *ctx_new = NULL;
+
+ if (yo->yang_lib_file) {
+ if (searchpaths_to_str(*ctx, &yo->searchpaths)) {
+ YLMSG_E("Storing searchpaths failed.\n");
+ return 1;
+ }
+ if (ly_ctx_new_ylpath(yo->searchpaths, yo->yang_lib_file, LYD_UNKNOWN, yo->ctx_options, &ctx_new)) {
+ YLMSG_E("Unable to create libyang context with yang-library data.\n");
+ return 1;
+ }
+ } else {
+ if (ly_ctx_new(NULL, yo->ctx_options, &ctx_new)) {
+ YLMSG_W("Failed to create context.\n");
+ return 1;
+ }
+ }
+
+ /* Global variables in commands are also deleted. */
+ cmd_free();
+
ly_ctx_destroy(*ctx);
*ctx = ctx_new;
-cleanup:
- free_cmdline(argv);
+ return 0;
}
diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c
index 69f94bc..9fa1b95 100644
--- a/tools/lint/cmd_data.c
+++ b/tools/lint/cmd_data.c
@@ -2,9 +2,10 @@
* @file cmd_data.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief 'data' command of the libyang's yanglint tool.
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -17,6 +18,7 @@
#include "cmd.h"
+#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <stdint.h>
@@ -27,18 +29,23 @@
#include "libyang.h"
#include "common.h"
+#include "yl_opt.h"
-void
-cmd_data_help(void)
+static void
+cmd_data_help_header(void)
{
printf("Usage: data [-emn] [-t TYPE]\n"
" [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n"
" data [-n] -t (rpc | notif | reply) [-O FILE]\n"
" [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n"
" data [-en] [-t TYPE] [-F FORMAT] -x XPATH [-o OUTFILE] <data1> ...\n"
- " Parse, validate and optionally print data instances\n\n"
+ " Parse, validate and optionally print data instances\n");
+}
- " -t TYPE, --type=TYPE\n"
+static void
+cmd_data_help_type(void)
+{
+ printf(" -t TYPE, --type=TYPE\n"
" Specify data tree type in the input data file(s):\n"
" data - Complete datastore with status data (default type).\n"
" config - Configuration datastore (without status data).\n"
@@ -47,14 +54,73 @@
" edit - Content of the NETCONF <edit-config> operation.\n"
" rpc - Content of the NETCONF <rpc> message, defined as YANG's\n"
" RPC/Action input statement.\n"
+ " nc-rpc - Similar to 'rpc' but expect and check also the NETCONF\n"
+ " envelopes <rpc> or <action>.\n"
" reply - Reply to the RPC/Action. Note that the reply data are\n"
" expected inside a container representing the original\n"
" RPC/Action. This is necessary to identify appropriate\n"
" data definitions in the schema module.\n"
+ " nc-reply - Similar to 'reply' but expect and check also the NETCONF\n"
+ " envelope <rpc-reply> with output data nodes as direct\n"
+ " descendants. The original RPC/action invocation is expected\n"
+ " in a separate parameter '-R' and is parsed as 'nc-rpc'.\n"
" notif - Notification instance (content of the <notification>\n"
- " element without <eventTime>).\n\n"
+ " element without <eventTime>).\n"
+ " nc-notif - Similar to 'notif' but expect and check also the NETCONF\n"
+ " envelope <notification> with element <eventTime> and its\n"
+ " sibling as the actual notification.\n");
+}
- " -e, --present Validate only with the schema modules whose data actually\n"
+static void
+cmd_data_help_format(void)
+{
+ printf(" -f FORMAT, --format=FORMAT\n"
+ " Print the data in one of the following formats:\n"
+ " xml, json, lyb\n"
+ " Note that the LYB format requires the -o option specified.\n");
+}
+
+static void
+cmd_data_help_in_format(void)
+{
+ printf(" -F FORMAT, --in-format=FORMAT\n"
+ " Load the data in one of the following formats:\n"
+ " xml, json, lyb\n"
+ " If input format not specified, it is detected from the file extension.\n");
+}
+
+static void
+cmd_data_help_default(void)
+{
+ printf(" -d MODE, --default=MODE\n"
+ " Print data with default values, according to the MODE\n"
+ " (to print attributes, ietf-netconf-with-defaults model\n"
+ " must be loaded):\n"
+ " all - Add missing default nodes.\n"
+ " all-tagged - Add missing default nodes and mark all the default\n"
+ " nodes with the attribute.\n"
+ " trim - Remove all nodes with a default value.\n"
+ " implicit-tagged - Add missing nodes and mark them with the attribute.\n");
+}
+
+static void
+cmd_data_help_xpath(void)
+{
+ printf(" -x XPATH, --xpath=XPATH\n"
+ " Evaluate XPATH expression and print the nodes satisfying the\n"
+ " expression. The output format is specific and the option cannot\n"
+ " be combined with the -f and -d options. Also all the data\n"
+ " inputs are merged into a single data tree where the expression\n"
+ " is evaluated, so the -m option is always set implicitly.\n");
+}
+
+void
+cmd_data_help(void)
+{
+ cmd_data_help_header();
+ printf("\n");
+ cmd_data_help_type();
+ printf(" -e, --present Validate only with the schema modules whose data actually\n"
" exist in the provided input data files. Takes effect only\n"
" with the 'data' or 'config' TYPEs. Used to avoid requiring\n"
" mandatory nodes from modules which data are not present in the\n"
@@ -64,47 +130,28 @@
" In case of using -x option, the data are always merged.\n"
" -n, --not-strict\n"
" Do not require strict data parsing (silently skip unknown data),\n"
- " has no effect for schemas.\n\n"
+ " has no effect for schemas.\n"
" -O FILE, --operational=FILE\n"
" Provide optional data to extend validation of the 'rpc',\n"
" 'reply' or 'notif' TYPEs. The FILE is supposed to contain\n"
- " the :running configuration datastore and state data\n"
- " (operational datastore) referenced from the RPC/Notification.\n\n"
-
- " -f FORMAT, --format=FORMAT\n"
- " Print the data in one of the following formats:\n"
- " xml, json, lyb\n"
- " Note that the LYB format requires the -o option specified.\n"
- " -F FORMAT, --in-format=FORMAT\n"
- " Load the data in one of the following formats:\n"
- " xml, json, lyb\n"
- " If input format not specified, it is detected from the file extension.\n"
- " -d MODE, --default=MODE\n"
- " Print data with default values, according to the MODE\n"
- " (to print attributes, ietf-netconf-with-defaults model\n"
- " must be loaded):\n"
- " all - Add missing default nodes.\n"
- " all-tagged - Add missing default nodes and mark all the default\n"
- " nodes with the attribute.\n"
- " trim - Remove all nodes with a default value.\n"
- " implicit-tagged - Add missing nodes and mark them with the attribute.\n"
- " -o OUTFILE, --output=OUTFILE\n"
- " Write the output to OUTFILE instead of stdout.\n\n"
-
- " -x XPATH, --xpath=XPATH\n"
- " Evaluate XPATH expression and print the nodes satysfying the.\n"
- " expression. The output format is specific and the option cannot\n"
- " be combined with the -f and -d options. Also all the data\n"
- " inputs are merged into a single data tree where the expression\n"
- " is evaluated, so the -m option is always set implicitly.\n\n");
-
+ " the operational datastore referenced from the operation.\n"
+ " In case of a nested notification or action, its parent\n"
+ " existence is also checked in these operational data.\n"
+ " -R FILE, --reply-rpc=FILE\n"
+ " Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n"
+ " is supposed to contain the source 'nc-rpc' operation of the reply.\n");
+ cmd_data_help_format();
+ cmd_data_help_in_format();
+ printf(" -o OUTFILE, --output=OUTFILE\n"
+ " Write the output to OUTFILE instead of stdout.\n");
+ cmd_data_help_xpath();
+ printf("\n");
}
-void
-cmd_data(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
{
- int argc = 0;
- char **argv = NULL;
+ int rc = 0, argc = 0;
int opt, opt_index;
struct option options[] = {
{"defaults", required_argument, NULL, 'd'},
@@ -115,214 +162,525 @@
{"merge", no_argument, NULL, 'm'},
{"output", required_argument, NULL, 'o'},
{"operational", required_argument, NULL, 'O'},
+ {"reply-rpc", required_argument, NULL, 'R'},
{"not-strict", no_argument, NULL, 'n'},
{"type", required_argument, NULL, 't'},
{"xpath", required_argument, NULL, 'x'},
{NULL, 0, NULL, 0}
};
- uint8_t data_merge = 0;
- uint32_t options_print = 0;
- uint32_t options_parse = YL_DEFAULT_DATA_PARSE_OPTIONS;
- uint32_t options_validate = YL_DEFAULT_DATA_VALIDATE_OPTIONS;
- enum lyd_type data_type = 0;
uint8_t data_type_set = 0;
- LYD_FORMAT outformat = LYD_UNKNOWN;
- LYD_FORMAT informat = LYD_UNKNOWN;
- struct ly_out *out = NULL;
- struct cmdline_file *operational = NULL;
- struct ly_set inputs = {0};
- struct ly_set xpaths = {0};
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
+ yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS;
+ yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS;
+
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
}
- while ((opt = getopt_long(argc, argv, "d:ef:F:hmo:O:r:nt:x:", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_DATA].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'd': /* --default */
- if (!strcasecmp(optarg, "all")) {
- options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL;
- } else if (!strcasecmp(optarg, "all-tagged")) {
- options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG;
- } else if (!strcasecmp(optarg, "trim")) {
- options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM;
- } else if (!strcasecmp(optarg, "implicit-tagged")) {
- options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG;
- } else {
+ if (yo_opt_update_data_default(optarg, yo)) {
YLMSG_E("Unknown default mode %s\n", optarg);
- cmd_data_help();
- goto cleanup;
+ cmd_data_help_default();
+ return 1;
}
break;
case 'f': /* --format */
- if (!strcasecmp(optarg, "xml")) {
- outformat = LYD_XML;
- } else if (!strcasecmp(optarg, "json")) {
- outformat = LYD_JSON;
- } else if (!strcasecmp(optarg, "lyb")) {
- outformat = LYD_LYB;
- } else {
- YLMSG_E("Unknown output format %s\n", optarg);
- cmd_data_help();
- goto cleanup;
+ if (yl_opt_update_data_out_format(optarg, yo)) {
+ cmd_data_help_format();
+ return 1;
}
break;
case 'F': /* --in-format */
- if (!strcasecmp(optarg, "xml")) {
- informat = LYD_XML;
- } else if (!strcasecmp(optarg, "json")) {
- informat = LYD_JSON;
- } else if (!strcasecmp(optarg, "lyb")) {
- informat = LYD_LYB;
- } else {
+ if (yo_opt_update_data_in_format(optarg, yo)) {
YLMSG_E("Unknown input format %s\n", optarg);
- cmd_data_help();
- goto cleanup;
+ cmd_data_help_in_format();
+ return 1;
}
break;
case 'o': /* --output */
- if (out) {
+ if (yo->out) {
YLMSG_E("Only a single output can be specified.\n");
- goto cleanup;
+ return 1;
} else {
- if (ly_out_new_filepath(optarg, &out)) {
+ if (ly_out_new_filepath(optarg, &yo->out)) {
YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno));
- goto cleanup;
+ return 1;
}
}
break;
- case 'O': { /* --operational */
- struct ly_in *in;
- LYD_FORMAT f;
-
- if (operational) {
+ case 'O': /* --operational */
+ if (yo->data_operational.path) {
YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n");
- goto cleanup;
+ return 1;
}
- if (get_input(optarg, NULL, &f, &in)) {
- goto cleanup;
- }
- operational = fill_cmdline_file(NULL, in, optarg, f);
+ yo->data_operational.path = optarg;
break;
- } /* case 'O' */
-
+ case 'R': /* --reply-rpc */
+ if (yo->reply_rpc.path) {
+ YLMSG_E("The PRC of the reply (-R) cannot be set multiple times.\n");
+ return 1;
+ }
+ yo->reply_rpc.path = optarg;
+ break;
case 'e': /* --present */
- options_validate |= LYD_VALIDATE_PRESENT;
+ yo->data_validate_options |= LYD_VALIDATE_PRESENT;
break;
case 'm': /* --merge */
- data_merge = 1;
+ yo->data_merge = 1;
break;
case 'n': /* --not-strict */
- options_parse &= ~LYD_PARSE_STRICT;
+ yo->data_parse_options &= ~LYD_PARSE_STRICT;
break;
case 't': /* --type */
if (data_type_set) {
YLMSG_E("The data type (-t) cannot be set multiple times.\n");
- goto cleanup;
+ return 1;
}
- if (!strcasecmp(optarg, "config")) {
- options_parse |= LYD_PARSE_NO_STATE;
- options_validate |= LYD_VALIDATE_NO_STATE;
- } else if (!strcasecmp(optarg, "get")) {
- options_parse |= LYD_PARSE_ONLY;
- } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config") || !strcasecmp(optarg, "edit")) {
- options_parse |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE;
- } else if (!strcasecmp(optarg, "rpc") || !strcasecmp(optarg, "action")) {
- data_type = LYD_TYPE_RPC_YANG;
- } else if (!strcasecmp(optarg, "reply") || !strcasecmp(optarg, "rpcreply")) {
- data_type = LYD_TYPE_REPLY_YANG;
- } else if (!strcasecmp(optarg, "notif") || !strcasecmp(optarg, "notification")) {
- data_type = LYD_TYPE_NOTIF_YANG;
- } else if (!strcasecmp(optarg, "data")) {
- /* default option */
- } else {
+ if (yl_opt_update_data_type(optarg, yo)) {
YLMSG_E("Unknown data tree type %s.\n", optarg);
- cmd_data_help();
- goto cleanup;
+ cmd_data_help_type();
+ return 1;
}
data_type_set = 1;
break;
case 'x': /* --xpath */
- if (ly_set_add(&xpaths, optarg, 0, NULL)) {
+ if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) {
YLMSG_E("Storing XPath \"%s\" failed.\n", optarg);
- goto cleanup;
+ return 1;
}
break;
case 'h': /* --help */
cmd_data_help();
- goto cleanup;
+ return 1;
default:
YLMSG_E("Unknown option.\n");
- goto cleanup;
+ return 1;
}
}
- if (optind == argc) {
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return rc;
+}
+
+int
+cmd_data_dep(struct yl_opt *yo, int posc)
+{
+ if (yo->interactive && !posc) {
YLMSG_E("Missing the data file to process.\n");
- goto cleanup;
+ return 1;
}
- if (data_merge) {
- if (data_type || (options_parse & LYD_PARSE_ONLY)) {
+ if (yo->data_merge) {
+ if (yo->data_type || (yo->data_parse_options & LYD_PARSE_ONLY)) {
/* switch off the option, incompatible input data type */
- data_merge = 0;
+ YLMSG_W("The --merge option has effect only for 'data' and 'config' TYPEs\n");
+ yo->data_merge = 0;
} else {
/* postpone validation after the merge of all the input data */
- options_parse |= LYD_PARSE_ONLY;
+ yo->data_parse_options |= LYD_PARSE_ONLY;
}
- } else if (xpaths.count) {
- data_merge = 1;
+ } else if (yo->data_xpath.count) {
+ yo->data_merge = 1;
}
- if (xpaths.count && outformat) {
+ if (yo->data_xpath.count && (yo->schema_out_format || yo->data_out_format)) {
YLMSG_E("The --format option cannot be combined with --xpath option.\n");
- cmd_data_help();
- goto cleanup;
+ if (yo->interactive) {
+ cmd_data_help_xpath();
+ }
+ return 1;
+ }
+ if (yo->data_xpath.count && (yo->data_print_options & LYD_PRINT_WD_MASK)) {
+ YLMSG_E("The --default option cannot be combined with --xpath option.\n");
+ if (yo->interactive) {
+ cmd_data_help_xpath();
+ }
+ return 1;
}
- if (operational && !data_type) {
- YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notifications input data types.\n");
- free_cmdline_file(operational);
- operational = NULL;
+ if (yo->data_operational.path && !yo->data_type) {
+ YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types.\n");
+ yo->data_operational.path = NULL;
}
- /* process input data files provided as standalone command line arguments */
- for (int i = 0; i < argc - optind; i++) {
- struct ly_in *in;
+ if (yo->reply_rpc.path && (yo->data_type != LYD_TYPE_REPLY_NETCONF)) {
+ YLMSG_W("Source RPC is needed only for NETCONF Reply input data type.\n");
+ yo->data_operational.path = NULL;
+ } else if (!yo->reply_rpc.path && (yo->data_type == LYD_TYPE_REPLY_NETCONF)) {
+ YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type.\n");
+ return 1;
+ }
- if (get_input(argv[optind + i], NULL, &informat, &in)) {
- goto cleanup;
- }
-
- if (!fill_cmdline_file(&inputs, in, argv[optind + i], informat)) {
- ly_in_free(in, 1);
- goto cleanup;
- }
+ if (!yo->out && (yo->data_out_format == LYD_LYB)) {
+ YLMSG_E("The LYB format requires the -o option specified.\n");
+ return 1;
}
/* default output stream */
- if (!out) {
- if (ly_out_new_file(stdout, &out)) {
+ if (!yo->out) {
+ if (ly_out_new_file(stdout, &yo->out)) {
YLMSG_E("Unable to set stdout as output.\n");
- goto cleanup;
+ return 1;
+ }
+ yo->out_stdout = 1;
+ }
+
+ /* process the operational and/or reply RPC content if any */
+ if (yo->data_operational.path) {
+ if (get_input(yo->data_operational.path, NULL, &yo->data_operational.format, &yo->data_operational.in)) {
+ return -1;
+ }
+ }
+ if (yo->reply_rpc.path) {
+ if (get_input(yo->reply_rpc.path, NULL, &yo->reply_rpc.format, &yo->reply_rpc.in)) {
+ return -1;
}
}
- /* parse, validate and print data */
- if (process_data(*ctx, data_type, data_merge, outformat, out, options_parse, options_validate, options_print,
- operational, NULL, &inputs, &xpaths)) {
+ return 0;
+}
+
+int
+cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ (void) ctx;
+ struct ly_in *in;
+ LYD_FORMAT format_data;
+
+ assert(posv);
+
+ format_data = yo->data_in_format;
+
+ /* process input data files provided as standalone command line arguments */
+ if (get_input(posv, NULL, &format_data, &in)) {
+ return 1;
+ }
+
+ if (!fill_cmdline_file(&yo->data_inputs, in, posv, format_data)) {
+ ly_in_free(in, 1);
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Evaluate xpath adn print result.
+ *
+ * @param[in] tree Data tree.
+ * @param[in] xpath Xpath to evaluate.
+ * @return 0 on success.
+ */
+static int
+evaluate_xpath(const struct lyd_node *tree, const char *xpath)
+{
+ struct ly_set *set = NULL;
+
+ if (lyd_find_xpath(tree, xpath, &set)) {
+ return -1;
+ }
+
+ /* print result */
+ printf("XPath \"%s\" evaluation result:\n", xpath);
+ if (!set->count) {
+ printf("\tEmpty\n");
+ } else {
+ for (uint32_t u = 0; u < set->count; ++u) {
+ struct lyd_node *node = (struct lyd_node *)set->objs[u];
+
+ printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
+ if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
+ printf(" (value: \"%s\")\n", lyd_get_value(node));
+ } else if (node->schema->nodetype == LYS_LIST) {
+ printf(" (");
+ for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
+ printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
+ key->schema->name, lyd_get_value(key));
+ }
+ printf(")\n");
+ } else {
+ printf("\n");
+ }
+ }
+ }
+
+ ly_set_free(set, NULL);
+ return 0;
+}
+
+/**
+ * @brief Checking that a parent data node exists in the datastore for the nested-notification and action.
+ *
+ * @param[in] op Operation to check.
+ * @param[in] oper_tree Data from datastore.
+ * @param[in] operational_f Operational datastore file information.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+check_operation_parent(struct lyd_node *op, struct lyd_node *oper_tree, struct cmdline_file *operational_f)
+{
+ LY_ERR ret;
+ struct ly_set *set = NULL;
+ char *path = NULL;
+
+ if (!op || !lyd_parent(op)) {
+ /* The function is defined only for nested-notification and action. */
+ return LY_SUCCESS;
+ }
+
+ if (!operational_f || (operational_f && !operational_f->in)) {
+ YLMSG_E("The --operational parameter needed to validate operation \"%s\" is missing.\n", LYD_NAME(op));
+ ret = LY_EVALID;
+ goto cleanup;
+ }
+
+ path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0);
+ if (!path) {
+ ret = LY_EMEM;
+ goto cleanup;
+ }
+
+ if (!oper_tree) {
+ YLMSG_W("Operational datastore is empty or contains unknown data.\n");
+ YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.\n", LYD_NAME(op), path);
+ ret = LY_EVALID;
+ goto cleanup;
+ }
+ if ((ret = lyd_find_xpath(oper_tree, path, &set))) {
+ goto cleanup;
+ }
+ if (!set->count) {
+ YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.\n", LYD_NAME(op), path);
+ ret = LY_EVALID;
goto cleanup;
}
cleanup:
- ly_out_free(out, NULL, 0);
- ly_set_erase(&inputs, free_cmdline_file);
- ly_set_erase(&xpaths, NULL);
- free_cmdline_file(operational);
- free_cmdline(argv);
+ ly_set_free(set, NULL);
+ free(path);
+
+ return ret;
+}
+
+/**
+ * @brief Process the input data files - parse, validate and print according to provided options.
+ *
+ * @param[in] ctx libyang context with schema.
+ * @param[in] type The type of data in the input files.
+ * @param[in] merge Flag if the data should be merged before validation.
+ * @param[in] out_format Data format for printing.
+ * @param[in] out The output handler for printing.
+ * @param[in] parse_options Parser options.
+ * @param[in] validate_options Validation options.
+ * @param[in] print_options Printer options.
+ * @param[in] operational Optional operational datastore file information for the case of an extended validation of
+ * operation(s).
+ * @param[in] reply_rpc Source RPC operation file information for parsing NETCONF rpc-reply.
+ * @param[in] inputs Set of file informations of input data files.
+ * @param[in] xpaths The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set
+ * is printed. Alternative to data printing.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT out_format,
+ struct ly_out *out, uint32_t parse_options, uint32_t validate_options, uint32_t print_options,
+ struct cmdline_file *operational, struct cmdline_file *reply_rpc, struct ly_set *inputs,
+ struct ly_set *xpaths)
+{
+ LY_ERR ret = LY_SUCCESS;
+ struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL;
+ const char *xpath;
+ struct ly_set *set = NULL;
+
+ /* additional operational datastore */
+ if (operational && operational->in) {
+ ret = lyd_parse_data(ctx, NULL, operational->in, operational->format, LYD_PARSE_ONLY, 0, &oper_tree);
+ if (ret) {
+ YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational->path);
+ goto cleanup;
+ }
+ }
+
+ for (uint32_t u = 0; u < inputs->count; ++u) {
+ struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
+
+ switch (type) {
+ case LYD_TYPE_DATA_YANG:
+ ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, parse_options, validate_options, &tree);
+ break;
+ case LYD_TYPE_RPC_YANG:
+ case LYD_TYPE_REPLY_YANG:
+ case LYD_TYPE_NOTIF_YANG:
+ ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &tree, &op);
+ break;
+ case LYD_TYPE_RPC_NETCONF:
+ case LYD_TYPE_NOTIF_NETCONF:
+ ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &envp, &op);
+
+ /* adjust pointers */
+ for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
+ break;
+ case LYD_TYPE_REPLY_NETCONF:
+ /* parse source RPC operation */
+ assert(reply_rpc && reply_rpc->in);
+ ret = lyd_parse_op(ctx, NULL, reply_rpc->in, reply_rpc->format, LYD_TYPE_RPC_NETCONF, &envp, &op);
+ if (ret) {
+ YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".\n", reply_rpc->path);
+ goto cleanup;
+ }
+
+ /* adjust pointers */
+ for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
+
+ /* free input */
+ lyd_free_siblings(lyd_child(op));
+
+ /* we do not care */
+ lyd_free_all(envp);
+ envp = NULL;
+
+ ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, type, &envp, NULL);
+ break;
+ default:
+ YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
+ goto cleanup;
+ }
+
+ if (ret) {
+ YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
+ goto cleanup;
+ }
+
+ if (merge) {
+ /* merge the data so far parsed for later validation and print */
+ if (!merged_tree) {
+ merged_tree = tree;
+ } else {
+ ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
+ if (ret) {
+ YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
+ goto cleanup;
+ }
+ }
+ tree = NULL;
+ } else if (out_format) {
+ /* print */
+ switch (type) {
+ case LYD_TYPE_DATA_YANG:
+ lyd_print_all(out, tree, out_format, print_options);
+ break;
+ case LYD_TYPE_RPC_YANG:
+ case LYD_TYPE_REPLY_YANG:
+ case LYD_TYPE_NOTIF_YANG:
+ case LYD_TYPE_RPC_NETCONF:
+ case LYD_TYPE_NOTIF_NETCONF:
+ lyd_print_tree(out, tree, out_format, print_options);
+ break;
+ case LYD_TYPE_REPLY_NETCONF:
+ /* just the output */
+ lyd_print_tree(out, lyd_child(tree), out_format, print_options);
+ break;
+ default:
+ assert(0);
+ }
+ } else {
+ /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */
+ switch (type) {
+ case LYD_TYPE_DATA_YANG:
+ /* already validated */
+ break;
+ case LYD_TYPE_RPC_YANG:
+ case LYD_TYPE_RPC_NETCONF:
+ ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_RPC_YANG, NULL);
+ break;
+ case LYD_TYPE_REPLY_YANG:
+ case LYD_TYPE_REPLY_NETCONF:
+ ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_REPLY_YANG, NULL);
+ break;
+ case LYD_TYPE_NOTIF_YANG:
+ case LYD_TYPE_NOTIF_NETCONF:
+ ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_NOTIF_YANG, NULL);
+ break;
+ default:
+ assert(0);
+ }
+ if (ret) {
+ if (operational->path) {
+ YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
+ input_f->path, operational->path);
+ } else {
+ YLMSG_E("Failed to validate input data file \"%s\".\n", input_f->path);
+ }
+ goto cleanup;
+ }
+
+ if ((ret = check_operation_parent(op, oper_tree, operational))) {
+ goto cleanup;
+ }
+ }
+
+ /* next iter */
+ lyd_free_all(tree);
+ tree = NULL;
+ lyd_free_all(envp);
+ envp = NULL;
+ }
+
+ if (merge) {
+ /* validate the merged result */
+ ret = lyd_validate_all(&merged_tree, ctx, validate_options, NULL);
+ if (ret) {
+ YLMSG_E("Merged data are not valid.\n");
+ goto cleanup;
+ }
+
+ if (out_format) {
+ /* and print it */
+ lyd_print_all(out, merged_tree, out_format, print_options);
+ }
+
+ for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) {
+ xpath = (const char *)xpaths->objs[u];
+ ly_set_free(set, NULL);
+ ret = lys_find_xpath(ctx, NULL, xpath, LYS_FIND_NO_MATCH_ERROR, &set);
+ if (ret || !set->count) {
+ ret = (ret == LY_SUCCESS) ? LY_EINVAL : ret;
+ YLMSG_E("The requested xpath failed.\n");
+ goto cleanup;
+ }
+ if (evaluate_xpath(merged_tree, xpath)) {
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ lyd_free_all(tree);
+ lyd_free_all(envp);
+ lyd_free_all(merged_tree);
+ lyd_free_all(oper_tree);
+ ly_set_free(set, NULL);
+ return ret;
+}
+
+int
+cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo)
+{
+ /* parse, validate and print data */
+ if (process_data(ctx, yo->data_type, yo->data_merge, yo->data_out_format, yo->out, yo->data_parse_options,
+ yo->data_validate_options, yo->data_print_options, &yo->data_operational, &yo->reply_rpc,
+ &yo->data_inputs, &yo->data_xpath)) {
+ return 1;
+ }
+
+ return 0;
}
diff --git a/tools/lint/cmd_debug.c b/tools/lint/cmd_debug.c
new file mode 100644
index 0000000..1432fe9
--- /dev/null
+++ b/tools/lint/cmd_debug.c
@@ -0,0 +1,134 @@
+/**
+ * @file cmd_debug.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief 'verb' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2023-2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef NDEBUG
+
+#include "cmd.h"
+
+#include <assert.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <strings.h>
+
+#include "libyang.h"
+
+#include "common.h"
+#include "yl_opt.h"
+
+struct debug_groups {
+ char *name;
+ uint32_t flag;
+} const dg [] = {
+ {"dict", LY_LDGDICT},
+ {"xpath", LY_LDGXPATH},
+ {"dep-sets", LY_LDGDEPSETS},
+};
+#define DG_LENGTH (sizeof dg / sizeof *dg)
+
+void
+cmd_debug_help(void)
+{
+ uint32_t i;
+
+ printf("Usage: debug (");
+ for (i = 0; i < DG_LENGTH; i++) {
+ if ((i + 1) == DG_LENGTH) {
+ printf("%s", dg[i].name);
+ } else {
+ printf("%s | ", dg[i].name);
+ }
+ }
+ printf(")+\n");
+}
+
+int
+cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+ int rc = 0, argc = 0;
+ int opt, opt_index;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
+ }
+
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_DEBUG].optstring, options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'h':
+ cmd_debug_help();
+ return 1;
+ default:
+ YLMSG_E("Unknown option.\n");
+ return 1;
+ }
+ }
+
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return 0;
+}
+
+int
+cmd_debug_dep(struct yl_opt *yo, int posc)
+{
+ (void) yo;
+
+ if (yo->interactive && !posc) {
+ /* no argument */
+ cmd_debug_help();
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ (void) ctx;
+ uint32_t i;
+ ly_bool set;
+
+ assert(posv);
+
+ set = 0;
+ for (i = 0; i < DG_LENGTH; i++) {
+ if (!strcasecmp(posv, dg[i].name)) {
+ yo->dbg_groups |= dg[i].flag;
+ set = 1;
+ break;
+ }
+ }
+
+ if (!set) {
+ YLMSG_E("Unknown debug group \"%s\"\n", posv);
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo)
+{
+ (void) ctx;
+ return ly_log_dbg_groups(yo->dbg_groups);
+}
+
+#endif
diff --git a/tools/lint/cmd_extdata.c b/tools/lint/cmd_extdata.c
new file mode 100644
index 0000000..fe14f4a
--- /dev/null
+++ b/tools/lint/cmd_extdata.c
@@ -0,0 +1,115 @@
+/**
+ * @file cmd_extdata.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief 'extdata' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+#define _POSIX_C_SOURCE 200809L /* strdup */
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "libyang.h"
+
+#include "common.h"
+#include "yl_opt.h"
+
+char *filename;
+
+void
+cmd_extdata_free(void)
+{
+ free(filename);
+ filename = NULL;
+}
+
+void
+cmd_extdata_help(void)
+{
+ printf("Usage: extdata [--clear] [<extdata-file-path>]\n"
+ " File containing the specific data required by an extension. Required by\n"
+ " the schema-mount extension, for example, when the operational data are\n"
+ " expected in the file. File format is guessed.\n");
+}
+
+int
+cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+ int rc = 0, argc = 0;
+ int opt, opt_index;
+ struct option options[] = {
+ {"clear", no_argument, NULL, 'c'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
+ }
+
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_EXTDATA].optstring, options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'c':
+ yo->extdata_unset = 1;
+ free(filename);
+ filename = NULL;
+ break;
+ case 'h':
+ cmd_extdata_help();
+ return 1;
+ default:
+ YLMSG_E("Unknown option.\n");
+ return 1;
+ }
+ }
+
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return 0;
+}
+
+int
+cmd_extdata_dep(struct yl_opt *yo, int posc)
+{
+ if (!yo->extdata_unset && (posc > 1)) {
+ YLMSG_E("Only one file must be entered.\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ if (yo->extdata_unset) {
+ ly_ctx_set_ext_data_clb(*ctx, NULL, NULL);
+ } else if (!yo->extdata_unset && !posv) {
+ /* no argument - print the current file */
+ printf("%s\n", filename ? filename : "No file set.");
+ } else if (posv) {
+ /* set callback providing run-time extension instance data */
+ free(filename);
+ filename = strdup(posv);
+ if (!filename) {
+ YLMSG_E("Memory allocation error.\n");
+ return 1;
+ }
+ ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, filename);
+ }
+
+ return 0;
+}
diff --git a/tools/lint/cmd_feature.c b/tools/lint/cmd_feature.c
index 6b332ab..5c8bd82 100644
--- a/tools/lint/cmd_feature.c
+++ b/tools/lint/cmd_feature.c
@@ -1,9 +1,10 @@
/**
* @file cmd_feature.c
* @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief 'feature' command of the libyang's yanglint tool.
*
- * Copyright (c) 2015-2021 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -23,6 +24,8 @@
#include "libyang.h"
#include "common.h"
+#include "yl_opt.h"
+#include "yl_schema_features.h"
void
cmd_feature_help(void)
@@ -37,17 +40,11 @@
" Print features of all implemented modules.\n");
}
-void
-cmd_feature(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
{
- int argc = 0;
- char **argv = NULL;
- char *features_output = NULL;
- int opt, opt_index, i;
- ly_bool generate_features = 0, print_all = 0;
- struct ly_set set = {0};
- const struct lys_module *mod;
- struct ly_out *out = NULL;
+ int rc = 0, argc = 0;
+ int opt, opt_index;
struct option options[] = {
{"help", no_argument, NULL, 'h'},
{"all", no_argument, NULL, 'a'},
@@ -55,81 +52,80 @@
{NULL, 0, NULL, 0}
};
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
}
- while ((opt = getopt_long(argc, argv, "haf", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_FEATURE].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'h':
cmd_feature_help();
- goto cleanup;
+ return 1;
case 'a':
- print_all = 1;
+ yo->feature_print_all = 1;
break;
case 'f':
- generate_features = 1;
+ yo->feature_param_format = 1;
break;
default:
YLMSG_E("Unknown option.\n");
- goto cleanup;
+ return 1;
}
}
- if (ly_out_new_file(stdout, &out)) {
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return 0;
+}
+
+int
+cmd_feature_dep(struct yl_opt *yo, int posc)
+{
+ if (ly_out_new_file(stdout, &yo->out)) {
YLMSG_E("Unable to print to the standard output.\n");
- goto cleanup;
+ return 1;
}
+ yo->out_stdout = 1;
- if (print_all) {
- if (print_all_features(out, *ctx, generate_features, &features_output)) {
- YLMSG_E("Printing all features failed.\n");
- goto cleanup;
- }
- if (generate_features) {
- printf("%s\n", features_output);
- }
- goto cleanup;
- }
-
- if (argc == optind) {
+ if (yo->interactive && !yo->feature_print_all && !posc) {
YLMSG_E("Missing modules to print.\n");
- goto cleanup;
+ return 1;
}
- for (i = 0; i < argc - optind; i++) {
- /* always erase the set, so the previous module's features don't carry over to the next module's features */
- ly_set_erase(&set, NULL);
+ return 0;
+}
- mod = ly_ctx_get_module_latest(*ctx, argv[optind + i]);
- if (!mod) {
- YLMSG_E("Module \"%s\" not found.\n", argv[optind + i]);
- goto cleanup;
- }
+int
+cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ const struct lys_module *mod;
- /* collect features of the module */
- if (collect_features(mod, &set)) {
- goto cleanup;
- }
-
- if (generate_features) {
- if (generate_features_output(mod, &set, &features_output)) {
- goto cleanup;
- }
- /* don't print features and their state of each module if generating features parameter */
- continue;
- }
-
- print_features(out, mod, &set);
+ if (yo->feature_print_all) {
+ print_all_features(yo->out, *ctx, yo->feature_param_format);
+ return 0;
}
- if (generate_features) {
- printf("%s\n", features_output);
+ mod = ly_ctx_get_module_latest(*ctx, posv);
+ if (!mod) {
+ YLMSG_E("Module \"%s\" not found.\n", posv);
+ return 1;
}
-cleanup:
- free_cmdline(argv);
- ly_out_free(out, NULL, 0);
- ly_set_erase(&set, NULL);
- free(features_output);
+ if (yo->feature_param_format) {
+ print_feature_param(yo->out, mod);
+ } else {
+ print_features(yo->out, mod);
+ }
+
+ return 0;
+}
+
+int
+cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo)
+{
+ (void) ctx;
+
+ ly_print(yo->out, "\n");
+ return 0;
}
diff --git a/tools/lint/cmd_help.c b/tools/lint/cmd_help.c
new file mode 100644
index 0000000..8a2a597
--- /dev/null
+++ b/tools/lint/cmd_help.c
@@ -0,0 +1,107 @@
+/**
+ * @file cmd_help.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief 'help' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2023-2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "libyang.h"
+
+#include "common.h"
+#include "yl_opt.h"
+
+void
+cmd_help_help(void)
+{
+ printf("Usage: help [cmd ...]\n");
+}
+
+int
+cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+ int rc = 0, argc = 0;
+ int opt, opt_index;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
+ }
+
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_HELP].optstring, options, &opt_index)) != -1) {
+ if (opt == 'h') {
+ cmd_help_help();
+ return 1;
+ } else {
+ YLMSG_E("Unknown option.\n");
+ return 1;
+ }
+ }
+
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return rc;
+}
+
+static void
+print_generic_help(void)
+{
+ printf("Available commands:\n");
+ for (uint16_t i = 0; commands[i].name; i++) {
+ if (commands[i].helpstring != NULL) {
+ printf(" %-15s %s\n", commands[i].name, commands[i].helpstring);
+ }
+ }
+}
+
+int
+cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ (void)ctx, (void)yo;
+
+ if (!posv) {
+ print_generic_help();
+ } else {
+ /* print specific help for the selected command(s) */
+
+ int8_t match = 0;
+
+ /* get the command of the specified name */
+ for (uint16_t i = 0; commands[i].name; i++) {
+ if (strcmp(posv, commands[i].name) == 0) {
+ match = 1;
+ if (commands[i].help_func != NULL) {
+ commands[i].help_func();
+ } else {
+ printf("%s: %s\n", posv, commands[i].helpstring);
+ }
+ break;
+ }
+ }
+ if (!match) {
+ /* if unknown command specified, print the list of commands */
+ printf("Unknown command \'%s\'\n", posv);
+ print_generic_help();
+ }
+ }
+
+ return 0;
+}
diff --git a/tools/lint/cmd_list.c b/tools/lint/cmd_list.c
index ec7a021..fb9a2a7 100644
--- a/tools/lint/cmd_list.c
+++ b/tools/lint/cmd_list.c
@@ -2,9 +2,10 @@
* @file cmd_list.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief 'list' command of the libyang's yanglint tool.
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -24,6 +25,7 @@
#include "libyang.h"
#include "common.h"
+#include "yl_opt.h"
void
cmd_list_help(void)
@@ -37,53 +39,147 @@
" modules.\n");
}
-void
-cmd_list(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
{
- int argc = 0;
- char **argv = NULL;
+ int rc = 0, argc = 0;
int opt, opt_index;
struct option options[] = {
{"format", required_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
- LYD_FORMAT format = LYD_UNKNOWN;
- struct ly_out *out = NULL;
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
+ yo->data_out_format = LYD_UNKNOWN;
+
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
}
- while ((opt = getopt_long(argc, argv, "f:h", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_LIST].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'f': /* --format */
if (!strcasecmp(optarg, "xml")) {
- format = LYD_XML;
+ yo->data_out_format = LYD_XML;
} else if (!strcasecmp(optarg, "json")) {
- format = LYD_JSON;
+ yo->data_out_format = LYD_JSON;
} else {
YLMSG_E("Unknown output format %s\n", optarg);
cmd_list_help();
- goto cleanup;
+ return 1;
}
break;
case 'h':
cmd_list_help();
- goto cleanup;
+ return 1;
default:
YLMSG_E("Unknown option.\n");
- goto cleanup;
+ return 1;
}
}
- if (!ly_out_new_file(stdout, &out)) {
- print_list(out, *ctx, format);
- ly_out_free(out, NULL, 0);
- } else {
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return 0;
+}
+
+int
+cmd_list_dep(struct yl_opt *yo, int posc)
+{
+ if (posc) {
+ YLMSG_E("No positional arguments are allowed.\n");
+ return 1;
+ }
+ if (ly_out_new_file(stdout, &yo->out)) {
YLMSG_E("Unable to print to the standard output.\n");
+ return 1;
+ }
+ yo->out_stdout = 1;
+
+ return 0;
+}
+
+/**
+ * @brief Print yang library data.
+ *
+ * @param[in] ctx Context for libyang.
+ * @param[in] data_out_format Output format of printed data.
+ * @param[in] out Output handler.
+ * @return 0 on success.
+ */
+static int
+print_yang_lib_data(struct ly_ctx *ctx, LYD_FORMAT data_out_format, struct ly_out *out)
+{
+ struct lyd_node *ylib;
+
+ if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) {
+ YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, use an option to add it internally.\n");
+ return 1;
}
-cleanup:
- free_cmdline(argv);
+ lyd_print_all(out, ylib, data_out_format, 0);
+ lyd_free_all(ylib);
+
+ return 0;
+}
+
+int
+cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ (void) posv;
+ uint32_t idx = 0, has_modules = 0;
+ const struct lys_module *mod;
+
+ if (yo->data_out_format != LYD_UNKNOWN) {
+ /* ietf-yang-library data are printed in the specified format */
+ if (print_yang_lib_data(*ctx, yo->data_out_format, yo->out)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ /* iterate schemas in context and provide just the basic info */
+ ly_print(yo->out, "List of the loaded models:\n");
+ while ((mod = ly_ctx_get_module_iter(*ctx, &idx))) {
+ has_modules++;
+
+ /* conformance print */
+ if (mod->implemented) {
+ ly_print(yo->out, " I");
+ } else {
+ ly_print(yo->out, " i");
+ }
+
+ /* module print */
+ ly_print(yo->out, " %s", mod->name);
+ if (mod->revision) {
+ ly_print(yo->out, "@%s", mod->revision);
+ }
+
+ /* submodules print */
+ if (mod->parsed && mod->parsed->includes) {
+ uint64_t u = 0;
+
+ ly_print(yo->out, " (");
+ LY_ARRAY_FOR(mod->parsed->includes, u) {
+ ly_print(yo->out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name);
+ if (mod->parsed->includes[u].rev[0]) {
+ ly_print(yo->out, "@%s", mod->parsed->includes[u].rev);
+ }
+ }
+ ly_print(yo->out, ")");
+ }
+
+ /* finish the line */
+ ly_print(yo->out, "\n");
+ }
+
+ if (!has_modules) {
+ ly_print(yo->out, "\t(none)\n");
+ }
+
+ ly_print_flush(yo->out);
+
+ return 0;
}
diff --git a/tools/lint/cmd_load.c b/tools/lint/cmd_load.c
index f5883e9..f7457c8 100644
--- a/tools/lint/cmd_load.c
+++ b/tools/lint/cmd_load.c
@@ -2,9 +2,10 @@
* @file cmd_load.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief 'load' command of the libyang's yanglint tool.
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -17,6 +18,7 @@
#include "cmd.h"
+#include <assert.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
@@ -25,13 +27,15 @@
#include "libyang.h"
#include "common.h"
+#include "yl_opt.h"
+#include "yl_schema_features.h"
void
cmd_load_help(void)
{
printf("Usage: load [-i] <module-name1>[@<revision>] [<module-name2>[@revision] ...]\n"
" Add a new module of the specified name, yanglint will find\n"
- " them in searchpaths. if the <revision> of the module not\n"
+ " them in searchpaths. If the <revision> of the module not\n"
" specified, the latest revision available is loaded.\n\n"
" -F FEATURES, --features=FEATURES\n"
" Features to support, default all in all implemented modules.\n"
@@ -39,101 +43,110 @@
" -i, --make-implemented\n"
" Make the imported modules \"referenced\" from any loaded\n"
" <schema> module also implemented. If specified a second time,\n"
- " all the modules are set implemented.\n");
+ " all the modules are set implemented.\n"
+ " -X, --extended-leafref\n"
+ " Allow usage of deref() XPath function within leafref.\n");
}
-void
-cmd_load(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
{
- int argc = 0;
- char **argv = NULL;
+ int rc, argc = 0;
int opt, opt_index;
struct option options[] = {
{"features", required_argument, NULL, 'F'},
{"help", no_argument, NULL, 'h'},
{"make-implemented", no_argument, NULL, 'i'},
+ {"extended-leafref", no_argument, NULL, 'X'},
{NULL, 0, NULL, 0}
};
- uint16_t options_ctx = 0;
- const char *all_features[] = {"*", NULL};
- struct ly_set fset = {0};
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
}
- while ((opt = getopt_long(argc, argv, "F:hi", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_LOAD].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'F': /* --features */
- if (parse_features(optarg, &fset)) {
- goto cleanup;
+ if (parse_features(optarg, &yo->schema_features)) {
+ return 1;
}
break;
case 'h':
cmd_load_help();
- goto cleanup;
+ return 1;
case 'i': /* --make-implemented */
- if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
- options_ctx &= ~LY_CTX_REF_IMPLEMENTED;
- options_ctx |= LY_CTX_ALL_IMPLEMENTED;
- } else {
- options_ctx |= LY_CTX_REF_IMPLEMENTED;
- }
+ yo_opt_update_make_implemented(yo);
+ break;
+
+ case 'X': /* --extended-leafref */
+ yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED;
break;
default:
YLMSG_E("Unknown option.\n");
- goto cleanup;
+ return 1;
}
}
- if (argc == optind) {
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return 0;
+}
+
+int
+cmd_load_dep(struct yl_opt *yo, int posc)
+{
+ if (yo->interactive && !posc) {
/* no argument */
- cmd_add_help();
- goto cleanup;
+ cmd_load_help();
+ return 1;
}
- if (!fset.count) {
+ if (!yo->schema_features.count) {
/* no features, enable all of them */
- options_ctx |= LY_CTX_ENABLE_IMP_FEATURES;
+ yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES;
}
- if (options_ctx) {
- ly_ctx_set_options(*ctx, options_ctx);
+ return 0;
+}
+
+int
+cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ const char *all_features[] = {"*", NULL};
+ char *revision;
+ const char **features = NULL;
+
+ assert(posv);
+
+ if (yo->ctx_options) {
+ ly_ctx_set_options(*ctx, yo->ctx_options);
+ yo->ctx_options = 0;
}
- for (int i = 0; i < argc - optind; i++) {
- /* process the schema module files */
- char *revision;
- const char **features = NULL;
-
- /* get revision */
- revision = strchr(argv[optind + i], '@');
- if (revision) {
- revision[0] = '\0';
- ++revision;
- }
-
- /* get features list for this module */
- if (!fset.count) {
- features = all_features;
- } else {
- get_features(&fset, argv[optind + i], &features);
- }
-
- /* load the module */
- if (!ly_ctx_load_module(*ctx, argv[optind + i], revision, features)) {
- /* libyang printed the error messages */
- goto cleanup;
- }
+ /* get revision */
+ revision = strchr(posv, '@');
+ if (revision) {
+ revision[0] = '\0';
+ ++revision;
}
-cleanup:
- if (options_ctx) {
- ly_ctx_unset_options(*ctx, options_ctx);
+ /* get features list for this module */
+ if (!yo->schema_features.count) {
+ features = all_features;
+ } else {
+ get_features(&yo->schema_features, posv, &features);
}
- ly_set_erase(&fset, free_features);
- free_cmdline(argv);
+
+ /* load the module */
+ if (!ly_ctx_load_module(*ctx, posv, revision, features)) {
+ /* libyang printed the error messages */
+ return 1;
+ }
+
+ return 0;
}
diff --git a/tools/lint/cmd_print.c b/tools/lint/cmd_print.c
index c1a5359..a89ac1a 100644
--- a/tools/lint/cmd_print.c
+++ b/tools/lint/cmd_print.c
@@ -2,9 +2,10 @@
* @file cmd_print.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief 'print' command of the libyang's yanglint tool.
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -27,6 +28,7 @@
#include "libyang.h"
#include "common.h"
+#include "yl_opt.h"
void
cmd_print_help(void)
@@ -34,7 +36,8 @@
printf("Usage: print [-f (yang | yin | tree [-q -P PATH -L LINE_LENGTH ] | info [-q -P PATH])]\n"
" [-o OUTFILE] [<module-name1>[@revision]] ...\n"
" Print a schema module. The <module-name> is not required\n"
- " only in case the -P option is specified.\n\n"
+ " only in case the -P option is specified. For yang, yin and tree\n"
+ " formats, a submodule can also be printed.\n\n"
" -f FORMAT, --format=FORMAT\n"
" Print the module in the specified FORMAT. If format not\n"
" specified, the 'tree' format is used.\n"
@@ -53,8 +56,105 @@
" Write the output to OUTFILE instead of stdout.\n");
}
+int
+cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+ int rc = 0, argc = 0;
+ int opt, opt_index;
+ struct option options[] = {
+ {"format", required_argument, NULL, 'f'},
+ {"help", no_argument, NULL, 'h'},
+ {"tree-line-length", required_argument, NULL, 'L'},
+ {"output", required_argument, NULL, 'o'},
+ {"schema-node", required_argument, NULL, 'P'},
+ {"single-node", no_argument, NULL, 'q'},
+ {NULL, 0, NULL, 0}
+ };
+
+ yo->schema_out_format = LYS_OUT_TREE;
+
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
+ }
+
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_PRINT].optstring, options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'o': /* --output */
+ if (yo->out) {
+ YLMSG_E("Only a single output can be specified.\n");
+ return 1;
+ } else {
+ if (ly_out_new_filepath(optarg, &yo->out)) {
+ YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno));
+ return 1;
+ }
+ }
+ break;
+
+ case 'f': /* --format */
+ if (yl_opt_update_schema_out_format(optarg, yo)) {
+ cmd_print_help();
+ return 1;
+ }
+ break;
+
+ case 'L': /* --tree-line-length */
+ yo->line_length = atoi(optarg);
+ break;
+
+ case 'P': /* --schema-node */
+ yo->schema_node_path = optarg;
+ break;
+
+ case 'q': /* --single-node */
+ yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT;
+ break;
+
+ case 'h':
+ cmd_print_help();
+ return 1;
+ default:
+ YLMSG_E("Unknown option.\n");
+ return 1;
+ }
+ }
+
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return 0;
+}
+
+int
+cmd_print_dep(struct yl_opt *yo, int posc)
+{
+ /* file name */
+ if (yo->interactive && !posc && !yo->schema_node_path) {
+ YLMSG_E("Missing the name of the module to print.\n");
+ return 1;
+ }
+
+ if ((yo->schema_out_format != LYS_OUT_TREE) && yo->line_length) {
+ YLMSG_W("--tree-line-length take effect only in case of the tree output format.\n");
+ }
+
+ if (!yo->out) {
+ if (ly_out_new_file(stdout, &yo->out)) {
+ YLMSG_E("Could not use stdout to print output.\n");
+ }
+ yo->out_stdout = 1;
+ }
+
+ if (yo->schema_out_format == LYS_OUT_TREE) {
+ /* print tree from lysc_nodes */
+ yo->ctx_options |= LY_CTX_SET_PRIV_PARSED;
+ }
+
+ return 0;
+}
+
static LY_ERR
-cmd_print_submodule(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
+print_submodule(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
{
LY_ERR erc;
const struct lysp_submodule *submodule;
@@ -67,11 +167,19 @@
lys_print_submodule(out, submodule, format, line_length, options) :
LY_ENOTFOUND;
+ if (!erc) {
+ return 0;
+ } else if ((erc == LY_ENOTFOUND) && revision) {
+ YLMSG_E("No submodule \"%s\" found.\n", name);
+ } else {
+ YLMSG_E("Unable to print submodule %s.\n", name);
+ }
+
return erc;
}
static LY_ERR
-cmd_print_module(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
+print_module(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
{
LY_ERR erc;
struct lys_module *module;
@@ -84,181 +192,108 @@
lys_print_module(out, module, format, line_length, options) :
LY_ENOTFOUND;
+ if (!erc) {
+ return 0;
+ } else if ((erc == LY_ENOTFOUND) && revision) {
+ YLMSG_E("No module \"%s\" found.\n", name);
+ } else {
+ YLMSG_E("Unable to print module %s.\n", name);
+ }
+
return erc;
}
-static void
-cmd_print_modules(int argc, char **argv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
+static int
+cmd_print_module(const char *posv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format,
+ size_t line_length, uint32_t options)
{
LY_ERR erc;
- char *name, *revision;
- ly_bool search_submodul;
- const int stop = argc - optind;
+ char *name = NULL, *revision;
- for (int i = 0; i < stop; i++) {
- name = argv[optind + i];
- /* get revision */
- revision = strchr(name, '@');
- if (revision) {
- revision[0] = '\0';
- ++revision;
- }
-
- erc = cmd_print_module(out, ctx, name, revision, format, line_length, options);
-
- if (erc == LY_ENOTFOUND) {
- search_submodul = 1;
- erc = cmd_print_submodule(out, ctx, name, revision, format, line_length, options);
- } else {
- search_submodul = 0;
- }
-
- if (erc == LY_SUCCESS) {
- /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */
- if ((format == LYS_OUT_TREE) && (i + 1 < stop)) {
- ly_print(out, "\n");
- }
- continue;
- } else if (erc == LY_ENOTFOUND) {
- if (revision) {
- YLMSG_E("No (sub)module \"%s\" in revision %s found.\n", name, revision);
- } else {
- YLMSG_E("No (sub)module \"%s\" found.\n", name);
- }
- break;
- } else {
- if (search_submodul) {
- YLMSG_E("Unable to print submodule %s.\n", name);
- } else {
- YLMSG_E("Unable to print module %s.\n", name);
- }
- break;
- }
+ name = strdup(posv);
+ /* get revision */
+ revision = strchr(name, '@');
+ if (revision) {
+ revision[0] = '\0';
+ ++revision;
}
+
+ erc = print_module(out, ctx, name, revision, format, line_length, options);
+
+ if (erc == LY_ENOTFOUND) {
+ erc = print_submodule(out, ctx, name, revision, format, line_length, options);
+ }
+
+ free(name);
+ return erc;
}
-void
-cmd_print(struct ly_ctx **ctx, const char *cmdline)
+/**
+ * @brief Print schema node path.
+ *
+ * @param[in] ctx Context for libyang.
+ * @param[in] yo Context for yanglint.
+ * @return 0 on success.
+ */
+static int
+print_node(struct ly_ctx *ctx, struct yl_opt *yo)
{
- int argc = 0;
- char **argv = NULL;
- int opt, opt_index;
- struct option options[] = {
- {"format", required_argument, NULL, 'f'},
- {"help", no_argument, NULL, 'h'},
- {"tree-line-length", required_argument, NULL, 'L'},
- {"output", required_argument, NULL, 'o'},
- {"schema-node", required_argument, NULL, 'P'},
- {"single-node", no_argument, NULL, 'q'},
- {NULL, 0, NULL, 0}
- };
- uint16_t options_print = 0;
- const char *node_path = NULL;
- LYS_OUTFORMAT format = LYS_OUT_TREE;
- struct ly_out *out = NULL;
- ly_bool out_stdout = 0;
- size_t line_length = 0;
+ const struct lysc_node *node;
+ uint32_t temp_lo = 0;
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
- }
-
- while ((opt = getopt_long(argc, argv, "f:hL:o:P:q", options, &opt_index)) != -1) {
- switch (opt) {
- case 'o': /* --output */
- if (out) {
- if (ly_out_filepath(out, optarg) != NULL) {
- YLMSG_E("Unable to use output file %s for printing output.\n", optarg);
- goto cleanup;
- }
- } else {
- if (ly_out_new_filepath(optarg, &out)) {
- YLMSG_E("Unable to use output file %s for printing output.\n", optarg);
- goto cleanup;
- }
+ if (yo->interactive) {
+ /* Use the same approach as for completion. */
+ node = find_schema_path(ctx, yo->schema_node_path);
+ if (!node) {
+ YLMSG_E("The requested schema node \"%s\" does not exists.\n", yo->schema_node_path);
+ return 1;
+ }
+ } else {
+ /* turn off logging so that the message is not repeated */
+ ly_temp_log_options(&temp_lo);
+ /* search operation input */
+ node = lys_find_path(ctx, NULL, yo->schema_node_path, 0);
+ if (!node) {
+ /* restore logging so an error may be displayed */
+ ly_temp_log_options(NULL);
+ /* search operation output */
+ node = lys_find_path(ctx, NULL, yo->schema_node_path, 1);
+ if (!node) {
+ YLMSG_E("Invalid schema path.\n");
+ return 1;
}
- break;
-
- case 'f': /* --format */
- if (!strcasecmp(optarg, "yang")) {
- format = LYS_OUT_YANG;
- } else if (!strcasecmp(optarg, "yin")) {
- format = LYS_OUT_YIN;
- } else if (!strcasecmp(optarg, "info")) {
- format = LYS_OUT_YANG_COMPILED;
- } else if (!strcasecmp(optarg, "tree")) {
- format = LYS_OUT_TREE;
- } else {
- YLMSG_E("Unknown output format %s\n", optarg);
- cmd_print_help();
- goto cleanup;
- }
- break;
-
- case 'L': /* --tree-line-length */
- line_length = atoi(optarg);
- break;
-
- case 'P': /* --schema-node */
- node_path = optarg;
- break;
-
- case 'q': /* --single-node */
- options_print |= LYS_PRINT_NO_SUBSTMT;
- break;
-
- case 'h':
- cmd_print_help();
- goto cleanup;
- default:
- YLMSG_E("Unknown option.\n");
- goto cleanup;
}
}
- /* file name */
- if ((argc == optind) && !node_path) {
- YLMSG_E("Missing the name of the module to print.\n");
- goto cleanup;
+ if (lys_print_node(yo->out, node, yo->schema_out_format, yo->line_length, yo->schema_print_options)) {
+ YLMSG_E("Unable to print schema node %s.\n", yo->schema_node_path);
+ return 1;
}
- if ((format != LYS_OUT_TREE) && line_length) {
- YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n");
- goto cleanup;
- }
+ return 0;
+}
- if (!out) {
- if (ly_out_new_file(stdout, &out)) {
- YLMSG_E("Could not use stdout to print output.\n");
- goto cleanup;
- }
- out_stdout = 1;
- }
+int
+cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ int rc = 0;
- if (format == LYS_OUT_TREE) {
+ if (yo->ctx_options & LY_CTX_SET_PRIV_PARSED) {
/* print tree from lysc_nodes */
ly_ctx_set_options(*ctx, LY_CTX_SET_PRIV_PARSED);
}
- if (node_path) {
- const struct lysc_node *node;
-
- node = find_schema_path(*ctx, node_path);
- if (!node) {
- YLMSG_E("The requested schema node \"%s\" does not exists.\n", node_path);
- goto cleanup;
- }
-
- if (lys_print_node(out, node, format, 0, options_print)) {
- YLMSG_E("Unable to print schema node %s.\n", node_path);
- goto cleanup;
- }
+ if (yo->schema_node_path) {
+ rc = print_node(*ctx, yo);
+ } else if (!yo->interactive && yo->submodule) {
+ rc = print_submodule(yo->out, ctx, yo->submodule, NULL, yo->schema_out_format, yo->line_length,
+ yo->schema_print_options);
} else {
- cmd_print_modules(argc, argv, out, ctx, format, line_length, options_print);
- goto cleanup;
+ rc = cmd_print_module(posv, yo->out, ctx, yo->schema_out_format, yo->line_length, yo->schema_print_options);
+ if (!yo->last_one && (yo->schema_out_format == LYS_OUT_TREE)) {
+ ly_print(yo->out, "\n");
+ }
}
-cleanup:
- free_cmdline(argv);
- ly_out_free(out, NULL, out_stdout ? 0 : 1);
+ return rc;
}
diff --git a/tools/lint/cmd_searchpath.c b/tools/lint/cmd_searchpath.c
index 529e05d..15c98a1 100644
--- a/tools/lint/cmd_searchpath.c
+++ b/tools/lint/cmd_searchpath.c
@@ -2,9 +2,10 @@
* @file cmd_searchpath.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief 'searchpath' command of the libyang's yanglint tool.
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -24,51 +25,62 @@
#include "libyang.h"
#include "common.h"
+#include "yl_opt.h"
void
cmd_searchpath_help(void)
{
printf("Usage: searchpath [--clear] [<modules-dir-path> ...]\n"
- " Set paths of directories where to search for imports and\n"
- " includes of the schema modules. The current working directory\n"
- " and the path of the module being added is used implicitly.\n"
- " The 'load' command uses these paths to search even for the\n"
- " schema modules to be loaded.\n");
+ " Set paths of directories where to search for imports and includes\n"
+ " of the schema modules. Subdirectories are also searched. The current\n"
+ " working directory and the path of the module being added is used implicitly.\n"
+ " The 'load' command uses these paths to search even for the schema modules\n"
+ " to be loaded.\n");
}
-void
-cmd_searchpath(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
{
- int argc = 0;
- char **argv = NULL;
+ int rc = 0, argc = 0;
int opt, opt_index;
struct option options[] = {
{"clear", no_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
- int8_t cleared = 0;
- if (parse_cmdline(cmdline, &argc, &argv)) {
- goto cleanup;
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
}
- while ((opt = getopt_long(argc, argv, "ch", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_SEARCHPATH].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'c':
- ly_ctx_unset_searchdir(*ctx, NULL);
- cleared = 1;
+ yo->searchdir_unset = 1;
break;
case 'h':
cmd_searchpath_help();
- goto cleanup;
+ return 1;
default:
YLMSG_E("Unknown option.\n");
- goto cleanup;
+ return 1;
}
}
- if (!cleared && (argc == optind)) {
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return 0;
+}
+
+int
+cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ int rc = 0;
+
+ if (yo->searchdir_unset) {
+ ly_ctx_unset_searchdir(*ctx, NULL);
+ } else if (!yo->searchdir_unset && !posv) {
/* no argument - print the paths */
const char * const *dirs = ly_ctx_get_searchdirs(*ctx);
@@ -76,15 +88,9 @@
for (uint32_t i = 0; dirs[i]; ++i) {
printf(" %s\n", dirs[i]);
}
- goto cleanup;
+ } else {
+ rc = ly_ctx_set_searchdir(*ctx, posv);
}
- for (int i = 0; i < argc - optind; i++) {
- if (ly_ctx_set_searchdir(*ctx, argv[optind + i])) {
- goto cleanup;
- }
- }
-
-cleanup:
- free_cmdline(argv);
+ return rc;
}
diff --git a/tools/lint/cmd_verb.c b/tools/lint/cmd_verb.c
new file mode 100644
index 0000000..6d31c33
--- /dev/null
+++ b/tools/lint/cmd_verb.c
@@ -0,0 +1,114 @@
+/**
+ * @file cmd_verb.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief 'verb' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2023-2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <strings.h>
+
+#include "libyang.h"
+
+#include "common.h"
+#include "yl_opt.h"
+
+void
+cmd_verb_help(void)
+{
+ printf("Usage: verb (error | warning | verbose | debug)\n");
+}
+
+int
+cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+ int rc = 0, argc = 0;
+ int opt, opt_index;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+ return rc;
+ }
+
+ while ((opt = getopt_long(argc, yo->argv, commands[CMD_VERB].optstring, options, &opt_index)) != -1) {
+ if (opt == 'h') {
+ cmd_verb_help();
+ return 1;
+ } else {
+ YLMSG_E("Unknown option.\n");
+ return 1;
+ }
+ }
+
+ *posv = &yo->argv[optind];
+ *posc = argc - optind;
+
+ return 0;
+}
+
+int
+cmd_verb_dep(struct yl_opt *yo, int posc)
+{
+ (void) yo;
+
+ if (posc > 1) {
+ YLMSG_E("Only a single verbosity level can be set.\n");
+ cmd_verb_help();
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+ (void) ctx, (void) yo;
+
+ if (!posv) {
+ /* no argument - print current value */
+ LY_LOG_LEVEL level = ly_log_level(LY_LLERR);
+
+ ly_log_level(level);
+ printf("Current verbosity level: ");
+ if (level == LY_LLERR) {
+ printf("error\n");
+ } else if (level == LY_LLWRN) {
+ printf("warning\n");
+ } else if (level == LY_LLVRB) {
+ printf("verbose\n");
+ } else if (level == LY_LLDBG) {
+ printf("debug\n");
+ }
+ return 0;
+ } else {
+ if (!strcasecmp("error", posv) || !strcmp("0", posv)) {
+ ly_log_level(LY_LLERR);
+ } else if (!strcasecmp("warning", posv) || !strcmp("1", posv)) {
+ ly_log_level(LY_LLWRN);
+ } else if (!strcasecmp("verbose", posv) || !strcmp("2", posv)) {
+ ly_log_level(LY_LLVRB);
+ } else if (!strcasecmp("debug", posv) || !strcmp("3", posv)) {
+ ly_log_level(LY_LLDBG);
+ } else {
+ YLMSG_E("Unknown verbosity \"%s\"\n", posv);
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/tools/lint/common.c b/tools/lint/common.c
index fc9b1cd..bdf96e6 100644
--- a/tools/lint/common.c
+++ b/tools/lint/common.c
@@ -1,9 +1,10 @@
/**
* @file common.c
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief libyang's yanglint tool - common functions for both interactive and non-interactive mode.
*
- * Copyright (c) 2020 CESNET, z.s.p.o.
+ * Copyright (c) 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -19,7 +20,6 @@
#include <assert.h>
#include <errno.h>
-#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -27,6 +27,8 @@
#include "compat.h"
#include "libyang.h"
+#include "plugins_exts.h"
+#include "yl_opt.h"
int
parse_schema_path(const char *path, char **dir, char **module)
@@ -73,14 +75,11 @@
return -1;
}
- if ((format_schema && !*format_schema) || (format_data && !*format_data)) {
- /* get the file format */
- if (get_format(filepath, format_schema, format_data)) {
- return -1;
- }
+ if (get_format(filepath, format_schema, format_data)) {
+ return -1;
}
- if (ly_in_new_filepath(filepath, 0, in)) {
+ if (in && ly_in_new_filepath(filepath, 0, in)) {
YLMSG_E("Unable to process input file.\n");
return -1;
}
@@ -88,701 +87,81 @@
return 0;
}
-void
-free_features(void *flist)
-{
- struct schema_features *rec = (struct schema_features *)flist;
-
- if (rec) {
- free(rec->mod_name);
- if (rec->features) {
- for (uint32_t u = 0; rec->features[u]; ++u) {
- free(rec->features[u]);
- }
- free(rec->features);
- }
- free(rec);
- }
-}
-
-void
-get_features(struct ly_set *fset, const char *module, const char ***features)
-{
- /* get features list for this module */
- for (uint32_t u = 0; u < fset->count; ++u) {
- struct schema_features *sf = (struct schema_features *)fset->objs[u];
-
- if (!strcmp(module, sf->mod_name)) {
- /* matched module - explicitly set features */
- *features = (const char **)sf->features;
- sf->applied = 1;
- return;
- }
- }
-
- /* features not set so disable all */
- *features = NULL;
-}
-
-int
-parse_features(const char *fstring, struct ly_set *fset)
-{
- struct schema_features *rec;
- uint32_t count;
- char *p, **fp;
-
- rec = calloc(1, sizeof *rec);
- if (!rec) {
- YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno));
- return -1;
- }
- if (ly_set_add(fset, rec, 1, NULL)) {
- YLMSG_E("Unable to store features information (%s).\n", strerror(errno));
- free(rec);
- return -1;
- }
-
- /* fill the record */
- p = strchr(fstring, ':');
- if (!p) {
- YLMSG_E("Invalid format of the features specification (%s).\n", fstring);
- return -1;
- }
- rec->mod_name = strndup(fstring, p - fstring);
-
- count = 0;
- while (p) {
- size_t len = 0;
- char *token = p + 1;
-
- p = strchr(token, ',');
- if (!p) {
- /* the last item, if any */
- len = strlen(token);
- } else {
- len = p - token;
- }
-
- if (len) {
- fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
- if (!fp) {
- YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
- return -1;
- }
- rec->features = fp;
- fp = &rec->features[count++]; /* array item to set */
- (*fp) = strndup(token, len);
- }
- }
-
- /* terminating NULL */
- fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
- if (!fp) {
- YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
- return -1;
- }
- rec->features = fp;
- rec->features[count++] = NULL;
-
- return 0;
-}
-
-struct cmdline_file *
-fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format)
-{
- struct cmdline_file *rec;
-
- rec = malloc(sizeof *rec);
- if (!rec) {
- YLMSG_E("Allocating memory for data file information failed.\n");
- return NULL;
- }
- rec->in = in;
- rec->path = path;
- rec->format = format;
-
- if (set && ly_set_add(set, rec, 1, NULL)) {
- free(rec);
- YLMSG_E("Storing data file information failed.\n");
- return NULL;
- }
-
- return rec;
-}
-
-int
-collect_features(const struct lys_module *mod, struct ly_set *set)
-{
- struct lysp_feature *f = NULL;
- uint32_t idx = 0;
-
- while ((f = lysp_feature_next(f, mod->parsed, &idx))) {
- if (ly_set_add(set, (void *)f->name, 1, NULL)) {
- YLMSG_E("Memory allocation failed.\n");
- ly_set_erase(set, NULL);
- return 1;
- }
- }
-
- return 0;
-}
-
-void
-print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set)
-{
- size_t max_len;
- uint32_t j;
- const char *name;
-
- /* header */
- ly_print(out, "%s:\n", mod->name);
-
- /* no features */
- if (!set->count) {
- ly_print(out, "\t(none)\n\n");
- return;
- }
-
- /* get max len, so the statuses of all the features will be aligned */
- max_len = 0;
- for (j = 0; j < set->count; ++j) {
- name = set->objs[j];
- if (strlen(name) > max_len) {
- max_len = strlen(name);
- }
- }
-
- /* print features */
- for (j = 0; j < set->count; ++j) {
- name = set->objs[j];
- ly_print(out, "\t%-*s (%s)\n", (int)max_len, name, lys_feature_value(mod, name) ? "off" : "on");
- }
-
- ly_print(out, "\n");
-}
-
-int
-generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param)
-{
- uint32_t j;
- /*
- * features_len - length of all the features in the current module
- * added_len - length of a string to be added, = features_len + extra necessary length
- * param_len - length of the parameter before appending new string
- */
- size_t features_len, added_len, param_len;
- char *tmp;
-
- features_len = 0;
- for (j = 0; j < set->count; j++) {
- features_len += strlen(set->objs[j]);
- }
-
- if (j == 0) {
- /* no features */
- added_len = strlen("-F ") + strlen(mod->name) + strlen(":");
- } else {
- /* j = comma count, -1 because of trailing comma */
- added_len = strlen("-F ") + strlen(mod->name) + strlen(":") + features_len + j - 1;
- }
-
- /* to avoid strlen(NULL) if this is the first call */
- param_len = 0;
- if (*features_param) {
- param_len = strlen(*features_param);
- }
-
- /* +1 because of white space at the beginning */
- tmp = realloc(*features_param, param_len + added_len + 1 + 1);
- if (!tmp) {
- goto error;
- } else {
- *features_param = tmp;
- }
- sprintf(*features_param + param_len, " -F %s:", mod->name);
-
- for (j = 0; j < set->count; j++) {
- strcat(*features_param, set->objs[j]);
- /* no trailing comma */
- if (j != (set->count - 1)) {
- strcat(*features_param, ",");
- }
- }
-
- return 0;
-
-error:
- YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno));
- return 1;
-}
-
-int
-print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param)
-{
- int ret = 0;
- uint32_t i = 0;
- struct lys_module *mod;
- struct ly_set set = {0};
-
- while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) {
- /* only care about implemented modules */
- if (!mod->implemented) {
- continue;
- }
-
- /* always erase the set, so the previous module's features don't carry over to the next module's features */
- ly_set_erase(&set, NULL);
-
- if (collect_features(mod, &set)) {
- ret = 1;
- goto cleanup;
- }
-
- if (generate_features && generate_features_output(mod, &set, features_param)) {
- ret = 1;
- goto cleanup;
- }
- print_features(out, mod, &set);
- }
-
-cleanup:
- ly_set_erase(&set, NULL);
- return ret;
-}
-
-void
-free_cmdline_file(void *cmdline_file)
-{
- struct cmdline_file *rec = (struct cmdline_file *)cmdline_file;
-
- if (rec) {
- ly_in_free(rec->in, 1);
- free(rec);
- }
-}
-
-void
-free_cmdline(char *argv[])
-{
- if (argv) {
- free(argv[0]);
- free(argv);
- }
-}
-
-int
-parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[])
-{
- int count;
- char **vector;
- char *ptr;
- char qmark = 0;
-
- assert(cmdline);
- assert(argc_p);
- assert(argv_p);
-
- /* init */
- optind = 0; /* reinitialize getopt() */
- count = 1;
- vector = malloc((count + 1) * sizeof *vector);
- vector[0] = strdup(cmdline);
-
- /* command name */
- strtok(vector[0], " ");
-
- /* arguments */
- while ((ptr = strtok(NULL, " "))) {
- size_t len;
- void *r;
-
- len = strlen(ptr);
-
- if (qmark) {
- /* still in quotated text */
- /* remove NULL termination of the previous token since it is not a token,
- * but a part of the quotation string */
- ptr[-1] = ' ';
-
- if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
- /* end of quotation */
- qmark = 0;
- /* shorten the argument by the terminating quotation mark */
- ptr[len - 1] = '\0';
- }
- continue;
- }
-
- /* another token in cmdline */
- ++count;
- r = realloc(vector, (count + 1) * sizeof *vector);
- if (!r) {
- YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno));
- free(vector);
- return -1;
- }
- vector = r;
- vector[count - 1] = ptr;
-
- if ((ptr[0] == '"') || (ptr[0] == '\'')) {
- /* remember the quotation mark to identify end of quotation */
- qmark = ptr[0];
-
- /* move the remembered argument after the quotation mark */
- ++vector[count - 1];
-
- /* check if the quotation is terminated within this token */
- if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
- /* end of quotation */
- qmark = 0;
- /* shorten the argument by the terminating quotation mark */
- ptr[len - 1] = '\0';
- }
- }
- }
- vector[count] = NULL;
-
- *argc_p = count;
- *argv_p = vector;
-
- return 0;
-}
-
-int
-get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data)
+LYS_INFORMAT
+get_schema_format(const char *filename)
{
char *ptr;
- LYS_INFORMAT informat_s;
- LYD_FORMAT informat_d;
- /* get the file format */
if ((ptr = strrchr(filename, '.')) != NULL) {
++ptr;
if (!strcmp(ptr, "yang")) {
- informat_s = LYS_IN_YANG;
- informat_d = 0;
+ return LYS_IN_YANG;
} else if (!strcmp(ptr, "yin")) {
- informat_s = LYS_IN_YIN;
- informat_d = 0;
- } else if (!strcmp(ptr, "xml")) {
- informat_s = 0;
- informat_d = LYD_XML;
- } else if (!strcmp(ptr, "json")) {
- informat_s = 0;
- informat_d = LYD_JSON;
- } else if (!strcmp(ptr, "lyb")) {
- informat_s = 0;
- informat_d = LYD_LYB;
+ return LYS_IN_YIN;
} else {
- YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr);
- return 0;
+ return LYS_IN_UNKNOWN;
}
} else {
- YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename);
- return 1;
+ return LYS_IN_UNKNOWN;
}
-
- if (informat_d) {
- if (!data) {
- YLMSG_E("Input file \"%s\" not expected to contain data instances (unexpected format).\n", filename);
- return 2;
- }
- (*data) = informat_d;
- } else if (informat_s) {
- if (!schema) {
- YLMSG_E("Input file \"%s\" not expected to contain schema definition (unexpected format).\n", filename);
- return 3;
- }
- (*schema) = informat_s;
- }
-
- return 0;
}
-int
-print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat)
+LYD_FORMAT
+get_data_format(const char *filename)
{
- struct lyd_node *ylib;
- uint32_t idx = 0, has_modules = 0;
- const struct lys_module *mod;
+ char *ptr;
- if (outformat != LYD_UNKNOWN) {
- if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) {
- YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, use an option to add it internally.\n");
- return 1;
- }
-
- lyd_print_all(out, ylib, outformat, 0);
- lyd_free_all(ylib);
- return 0;
- }
-
- /* iterate schemas in context and provide just the basic info */
- ly_print(out, "List of the loaded models:\n");
- while ((mod = ly_ctx_get_module_iter(ctx, &idx))) {
- has_modules++;
-
- /* conformance print */
- if (mod->implemented) {
- ly_print(out, " I");
+ if ((ptr = strrchr(filename, '.')) != NULL) {
+ ++ptr;
+ if (!strcmp(ptr, "xml")) {
+ return LYD_XML;
+ } else if (!strcmp(ptr, "json")) {
+ return LYD_JSON;
+ } else if (!strcmp(ptr, "lyb")) {
+ return LYD_LYB;
} else {
- ly_print(out, " i");
+ return LYD_UNKNOWN;
}
-
- /* module print */
- ly_print(out, " %s", mod->name);
- if (mod->revision) {
- ly_print(out, "@%s", mod->revision);
- }
-
- /* submodules print */
- if (mod->parsed && mod->parsed->includes) {
- uint64_t u = 0;
-
- ly_print(out, " (");
- LY_ARRAY_FOR(mod->parsed->includes, u) {
- ly_print(out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name);
- if (mod->parsed->includes[u].rev[0]) {
- ly_print(out, "@%s", mod->parsed->includes[u].rev);
- }
- }
- ly_print(out, ")");
- }
-
- /* finish the line */
- ly_print(out, "\n");
+ } else {
+ return LYD_UNKNOWN;
}
-
- if (!has_modules) {
- ly_print(out, "\t(none)\n");
- }
-
- ly_print_flush(out);
- return 0;
}
int
-evaluate_xpath(const struct lyd_node *tree, const char *xpath)
+get_format(const char *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form)
{
- struct ly_set *set = NULL;
+ LYS_INFORMAT schema;
+ LYD_FORMAT data;
- if (lyd_find_xpath(tree, xpath, &set)) {
+ schema = !schema_form || !*schema_form ? LYS_IN_UNKNOWN : *schema_form;
+ data = !data_form || !*data_form ? LYD_UNKNOWN : *data_form;
+
+ if (!schema) {
+ schema = get_schema_format(filepath);
+ }
+ if (!data) {
+ data = get_data_format(filepath);
+ }
+
+ if (!schema && !data) {
+ YLMSG_E("Input schema format for %s file not recognized.", filepath);
+ return -1;
+ } else if (!data && !schema) {
+ YLMSG_E("Input data format for %s file not recognized.", filepath);
return -1;
}
+ assert(schema || data);
- /* print result */
- printf("XPath \"%s\" evaluation result:\n", xpath);
- if (!set->count) {
- printf("\tEmpty\n");
- } else {
- for (uint32_t u = 0; u < set->count; ++u) {
- struct lyd_node *node = (struct lyd_node *)set->objs[u];
-
- printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
- if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
- printf(" (value: \"%s\")\n", lyd_get_value(node));
- } else if (node->schema->nodetype == LYS_LIST) {
- printf(" (");
- for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
- printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
- key->schema->name, lyd_get_value(key));
- }
- printf(")\n");
- }
- }
+ if (schema_form) {
+ *schema_form = schema;
+ }
+ if (data_form) {
+ *data_form = data;
}
- ly_set_free(set, NULL);
return 0;
}
-LY_ERR
-process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
- uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f,
- struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths)
-{
- LY_ERR ret = LY_SUCCESS;
- struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL;
- char *path = NULL;
- struct ly_set *set = NULL;
-
- /* additional operational datastore */
- if (operational_f && operational_f->in) {
- ret = lyd_parse_data(ctx, NULL, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &oper_tree);
- if (ret) {
- YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path);
- goto cleanup;
- }
- }
-
- for (uint32_t u = 0; u < inputs->count; ++u) {
- struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
-
- switch (data_type) {
- case LYD_TYPE_DATA_YANG:
- ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, options_parse, options_validate, &tree);
- break;
- case LYD_TYPE_RPC_YANG:
- case LYD_TYPE_REPLY_YANG:
- case LYD_TYPE_NOTIF_YANG:
- ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &tree, &op);
- break;
- case LYD_TYPE_RPC_NETCONF:
- case LYD_TYPE_NOTIF_NETCONF:
- ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &envp, &op);
-
- /* adjust pointers */
- for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
- break;
- case LYD_TYPE_REPLY_NETCONF:
- /* parse source RPC operation */
- assert(rpc_f && rpc_f->in);
- ret = lyd_parse_op(ctx, NULL, rpc_f->in, rpc_f->format, LYD_TYPE_RPC_NETCONF, &envp, &op);
- if (ret) {
- YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".\n", rpc_f->path);
- goto cleanup;
- }
-
- /* adjust pointers */
- for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
-
- /* free input */
- lyd_free_siblings(lyd_child(op));
-
- /* we do not care */
- lyd_free_all(envp);
- envp = NULL;
-
- ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, data_type, &envp, NULL);
- break;
- default:
- YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
- goto cleanup;
- }
-
- if (ret) {
- YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
- goto cleanup;
- }
-
- if (merge) {
- /* merge the data so far parsed for later validation and print */
- if (!merged_tree) {
- merged_tree = tree;
- } else {
- ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
- if (ret) {
- YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
- goto cleanup;
- }
- }
- tree = NULL;
- } else if (format) {
- /* print */
- switch (data_type) {
- case LYD_TYPE_DATA_YANG:
- lyd_print_all(out, tree, format, options_print);
- break;
- case LYD_TYPE_RPC_YANG:
- case LYD_TYPE_REPLY_YANG:
- case LYD_TYPE_NOTIF_YANG:
- case LYD_TYPE_RPC_NETCONF:
- case LYD_TYPE_NOTIF_NETCONF:
- lyd_print_tree(out, tree, format, options_print);
- break;
- case LYD_TYPE_REPLY_NETCONF:
- /* just the output */
- lyd_print_tree(out, lyd_child(tree), format, options_print);
- break;
- default:
- assert(0);
- }
- } else {
- /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */
- switch (data_type) {
- case LYD_TYPE_DATA_YANG:
- /* already validated */
- break;
- case LYD_TYPE_RPC_YANG:
- case LYD_TYPE_RPC_NETCONF:
- ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_RPC_YANG, NULL);
- break;
- case LYD_TYPE_REPLY_YANG:
- case LYD_TYPE_REPLY_NETCONF:
- ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_REPLY_YANG, NULL);
- break;
- case LYD_TYPE_NOTIF_YANG:
- case LYD_TYPE_NOTIF_NETCONF:
- ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_NOTIF_YANG, NULL);
- break;
- default:
- assert(0);
- }
- if (ret) {
- if (operational_f->path) {
- YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
- input_f->path, operational_f->path);
- } else {
- YLMSG_E("Failed to validate input data file \"%s\".\n", input_f->path);
- }
- goto cleanup;
- }
-
- if (op && oper_tree && lyd_parent(op)) {
- /* check operation parent existence */
- path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0);
- if (!path) {
- ret = LY_EMEM;
- goto cleanup;
- }
- if ((ret = lyd_find_xpath(oper_tree, path, &set))) {
- goto cleanup;
- }
- if (!set->count) {
- YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.\n", LYD_NAME(op), path);
- ret = LY_EVALID;
- goto cleanup;
- }
- }
- }
-
- /* next iter */
- lyd_free_all(tree);
- tree = NULL;
- lyd_free_all(envp);
- envp = NULL;
- }
-
- if (merge) {
- /* validate the merged result */
- ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL);
- if (ret) {
- YLMSG_E("Merged data are not valid.\n");
- goto cleanup;
- }
-
- if (format) {
- /* and print it */
- lyd_print_all(out, merged_tree, format, options_print);
- }
-
- for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) {
- if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) {
- goto cleanup;
- }
- }
- }
-
-cleanup:
- lyd_free_all(tree);
- lyd_free_all(envp);
- lyd_free_all(merged_tree);
- lyd_free_all(oper_tree);
- free(path);
- ly_set_free(set, NULL);
- return ret;
-}
-
const struct lysc_node *
find_schema_path(const struct ly_ctx *ctx, const char *schema_path)
{
@@ -866,3 +245,42 @@
free(module_name);
return parent_node;
}
+
+LY_ERR
+ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free)
+{
+ struct ly_ctx *ctx;
+ struct lyd_node *data = NULL;
+
+ ctx = ext->module->ctx;
+ if (user_data) {
+ lyd_parse_data_path(ctx, user_data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data);
+ }
+
+ *ext_data = data;
+ *ext_data_free = 1;
+ return LY_SUCCESS;
+}
+
+LY_ERR
+searchpath_strcat(char **searchpaths, const char *path)
+{
+ uint64_t len;
+ char *new;
+
+ if (!(*searchpaths)) {
+ *searchpaths = strdup(path);
+ return LY_SUCCESS;
+ }
+
+ len = strlen(*searchpaths) + strlen(path) + strlen(PATH_SEPARATOR);
+ new = realloc(*searchpaths, sizeof(char) * len + 1);
+ if (!new) {
+ return LY_EMEM;
+ }
+ strcat(new, PATH_SEPARATOR);
+ strcat(new, path);
+ *searchpaths = new;
+
+ return LY_SUCCESS;
+}
diff --git a/tools/lint/common.h b/tools/lint/common.h
index c3b8f95..340cecd 100644
--- a/tools/lint/common.h
+++ b/tools/lint/common.h
@@ -1,9 +1,10 @@
/**
* @file common.h
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief libyang's yanglint tool - common functions and definitions for both interactive and non-interactive mode.
*
- * Copyright (c) 2020 CESNET, z.s.p.o.
+ * Copyright (c) 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -55,90 +56,7 @@
# define PATH_SEPARATOR ";"
#endif
-/**
- * @brief Storage for the list of the features (their names) in a specific YANG module.
- */
-struct schema_features {
- char *mod_name;
- char **features;
- ly_bool applied;
-};
-
-/**
- * @brief Data connected with a file provided on a command line as a file path.
- */
-struct cmdline_file {
- struct ly_in *in;
- const char *path;
- LYD_FORMAT format;
-};
-
-/**
- * @brief Free the schema features list (struct schema_features *)
- * @param[in,out] flist The (struct schema_features *) to free.
- */
-void free_features(void *flist);
-
-/**
- * @brief Get the list of features connected with the specific YANG module.
- *
- * @param[in] fset The set of features information (struct schema_features *).
- * @param[in] module Name of the YANG module which features should be found.
- * @param[out] features Pointer to the list of features being returned.
- */
-void get_features(struct ly_set *fset, const char *module, const char ***features);
-
-/**
- * @brief Parse features being specified for the specific YANG module.
- *
- * Format of the input @p fstring is as follows: <module_name>:[<feature>,]*
- *
- * @param[in] fstring Input string to be parsed.
- * @param[in, out] fset Features information set (of struct schema_features *). The set is being filled.
- */
-int parse_features(const char *fstring, struct ly_set *fset);
-
-/**
- * @brief Collect all features of a module.
- *
- * @param[in] mod Module to be searched for features.
- * @param[out] set Set in which the features will be stored.
- * @return 0 on success.
- * @return 1 on error.
- */
-int collect_features(const struct lys_module *mod, struct ly_set *set);
-
-/**
- * @brief Print all features of a single module.
- *
- * @param[in] out The output handler for printing.
- * @param[in] mod Module which contains the features.
- * @param[in] set Set which holds the features.
- */
-void print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set);
-
-/**
- * @brief Generate a string, which will contain features paramater.
- *
- * @param[in] mod Module, for which the string will be generated.
- * @param[in] set Set containing the features.
- * @param[out] features_param String which will contain the output.
- * @return 0 on success.
- * @return 1 on error.
- */
-int generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param);
-
-/**
- * @brief Print all features of all implemented modules.
- *
- * @param[in] out The output handler for printing.
- * @param[in] ctx Libyang context.
- * @param[in] generate_features Flag expressing whether to generate features parameter.
- * @param[out] features_param String, which will contain the output if the above flag is set.
- * @return 0 on success.
- * @return 1 on error.
- */
-int print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param);
+struct cmdline_file;
/**
* @brief Parse path of a schema module file into the directory and module name.
@@ -146,7 +64,7 @@
* @param[in] path Schema module file path to be parsed.
* @param[out] dir Pointer to the directory path where the file resides. Caller is expected to free the returned string.
* @param[out] module Pointer to the name of the module (without file suffixes or revision information) specified by the
- * @path. Caller is expected to free the returned string.
+ * @p path. Caller is expected to free the returned string.
* @return 0 on success
* @return -1 on error
*/
@@ -163,92 +81,40 @@
* prohibited and such files are refused.
* @param[out] format_data Format of the data detected from the file name. If NULL specified, the data formats are
* prohibited and such files are refused.
- * @param[out] in Created input handler referring the file behind the @p filepath.
+ * @param[out] in Created input handler referring the file behind the @p filepath. Can be NULL.
* @return 0 on success.
* @return -1 on failure.
*/
int get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in);
/**
- * @brief Free the command line file data (struct cmdline_file *)
- * @param[in,out] cmdline_file The (struct cmdline_file *) to free.
- */
-void free_cmdline_file(void *cmdline_file);
-
-/**
- * @brief Create and fill the command line file data (struct cmdline_file *).
- * @param[in] set Optional parameter in case the record is supposed to be added into a set.
- * @param[in] in Input file handler.
- * @param[in] path Filepath of the file.
- * @param[in] format Format of the data file.
- * @return The created command line file structure.
- * @return NULL on failure
- */
-struct cmdline_file *fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format);
-
-/**
- * @brief Helper function to prepare argc, argv pair from a command line string.
+ * @brief Get schema format of the @p filename's content according to the @p filename's suffix.
*
- * @param[in] cmdline Complete command line string.
- * @param[out] argc_p Pointer to store argc value.
- * @param[out] argv_p Pointer to store argv vector.
- * @return 0 on success, non-zero on failure.
- */
-int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]);
-
-/**
- * @brief Destructor for the argument vector prepared by ::parse_cmdline().
- *
- * @param[in,out] argv Argument vector to destroy.
- */
-void free_cmdline(char *argv[]);
-
-/**
- * @brief Get expected format of the @p filename's content according to the @p filename's suffix.
* @param[in] filename Name of the file to examine.
- * @param[out] schema Pointer to a variable to store the expected input schema format. Do not provide the pointer in case a
- * schema format is not expected.
- * @param[out] data Pointer to a variable to store the expected input data format. Do not provide the pointer in case a data
- * format is not expected.
+ * @return Detected schema input format.
+ */
+LYS_INFORMAT get_schema_format(const char *filename);
+
+/**
+ * @brief Get data format of the @p filename's content according to the @p filename's suffix.
+ *
+ * @param[in] filename Name of the file to examine.
+ * @return Detected data input format.
+ */
+LYD_FORMAT get_data_format(const char *filename);
+
+/**
+ * @brief Get format of the @p filename's content according to the @p filename's suffix.
+ *
+ * Either the @p schema or @p data parameter is set.
+ *
+ * @param[in] filepath Name of the file to examine.
+ * @param[out] schema_form Pointer to a variable to store the input schema format.
+ * @param[out] data_form Pointer to a variable to store the expected input data format.
* @return zero in case a format was successfully detected.
* @return nonzero in case it is not possible to get valid format from the @p filename.
*/
-int get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data);
-
-/**
- * @brief Print list of schemas in the context.
- *
- * @param[in] out Output handler where to print.
- * @param[in] ctx Context to print.
- * @param[in] outformat Optional output format. If not specified (:LYD_UNKNOWN), a simple list with single module per line
- * is printed. Otherwise, the ietf-yang-library data are printed in the specified format.
- * @return zero in case the data successfully printed.
- * @return nonzero in case of error.
- */
-int print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat);
-
-/**
- * @brief Process the input data files - parse, validate and print according to provided options.
- *
- * @param[in] ctx libyang context with schema.
- * @param[in] data_type The type of data in the input files.
- * @param[in] merge Flag if the data should be merged before validation.
- * @param[in] format Data format for printing.
- * @param[in] out The output handler for printing.
- * @param[in] options_parse Parser options.
- * @param[in] options_validate Validation options.
- * @param[in] options_print Printer options.
- * @param[in] operational_f Optional operational datastore file information for the case of an extended validation of
- * operation(s).
- * @param[in] rpc_f Source RPC operation file information for parsing NETCONF rpc-reply.
- * @param[in] inputs Set of file informations of input data files.
- * @param[in] xpath The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set
- * is printed. Alternative to data printing.
- * @return LY_ERR value.
- */
-LY_ERR process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
- uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f,
- struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths);
+int get_format(const char *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form);
/**
* @brief Get the node specified by the path.
@@ -257,6 +123,27 @@
* @param[in] schema_path Path to the wanted node.
* @return Pointer to the schema node specified by the path on success, NULL otherwise.
*/
-const struct lysc_node * find_schema_path(const struct ly_ctx *ctx, const char *schema_path);
+const struct lysc_node *find_schema_path(const struct ly_ctx *ctx, const char *schema_path);
+
+/**
+ * @brief General callback providing run-time extension instance data.
+ *
+ * @param[in] ext Compiled extension instance.
+ * @param[in] user_data User-supplied callback data.
+ * @param[out] ext_data Provided extension instance data.
+ * @param[out] ext_data_free Whether the extension instance should free @p ext_data or not.
+ * @return LY_ERR value.
+ */
+LY_ERR ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free);
+
+/**
+ * @brief Concatenation of paths into one string.
+ *
+ * @param[in,out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":"
+ * (on Windows, used semicolon ";" instead).
+ * @param[in] path Path to add.
+ * @return LY_ERR value.
+ */
+LY_ERR searchpath_strcat(char **searchpaths, const char *path);
#endif /* COMMON_H_ */
diff --git a/tools/lint/completion.c b/tools/lint/completion.c
index 9843816..7a1842a 100644
--- a/tools/lint/completion.c
+++ b/tools/lint/completion.c
@@ -43,14 +43,18 @@
{
void *p;
- ++(*match_count);
- p = realloc(*matches, *match_count * sizeof **matches);
+ p = realloc(*matches, (*match_count + 1) * sizeof **matches);
if (!p) {
YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
return;
}
*matches = p;
- (*matches)[*match_count - 1] = strdup(match);
+ (*matches)[*match_count] = strdup(match);
+ if (!((*matches)[*match_count])) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ return;
+ }
+ ++(*match_count);
}
/**
@@ -76,6 +80,34 @@
}
/**
+ * @brief Provides completion for arguments.
+ *
+ * @param[in] hint User input.
+ * @param[in] args Array of all possible arguments. The last element must be NULL.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_arg_completion(const char *hint, const char **args, char ***matches, unsigned int *match_count)
+{
+ int i;
+
+ *match_count = 0;
+ *matches = NULL;
+
+ for (i = 0; args[i]; i++) {
+ if (!strncmp(hint, args[i], strlen(hint))) {
+ cmd_completion_add_match(args[i], matches, match_count);
+ }
+ }
+ if (*match_count == 0) {
+ for (i = 0; args[i]; i++) {
+ cmd_completion_add_match(args[i], matches, match_count);
+ }
+ }
+}
+
+/**
* @brief Provides completion for module names.
*
* @param[in] hint User input.
@@ -108,21 +140,21 @@
/**
* @brief Add all child nodes of a single node to the completion hint.
*
- * @param[in] last_node Node of which children will be added to the hint.
- * @param matches[out] Matches provided to the user as a completion hint.
- * @param match_count[out] Number of matches.
+ * @param[in] parent Node of which children will be added to the hint.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
*/
static void
-single_hint_add_children(const struct lysc_node *last_node, char ***matches, unsigned int *match_count)
+single_hint_add_children(const struct lysc_node *parent, char ***matches, unsigned int *match_count)
{
const struct lysc_node *node = NULL;
char *match;
- if (!last_node) {
+ if (!parent) {
return;
}
- while ((node = lys_getnext(node, last_node, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) {
+ while ((node = lys_getnext(node, parent, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) {
match = lysc_path(node, LYSC_PATH_LOG, NULL, 0);
cmd_completion_add_match(match, matches, match_count);
free(match);
@@ -277,6 +309,90 @@
}
/**
+ * @brief Get all possible argument hints for option.
+ *
+ * @param[in] hint User input.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_print_format_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+ const char *args[] = {"yang", "yin", "tree", "info", NULL};
+
+ get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_data_type_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+ const char *args[] = {"data", "config", "get", "getconfig", "edit", "rpc", "reply", "notif", NULL};
+
+ get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_data_in_format_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+ const char *args[] = {"xml", "json", "lyb", NULL};
+
+ get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_data_default_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+ const char *args[] = {"all", "all-tagged", "trim", "implicit-tagged", NULL};
+
+ get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_list_format_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+ const char *args[] = {"xml", "json", NULL};
+
+ get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_verb_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+ const char *args[] = {"error", "warning", "verbose", "debug", NULL};
+
+ get_arg_completion(hint, args, matches, match_count);
+}
+
+#ifndef NDEBUG
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_debug_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+ const char *args[] = {"dict", "xpath", "dep-sets", NULL};
+
+ get_arg_completion(hint, args, matches, match_count);
+}
+
+#endif
+
+/**
* @brief Get the string before the hint, which autocompletion is for.
*
* @param[in] buf Complete user input.
@@ -315,22 +431,37 @@
complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc)
{
struct autocomplete {
- const char *cmd; /**< command */
- const char *opt; /**< optional option */
- int last_opt; /**< whether to autocomplete even if an option is last in the hint */
-
+ enum COMMAND_INDEX ci; /**< command index to global variable 'commands' */
+ const char *opt; /**< optional option */
void (*ln_cb)(const char *, const char *, linenoiseCompletions *); /**< linenoise callback to call */
void (*yl_cb)(const char *, char ***, unsigned int *); /**< yanglint callback to call */
} ac[] = {
- {"add", NULL, 1, linenoisePathCompletion, NULL},
- {"searchpath", NULL, 0, linenoisePathCompletion, NULL},
- {"data", NULL, 0, linenoisePathCompletion, NULL},
- {"print", NULL, 0, NULL, get_model_completion},
- {"feature", NULL, 0, NULL, get_model_completion},
- {"print", "-P", 1, NULL, get_schema_completion},
+ {CMD_ADD, NULL, linenoisePathCompletion, NULL},
+ {CMD_PRINT, "-f", NULL, get_print_format_arg},
+ {CMD_PRINT, "-P", NULL, get_schema_completion},
+ {CMD_PRINT, "-o", linenoisePathCompletion, NULL},
+ {CMD_PRINT, NULL, NULL, get_model_completion},
+ {CMD_SEARCHPATH, NULL, linenoisePathCompletion, NULL},
+ {CMD_EXTDATA, NULL, linenoisePathCompletion, NULL},
+ {CMD_CLEAR, "-Y", linenoisePathCompletion, NULL},
+ {CMD_DATA, "-t", NULL, get_data_type_arg},
+ {CMD_DATA, "-O", linenoisePathCompletion, NULL},
+ {CMD_DATA, "-R", linenoisePathCompletion, NULL},
+ {CMD_DATA, "-f", NULL, get_data_in_format_arg},
+ {CMD_DATA, "-F", NULL, get_data_in_format_arg},
+ {CMD_DATA, "-d", NULL, get_data_default_arg},
+ {CMD_DATA, "-o", linenoisePathCompletion, NULL},
+ {CMD_DATA, NULL, linenoisePathCompletion, NULL},
+ {CMD_LIST, NULL, NULL, get_list_format_arg},
+ {CMD_FEATURE, NULL, NULL, get_model_completion},
+ {CMD_VERB, NULL, NULL, get_verb_arg},
+#ifndef NDEBUG
+ {CMD_DEBUG, NULL, NULL, get_debug_arg},
+#endif
};
- size_t cmd_len;
- const char *last;
+ size_t name_len;
+ const char *last, *name, *getoptstr;
+ char opt[3] = {'\0', ':', '\0'};
char **matches = NULL;
unsigned int match_count = 0, i;
@@ -340,24 +471,27 @@
} else {
for (i = 0; i < (sizeof ac / sizeof *ac); ++i) {
- cmd_len = strlen(ac[i].cmd);
- if (strncmp(buf, ac[i].cmd, cmd_len) || (buf[cmd_len] != ' ')) {
+ /* Find the right command. */
+ name = commands[ac[i].ci].name;
+ name_len = strlen(name);
+ if (strncmp(buf, name, name_len) || (buf[name_len] != ' ')) {
/* not this command */
continue;
}
+ /* Select based on the right option. */
last = get_last_str(buf, hint);
- if (ac[i].opt && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
- /* autocompletion for (another) option */
+ opt[0] = (last[0] == '-') && last[1] ? last[1] : '\0';
+ getoptstr = commands[ac[i].ci].optstring;
+ if (!ac[i].opt && opt[0] && strstr(getoptstr, opt)) {
+ /* completion for the argument must be defined */
continue;
- }
- if (!ac[i].last_opt && (last[0] == '-')) {
- /* autocompletion for the command, not an option */
+ } else if (ac[i].opt && opt[0] && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
+ /* completion for (another) option */
continue;
- }
- if ((last != buf) && (last[0] != '-')) {
- /* autocompleted */
- return;
+ } else if (ac[i].opt && !opt[0]) {
+ /* completion is defined for option */
+ continue;
}
/* callback */
diff --git a/tools/lint/examples/README.md b/tools/lint/examples/README.md
index 36cecd1..93d3c2a 100644
--- a/tools/lint/examples/README.md
+++ b/tools/lint/examples/README.md
@@ -33,11 +33,11 @@
```
> help searchpath
Usage: searchpath [--clear] [<modules-dir-path> ...]
- Set paths of directories where to search for imports and
- includes of the schema modules. The current working directory
- and the path of the module being added is used implicitly.
- The 'load' command uses these paths to search even for the
- schema modules to be loaded.
+ Set paths of directories where to search for imports and includes
+ of the schema modules. Subdirectories are also searched. The current
+ working directory and the path of the module being added is used implicitly.
+ The 'load' command uses these paths to search even for the schema modules
+ to be loaded.
```
The input files referred in this document are available together with this
diff --git a/tools/lint/main.c b/tools/lint/main.c
index 9f0d027..987ea10 100644
--- a/tools/lint/main.c
+++ b/tools/lint/main.c
@@ -1,9 +1,10 @@
/**
* @file main.c
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief libyang's yanglint tool
*
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -27,6 +28,7 @@
#include "completion.h"
#include "configuration.h"
#include "linenoise/linenoise.h"
+#include "yl_opt.h"
int done;
struct ly_ctx *ctx = NULL;
@@ -37,13 +39,17 @@
int
main(int argc, char *argv[])
{
- char *cmdline;
- int cmdlen;
+ int cmdlen, posc, i, j;
+ struct yl_opt yo = {0};
+ char *empty = NULL, *cmdline;
+ char **posv;
+ uint8_t cmd_found;
if (argc > 1) {
/* run in non-interactive mode */
return main_ni(argc, argv);
}
+ yo.interactive = 1;
/* continue in interactive mode */
linenoiseSetCompletionCallback(complete_cmd);
@@ -55,7 +61,10 @@
}
while (!done) {
- uint8_t executed = 0;
+ cmd_found = 0;
+
+ posv = ∅
+ posc = 0;
/* get the command from user */
cmdline = linenoise(PROMPT);
@@ -76,25 +85,48 @@
for (cmdlen = 0; cmdline[cmdlen] && (cmdline[cmdlen] != ' '); cmdlen++) {}
/* execute the command if any valid specified */
- for (uint16_t i = 0; commands[i].name; i++) {
+ for (i = 0; commands[i].name; i++) {
if (strncmp(cmdline, commands[i].name, (size_t)cmdlen) || (commands[i].name[cmdlen] != '\0')) {
continue;
}
- commands[i].func(&ctx, cmdline);
- executed = 1;
+ cmd_found = 1;
+ if (commands[i].opt_func && commands[i].opt_func(&yo, cmdline, &posv, &posc)) {
+ break;
+ }
+ if (commands[i].dep_func && commands[i].dep_func(&yo, posc)) {
+ break;
+ }
+ if (posc) {
+ for (j = 0; j < posc; j++) {
+ yo.last_one = (j + 1) == posc;
+ if (commands[i].exec_func(&ctx, &yo, posv[j])) {
+ break;
+ }
+ }
+ } else {
+ commands[i].exec_func(&ctx, &yo, NULL);
+ }
+ if (commands[i].fin_func) {
+ commands[i].fin_func(ctx, &yo);
+ }
+
break;
}
- if (!executed) {
+ if (!cmd_found) {
/* if unknown command specified, tell it to user */
YLMSG_E("Unknown command \"%.*s\", type 'help' for more information.\n", cmdlen, cmdline);
}
linenoiseHistoryAdd(cmdline);
free(cmdline);
+ yl_opt_erase(&yo);
}
+ /* Global variables in commands are freed. */
+ cmd_free();
+
store_config();
ly_ctx_destroy(ctx);
diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c
index 6e00339..6f04623 100644
--- a/tools/lint/main_ni.c
+++ b/tools/lint/main_ni.c
@@ -2,9 +2,10 @@
* @file main_ni.c
* @author Radek Krejci <rkrejci@cesnet.cz>
* @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief libyang's yanglint tool - non-interactive code
*
- * Copyright (c) 2020 - 2022 CESNET, z.s.p.o.
+ * Copyright (c) 2020 - 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@@ -25,107 +26,13 @@
#include <sys/stat.h>
#include "libyang.h"
-#include "plugins_exts.h"
+#include "cmd.h"
#include "common.h"
#include "out.h"
#include "tools/config.h"
-
-/**
- * @brief Context structure to hold and pass variables in a structured form.
- */
-struct context {
- /* libyang context for the run */
- const char *yang_lib_file;
- uint16_t ctx_options;
- struct ly_ctx *ctx;
-
- /* prepared output (--output option or stdout by default) */
- struct ly_out *out;
-
- char *searchpaths;
-
- /* options flags */
- uint8_t list; /* -l option to print list of schemas */
-
- /* line length for 'tree' format */
- size_t line_length; /* --tree-line-length */
-
- /*
- * schema
- */
- /* set schema modules' features via --features option (struct schema_features *) */
- struct ly_set schema_features;
-
- /* set of loaded schema modules (struct lys_module *) */
- struct ly_set schema_modules;
-
- /* options to parse and print schema modules */
- uint32_t schema_parse_options;
- uint32_t schema_print_options;
-
- /* specification of printing schema node subtree, option --schema-node */
- const char *schema_node_path;
- const struct lysc_node *schema_node;
- const char *submodule;
-
- /* name of file containing explicit context passed to callback
- * for schema-mount extension. This also causes a callback to
- * be registered.
- */
- char *schema_context_filename;
-
- /* value of --format in case of schema format */
- LYS_OUTFORMAT schema_out_format;
- ly_bool feature_param_format;
-
- /*
- * data
- */
- /* various options based on --type option */
- enum lyd_type data_type;
- uint32_t data_parse_options;
- uint32_t data_validate_options;
- uint32_t data_print_options;
-
- /* flag for --merge option */
- uint8_t data_merge;
-
- /* value of --format in case of data format */
- LYD_FORMAT data_out_format;
-
- /* input data files (struct cmdline_file *) */
- struct ly_set data_inputs;
-
- /* storage for --operational */
- struct cmdline_file data_operational;
-
- /* storage for --reply-rpc */
- struct cmdline_file reply_rpc;
-};
-
-static void
-erase_context(struct context *c)
-{
- /* data */
- ly_set_erase(&c->data_inputs, free_cmdline_file);
- ly_in_free(c->data_operational.in, 1);
-
- /* schema */
- ly_set_erase(&c->schema_features, free_features);
- ly_set_erase(&c->schema_modules, NULL);
-
- /* context */
- free(c->searchpaths);
- c->searchpaths = NULL;
-
- ly_out_free(c->out, NULL, 0);
- ly_ctx_destroy(c->ctx);
-
- if (c->schema_context_filename) {
- free(c->schema_context_filename);
- }
-}
+#include "yl_opt.h"
+#include "yl_schema_features.h"
static void
version(void)
@@ -146,7 +53,8 @@
" printing them in the specified format.\n\n"
" yanglint -t (nc-)rpc/notif [-O <operational-file>] <schema>... <file>\n"
" Validates the YANG/NETCONF RPC/notification <file> according to the <schema>(s) using\n"
- " <operational-file> with possible references to the operational datastore data.\n\n"
+ " <operational-file> with possible references to the operational datastore data.\n"
+ " To validate nested-notification or action, the <operational-file> is required.\n\n"
" yanglint -t nc-reply -R <rpc-file> [-O <operational-file>] <schema>... <file>\n"
" Validates the NETCONF rpc-reply <file> of RPC <rpc-file> according to the <schema>(s)\n"
" using <operational-file> with possible references to the operational datastore data.\n\n"
@@ -169,10 +77,16 @@
" yang, yin, tree, info and feature-param for schemas,\n"
" xml, json, and lyb for data.\n\n");
+ printf(" -I FORMAT, --in-format=FORMAT\n"
+ " Load the data in one of the following formats:\n"
+ " xml, json, lyb\n"
+ " If input format not specified, it is detected from the file extension.\n\n");
+
printf(" -p PATH, --path=PATH\n"
" Search path for schema (YANG/YIN) modules. The option can be\n"
" used multiple times. The current working directory and the\n"
- " path of the module being added is used implicitly.\n\n");
+ " path of the module being added is used implicitly. Subdirectories\n"
+ " are also searched\n\n");
printf(" -D, --disable-searchdir\n"
" Do not implicitly search in current working directory for\n"
@@ -251,6 +165,13 @@
" trim - Remove all nodes with a default value.\n"
" implicit-tagged - Add missing nodes and mark them with the attribute.\n\n");
+ printf(" -E XPATH, --data-xpath=XPATH\n"
+ " Evaluate XPATH expression over the data and print the nodes satisfying\n"
+ " the expression. The output format is specific and the option cannot\n"
+ " be combined with the -f and -d options. Also all the data\n"
+ " inputs are merged into a single data tree where the expression\n"
+ " is evaluated, so the -m option is always set implicitly.\n\n");
+
printf(" -l, --list Print info about the loaded schemas.\n"
" (i - imported module, I - implemented module)\n"
" In case the '-f' option with data encoding is specified,\n"
@@ -267,8 +188,8 @@
" Provide optional data to extend validation of the '(nc-)rpc',\n"
" '(nc-)reply' or '(nc-)notif' TYPEs. The FILE is supposed to contain\n"
" the operational datastore referenced from the operation.\n"
- " In case of a nested operation, its parent existence is also\n"
- " checked in these operational data.\n\n");
+ " In case of a nested notification or action, its parent existence\n"
+ " is also checked in these operational data.\n\n");
printf(" -R FILE, --reply-rpc=FILE\n"
" Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n"
@@ -287,6 +208,9 @@
" create an exact YANG schema context. If specified, the '-F'\n"
" parameter (enabled features) is ignored.\n\n");
+ printf(" -X, --extended-leafref\n"
+ " Allow usage of deref() XPath function within leafref\n\n");
+
#ifndef NDEBUG
printf(" -G GROUPS, --debug=GROUPS\n"
" Enable printing of specific debugging message group\n"
@@ -321,11 +245,11 @@
}
}
-static struct schema_features *
+static struct yl_schema_features *
get_features_not_applied(const struct ly_set *fset)
{
for (uint32_t u = 0; u < fset->count; ++u) {
- struct schema_features *sf = fset->objs[u];
+ struct yl_schema_features *sf = fset->objs[u];
if (!sf->applied) {
return sf;
@@ -335,191 +259,167 @@
return NULL;
}
-static LY_ERR
-ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free)
-{
- struct ly_ctx *ctx;
- struct lyd_node *data = NULL;
-
- ctx = ext->module->ctx;
- if (user_data) {
- lyd_parse_data_path(ctx, user_data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data);
- }
-
- *ext_data = data;
- *ext_data_free = 1;
- return LY_SUCCESS;
-}
-
-static LY_ERR
-searchpath_strcat(char **searchpaths, const char *path)
-{
- uint64_t len;
- char *new;
-
- if (!(*searchpaths)) {
- *searchpaths = strdup(path);
- return LY_SUCCESS;
- }
-
- len = strlen(*searchpaths) + strlen(path) + strlen(PATH_SEPARATOR);
- new = realloc(*searchpaths, sizeof(char) * len + 1);
- if (!new) {
- return LY_EMEM;
- }
- strcat(new, PATH_SEPARATOR);
- strcat(new, path);
- *searchpaths = new;
-
- return LY_SUCCESS;
-}
-
+/**
+ * @brief Create the libyang context.
+ *
+ * @param[in] yang_lib_file Context can be defined in yang library file.
+ * @param[in] searchpaths Directories in which modules are searched.
+ * @param[in,out] schema_features Set of features.
+ * @param[in,out] ctx_options Options for libyang context.
+ * @param[out] ctx Context for libyang.
+ * @return 0 on success.
+ */
static int
-fill_context_inputs(int argc, char *argv[], struct context *c)
+create_ly_context(const char *yang_lib_file, const char *searchpaths, struct ly_set *schema_features,
+ uint16_t *ctx_options, struct ly_ctx **ctx)
{
- struct ly_in *in = NULL;
- struct schema_features *sf;
- struct lys_module *mod;
- const char *all_features[] = {"*", NULL};
- char *dir = NULL, *module = NULL;
-
- /* Create libyang context. */
- if (c->yang_lib_file) {
+ if (yang_lib_file) {
/* ignore features */
- ly_set_erase(&c->schema_features, free_features);
+ ly_set_erase(schema_features, yl_schema_features_free);
- if (ly_ctx_new_ylpath(c->searchpaths, c->yang_lib_file, LYD_UNKNOWN, c->ctx_options, &c->ctx)) {
+ if (ly_ctx_new_ylpath(searchpaths, yang_lib_file, LYD_UNKNOWN, *ctx_options, ctx)) {
YLMSG_E("Unable to modify libyang context with yang-library data.\n");
return -1;
}
} else {
/* set imp feature flag if all should be enabled */
- c->ctx_options |= !c->schema_features.count ? LY_CTX_ENABLE_IMP_FEATURES : 0;
+ (*ctx_options) |= !schema_features->count ? LY_CTX_ENABLE_IMP_FEATURES : 0;
- if (ly_ctx_new(c->searchpaths, c->ctx_options, &c->ctx)) {
+ if (ly_ctx_new(searchpaths, *ctx_options, ctx)) {
YLMSG_E("Unable to create libyang context\n");
return -1;
}
}
- /* set callback providing run-time extension instance data */
- if (c->schema_context_filename) {
- ly_ctx_set_ext_data_clb(c->ctx, ext_data_clb, c->schema_context_filename);
- }
+ return 0;
+}
- /* process the operational and/or reply RPC content if any */
- if (c->data_operational.path) {
- if (get_input(c->data_operational.path, NULL, &c->data_operational.format, &c->data_operational.in)) {
- return -1;
- }
- }
- if (c->reply_rpc.path) {
- if (get_input(c->reply_rpc.path, NULL, &c->reply_rpc.format, &c->reply_rpc.in)) {
- return -1;
- }
- }
-
- for (int i = 0; i < argc - optind; i++) {
- LYS_INFORMAT format_schema = LYS_IN_UNKNOWN;
- LYD_FORMAT format_data = LYD_UNKNOWN;
-
- if (get_input(argv[optind + i], &format_schema, &format_data, &in)) {
- goto error;
- }
-
- if (format_schema) {
- LY_ERR ret;
- uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */
- const char **features;
-
- /* parse the input */
- if (parse_schema_path(argv[optind + i], &dir, &module)) {
- goto error;
- }
-
- if (c->yang_lib_file) {
- /* just get the module, it should already be parsed */
- mod = ly_ctx_get_module_implemented(c->ctx, module);
- if (!mod) {
- YLMSG_E("Schema module \"%s\" not implemented in yang-library data.\n", module);
- goto error;
- }
- } else {
- /* add temporarily also the path of the module itself */
- if (ly_ctx_set_searchdir(c->ctx, dir) == LY_EEXIST) {
- path_unset = 0;
- }
-
- /* get features list for this module */
- if (!c->schema_features.count) {
- features = all_features;
- } else {
- get_features(&c->schema_features, module, &features);
- }
-
- /* parse module */
- ret = lys_parse(c->ctx, in, format_schema, features, &mod);
- ly_ctx_unset_searchdir_last(c->ctx, path_unset);
- if (ret) {
- YLMSG_E("Parsing schema module \"%s\" failed.\n", argv[optind + i]);
- goto error;
- }
- }
-
- /* temporary cleanup */
- free(dir);
- dir = NULL;
- free(module);
- module = NULL;
-
- if (c->schema_out_format || c->feature_param_format) {
- /* module will be printed */
- if (ly_set_add(&c->schema_modules, (void *)mod, 1, NULL)) {
- YLMSG_E("Storing parsed schema module (%s) for print failed.\n", argv[optind + i]);
- goto error;
- }
- }
- } else if (format_data) {
- if (!fill_cmdline_file(&c->data_inputs, in, argv[optind + i], format_data)) {
- goto error;
- }
- in = NULL;
- }
-
- ly_in_free(in, 1);
- in = NULL;
- }
+/**
+ * @brief Implement module if some feature has not been applied.
+ *
+ * @param[in] schema_features Set of features.
+ * @param[in,out] ctx Context for libyang.
+ * @return 0 on success.
+ */
+static int
+apply_features(struct ly_set *schema_features, struct ly_ctx *ctx)
+{
+ struct yl_schema_features *sf;
+ struct lys_module *mod;
/* check that all specified features were applied, apply now if possible */
- while ((sf = get_features_not_applied(&c->schema_features))) {
+ while ((sf = get_features_not_applied(schema_features))) {
/* try to find implemented or the latest revision of this module */
- mod = ly_ctx_get_module_implemented(c->ctx, sf->mod_name);
+ mod = ly_ctx_get_module_implemented(ctx, sf->mod_name);
if (!mod) {
- mod = ly_ctx_get_module_latest(c->ctx, sf->mod_name);
+ mod = ly_ctx_get_module_latest(ctx, sf->mod_name);
}
if (!mod) {
YLMSG_E("Specified features not applied, module \"%s\" not loaded.\n", sf->mod_name);
- goto error;
+ return 1;
}
/* we have the module, implement it if needed and enable the specific features */
if (lys_set_implemented(mod, (const char **)sf->features)) {
YLMSG_E("Implementing module \"%s\" failed.\n", mod->name);
- goto error;
+ return 1;
}
sf->applied = 1;
}
return 0;
-
-error:
- ly_in_free(in, 1);
- free(dir);
- free(module);
- return -1;
}
/**
+ * @brief Parse and compile modules, data are only stored for later processing.
+ *
+ * @param[in] argc Number of strings in @p argv.
+ * @param[in] argv Strings from command line.
+ * @param[in] optind Index to the first input file in @p argv.
+ * @param[in] data_in_format Specified input data format.
+ * @param[in,out] ctx Context for libyang.
+ * @param[in,out] yo Options for yanglint.
+ * @return 0 on success.
+ */
+static int
+fill_context_inputs(int argc, char *argv[], int optind, LYD_FORMAT data_in_format, struct ly_ctx *ctx,
+ struct yl_opt *yo)
+{
+ char *filepath = NULL;
+ LYS_INFORMAT format_schema;
+ LYD_FORMAT format_data;
+
+ for (int i = 0; i < argc - optind; i++) {
+ format_schema = LYS_IN_UNKNOWN;
+ format_data = data_in_format;
+
+ filepath = argv[optind + i];
+
+ if (!filepath) {
+ return -1;
+ }
+ if (get_format(filepath, &format_schema, &format_data)) {
+ return -1;
+ }
+
+ if (format_schema) {
+ if (cmd_add_exec(&ctx, yo, filepath)) {
+ return -1;
+ }
+ } else {
+ if (cmd_data_store(&ctx, yo, filepath)) {
+ return -1;
+ }
+ }
+ }
+
+ /* Check that all specified features were applied, apply now if possible. */
+ if (apply_features(&yo->schema_features, ctx)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifndef NDEBUG
+/**
+ * @brief Enable specific debugging messages.
+ *
+ * @param[in] groups String in the form "<group>[,group>]*".
+ * @param[in,out] yo Options for yanglint.
+ * return 0 on success.
+ */
+static int
+set_debug_groups(char *groups, struct yl_opt *yo)
+{
+ int rc;
+ char *str, *end;
+
+ /* Process all debug arguments except the last one. */
+ for (str = groups; (end = strchr(str, ',')); str = end + 1) {
+ /* Temporary modify input string. */
+ *end = '\0';
+ rc = cmd_debug_store(NULL, yo, str);
+ *end = ',';
+ if (rc) {
+ return -1;
+ }
+ }
+ /* Process single/last debug argument. */
+ if (cmd_debug_store(NULL, yo, str)) {
+ return -1;
+ }
+ /* All debug arguments are valid, so they can apply. */
+ if (cmd_debug_setlog(NULL, yo)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif
+
+/**
* @brief Process command line options and store the settings into the context.
*
* return -1 in case of error;
@@ -527,10 +427,8 @@
* return 1 in case of success, but expect to exit.
*/
static int
-fill_context(int argc, char *argv[], struct context *c)
+fill_context(int argc, char *argv[], struct yl_opt *yo, struct ly_ctx **ctx)
{
- int ret;
-
int opt, opt_index;
struct option options[] = {
{"help", no_argument, NULL, 'h'},
@@ -542,6 +440,7 @@
{"disable-searchdir", no_argument, NULL, 'D'},
{"features", required_argument, NULL, 'F'},
{"make-implemented", no_argument, NULL, 'i'},
+ {"in-format", required_argument, NULL, 'I'},
{"schema-node", required_argument, NULL, 'P'},
{"single-node", no_argument, NULL, 'q'},
{"submodule", required_argument, NULL, 's'},
@@ -550,6 +449,7 @@
{"present", no_argument, NULL, 'e'},
{"type", required_argument, NULL, 't'},
{"default", required_argument, NULL, 'd'},
+ {"data-xpath", required_argument, NULL, 'E'},
{"list", no_argument, NULL, 'l'},
{"tree-line-length", required_argument, NULL, 'L'},
{"output", required_argument, NULL, 'o'},
@@ -558,6 +458,7 @@
{"merge", no_argument, NULL, 'm'},
{"yang-library", no_argument, NULL, 'y'},
{"yang-library-file", required_argument, NULL, 'Y'},
+ {"extended-leafref", no_argument, NULL, 'X'},
#ifndef NDEBUG
{"debug", required_argument, NULL, 'G'},
#endif
@@ -565,16 +466,16 @@
};
uint8_t data_type_set = 0;
- c->ctx_options = YL_DEFAULT_CTX_OPTIONS;
- c->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS;
- c->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS;
- c->line_length = 0;
+ yo->ctx_options = YL_DEFAULT_CTX_OPTIONS;
+ yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS;
+ yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS;
+ yo->line_length = 0;
opterr = 0;
#ifndef NDEBUG
- while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:G:", options, &opt_index)) != -1)
+ while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:Xx:G:", options, &opt_index)) != -1)
#else
- while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:", options, &opt_index)) != -1)
+ while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:Xx:", options, &opt_index)) != -1)
#endif
{
switch (opt) {
@@ -610,105 +511,67 @@
} /* case 'Q' */
case 'f': /* --format */
- if (!strcasecmp(optarg, "yang")) {
- c->schema_out_format = LYS_OUT_YANG;
- c->data_out_format = 0;
- } else if (!strcasecmp(optarg, "yin")) {
- c->schema_out_format = LYS_OUT_YIN;
- c->data_out_format = 0;
- } else if (!strcasecmp(optarg, "info")) {
- c->schema_out_format = LYS_OUT_YANG_COMPILED;
- c->data_out_format = 0;
- } else if (!strcasecmp(optarg, "tree")) {
- c->schema_out_format = LYS_OUT_TREE;
- c->data_out_format = 0;
- } else if (!strcasecmp(optarg, "xml")) {
- c->schema_out_format = 0;
- c->data_out_format = LYD_XML;
- } else if (!strcasecmp(optarg, "json")) {
- c->schema_out_format = 0;
- c->data_out_format = LYD_JSON;
- } else if (!strcasecmp(optarg, "lyb")) {
- c->schema_out_format = 0;
- c->data_out_format = LYD_LYB;
- } else if (!strcasecmp(optarg, "feature-param")) {
- c->feature_param_format = 1;
- } else {
- YLMSG_E("Unknown output format %s\n", optarg);
+ if (yl_opt_update_out_format(optarg, yo)) {
help(1);
return -1;
}
break;
- case 'p': { /* --path */
- struct stat st;
-
- if (stat(optarg, &st) == -1) {
- YLMSG_E("Unable to use search path (%s) - %s.\n", optarg, strerror(errno));
+ case 'I': /* --in-format */
+ if (yo_opt_update_data_in_format(optarg, yo)) {
+ YLMSG_E("Unknown input format %s\n", optarg);
+ help(1);
return -1;
}
- if (!S_ISDIR(st.st_mode)) {
- YLMSG_E("Provided search path is not a directory.\n");
- return -1;
- }
+ break;
- if (searchpath_strcat(&c->searchpaths, optarg)) {
+ case 'p': /* --path */
+ if (searchpath_strcat(&yo->searchpaths, optarg)) {
YLMSG_E("Storing searchpath failed.\n");
return -1;
}
-
break;
- } /* case 'p' */
+ /* case 'p' */
- case 'D': /* --disable-search */
- if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) {
+ case 'D': /* --disable-searchdir */
+ if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) {
YLMSG_W("The -D option specified too many times.\n");
}
- if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) {
- c->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
- c->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS;
- } else {
- c->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD;
- }
+ yo_opt_update_disable_searchdir(yo);
break;
case 'F': /* --features */
- if (parse_features(optarg, &c->schema_features)) {
+ if (parse_features(optarg, &yo->schema_features)) {
return -1;
}
break;
case 'i': /* --make-implemented */
- if (c->ctx_options & LY_CTX_REF_IMPLEMENTED) {
- c->ctx_options &= ~LY_CTX_REF_IMPLEMENTED;
- c->ctx_options |= LY_CTX_ALL_IMPLEMENTED;
- } else {
- c->ctx_options |= LY_CTX_REF_IMPLEMENTED;
- }
+ yo_opt_update_make_implemented(yo);
break;
case 'P': /* --schema-node */
- c->schema_node_path = optarg;
+ yo->schema_node_path = optarg;
break;
case 'q': /* --single-node */
- c->schema_print_options |= LYS_PRINT_NO_SUBSTMT;
+ yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT;
break;
case 's': /* --submodule */
- c->submodule = optarg;
+ yo->submodule = optarg;
break;
case 'x': /* --ext-data */
- c->schema_context_filename = strdup(optarg);
+ yo->schema_context_filename = optarg;
break;
case 'n': /* --not-strict */
- c->data_parse_options &= ~LYD_PARSE_STRICT;
+ yo->data_parse_options &= ~LYD_PARSE_STRICT;
break;
case 'e': /* --present */
- c->data_validate_options |= LYD_VALIDATE_PRESENT;
+ yo->data_validate_options |= LYD_VALIDATE_PRESENT;
break;
case 't': /* --type */
@@ -717,30 +580,7 @@
return -1;
}
- if (!strcasecmp(optarg, "config")) {
- c->data_parse_options |= LYD_PARSE_NO_STATE;
- c->data_validate_options |= LYD_VALIDATE_NO_STATE;
- } else if (!strcasecmp(optarg, "get")) {
- c->data_parse_options |= LYD_PARSE_ONLY;
- } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config")) {
- c->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE;
- } else if (!strcasecmp(optarg, "edit")) {
- c->data_parse_options |= LYD_PARSE_ONLY;
- } else if (!strcasecmp(optarg, "rpc")) {
- c->data_type = LYD_TYPE_RPC_YANG;
- } else if (!strcasecmp(optarg, "nc-rpc")) {
- c->data_type = LYD_TYPE_RPC_NETCONF;
- } else if (!strcasecmp(optarg, "reply")) {
- c->data_type = LYD_TYPE_REPLY_YANG;
- } else if (!strcasecmp(optarg, "nc-reply")) {
- c->data_type = LYD_TYPE_REPLY_NETCONF;
- } else if (!strcasecmp(optarg, "notif")) {
- c->data_type = LYD_TYPE_NOTIF_YANG;
- } else if (!strcasecmp(optarg, "nc-notif")) {
- c->data_type = LYD_TYPE_NOTIF_NETCONF;
- } else if (!strcasecmp(optarg, "data")) {
- /* default option */
- } else {
+ if (yl_opt_update_data_type(optarg, yo)) {
YLMSG_E("Unknown data tree type %s\n", optarg);
help(1);
return -1;
@@ -750,35 +590,34 @@
break;
case 'd': /* --default */
- if (!strcasecmp(optarg, "all")) {
- c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL;
- } else if (!strcasecmp(optarg, "all-tagged")) {
- c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG;
- } else if (!strcasecmp(optarg, "trim")) {
- c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM;
- } else if (!strcasecmp(optarg, "implicit-tagged")) {
- c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG;
- } else {
+ if (yo_opt_update_data_default(optarg, yo)) {
YLMSG_E("Unknown default mode %s\n", optarg);
help(1);
return -1;
}
break;
+ case 'E': /* --data-xpath */
+ if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) {
+ YLMSG_E("Storing XPath \"%s\" failed.\n", optarg);
+ return -1;
+ }
+ break;
+
case 'l': /* --list */
- c->list = 1;
+ yo->list = 1;
break;
case 'L': /* --tree-line-length */
- c->line_length = atoi(optarg);
+ yo->line_length = atoi(optarg);
break;
case 'o': /* --output */
- if (c->out) {
+ if (yo->out) {
YLMSG_E("Only a single output can be specified.\n");
return -1;
} else {
- if (ly_out_new_filepath(optarg, &c->out)) {
+ if (ly_out_new_filepath(optarg, &yo->out)) {
YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno));
return -1;
}
@@ -786,62 +625,45 @@
break;
case 'O': /* --operational */
- if (c->data_operational.path) {
+ if (yo->data_operational.path) {
YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n");
return -1;
}
- c->data_operational.path = optarg;
+ yo->data_operational.path = optarg;
break;
case 'R': /* --reply-rpc */
- if (c->reply_rpc.path) {
+ if (yo->reply_rpc.path) {
YLMSG_E("The PRC of the reply (-R) cannot be set multiple times.\n");
return -1;
}
- c->reply_rpc.path = optarg;
+ yo->reply_rpc.path = optarg;
break;
case 'm': /* --merge */
- c->data_merge = 1;
+ yo->data_merge = 1;
break;
case 'y': /* --yang-library */
- c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+ yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
break;
case 'Y': /* --yang-library-file */
- c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
- c->yang_lib_file = optarg;
+ yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+ yo->yang_lib_file = optarg;
+ break;
+
+ case 'X': /* --extended-leafref */
+ yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED;
break;
#ifndef NDEBUG
- case 'G': { /* --debug */
- uint32_t dbg_groups = 0;
- const char *ptr = optarg;
-
- while (ptr[0]) {
- if (!strncasecmp(ptr, "dict", sizeof "dict" - 1)) {
- dbg_groups |= LY_LDGDICT;
- ptr += sizeof "dict" - 1;
- } else if (!strncasecmp(ptr, "xpath", sizeof "xpath" - 1)) {
- dbg_groups |= LY_LDGXPATH;
- ptr += sizeof "xpath" - 1;
- } else if (!strncasecmp(ptr, "dep-sets", sizeof "dep-sets" - 1)) {
- dbg_groups |= LY_LDGDEPSETS;
- ptr += sizeof "dep-sets" - 1;
- }
-
- if (ptr[0]) {
- if (ptr[0] != ',') {
- YLMSG_E("Unknown debug group string \"%s\"\n", optarg);
- return -1;
- }
- ++ptr;
- }
+ case 'G': /* --debug */
+ if (set_debug_groups(optarg, yo)) {
+ return -1;
}
- ly_log_dbg_groups(dbg_groups);
break;
- } /* case 'G' */
+ /* case 'G' */
#endif
default:
YLMSG_E("Invalid option or missing argument: -%c\n", optopt);
@@ -850,86 +672,48 @@
}
/* additional checks for the options combinations */
- if (!c->list && (optind >= argc)) {
+ if (!yo->list && (optind >= argc)) {
help(1);
YLMSG_E("Missing <schema> to process.\n");
return 1;
}
- if (c->data_merge) {
- if (c->data_type || (c->data_parse_options & LYD_PARSE_ONLY)) {
- /* switch off the option, incompatible input data type */
- c->data_merge = 0;
- } else {
- /* postpone validation after the merge of all the input data */
- c->data_parse_options |= LYD_PARSE_ONLY;
- }
+ if (cmd_data_dep(yo, 0)) {
+ return -1;
}
-
- if (c->data_operational.path && !c->data_type) {
- YLMSG_E("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types.\n");
- c->data_operational.path = NULL;
- }
-
- if (c->reply_rpc.path && (c->data_type != LYD_TYPE_REPLY_NETCONF)) {
- YLMSG_E("Source RPC is needed only for NETCONF Reply input data type.\n");
- c->data_operational.path = NULL;
- } else if (!c->reply_rpc.path && (c->data_type == LYD_TYPE_REPLY_NETCONF)) {
- YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type.\n");
+ if (cmd_print_dep(yo, 0)) {
return -1;
}
- if ((c->schema_out_format != LYS_OUT_TREE) && c->line_length) {
- YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n");
+ /* Create the libyang context. */
+ if (create_ly_context(yo->yang_lib_file, yo->searchpaths, &yo->schema_features, &yo->ctx_options, ctx)) {
+ return -1;
}
- /* default output stream */
- if (!c->out) {
- if (ly_out_new_file(stdout, &c->out)) {
- YLMSG_E("Unable to set stdout as output.\n");
- return -1;
- }
+ /* Set callback providing run-time extension instance data. */
+ if (yo->schema_context_filename) {
+ ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, yo->schema_context_filename);
}
- if (c->schema_out_format == LYS_OUT_TREE) {
- /* print tree from lysc_nodes */
- c->ctx_options |= LY_CTX_SET_PRIV_PARSED;
- }
-
- /* process input files provided as standalone command line arguments,
- * schema modules are parsed and inserted into the context,
- * data files are just checked and prepared into internal structures for further processing */
- ret = fill_context_inputs(argc, argv, c);
- if (ret) {
- return ret;
+ /* Schema modules and data files are just checked and prepared into internal structures for further processing. */
+ if (fill_context_inputs(argc, argv, optind, yo->data_in_format, *ctx, yo)) {
+ return -1;
}
/* the second batch of checks */
- if (c->schema_print_options && !c->schema_out_format) {
+ if (yo->schema_print_options && !yo->schema_out_format) {
YLMSG_W("Schema printer options specified, but the schema output format is missing.\n");
}
- if (c->schema_parse_options && !c->schema_modules.count) {
+ if (yo->schema_parse_options && !yo->schema_modules.count) {
YLMSG_W("Schema parser options specified, but no schema input file provided.\n");
}
- if (c->data_print_options && !c->data_out_format) {
+ if (yo->data_print_options && !yo->data_out_format) {
YLMSG_W("data printer options specified, but the data output format is missing.\n");
}
- if (((c->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || c->data_type) && !c->data_inputs.count) {
+ if (((yo->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || yo->data_type) && !yo->data_inputs.count) {
YLMSG_W("Data parser options specified, but no data input file provided.\n");
}
- if (c->schema_node_path) {
- c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 0);
- if (!c->schema_node) {
- c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 1);
-
- if (!c->schema_node) {
- YLMSG_E("Invalid schema path.\n");
- return -1;
- }
- }
- }
-
return 0;
}
@@ -937,7 +721,8 @@
main_ni(int argc, char *argv[])
{
int ret = EXIT_SUCCESS, r;
- struct context c = {0};
+ struct yl_opt yo = {0};
+ struct ly_ctx *ctx = NULL;
char *features_output = NULL;
struct ly_set set = {0};
uint32_t u;
@@ -945,7 +730,7 @@
/* set callback for printing libyang messages */
ly_set_log_clb(libyang_verbclb, 1);
- r = fill_context(argc, argv, &c);
+ r = fill_context(argc, argv, &yo, &ctx);
if (r < 0) {
ret = EXIT_FAILURE;
}
@@ -955,72 +740,46 @@
/* do the required job - parse, validate, print */
- if (c.list) {
+ if (yo.list) {
/* print the list of schemas */
- print_list(c.out, c.ctx, c.data_out_format);
- } else {
- if (c.feature_param_format) {
- for (u = 0; u < c.schema_modules.count; u++) {
- if (collect_features(c.schema_modules.objs[u], &set)) {
- YLMSG_E("Unable to read features from a module.\n");
- goto cleanup;
- }
- if (generate_features_output(c.schema_modules.objs[u], &set, &features_output)) {
- YLMSG_E("Unable to generate feature command output.\n");
- goto cleanup;
- }
- ly_set_erase(&set, NULL);
- }
- ly_print(c.out, "%s\n", features_output);
- } else if (c.schema_out_format) {
- if (c.schema_node) {
- ret = lys_print_node(c.out, c.schema_node, c.schema_out_format, 0, c.schema_print_options);
- if (ret) {
- YLMSG_E("Unable to print schema node %s.\n", c.schema_node_path);
- goto cleanup;
- }
- } else if (c.submodule) {
- const struct lysp_submodule *submod = ly_ctx_get_submodule_latest(c.ctx, c.submodule);
-
- if (!submod) {
- YLMSG_E("Unable to find submodule %s.\n", c.submodule);
- goto cleanup;
- }
-
- ret = lys_print_submodule(c.out, submod, c.schema_out_format, c.line_length, c.schema_print_options);
- if (ret) {
- YLMSG_E("Unable to print submodule %s.\n", submod->name);
- goto cleanup;
- }
- } else {
- for (u = 0; u < c.schema_modules.count; ++u) {
- ret = lys_print_module(c.out, (struct lys_module *)c.schema_modules.objs[u], c.schema_out_format,
- c.line_length, c.schema_print_options);
- /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */
- if ((c.schema_out_format == LYS_OUT_TREE) && (u + 1 < c.schema_modules.count)) {
- ly_print(c.out, "\n");
- }
- if (ret) {
- YLMSG_E("Unable to print module %s.\n", ((struct lys_module *)c.schema_modules.objs[u])->name);
- goto cleanup;
- }
- }
+ ret = cmd_list_exec(&ctx, &yo, NULL);
+ goto cleanup;
+ }
+ if (yo.feature_param_format) {
+ for (u = 0; u < yo.schema_modules.count; u++) {
+ if ((ret = cmd_feature_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) {
+ goto cleanup;
}
}
-
- /* do the data validation despite the schema was printed */
- if (c.data_inputs.size) {
- ret = process_data(c.ctx, c.data_type, c.data_merge, c.data_out_format, c.out, c.data_parse_options,
- c.data_validate_options, c.data_print_options, &c.data_operational, &c.reply_rpc, &c.data_inputs, NULL);
- if (ret) {
+ cmd_feature_fin(ctx, &yo);
+ } else if (yo.schema_out_format && yo.schema_node_path) {
+ if ((ret = cmd_print_exec(&ctx, &yo, NULL))) {
+ goto cleanup;
+ }
+ } else if (yo.schema_out_format && yo.submodule) {
+ if ((ret = cmd_print_exec(&ctx, &yo, yo.submodule))) {
+ goto cleanup;
+ }
+ } else if (yo.schema_out_format) {
+ for (u = 0; u < yo.schema_modules.count; ++u) {
+ yo.last_one = (u + 1) == yo.schema_modules.count;
+ if ((ret = cmd_print_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) {
goto cleanup;
}
}
}
+ /* do the data validation despite the schema was printed */
+ if (yo.data_inputs.size) {
+ if ((ret = cmd_data_process(ctx, &yo))) {
+ goto cleanup;
+ }
+ }
+
cleanup:
/* cleanup */
- erase_context(&c);
+ yl_opt_erase(&yo);
+ ly_ctx_destroy(ctx);
free(features_output);
ly_set_erase(&set, NULL);
diff --git a/tools/lint/tests/expect/common.exp b/tools/lint/tests/expect/common.exp
deleted file mode 100644
index 0381e6c..0000000
--- a/tools/lint/tests/expect/common.exp
+++ /dev/null
@@ -1,68 +0,0 @@
-# detect the path to the yanglint binary
-if { [info exists ::env(YANGLINT)] } {
- set yanglint "$env(YANGLINT)"
-} else {
- set yanglint "../../../../build/yanglint"
-}
-
-# set the timeout to 1 second
-set timeout 1
-
-# expect a single line of anchored regex output
-proc expect_output {output} {
- expect {
- -re "^${output}$" {}
- timeout {exit 1}
- }
-}
-
-# send a command and either expect some anchored regex output if specified or just an empty line
-proc expect_command {command has_output output} {
- send -- "${command}\r"
-
- if ($has_output==1) {
- expect {
- -re "^${command}\r\n${output}$" {}
- timeout {exit 1}
- }
- } else {
- # input echoes
- expect {
- -re "^${command}\r\n$" {}
- timeout {exit 1}
- }
- expect {
- -re "^> $" {}
- timeout {exit 1}
- }
- }
-}
-
-# send a completion request and check if the anchored regex output matches
-proc expect_completion {input output} {
- send -- "${input}\t"
-
- expect {
- # expecting echoing input, output and 10 terminal control characters
- -re "^${input}\r> ${output}.*\r.*$" {}
- timeout {exit 1}
- }
-}
-
-# send a completion request and check if the anchored regex hint options match
-proc expect_hint {input prev_input hints} {
- set output {}
- foreach i $hints {
- # each element might have some number of spaces and CRLF around it
- append output "${i} *(?:\\r\\n)?"
- }
-
- send -- "${input}\t"
-
- expect {
- # expecting the hints, previous input from which the hints were generated
- # and some number of terminal control characters
- -re "^\r\n${output}\r> ${prev_input}.*\r.*$" {}
- timeout {exit 1}
- }
-}
diff --git a/tools/lint/tests/expect/completion.exp b/tools/lint/tests/expect/completion.exp
deleted file mode 100755
index ed4f6bd..0000000
--- a/tools/lint/tests/expect/completion.exp
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/expect -f
-
-if { [info exists ::env(CURRENT_SOURCE_DIR)] } {
- source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp"
- set yang_dir "$env(CURRENT_SOURCE_DIR)/examples"
-} else {
- source "common.exp"
- set yang_dir "../../examples"
-}
-
-spawn $yanglint
-
-# skip no dir and/or no history warnings
-expect_output "(YANGLINT.*)*> "
-
-expect_command "clear -ii" 0 ""
-
-expect_command "add ${yang_dir}/ietf-ip.yang" 0 ""
-
-expect_completion "print -f info -P " "print -f info -P /ietf-"
-
-set hints {"/ietf-yang-schema-mount:schema-mounts" "/ietf-interfaces:interfaces" "/ietf-interfaces:interfaces-state"}
-
-expect_hint "" "print -f info -P /ietf-" $hints
-
-expect_completion "i" "print -f info -P /ietf-interfaces:interfaces"
-
-expect_completion "/" "print -f info -P /ietf-interfaces:interfaces/interface"
-
-set hints {"/ietf-interfaces:interfaces/interface"
-"/ietf-interfaces:interfaces/interface/name" "/ietf-interfaces:interfaces/interface/description"
-"/ietf-interfaces:interfaces/interface/type" "/ietf-interfaces:interfaces/interface/enabled"
-"/ietf-interfaces:interfaces/interface/link-up-down-trap-enable"
-"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6"}
-
-expect_hint "" "print -f info -P /ietf-interfaces:interfaces/interface" $hints
-
-expect_completion "/i" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv"
-
-expect_completion "4" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4"
-
-set hints { "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled"
-"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/forwarding" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/mtu"
-"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/neighbor"
-}
-
-expect_hint "\t" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" $hints
-
-expect_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled "
-
-send -- "\r"
-
-expect {
- -re ".*\r\n> " {}
- timeout {exit 1}
-}
-
-send -- "exit\r"
-
-expect eof
diff --git a/tools/lint/tests/expect/feature.exp b/tools/lint/tests/expect/feature.exp
deleted file mode 100755
index 37680b0..0000000
--- a/tools/lint/tests/expect/feature.exp
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/expect -f
-
-if { [info exists ::env(CURRENT_SOURCE_DIR)] } {
- source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp"
- set yang_dir "$env(CURRENT_SOURCE_DIR)/examples"
-} else {
- source "common.exp"
- set yang_dir "../../examples"
-}
-
-spawn $yanglint
-
-# skip no dir and/or no history warnings
-expect_output "(YANGLINT.*)*> "
-
-expect_command "feature -a" 1 "yang:\r\n\t\\(none\\)\r\n\r\nietf-yang-schema-mount:\r\n\t\\(none\\)\r\n\r\n> "
-
-send -- "exit\r"
-
-expect eof
diff --git a/tools/lint/tests/expect/list.exp b/tools/lint/tests/expect/list.exp
deleted file mode 100755
index ec3cdba..0000000
--- a/tools/lint/tests/expect/list.exp
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/expect -f
-
-if { [info exists ::env(CURRENT_SOURCE_DIR)] } {
- source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp"
- set yang_dir "$env(CURRENT_SOURCE_DIR)/examples"
-} else {
- source "common.exp"
- set yang_dir "../../examples"
-}
-
-spawn $yanglint
-
-# skip no dir and/or no history warnings
-expect_output "(YANGLINT.*)*> "
-
-expect_command "list" 1 "List of the loaded models:\r\n *i ietf-yang-metadata@2016-08-05\r\n *I yang@2022-06-16\r\n *i ietf-inet-types@2013-07-15\r\n *i ietf-yang-types@2013-07-15\r\n *I ietf-yang-schema-mount@2019-01-14\r\n *i ietf-yang-structure-ext@2020-06-17\r\n> "
-
-send -- "exit\r"
-
-expect eof
diff --git a/tools/lint/tests/shunit2/feature.sh b/tools/lint/tests/shunit2/feature.sh
deleted file mode 100755
index fb2ee88..0000000
--- a/tools/lint/tests/shunit2/feature.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env bash
-
-testFeature() {
- models=( "iana-if-type@2014-05-08.yang" "ietf-netconf@2011-06-01.yang" "ietf-netconf-with-defaults@2011-06-01.yang"
- "sm.yang" "ietf-interfaces@2014-05-08.yang" "ietf-netconf-acm@2018-02-14.yang" "ietf-origin@2018-02-14.yang"
- "ietf-ip@2014-06-16.yang" "ietf-restconf@2017-01-26.yang" )
- features=( " -F iana-if-type:"
- " -F ietf-netconf:writable-running,candidate,confirmed-commit,rollback-on-error,validate,startup,url,xpath"
- " -F ietf-netconf-with-defaults:" " -F sm:" " -F ietf-interfaces:arbitrary-names,pre-provisioning,if-mib"
- " -F ietf-netconf-acm:" " -F ietf-origin:" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf"
- " -F ietf-restconf:" )
-
- for i in ${!models[@]}; do
- output=`${YANGLINT} -f feature-param ${YANG_MODULES_DIR}/${models[$i]}`
- assertEquals "Unexpected features of module ${models[$i]}." "${features[$i]}" "${output}"
- done
-}
-
-. shunit2
diff --git a/tools/lint/tests/shunit2/list.sh b/tools/lint/tests/shunit2/list.sh
deleted file mode 100755
index d64503a..0000000
--- a/tools/lint/tests/shunit2/list.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env bash
-
-LIST_BASE="List of the loaded models:
- i ietf-yang-metadata@2016-08-05
- I yang@2022-06-16
- i ietf-inet-types@2013-07-15
- i ietf-yang-types@2013-07-15
- I ietf-yang-schema-mount@2019-01-14
- i ietf-yang-structure-ext@2020-06-17"
-
-testListEmptyContext() {
- output=`${YANGLINT} -l`
- assertEquals "Unexpected list of modules in empty context." "${LIST_BASE}" "${output}"
-}
-
-testListAllImplemented() {
- LIST_BASE_ALLIMPLEMENTED="List of the loaded models:
- I ietf-yang-metadata@2016-08-05
- I yang@2022-06-16
- I ietf-inet-types@2013-07-15
- I ietf-yang-types@2013-07-15
- I ietf-yang-schema-mount@2019-01-14
- I ietf-yang-structure-ext@2020-06-17"
- output=`${YANGLINT} -lii`
- assertEquals "Unexpected list of modules in empty context with -ii." "${LIST_BASE_ALLIMPLEMENTED}" "${output}"
-}
-
-. shunit2
diff --git a/tools/lint/yl_opt.c b/tools/lint/yl_opt.c
new file mode 100644
index 0000000..1a6add0
--- /dev/null
+++ b/tools/lint/yl_opt.c
@@ -0,0 +1,344 @@
+/**
+ * @file yl_opt.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief Settings options for the libyang context.
+ *
+ * Copyright (c) 2020 - 2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <strings.h>
+
+#include "in.h" /* ly_in_free */
+
+#include "common.h"
+#include "yl_opt.h"
+#include "yl_schema_features.h"
+
+struct cmdline_file *
+fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format)
+{
+ struct cmdline_file *rec;
+
+ rec = malloc(sizeof *rec);
+ if (!rec) {
+ YLMSG_E("Allocating memory for data file information failed.\n");
+ return NULL;
+ }
+ rec->in = in;
+ rec->path = path;
+ rec->format = format;
+
+ if (set && ly_set_add(set, rec, 1, NULL)) {
+ free(rec);
+ YLMSG_E("Storing data file information failed.\n");
+ return NULL;
+ }
+
+ return rec;
+}
+
+void
+free_cmdline_file_items(struct cmdline_file *rec)
+{
+ if (rec && rec->in) {
+ ly_in_free(rec->in, 1);
+ }
+}
+
+void
+free_cmdline_file(void *cmdline_file)
+{
+ struct cmdline_file *rec = (struct cmdline_file *)cmdline_file;
+
+ if (rec) {
+ free_cmdline_file_items(rec);
+ free(rec);
+ }
+}
+
+void
+yl_opt_erase(struct yl_opt *yo)
+{
+ ly_bool interactive;
+
+ interactive = yo->interactive;
+
+ /* data */
+ ly_set_erase(&yo->data_inputs, free_cmdline_file);
+ ly_in_free(yo->data_operational.in, 1);
+ ly_set_erase(&yo->data_xpath, NULL);
+
+ /* schema */
+ ly_set_erase(&yo->schema_features, yl_schema_features_free);
+ ly_set_erase(&yo->schema_modules, NULL);
+
+ /* context */
+ free(yo->searchpaths);
+
+ /* --reply-rpc */
+ ly_in_free(yo->reply_rpc.in, 1);
+
+ ly_out_free(yo->out, NULL, yo->out_stdout ? 0 : 1);
+
+ free_cmdline(yo->argv);
+
+ *yo = (const struct yl_opt) {
+ 0
+ };
+ yo->interactive = interactive;
+}
+
+int
+yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo)
+{
+ if (!strcasecmp(arg, "yang")) {
+ yo->schema_out_format = LYS_OUT_YANG;
+ yo->data_out_format = 0;
+ } else if (!strcasecmp(arg, "yin")) {
+ yo->schema_out_format = LYS_OUT_YIN;
+ yo->data_out_format = 0;
+ } else if (!strcasecmp(arg, "info")) {
+ yo->schema_out_format = LYS_OUT_YANG_COMPILED;
+ yo->data_out_format = 0;
+ } else if (!strcasecmp(arg, "tree")) {
+ yo->schema_out_format = LYS_OUT_TREE;
+ yo->data_out_format = 0;
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo)
+{
+ if (!strcasecmp(arg, "xml")) {
+ yo->schema_out_format = 0;
+ yo->data_out_format = LYD_XML;
+ } else if (!strcasecmp(arg, "json")) {
+ yo->schema_out_format = 0;
+ yo->data_out_format = LYD_JSON;
+ } else if (!strcasecmp(arg, "lyb")) {
+ yo->schema_out_format = 0;
+ yo->data_out_format = LYD_LYB;
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+yl_opt_update_other_out_format(const char *arg, struct yl_opt *yo)
+{
+ if (!strcasecmp(arg, "feature-param")) {
+ yo->feature_param_format = 1;
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+yl_opt_update_out_format(const char *arg, struct yl_opt *yo)
+{
+ if (!yl_opt_update_schema_out_format(arg, yo)) {
+ return 0;
+ }
+ if (!yl_opt_update_data_out_format(arg, yo)) {
+ return 0;
+ }
+ if (!yl_opt_update_other_out_format(arg, yo)) {
+ return 0;
+ }
+
+ YLMSG_E("Unknown output format %s\n", arg);
+ return 1;
+}
+
+int
+yl_opt_update_data_type(const char *arg, struct yl_opt *yo)
+{
+ if (!strcasecmp(arg, "config")) {
+ yo->data_parse_options |= LYD_PARSE_NO_STATE;
+ yo->data_validate_options |= LYD_VALIDATE_NO_STATE;
+ } else if (!strcasecmp(arg, "get")) {
+ yo->data_parse_options |= LYD_PARSE_ONLY;
+ } else if (!strcasecmp(arg, "getconfig") || !strcasecmp(arg, "get-config") || !strcasecmp(arg, "edit")) {
+ yo->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE;
+ } else if (!strcasecmp(arg, "rpc") || !strcasecmp(arg, "action")) {
+ yo->data_type = LYD_TYPE_RPC_YANG;
+ } else if (!strcasecmp(arg, "nc-rpc")) {
+ yo->data_type = LYD_TYPE_RPC_NETCONF;
+ } else if (!strcasecmp(arg, "reply") || !strcasecmp(arg, "rpcreply")) {
+ yo->data_type = LYD_TYPE_REPLY_YANG;
+ } else if (!strcasecmp(arg, "nc-reply")) {
+ yo->data_type = LYD_TYPE_REPLY_NETCONF;
+ } else if (!strcasecmp(arg, "notif") || !strcasecmp(arg, "notification")) {
+ yo->data_type = LYD_TYPE_NOTIF_YANG;
+ } else if (!strcasecmp(arg, "nc-notif")) {
+ yo->data_type = LYD_TYPE_NOTIF_NETCONF;
+ } else if (!strcasecmp(arg, "data")) {
+ /* default option */
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+yo_opt_update_data_default(const char *arg, struct yl_opt *yo)
+{
+ if (!strcasecmp(arg, "all")) {
+ yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL;
+ } else if (!strcasecmp(arg, "all-tagged")) {
+ yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG;
+ } else if (!strcasecmp(arg, "trim")) {
+ yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM;
+ } else if (!strcasecmp(arg, "implicit-tagged")) {
+ yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG;
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo)
+{
+ if (!strcasecmp(arg, "xml")) {
+ yo->data_in_format = LYD_XML;
+ } else if (!strcasecmp(arg, "json")) {
+ yo->data_in_format = LYD_JSON;
+ } else if (!strcasecmp(arg, "lyb")) {
+ yo->data_in_format = LYD_LYB;
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+yo_opt_update_make_implemented(struct yl_opt *yo)
+{
+ if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) {
+ yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED;
+ yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED;
+ } else {
+ yo->ctx_options |= LY_CTX_REF_IMPLEMENTED;
+ }
+}
+
+void
+yo_opt_update_disable_searchdir(struct yl_opt *yo)
+{
+ if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) {
+ yo->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
+ yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS;
+ } else {
+ yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD;
+ }
+}
+
+void
+free_cmdline(char *argv[])
+{
+ if (argv) {
+ free(argv[0]);
+ free(argv);
+ }
+}
+
+int
+parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[])
+{
+ int count;
+ char **vector;
+ char *ptr;
+ char qmark = 0;
+
+ assert(cmdline);
+ assert(argc_p);
+ assert(argv_p);
+
+ /* init */
+ optind = 0; /* reinitialize getopt() */
+ count = 1;
+ vector = malloc((count + 1) * sizeof *vector);
+ vector[0] = strdup(cmdline);
+
+ /* command name */
+ strtok(vector[0], " ");
+
+ /* arguments */
+ while ((ptr = strtok(NULL, " "))) {
+ size_t len;
+ void *r;
+
+ len = strlen(ptr);
+
+ if (qmark) {
+ /* still in quotated text */
+ /* remove NULL termination of the previous token since it is not a token,
+ * but a part of the quotation string */
+ ptr[-1] = ' ';
+
+ if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
+ /* end of quotation */
+ qmark = 0;
+ /* shorten the argument by the terminating quotation mark */
+ ptr[len - 1] = '\0';
+ }
+ continue;
+ }
+
+ /* another token in cmdline */
+ ++count;
+ r = realloc(vector, (count + 1) * sizeof *vector);
+ if (!r) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno));
+ free(vector);
+ return -1;
+ }
+ vector = r;
+ vector[count - 1] = ptr;
+
+ if ((ptr[0] == '"') || (ptr[0] == '\'')) {
+ /* remember the quotation mark to identify end of quotation */
+ qmark = ptr[0];
+
+ /* move the remembered argument after the quotation mark */
+ ++vector[count - 1];
+
+ /* check if the quotation is terminated within this token */
+ if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
+ /* end of quotation */
+ qmark = 0;
+ /* shorten the argument by the terminating quotation mark */
+ ptr[len - 1] = '\0';
+ }
+ }
+ }
+ vector[count] = NULL;
+
+ *argc_p = count;
+ *argv_p = vector;
+
+ return 0;
+}
diff --git a/tools/lint/yl_opt.h b/tools/lint/yl_opt.h
new file mode 100644
index 0000000..d66ae4d
--- /dev/null
+++ b/tools/lint/yl_opt.h
@@ -0,0 +1,237 @@
+/**
+ * @file yl_opt.h
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief Settings options for the libyang context.
+ *
+ * Copyright (c) 2020 - 2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef YL_OPT_H_
+#define YL_OPT_H_
+
+#include "parser_data.h" /* enum lyd_type */
+#include "printer_schema.h" /* LYS_OUTFORMAT */
+#include "set.h" /* ly_set */
+
+/**
+ * @brief Data connected with a file provided on a command line as a file path.
+ */
+struct cmdline_file {
+ struct ly_in *in;
+ const char *path;
+ LYD_FORMAT format;
+};
+
+/**
+ * @brief Create and fill the command line file data (struct cmdline_file *).
+ * @param[in] set Optional parameter in case the record is supposed to be added into a set.
+ * @param[in] in Input file handler.
+ * @param[in] path Filepath of the file.
+ * @param[in] format Format of the data file.
+ * @return The created command line file structure.
+ * @return NULL on failure
+ */
+struct cmdline_file *fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format);
+
+/**
+ * @brief Free the command line file data items.
+ * @param[in,out] rec record to free.
+ */
+void free_cmdline_file_items(struct cmdline_file *rec);
+
+/**
+ * @brief Free the command line file data (struct cmdline_file *).
+ * @param[in,out] cmdline_file The (struct cmdline_file *) to free.
+ */
+void free_cmdline_file(void *cmdline_file);
+
+/**
+ * @brief Context structure to hold and pass variables in a structured form.
+ */
+struct yl_opt {
+ /* Set to 1 if yanglint running in the interactive mode */
+ ly_bool interactive;
+ ly_bool last_one;
+
+ /* libyang context for the run */
+ char *yang_lib_file;
+ uint16_t ctx_options;
+
+ /* prepared output (--output option or stdout by default) */
+ ly_bool out_stdout;
+ struct ly_out *out;
+
+ char *searchpaths;
+ ly_bool searchdir_unset;
+
+ /* options flags */
+ uint8_t list; /* -l option to print list of schemas */
+
+ /* line length for 'tree' format */
+ size_t line_length; /* --tree-line-length */
+
+ uint32_t dbg_groups;
+
+ /*
+ * schema
+ */
+ /* set schema modules' features via --features option (struct schema_features *) */
+ struct ly_set schema_features;
+
+ /* set of loaded schema modules (struct lys_module *) */
+ struct ly_set schema_modules;
+
+ /* options to parse and print schema modules */
+ uint32_t schema_parse_options;
+ uint32_t schema_print_options;
+
+ /* specification of printing schema node subtree, option --schema-node */
+ char *schema_node_path;
+ char *submodule;
+
+ /* name of file containing explicit context passed to callback
+ * for schema-mount extension. This also causes a callback to
+ * be registered.
+ */
+ char *schema_context_filename;
+ ly_bool extdata_unset;
+
+ /* value of --format in case of schema format */
+ LYS_OUTFORMAT schema_out_format;
+ ly_bool feature_param_format;
+ ly_bool feature_print_all;
+
+ /*
+ * data
+ */
+ /* various options based on --type option */
+ enum lyd_type data_type;
+ uint32_t data_parse_options;
+ uint32_t data_validate_options;
+ uint32_t data_print_options;
+
+ /* flag for --merge option */
+ uint8_t data_merge;
+
+ /* value of --format in case of data format */
+ LYD_FORMAT data_out_format;
+
+ /* value of --in-format in case of data format */
+ LYD_FORMAT data_in_format;
+
+ /* input data files (struct cmdline_file *) */
+ struct ly_set data_inputs;
+
+ /* storage for --operational */
+ struct cmdline_file data_operational;
+
+ /* storage for --reply-rpc */
+ struct cmdline_file reply_rpc;
+
+ /* storage for --data-xpath */
+ struct ly_set data_xpath;
+
+ char **argv;
+};
+
+/**
+ * @brief Erase all values in @p opt.
+ *
+ * The yl_opt.interactive item is not deleted.
+ *
+ * @param[in,out] yo Option context to erase.
+ */
+void yl_opt_erase(struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the schema --format parameter.
+ *
+ * @param[in] arg Format parameter argument (for example yang, yin, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the data --format parameter.
+ *
+ * @param[in] arg Format parameter argument (for example xml, json, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the general --format parameter.
+ *
+ * @param[in] arg Format parameter argument (for example yang, xml, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yl_opt_update_out_format(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the data --type parameter.
+ *
+ * @param[in] arg Format parameter argument (for example config, rpc, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yl_opt_update_data_type(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the data --default parameter.
+ *
+ * @param[in] arg Format parameter argument (for example all, trim, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yo_opt_update_data_default(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the data --in-format parameter.
+ *
+ * @param[in] arg Format parameter argument (for example xml, json, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the --make-implemented parameter.
+ *
+ * @param[in,out] yo yanglint options used to update.
+ */
+void yo_opt_update_make_implemented(struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the --disable-searchdir parameter.
+ *
+ * @param[in,out] yo yanglint options used to update.
+ */
+void yo_opt_update_disable_searchdir(struct yl_opt *yo);
+
+/**
+ * @brief Helper function to prepare argc, argv pair from a command line string.
+ *
+ * @param[in] cmdline Complete command line string.
+ * @param[out] argc_p Pointer to store argc value.
+ * @param[out] argv_p Pointer to store argv vector.
+ * @return 0 on success, non-zero on failure.
+ */
+int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]);
+
+/**
+ * @brief Destructor for the argument vector prepared by ::parse_cmdline().
+ *
+ * @param[in,out] argv Argument vector to destroy.
+ */
+void free_cmdline(char *argv[]);
+
+#endif /* YL_OPT_H_ */
diff --git a/tools/lint/yl_schema_features.c b/tools/lint/yl_schema_features.c
new file mode 100644
index 0000000..ceaff36
--- /dev/null
+++ b/tools/lint/yl_schema_features.c
@@ -0,0 +1,212 @@
+/**
+ * @file yl_schema_features.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief Control features for the schema.
+ *
+ * Copyright (c) 2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stdlib.h> /* calloc */
+#include <string.h> /* strcmp */
+
+#include "compat.h" /* strndup */
+#include "set.h" /* ly_set */
+
+#include "common.h"
+#include "yl_schema_features.h"
+
+void
+yl_schema_features_free(void *flist)
+{
+ struct yl_schema_features *rec = (struct yl_schema_features *)flist;
+
+ if (rec) {
+ free(rec->mod_name);
+ if (rec->features) {
+ for (uint32_t u = 0; rec->features[u]; ++u) {
+ free(rec->features[u]);
+ }
+ free(rec->features);
+ }
+ free(rec);
+ }
+}
+
+void
+get_features(const struct ly_set *fset, const char *module, const char ***features)
+{
+ /* get features list for this module */
+ for (uint32_t u = 0; u < fset->count; ++u) {
+ struct yl_schema_features *sf = (struct yl_schema_features *)fset->objs[u];
+
+ if (!strcmp(module, sf->mod_name)) {
+ /* matched module - explicitly set features */
+ *features = (const char **)sf->features;
+ sf->applied = 1;
+ return;
+ }
+ }
+
+ /* features not set so disable all */
+ *features = NULL;
+}
+
+int
+parse_features(const char *fstring, struct ly_set *fset)
+{
+ struct yl_schema_features *rec = NULL;
+ uint32_t count;
+ char *p, **fp;
+
+ rec = calloc(1, sizeof *rec);
+ if (!rec) {
+ YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno));
+ goto error;
+ }
+
+ /* fill the record */
+ p = strchr(fstring, ':');
+ if (!p) {
+ YLMSG_E("Invalid format of the features specification (%s).\n", fstring);
+ goto error;
+ }
+ rec->mod_name = strndup(fstring, p - fstring);
+
+ count = 0;
+ while (p) {
+ size_t len = 0;
+ char *token = p + 1;
+
+ p = strchr(token, ',');
+ if (!p) {
+ /* the last item, if any */
+ len = strlen(token);
+ } else {
+ len = p - token;
+ }
+
+ if (len) {
+ fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
+ if (!fp) {
+ YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
+ goto error;
+ }
+ rec->features = fp;
+ fp = &rec->features[count++]; /* array item to set */
+ (*fp) = strndup(token, len);
+ }
+ }
+
+ /* terminating NULL */
+ fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
+ if (!fp) {
+ YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
+ goto error;
+ }
+ rec->features = fp;
+ rec->features[count++] = NULL;
+
+ /* Store record to the output set. */
+ if (ly_set_add(fset, rec, 1, NULL)) {
+ YLMSG_E("Unable to store features information (%s).\n", strerror(errno));
+ goto error;
+ }
+ rec = NULL;
+
+ return 0;
+
+error:
+ yl_schema_features_free(rec);
+ return -1;
+}
+
+void
+print_features(struct ly_out *out, const struct lys_module *mod)
+{
+ struct lysp_feature *f;
+ uint32_t idx;
+ size_t max_len, len;
+
+ ly_print(out, "%s:\n", mod->name);
+
+ /* get max len, so the statuses of all the features will be aligned */
+ max_len = 0, idx = 0, f = NULL;
+ while ((f = lysp_feature_next(f, mod->parsed, &idx))) {
+ len = strlen(f->name);
+ max_len = (max_len > len) ? max_len : len;
+ }
+ if (!max_len) {
+ ly_print(out, "\t(none)\n");
+ return;
+ }
+
+ /* print features */
+ idx = 0, f = NULL;
+ while ((f = lysp_feature_next(f, mod->parsed, &idx))) {
+ ly_print(out, "\t%-*s (%s)\n", (int)max_len, f->name, lys_feature_value(mod, f->name) ? "off" : "on");
+ }
+}
+
+void
+print_feature_param(struct ly_out *out, const struct lys_module *mod)
+{
+ struct lysp_feature *f = NULL;
+ uint32_t idx = 0;
+ uint8_t first = 1;
+
+ ly_print(out, " -F %s:", mod->name);
+ while ((f = lysp_feature_next(f, mod->parsed, &idx))) {
+ if (first) {
+ ly_print(out, "%s", f->name);
+ first = 0;
+ } else {
+ ly_print(out, ",%s", f->name);
+ }
+ }
+}
+
+void
+print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param)
+{
+ uint32_t i;
+ struct lys_module *mod;
+ uint8_t first;
+
+ /* Print features for all implemented modules. */
+ first = 1;
+ i = 0;
+ while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) {
+ if (!mod->implemented) {
+ continue;
+ }
+ if (first) {
+ print_features(out, mod);
+ first = 0;
+ } else {
+ ly_print(out, "\n");
+ print_features(out, mod);
+ }
+ }
+
+ if (!feature_param) {
+ return;
+ }
+ ly_print(out, "\n");
+
+ /* Print features for all implemented modules in 'feature-param' format. */
+ i = 0;
+ while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) {
+ if (mod->implemented) {
+ print_feature_param(out, mod);
+ }
+ }
+}
diff --git a/tools/lint/yl_schema_features.h b/tools/lint/yl_schema_features.h
new file mode 100644
index 0000000..7bfe9fd
--- /dev/null
+++ b/tools/lint/yl_schema_features.h
@@ -0,0 +1,84 @@
+/**
+ * @file yl_schema_features.h
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief Control features for the schema.
+ *
+ * Copyright (c) 2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef YL_SCHEMA_FEATURES_H_
+#define YL_SCHEMA_FEATURES_H_
+
+#include <stdint.h>
+
+struct ly_set;
+struct lys_module;
+struct ly_out;
+struct ly_ctx;
+
+/**
+ * @brief Storage for the list of the features (their names) in a specific YANG module.
+ */
+struct yl_schema_features {
+ char *mod_name;
+ char **features;
+ uint8_t applied;
+};
+
+/**
+ * @brief Free the schema features list (struct schema_features *)
+ * @param[in,out] flist The (struct schema_features *) to free.
+ */
+void yl_schema_features_free(void *flist);
+
+/**
+ * @brief Get the list of features connected with the specific YANG module.
+ *
+ * @param[in] fset The set of features information (struct schema_features *).
+ * @param[in] module Name of the YANG module which features should be found.
+ * @param[out] features Pointer to the list of features being returned.
+ */
+void get_features(const struct ly_set *fset, const char *module, const char ***features);
+
+/**
+ * @brief Parse features being specified for the specific YANG module.
+ *
+ * Format of the input @p fstring is as follows: "<module_name>:[<feature>,]*"
+ *
+ * @param[in] fstring Input string to be parsed.
+ * @param[in, out] fset Features information set (of struct schema_features *). The set is being filled.
+ */
+int parse_features(const char *fstring, struct ly_set *fset);
+
+/**
+ * @brief Print all features of a single module.
+ *
+ * @param[in] out The output handler for printing.
+ * @param[in] mod Module which can contains the features.
+ */
+void print_features(struct ly_out *out, const struct lys_module *mod);
+
+/**
+ * @brief Print all features in the 'feature-param' format.
+ *
+ * @param[in] out The output handler for printing.
+ * @param[in] mod Module which can contains the features.
+ */
+void print_feature_param(struct ly_out *out, const struct lys_module *mod);
+
+/**
+ * @brief Print all features of all implemented modules.
+ *
+ * @param[in] out The output handler for printing.
+ * @param[in] ctx Libyang context.
+ * @param[in] feature_param Flag expressing whether to print features parameter.
+ */
+void print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param);
+
+#endif /* YL_SCHEMA_FEATURES_H_ */
diff --git a/tools/re/main.c b/tools/re/main.c
index 2292b2a..5e33536 100644
--- a/tools/re/main.c
+++ b/tools/re/main.c
@@ -1,6 +1,7 @@
/**
* @file main.c
* @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
* @brief libyang's YANG Regular Expression tool
*
* Copyright (c) 2017 CESNET, z.s.p.o.
@@ -26,6 +27,11 @@
#include "compat.h"
#include "tools/config.h"
+struct yr_pattern {
+ char *expr;
+ ly_bool invert;
+};
+
void
help(void)
{
@@ -34,7 +40,9 @@
fprintf(stdout, " yangre [-hv]\n");
fprintf(stdout, " yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n");
fprintf(stdout, " yangre [-V] -f <file>\n");
- fprintf(stdout, "Returns 0 if string matches the pattern(s), 1 if not and -1 on error.\n\n");
+ fprintf(stdout, "Returns 0 if string matches the pattern(s) or if otherwise successful.\n");
+ fprintf(stdout, "Returns 1 on error.\n");
+ fprintf(stdout, "Returns 2 if string does not match the pattern(s).\n\n");
fprintf(stdout, "Options:\n"
" -h, --help Show this help message and exit.\n"
" -v, --version Show version number and exit.\n"
@@ -74,43 +82,248 @@
}
}
-static const char *module_start = "module yangre {"
- "yang-version 1.1;"
- "namespace urn:cesnet:libyang:yangre;"
- "prefix re;"
- "leaf pattern {"
- " type string {";
-static const char *module_invertmatch = " { modifier invert-match; }";
-static const char *module_match = ";";
-static const char *module_end = "}}}";
+static int
+add_pattern(struct yr_pattern **patterns, int *counter, char *pattern)
+{
+ void *reallocated;
+ int orig_counter;
+
+ /* Store the original number of items. */
+ orig_counter = *counter;
+
+ /* Reallocate 'patterns' memory with additional space. */
+ reallocated = realloc(*patterns, (orig_counter + 1) * sizeof **patterns);
+ if (!reallocated) {
+ goto error;
+ }
+ (*patterns) = reallocated;
+ /* Allocated memory is now larger. */
+ (*counter)++;
+ /* Copy the pattern and store it to the additonal space. */
+ (*patterns)[orig_counter].expr = strdup(pattern);
+ if (!(*patterns)[orig_counter].expr) {
+ goto error;
+ }
+ (*patterns)[orig_counter].invert = 0;
+
+ return 0;
+
+error:
+ fprintf(stderr, "yangre error: memory allocation error.\n");
+ return 1;
+}
static int
-add_pattern(char ***patterns, int **inverts, int *counter, char *pattern)
+create_empty_string(char **str)
{
- void *reallocated1, *reallocated2;
-
- (*counter)++;
- reallocated1 = realloc(*patterns, *counter * sizeof **patterns);
- reallocated2 = realloc(*inverts, *counter * sizeof **inverts);
- if (!reallocated1 || !reallocated2) {
- fprintf(stderr, "yangre error: memory allocation error.\n");
- free(reallocated1);
- free(reallocated2);
- return EXIT_FAILURE;
+ free(*str);
+ *str = malloc(sizeof(char));
+ if (!(*str)) {
+ fprintf(stderr, "yangre error: memory allocation failed.\n");
+ return 1;
}
- (*patterns) = reallocated1;
- (*patterns)[*counter - 1] = strdup(pattern);
- (*inverts) = reallocated2;
- (*inverts)[*counter - 1] = 0;
+ (*str)[0] = '\0';
- return EXIT_SUCCESS;
+ return 0;
+}
+
+static ly_bool
+file_is_empty(FILE *fp)
+{
+ int c;
+
+ c = fgetc(fp);
+ if (c == EOF) {
+ return 1;
+ } else {
+ ungetc(c, fp);
+ return 0;
+ }
+}
+
+/**
+ * @brief Open the @p filepath, parse patterns and given string-argument.
+ *
+ * @param[in] filepath File to parse. Contains patterns and string.
+ * @param[out] infile The file descriptor of @p filepath.
+ * @param[out] patterns Storage of patterns.
+ * @param[out] patterns_count Number of items in @p patterns.
+ * @param[out] strarg The string-argument to check.
+ * @return 0 on success.
+ */
+static int
+parse_patterns_file(const char *filepath, FILE **infile, struct yr_pattern **patterns, int *patterns_count, char **strarg)
+{
+ int blankline = 0;
+ char *str = NULL;
+ size_t len = 0;
+ ssize_t l;
+
+ *infile = fopen(filepath, "rb");
+ if (!(*infile)) {
+ fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
+ goto error;
+ }
+ if (file_is_empty(*infile)) {
+ if (create_empty_string(strarg)) {
+ goto error;
+ }
+ return 0;
+ }
+
+ while ((l = getline(&str, &len, *infile)) != -1) {
+ if (!blankline && ((str[0] == '\n') || ((str[0] == '\r') && (str[1] == '\n')))) {
+ /* blank line */
+ blankline = 1;
+ continue;
+ }
+ if ((str[0] != '\n') && (str[0] != '\r') && (str[l - 1] == '\n')) {
+ /* remove ending newline */
+ if ((l > 1) && (str[l - 2] == '\r') && (str[l - 1] == '\n')) {
+ str[l - 2] = '\0';
+ } else {
+ str[l - 1] = '\0';
+ }
+ }
+ if (blankline) {
+ /* done - str is now the string to check */
+ blankline = 0;
+ *strarg = str;
+ break;
+ /* else read the patterns */
+ } else if (add_pattern(patterns, patterns_count, (str[0] == ' ') ? &str[1] : str)) {
+ goto error;
+ }
+ if (str[0] == ' ') {
+ /* set invert-match */
+ (*patterns)[*patterns_count - 1].invert = 1;
+ }
+ }
+ if (!str || (blankline && (str[0] != '\0'))) {
+ /* corner case, no input after blankline meaning the pattern to check is empty */
+ if (create_empty_string(&str)) {
+ goto error;
+ }
+ }
+ *strarg = str;
+
+ return 0;
+
+error:
+ free(str);
+ if (*infile) {
+ fclose(*infile);
+ *infile = NULL;
+ }
+ *strarg = NULL;
+
+ return 1;
+}
+
+static char *
+modstr_init(void)
+{
+ const char *module_start = "module yangre {"
+ "yang-version 1.1;"
+ "namespace urn:cesnet:libyang:yangre;"
+ "prefix re;"
+ "leaf pattern {"
+ " type string {";
+
+ return strdup(module_start);
+}
+
+static char *
+modstr_add_pattern(char **modstr, const struct yr_pattern *pattern)
+{
+ char *new;
+ const char *module_invertmatch = " { modifier invert-match; }";
+ const char *module_match = ";";
+
+ if (asprintf(&new, "%s pattern %s%s", *modstr, pattern->expr,
+ pattern->invert ? module_invertmatch : module_match) == -1) {
+ fprintf(stderr, "yangre error: memory allocation failed.\n");
+ return NULL;
+ }
+ free(*modstr);
+ *modstr = NULL;
+
+ return new;
+}
+
+static char *
+modstr_add_ending(char **modstr)
+{
+ char *new;
+ static const char *module_end = "}}}";
+
+ if (asprintf(&new, "%s%s", *modstr, module_end) == -1) {
+ fprintf(stderr, "yangre error: memory allocation failed.\n");
+ return NULL;
+ }
+ free(*modstr);
+ *modstr = NULL;
+
+ return new;
+}
+
+static int
+create_module(struct yr_pattern *patterns, int patterns_count, char **mod)
+{
+ int i;
+ char *new = NULL, *modstr;
+
+ if (!(modstr = modstr_init())) {
+ goto error;
+ }
+
+ for (i = 0; i < patterns_count; i++) {
+ if (!(new = modstr_add_pattern(&modstr, &patterns[i]))) {
+ goto error;
+ }
+ modstr = new;
+ }
+
+ if (!(new = modstr_add_ending(&modstr))) {
+ goto error;
+ }
+
+ *mod = new;
+
+ return 0;
+
+error:
+ *mod = NULL;
+ free(new);
+ free(modstr);
+
+ return 1;
+}
+
+static void
+print_verbose(struct ly_ctx *ctx, struct yr_pattern *patterns, int patterns_count, char *str, LY_ERR match)
+{
+ int i;
+
+ for (i = 0; i < patterns_count; i++) {
+ fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i].expr);
+ fprintf(stdout, "matching %d: %s\n", i + 1, patterns[i].invert ? "inverted" : "regular");
+ }
+ fprintf(stdout, "string : %s\n", str);
+ if (match == LY_SUCCESS) {
+ fprintf(stdout, "result : matching\n");
+ } else if (match == LY_EVALID) {
+ fprintf(stdout, "result : not matching\n");
+ } else {
+ fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx));
+ }
}
int
main(int argc, char *argv[])
{
LY_ERR match;
- int i, opt_index = 0, ret = -1, verbose = 0, blankline = 0;
+ int i, opt_index = 0, ret = 1, verbose = 0;
struct option options[] = {
{"help", no_argument, NULL, 'h'},
{"file", required_argument, NULL, 'f'},
@@ -120,21 +333,20 @@
{"verbose", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}
};
- char **patterns = NULL, *str = NULL, *modstr = NULL, *s;
- int *invert_match = NULL;
+ struct yr_pattern *patterns = NULL;
+ char *str = NULL, *modstr = NULL;
int patterns_count = 0;
struct ly_ctx *ctx = NULL;
struct lys_module *mod;
FILE *infile = NULL;
- size_t len = 0;
- ssize_t l;
+ ly_bool info_printed = 0;
opterr = 0;
while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
switch (i) {
case 'h':
help();
- ret = -2; /* continue to allow printing version and help at once */
+ info_printed = 1;
break;
case 'f':
if (infile) {
@@ -146,52 +358,17 @@
fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
goto cleanup;
}
- infile = fopen(optarg, "rb");
- if (!infile) {
- fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
+ if (parse_patterns_file(optarg, &infile, &patterns, &patterns_count, &str)) {
goto cleanup;
}
-
- while ((l = getline(&str, &len, infile)) != -1) {
- if (!blankline && (str[0] == '\n')) {
- /* blank line */
- blankline = 1;
- continue;
- }
- if ((str[0] != '\n') && (str[l - 1] == '\n')) {
- /* remove ending newline */
- str[l - 1] = '\0';
- }
- if (blankline) {
- /* done - str is now the string to check */
- blankline = 0;
- break;
- /* else read the patterns */
- } else if (add_pattern(&patterns, &invert_match, &patterns_count,
- (str[0] == ' ') ? &str[1] : str)) {
- goto cleanup;
- }
- if (str[0] == ' ') {
- /* set invert-match */
- invert_match[patterns_count - 1] = 1;
- }
- }
- if (blankline) {
- /* corner case, no input after blankline meaning the pattern to check is empty */
- if (str != NULL) {
- free(str);
- }
- str = malloc(sizeof(char));
- str[0] = '\0';
- }
break;
case 'i':
- if (!patterns_count || invert_match[patterns_count - 1]) {
+ if (!patterns_count || patterns[patterns_count - 1].invert) {
help();
fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
goto cleanup;
}
- invert_match[patterns_count - 1] = 1;
+ patterns[patterns_count - 1].invert = 1;
break;
case 'p':
if (infile) {
@@ -199,13 +376,13 @@
fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
goto cleanup;
}
- if (add_pattern(&patterns, &invert_match, &patterns_count, optarg)) {
+ if (add_pattern(&patterns, &patterns_count, optarg)) {
goto cleanup;
}
break;
case 'v':
version();
- ret = -2; /* continue to allow printing version and help at once */
+ info_printed = 1;
break;
case 'V':
verbose = 1;
@@ -221,7 +398,8 @@
}
}
- if (ret == -2) {
+ if (info_printed) {
+ ret = 0;
goto cleanup;
}
@@ -239,24 +417,9 @@
str = argv[optind];
}
- for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) {
- if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) {
- fprintf(stderr, "yangre error: memory allocation failed.\n");
- goto cleanup;
- }
- if (modstr != module_start) {
- free(modstr);
- }
- modstr = s;
- }
- if (asprintf(&s, "%s%s", modstr, module_end) == -1) {
- fprintf(stderr, "yangre error: memory allocation failed.\n");
+ if (create_module(patterns, patterns_count, &modstr)) {
goto cleanup;
}
- if (modstr != module_start) {
- free(modstr);
- }
- modstr = s;
if (ly_ctx_new(NULL, 0, &ctx)) {
goto cleanup;
@@ -271,34 +434,24 @@
match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
if (verbose) {
- for (i = 0; i < patterns_count; i++) {
- fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i]);
- fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "inverted" : "regular");
- }
- fprintf(stdout, "string : %s\n", str);
- if (match == LY_SUCCESS) {
- fprintf(stdout, "result : matching\n");
- } else if (match == LY_EVALID) {
- fprintf(stdout, "result : not matching\n");
- } else {
- fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx));
- }
+ print_verbose(ctx, patterns, patterns_count, str, match);
}
if (match == LY_SUCCESS) {
ret = 0;
} else if (match == LY_EVALID) {
- ret = 1;
+ ret = 2;
} else {
- ret = -1;
+ ret = 1;
}
cleanup:
ly_ctx_destroy(ctx);
for (i = 0; i < patterns_count; i++) {
- free(patterns[i]);
+ free(patterns[i].expr);
}
- free(patterns);
- free(invert_match);
+ if (patterns_count) {
+ free(patterns);
+ }
free(modstr);
if (infile) {
fclose(infile);