This provides an easy to use modular CLI that can be used to interact with OpenWrt services. It has full support for context sensitive tab completion and help. Extra modules can be provided by packages and can extend the existing node structure in any place. Signed-off-by: Felix Fietkau <nbd@nbd.name>
14 KiB
Design of the cli
module API
Structure:
The cli is organized as a set of nodes, which are ucode objects objects describing entries.
Each entry can either implement a command, or select another node, optionally with parameters.
Additionally, it contains helptext and full parameter descriptions, including everything needed for tab completion.
The initial node on startup is Root
, representing the main menu.
Simple example:
Code:
const Example = {
hello: {
help: "Example command",
args: [
{
name: "name",
type: "string",
min: 3,
max: 16,
required: true,
}
],
call: function(ctx, argv, named) {
return ctx.ok("Hello, " + argv[0]);
},
},
hello2: {
help: "Example command (named_args version)",
named_args: {
name: {
required: true,
args: {
type: "string",
min: 3,
max: 16,
}
}
},
call: function(ctx, argv, named) {
return ctx.ok("Hello, " + named.name);
},
}
};
const Root = {
example: {
help: "Example node",
select_node: "Example",
}
};
model.add_nodes({ Root, Example });
Example interaction:
root@OpenWrt:~# cli
Welcome to the OpenWrt CLI. Press '?' for help on commands/arguments
cli> example
cli example> hello
Error: Missing argument 1: name
cli example> hello foo
Hello, foo
cli example> hello2
Error: Missing argument: name
cli example> hello2 name foo2
Hello, foo2
cli example>
API documentation:
Each module is placed in /usr/share/ucode/cli/modules
on the root filesystem.
When included by the cli code, the scope contains the model
variable, which is the main cli API object. This variable is also present in the scope of the other callback functions described below.
model
methods:
model.warn(msg)
: Pass a warning to the user (similar to the ucodewarn
function).model.exception(e)
: Print an exception with stack trace.model.add_module(path)
: Load a single module frompath
model.add_modules(path)
: Load multiple modules from thepath
wildcard patternmodel.add_node(name, obj)
: Add a single node under the given namemodel.add_nodes(nodes)
: Add multiple nodes with takingname
andobj
from thenodes
object.model.add_type(name, info)
: Add a data type with validation information,model.add_types(types)
: Add multiple data types, takingname
andinfo
from thetypes
object.model.status_msg(msg)
: Print an asynchronous status message (should not be used from within a nodecall
orselect
function).
Properties of an entry
inside a node
:
Each entry must have at least help
and either call
or select_node
set.
help
: Helptext describing the commandcall: function(ctx, argv, named)
: main command handler function of the entry.this
: pointer to theentry
ctx
: call context object (see below)argv
: array of positional arguments after the command namenamed
: object of named parameters passed to the command- Return value: either
ctx.ok(msg)
for successfull calls, or the result of an error function (see below).
select_node
: (string) name of the node that this entry points to. Mutually exclusive with implementingcall
.select: function(ctx, argv, named)
: function for selecting another node.this
: pointer to the entryctx
: node context object (see below)argv
,named
: seecall
- Return value: either
ctx.set(prompt, data)
,true
, or the result of an error function (see below).
args
: array of positional arguments (see argument property description)named_args
: object of named parameters (see parameter property description)available: function(ctx)
: function indicating if the entry can be used (affects tab completion and running commands)this
: pointer to the entryctx
: node context object (see below)- Return value:
true
if available,false
otherwise.
validate: function (ctx, argv, named)
: validate command arguments- Function parameters: see
call
- Function parameters: see
Named parameter properties:
help
: Description of the named parameter's purposeargs
: Either an array of argument objects, or an object with a single argument (see below). If not set, paramter will not take any arguments, and its value will betrue
if the parameter was specified on the command line.available: function(ctx, argv, named)
: function indicating if the named parameter can be used (affects tab completion and argument validation). May depend on arguments/parameters specified before this one.multiple
(bool): indicates if an argument may be specified multiple times. Turns the value innamed
into an array.required
(bool): Parameter must be specified for the commanddefault
: default value for the parameter.allow_empty
: empty values are allowed and can be specified on the command line using-param_name
instead ofparam_name
. The value in thenamed
object will benull
in that case.
Positional argument properties:
name
: Short name of the argumenthelp
: Longer description of the argument (used in helptext/completion)type
: data type name (see below)required
(bool): Value must not be emptyvalue
: possible values for tab completion, one of:- array of objects with the following contents:
name
: value stringhelp
: help text for this value
function(ctx, argv, named)
returning the above.
- array of objects with the following contents:
- extra properties specific to the data type (see below)
Default data types:
int
: Integer value. The valid range can be specified using themin
andmax
properties.string
: String value. The valid string length can be specified using themin
andmax
properties.bool
: Boolean value. Converts"1"
and"0"
totrue
andfalse
enum
: String value that must match one entry of the list provided via thevalue
property. Case-insensitive match can be enabled using theignore_case
property.path
: Local filesystem path. When thenew_path
property is set, only match directories for a file to be created.host
: Host name or IP addressmacaddr
: MAC addressipv4
: IPv4 addressipv6
: IPv6 addresscidr4
: IPv4 address with netmask size, e.g. 192.168.1.1/24. Allowsauto
as value if theallow_auto
property is set.
call
context:
Passed as ctx
argument to entry call
functions.
ctx.data
: Object containing any data passed viactx.set()
from aselect
context.ctx.ok(msg)
: Indicates successful call, passes the messagemsg
to the user.ctx.select(...args)
: After completion, switch to a different node by running the command chain provided as function argument (only entries with.select_node
are supported).ctx.string(name, val)
: Passes a string to the caller as return value.ctx.list(name, val)
: Passes a list of values to the caller as return value.val
must be an array.ctx.table(name, val)
: Passes a table as value to the caller.val
can be an array[ column_1, column_2 ]
, where each member of the outer array describes a row in the table. It can also be an object, where the property name is the first column value, and the value the second column value.ctx.multi_table(name, val)
: Passes multiple tables to the caller. Can be an array of[ title, table ]
, or an object.- Error functions (see below)
select
context:
ctx.data
: Object containing any data passed via parentctx.set
calls.ctx.set(prompt, data)
: Modify the prompt andctx.data
for the child context. The string given inprompt
is appended to the existing prompt. The data given in thedata
object is merged with the previousctx.data
value.- Error functions (see below)
Error functions:
All error messages accept a format string in msg
, with arguments added after it.
ctx.invalid_argument(msg, ...args)
: Indicates that invalid arguments were provided.ctx.missing_argument(msg, ...args)
: Indicates that an expected argument was missing.ctx.command_failed(msg, ...args)
: Indicates that the command failed.ctx.not_found(msg, ...args)
: Indicates that a given entry was not found.ctx.unknown_error(msg, ...args)
: Indicates that the command failed for unknown or unspecified reasons.ctx.error(id, msg, ...args)
: Generic error message withid
specifying a machine readable error type string.
Editor API documentation
The editor API provides a layer of abstraction above node entries/calls in order to make it easy to edit properties of an object based on an attribute list, as well as create/destroy/show object instances using a consistent user interface.
Simple example:
import * as editor from "cli.object-editor";
let changed = false;
let data = {
things: {
foo: {
label_str: [ "bar" ],
id: 31337,
}
},
};
const thing_editor = {
change_cb: function(ctx) {
changed = true;
},
named_args: {
label: {
help: "Thing label",
attribute: "label_str",
multiple: true,
args: {
type: "string",
min: 2,
max: 16
},
},
id: {
help: "Thing id",
required: true,
args: {
type: "int",
min: 1,
},
},
},
};
const ExampleThing = editor.new(thing_editor);
let Example = {
dump: {
help: "Dump current data",
call: function(ctx, argv, named) {
return ctx.json("Data", {
changed,
data
});
},
}
};
const example_editor = {
change_cb: function(ctx) {
changed = true;
},
types: {
thing: {
node_name: "ExampleThing",
node: ExampleThing,
object: "things",
},
},
};
editor.edit_create_destroy(example_editor, Example);
const Root = {
example: {
help: "Example node",
select_node: "Example",
select: function(ctx, argv, named) {
return ctx.set(null, {
object_edit: data,
});
}
}
};
model.add_nodes({ Root, Example, ExampleThing });
Example interaction:
root@OpenWrt:~# cli
Welcome to the OpenWrt CLI. Press '?' for help on commands/arguments
cli> example
cli example> dump
Data: {
"changed": false,
"data": {
"things": {
"foo": {
"label_str": [
"bar"
],
"id": 31337
}
}
}
}
cli example> thing foo set id 1337
cli example> create thing bar id 168 label l1 label l2
Added thing 'bar'
cli example> thing bar show
Values:
id: 168
label: l1, l2
cli example> thing bar remove label 1
cli example> thing bar show
Values:
id: 168
label: l2
cli example> dump
Data: {
"changed": true,
"data": {
"things": {
"foo": {
"label_str": [
"bar"
],
"id": 1337
},
"bar": {
"id": 168,
"label_str": [
"l2"
]
}
}
}
}
cli example> destroy thing foo
Deleted thing 'foo'
cli example>
API documentation
Prelude: import * as editor from "cli.object-editor";
Object editor:
For editing an object, the following user commands are defined:
set
: Changes property valuesshow
Show all values
If properties with multiple: true
are defined, the following commands are also defined:
add
: Add values to propertiesremove
Remove specific values from properties
Variant 1 (editor-only node):
const Node = editor.new(editor_data)
Variant 2 (merge with existing entries):
let Node = {};
editor.new(editor_data, Node);
The editor code assumes that the node that selects the editor node uses ctx.set()
to set the edit
field in ctx.data
to the object being edited.
editor_data
properties:
change_cb: function(ctx)
: Called whenever a property is changed by the usernamed_args
: Parameters for editing properties (based on entrynamed_args
, see below)add
,set
,show
,remove
: Object for overriding fields of the commands defined by the editor. Primarily used to override the helptext.
Instance editor named_args
entry properties:
All entry named_args
properties are supported, but the meaning is extended slightly:
multiple
: Property array values can be added/removeddefault
: Default value when creating the objectallow_empty
: Property can be deletedrequired
: Property is mandatory in the object.
Object instance editor:
For managing object instances, the following user commands are defined:
create <type> <name> <...>
: Create a new instance. Also takes parameter values to be set on the object.destroy <type> <name>
: Delete an instance.list <type>
List all instances of a given type.
The instance editor code assumes that the node that selects the editor node uses ctx.set()
to set the object_edit
field in ctx.data
to the object being edited.
Variant 1 (editor-only node):
const Node = editor.edit_create_destroy(instance_data);
Variant 2 (merge with existing entries):
let Node = {};
editor.edit_create_destroy(instance_data, Node);
instance_data
properties:
change_cb: function(ctx)
: Called whenever an instance is added or deletedtypes
: Metadata about instances types (see below)
instance_data.types
object properties:
node_name
: name of the editor node belonging to the object instance.node
: The editor node itself.object
: Name of the type specific container object inside the object pointed to byctx.data.object_edit
.