* Renamed clixon-clispec.yang to clixon-autocli.yang

* First version of clixon-autocli.yang semantics
   * Default rules for module exclusion, list-keywords, completion, treeref-state
   * Specialized rules for compress and exclusion of modules
   * See [autocli documentation](https://clixon-docs.readthedocs.io/en/latest/cli.html#autocli)
* Obsoleted and moved autocli config options from clixon-config.yang to clixon-autocli.yang as follows:
   * `CLICON_CLI_GENMODEL`, use: `autocli/module-default=false` instead
      * Removed `clicon_cli_genmodel()`
   * `CLICON_CLI_GENMODEL_TYPE`, use `autocli/list-keyword-default` and compress rules instead)
      * Removed `clicon_cli_genmodel_type()`
   * `CLICON_CLI_GENMODEL_COMPLETION`, use `autocli/completion-default` instead
      * Removed `clicon_cli_genmodel_completion()`
   * `CLICON_CLI_AUTOCLI_EXCLUDE`, use `autocli/rule/operation=exclude` instead
   * `CLICON_CLI_MODEL_TREENAME`, use constant `AUTOCLI_TREENAME` instead
     * Removed `clicon_cli_model_treename()`
* New YANG functions: yang_single_child_type, yang_find_namespace_by_prefix, yang_str2key
* Changed return values of yang_find_prefix_by_namespace
* Merged `cli_cli2xml()` into `cli2xml()`
This commit is contained in:
Olof hagsand 2021-12-28 19:21:52 +01:00
parent dec05e2cae
commit 081e6871b3
45 changed files with 1804 additions and 900 deletions

View file

@ -477,22 +477,22 @@ clicon_conf_restconf(clicon_handle h)
return NULL;
}
/*! Get local YANG specification for Clixon-clispec.yang tree
/*! Get clixon-autocli.yang part of the clixon config tree
*
* That is, get the XML of clixon-config/clispec container of clixon-config.yang
* That is, get the XML of clixon-config/autocli container of clixon-config.yang
* @param[in] h Clicon handle
* @retval x XML tree containing clispec xml node from clixon-clispec.yang
* @retval x XML tree containing clispec xml node from clixon-autoclu.yang
* @code
* cxobj *xclispec = clicon_conf_clispec(h);
* cxobj *xautocli = clicon_conf_autocli(h);
* @endcode
*/
cxobj *
clicon_conf_clispec(clicon_handle h)
clicon_conf_autocli(clicon_handle h)
{
cxobj *xconfig = NULL;
if ((xconfig = clicon_conf_xml(h)) != NULL) /* Get local config */
return xpath_first(xconfig, NULL, "clispec");
return xpath_first(xconfig, NULL, "autocli");
return NULL;
}

View file

