cli: Application command line support¶
This module provides a method for parsing text commands
and calling the functions that implement them.
First, commands are registered
with a description of the arguments they take,
and with a function that implements the command.
Later, a Command
instance is used to parse
a command line, and optionally execute the command.
Incomplete command lines are supported,
so possible commands can be suggested.
In addition to registering command functions, there is a separate mechanism to support textual Command Aliases.
Text Commands¶
Synopsis:
command_name rv1 rv2 [ov1 [ov2]] [kn1 kv1] [kn2 kv2]
Text commands are composed of a command name, which can be multiple words,
followed by required positional arguments, rvX,
optional positional arguments, ovX,
and keyword arguments with a value, knX kvX.
Each argument has an associated Python argument name
(for keyword arguments it is the keyword, knX).
rvX, ovX, and kvX are the type-checked values.
If the argument name is the same as a Python keyword,
then an underscore appended to it to form the Python argument name.
The names of the optional arguments are used to
let them be given as keyword arguments as well.
Multiple value arguments are separated by commas
and the commas may be followed by whitespace.
Depending on the type of an argument, e.g., a color name,
whitespace can also appear within an argument value.
Argument values may be quoted with double quotes.
And in quoted text, Python’s textual escape sequences are recognized,
e.g., \N{LATIN CAPITAL LETTER A WITH RING ABOVE}
for the ångström sign,
Å.
Words in the command name may be truncated and are automatically completed to the first registered command with the given prefix. Likewise for keyword arguments.
Keywords are case sensitive, and are expected to be all lowercase.
Underscores are elided, but are documented as mixed case.
For example, a bg_color
keyword would be documented as bgColor
.
Registering Commands¶
To add a command, register()
the command name,
a description of the arguments it takes,
and the function to call that implements the command.
Command registration can be partially delayed to avoid importing
the command description and function until needed.
See register()
and delay_registration()
for details.
The description is either an instance of the Command Description class,
CmdDesc
, or a tuple with the arguments to the initializer.
The CmdDesc initializer takes tuples describing the required, optional,
and keyword arguments.
Each tuple contains tuples with the argument name and a type annotation
(see below).
Postconditions (see below) can be given too.
Command Functions¶
The command function arguments are expected to start with a session
argument. The rest of the arguments are assembled as keyword arguments,
as built from the command line and the command description.
The initial session
argument to a command function
is not part of the command description.
Type Annotations¶
There are many standard type notations and they should be reused as much as possible:
Type | Annotation |
---|---|
bool |
BoolArg |
float |
FloatArg |
int |
IntArg |
str |
StringArg |
tuple of 3 bool |
Bool3Arg |
tuple of 3 float |
Float3Arg |
tuple of 3 int |
Int3Arg |
list of float |
FloatsArg |
list of int |
IntsArg |
There is one special annotation: RestOfLine
that consumes
the rest of the command line as text.
Annotations are used to parse text and convert it to the appropriate type. Annotations can be extended with various specializers:
Specializer | Example |
---|---|
Bounded |
Bounded(FloatArg, 0.0, 100.0) |
ListOf |
ListOf(FloatArg)
a.k.a., FloatsArg |
SetOf |
SetOf(IntArg) |
TupleOf |
TupleOf(FloatArg, 3)
a.k.a., Float3Arg |
Or |
Or(FloatArg, StringArg) discouraged |
EnumOf |
enumerated values |
Creating Your Own Type Annotation¶
Annotations perform several functions: (1) to convert text to a value of the appropriate type, and (2) to give reasonable error messages.
See the Annotation
documentation for details.
Example¶
Here is a simple example:
import from chimera.core.commands import cli, errors
@register("echo", cli.CmdDesc(optional=[('text', cli.RestOfLine)]))
def echo(session, text=''):
print(text)
...
command = cli.Command(session)
command.parse_text(text, final=True)
try:
status = command.execute()
if status:
print(status)
except errors.UserError as err:
print(err, file=sys.stderr)
Command Aliases¶
-
class
Aggregate
(annotation, min_size=None, max_size=None, name=None, prefix=None)¶ Bases:
chimera.core.commands.cli.Annotation
Common class for collections of values.
- Aggregate(annotation, constructor, add_to, min_size=None, max_size=None,
- name=None) -> annotation
Parameters: - annotation – annotation for values in the collection.
- min_size – minimum size of collection, default None.
- max_size – maximum size of collection, default None.
- name – optionally override name in error messages.
This class is typically used via
ListOf
,SetOf
, orTupleOf
. The comma separator for aggregate values is handled by theCommand
class, so parsing is delegated to the underlying annotation.Subclasses need to set the constructor attribute and replace the add_to method.
-
add_to
(container, element)¶ Add to add an element to the container
Parameters: - container – the container to add elements to
- element – the element to add to the container
Returns: None for mutable containers, or a new container if immutable.
-
class
Alias
(text)¶ Bases:
object
alias a command
Returns a callable unnamed command alias.
Parameters: text – parameterized command text The text is scanned for $n, where n is the n-th argument, $* for the rest of the line, and $$ for a single $.
-
class
Annotation
¶ Bases:
object
Base class for all annotations
Each annotation should have the following attributes:
-
name
¶ Set to textual description of the annotation, including the leading article, e.g., “a truth value”.
-
static
parse
(text, session)¶ Convert text to appropriate type.
Parameters: - text – command line text to parse
- session – for session-dependent data types
Returns: 3-tuple with the converted value, consumed text (possibly altered with expanded abbreviations), and the remaining unconsumed text
Raises ValueError: if unable to convert text
The leading space in text must already be removed. It is up to the particular annotation to support abbreviations.
-
url
= None¶ URL for help information
-
-
exception
AnnotationError
(message, offset=None)¶ Bases:
chimera.core.errors.UserError
,ValueError
Error, with optional offset, in annotation
-
class
AtomicStructuresArg
¶ Bases:
chimera.core.commands.cli.Annotation
Parse command atomic structures specifier
-
class
AtomsArg
¶ Bases:
chimera.core.commands.cli.Annotation
Parse command atoms specifier
-
class
AxisArg
¶ Bases:
chimera.core.commands.cli.Annotation
Annotation for axis vector that can be 3 floats or “x”, or “y”, or “z”
-
class
BoolArg
¶ Bases:
chimera.core.commands.cli.Annotation
Annotation for boolean literals
-
class
Bounded
(annotation, min=None, max=None, name=None)¶ Bases:
chimera.core.commands.cli.Annotation
Support bounded numerical values
Bounded(annotation, min=None, max=None, name=None) -> an Annotation
Parameters: - annotation – numerical annotation
- min – optional lower bound
- max – optional upper bound
- name – optional explicit name for annotation
-
class
CmdDesc
(required=(), optional=(), keyword=(), postconditions=(), required_arguments=(), url=None, synopsis=None, official=False)¶ Bases:
object
Describe command arguments.
Parameters: - required – required positional arguments sequence
- optional – optional positional arguments sequence
- keyword – keyword arguments sequence
- required_arguments – sequence of argument names that must be given
- url – URL to help page
- synopsis – one line description
- official – True if officially supported command
Each :param required:, :param optional:, :param keyword: sequence contains tuples with the argument name and a type annotation. The command line parser uses the :param optional: argument names as additional keyword arguments. :param required_arguments: are for Python function arguments that don’t have default values, but should be given on the command line (typically keyword arguments, but could be used for syntactically optional arguments).
-
copy
()¶ Return a copy suitable for use with another function.
-
class
Command
(session, text='', final=False, _used_aliases=None)¶ Bases:
object
Keep track of (partially) typed command with possible completions
Parameters: - text – the command text
- final – true if text is the complete command line (final version).
-
error_check
()¶ Error check results of calling parse_text
Raises UserError: if parsing error is found Separate error checking logic from execute() so it may be done separately
-
execute
(_used_aliases=None)¶ If command is valid, execute it with given session.
-
parse_text
(text, final=False, no_aliases=False, _used_aliases=None)¶ Parse text into function and arguments
Parameters: - text – The text to be parsed.
- final – True if last version of command text.
- no_aliases – True if aliases should not be considered.
May be called multiple times. There are a couple side effects:
- The automatically completed text is put in self.current_text.
- Possible completions are in self.completions.
- The prefix of the completions is in self.completion_prefix.
-
class
DottedTupleOf
(annotation, min_size=None, max_size=None, name=None, prefix=None)¶ Bases:
chimera.core.commands.cli.Aggregate
Annotation for dot-separated lists of a single type
DottedListOf(annotation, min_size=None, max_size=None) -> annotation
-
constructor
¶ alias of
tuple
-
-
class
EmptyArg
¶ Bases:
chimera.core.commands.cli.Annotation
Annotation for optionally missing ‘required’ argument
-
class
EnumOf
(values, ids=None, name=None)¶ Bases:
chimera.core.commands.cli.Annotation
Support enumerated types
EnumOf(values, ids=None, name=None) -> an Annotation
Parameters: - values – sequence of values
- ids – optional sequence of identifiers
- name – optional explicit name for annotation
If the ids are given, then there must be one for each and every value, otherwise the values are used as the identifiers. The identifiers must all be strings.
-
class
FloatArg
¶ Bases:
chimera.core.commands.cli.Annotation
Annotation for floating point literals
-
class
IntArg
¶ Bases:
chimera.core.commands.cli.Annotation
Annotation for integer literals
-
class
Limited
(name, min=None, max=None)¶ Bases:
chimera.core.commands.cli.Postcondition
Bounded numerical values postcondition
Limited(name, min=None, max=None) -> Postcondition
Parameters: - name – name of argument to check
- min – optional inclusive lower bound
- max – optional inclusive upper bound
If possible, use the Bounded annotation because the location of the error is the beginning of the argument, not the end of the line.
-
class
ListOf
(annotation, min_size=None, max_size=None, name=None, prefix=None)¶ Bases:
chimera.core.commands.cli.Aggregate
Annotation for lists of a single type
ListOf(annotation, min_size=None, max_size=None) -> annotation
-
constructor
¶ alias of
list
-
-
class
NoArg
¶ Bases:
chimera.core.commands.cli.Annotation
Annotation for keyword whose presence indicates True
-
class
Or
(*annotations, name=None)¶ Bases:
chimera.core.commands.cli.Annotation
Support two or more alternative annotations
Or(annotation, annotation [, annotation]*, name=None) -> an Annotation
Parameters: name – optional explicit name for annotation
-
class
Postcondition
¶ Bases:
object
Base class for postconditions
-
check
(kw_args)¶ Assert arguments match postcondition
Parameters: kw_args – dictionary of arguments that will be passed to command callback function. Returns: True if arguments are consistent
-
error_message
()¶ Appropriate error message if check fails.
Returns: error message
-
-
class
SameSize
(name1, name2)¶ Bases:
chimera.core.commands.cli.Postcondition
Postcondition check for same size arguments
SameSize(name1, name2) -> a SameSize object
Parameters: - name1 – name of first argument to check
- name2 – name of second argument to check
-
class
SetOf
(annotation, min_size=None, max_size=None, name=None, prefix=None)¶ Bases:
chimera.core.commands.cli.Aggregate
Annotation for sets of a single type
SetOf(annotation, min_size=None, max_size=None) -> annotation
-
constructor
¶ alias of
set
-
-
class
StringArg
¶ Bases:
chimera.core.commands.cli.Annotation
Annotation for text (a word or quoted)
-
class
TupleOf
(annotation, size, name=None)¶ Bases:
chimera.core.commands.cli.Aggregate
Annotation for tuples of a single type
TupleOf(annotation, size) -> annotation
-
constructor
¶ alias of
tuple
-
-
add_keyword_arguments
(name, kw_info)¶ Make known additional keyword argument(s) for a command
Parameters: - name – the name of the command (must not be an alias)
- kw_info – { keyword: annotation }
-
alias
(session, name='', text='')¶ Create command alias
Parameters: - name – optional name of the alias
- text – optional text of the alias
If the alias name is not given, then a text list of all the aliases is returned. If alias text is not given, the text of the named alias is returned. If both arguments are given, then a new alias is made.
-
command_function
(name, no_aliases=False)¶ Return callable for given command name
Parameters: - name – the name of the command
- no_aliases – True if aliases should not be considered.
Returns: the callable that implements the command
-
command_url
(name, no_aliases=False)¶ Return help URL for given command name
Parameters: - name – the name of the command
- no_aliases – True if aliases should not be considered.
Returns: the URL registered with the command
-
commas
(text_seq, conjunction=' or', suffix='s')¶ Return comma separated list of words and suffix
Parameters: - text_seq – a sequence of text strings
- conjunction – a word with a leading space
- suffix – suffix to return if more than one string
-
delay_registration
(name, proxy_function, logger=None)¶ delay registering a named command until needed
Parameters: - proxy_function – the function to call if command is used
- logger – optional logger
The proxy function should explicitly reregister the command or register subcommands and return nothing.
Example:
from chimera.core.commands import cli def lazy_reg(): import module cli.register('cmd subcmd1', module.subcmd1_desc, module.subcmd1) cli.register('cmd subcmd2', module.subcmd2_desc, module.subcmd2) cli.delay_registration('cmd', lazy_reg)
-
deregister
(name)¶ Remove existing command
If the command was an alias, the previous version is restored
-
discard_article
(text)¶ remove leading article from text
-
html_usage
(name, no_aliases=False)¶ Return usage string in HTML for given command name
Parameters: - name – the name of the command
- no_aliases – True if aliases should not be considered.
Returns: a HTML usage string for the command
-
is_python_keyword
()¶ x.__contains__(y) <==> y in x.
-
next_token
(text)¶ Extract next token from given text.
Parameters: text – text to parse without leading whitespace Returns: a 3-tuple of first argument in text, the actual text used, and the rest of the text. Tokens may be quoted, in which case the text between the quotes is returned.
-
register
(name, cmd_desc=(), function=None, logger=None)¶ register function that implements command
Parameters: - name – the name of the command and may include spaces.
- cmd_desc – information about the command, either an
instance of
CmdDesc
, or the tuple with CmdDesc parameters. - function – the callback function.
- logger – optional logger
If the function is None, then it assumed that
register()
is being used as a decorator.To delay introspecting the function until it is actually used, register using the
delay_registration()
function.For autocompletion, the first command registered with a given prefix wins. Registering a command that is a prefix of an existing command is an error since it breaks backwards compatibility.
-
registered_commands
(multiword=False)¶ Return a list of the currently registered commands
-
unalias
(session, name)¶ Remove command alias
Parameters: name – name of the alias
-
unescape
(text)¶ Replace backslash escape sequences with actual character.
Parameters: text – the input text Returns: the processed text Follows Python’s string literal syntax for escape sequences.
-
unescape_with_index_map
(text)¶ Replace backslash escape sequences with actual character.
Parameters: text – the input text Returns: the processed text and index map from processed to input text Follows Python’s string literal syntax for escape sequences.
-
usage
(name, no_aliases=False)¶ Return usage string for given command name
Parameters: - name – the name of the command
- no_aliases – True if aliases should not be considered.
Returns: a usage string for the command