* Added lastkey argument to yang_key_match() * Fixed segv in process-sigchld * Added ietf-yang-library to CLICON_CLI_AUTOCLI_EXCLUDE default value * Added yang_spec_print() function
1356 lines
39 KiB
C
1356 lines
39 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2019 Olof Hagsand
|
|
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
|
|
|
This file is part of CLIXON.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
the GNU General Public License Version 3 or later (the "GPL"),
|
|
in which case the provisions of the GPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of the GPL, and not to allow others to
|
|
use your version of this file under the terms of Apache License version 2,
|
|
indicate your decision by deleting the provisions above and replace them with
|
|
the notice and other provisions required by the GPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the Apache License version 2 or the GPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
*
|
|
* Translation between database specs
|
|
* yang_spec CLIgen parse_tree
|
|
* +-------------+ yang2cli +-------------+
|
|
* | | ------------> | cli |
|
|
* | list{key A;}| | syntax |
|
|
* +-------------+ +-------------+
|
|
|
|
* YANG generate CLI
|
|
|
|
* A special tree called @datamodel is generated by the yang2cli function.
|
|
* This tree contains generated CLIgen syntax for all loaded YANG modules, except the ones given by
|
|
* CLICON_CLI_AUTOCLI_EXCLUDE
|
|
* The @datamodel tree can be used using the CLIgen "tree reference" functionality as described in
|
|
* the cligen tutorial Secion 2.7.
|
|
* The tree can be modified by removing labels.
|
|
* By default "nonconfig" and "show" labels are by default removed.
|
|
* This means that using @datamodel without modifiers is a "clean" config tree.
|
|
* "nonconfig" and "show" can be removed by using, eg
|
|
* cmd @basemodel, @remove:show, @remove:nonconfig, callback();
|
|
|
|
This is an example yang module:
|
|
module m {
|
|
container x {
|
|
namespace "urn:example:m";
|
|
prefix m;
|
|
list m1 {
|
|
key "a";
|
|
leaf a {
|
|
type string;
|
|
}
|
|
leaf b {
|
|
type string;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
You can see which CLISPEC it generates via clixon_cli -D 2:
|
|
x,cli_set("/example:x");{
|
|
m1 a (<a:string>|<a:string expand_dbvar("candidate","/example:x/m1=%s/a")>),overwrite_me("/example:x/m1=%s/");
|
|
{
|
|
b (<b:string>|<b:string expand_dbvar("candidate","/example:x/m1=%s/b")>),overwrite_me("/example:x/m1=%s/b");
|
|
}
|
|
}
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "clixon_config.h" /* generated by config & autoconf */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <syslog.h>
|
|
#include <signal.h>
|
|
#include <sys/param.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* Clicon */
|
|
#include <clixon/clixon.h>
|
|
|
|
#include "clixon_cli_api.h"
|
|
#include "cli_plugin.h"
|
|
#include "cli_generate.h"
|
|
|
|
/*
|
|
* Constants
|
|
*/
|
|
/* variable expand function */
|
|
#define GENERATE_EXPAND_XMLDB "expand_dbvar"
|
|
|
|
/*! Create cligen variable expand entry with xmlkey format string as argument
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys yang_stmt of the node at hand
|
|
* @param[in] cvtype Type of the cligen variable
|
|
* @param[in] options
|
|
* @param[in] fraction_digits
|
|
* @param[out] cb The string where the result format string is inserted.
|
|
* @retval 1 Hide, dont show helptext etc
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see expand_dbvar This is where the expand string is used
|
|
* @note XXX only fraction_digits handled,should also have mincv, maxcv, pattern
|
|
*/
|
|
static int
|
|
cli_expand_var_generate(clicon_handle h,
|
|
yang_stmt *ys,
|
|
char *cvtypestr,
|
|
int options,
|
|
uint8_t fraction_digits,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
char *api_path_fmt = NULL, *opext = NULL;
|
|
|
|
if (yang_extension_value(ys, "autocli-op", CLIXON_LIB_NS, NULL, &opext) < 0)
|
|
goto done;
|
|
if (opext && strcmp(opext, "hide-database") == 0) {
|
|
retval = 1;
|
|
goto done;
|
|
}
|
|
if (yang2api_path_fmt(ys, 1, &api_path_fmt) < 0)
|
|
goto done;
|
|
cprintf(cb, "|<%s:%s", yang_argument_get(ys), cvtypestr);
|
|
if (options & YANG_OPTIONS_FRACTION_DIGITS)
|
|
cprintf(cb, " fraction-digits:%u", fraction_digits);
|
|
cprintf(cb, " %s(\"candidate\",\"%s\")>",
|
|
GENERATE_EXPAND_XMLDB,
|
|
api_path_fmt);
|
|
retval = 0;
|
|
done:
|
|
if (api_path_fmt)
|
|
free(api_path_fmt);
|
|
return retval;
|
|
}
|
|
|
|
/*! Create callback with api_path format string as argument
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys yang_stmt of the node at hand
|
|
* @param[out] cb The string where the result format string is inserted.
|
|
* @see cli_dbxml This is where the xmlkeyfmt string is used
|
|
* @see pt_callback_reference in CLIgen where the actual callback overwrites the template
|
|
*/
|
|
static int
|
|
cli_callback_generate(clicon_handle h,
|
|
yang_stmt *ys,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
char *api_path_fmt = NULL;
|
|
|
|
if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0)
|
|
goto done;
|
|
cprintf(cb, ",%s(\"%s\")", GENERATE_CALLBACK,
|
|
api_path_fmt);
|
|
retval = 0;
|
|
done:
|
|
if (api_path_fmt)
|
|
free(api_path_fmt);
|
|
return retval;
|
|
}
|
|
|
|
/*! Print cligen help string as ("<helpstring>")
|
|
* @param[in] cb CLIgen buf holding generated CLIspec
|
|
* @param[in] helptext Help text
|
|
*/
|
|
static int
|
|
yang2cli_helptext(cbuf *cb,
|
|
char *helptext)
|
|
{
|
|
if (helptext)
|
|
cprintf(cb, "(\"%s\")", helptext);
|
|
return 0;
|
|
}
|
|
|
|
/*! Generate identityref statements for CLI variables
|
|
* @param[in] ys Yang statement
|
|
* @param[in] ytype Resolved yang type.
|
|
* @param[in] helptext CLI help text
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @see yang2cli_var_sub Its sub-function
|
|
*/
|
|
static int
|
|
yang2cli_var_identityref(yang_stmt *ys,
|
|
yang_stmt *ytype,
|
|
char *cvtypestr,
|
|
char *helptext,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ybaseref;
|
|
yang_stmt *ybaseid;
|
|
cg_var *cv = NULL;
|
|
char *prefix = NULL;
|
|
char *id = NULL;
|
|
int i;
|
|
cvec *idrefvec;
|
|
yang_stmt *ymod;
|
|
yang_stmt *yprefix;
|
|
yang_stmt *yspec;
|
|
|
|
if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL)
|
|
goto ok;
|
|
if ((ybaseid = yang_find_identity(ytype, yang_argument_get(ybaseref))) == NULL)
|
|
goto ok;
|
|
idrefvec = yang_cvec_get(ybaseid);
|
|
if (cvec_len(idrefvec) > 0){
|
|
/* Add a wildchar string first -let validate take it for default prefix */
|
|
cprintf(cb, ">");
|
|
yang2cli_helptext(cb, helptext);
|
|
cprintf(cb, "|<%s:%s choice:", yang_argument_get(ys), cvtypestr);
|
|
yspec = ys_spec(ys);
|
|
i = 0;
|
|
while ((cv = cvec_each(idrefvec, cv)) != NULL){
|
|
if (nodeid_split(cv_name_get(cv), &prefix, &id) < 0)
|
|
goto done;
|
|
/* Translate from module-name(prefix) to global prefix
|
|
* This is really a kludge for true identityref prefix handling
|
|
* IDENTITYREF_KLUDGE
|
|
* This is actually quite complicated: the cli needs to generate
|
|
* a netconf statement with correct xmlns binding
|
|
*/
|
|
if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL &&
|
|
(yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL){
|
|
if (i++)
|
|
cprintf(cb, "|");
|
|
cprintf(cb, "%s:%s", yang_argument_get(yprefix), id);
|
|
}
|
|
if (prefix){
|
|
free(prefix);
|
|
prefix = NULL;
|
|
}
|
|
if (id){
|
|
free(id);
|
|
id = NULL;
|
|
}
|
|
}
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (prefix)
|
|
free(prefix);
|
|
if (id)
|
|
free(id);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate range check statements for CLI variables
|
|
* @param[in] ys Yang statement
|
|
* @param[in] options Flags field of optional values, eg YANG_OPTIONS_RANGE
|
|
* @param[in] cvv Cvec with array of range_min/range_max cv:s (if YANG_OPTIONS_RANGE is set in options)
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @see yang2cli_var_sub which is the main function
|
|
* In yang ranges are given as range 1 or range 1 .. 16, encoded in a cvv
|
|
* 0 : range_min = x
|
|
* and
|
|
* 0 : range_min = x
|
|
* 1 : range_max = y
|
|
* Multiple ranges are given as: range x..y | x1..y1
|
|
* This is encode in clixon as a cvec as:
|
|
* 0 : range_min = x
|
|
* 1 : range_max = y
|
|
* 0 : range_min = x1
|
|
* 1 : range_max = y1
|
|
*
|
|
* Generation of cli code
|
|
* Single range is made by eg:
|
|
* <n:uint8 range[1:16]>
|
|
* Multiple ranges is made by generating code eg:
|
|
* <n:uint8 range[1:16] range[32:64]>
|
|
*/
|
|
static int
|
|
yang2cli_var_range(yang_stmt *ys,
|
|
int options,
|
|
cvec *cvv,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
int i;
|
|
cg_var *cv1; /* lower limit */
|
|
cg_var *cv2; /* upper limit */
|
|
|
|
/* Loop through range_min and range_min..range_max */
|
|
i = 0;
|
|
while (i<cvec_len(cvv)){
|
|
cv1 = cvec_i(cvv, i++);
|
|
if (strcmp(cv_name_get(cv1),"range_min") == 0){
|
|
cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length");
|
|
cv2cbuf(cv1, cb);
|
|
cprintf(cb,":");
|
|
/* probe next */
|
|
if (i<cvec_len(cvv) &&
|
|
(cv2 = cvec_i(cvv, i)) != NULL &&
|
|
strcmp(cv_name_get(cv2),"range_max") == 0){
|
|
i++;
|
|
cv2cbuf(cv2, cb);
|
|
}
|
|
else /* If not, it is a single number range [x:x]*/
|
|
cv2cbuf(cv1, cb);
|
|
cprintf(cb,"]");
|
|
}
|
|
}
|
|
retval = 0;
|
|
// done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang variable pattern statement
|
|
* @param[in] h Clixon handle
|
|
* @param[in] patterns Cvec of regexp patterns
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @see cv_validate_pattern for netconf validate code
|
|
*/
|
|
static int
|
|
yang2cli_var_pattern(clicon_handle h,
|
|
cvec *patterns,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
enum regexp_mode mode;
|
|
cg_var *cvp;
|
|
char *pattern;
|
|
int invert;
|
|
char *posix;
|
|
|
|
mode = clicon_yang_regexp(h);
|
|
cvp = NULL; /* Loop over compiled regexps */
|
|
while ((cvp = cvec_each(patterns, cvp)) != NULL){
|
|
pattern = cv_string_get(cvp);
|
|
invert = cv_flag(cvp, V_INVERT);
|
|
if (mode == REGEXP_POSIX){
|
|
posix = NULL;
|
|
if (regexp_xsd2posix(pattern, &posix) < 0)
|
|
goto done;
|
|
cprintf(cb, " regexp:%s\"%s\"",
|
|
invert?"!":"",
|
|
posix);
|
|
if (posix){
|
|
free(posix);
|
|
posix = NULL;
|
|
}
|
|
}
|
|
else
|
|
cprintf(cb, " regexp:%s\"%s\"",
|
|
invert?"!":"",
|
|
pattern);
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/* Forward */
|
|
static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, genmodel_type gt,
|
|
int level, cbuf *cb);
|
|
|
|
static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype,
|
|
yang_stmt *ytype, char *helptext, cbuf *cb);
|
|
|
|
/*! Generate CLI code for Yang leaf state ment to CLIgen variable of specific type
|
|
* Check for completion (of already existent values), ranges (eg range[min:max]) and
|
|
* patterns, (eg regexp:"[0.9]*").
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] ytype Resolved yang type.
|
|
* @param[in] helptext CLI help text
|
|
* @param[in] cvtype
|
|
* @param[in] options Flags field of optional values, see YANG_OPTIONS_*
|
|
* @param[in] cvv Cvec with array of range_min/range_max cv:s
|
|
* @param[in] patterns Cvec of regexp patterns
|
|
* @param[in] fraction for decimal64, how many digits after period
|
|
* @param[out] cb Buffer where cligen code is written
|
|
* @see yang_type_resolve for options and other arguments
|
|
*/
|
|
static int
|
|
yang2cli_var_sub(clicon_handle h,
|
|
yang_stmt *ys,
|
|
yang_stmt *ytype, /* resolved type */
|
|
char *helptext,
|
|
enum cv_type cvtype,
|
|
int options,
|
|
cvec *cvv,
|
|
cvec *patterns,
|
|
uint8_t fraction_digits,
|
|
cbuf *cb
|
|
)
|
|
{
|
|
int retval = -1;
|
|
char *type;
|
|
yang_stmt *yi = NULL;
|
|
int i = 0;
|
|
int j;
|
|
char *cvtypestr;
|
|
char *arg;
|
|
size_t len;
|
|
|
|
if (cvtype == CGV_VOID){
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
type = ytype?yang_argument_get(ytype):NULL;
|
|
cvtypestr = cv_type2str(cvtype);
|
|
|
|
if (type && strcmp(type, "identityref") == 0)
|
|
cprintf(cb, "(");
|
|
cprintf(cb, "<%s:%s", yang_argument_get(ys), cvtypestr);
|
|
/* enumeration special case completion */
|
|
if (type){
|
|
if (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0){
|
|
cprintf(cb, " choice:");
|
|
i = 0;
|
|
yi = NULL;
|
|
while ((yi = yn_each(ytype, yi)) != NULL){
|
|
if (yang_keyword_get(yi) != Y_ENUM && yang_keyword_get(yi) != Y_BIT)
|
|
continue;
|
|
if (i)
|
|
cprintf(cb, "|");
|
|
/* Encode by escaping delimiters */
|
|
arg = yang_argument_get(yi);
|
|
len = strlen(arg);
|
|
for (j=0; j<len; j++){
|
|
if (index(CLIGEN_DELIMITERS, arg[j]))
|
|
cprintf(cb, "\\");
|
|
cprintf(cb, "%c", arg[j]);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
else if (strcmp(type, "identityref") == 0){
|
|
if (yang2cli_var_identityref(ys, ytype, cvtypestr, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
if (options & YANG_OPTIONS_FRACTION_DIGITS)
|
|
cprintf(cb, " fraction-digits:%u", fraction_digits);
|
|
|
|
if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){
|
|
if (yang2cli_var_range(ys, options, cvv, cb) < 0)
|
|
goto done;
|
|
}
|
|
if (patterns && cvec_len(patterns)){
|
|
if (yang2cli_var_pattern(h, patterns, cb) < 0)
|
|
goto done;
|
|
}
|
|
cprintf(cb, ">");
|
|
yang2cli_helptext(cb, helptext);
|
|
if (type && strcmp(type, "identityref") == 0)
|
|
cprintf(cb, ")");
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Resolve a single Yang union and generate code
|
|
* Part of generating CLI code for Yang leaf statement to CLIgen variable
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement (caller of type)
|
|
* @param[in] origtype Name of original type in the call
|
|
* @param[in] ytsub Yang type invocation, a sub-type of a resolved union type
|
|
* @param[in] cb Buffer where cligen code is written
|
|
* @param[in] helptext CLI help text
|
|
*/
|
|
static int
|
|
yang2cli_var_union_one(clicon_handle h,
|
|
yang_stmt *ys,
|
|
char *origtype,
|
|
yang_stmt *ytsub,
|
|
char *helptext,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
int options = 0;
|
|
cvec *cvv = NULL;
|
|
cvec *patterns = NULL;
|
|
uint8_t fraction_digits = 0;
|
|
enum cv_type cvtype;
|
|
yang_stmt *ytype; /* resolved type */
|
|
char *restype;
|
|
|
|
if ((patterns = cvec_new(0)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cvec_new");
|
|
goto done;
|
|
}
|
|
/* Resolve the sub-union type to a resolved type */
|
|
if (yang_type_resolve(ys, ys, ytsub, /* in */
|
|
&ytype, &options, /* resolved type */
|
|
&cvv, patterns, NULL, &fraction_digits) < 0)
|
|
goto done;
|
|
if (ytype == NULL){
|
|
clicon_err(OE_YANG, 0, "result-type should not be NULL");
|
|
goto done;
|
|
}
|
|
restype = ytype?yang_argument_get(ytype):NULL;
|
|
|
|
if (restype && strcmp(restype, "union") == 0){ /* recursive union */
|
|
if (yang2cli_var_union(h, ys, origtype, ytype, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
/* XXX leafref inside union ? */
|
|
else {
|
|
if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
|
|
goto done;
|
|
if ((retval = yang2cli_var_sub(h, ys, ytype, helptext, cvtype,
|
|
options, cvv, patterns, fraction_digits, cb)) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (patterns)
|
|
cvec_free(patterns);
|
|
return retval;
|
|
}
|
|
|
|
/*! Loop over all sub-types of a Yang union
|
|
* Part of generating CLI code for Yang leaf statement to CLIgen variable
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement (caller)
|
|
* @param[in] origtype Name of original type in the call
|
|
* @param[in] ytype Yang resolved type (a union in this case)
|
|
* @param[in] helptext CLI help text
|
|
* @param[out] cb Buffer where cligen code is written
|
|
*/
|
|
static int
|
|
yang2cli_var_union(clicon_handle h,
|
|
yang_stmt *ys,
|
|
char *origtype,
|
|
yang_stmt *ytype,
|
|
char *helptext,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ytsub = NULL;
|
|
int i;
|
|
|
|
i = 0;
|
|
/* Loop over all sub-types in the resolved union type, note these are
|
|
* not resolved types (unless they are built-in, but the resolve call is
|
|
* made in the union_one call.
|
|
*/
|
|
while ((ytsub = yn_each(ytype, ytsub)) != NULL){
|
|
if (yang_keyword_get(ytsub) != Y_TYPE)
|
|
continue;
|
|
if (i++)
|
|
cprintf(cb, "|");
|
|
if (yang2cli_var_union_one(h, ys, origtype, ytsub, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
yang2cli_var_leafref(clicon_handle h,
|
|
yang_stmt *ys,
|
|
yang_stmt *yrestype,
|
|
char *helptext,
|
|
enum cv_type cvtype,
|
|
int options,
|
|
cvec *cvv,
|
|
cvec *patterns,
|
|
uint8_t fraction_digits,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
char *type;
|
|
int completionp;
|
|
char *cvtypestr;
|
|
int ret;
|
|
|
|
/* Give up: use yreferred
|
|
* XXX: inline of else clause below
|
|
*/
|
|
type = yrestype?yang_argument_get(yrestype):NULL;
|
|
cvtypestr = cv_type2str(cvtype);
|
|
if (type)
|
|
completionp = clicon_cli_genmodel_completion(h) &&
|
|
strcmp(type, "enumeration") != 0 &&
|
|
strcmp(type, "identityref") != 0 &&
|
|
strcmp(type, "bits") != 0;
|
|
else
|
|
completionp = clicon_cli_genmodel_completion(h);
|
|
if (completionp)
|
|
cprintf(cb, "(");
|
|
if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype,
|
|
options, cvv, patterns, fraction_digits, cb) < 0)
|
|
goto done;
|
|
if (completionp){
|
|
if ((ret = cli_expand_var_generate(h, ys, cvtypestr,
|
|
options, fraction_digits,
|
|
cb)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
yang2cli_helptext(cb, helptext);
|
|
cprintf(cb, ")");
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang leaf statement to CLIgen variable
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement of original leaf
|
|
* @param[in] yreferred Yang statement of referred node for type (leafref)
|
|
* @param[in] helptext CLI help text
|
|
* @param[out] cb Buffer where cligen code is written
|
|
*
|
|
* Make a type lookup and complete a cligen variable expression such as <a:string>.
|
|
* One complication is yang union, that needs a recursion since it consists of
|
|
* sub-types.
|
|
* eg type union{ type int32; type string } --> (<x:int32>| <x:string>)
|
|
* Another is multiple ranges
|
|
* @note leafrefs are troublesome. In this code their cligen type are string, but they should really
|
|
* be the type of the referred node. But since the path pointing to the referred node is XML, and
|
|
* only YANG is known here, we cannot easily determine the YANG node of the referred XML node,
|
|
* and thus its type.
|
|
*/
|
|
static int
|
|
yang2cli_var(clicon_handle h,
|
|
yang_stmt *ys,
|
|
yang_stmt *yreferred,
|
|
char *helptext,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
char *origtype = NULL;
|
|
yang_stmt *yrestype; /* resolved type */
|
|
char *restype; /* resolved type */
|
|
cvec *cvv = NULL;
|
|
cvec *patterns = NULL;
|
|
uint8_t fraction_digits = 0;
|
|
enum cv_type cvtype;
|
|
char *cvtypestr;
|
|
int options = 0;
|
|
int result;
|
|
|
|
if ((patterns = cvec_new(0)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cvec_new");
|
|
goto done;
|
|
}
|
|
if (yang_type_get(yreferred, &origtype, &yrestype,
|
|
&options, &cvv, patterns, NULL, &fraction_digits) < 0)
|
|
goto done;
|
|
restype = yang_argument_get(yrestype);
|
|
|
|
if (strcmp(restype, "empty") == 0)
|
|
goto ok;
|
|
if (clicon_type2cv(origtype, restype, yreferred, &cvtype) < 0)
|
|
goto done;
|
|
cvtypestr = cv_type2str(cvtype);
|
|
/* Note restype can be NULL here for example with unresolved hardcoded uuid */
|
|
|
|
if (strcmp(restype, "union") == 0){
|
|
/* Union: loop over resolved type's sub-types (can also be recursive unions) */
|
|
cprintf(cb, "(");
|
|
if (yang2cli_var_union(h, ys, origtype, yrestype, helptext, cb) < 0)
|
|
goto done;
|
|
if (clicon_cli_genmodel_completion(h)){
|
|
if ((result = cli_expand_var_generate(h, ys, cvtypestr,
|
|
options, fraction_digits,cb)) < 0)
|
|
goto done;
|
|
if (result == 0)
|
|
yang2cli_helptext(cb, helptext);
|
|
}
|
|
cprintf(cb, ")");
|
|
}
|
|
else if (strcmp(restype,"leafref")==0){
|
|
yang_stmt *ypath;
|
|
char *path_arg;
|
|
yang_stmt *yref = NULL;
|
|
|
|
if ((ypath = yang_find(yrestype, Y_PATH, NULL)) == NULL){
|
|
clicon_err(OE_YANG, 0, "No Y_PATH for leafref");
|
|
goto done;
|
|
}
|
|
if ((path_arg = yang_argument_get(ypath)) == NULL){
|
|
clicon_err(OE_YANG, 0, "No argument for Y_PATH");
|
|
goto done;
|
|
}
|
|
if (yang_path_arg(yreferred, path_arg, &yref) < 0)
|
|
goto done;
|
|
if (yref == NULL){
|
|
/* Give up: use yreferred
|
|
*/
|
|
if (yang2cli_var_leafref(h, ys, yrestype, helptext, cvtype, options,
|
|
cvv, patterns, fraction_digits, cb) < 0)
|
|
goto done;
|
|
}
|
|
else {
|
|
if (yreferred == yref){
|
|
clicon_err(OE_YANG, 0, "Referred YANG node for leafref path %s points to self", path_arg);
|
|
goto done;
|
|
}
|
|
/* recurse call with new referred node */
|
|
if (yang2cli_var(h, ys, yref, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
else{
|
|
if (yang2cli_var_leafref(h, ys, yrestype, helptext, cvtype, options,
|
|
cvv, patterns, fraction_digits, cb) < 0)
|
|
goto done;
|
|
}
|
|
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (origtype)
|
|
free(origtype);
|
|
if (patterns)
|
|
cvec_free(patterns);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang leaf statement
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] gt CLI Generate style
|
|
* @param[in] level Indentation level
|
|
* @param[in] callback If set, include a "; cli_set()" callback, otherwise not
|
|
* @param[in] key_leaf Is leaf in a key in a list module
|
|
* @param[out] cb Buffer where cligen code is written
|
|
*/
|
|
static int
|
|
yang2cli_leaf(clicon_handle h,
|
|
yang_stmt *ys,
|
|
genmodel_type gt,
|
|
int level,
|
|
int callback,
|
|
int key_leaf,
|
|
cbuf *cb)
|
|
{
|
|
yang_stmt *yd; /* description */
|
|
int retval = -1;
|
|
char *helptext = NULL;
|
|
char *s;
|
|
char *opext = NULL;
|
|
int extralevel = 0;
|
|
|
|
/* description */
|
|
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){
|
|
if ((helptext = strdup(yang_argument_get(yd))) == NULL){
|
|
clicon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
if ((s = strstr(helptext, "\n\n")) != NULL)
|
|
*s = '\0';
|
|
}
|
|
cprintf(cb, "%*s", level*3, "");
|
|
/* Look for autocli-op defined in clixon-lib.yang */
|
|
if (yang_extension_value(ys, "autocli-op", CLIXON_LIB_NS, NULL, &opext) < 0)
|
|
goto done;
|
|
if (gt == GT_VARS || gt == GT_ALL || gt == GT_HIDE || gt == GT_OC_COMPRESS){
|
|
cprintf(cb, "%s", yang_argument_get(ys));
|
|
yang2cli_helptext(cb, helptext);
|
|
cprintf(cb, " ");
|
|
if (opext && strcmp(opext, "hide") == 0){
|
|
cprintf(cb, ", hide{");
|
|
extralevel = 1;
|
|
}
|
|
if (opext && strcmp(opext, "hide-database-auto-completion") == 0){
|
|
cprintf(cb, ", hide-database-auto-completion{");
|
|
extralevel = 1;
|
|
}
|
|
if (yang2cli_var(h, ys, ys, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
else{
|
|
if (yang2cli_var(h, ys, ys, helptext, cb) < 0)
|
|
goto done;
|
|
}
|
|
if (callback){
|
|
if (cli_callback_generate(h, ys, cb) < 0)
|
|
goto done;
|
|
cprintf(cb, ";\n");
|
|
}
|
|
if (extralevel)
|
|
cprintf(cb, "}\n");
|
|
retval = 0;
|
|
done:
|
|
if (helptext)
|
|
free(helptext);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang container statement
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] gt CLI Generate style
|
|
* @param[in] level Indentation level
|
|
* @param[out] cb Buffer where cligen code is written
|
|
*/
|
|
static int
|
|
yang2cli_container(clicon_handle h,
|
|
yang_stmt *ys,
|
|
genmodel_type gt,
|
|
int level,
|
|
cbuf *cb)
|
|
{
|
|
yang_stmt *yc;
|
|
yang_stmt *yd;
|
|
int retval = -1;
|
|
char *helptext = NULL;
|
|
char *s;
|
|
int hide = 0;
|
|
int hide_oc = 0;
|
|
int isoc = 0;
|
|
char *opext = NULL;
|
|
yang_stmt *ymod = NULL;
|
|
|
|
if (ys_real_module(ys, &ymod) < 0)
|
|
goto done;
|
|
/* Hide container "config" if openconfig and OC_COMPRESS */
|
|
if (strcmp(yang_argument_get(ys), "config") == 0){
|
|
if (yang_extension_value(ymod, "openconfig-version", "http://openconfig.net/yang/openconfig-ext", &isoc, NULL) < 0)
|
|
goto done;
|
|
if (isoc &&
|
|
gt == GT_OC_COMPRESS)
|
|
hide_oc = 1;
|
|
}
|
|
|
|
/* If non-presence container && HIDE mode && only child is
|
|
* a list, then skip container keyword
|
|
* See also xml2cli
|
|
*/
|
|
if ((hide = yang_container_cli_hide(ys, gt)) == 0 && hide_oc == 0){
|
|
cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys));
|
|
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){
|
|
if ((helptext = strdup(yang_argument_get(yd))) == NULL){
|
|
clicon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
if ((s = strstr(helptext, "\n\n")) != NULL)
|
|
*s = '\0';
|
|
yang2cli_helptext(cb, helptext);
|
|
}
|
|
if (cli_callback_generate(h, ys, cb) < 0)
|
|
goto done;
|
|
|
|
/* Look for autocli-op defined in clixon-lib.yang */
|
|
if (yang_extension_value(ys, "autocli-op", CLIXON_LIB_NS, NULL, &opext) < 0)
|
|
goto done;
|
|
if (opext != NULL && strcmp(opext, "hide") == 0){
|
|
cprintf(cb, ",hide");
|
|
}
|
|
if (opext != NULL && strcmp(opext, "hide-database-auto-completion") == 0){
|
|
cprintf(cb, ", hide-database-auto-completion");
|
|
}
|
|
cprintf(cb, ";{\n");
|
|
}
|
|
|
|
yc = NULL;
|
|
while ((yc = yn_each(ys, yc)) != NULL)
|
|
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
|
|
goto done;
|
|
if (hide == 0 && hide_oc == 0)
|
|
cprintf(cb, "%*s}\n", level*3, "");
|
|
retval = 0;
|
|
done:
|
|
if (helptext)
|
|
free(helptext);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang list statement
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] gt CLI Generate style
|
|
* @param[in] level Indentation level
|
|
* @param[out] cb Buffer where cligen code is written
|
|
*/
|
|
static int
|
|
yang2cli_list(clicon_handle h,
|
|
yang_stmt *ys,
|
|
genmodel_type gt,
|
|
int level,
|
|
cbuf *cb)
|
|
{
|
|
yang_stmt *yc;
|
|
yang_stmt *yd;
|
|
yang_stmt *yleaf;
|
|
cg_var *cvi;
|
|
char *keyname;
|
|
cvec *cvk = NULL; /* vector of index keys */
|
|
int retval = -1;
|
|
char *helptext = NULL;
|
|
char *s;
|
|
int last_key = 0;
|
|
char *opext = NULL;
|
|
|
|
cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys));
|
|
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){
|
|
if ((helptext = strdup(yang_argument_get(yd))) == NULL){
|
|
clicon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
if ((s = strstr(helptext, "\n\n")) != NULL)
|
|
*s = '\0';
|
|
yang2cli_helptext(cb, helptext);
|
|
}
|
|
/* Look for autocli-op defined in clixon-lib.yang */
|
|
if (yang_extension_value(ys, "autocli-op", CLIXON_LIB_NS, NULL, &opext) < 0)
|
|
goto done;
|
|
if (opext != NULL && strcmp(opext, "hide") == 0){
|
|
cprintf(cb, ",hide");
|
|
}
|
|
if (opext != NULL && strcmp(opext, "hide-database-auto-completion") == 0){
|
|
cprintf(cb, ",hide-database-auto-completion");
|
|
}
|
|
/* Loop over all key variables */
|
|
cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */
|
|
cvi = NULL;
|
|
/* Iterate over individual keys */
|
|
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
|
keyname = cv_string_get(cvi);
|
|
if ((yleaf = yang_find(ys, Y_LEAF, keyname)) == NULL){
|
|
clicon_err(OE_XML, 0, "List statement \"%s\" has no key leaf \"%s\"",
|
|
yang_argument_get(ys), keyname);
|
|
goto done;
|
|
}
|
|
/* Print key variable now, and skip it in loop below
|
|
* Note, only print callback on last statement
|
|
*/
|
|
last_key = cvec_next(cvk, cvi)?0:1;
|
|
if (last_key){
|
|
if (cli_callback_generate(h, ys, cb) < 0)
|
|
goto done;
|
|
cprintf(cb, ";\n");
|
|
cprintf(cb, "{\n");
|
|
}
|
|
if (yang2cli_leaf(h, yleaf,
|
|
(gt==GT_VARS||gt==GT_HIDE||gt==GT_OC_COMPRESS)?GT_NONE:gt, level+1,
|
|
last_key, 1, cb) < 0)
|
|
goto done;
|
|
}
|
|
cprintf(cb, "{\n");
|
|
yc = NULL;
|
|
while ((yc = yn_each(ys, yc)) != NULL) {
|
|
/* cvk is a cvec of strings containing variable names
|
|
yc is a leaf that may match one of the values of cvk.
|
|
*/
|
|
cvi = NULL;
|
|
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
|
keyname = cv_string_get(cvi);
|
|
if (strcmp(keyname, yang_argument_get(yc)) == 0)
|
|
break;
|
|
}
|
|
if (cvi != NULL)
|
|
continue;
|
|
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
|
|
goto done;
|
|
}
|
|
cprintf(cb, "%*s}\n", level*3, "");
|
|
if (last_key)
|
|
cprintf(cb, "%*s}\n", level*3, "");
|
|
retval = 0;
|
|
done:
|
|
if (helptext)
|
|
free(helptext);
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang choice statement
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] gt CLI Generate style
|
|
* @param[in] level Indentation level
|
|
* @param[out] cb Buffer where cligen code is written
|
|
@example
|
|
choice interface-type {
|
|
container ethernet { ... }
|
|
container fddi { ... }
|
|
}
|
|
@example.end
|
|
@Note Removes 'meta-syntax' from cli syntax. They are not shown when xml is
|
|
translated to cli. and therefore input-syntax != output syntax. Which is bad
|
|
*/
|
|
static int
|
|
yang2cli_choice(clicon_handle h,
|
|
yang_stmt *ys,
|
|
genmodel_type gt,
|
|
int level,
|
|
cbuf *cb)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yc;
|
|
|
|
yc = NULL;
|
|
while ((yc = yn_each(ys, yc)) != NULL) {
|
|
switch (yang_keyword_get(yc)){
|
|
case Y_CASE:
|
|
if (yang2cli_stmt(h, yc, gt, level+2, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_CONTAINER:
|
|
case Y_LEAF:
|
|
case Y_LEAF_LIST:
|
|
case Y_LIST:
|
|
default:
|
|
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
|
|
goto done;
|
|
break;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generate CLI code for Yang statement
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang statement
|
|
* @param[in] gt CLI Generate style
|
|
* @param[in] level Indentation level
|
|
* @param[out] cb Buffer where cligen code is written
|
|
*/
|
|
static int
|
|
yang2cli_stmt(clicon_handle h,
|
|
yang_stmt *ys,
|
|
genmodel_type gt,
|
|
int level,
|
|
cbuf *cb)
|
|
{
|
|
yang_stmt *yc;
|
|
int retval = -1;
|
|
|
|
switch (yang_keyword_get(ys)){
|
|
case Y_CONTAINER:
|
|
if (yang2cli_container(h, ys, gt, level, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_LIST:
|
|
if (yang2cli_list(h, ys, gt, level, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_CHOICE:
|
|
if (yang2cli_choice(h, ys, gt, level, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_LEAF_LIST:
|
|
case Y_LEAF:
|
|
if (yang2cli_leaf(h, ys, gt, level, 1, 0, cb) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_CASE:
|
|
case Y_SUBMODULE:
|
|
case Y_MODULE:
|
|
yc = NULL;
|
|
while ((yc = yn_each(ys, yc)) != NULL)
|
|
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
|
|
goto done;
|
|
break;
|
|
default: /* skip */
|
|
break;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Add cv with name to cvec
|
|
* @param[in] cvv Either existing or NULL
|
|
* @param[in] name Name of cv to add
|
|
* @retval cvv Either same as in cvv parameter or new
|
|
*/
|
|
static cvec*
|
|
cvec_add_name(cvec *cvv,
|
|
char *name)
|
|
{
|
|
cg_var *cv= NULL;
|
|
|
|
if (cvv == NULL &&
|
|
(cvv = cvec_new(0)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cvec_new");
|
|
return NULL;
|
|
}
|
|
if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cvec_add");
|
|
return NULL;
|
|
}
|
|
/* Filter out state data, use "nonconfig" as defined in RFC8040 4.8.1
|
|
*/
|
|
cv_name_set(cv, name);
|
|
return cvv;
|
|
}
|
|
|
|
/*! Recursive post processing of generated cligen parsetree: populate with co_cvec labels
|
|
*
|
|
* This function adds labels to the generated CLIgen tree using YANG as follows:
|
|
* (terminal entry means eg "a ;" where ; is an "empty" child of "a" representing a terminal)
|
|
* 1. Add "termfirstkeys" label on terminal entries of LIST keys, except last
|
|
* 2. Add "termlist" label on terminal entries of LIST
|
|
* 3. Add "termleaf" label on terminal entries of non-empty LEAF/LEAF_LISTs
|
|
* 4. Add "leafvar" label on nodes which are children of non-key LEAFs, eg "a <a>" -> "a <a>,leaf"
|
|
* 5. Add "nonconfig" label on nodes which have YANG "config false" as children
|
|
*
|
|
* Then, later, labels can be grouped into specific usages:
|
|
* - config: @remove:termfirstkeys,@remote:termlist,@remove:termleaf,@remove:nonconfig,
|
|
* - show: @remove:leafvar,@remove:nonconfig
|
|
* - showstate: @remove:leafvar
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] cop Parent cliegn object (if any)
|
|
* @param[in] pt CLIgen parse-tree (generated syntax)
|
|
* @param[in] i0 Offset into pt
|
|
* @param[in] y YANG node of "pt"
|
|
* @param[in] ykey Special case, If y is list, yc can be a leaf key
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
yang2cli_post(clicon_handle h,
|
|
cg_obj *cop,
|
|
parse_tree *pt,
|
|
int i0,
|
|
yang_stmt *y,
|
|
yang_stmt *ykey)
|
|
{
|
|
int retval = -1;
|
|
cg_obj *co;
|
|
int i;
|
|
yang_stmt *yc;
|
|
int yciskey;
|
|
int ycislastkey;
|
|
enum rfc_6020 ykeyword;
|
|
|
|
ykeyword = yang_keyword_get(y);
|
|
for (i = i0; i<pt_len_get(pt); i++){
|
|
if ((co = pt_vec_i_get(pt, i)) == NULL){
|
|
clicon_err(OE_YANG, 0, "Empty object in parsetreelist"); /* shouldnt happen */
|
|
goto done;
|
|
}
|
|
if (co->co_type == CO_EMPTY){
|
|
if (ykeyword == Y_LIST){
|
|
if (ykey){
|
|
/* key, list has a <cr> which is marked as "show" */
|
|
ycislastkey = 0;
|
|
yang_key_match(y, yang_argument_get(ykey), &ycislastkey);
|
|
if (!ycislastkey || (cop && cop->co_type==CO_COMMAND))
|
|
if ((co->co_cvec = cvec_add_name(co->co_cvec, "termfirstkeys")) == NULL)
|
|
goto done;
|
|
}
|
|
else{
|
|
/* key, list has a <cr> which is marked as "show" */
|
|
if ((co->co_cvec = cvec_add_name(co->co_cvec, "termlist")) == NULL)
|
|
goto done;
|
|
}
|
|
}
|
|
else if (ykeyword == Y_LEAF || ykeyword == Y_LEAF_LIST){
|
|
char *origtype = NULL;
|
|
yang_stmt *yrestype = NULL;
|
|
if (yang_type_get(y, &origtype, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0)
|
|
goto done;
|
|
if (origtype && strcmp(origtype,"empty") != 0)
|
|
if ((co->co_cvec = cvec_add_name(co->co_cvec, "termleaf")) == NULL)
|
|
goto done;
|
|
if (origtype)
|
|
free(origtype);
|
|
}
|
|
continue;
|
|
}
|
|
if ((yc = yang_find_datanode(y, co->co_command)) == NULL)
|
|
continue;
|
|
yciskey = yang_keyword_get(y) == Y_LIST && yang_key_match(y, co->co_command, NULL);
|
|
/* If leaf add "leafvar" label for non-key leafs
|
|
* Not a key leaf?
|
|
* : y is LIST &
|
|
*/
|
|
if ((yang_keyword_get(yc) == Y_LEAF) ||
|
|
yang_keyword_get(yc) == Y_LEAF_LIST){
|
|
/* add empty show
|
|
regular should have ; on last
|
|
other ; should be marked as ;
|
|
Only last key */
|
|
if (co->co_type == CO_COMMAND && !co_terminal(co, NULL)){
|
|
cg_obj *coe;
|
|
if ((coe = co_new(NULL, co)) == NULL) {
|
|
goto done;
|
|
}
|
|
coe->co_type = CO_EMPTY;
|
|
coe = co_insert(co_pt_get(co), coe);
|
|
}
|
|
/* XXX move to next recursion level ? */
|
|
int j;
|
|
cg_obj *coj;
|
|
|
|
if (!yciskey)
|
|
for (j = 0; j<pt_len_get(co_pt_get(co)); j++){
|
|
if ((coj = pt_vec_i_get(co_pt_get(co), j)) == NULL)
|
|
continue;
|
|
if (coj->co_type == CO_EMPTY)
|
|
continue;
|
|
if ((coj->co_cvec = cvec_add_name(coj->co_cvec, "leafvar")) == NULL)
|
|
goto done;
|
|
}
|
|
}
|
|
/* If state: Add nonconfig label*/
|
|
if (!yang_config(yc)){
|
|
if ((co->co_cvec = cvec_add_name(co->co_cvec, "nonconfig")) == NULL)
|
|
goto done;
|
|
}
|
|
/* If y is list and yc is key, then call with y */
|
|
if (yciskey){
|
|
if (yang2cli_post(h, co, co_pt_get(co), 0, y, yc) < 0) // note y not yc
|
|
goto done;
|
|
}
|
|
else if (yang2cli_post(h, co, co_pt_get(co), 0, yc, NULL) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Autocli generator
|
|
* Note mix of compile-time runtime
|
|
*/
|
|
int
|
|
yang2cli_yspec(clicon_handle h,
|
|
yang_stmt *yn,
|
|
char *name0,
|
|
int printgen)
|
|
{
|
|
int retval = -1;
|
|
parse_tree *pt0 = NULL;
|
|
cbuf *cb0 = NULL;
|
|
genmodel_type gt;
|
|
char *excludelist;
|
|
char **exvec = NULL;
|
|
int nexvec = 0;
|
|
int e;
|
|
yang_stmt *ym;
|
|
pt_head *ph;
|
|
size_t len0;
|
|
|
|
if ((pt0 = pt_new()) == NULL){
|
|
clicon_err(OE_UNIX, errno, "pt_new");
|
|
goto done;
|
|
}
|
|
/* List of modules that should not generate autocli */
|
|
if ((excludelist = clicon_option_str(h, "CLICON_CLI_AUTOCLI_EXCLUDE")) != NULL){
|
|
if ((exvec = clicon_strsep(excludelist, " \t", &nexvec)) == NULL)
|
|
goto done;
|
|
}
|
|
gt = clicon_cli_genmodel_type(h);
|
|
if ((cb0 = cbuf_new()) == NULL){
|
|
clicon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
/* Traverse YANG, loop through all modules and generate CLI */
|
|
ym = NULL;
|
|
while ((ym = yn_each(yn, ym)) != NULL){
|
|
/* Check if module is in exclude list */
|
|
for (e = 0; e < nexvec; e++){
|
|
if (strcmp(yang_argument_get(ym), exvec[e]) == 0)
|
|
break;
|
|
}
|
|
if (e < nexvec)
|
|
continue;
|
|
len0 = cbuf_len(cb0);
|
|
if (yang2cli_stmt(h, ym, gt, 0, cb0) < 0)
|
|
goto done;
|
|
if (len0 != cbuf_len(cb0))
|
|
clicon_debug(1, "%s Generated auto-cli for %s", __FUNCTION__, yang_argument_get(ym));
|
|
}
|
|
if (printgen)
|
|
clicon_log(LOG_NOTICE, "%s: Top-level cli-spec %s:\n%s",
|
|
__FUNCTION__, name0, cbuf_get(cb0));
|
|
else
|
|
clicon_debug(2, "%s: Top-level cli-spec %s:\n%s",
|
|
__FUNCTION__, name0, cbuf_get(cb0));
|
|
|
|
/* load top-level yangspec cli syntax (that point to modules) */
|
|
if (cligen_parse_str(cli_cligen(h), cbuf_get(cb0), "yang2cli", pt0, NULL) < 0)
|
|
goto done;
|
|
/* Resolve the expand callback functions in the generated syntax.
|
|
* This "should" only be GENERATE_EXPAND_XMLDB
|
|
* handle=NULL for global namespace, this means expand callbacks must be in
|
|
* CLICON namespace, not in a cli frontend plugin.
|
|
*/
|
|
if (cligen_expandv_str2fn(pt0, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0)
|
|
goto done;
|
|
/* Post-processing, iterate over the generated cligen parse-tree with corresponding yang
|
|
* Note cannot do it inline in yang2cli above since:
|
|
* 1. labels cannot be set on "empty"
|
|
* 2. a; <a>, fn() cannot be set properly
|
|
*/
|
|
ym = NULL;
|
|
while ((ym = yn_each(yn, ym)) != NULL){
|
|
/* Check if module is in exclude list */
|
|
for (e = 0; e < nexvec; e++){
|
|
if (strcmp(yang_argument_get(ym), exvec[e]) == 0)
|
|
break;
|
|
}
|
|
if (e < nexvec)
|
|
continue;
|
|
/* Top level find co from yang:
|
|
* XXX: top-level without namespace means check parsetree symbol against each module
|
|
* This maye break if there are two top-level symbols with the same name
|
|
*/
|
|
if (yang2cli_post(h, NULL, pt0, 0, ym, NULL) < 0)
|
|
goto done;
|
|
}
|
|
/* Append cligen tree and name it */
|
|
if ((ph = cligen_ph_add(cli_cligen(h), name0)) == NULL)
|
|
goto done;
|
|
if (cligen_ph_parsetree_set(ph, pt0) < 0)
|
|
goto done;
|
|
#if 0
|
|
if (printgen){
|
|
clicon_log(LOG_NOTICE, "%s: Top-level cli-spec %s", __FUNCTION__, name0);
|
|
pt_print1(stderr, pt0, 0);
|
|
}
|
|
#endif
|
|
retval = 0;
|
|
done:
|
|
if (exvec)
|
|
free(exvec);
|
|
if (cb0)
|
|
cbuf_free(cb0);
|
|
return retval;
|
|
}
|
|
|
|
/*! Init yang2cli
|
|
*
|
|
* Initialize CLIgen generation from YANG models.
|
|
* Nothing now
|
|
*
|
|
* @param[in] h Clixon handle
|
|
*/
|
|
int
|
|
yang2cli_init(clicon_handle h)
|
|
{
|
|
return 0;
|
|
}
|