Text syntax parser/loader, fixed double leaf-list issue

Test: extended test_cli with format 4x tests
This commit is contained in:
Olof hagsand 2022-06-03 14:03:01 +02:00
parent 625a0ed19a
commit 4f9ed02a46
3 changed files with 135 additions and 48 deletions

View file

@ -163,18 +163,3 @@
* If not set, client will exit * If not set, client will exit
*/ */
#define PROTO_RESTART_RECONNECT #define PROTO_RESTART_RECONNECT
/*! Text output keys as identifiers instead of ordinary leafs
* That is, given list "list" with key value "a", if set, the output of show config or save
* as text command is:
* list a {
* val 42;
* }
* If not set, the output is:
* list {
* keyname a;
* val 42;
* }
* The TEXT parser (ie load) accepts both formats.
*/
#define TEXT_LIST_KEYS

View file

@ -101,7 +101,8 @@ tleaf(cxobj *x)
* @param[in] fn Callback to make print function * @param[in] fn Callback to make print function
* @param[in] f File to print to * @param[in] f File to print to
* @param[in] level Print 4 spaces per level in front of each line * @param[in] level Print 4 spaces per level in front of each line
* @param[in,out] leaflist Leaflist state for [] * @param[in,out] leafl Leaflist state for keeping track of when [] ends
* @param[in,out] leaflname Leaflist state for []
* leaflist state: * leaflist state:
* 0: No leaflist * 0: No leaflist
* 1: In leaflist * 1: In leaflist
@ -111,7 +112,8 @@ xml2txt1(cxobj *xn,
clicon_output_cb *fn, clicon_output_cb *fn,
FILE *f, FILE *f,
int level, int level,
int *leaflist) int *leafl,
char **leaflname)
{ {
cxobj *xc = NULL; cxobj *xc = NULL;
int children=0; int children=0;
@ -123,10 +125,8 @@ xml2txt1(cxobj *xn,
yang_stmt *ypmod; yang_stmt *ypmod;
char *prefix = NULL; char *prefix = NULL;
char *value; char *value;
#ifdef TEXT_LIST_KEYS
cg_var *cvi; cg_var *cvi;
cvec *cvk = NULL; /* vector of index keys */ cvec *cvk = NULL; /* vector of index keys */
#endif
if (xn == NULL || fn == NULL){ if (xn == NULL || fn == NULL){
clicon_err(OE_XML, EINVAL, "xn or fn is NULL"); clicon_err(OE_XML, EINVAL, "xn or fn is NULL");
@ -149,14 +149,20 @@ xml2txt1(cxobj *xn,
} }
else else
prefix = yang_argument_get(ymod); prefix = yang_argument_get(ymod);
#ifdef TEXT_LIST_KEYS
if (yang_keyword_get(yn) == Y_LIST){ if (yang_keyword_get(yn) == Y_LIST){
if ((cvk = yang_cvec_get(yn)) == NULL){ if ((cvk = yang_cvec_get(yn)) == NULL){
clicon_err(OE_YANG, 0, "No keys"); clicon_err(OE_YANG, 0, "No keys");
goto done; goto done;
} }
} }
#endif }
if (yn && yang_keyword_get(yn) == Y_LEAF_LIST && *leafl){
if (strcmp(*leaflname, yang_argument_get(yn)) != 0){
*leafl = 0;
*leaflname = NULL;
(*fn)(f, "%*s\n", 4*(level), "]");
// XXX
}
} }
xc = NULL; /* count children (elements and bodies, not attributes) */ xc = NULL; /* count children (elements and bodies, not attributes) */
while ((xc = xml_child_each(xn, xc, -1)) != NULL) while ((xc = xml_child_each(xn, xc, -1)) != NULL)
@ -166,23 +172,20 @@ xml2txt1(cxobj *xn,
switch (xml_type(xn)){ switch (xml_type(xn)){
case CX_BODY: case CX_BODY:
value = xml_value(xn); value = xml_value(xn);
/* Add quotes if string contains spaces */ if (*leafl) /* Skip keyword if leaflist */
if (*leaflist)
(*fn)(f, "%*s%s\n", 4*level, "", xml_value(xn)); (*fn)(f, "%*s%s\n", 4*level, "", xml_value(xn));
else if (index(value, ' ') != NULL) else if (index(value, ' ') != NULL) /* Add quotes if string contains spaces */
(*fn)(f, "\"%s\";\n", xml_value(xn)); (*fn)(f, "\"%s\";\n", xml_value(xn));
else else
(*fn)(f, "%s;\n", xml_value(xn)); (*fn)(f, "%s;\n", xml_value(xn));
break; break;
case CX_ELMNT: case CX_ELMNT:
(*fn)(f, "%*s%s", 4*level, "", xml_name(xn)); (*fn)(f, "%*s%s", 4*level, "", xml_name(xn));
#ifdef TEXT_LIST_KEYS
cvi = NULL; /* Lists only */ cvi = NULL; /* Lists only */
while ((cvi = cvec_each(cvk, cvi)) != NULL) { while ((cvi = cvec_each(cvk, cvi)) != NULL) {
if ((xc = xml_find_type(xn, NULL, cv_string_get(cvi), CX_ELMNT)) != NULL) if ((xc = xml_find_type(xn, NULL, cv_string_get(cvi), CX_ELMNT)) != NULL)
(*fn)(f, " %s", xml_body(xc)); (*fn)(f, " %s", xml_body(xc));
} }
#endif
(*fn)(f, ";\n"); (*fn)(f, ";\n");
break; break;
default: default:
@ -190,23 +193,23 @@ xml2txt1(cxobj *xn,
} }
goto ok; goto ok;
} }
if (*leaflist == 0){ if (*leafl == 0){
(*fn)(f, "%*s", 4*level, ""); (*fn)(f, "%*s", 4*level, "");
if (prefix) if (prefix)
(*fn)(f, "%s:", prefix); (*fn)(f, "%s:", prefix);
(*fn)(f, "%s", xml_name(xn)); (*fn)(f, "%s", xml_name(xn));
} }
#ifdef TEXT_LIST_KEYS
cvi = NULL; /* Lists only */ cvi = NULL; /* Lists only */
while ((cvi = cvec_each(cvk, cvi)) != NULL) { while ((cvi = cvec_each(cvk, cvi)) != NULL) {
if ((xc = xml_find_type(xn, NULL, cv_string_get(cvi), CX_ELMNT)) != NULL) if ((xc = xml_find_type(xn, NULL, cv_string_get(cvi), CX_ELMNT)) != NULL)
(*fn)(f, " %s", xml_body(xc)); (*fn)(f, " %s", xml_body(xc));
} }
#endif if (yn && yang_keyword_get(yn) == Y_LEAF_LIST && *leafl){
if (yn && yang_keyword_get(yn) == Y_LEAF_LIST && *leaflist)
; ;
else if (yn && yang_keyword_get(yn) == Y_LEAF_LIST && *leaflist == 0){ }
*leaflist = 1; else if (yn && yang_keyword_get(yn) == Y_LEAF_LIST && *leafl == 0){
*leafl = 1;
*leaflname = yang_argument_get(yn);
(*fn)(f, " [\n"); (*fn)(f, " [\n");
} }
else if (!tleaf(xn)) else if (!tleaf(xn))
@ -216,18 +219,30 @@ xml2txt1(cxobj *xn,
xc = NULL; xc = NULL;
while ((xc = xml_child_each(xn, xc, -1)) != NULL){ while ((xc = xml_child_each(xn, xc, -1)) != NULL){
if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY){ if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY){
#ifdef TEXT_LIST_KEYS
if (yang_key_match(yn, xml_name(xc), NULL)) if (yang_key_match(yn, xml_name(xc), NULL))
continue; /* Skip keys, already printed */ continue; /* Skip keys, already printed */
#endif if (xml2txt1(xc, fn, f, level+1, leafl, leaflname) < 0)
if (xml2txt1(xc, fn, f, level+1, leaflist) < 0)
break; break;
} }
} }
if (yn && yang_keyword_get(yn) != Y_LEAF_LIST && *leaflist != 0){ /* Stop leaf-list printing (ie []) if no longer leaflist and same name */
*leaflist = 0; #if 0
if (*leafl != 0){
if (yn && yang_keyword_get(yn) == Y_LEAF_LIST &&
strcmp(*leaflname, yang_argument_get(yn))==0)
;
else{
*leafl = 0;
*leaflname = NULL;
(*fn)(f, "%*s\n", 4*(level+1), "]");
}
}
#else
if (yn && yang_keyword_get(yn) != Y_LEAF_LIST && *leafl != 0){
*leafl = 0;
(*fn)(f, "%*s\n", 4*(level+1), "]"); (*fn)(f, "%*s\n", 4*(level+1), "]");
} }
#endif
if (!tleaf(xn)) if (!tleaf(xn))
(*fn)(f, "%*s}\n", 4*level, ""); (*fn)(f, "%*s}\n", 4*level, "");
ok: ok:
@ -255,18 +270,19 @@ clixon_txt2file(FILE *f,
{ {
int retval = 1; int retval = 1;
cxobj *xc; cxobj *xc;
int leaflist = 0; int leafl = 0;
char *leaflname = NULL;
if (fn == NULL) if (fn == NULL)
fn = fprintf; fn = fprintf;
if (skiptop){ if (skiptop){
xc = NULL; xc = NULL;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
if (xml2txt1(xc, fn, f, level, &leaflist) < 0) if (xml2txt1(xc, fn, f, level, &leafl, &leaflname) < 0)
goto done; goto done;
} }
else { else {
if (xml2txt1(xn, fn, f, level, &leaflist) < 0) if (xml2txt1(xn, fn, f, level, &leafl, &leaflname) < 0)
goto done; goto done;
} }
retval = 0; retval = 0;
@ -283,7 +299,6 @@ clixon_txt2file(FILE *f,
* The compromise between (1) and (2) is to first parse without YANG (2) and then call a special * The compromise between (1) and (2) is to first parse without YANG (2) and then call a special
* function after YANG binding to populate key tags properly. * function after YANG binding to populate key tags properly.
* @param[in] x XML node * @param[in] x XML node
* @see TEXT_LIST_KEYS which controls output/save, whereas this is parsing/input
* @see text_mark_bodies where marking of bodies made transformed here * @see text_mark_bodies where marking of bodies made transformed here
*/ */
static int static int

View file

@ -17,6 +17,8 @@ APPNAME=example
cfg=$dir/conf_yang.xml cfg=$dir/conf_yang.xml
clidir=$dir/cli clidir=$dir/cli
fyang=$dir/clixon-example.yang
test -d ${clidir} || rm -rf ${clidir} test -d ${clidir} || rm -rf ${clidir}
mkdir $clidir mkdir $clidir
@ -27,7 +29,7 @@ cat <<EOF > $cfg
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE> <CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>${YANG_INSTALLDIR}</CLICON_YANG_DIR> <CLICON_YANG_DIR>${YANG_INSTALLDIR}</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR> <CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>clixon-example</CLICON_YANG_MODULE_MAIN> <CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR> <CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE> <CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR> <CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
@ -38,6 +40,76 @@ cat <<EOF > $cfg
</clixon-config> </clixon-config>
EOF EOF
cat <<EOF > $fyang
module clixon-example {
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
import ietf-interfaces {
prefix if;
}
import ietf-ip {
prefix ip;
}
import iana-if-type {
prefix ianaift;
}
import clixon-autocli{
prefix autocli;
}
/* Example interface type for tests, local callbacks, etc */
identity eth {
base if:interface-type;
}
/* Generic config data */
container table{
list parameter{
key name;
leaf name{
type string;
}
leaf value{
type string;
}
leaf-list array1{
type string;
}
leaf-list array2{
type string;
}
}
}
rpc example {
description "Some example input/output for testing RFC7950 7.14.
RPC simply echoes the input for debugging.";
input {
leaf x {
description
"If a leaf in the input tree has a 'mandatory' statement with
the value 'true', the leaf MUST be present in an RPC invocation.";
type string;
mandatory true;
}
leaf y {
description
"If a leaf in the input tree has a 'mandatory' statement with the
value 'true', the leaf MUST be present in an RPC invocation.";
type string;
default "42";
}
}
output {
leaf x {
type string;
}
leaf y {
type string;
}
}
}
}
EOF
cat <<EOF > $clidir/ex.cli cat <<EOF > $clidir/ex.cli
# Clixon example specification # Clixon example specification
CLICON_MODE="example"; CLICON_MODE="example";
@ -73,6 +145,7 @@ show("Show a particular state of the system"){
configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{ configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{
cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", true, false, "set "); cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", true, false, "set ");
xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", true, false, "set "); xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", true, false, "set ");
text("Show configuration as TEXT"), cli_auto_show("datamodel", "candidate", "text", true, false, "set ");
} }
} }
save("Save candidate configuration to XML file") <filename:string>("Filename (local filename)"), save_config_file("candidate","filename", "xml"){ save("Save candidate configuration to XML file") <filename:string>("Filename (local filename)"), save_config_file("candidate","filename", "xml"){
@ -170,21 +243,35 @@ expectpart "$($clixon_cli -1 -f $cfg -l o show compare text)" 0 "+ ad
new "cli start shell" new "cli start shell"
expectpart "$($clixon_cli -1 -f $cfg -l o shell echo foo)" 0 "foo" expectpart "$($clixon_cli -1 -f $cfg -l o shell echo foo)" 0 "foo"
# For formats, create three leaf-lists
new "cli create leaflist array1 a"
expectpart "$($clixon_cli -1 -f $cfg -l o set table parameter a array1 a)" 0 "^$"
new "cli create leaflist array1 b"
expectpart "$($clixon_cli -1 -f $cfg -l o set table parameter a array1 b)" 0 "^$"
new "cli create leaflist array2 c"
expectpart "$($clixon_cli -1 -f $cfg -l o set table parameter a array2 c)" 0 "^$"
new "cli commit" new "cli commit"
expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$"
for format in cli json text xml; do for format in cli text xml json; do
new "cli save $format" new "cli save $format"
expectpart "$($clixon_cli -1 -f $cfg -l o save $dir/foo $format)" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o save $dir/config.$format $format)" 0 "^$"
new "cli delete all" new "cli delete all"
expectpart "$($clixon_cli -1 -f $cfg -l o delete all)" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o delete all)" 0 "^$"
new "cli load $format"
expectpart "$($clixon_cli -1 -f $cfg -l o load $dir/foo $format)" 0 "^$"
new "cli check load" new "cli load $format"
expectpart "$($clixon_cli -1 -f $cfg -l o show conf cli)" 0 "interfaces interface eth/0/0 ipv4 enabled true" expectpart "$($clixon_cli -1 -f $cfg -l o load $dir/config.$format $format)" 0 "^$"
if [ $format != json ]; then # XXX JSON identity problem
new "cli check compare $format"
expectpart "$($clixon_cli -1 -f $cfg -l o show compare xml)" 0 "^$" --not-- "i" # interface?
fi
done done
new "cli debug set" new "cli debug set"