Skip to content

Commit

Permalink
New: [CLI simple alias](clicon/cligen#112)
Browse files Browse the repository at this point in the history
  • Loading branch information
olofhagsand committed Jul 24, 2024
1 parent 9ee5544 commit aa4feee
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
204 changes: 204 additions & 0 deletions apps/cli/cli_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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> <command> [<treename>]
* <name> Name of variable containing alias name
* <command> Name of variable containing alias command
* <treename> Optional name of treename (mode), default is active pt
* @retval 0 OK
* @retval -1 Error
* @code
* alias <name:string> <command:rest>, 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: <name> <command> [<ptname>]");
goto done;
}
/* Indirectopn of name argument */
if ((cvname = cvec_i_str(argv, argi++)) == NULL){
clixon_err(OE_PLUGIN, 0, "No <name> 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 <command> 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>
* <name> Name of variable containing alias name
* @retval 0 OK
* @retval -1 Error
* @code
* aliasref <name:string> , aliasref_cb("name");
* @endcode
* @see cli_alias_add add a constant string command
* @note Not generic implementation, assumes cmd string: <cmd> <name> <rest>
*/
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; i<pt_len_get(pt); i++){
if ((co = pt_vec_i_get(pt, i)) == NULL)
continue;
if (co->co_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;
}
1 change: 1 addition & 0 deletions apps/cli/clixon_cli_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
138 changes: 138 additions & 0 deletions test/test_cli_alias.sh
Original file line number Diff line number Diff line change
@@ -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 <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
<CLICON_YANG_DIR>${YANG_INSTALLDIR}</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_DIR>$dir</CLICON_YANG_MAIN_DIR>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLISPEC_DIR>$clidir</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/run/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_YANG_LIBRARY>false</CLICON_YANG_LIBRARY>
${AUTOCLI}
</clixon-config>
EOF

cat <<EOF > $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:string>("Name of alias") <command:rest>("Alias commands"), cli_alias_add("name", "command");
aliasref("Define alias function using completion") <name:string>("Name of alias") @example, cli_aliasref_add("name");
EOF

cat <<EOF > $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 <<EOF > $fin
set table parameter x
alias newcmd show config
newcmd
EOF

new "Load and check alias"
expectpart "$(cat $fin | $clixon_cli -f $cfg)" 0 "<table xmlns=\"urn:example:clixon\"><parameter><name>x</name></parameter></table>"

cat <<EOF > $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-- "<table xmlns=\"urn:example:clixon\"><parameter><name>x</name></parameter></table>"

cat <<EOF > $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 <<EOF > $fin
aliasref newcmd show config
newcmd
EOF

new "Load and check alias reference"
expectpart "$(cat $fin | $clixon_cli -f $cfg)" 0 "<table xmlns=\"urn:example:clixon\"><parameter><name>x</name></parameter></table>"

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

0 comments on commit aa4feee

Please sign in to comment.