From 4f9ed02a46d4422eab742793045d274b264c45b9 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 3 Jun 2022 14:03:01 +0200 Subject: [PATCH] Text syntax parser/loader, fixed double leaf-list issue Test: extended test_cli with format 4x tests --- include/clixon_custom.h | 15 ------ lib/src/clixon_text_syntax.c | 67 ++++++++++++++--------- test/test_cli.sh | 101 ++++++++++++++++++++++++++++++++--- 3 files changed, 135 insertions(+), 48 deletions(-) diff --git a/include/clixon_custom.h b/include/clixon_custom.h index f25674fb..064d8973 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -163,18 +163,3 @@ * If not set, client will exit */ #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 diff --git a/lib/src/clixon_text_syntax.c b/lib/src/clixon_text_syntax.c index 5bb07a74..bf19336b 100644 --- a/lib/src/clixon_text_syntax.c +++ b/lib/src/clixon_text_syntax.c @@ -101,7 +101,8 @@ tleaf(cxobj *x) * @param[in] fn Callback to make print function * @param[in] f File to print to * @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: * 0: No leaflist * 1: In leaflist @@ -111,7 +112,8 @@ xml2txt1(cxobj *xn, clicon_output_cb *fn, FILE *f, int level, - int *leaflist) + int *leafl, + char **leaflname) { cxobj *xc = NULL; int children=0; @@ -123,10 +125,8 @@ xml2txt1(cxobj *xn, yang_stmt *ypmod; char *prefix = NULL; char *value; -#ifdef TEXT_LIST_KEYS cg_var *cvi; cvec *cvk = NULL; /* vector of index keys */ -#endif if (xn == NULL || fn == NULL){ clicon_err(OE_XML, EINVAL, "xn or fn is NULL"); @@ -149,14 +149,20 @@ xml2txt1(cxobj *xn, } else prefix = yang_argument_get(ymod); -#ifdef TEXT_LIST_KEYS if (yang_keyword_get(yn) == Y_LIST){ if ((cvk = yang_cvec_get(yn)) == NULL){ clicon_err(OE_YANG, 0, "No keys"); 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) */ while ((xc = xml_child_each(xn, xc, -1)) != NULL) @@ -166,23 +172,20 @@ xml2txt1(cxobj *xn, switch (xml_type(xn)){ case CX_BODY: value = xml_value(xn); - /* Add quotes if string contains spaces */ - if (*leaflist) + if (*leafl) /* Skip keyword if leaflist */ (*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)); else (*fn)(f, "%s;\n", xml_value(xn)); break; case CX_ELMNT: (*fn)(f, "%*s%s", 4*level, "", xml_name(xn)); -#ifdef TEXT_LIST_KEYS cvi = NULL; /* Lists only */ while ((cvi = cvec_each(cvk, cvi)) != NULL) { if ((xc = xml_find_type(xn, NULL, cv_string_get(cvi), CX_ELMNT)) != NULL) (*fn)(f, " %s", xml_body(xc)); } -#endif (*fn)(f, ";\n"); break; default: @@ -190,23 +193,23 @@ xml2txt1(cxobj *xn, } goto ok; } - if (*leaflist == 0){ + if (*leafl == 0){ (*fn)(f, "%*s", 4*level, ""); if (prefix) (*fn)(f, "%s:", prefix); (*fn)(f, "%s", xml_name(xn)); } -#ifdef TEXT_LIST_KEYS cvi = NULL; /* Lists only */ while ((cvi = cvec_each(cvk, cvi)) != NULL) { if ((xc = xml_find_type(xn, NULL, cv_string_get(cvi), CX_ELMNT)) != NULL) (*fn)(f, " %s", xml_body(xc)); } -#endif - if (yn && yang_keyword_get(yn) == Y_LEAF_LIST && *leaflist) + if (yn && yang_keyword_get(yn) == Y_LEAF_LIST && *leafl){ ; - 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"); } else if (!tleaf(xn)) @@ -216,18 +219,30 @@ xml2txt1(cxobj *xn, xc = NULL; while ((xc = xml_child_each(xn, xc, -1)) != NULL){ if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY){ -#ifdef TEXT_LIST_KEYS if (yang_key_match(yn, xml_name(xc), NULL)) continue; /* Skip keys, already printed */ -#endif - if (xml2txt1(xc, fn, f, level+1, leaflist) < 0) + if (xml2txt1(xc, fn, f, level+1, leafl, leaflname) < 0) break; } } - if (yn && yang_keyword_get(yn) != Y_LEAF_LIST && *leaflist != 0){ - *leaflist = 0; + /* Stop leaf-list printing (ie []) if no longer leaflist and same name */ +#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), "]"); } +#endif if (!tleaf(xn)) (*fn)(f, "%*s}\n", 4*level, ""); ok: @@ -255,18 +270,19 @@ clixon_txt2file(FILE *f, { int retval = 1; cxobj *xc; - int leaflist = 0; + int leafl = 0; + char *leaflname = NULL; if (fn == NULL) fn = fprintf; if (skiptop){ xc = 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; } else { - if (xml2txt1(xn, fn, f, level, &leaflist) < 0) + if (xml2txt1(xn, fn, f, level, &leafl, &leaflname) < 0) goto done; } 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 * function after YANG binding to populate key tags properly. * @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 */ static int diff --git a/test/test_cli.sh b/test/test_cli.sh index 90df5332..22b363b0 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -17,6 +17,8 @@ APPNAME=example cfg=$dir/conf_yang.xml clidir=$dir/cli +fyang=$dir/clixon-example.yang + test -d ${clidir} || rm -rf ${clidir} mkdir $clidir @@ -27,7 +29,7 @@ cat < $cfg $cfg ${YANG_INSTALLDIR} $IETFRFC - clixon-example + $fyang /usr/local/lib/$APPNAME/backend $APPNAME /usr/local/lib/$APPNAME/cli @@ -38,6 +40,76 @@ cat < $cfg EOF +cat < $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 < $clidir/ex.cli # Clixon example specification 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);{ 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 "); + text("Show configuration as TEXT"), cli_auto_show("datamodel", "candidate", "text", true, false, "set "); } } save("Save candidate configuration to XML file") ("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" 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" 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" - 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" 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" - expectpart "$($clixon_cli -1 -f $cfg -l o show conf cli)" 0 "interfaces interface eth/0/0 ipv4 enabled true" + new "cli load $format" + 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 new "cli debug set"