CLI show compare example function

Improved diff algorithm for XML and TEXT/curly, replaced UNIX diff with structural in-mem algorithm
Fixed: ["show compare" and "show compare | display cli" differs #23](https://github.com/clicon/clixon-controller/issues/23)
Changed parameters of example clispec function `compare_dbs()`
Added show2cbuf functions for TEXT/CLI
Renamed clixon_txt2file to clixon_text2file
This commit is contained in:
Olof hagsand 2023-09-22 10:26:09 +02:00
parent 45f41e3e4d
commit 2603b6f139
19 changed files with 1170 additions and 193 deletions

View file

@ -872,6 +872,7 @@ compare_db_names(clicon_handle h,
cxobj *xc1 = NULL;
cxobj *xc2 = NULL;
cxobj *xerr = NULL;
cbuf *cb = NULL;
if (clicon_rpc_get_config(h, NULL, db1, "/", NULL, NULL, &xc1) < 0)
goto done;
@ -885,10 +886,39 @@ compare_db_names(clicon_handle h,
clixon_netconf_error(xerr, "Get configuration", NULL);
goto done;
}
if (clixon_compare_xmls(xc1, xc2, format) < 0) /* astext? */
goto done;
/* Note that XML and TEXT uses a (new) structured in-mem algorithm while
* JSON and CLI uses (old) UNIX file diff.
*/
switch (format){
case FORMAT_XML:
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (clixon_xml_diff2cbuf(cb, xc1, xc2) < 0)
goto done;
cligen_output(stdout, "%s", cbuf_get(cb));
break;
case FORMAT_TEXT:
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (clixon_text_diff2cbuf(cb, xc1, xc2) < 0)
goto done;
cligen_output(stdout, "%s", cbuf_get(cb));
break;
case FORMAT_JSON:
case FORMAT_CLI:
if (clixon_compare_xmls(xc1, xc2, format) < 0) /* astext? */
goto done;
default:
break;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (xc1)
xml_free(xc1);
if (xc2)
@ -899,25 +929,31 @@ compare_db_names(clicon_handle h,
/*! Compare two dbs using XML. Write to file and run diff
* @param[in] h Clicon handle
* @param[in] cvv
* @param[in] argv arg: 0 as xml, 1: as text
* @param[in] argv <db1> <db2> <format>
*/
int
compare_dbs(clicon_handle h,
cvec *cvv,
cvec *argv)
{
int retval = -1;
int retval = -1;
enum format_enum format;
char *db1;
char *db2;
char *formatstr;
if (cvec_len(argv) > 1){
clicon_err(OE_PLUGIN, EINVAL, "Requires 0 or 1 element. If given: astext flag 0|1");
if (cvec_len(argv) != 3){
clicon_err(OE_PLUGIN, EINVAL, "Expected arguments: <db1> <db2> <format>");
goto done;
}
if (cvec_len(argv) && cv_int32_get(cvec_i(argv, 0)) == 1)
format = FORMAT_TEXT;
else
format = FORMAT_XML;
if (compare_db_names(h, format, "running", "candidate") < 0)
db1 = cv_string_get(cvec_i(argv, 0));
db2 = cv_string_get(cvec_i(argv, 1));
formatstr = cv_string_get(cvec_i(argv, 2));
if ((format = format_str2int(formatstr)) < 0){
clicon_err(OE_XML, 0, "format not found %s", formatstr);
goto done;
}
if (compare_db_names(h, format, db1, db2) < 0)
goto done;
retval = 0;
done:
@ -1186,7 +1222,7 @@ save_config_file(clicon_handle h,
goto done;
break;
case FORMAT_TEXT:
if (clixon_txt2file(f, xt, 0, fprintf, 0, 1) < 0)
if (clixon_text2file(f, xt, 0, fprintf, 0, 1) < 0)
goto done;
break;
case FORMAT_CLI:
@ -1304,7 +1340,7 @@ cli_notification_cb(int s,
if (clixon_json2file(stdout, xt, 1, cligen_output, 1, 1) < 0)
goto done;
case FORMAT_TEXT:
if (clixon_txt2file(stdout, xt, 0, cligen_output, 1, 1) < 0)
if (clixon_text2file(stdout, xt, 0, cligen_output, 1, 1) < 0)
goto done;
break;
case FORMAT_XML:

View file

@ -319,7 +319,7 @@ pipe_showas_fn(clicon_handle h,
goto done;
break;
case FORMAT_TEXT:
if (clixon_txt2file(stdout, xt, 0, cligen_output, 1, 1) < 0)
if (clixon_text2file(stdout, xt, 0, cligen_output, 1, 1) < 0)
goto done;
break;
case FORMAT_CLI:

View file

@ -557,7 +557,7 @@ cli_show_common(clicon_handle h,
cligen_output(stdout, "\n");
break;
case FORMAT_TEXT: /* XXX does not handle multiple leaf-list */
if (clixon_txt2file(stdout, xp, 0, cligen_output, skiptop, 1) < 0)
if (clixon_text2file(stdout, xp, 0, cligen_output, skiptop, 1) < 0)
goto done;
break;
case FORMAT_CLI:
@ -1299,7 +1299,7 @@ cli_pagination(clicon_handle h,
goto done;
break;
case FORMAT_TEXT:
if (clixon_txt2file(stdout, xc, 0, cligen_output, 0, 1) < 0)
if (clixon_text2file(stdout, xc, 0, cligen_output, 0, 1) < 0)
goto done;
break;
case FORMAT_CLI:
@ -1341,6 +1341,134 @@ cli_pagination(clicon_handle h,
return retval;
}
/*! Translate to CLI commands in cbuf
*
* Howto: join strings and pass them down.
* Identify unique/index keywords for correct set syntax.
* @param[in] h Clicon handle
* @param[in,out] cb Cligen buffer to write to
* @param[in] xn XML Parse-tree (to translate)
* @param[in] prepend Print this text in front of all commands.
* @retval 0 OK
* @retval -1 Error
* @see clixon_cli2file
*/
static int
cli2cbuf(clicon_handle h,
cbuf *cb,
cxobj *xn,
char *prepend)
{
int retval = -1;
cxobj *xe = NULL;
cbuf *cbpre = NULL;
yang_stmt *ys;
int match;
char *body;
int compress = 0;
autocli_listkw_t listkw;
int exist = 0;
char *name;
if (autocli_list_keyword(h, &listkw) < 0)
goto done;
if (xml_type(xn)==CX_ATTR)
goto ok;
if ((ys = xml_spec(xn)) == NULL)
goto ok;
if (yang_extension_value(ys, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0)
goto done;
if (exist)
goto ok;
exist = 0;
if (yang_extension_value(ys, "alias", CLIXON_AUTOCLI_NS, &exist, &name) < 0)
goto done;
if (!exist)
name = xml_name(xn);
/* If leaf/leaf-list or presence container, then print line */
if (yang_keyword_get(ys) == Y_LEAF ||
yang_keyword_get(ys) == Y_LEAF_LIST){
if (prepend)
cprintf(cb, "%s", prepend);
if (listkw != AUTOCLI_LISTKW_NONE)
cprintf(cb, "%s ", name);
if ((body = xml_body(xn)) != NULL){
if (index(body, ' '))
cprintf(cb, "\"%s\"", body);
else
cprintf(cb, "%s", body);
}
cprintf(cb, "\n");
goto ok;
}
/* Create prepend variable string */
if ((cbpre = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
if (prepend)
cprintf(cbpre, "%s", prepend);
/* If non-presence container && HIDE mode && only child is
* a list, then skip container keyword
* See also yang2cli_container */
if (autocli_compress(h, ys, &compress) < 0)
goto done;
if (!compress)
cprintf(cbpre, "%s ", xml_name(xn));
/* If list then first loop through keys */
if (yang_keyword_get(ys) == Y_LIST){
xe = NULL;
while ((xe = xml_child_each(xn, xe, -1)) != NULL){
if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
goto done;
if (!match)
continue;
if (listkw == AUTOCLI_LISTKW_ALL)
cprintf(cbpre, "%s ", xml_name(xe));
cprintf(cbpre, "%s ", xml_body(xe));
}
}
else if ((yang_keyword_get(ys) == Y_CONTAINER) &&
yang_find(ys, Y_PRESENCE, NULL) != NULL){
/* If presence container, then print as leaf (but continue to children) */
if (prepend)
cprintf(cb, "%s", prepend);
if (listkw != AUTOCLI_LISTKW_NONE)
cprintf(cb, "%s ", xml_name(xn));
if ((body = xml_body(xn)) != NULL){
if (index(body, ' '))
cprintf(cb, "\"%s\"", body);
else
cprintf(cb, "%s", body);
}
cprintf(cb, "\n");
}
/* For lists, print cbpre before its elements */
if (yang_keyword_get(ys) == Y_LIST)
cprintf(cb, "%s\n", cbuf_get(cbpre));
/* Then loop through all other (non-keys) */
xe = NULL;
while ((xe = xml_child_each(xn, xe, -1)) != NULL){
if (yang_keyword_get(ys) == Y_LIST){
if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
goto done;
if (match)
continue; /* Not key itself */
}
if (cli2cbuf(h, cb, xe, cbuf_get(cbpre)) < 0)
goto done;
}
ok:
retval = 0;
done:
if (cbpre)
cbuf_free(cbpre);
return retval;
}
/*! Translate from XML to CLI commands, internal
*
* Howto: join strings and pass them down.
@ -1352,7 +1480,7 @@ cli_pagination(clicon_handle h,
* @param[in] fn Callback to make print function
*/
static int
xml2cli1(clicon_handle h,
cli2file(clicon_handle h,
FILE *f,
cxobj *xn,
char *prepend,
@ -1457,7 +1585,7 @@ xml2cli1(clicon_handle h,
if (match)
continue; /* Not key itself */
}
if (xml2cli1(h, f, xe, cbuf_get(cbpre), fn) < 0)
if (cli2file(h, f, xe, cbuf_get(cbpre), fn) < 0)
goto done;
}
ok:
@ -1480,6 +1608,7 @@ xml2cli1(clicon_handle h,
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 OK
* @retval -1 Error
* @see clixon_cli2cbuf
*/
int
clixon_cli2file(clicon_handle h,
@ -1497,11 +1626,50 @@ clixon_cli2file(clicon_handle h,
if (skiptop){
xc = NULL;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
if (xml2cli1(h, f, xc, prepend, fn) < 0)
if (cli2file(h, f, xc, prepend, fn) < 0)
goto done;
}
else {
if (xml2cli1(h, f, xn, prepend, fn) < 0)
if (cli2file(h, f, xn, prepend, fn) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Translate from XML to CLI commands
*
* Howto: join strings and pass them down.
* Identify unique/index keywords for correct set syntax.
* @param[in] h Clicon handle
* @param[in] f Output FILE (eg stdout)
* @param[in] xn XML Parse-tree (to translate)
* @param[in] prepend Print this text in front of all commands.
* @param[in] fn File print function (if NULL, use fprintf)
* @param[in] skiptop 0: Include top object 1: Skip top-object, only children,
* @retval 0 OK
* @retval -1 Error
* @see clixon_cli2file
*/
int
clixon_cli2cbuf(clicon_handle h,
cbuf *cb,
cxobj *xn,
char *prepend,
int skiptop)
{
int retval = 1;
cxobj *xc;
if (skiptop){
xc = NULL;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
if (cli2cbuf(h, cb, xc, prepend) < 0)
goto done;
}
else {
if (cli2cbuf(h, cb, xn, prepend) < 0)
goto done;
}
retval = 0;

View file

@ -118,7 +118,7 @@ int cli_process_control(clicon_handle h, cvec *vars, cvec *argv);
int expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv,
cvec *commands, cvec *helptexts);
int clixon_cli2file(clicon_handle h, FILE *f, cxobj *xn, char *prepend, clicon_output_cb *fn, int skiptop);
int clixon_cli2cbuf(clicon_handle h, cbuf *cb, cxobj *xn, char *prepend, int skiptop);
/* cli_show.c: CLIgen new vector arg callbacks */
int cli_show_common(clicon_handle h, char *db, enum format_enum format, int pretty, int state, char *withdefault, char *extdefault, char *prepend, char *xpath, int fromroot, cvec *nsc, int skiptop);