@ -82,17 +82,6 @@
#include "clixon_validate.h"
#include "clixon_xml_map.h"
/* Mapping between Cli generation from Yang string <--> constants,
see clixon-config.yang type cli_genmodel_type */
static const map_str2int cli_genmodel_map[] = {
{"NONE", GT_NONE},
{"VARS", GT_VARS},
{"ALL", GT_ALL},
{"HIDE", GT_HIDE},
{"OC_COMPRESS", GT_OC_COMPRESS},
{NULL, -1}
};
/* Mapping between Clicon startup modes string <--> constants,
see clixon-config.yang type startup_mode */
static const map_str2int startup_mode_map[] = {
@ -113,7 +102,6 @@ static const map_str2int priv_mode_map[] = {
{NULL, -1}
};
/* Mapping between Clicon nacm user credential string <--> constants,
* see clixon-config.yang type nacm_cred_mode */
static const map_str2int nacm_credentials_map[] = {
@ -742,54 +730,6 @@ clicon_option_del(clicon_handle h,
* But sometimes there are type conversions, etc which makes it more
* convenient to make wrapper functions. Or not?
*-----------------------------------------------------------------*/
/*! Whether to generate CLIgen syntax from datamodel or not (0, 1 or 2)
* Must be used with a previous clicon_option_exists().
* @param[in] h Clicon handle
* @retval flag If set, generate CLI code from yang model, otherwise not
* @see clixon-config@<date>.yang CLICON_CLI_GENMODEL
*/
int
clicon_cli_genmodel(clicon_handle h)
{
char const *opt = "CLICON_CLI_GENMODEL";
if (clicon_option_exists(h, opt))
return clicon_option_int(h, opt);
else
return 0;
}
/*! Generate code for CLI completion of existing db symbols
* @param[in] h Clicon handle
* @retval flag If set, generate auto-complete CLI specs
* @see clixon-config@<date>.yang CLICON_CLI_GENMODEL_COMPLETION
*/
int
clicon_cli_genmodel_completion(clicon_handle h)
{
char const *opt = "CLICON_CLI_GENMODEL_COMPLETION";
if (clicon_option_exists(h, opt))
return clicon_option_int(h, opt);
else
return 0;
}
/*! How to generate and show CLI syntax: VARS|ALL
* @param[in] h Clicon handle
* @retval mode
* @see clixon-config@<date>.yang CLICON_CLI_GENMODEL_TYPE
*/
enum genmodel_type
clicon_cli_genmodel_type(clicon_handle h)
{
char *str;
if ((str = clicon_option_str(h, "CLICON_CLI_GENMODEL_TYPE")) == NULL)
return GT_VARS;
else
return clicon_str2int(cli_genmodel_map, str);
}
/*! Get "do not include keys in cvec" in cli vars callbacks
* @param[in] h Clicon handle

View file

@ -357,9 +357,6 @@ uri_percent_decode(char *enc,
* @param[in] ... stdarg variable parameters
* @retval 0 OK
* @retval -1 Error
* @see https://www.w3.org/TR/2008/REC-xml-20081126/#syntax chapter 2.6
* @see uri_percent_encode
* @see AMPERSAND mode in clixon_xml_parse.l
* @code
* char *encstr = NULL;
* if (xml_chardata_encode(&encstr, "fmtstr<>& %s", "substr<>") < 0)
@ -368,12 +365,14 @@ uri_percent_decode(char *enc,
* free(encstr);
* @endcode
* Essentially encode as follows:
* & -> "&amp; " must
* < -> "&lt; " must
* > -> "&gt; " must for backward compatibility
* ' -> "&apos; " may
* ' -> "&quot; " may
* Optionally >
* & -> "&amp;" must
* < -> "&lt;" must
* > -> "&gt;" must for backward compatibility
* ' -> "&apos;" may
* " -> "&quot;" may
* @see https://www.w3.org/TR/2008/REC-xml-20081126/#syntax chapter 2.6
* @see uri_percent_encode
* @see AMPERSAND mode in clixon_xml_parse.l, implicit decoding
* @see xml_chardata_cbuf_append for a specialized version
*/
int

View file

@ -211,151 +211,6 @@ xml2txt(FILE *f,
return xml2txt_recurse(f, x, fprintf, level);
}
/*! Translate from XML to CLI commands
* Howto: join strings and pass them down.
* Identify unique/index keywords for correct set syntax.
* @param[in] f Where to print cli commands
* @param[in] x XML Parse-tree (to translate)
* @param[in] prepend Print this text in front of all commands.
* @param[in] gt option to steer cli syntax
* @param[in] fn Callback to make print function
*/
int
xml2cli_recurse(FILE *f,
cxobj *x,
char *prepend,
enum genmodel_type gt,
clicon_output_cb *fn)
{
int retval = -1;
cxobj *xe = NULL;
cbuf *cbpre = NULL;
yang_stmt *ys;
int match;
char *body;
if (xml_type(x)==CX_ATTR)
goto ok;
if ((ys = xml_spec(x)) == NULL)
goto ok;
/* 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)
(*fn)(f, "%s", prepend);
if (gt == GT_ALL || gt == GT_VARS || gt == GT_HIDE)
(*fn)(f, "%s ", xml_name(x));
if ((body = xml_body(x)) != NULL){
if (index(body, ' '))
(*fn)(f, "\"%s\"", body);
else
(*fn)(f, "%s", body);
}
(*fn)(f, "\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 (yang_container_cli_hide(ys, gt) == 0)
cprintf(cbpre, "%s ", xml_name(x));
/* If list then first loop through keys */
if (yang_keyword_get(ys) == Y_LIST){
xe = NULL;
while ((xe = xml_child_each(x, xe, -1)) != NULL){
if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
goto done;
if (!match)
continue;
if (gt == GT_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)
(*fn)(f, "%s", prepend);
if (gt == GT_ALL || gt == GT_VARS || gt == GT_HIDE || gt == GT_OC_COMPRESS)
(*fn)(f, "%s ", xml_name(x));
if ((body = xml_body(x)) != NULL){
if (index(body, ' '))
(*fn)(f, "\"%s\"", body);
else
(*fn)(f, "%s", body);
}
(*fn)(f, "\n");
}
/* Then loop through all other (non-keys) */
xe = NULL;
while ((xe = xml_child_each(x, 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){
(*fn)(f, "%s\n", cbuf_get(cbpre));
continue; /* Not key itself */
}
}
if (xml2cli_recurse(f, xe, cbuf_get(cbpre), gt, fn) < 0)
goto done;
}
ok:
retval = 0;
done:
if (cbpre)
cbuf_free(cbpre);
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] f Where to print cli commands
* @param[in] x XML Parse-tree (to translate)
* @param[in] prepend Print this text in front of all commands.
* @param[in] gt option to steer cli syntax
* @param[in] fn Callback to make print function
*/
int
xml2cli_cb(FILE *f,
cxobj *x,
char *prepend,
enum genmodel_type gt,
clicon_output_cb *fn)
{
return xml2cli_recurse(f, x, prepend, gt, fn);
}
/*! Translate from XML to CLI commands
* Howto: join strings and pass them down.
* Identify unique/index keywords for correct set syntax.
* Args:
* @param[in] f Where to print cli commands
* @param[in] x XML Parse-tree (to translate)
* @param[in] prepend Print this text in front of all commands.
* @param[in] gt option to steer cli syntax
*/
int
xml2cli(FILE *f,
cxobj *x,
char *prepend,
enum genmodel_type gt)
{
return xml2cli_recurse(f, x, prepend, gt, fprintf);
}
/*! Translate a single xml node to a cligen variable vector. Note not recursive
* @param[in] xt XML tree containing one top node
* @param[in] ys Yang spec containing type specification of top-node of xt
@ -1592,6 +1447,7 @@ assign_namespace(cxobj *x0, /* source */
cvec *nsc0 = NULL;
cvec *nsc = NULL;
yang_stmt *y;
int ret;
/* 2a. Detect if namespace is declared in x1 target parent */
if (xml2prefix(x1p, ns, &pexist) == 1){
@ -1649,9 +1505,9 @@ assign_namespace(cxobj *x0, /* source */
goto done;
}
/* Find local (imported) prefix for that module namespace */
if (yang_find_prefix_by_namespace(y, ns, &ptmp) < 0)
if ((ret = yang_find_prefix_by_namespace(y, ns, &ptmp)) < 0)
goto done;
if ((prefix1 = strdup(ptmp)) == NULL){
if (ret == 1 && (prefix1 = strdup(ptmp)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}

View file

@ -1267,23 +1267,27 @@ yang_find_mynamespace(yang_stmt *ys)
* (global) namespace of a module, but you do not know the local prefix
* used to access it in XML.
* @param[in] ys Yang statement in module tree (or module itself)
* @param[in] ns Namspace URI as char* pointer into yang tree
* @param[in] ns Namespace URI as char* pointer into yang tree
* @param[out] prefix Local prefix to access module with (direct pointer)
* @retval 0 not found
* @retval -1 found
* @retval -1 Error
* @retval 0 Not found
* @retval 1 Found
* @note prefix NULL is not returned, if own module, then return its prefix
* @code
* char *prefix = NULL;
* if (yang_find_prefix_by_namespace(ys, "urn:example:clixon", &prefix) < 0)
* if ((found = yang_find_prefix_by_namespace(ys, "urn:example:clixon", &prefix)) < 0)
* err;
* if (found)
* // use prefix
* @endcode
* @see yang_find_module_by_namespace
*/
int
yang_find_prefix_by_namespace(yang_stmt *ys,
char *ns,
char **prefix)
{
int retval = 0; /* not found */
int retval = -1;
yang_stmt *my_ymod; /* My module */
char *myns; /* My ns */
yang_stmt *yspec;
@ -1293,6 +1297,10 @@ yang_find_prefix_by_namespace(yang_stmt *ys,
yang_stmt *yprefix;
clicon_debug(2, "%s", __FUNCTION__);
if (prefix == NULL){
clicon_err(OE_YANG, EINVAL, "prefix is NULL");
goto done;
}
/* First check if namespace is my own module */
myns = yang_find_mynamespace(ys);
if (strcmp(myns, ns) == 0){
@ -1316,10 +1324,55 @@ yang_find_prefix_by_namespace(yang_stmt *ys,
}
}
notfound:
retval = 0; /* not found */
done:
return retval;
found:
assert(*prefix);
return 1;
retval = 1;
goto done;
}
/*! Given a yang statement and local prefi valid in module , find namespace
*
* @param[in] ys Yang statement in module tree (or module itself)
* @param[in] prefix Local prefix to access module with (direct pointer)
* @param[out] ns Namespace URI as char* pointer into yang tree
* @retval -1 Error
* @retval 0 Not found
* @retval 1 Found
* @note prefix NULL is not returned, if own module, then return its prefix
* @code
* char *ns = NULL;
* if ((found = yang_find_namespace_by_prefix(ys, "ex", &ns)) < 0)
* err;
* if (found)
* // use ns *
* @endcode
* @see yang_find_module_by_prefix
*/
int
yang_find_namespace_by_prefix(yang_stmt *ys,
char *prefix,
char **ns)
{
int retval = -1;
yang_stmt *ym;
if (ns == NULL){
clicon_err(OE_YANG, EINVAL, "ns is NULL");
goto done;
}
if ((ym = yang_find_module_by_prefix(ys, prefix)) == NULL)
goto notfound;
if ((*ns = yang_find_mynamespace(ym)) == NULL)
goto notfound;
retval = 1; /* found */
done:
return retval;
notfound:
retval = 0;
goto done;
}
/*! Return topmost yang root node directly under module/submodule
@ -1505,6 +1558,12 @@ yang_key2str(int keyword)
return (char*)clicon_int2str(ykmap, keyword);
}
int
yang_str2key(char *str)
{
return clicon_str2int(ykmap, str);
}
/*! Find top data node among all modules by namespace in xml tree
* @param[in] yspec Yang specification
* @param[in] xt XML node
@ -3392,53 +3451,6 @@ yang_arg2cvec(yang_stmt *ys,
return cvv;
}
/*! Check if yang is subject to generated cli GT_HIDE boolean
* The yang should be:
* 1) a non-presence container
* 2) parent of a (single) list XXX: or could multiple lists work?
* 3) no other data node children
* @retval 0 No, does not satisfy the GT_HIDE condition
* @retval 1 Yes, satisfies the GT_HIDE condition
* @see clixon-config.yang HIDE enumeration type
*/
int
yang_container_cli_hide(yang_stmt *ys,
enum genmodel_type gt)
{
yang_stmt *yc = NULL;
int i;
enum rfc_6020 keyw;
keyw = yang_keyword_get(ys);
/* HIDE mode */
if (gt != GT_HIDE && gt != GT_OC_COMPRESS)
return 0;
/* A container */
if (yang_keyword_get(ys) != Y_CONTAINER)
return 0;
/* Non-presence */
if (yang_find(ys, Y_PRESENCE, NULL) != NULL)
return 0;
/* Ensure a single list child and no other data nodes */
i = 0; /* Number of list nodes */
while ((yc = yn_each(ys, yc)) != NULL) {
keyw = yang_keyword_get(yc);
/* case/choice could hide anything so disqualify those */
if (keyw == Y_CASE || keyw == Y_CHOICE)
break;
if (!yang_datanode(yc)) /* Allowed, check next */
continue;
if (keyw != Y_LIST) /* Another datanode than list */
break;
if (i++>0) /* More than one list (or could this work?) */
break;
}
if (yc != NULL) /* break from loop */
return 0;
if (i != 1) /* List found */
return 0;
return 1; /* Passed all tests: yes you can hide this keyword */
}
/*! Check if yang node yn has key-stmt as child which matches name
*
@ -3719,9 +3731,9 @@ yang_anydata_add(yang_stmt *yp,
/*! Find extension argument and return if extension exists and its argument value
*
* @param[in] ys Yang statement
* @param[in] ys Yang statement where unknown statement may occur referncing to extension
* @param[in] name Name of the extension
* @param[in] ns The namespace
* @param[in] ns The namespace of the module where the extension is defined
* @param[out] exist The extension exists.
* @param[out] value clispec operator (hide/none) - direct pointer into yang, dont free
* @retval 0 OK: Look in exist and value for return value
@ -3751,6 +3763,7 @@ yang_extension_value(yang_stmt *ys,
cg_var *cv;
char *prefix = NULL;
cbuf *cb = NULL;
int ret;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
@ -3762,7 +3775,9 @@ yang_extension_value(yang_stmt *ys,
continue;
if ((ymod = ys_module(yext)) == NULL)
continue;
if (yang_find_prefix_by_namespace(ymod, ns, &prefix) < 0)
if ((ret = yang_find_prefix_by_namespace(ymod, ns, &prefix)) < 0)
goto done;
if (ret == 0) /* not found */
goto ok;
cbuf_reset(cb);
cprintf(cb, "%s:%s", prefix, name);
@ -3901,3 +3916,53 @@ yang_search_index_extension(clicon_handle h,
}
#endif /* XML_EXPLICIT_INDEX */
/*! Check if yang node has a single child of specific type
*
* Mainly used for condition for CLI compression
* The yang should be:
* 1) If container it should be non-presence
* 2) parent of a (single) specified type
* 3) no other data node children
* @param[in] ys Yang node
* @param[in] subkeyw Expected keyword of single child (typically Y_LIST)
* @retval 0 No, node does not have single child of specified type
* @retval 1 Yes, node has single child of specified type
* @see https://github.com/openconfig/ygot/blob/master/docs/design.md#openconfig-path-compression 2nd clause
*/
int
yang_single_child_type(yang_stmt *ys,
enum rfc_6020 subkeyw)
{
yang_stmt *yc = NULL;
int i;
enum rfc_6020 keyw;
/* Match parent */
/* If container, check it is Non-presence */
if (yang_keyword_get(ys) == Y_CONTAINER){
if (yang_find(ys, Y_PRESENCE, NULL) != NULL)
return 0;
}
/* Ensure a single list child and no other data nodes */
i = 0; /* Number of list nodes */
while ((yc = yn_each(ys, yc)) != NULL) {
keyw = yang_keyword_get(yc);
/* case/choice could hide anything so disqualify those */
if (keyw == Y_CASE || keyw == Y_CHOICE)
break;
if (!yang_datanode(yc)) /* Allowed, check next */
continue;
if (keyw != subkeyw) /* Another datanode than subkeyw */
break;
if (i++>0) /* More than one list (or could this work?) */
break;
}
if (yc != NULL) /* break from loop */
return 0;
if (i != 1) /* List found */
return 0;
return 1; /* Passed all tests: yes you can hide this keyword */
}

View file

@ -488,6 +488,7 @@ clixon_module_upgrade(clicon_handle h,
* @note Prefixes are relative to the module they are defined
* @see yang_find_module_by_name
* @see yang_find_module_by_namespace
* @see yang_find_namespace_by_prefix
*/
yang_stmt *
yang_find_module_by_prefix(yang_stmt *ys,
@ -564,6 +565,7 @@ yang_find_module_by_prefix_yspec(yang_stmt *yspec,
* @retval NULL not found
* @see yang_find_module_by_name
* @see yang_find_module_by_prefix module-specific prefix
* @see yang_find_prefix_by_namespace
*/
yang_stmt *
yang_find_module_by_namespace(yang_stmt *yspec,