diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a8a85d..83a91230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Users may have to change how they access the system ### Minor features +* [Strict auto completion for CLI argument expansion #163](https://github.com/clicon/clixon/issues/163) * [Convert int64, uint64 and decimal64 to string in xml to json #310](https://github.com/clicon/clixon/pull/310) * Backend ignore of SIGPIPE. This occurs if client quits unexpectedly over the UNIX socket. * This is a timing issue but occurs more frequently in large RESTCONF messgaes. diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 16049c4a..ee3ff3d6 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -128,6 +128,7 @@ cli_expand_var_generate(clicon_handle h, char *cvtypestr, int options, uint8_t fraction_digits, + int pre, cbuf *cb) { int retval = -1; @@ -142,7 +143,9 @@ cli_expand_var_generate(clicon_handle h, } if (yang2api_path_fmt(ys, 1, &api_path_fmt) < 0) goto done; - cprintf(cb, "|<%s:%s", yang_argument_get(ys), cvtypestr); + if (pre) + cprintf(cb, "|"); + 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\")>", @@ -600,6 +603,8 @@ yang2cli_var_leafref(clicon_handle h, int completionp; char *cvtypestr; int ret; + int flag; + int regular_value = 1; /* if strict-expand==0 then regular-value is false */ /* Give up: use yreferred * XXX: inline of else clause below @@ -613,21 +618,25 @@ yang2cli_var_leafref(clicon_handle h, strcmp(type, "identityref") != 0 && strcmp(type, "bits") != 0; } - - if (completionp) - cprintf(cb, "("); - if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype, - options, cvv, patterns, fraction_digits, cb) < 0) + if (yang_extension_value(ys, "strict-expand", CLIXON_AUTOCLI_NS, &flag, NULL) < 0) goto done; + regular_value = !flag; + if (completionp && regular_value) + cprintf(cb, "("); + if (regular_value) + 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, + options, fraction_digits, regular_value, cb)) < 0) goto done; if (ret == 0) yang2cli_helptext(cb, helptext); - cprintf(cb, ")"); } + if (completionp && regular_value) + cprintf(cb, ")"); retval = 0; done: return retval; @@ -694,7 +703,7 @@ yang2cli_var(clicon_handle h, goto done; if (completionp){ if ((result = cli_expand_var_generate(h, ys, cvtypestr, - options, fraction_digits,cb)) < 0) + options, fraction_digits, 1, cb)) < 0) goto done; if (result == 0) yang2cli_helptext(cb, helptext); @@ -738,7 +747,6 @@ yang2cli_var(clicon_handle h, cvv, patterns, fraction_digits, cb) < 0) goto done; } - ok: retval = 0; done: @@ -922,7 +930,6 @@ yang2cli_container(clicon_handle h, return retval; } - /*! Generate CLI code for Yang list statement * @param[in] h Clixon handle * @param[in] ys Yang statement diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 2513dc25..d45663e1 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -3770,6 +3770,8 @@ yang_extension_value(yang_stmt *ys, cbuf *cb = NULL; int ret; + if (exist) + *exist = 0; if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; diff --git a/test/config.sh.in b/test/config.sh.in index d3180aab..368610fe 100755 --- a/test/config.sh.in +++ b/test/config.sh.in @@ -68,10 +68,10 @@ CLIXON_VERSION=@CLIXON_VERSION@ # see also DATASTORE_TOP_SYMBOL DATASTORE_TOP="config" -# clixon yang revisions occuring in tests -CLIXON_AUTOCLI_REV="2021-12-05" +# clixon yang revisions occuring in tests (see eg yang/clixon/Makefile.in) +CLIXON_AUTOCLI_REV="2022-02-11" CLIXON_LIB_REV="2021-12-05" -CLIXON_CONFIG_REV="2021-11-11" +CLIXON_CONFIG_REV="2022-02-11" CLIXON_RESTCONF_REV="2021-05-20" CLIXON_EXAMPLE_REV="2020-12-01" diff --git a/test/test_autocli_strict_expand.sh b/test/test_autocli_strict_expand.sh new file mode 100755 index 00000000..a43a1d90 --- /dev/null +++ b/test/test_autocli_strict_expand.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# autocli extension strict expansion +# See https://github.com/clicon/clixon/issues/163 +# test is: add a couple of expansion alternatives, ensure cli cannot select any oother option + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf_yang.xml +fyang=$dir/example.yang +clidir=$dir/cli +if [ -d $clidir ]; then + rm -rf $clidir/* +else + mkdir $clidir +fi + +# Use yang in example + +# Generate autocli for these modules +AUTOCLI=$(autocli_config ${APPNAME}\* kw-nokey false) + +cat < $cfg + + $cfg + ietf-netconf:startup + ${YANG_INSTALLDIR} + $dir + $dir + /usr/local/lib/$APPNAME/backend + $clidir + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + $dir + false + ${AUTOCLI} + +EOF + +cat < $clidir/ex.cli +CLICON_MODE="example"; +CLICON_PROMPT="%U@%H %W> "; + +# Autocli syntax tree operations +edit @datamodel, cli_auto_edit("datamodel"); +up, cli_auto_up("datamodel"); +top, cli_auto_top("datamodel"); +set @datamodel, cli_auto_set(); +merge @datamodel, cli_auto_merge(); +create @datamodel, cli_auto_create(); +delete("Delete a configuration item") { + @datamodel, cli_auto_del(); + all("Delete whole candidate configuration"), delete_all("candidate"); +} +show("Show a particular state of the system") + configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "xml", false, false); +EOF + +# Yang specs must be here first for backend. But then the specs are changed but just for CLI +# Annotate original Yang spec example directly +# First annotate /table/parameter +# Had a problem with unknown in grouping -> test uses uses/grouping +cat < $fyang +module example { + namespace "urn:example:clixon"; + prefix ex; + import clixon-autocli{ + prefix autocli; + } + container table{ + list parameter{ + key name; + leaf name{ + autocli:strict-expand; + type string; + } + leaf value{ + type string; + } + leaf value2{ + autocli:strict-expand; + type string; + } + leaf-list value3{ + autocli:strict-expand; + type string; + } + } + } +} +EOF + +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg +fi + +new "wait backend" +wait_backend + +# The list +new "Add three list options" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOmergeopta42optb43optc44
]]>]]>" "^]]>]]>$" + +new "netconf commit" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + +new "show config" +expectpart "$($clixon_cli -1 -f $cfg show conf)" 0 "opta42optb43optc44
" + +new "Check query" +expectpart "$(echo "set table parameter ?" | $clixon_cli -f $cfg 2>&1)" 0 opta optb optc --not-- '' + +new "Check completion" +expectpart "$(echo "set table parameter " | $clixon_cli -f $cfg 2>&1)" 0 opta optb optc --not-- '' + +# Leafs +new "Add leafs" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOmergeopta424344
]]>]]>" "^]]>]]>$" + +new "netconf commit" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + +new "show config leafs" +expectpart "$($clixon_cli -1 -f $cfg show conf)" 0 "opta424344optb43optc44
" + +new "Check query value" +expectpart "$(echo "set table parameter opta value ?" | $clixon_cli -f $cfg 2>&1)" 0 42 '' + +new "Check query value2" +expectpart "$(echo "set table parameter opta value2 ?" | $clixon_cli -f $cfg 2>&1)" 0 43 --not-- '' + +new "Check query value3" +expectpart "$(echo "set table parameter opta value3 ?" | $clixon_cli -f $cfg 2>&1)" 0 44 --not-- '' + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +rm -rf $dir + +new "endtest" +endtest diff --git a/test/test_type.sh b/test/test_type.sh index 037f6197..2e6fc5cc 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -17,6 +17,9 @@ fyang=$dir/type.yang fyang2=$dir/example2.yang fyang3=$dir/example3.yang +# Generate autocli for these modules +AUTOCLI=$(autocli_config ${APPNAME}\* kw-nokey false) + # transitive type, exists in fyang3, referenced from fyang2, but not declared in fyang cat < $fyang3 module example3{ @@ -228,6 +231,7 @@ function testrun(){ /usr/local/var/$APPNAME $dbcache $format + ${AUTOCLI} EOF diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index 54837861..6947c57c 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -41,12 +41,13 @@ datarootdir = @datarootdir@ # Also mandatory standard YANGs (see ../mandatory) YANG_INSTALLDIR = @YANG_INSTALLDIR@ +# Note: mirror these to test/config.sh.in YANGSPECS = clixon-config@2022-02-11.yang # 5.6 YANGSPECS += clixon-lib@2021-12-05.yang # 5.5 YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang YANGSPECS += clixon-restconf@2021-05-20.yang # 5.2 -YANGSPECS += clixon-autocli@2021-12-05.yang # 5.5 +YANGSPECS += clixon-autocli@2022-02-11.yang # 5.6 all: diff --git a/yang/clixon/clixon-autocli@2022-02-11.yang b/yang/clixon/clixon-autocli@2022-02-11.yang new file mode 100644 index 00000000..be981565 --- /dev/null +++ b/yang/clixon/clixon-autocli@2022-02-11.yang @@ -0,0 +1,247 @@ +module clixon-autocli{ + yang-version 1.1; + namespace "http://clicon.org/autocli"; + prefix autocli; + + organization + "Clicon / Clixon"; + + contact + "Olof Hagsand "; + + description + "Clixon CLIgen specification declarations, including autocli. + Design inspired by ietf-netconf-acm.yang + + ***** BEGIN LICENSE BLOCK ***** + 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 *****"; + + revision 2022-02-11 { + description + "Initial version + Released in Clixon 5.6"; + } + revision 2021-12-05 { + description + "Initial version + Released in Clixon 5.5"; + } + extension hide { + description + "Modify the autocli by hiding the command associated with a YANG node and its + sub-commands. + The command is active but not shown by ? or TAB. In other words, it hides the + auto-completion of commands"; + } + extension hide-show { + description + "Modify the autocli by hiding the command associated with a YANG node and its + sub-commands in CLI show commands."; + } + extension strict-expand { + description + "Modify the autocli by only showing exactly the expanded values of a variable. + It should not be possible to add a new value that is not in the expanded list."; + } + typedef autocli-op { + description + "Autocli rule-type operation, each rule use different fields as + described in the individual enums below."; + type enumeration { + enum enable { + description + "Include a complete subtree to rendering of autocli. + Example: + false + + wifi + enable + openconfig-wifi + + Only on module-level and if module-default is false, + Rule fields used: module-name"; + } + enum compress { + description + "Skip a keyword from a command. + Keep the command, only make it shorter by omitting a part. + Example: compress containers if single list child + + container compress + compress + container + list + + Rule fields used: + module-name, yang-keyword, schema-nodeid, yang-keyword-child, extension"; + } + enum edit-mode { + description + "Autocli CLI edit modes for YANG symbols. + For example, + edit interface eth0 + enters a new mode with local context."; + } + } + } + typedef list-keyword-type { + description + "Autocli CLI keyword behaviour in YANG lists. + With 'keyword' is meant CLIgen 'constants' rather than 'variables'. + Assume a YANG LIST: list a{ key x; leaf x; leaf y;} and how to generate + the autocli"; + type enumeration { + enum kw-none{ + description "No extra keywords, only variables: a "; + } + enum kw-nokey{ + description "Keywords on non-key variables: a y "; + } + enum kw-all{ + description "Keywords on all variables: a x y "; + } + } + } + typedef yang-keywords { + type bits { + bit list; + bit listall{ /* NYI */ + description + "Variant of list encompassing all list entries, not just an instance"; + } + bit container; + bit leaf; /* Also leaf-list (NYI) */ + } + } + grouping clixon-autocli{ + /* options */ + leaf module-default { + description + "Include YANG modules for generation of autocli. + If true, all modules with a top-level datanode are generated, ie + they get a top-level entry in the @basemodel tree. + If false, you need to explicitly enable modules for autocli generation + using 'enable' rules"; + type boolean; + default true; + } + leaf list-keyword-default { + description + "Autocli CLI keyword behaviour in YANG lists."; + type list-keyword-type; + default kw-nokey; + } + leaf treeref-state-default { + description + "If 'true', generate CLI from YANG state/non-config statements as well, not only config data. + Many specs have very large state parts, for example openconfig has ca 10 times + larger state than config parts, see for example openconfig-isis.yang."; + type boolean; + default false; + } + leaf edit-mode-default { + description + "Open automatic edit-modes for some YANG keywords and do not allow others. + A CLI edit mode opens a carriage-return option and changes the context to be + in that local context. + For example: + cli> interfaces interface e0 + eth0> + Default is to generate edit-modes for all containers and lists."; + type yang-keywords; + default "list container"; + } + leaf completion-default { + description + "Generate code for CLI completion of existing db symbols. + That is, check existing configure database for completion options. + This is normally always enabled."; + type boolean; + default true; + } + /* rules */ + list rule { + description + "Represents a modification rule of a clixon clispec."; + key name; + leaf name { + description + "Arbitrary name assigned for the rule, must be unique"; + type string; + } + leaf description { + description + "Rule description"; + type string; + } + leaf operation { + description "Rule operation"; + type autocli-op; + } + leaf module-name { + description + "Name of the module associated with this rule. + Wildchars '*' and '?' can be used (glob pattern). + Revision and yang suffix are omitted + Example: 'openconfig-*'"; + type string; + } + leaf yang-keyword { + description + "If present identifes a YANG keyword which the rule applies to + Example: 'container' + "; + type string; + } + leaf schema-nodeid { + description + "path in the form of // or just a single identifying a YANG + schema-node identifier as defined in RFC 7950 Sec 6.5 + Example: 'config', '/interfaces/interface'"; + type string; + } + leaf yang-keyword-child { + description + "The YANG statement has a single child, and the yang type of the child is the + value of this option + A (maybe too) specific property to cover openconfig compressions + as defined here: + https://github.com/openconfig/ygot/blob/master/docs/design.md#openconfig-path-compression"; + type string; + } + leaf extension { + /* Consider making this a container with name/module/value instead */ + description + "The extension is set either in the node itself, or in this module + Extension prefix must be set + Example: oc-ext:openconfig-version"; + type string; + } + } + } +}