Text syntax parser/loader, fixed double leaf-list issue
Test: extended test_cli with format 4x tests
This commit is contained in:
parent
625a0ed19a
commit
4f9ed02a46
3 changed files with 135 additions and 48 deletions
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
101
test/test_cli.sh
101
test/test_cli.sh
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue