From aa4feee03ed90f416040f68abe6b07729e064037 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 23 Jul 2024 12:50:55 +0200 Subject: [PATCH] New: [CLI simple alias](https://github.com/clicon/cligen/issues/112) --- CHANGELOG.md | 2 + apps/cli/cli_common.c | 204 ++++++++++++++++++++++++++++++++++++++ apps/cli/clixon_cli_api.h | 1 + test/test_cli_alias.sh | 138 ++++++++++++++++++++++++++ 4 files changed, 345 insertions(+) create mode 100755 test/test_cli_alias.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index ebbfbfd0..784546ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ Expected: October 2024 ### Features +* New: [CLI simple alias](https://github.com/clicon/cligen/issues/112) + * See: https://clixon-docs.readthedocs.io/en/latest/cli.html#cli-aliases * List pagination: Added where, sort-by and direction parameter for configured data * New `clixon-lib@2024-04-01.yang` revision - Added: list-pagination-partial-state extension diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 7ea13f40..f2293b26 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -2086,3 +2086,207 @@ cli_process_control(clixon_handle h, cbuf_free(cb); return retval; } + +/*! Alias function + * + * @param[in] h Clixon handle + * @param[in] cvv Vector of variables: function parameters + * @param[in] argv Arguments given at the callback: this is the command to be executed + * @retval 0 OK + * @retval -1 Error + * @see cliread_eval original fn + */ +int +cli_alias_call(cligen_handle h, + cvec *cvv, + cvec *argv) +{ + return cligen_alias_call(cli_cligen(h), cvv, argv); +} + +/*! Define an alias CLI command, insert in top-level of parse-tree + * + * @param[in] h Clixon handle + * @param[in] cvv Vector of variables: function parameters + * @param[in] argv Arguments given at the callback: [] + * Name of variable containing alias name + * Name of variable containing alias command + * Optional name of treename (mode), default is active pt + * @retval 0 OK + * @retval -1 Error + * @code + * alias , alias_cb("name", "command"); + * @endcode + * @see cli_aliasref_add add using a tree reference + */ +int +cli_alias_add(clixon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + char *cvname; + char *cvcommand; + cg_var *cv; + char *name; + char *command; + char *treename = NULL; + int argi = 0; + + if (argv == NULL || cvec_len(argv) < 2 || cvec_len(argv) > 3){ + clixon_err(OE_PLUGIN, EINVAL, "Expected arguments: []"); + goto done; + } + /* Indirectopn of name argument */ + if ((cvname = cvec_i_str(argv, argi++)) == NULL){ + clixon_err(OE_PLUGIN, 0, "No argument"); + goto done; + } + if ((cv = cvec_find_var(cvv, cvname)) == NULL){ + clixon_err(OE_PLUGIN, 0, "Expected name argument"); + goto done; + } + else + name = cv_string_get(cv); + /* Indirection of command argument */ + if ((cvcommand = cvec_i_str(argv, argi++)) == NULL){ + clixon_err(OE_PLUGIN, 0, "No argument"); + goto done; + } + if ((cv = cvec_find_var(cvv, cvcommand)) == NULL){ + clixon_err(OE_PLUGIN, 0, "Expected command argument"); + goto done; + } + else + command = cv_string_get(cv); + /* Optional parse-tree name argument */ + if (cvec_len(argv) > argi) + treename = cvec_i_str(argv, argi++); + if (cligen_alias_add(cli_cligen(h), treename, name, NULL, command, cli_alias_call) < 0){ + clixon_err(OE_PLUGIN, errno, "Error adding alias %s", name); + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Define an alias CLI command via a tree refernce, insert in top-level of parse-tree + * + * @param[in] h Clixon handle + * @param[in] cvv Vector of variables: function parameters + * @param[in] argv Arguments given at the callback: + * Name of variable containing alias name + * @retval 0 OK + * @retval -1 Error + * @code + * aliasref , aliasref_cb("name"); + * @endcode + * @see cli_alias_add add a constant string command + * @note Not generic implementation, assumes cmd string: + */ +int +cli_aliasref_add(clixon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + cg_var *cv; + char *name; + char *command =NULL; + char *treename = NULL; + + /* argv handling is ad-hoc, unkown argv list */ + if ((cv = cvec_find_var(cvv, "name")) == NULL){ + clixon_err(OE_PLUGIN, 0, "Expected name argument"); + goto done; + } + else + name = cv_string_get(cv); + if ((command = cvec_i_str(cvv, 0)) == NULL){ + clixon_err(OE_PLUGIN, 0, "Expected cvv[0]"); + goto done; + } + /* skip fisrt two elements */ + if ((command = index(command, ' ')) == NULL) { + clixon_err(OE_PLUGIN, 0, "No command string"); + goto done; + } + command++; + if ((command = index(command, ' ')) == NULL) { + clixon_err(OE_PLUGIN, 0, "No command string"); + goto done; + } + command++; + if (cligen_alias_add(cli_cligen(h), treename, name, NULL, command, cli_alias_call) < 0){ + clixon_err(OE_PLUGIN, errno, "Error adding alias %s", name); + goto done; + } + retval = 0; + done: + return retval; +} + +int +cli_alias_show(clixon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + pt_head *ph; + char *phname = NULL; + cligen_handle ch = cli_cligen(h); + int i; + cg_obj *co; + parse_tree *pt; + cg_callback *cc; + cg_var *cv; + cbuf *cb = NULL; + + if ((cb = cbuf_new()) == NULL){ + clixon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (phname == NULL) { + if ((ph = cligen_ph_active_get(ch)) == NULL){ + errno = ENOENT; + goto done; + } + } + else { + if ((ph = cligen_ph_find(ch, phname)) == NULL) { + errno = ENOENT; + goto done; + } + } + if ((pt = cligen_ph_parsetree_get(ph)) == NULL){ + errno = ENOENT; + goto done; + } + for (i=0; ico_type == CO_EMPTY) + continue; + if (co_flags_get(co, CO_FLAGS_ALIAS) == 0x0) + continue; + cligen_output(stdout, "%s:", co->co_command); + if ((cc = co->co_callbacks) != NULL && cc->cc_cvec){ + cbuf_reset(cb); + cv = NULL; + while ((cv = cvec_each(cc->cc_cvec, cv)) != NULL) { + cprintf(cb, " "); + if (cv2cbuf(cv, cb) < 0){ + clixon_err(OE_UNIX, errno, "cv2cbuf"); + goto done; + } + } + cligen_output(stdout, "%s\n", cbuf_get(cb)); + } + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h index 4bfa9f62..f079962d 100644 --- a/apps/cli/clixon_cli_api.h +++ b/apps/cli/clixon_cli_api.h @@ -117,6 +117,7 @@ int cli_help(clixon_handle h, cvec *vars, cvec *argv); cvec *cvec_append(cvec *cvv0, cvec *cvv1); int cvec_concat_cb(cvec *cvv, cbuf *cb); int cli_process_control(clixon_handle h, cvec *vars, cvec *argv); +int cli_alias_cb(clixon_handle h, cvec *cvv, cvec *argv); /* In cli_show.c */ int expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv, diff --git a/test/test_cli_alias.sh b/test/test_cli_alias.sh new file mode 100755 index 00000000..162cc0fe --- /dev/null +++ b/test/test_cli_alias.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# Tests for CLI simple alias +# 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 +fin=$dir/alias.cli + +if [ -d $clidir ]; then + rm -rf $clidir/* +else + mkdir $clidir +fi + +# 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/run/$APPNAME.pidfile + $dir + false + ${AUTOCLI} + +EOF + +cat < $clidir/ex.cli +CLICON_MODE="example"; +CLICON_PROMPT="%U@%H %W> "; + +set @datamodel, cli_auto_set(); +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_show_auto_mode("candidate", "xml", false, false);{ + json, cli_show_auto_mode("candidate", "json", false, false); + } + alias, cli_alias_show(); +} +alias("Define alias function") ("Name of alias") ("Alias commands"), cli_alias_add("name", "command"); +aliasref("Define alias function using completion") ("Name of alias") @example, cli_aliasref_add("name"); +EOF + +cat < $fyang +module example { + namespace "urn:example:clixon"; + prefix ex; + container table{ + list parameter{ + key name; + leaf name{ + 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 "Set alias" +expectpart "$($clixon_cli -1f $cfg alias newcmd show config)" 0 "^$" + +cat < $fin +set table parameter x +alias newcmd show config +newcmd +EOF + +new "Load and check alias" +expectpart "$(cat $fin | $clixon_cli -f $cfg)" 0 "x
" + +cat < $fin +alias newcmd show config +alias newcmd show config json +newcmd +EOF + +new "Replace alias" +expectpart "$(cat $fin | $clixon_cli -f $cfg)" 0 '{"example:table":{"parameter":\[{"name":"x"}\]}}' --not-- "x
" + +cat < $fin +alias cmd1 show config +alias cmd2 show config json +show alias +EOF + +new "show alias" +expectpart "$(cat $fin | $clixon_cli -f $cfg)" 0 "cmd1: show config" "cmd2: show config json" + +cat < $fin +aliasref newcmd show config +newcmd +EOF + +new "Load and check alias reference" +expectpart "$(cat $fin | $clixon_cli -f $cfg)" 0 "x
" + +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