cliff(1)
| CLIFF(1) | cliff | CLIFF(1) |
NAME
cliff - cliff Documentation
cliff is a framework for building command line programs. It uses plugins to define sub-commands, output formatters, and other extensions.
INSTALLATION
Python Versions
cliff is being developed under Python 3.
Basic Installation
cliff should be installed into the same site-packages area where the application and extensions are installed (either a virtualenv or the global site-packages). You may need administrative privileges to do that. The easiest way to install it is using pip. For example:
$ pip install cliff
Source Code
The source is hosted on OpenDev: https://opendev.org/openstack/cliff
Reporting Bugs
Please report bugs through the storyboard: https://storyboard.openstack.org/#!/project/openstack/cliff
USING CLIFF
Introduction
The cliff framework is meant to be used to create multi-level commands such as subversion and git, where the main program handles some basic argument parsing and then invokes a sub-command to do the work.
Command Plugins
Cliff takes advantage of Python's ability to load code dynamically to allow the sub-commands of a main program to be implemented, packaged, and distributed separately from the main program. This organization provides a unified view of the command for users, while giving developers the opportunity organize source code in any way they see fit.
Cliff Objects
Cliff is organized around five objects that are combined to create a useful command line program.
The Application
An cliff.app.App is the main program that you run from the shell command prompt. It is responsible for global operations that apply to all of the commands, such as configuring logging and setting up I/O streams.
The CommandManager
The cliff.commandmanager.CommandManager knows how to load individual command plugins. The default implementation uses entry points but any mechanism for loading commands can be used by replacing the default CommandManager when instantiating an App.
The Command
The cliff.command.Command class is where the real work happens. The rest of the framework is present to help the user discover the command plugins and invoke them, and to provide runtime support for those plugins. Each Command subclass is responsible for taking action based on instructions from the user. It defines its own local argument parser (usually using argparse) and a take_action() method that does the appropriate work.
The CommandHook
The cliff.hooks.CommandHook class can extend a Command by modifying the command line arguments available, for example to add options used by a driver. Each CommandHook subclass must implement the full hook API, defined by the base class. Extensions should be registered using an entry point namespace based on the application namespace and the command name:
application_namespace + '.' + command_name.replace(' ', '_')
The Interactive Application
The main program uses an cliff.interactive.InteractiveApp instance to provide a command-shell mode in which the user can type multiple commands before the program exits. Many cliff-based applications will be able to use the default implementation of InteractiveApp without subclassing it.
Exploring the Demo App
The cliff source package includes a demoapp directory containing an example main program with several command plugins.
Setup
To install and experiment with the demo app you should create a virtual environment and activate it. This will make it easy to remove the app later, since it doesn't do anything useful and you aren't likely to want to hang onto it after you understand how it works:
$ pip install virtualenv $ virtualenv .venv $ . .venv/bin/activate (.venv)$
Next, install cliff in the same environment:
(.venv)$ python setup.py install
Finally, install the demo application into the virtual environment:
(.venv)$ cd demoapp (.venv)$ python setup.py install
Usage
Both cliff and the demo installed, you can now run the command cliffdemo.
For basic command usage instructions and a list of the commands available from the plugins, run:
(.venv)$ cliffdemo -h
or:
(.venv)$ cliffdemo --help
Run the simple command by passing its name as argument to cliffdemo:
(.venv)$ cliffdemo simple
The simple command prints this output to the console:
sending greeting hi!
To see help for an individual command, use the help command:
(.venv)$ cliffdemo help files
or the --help option:
(.venv)$ cliffdemo files --help
For more information, refer to the autogenerated documentation below.
The Source
The cliffdemo application is defined in a cliffdemo package containing several modules.
main.py
The main application is defined in main.py:
import sys from cliff.app import App from cliff.commandmanager import CommandManager class DemoApp(App):
def __init__(self):
super(DemoApp, self).__init__(
description='cliff demo app',
version='0.1',
command_manager=CommandManager('cliff.demo'),
deferred_help=True,
)
def initialize_app(self, argv):
self.LOG.debug('initialize_app')
def prepare_to_run_command(self, cmd):
self.LOG.debug('prepare_to_run_command %s', cmd.__class__.__name__)
def clean_up(self, cmd, result, err):
self.LOG.debug('clean_up %s', cmd.__class__.__name__)
if err:
self.LOG.debug('got an error: %s', err) def main(argv=sys.argv[1:]):
myapp = DemoApp()
return myapp.run(argv) if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
The DemoApp class inherits from App and overrides __init__() to set the program description and version number. It also passes a CommandManager instance configured to look for plugins in the cliff.demo namespace.
The initialize_app() method of DemoApp will be invoked after the main program arguments are parsed, but before any command processing is performed and before the application enters interactive mode. This hook is intended for opening connections to remote web services, databases, etc. using arguments passed to the main application.
The prepare_to_run_command() method of DemoApp will be invoked after a command is identified, but before the command is given its arguments and run. This hook is intended for pre-command validation or setup that must be repeated and cannot be handled by initialize_app().
The clean_up() method of DemoApp is invoked after a command runs. If the command raised an exception, the exception object is passed to clean_up(). Otherwise the err argument is None.
The main() function defined in main.py is registered as a console script entry point so that DemoApp can be run from the command line (see the discussion of setup.py below).
simple.py
Two commands are defined in simple.py:
import logging from cliff.command import Command class Simple(Command):
"A simple command that prints a message."
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
self.log.info('sending greeting')
self.log.debug('debugging')
self.app.stdout.write('hi!\n') class Error(Command):
"Always raises an error"
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
self.log.info('causing error')
raise RuntimeError('this is the expected exception')
Simple demonstrates using logging to emit messages on the console at different verbose levels:
(.venv)$ cliffdemo simple sending greeting hi! (.venv)$ cliffdemo -v simple prepare_to_run_command Simple sending greeting debugging hi! clean_up Simple (.venv)$ cliffdemo -q simple hi!
Error always raises a RuntimeError exception when it is invoked, and can be used to experiment with the error handling features of cliff:
(.venv)$ cliffdemo error causing error ERROR: this is the expected exception (.venv)$ cliffdemo -v error prepare_to_run_command Error causing error ERROR: this is the expected exception clean_up Error got an error: this is the expected exception (.venv)$ cliffdemo --debug error causing error this is the expected exception Traceback (most recent call last):
File ".../cliff/app.py", line 218, in run_subcommand
result = cmd.run(parsed_args)
File ".../cliff/command.py", line 43, in run
self.take_action(parsed_args)
File ".../demoapp/cliffdemo/simple.py", line 24, in take_action
raise RuntimeError('this is the expected exception') RuntimeError: this is the expected exception Traceback (most recent call last):
File "/Users/dhellmann/Envs/cliff/bin/cliffdemo", line 9, in <module>
load_entry_point('cliffdemo==0.1', 'console_scripts', 'cliffdemo')()
File ".../demoapp/cliffdemo/main.py", line 33, in main
return myapp.run(argv)
File ".../cliff/app.py", line 160, in run
result = self.run_subcommand(remainder)
File ".../cliff/app.py", line 218, in run_subcommand
result = cmd.run(parsed_args)
File ".../cliff/command.py", line 43, in run
self.take_action(parsed_args)
File ".../demoapp/cliffdemo/simple.py", line 24, in take_action
raise RuntimeError('this is the expected exception') RuntimeError: this is the expected exception
list.py
list.py includes a single command derived from cliff.lister.Lister which prints a list of the files in the current directory.
import logging import os from cliff.lister import Lister class Files(Lister):
"""Show a list of files in the current directory.
The file name and size are printed by default.
"""
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
return (('Name', 'Size'),
((n, os.stat(n).st_size) for n in os.listdir('.'))
)
Files prepares the data, and Lister manages the output formatter and printing the data to the console:
(.venv)$ cliffdemo files +---------------+------+ | Name | Size | +---------------+------+ | build | 136 | | cliffdemo.log | 2546 | | Makefile | 5569 | | source | 408 | +---------------+------+ (.venv)$ cliffdemo files -f csv "Name","Size" "build",136 "cliffdemo.log",2690 "Makefile",5569 "source",408
show.py
show.py includes a single command derived from cliff.show.ShowOne which prints the properties of the named file.
import logging import os from cliff.show import ShowOne class File(ShowOne):
"Show details about a file"
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(File, self).get_parser(prog_name)
parser.add_argument('filename', nargs='?', default='.')
return parser
def take_action(self, parsed_args):
stat_data = os.stat(parsed_args.filename)
columns = ('Name',
'Size',
'UID',
'GID',
'Modified Time',
)
data = (parsed_args.filename,
stat_data.st_size,
stat_data.st_uid,
stat_data.st_gid,
stat_data.st_mtime,
)
return (columns, data)
File prepares the data, and ShowOne manages the output formatter and printing the data to the console:
(.venv)$ cliffdemo file setup.py +---------------+--------------+ | Field | Value | +---------------+--------------+ | Name | setup.py | | Size | 5825 | | UID | 502 | | GID | 20 | | Modified Time | 1335569964.0 | +---------------+--------------+
setup.py
The demo application is packaged using setuptools.
#!/usr/bin/env python from setuptools import find_packages from setuptools import setup PROJECT = 'cliffdemo' # Change docs/sphinx/conf.py too! VERSION = '0.1' try:
long_description = open('README.rst', 'rt').read() except IOError:
long_description = '' setup(
name=PROJECT,
version=VERSION,
description='Demo app for cliff',
long_description=long_description,
author='Doug Hellmann',
author_email='doug.hellmann@gmail.com',
url='https://github.com/openstack/cliff',
download_url='https://github.com/openstack/cliff/tarball/master',
classifiers=[
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Intended Audience :: Developers',
'Environment :: Console',
],
platforms=['Any'],
scripts=[],
provides=[],
install_requires=['cliff'],
namespace_packages=[],
packages=find_packages(),
include_package_data=True,
entry_points={
'console_scripts': [
'cliffdemo = cliffdemo.main:main'
],
'cliff.demo': [
'simple = cliffdemo.simple:Simple',
'two_part = cliffdemo.simple:Simple',
'error = cliffdemo.simple:Error',
'list files = cliffdemo.list:Files',
'files = cliffdemo.list:Files',
'file = cliffdemo.show:File',
'show file = cliffdemo.show:File',
'unicode = cliffdemo.encoding:Encoding',
'hooked = cliffdemo.hook:Hooked',
],
'cliff.demo.hooked': [
'sample-hook = cliffdemo.hook:Hook',
],
},
zip_safe=False, )
The important parts of the packaging instructions are the entry_points settings. All of the commands are registered in the cliff.demo namespace. Each main program should define its own command namespace so that it only loads the command plugins that it should be managing.
Command Extension Hooks
Individual subcommands of an application can be extended via hooks registered as separate plugins. In the demo application, the hooked command has a single extension registered.
The namespace for hooks is a combination of the application namespace and the command name. In this case, the application namespace is cliff.demo and the command is hooked, so the extension namespace is cliff.demo.hooked. If the subcommand name includes spaces, they are replaced with underscores ("_") to build the namespace.
# All Rights Reserved. # # 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. import logging from cliff.command import Command from cliff.hooks import CommandHook class Hooked(Command):
"A command to demonstrate how the hooks work"
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
self.app.stdout.write('this command has an extension\n') class Hook(CommandHook):
"""Hook sample for the 'hooked' command.
This would normally be provided by a separate package from the
main application, but is included in the demo app for simplicity.
"""
def get_parser(self, parser):
print('sample hook get_parser()')
parser.add_argument('--added-by-hook')
return parser
def get_epilog(self):
return 'extension epilog text'
def before(self, parsed_args):
self.cmd.app.stdout.write('before\n')
def after(self, parsed_args, return_code):
self.cmd.app.stdout.write('after\n')
Although the hooked command does not add any arguments to the parser it creates, the help output shows that the extension adds a single --added-by-hook option.
(.venv)$ cliffdemo hooked -h sample hook get_parser() usage: cliffdemo hooked [-h] [--added-by-hook ADDED_BY_HOOK] A command to demonstrate how the hooks work optional arguments:
-h, --help show this help message and exit
--added-by-hook ADDED_BY_HOOK extension epilog text (.venv)$ cliffdemo hooked sample hook get_parser() before this command has an extension after
SEE ALSO:
Autogenerated Documentation
The following documentation is generated using the following directive, which is provided by the cliff Sphinx extension.
.. autoprogram-cliff:: cliffdemo.main.DemoApp
:application: cliffdemo .. autoprogram-cliff:: cliff.demo
:application: cliffdemo
Output
Global Options
cliff demo app
cliffdemo [--version] [-v | -q] [--log-file LOG_FILE] [--debug]
- --version
- show program's version number and exit
- -v, --verbose
- Increase verbosity of output. Can be repeated.
- -q, --quiet
- Suppress output except warnings and errors.
- --log-file <LOG_FILE>
- Specify a file to log output. Disabled by default.
- --debug
- Show tracebacks on errors.
Command Options
error
Always raises an error
cliffdemo error
file
Show details about a file
cliffdemo file
[-f {json,shell,table,value,yaml}]
[-c COLUMN]
[--noindent]
[--prefix PREFIX]
[--max-width <integer>]
[--fit-width]
[--print-empty]
[filename]
- -f <FORMATTER>, --format <FORMATTER>
- the output format, defaults to table
- -c COLUMN, --column COLUMN
- specify the column(s) to include, can be repeated to show multiple columns
- --noindent
- whether to disable indenting the JSON
- --prefix <PREFIX>
- add a prefix to all variable names
- --max-width <integer>
- Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.
- --fit-width
- Fit the table to the display width. Implied if --max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable
- --print-empty
- Print empty table if there is no data to show.
- filename
files
Show a list of files in the current directory.
The file name and size are printed by default.
cliffdemo files
[-f {csv,json,table,value,yaml}]
[-c COLUMN]
[--quote {all,minimal,none,nonnumeric}]
[--noindent]
[--max-width <integer>]
[--fit-width]
[--print-empty]
[--sort-column SORT_COLUMN]
[--sort-ascending | --sort-descending]
- -f <FORMATTER>, --format <FORMATTER>
- the output format, defaults to table
- -c COLUMN, --column COLUMN
- specify the column(s) to include, can be repeated to show multiple columns
- --quote <QUOTE_MODE>
- when to include quotes, defaults to nonnumeric
- --noindent
- whether to disable indenting the JSON
- --max-width <integer>
- Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.
- --fit-width
- Fit the table to the display width. Implied if --max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable
- --print-empty
- Print empty table if there is no data to show.
- --sort-column SORT_COLUMN
- specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated
- --sort-ascending
- sort the column(s) in ascending order
- --sort-descending
- sort the column(s) in descending order
hooked
A command to demonstrate how the hooks work
cliffdemo hooked
list files
Show a list of files in the current directory.
The file name and size are printed by default.
cliffdemo list files
[-f {csv,json,table,value,yaml}]
[-c COLUMN]
[--quote {all,minimal,none,nonnumeric}]
[--noindent]
[--max-width <integer>]
[--fit-width]
[--print-empty]
[--sort-column SORT_COLUMN]
[--sort-ascending | --sort-descending]
- -f <FORMATTER>, --format <FORMATTER>
- the output format, defaults to table
- -c COLUMN, --column COLUMN
- specify the column(s) to include, can be repeated to show multiple columns
- --quote <QUOTE_MODE>
- when to include quotes, defaults to nonnumeric
- --noindent
- whether to disable indenting the JSON
- --max-width <integer>
- Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.
- --fit-width
- Fit the table to the display width. Implied if --max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable
- --print-empty
- Print empty table if there is no data to show.
- --sort-column SORT_COLUMN
- specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated
- --sort-ascending
- sort the column(s) in ascending order
- --sort-descending
- sort the column(s) in descending order
show file
Show details about a file
cliffdemo show file
[-f {json,shell,table,value,yaml}]
[-c COLUMN]
[--noindent]
[--prefix PREFIX]
[--max-width <integer>]
[--fit-width]
[--print-empty]
[filename]
- -f <FORMATTER>, --format <FORMATTER>
- the output format, defaults to table
- -c COLUMN, --column COLUMN
- specify the column(s) to include, can be repeated to show multiple columns
- --noindent
- whether to disable indenting the JSON
- --prefix <PREFIX>
- add a prefix to all variable names
- --max-width <integer>
- Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.
- --fit-width
- Fit the table to the display width. Implied if --max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable
- --print-empty
- Print empty table if there is no data to show.
- filename
simple
A simple command that prints a message.
cliffdemo simple
two part
A simple command that prints a message.
cliffdemo two part
unicode
Show some unicode text
cliffdemo unicode
[-f {csv,json,table,value,yaml}]
[-c COLUMN]
[--quote {all,minimal,none,nonnumeric}]
[--noindent]
[--max-width <integer>]
[--fit-width]
[--print-empty]
[--sort-column SORT_COLUMN]
[--sort-ascending | --sort-descending]
- -f <FORMATTER>, --format <FORMATTER>
- the output format, defaults to table
- -c COLUMN, --column COLUMN
- specify the column(s) to include, can be repeated to show multiple columns
- --quote <QUOTE_MODE>
- when to include quotes, defaults to nonnumeric
- --noindent
- whether to disable indenting the JSON
- --max-width <integer>
- Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence.
- --fit-width
- Fit the table to the display width. Implied if --max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable
- --print-empty
- Print empty table if there is no data to show.
- --sort-column SORT_COLUMN
- specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated
- --sort-ascending
- sort the column(s) in ascending order
- --sort-descending
- sort the column(s) in descending order
List Commands
One of the most common patterns with command line programs is the need to print lists of data. cliff provides a base class for commands of this type so that they only need to prepare the data, and the user can choose from one of several output formatter plugins to see the list of data in their preferred format.
Lister
The cliff.lister.Lister base class API extends Command to allow take_action() to return data to be formatted using a user-selectable formatter. Subclasses should provide a take_action() implementation that returns a two member tuple containing a tuple with the names of the columns in the dataset and an iterable that will yield the data to be output. See the description of the files command in the demoapp for details.
List Output Formatters
cliff is delivered with two output formatters for list commands. Lister adds a command line switch to let the user specify the formatter they want, so you don't have to do any extra work in your application.
csv
The csv formatter produces a comma-separated-values document as output. CSV data can be imported into a database or spreadsheet for further manipulation.
(.venv)$ cliffdemo files -f csv "Name","Size" "build",136 "cliffdemo.log",2690 "Makefile",5569 "source",408
table
The table formatter uses PrettyTable to produce output formatted for human consumption.
(.venv)$ cliffdemo files +---------------+------+ | Name | Size | +---------------+------+ | build | 136 | | cliffdemo.log | 2546 | | Makefile | 5569 | | source | 408 | +---------------+------+
value
The value formatter produces a space separated output with no headers.
(.venv)$ cliffdemo files -f value build 136 cliffdemo.log 2690 Makefile 5569 source 408
This format can be very convenient when you want to pipe the output to a script.
(.venv)$ cliffdemo files -f value | while read NAME SIZE do
echo $NAME is $SIZE bytes done build is 136 bytes cliffdemo.log is 2690 bytes Makefile is 5569 bytes source is 408 bytes
yaml
The yaml formatter uses PyYAML to produce a YAML sequence of mappings.
(.venv)$ cliffdemo files -f yaml - Name: dist
Size: 4096 - Name: cliffdemo.egg-info
Size: 4096 - Name: README.rst
Size: 960 - Name: setup.py
Size: 1807 - Name: build
Size: 4096 - Name: cliffdemo
Size: 4096
json
The json formatter produces an array of objects in indented JSON format.
(.venv)$ cliffdemo files -f json [
{
"Name": "source",
"Size": 4096
},
{
"Name": "Makefile",
"Size": 5569
},
{
"Name": "build",
"Size": 4096
} ]
Other Formatters
A formatter using tablib to produce HTML is available as part of cliff-tablib.
Creating Your Own Formatter
If the standard formatters do not meet your needs, you can bundle another formatter with your program by subclassing from cliff.formatters.base.ListFormatter and registering the plugin in the cliff.formatter.list namespace.
Show Commands
One of the most common patterns with command line programs is the need to print properties of objects. cliff provides a base class for commands of this type so that they only need to prepare the data, and the user can choose from one of several output formatter plugins to see the data in their preferred format.
ShowOne
The cliff.show.ShowOne base class API extends Command to allow take_action() to return data to be formatted using a user-selectable formatter. Subclasses should provide a take_action() implementation that returns a two member tuple containing a tuple with the names of the columns in the dataset and an iterable that contains the data values associated with those names. See the description of the file command in the demoapp for details.
Show Output Formatters
cliff is delivered with output formatters for show commands. ShowOne adds a command line switch to let the user specify the formatter they want, so you don't have to do any extra work in your application.
table
The table formatter uses PrettyTable to produce output formatted for human consumption. This is the default formatter.
(.venv)$ cliffdemo file setup.py +---------------+--------------+ | Field | Value | +---------------+--------------+ | Name | setup.py | | Size | 5825 | | UID | 502 | | GID | 20 | | Modified Time | 1335569964.0 | +---------------+--------------+
shell
The shell formatter produces output that can be parsed directly by a typical UNIX shell as variable assignments. This avoids extra parsing overhead in shell scripts.
(.venv)$ cliffdemo file -f shell setup.py name="setup.py" size="5916" uid="527" gid="501" modified_time="1335655655.0" (.venv)$ eval "$(cliffdemo file -f shell --prefix example_ setup.py)" (.venv)$ echo $example_size 5916
value
The value formatter produces output that only contains the value of the field or fields.
(.venv)$ cliffdemo file -f value -c Size setup.py 5916 (.venv)$ SIZE="$(cliffdemo file -f value -c Size setup.py)" (.venv)$ echo $SIZE 5916
yaml
The yaml formatter uses PyYAML to produce a YAML mapping where the field name is the key.
(.venv)$ cliffdemo file -f yaml setup.py Name: setup.py Size: 1807 UID: 1000 GID: 1000 Modified Time: 1393531476.9587486
json
The json formatter produces a JSON object where the field name is the key.
(.venv)$ cliffdemo file -f json setup.py
{
"Modified Time": 1438726433.8055942,
"GID": 1000,
"UID": 1000,
"Name": "setup.py",
"Size": 1028
}
Other Formatters
A formatter using tablib to produce HTML is available as part of cliff-tablib.
Creating Your Own Formatter
If the standard formatters do not meet your needs, you can bundle another formatter with your program by subclassing from cliff.formatters.base.ShowFormatter and registering the plugin in the cliff.formatter.show namespace.
Command Completion
A generic command completion command is available to generate a bash-completion script. Currently, the command will generate a script for bash versions 3 or 4. There is also a mode that generates only data that can be used in your own script. The command completion script is generated based on the commands and options that you have specified in cliff.
Usage
In order for your command to support command completions, you need to add the cliff.complete.CompleteCommand class to your command manager.
self.command_manager.add_command('complete', cliff.complete.CompleteCommand)
When you run the command, it will generate a bash-completion script:
(.venv)$ mycmd complete
_mycmd()
{
local cur prev words
COMPREPLY=()
_get_comp_words_by_ref -n : cur prev words
# Command data:
cmds='agent aggregate backup'
cmds_agent='--name'
...
if [ -z "${completed}" ] ; then
COMPREPLY=( $( compgen -f -- "$cur" ) $( compgen -d -- "$cur" ) )
else
COMPREPLY=( $(compgen -W "${completed}" -- ${cur}) )
fi
return 0
}
complete -F _mycmd mycmd
Interactive Mode
In addition to running single commands from the command line, cliff supports an interactive mode in which the user is presented with a separate command shell. All of the command plugins available from the command line are automatically configured as commands within the shell.
Refer to the cmd2 documentation for more details about features of the shell.
Example
The cliffdemo application enters interactive mode if no command is specified on the command line.
(.venv)$ cliffdemo (cliffdemo) help Shell commands (type help <topic>): =================================== cmdenvironment edit hi l list pause r save shell show ed help history li load py run set shortcuts Undocumented commands: ====================== EOF eof exit q quit Application commands (type help <topic>): ========================================= files help simple file error two part
To obtain instructions for a built-in or application command, use the help command:
(cliffdemo) help simple usage: simple [-h] A simple command that prints a message. optional arguments:
-h, --help Show help message and exit.
The commands can be run, including options and arguments, as on the regular command line:
(cliffdemo) simple sending greeting hi! (cliffdemo) files +----------------------+-------+ | Name | Size | +----------------------+-------+ | .git | 578 | | .gitignore | 268 | | .tox | 238 | | .venv | 204 | | announce.rst | 1015 | | announce.rst~ | 708 | | cliff | 884 | | cliff.egg-info | 340 | | cliffdemo.log | 2193 | | cliffdemo.log.1 | 10225 | | demoapp | 408 | | dist | 136 | | distribute_setup.py | 15285 | | distribute_setup.pyc | 15196 | | docs | 238 | | LICENSE | 11358 | | Makefile | 376 | | Makefile~ | 94 | | MANIFEST.in | 186 | | MANIFEST.in~ | 344 | | README.rst | 1063 | | setup.py | 5855 | | setup.py~ | 8128 | | tests | 204 | | tox.ini | 76 | | tox.ini~ | 421 | +----------------------+-------+ (cliffdemo)
Sphinx Integration
Usage
cliff supports integration with Sphinx by way of a Sphinx directives.
Preparation
Before using the autoprogram-cliff directive you must add 'cliff.sphinxext' extension module to a list of extensions in conf.py:
extensions = ['cliff.sphinxext']
Directive
- .. autoprogram-cliff:: <namespace> or <app class>
- Automatically document an instance of cliff.command.Command or
cliff.app.App including a description, usage summary, and overview
of all options.
IMPORTANT:
The command mode documents various information of a specified instance of cliff.command.Command. The command mode takes the namespace that the command(s) can be found in as the argument. This is generally defined in the entry_points section of either setup.cfg or setup.py. You can specify which command(s) should be displayed using :command: option.
.. autoprogram-cliff:: openstack.compute.v2
:command: server add fixed ip
The app mode documents various information of a specified instance of cliff.app.App. The app mode takes the python path of the corresponding class as the argument. In the app mode, :application: option is usually specified so that the command name is shown in the rendered output.
.. autoprogram-cliff:: cliffdemo.main.DemoApp
:application: cliffdemo
Refer to the example below for more information.
In addition, the following directive options can be supplied:
- :command:
- The name of the command, as it would appear if called from the command
line without the executable name. This will be defined in setup.cfg
or setup.py albeit with underscores. This is optional and
fnmatch-style wildcarding is supported. Refer to the example
below for more information.
This option is effective only in the command mode.
- :arguments
- The arguments to be passed when the cliff application is instantiated.
Some cliff applications requires arguments when instantiated. This option
can be used to specify such arguments.
This option is effective only in the app mode.
- :application:
- The top-level application name, which will be prefixed before all
commands. This option overrides the global option
autoprogram_cliff_application described below. In most cases the
global configuration is enough, but this option is useful if your sphinx
document handles multiple cliff applications.
SEE ALSO:
- :ignored:
- A comma-separated list of options to exclude from documentation for this
option. This is useful for options that are of low value.
SEE ALSO:
The following global configuration values are supported. These should be placed in conf.py:
- autoprogram_cliff_application
- The top-level application name, which will be prefixed before all
commands. This is generally defined in the console_scripts
attribute of the entry_points section of either setup.cfg or
setup.py. Refer to the example below for more information.
For example:
autoprogram_cliff_application = 'my-sample-application'
Defaults to ''
SEE ALSO:
SEE ALSO:
- autoprogram_cliff_ignored
- A global list of options to exclude from documentation. This can be used
to prevent duplication of common options, such as those used for
pagination, across all options.
For example:
autoprogram_cliff_ignored = ['--help', '--page', '--order']
Defaults to ['--help']
SEE ALSO:
- autoprogram_cliff_app_dist_name
- The name of the python distribution (the name used with pip, as opposed to the package name used for importing) providing the commands/applications being documented. Generated documentation for plugin components includes a message indicating the name of the plugin. Setting this option tells cliff the name of the distribution providing components natively so their documentation does not include this message.
SEE ALSO:
- Module sphinxcontrib.autoprogram
- An equivalent library for use with plain-old argparse applications.
- Module sphinx-click
- An equivalent library for use with click applications.
IMPORTANT:
Examples
Simple Example (demoapp)
cliff provides a sample application, demoapp, to demonstrate some of the features of cliff. This application is documented using the cliff.sphinxext Sphinx extension.
Advanced Example (python-openstackclient)
It is also possible to document larger applications, such as python-openstackclient. Take a sample setup.cfg file, which is a minimal version of the setup.cfg provided by the python-openstackclient project:
[entry_points] console_scripts =
openstack = openstackclient.shell:main openstack.compute.v2 =
host_list = openstackclient.compute.v2.host:ListHost
host_set = openstackclient.compute.v2.host:SetHost
host_show = openstackclient.compute.v2.host:ShowHost
This will register three commands - host list, host set and host show - for a top-level executable called openstack. To document the first of these, add the following:
.. autoprogram-cliff:: openstack.compute.v2
:command: host list
You could also register all of these at once like so:
.. autoprogram-cliff:: openstack.compute.v2
:command: host *
Finally, if these are the only commands available in that namespace, you can omit the :command: parameter entirely:
.. autoprogram-cliff:: openstack.compute.v2
In all cases, you should add the following to your conf.py to ensure all usage examples show the full command name:
autoprogram_cliff_application = 'openstack'
CLIFF CLASS REFERENCE
Application
App
- class cliff.app.App(description, version, command_manager, stdin=None, stdout=None, stderr=None, interactive_app_factory=None, deferred_help=False)
- Application base class.
- Parameters
- description (str) -- one-liner explaining the program purpose
- version (str) -- application version number
- command_manager (cliff.commandmanager.CommandManager) -- plugin loader
- stdin (readable I/O stream) -- Standard input stream
- stdout (writable I/O stream) -- Standard output stream
- stderr (writable I/O stream) -- Standard error output stream
- interactive_app_factory (cliff.interactive.InteractiveApp) -- callable to create an interactive application
- deferred_help (bool) -- True - Allow subcommands to accept --help with allowing to defer help print after initialize_app
- build_option_parser(description, version, argparse_kwargs=None)
- Return an argparse option parser for this application.
Subclasses may override this method to extend the parser with more global options.
- Parameters
- description (str) -- full description of the application
- version (str) -- version number for the application
- argparse_kwargs -- extra keyword argument passed to the ArgumentParser constructor
- clean_up(cmd, result, err)
- Hook run after a command is done to shutdown the app.
- Parameters
- cmd (cliff.command.Command) -- command processor being invoked
- result (int) -- return value of cmd
- err (Exception) -- exception or None
- configure_logging()
- Create logging handlers for any log output.
- get_fuzzy_matches(cmd)
- return fuzzy matches of unknown command
- initialize_app(argv)
- Hook for subclasses to take global initialization action after the arguments are parsed but before a command is run. Invoked only once, even in interactive mode.
- Parameters
- argv -- List of arguments, including the subcommand to run. Empty for interactive mode.
- prepare_to_run_command(cmd)
- Perform any preliminary work needed to run a command.
- Parameters
- cmd (cliff.command.Command) -- command processor being invoked
- print_help_if_requested()
- Print help and exits if deferred help is enabled and requested.
- '--help' shows the help message and exits:
- without calling initialize_app if not self.deferred_help (default),
- after initialize_app call if self.deferred_help,
- during initialize_app call if self.deferred_help and subclass calls explicitly this method in initialize_app.
- run(argv)
- Equivalent to the main program for the application.
- Parameters
- argv (list of str) -- input arguments and options
InteractiveApp
- class cliff.interactive.InteractiveApp(parent_app, command_manager, stdin, stdout, errexit=False)
- Provides "interactive mode" features.
Refer to the cmd2 and cmd documentation for details about subclassing and configuring this class.
- Parameters
- parent_app -- The calling application (expected to be derived from cliff.main.App).
- command_manager -- A cliff.commandmanager.CommandManager instance.
- stdin -- Standard input stream
- stdout -- Standard output stream
- cmdloop()
- This is an outer wrapper around _cmdloop() which deals with extra features
provided by cmd2.
_cmdloop() provides the main loop equivalent to cmd.cmdloop(). This is a wrapper around that which deals with the following extra features provided by cmd2: - transcript testing - intro banner - exit code
- Parameters
- intro -- if provided this overrides self.intro and serves as the intro banner printed once at start
- completedefault(text, line, begidx, endidx)
- Default tab-completion for command prefix with completer delimiter.
This method filters only cliff style commands matching provided command prefix (line) as cmd2 style commands cannot contain spaces. This method returns text + missing command part of matching commands. This method does not handle options in cmd2/cliff style commands, you must define complete_$method to handle them.
- completenames(text, line, begidx, endidx)
- Tab-completion for command prefix without completer delimiter.
This method returns cmd style and cliff style commands matching provided command prefix (text).
- default(line)
- Executed when the command given isn't a recognized command implemented by a do_* method.
- Parameters
- statement -- Statement object with parsed input
- do_exit(_: argparse.Namespace) -> Optional[bool]
- Exit this application
- do_help(arg)
- List available commands or provide detailed help for a specific command
- get_names()
- Return an alphabetized list of names comprising the attributes of the cmd2 class instance.
- precmd(statement)
- Hook method executed just before the command is executed by onecmd() and after adding it to history.
- Parameters
- statement -- subclass of str which also contains the parsed input
- Returns
- a potentially modified version of the input Statement object
CommandManager
- class cliff.commandmanager.CommandManager(namespace, convert_underscores=True)
- Discovers commands and handles lookup based on argv data.
- Parameters
- namespace -- String containing the entrypoint namespace for the plugins to be loaded. For example, 'cliff.formatter.list'.
- convert_underscores -- Whether cliff should convert underscores to spaces in entry_point commands.
- add_command_group(group=None)
- Adds another group of command entrypoints
- add_legacy_command(old_name, new_name)
- Map an old command name to the new name.
- Parameters
- old_name (str) -- The old command name.
- new_name (str) -- The new command name.
- find_command(argv)
- Given an argument list, find a command and return the processor and any remaining arguments.
- get_command_groups()
- Returns a list of the loaded command groups
- get_command_names(group=None)
- Returns a list of commands loaded for the specified group
- load_commands(namespace)
- Load all the commands from an entrypoint
Command
- class cliff.command.Command(app, app_args, cmd_name=None)
- Base class for command plugins.
When the command is instantiated, it loads extensions from a namespace based on the parent application namespace and the command name:
app.namespace + '.' + cmd_name.replace(' ', '_')
- Parameters
- app (cliff.app.App) -- Application instance invoking the command.
- get_description()
- Return the command description.
The default is to use the first line of the class' docstring as the description. Set the _description class attribute to a one-line description of a command to use a different value. This is useful for enabling translations, for example, with _description set to a string wrapped with a gettext translation marker.
- get_epilog()
- Return the command epilog.
- get_parser(prog_name)
- Return an argparse.ArgumentParser.
- run(parsed_args)
- Invoked by the application when the command is run.
Developers implementing commands should override take_action().
Developers creating new command base classes (such as Lister and ShowOne) should override this method to wrap take_action().
Return the value returned by take_action() or 0.
- abstract take_action(parsed_args)
- Override to do something useful.
The returned value will be returned by the program.
CommandHook
- class cliff.hooks.CommandHook(command)
- Base class for command hooks.
- Parameters
- app (cliff.command.Command) -- Command instance being invoked
- abstract after(parsed_args, return_code)
- Called after the command's take_action() method.
- Parameters
- parsed_args (argparse.Namespace) -- The arguments to the command.
- return_code (int) -- The value returned from take_action().
- Returns
- int
- abstract before(parsed_args)
- Called before the command's take_action() method.
- Parameters
- parsed_args (argparse.Namespace) -- The arguments to the command.
- Returns
- argparse.Namespace
- abstract get_epilog()
- Return text to add to the command help epilog.
- abstract get_parser(parser)
- Return an argparse.ArgumentParser.
- Parameters
- parser (ArgumentParser) -- An existing ArgumentParser instance to be modified.
- Returns
- ArgumentParser
ShowOne
- class cliff.show.ShowOne(app, app_args, cmd_name=None)
- Command base class for displaying data about a single object.
- dict2columns(data)
- Implement the common task of converting a dict-based object to the two-column output that ShowOne expects.
- property formatter_default
- String specifying the name of the default formatter.
- property formatter_namespace
- String specifying the namespace to use for loading formatter plugins.
- produce_output(parsed_args, column_names, data)
- Use the formatter to generate the output.
- Parameters
- parsed_args -- argparse.Namespace instance with argument values
- column_names -- sequence of strings containing names of output columns
- data -- iterable with values matching the column names
- abstract take_action(parsed_args)
- Return a two-part tuple with a tuple of column names and a tuple of values.
Lister
- class cliff.lister.Lister(app, app_args, cmd_name=None)
- Command base class for providing a list of data as output.
- property formatter_default
- String specifying the name of the default formatter.
- property formatter_namespace
- String specifying the namespace to use for loading formatter plugins.
- get_parser(prog_name)
- Return an argparse.ArgumentParser.
- property need_sort_by_cliff
- Whether sort procedure is performed by cliff itself.
Should be overridden (return False) when there is a need to implement custom sorting procedure or data is already sorted.
- produce_output(parsed_args, column_names, data)
- Use the formatter to generate the output.
- Parameters
- parsed_args -- argparse.Namespace instance with argument values
- column_names -- sequence of strings containing names of output columns
- data -- iterable with values matching the column names
- abstract take_action(parsed_args)
- Run command.
Return a tuple containing the column names and an iterable containing the data to be listed.
Formatting Output
Formatter
- class cliff.formatters.base.Formatter
- abstract add_argument_group(parser)
- Add any options to the argument parser.
Should use our own argument group.
ListFormatter
- class cliff.formatters.base.ListFormatter
- Base class for formatters that know how to deal with multiple objects.
- abstract emit_list(column_names, data, stdout, parsed_args)
- Format and print the list from the iterable data source.
Data values can be primitive types like ints and strings, or can be an instance of a FormattableColumn for situations where the value is complex, and may need to be handled differently for human readable output vs. machine readable output.
- Parameters
- column_names -- names of the columns
- data -- iterable data source, one tuple per object with values in order of column names
- stdout -- output stream where data should be written
- parsed_args -- argparse namespace from our local options
SingleFormatter
- class cliff.formatters.base.SingleFormatter
- Base class for formatters that work with single objects.
- abstract emit_one(column_names, data, stdout, parsed_args)
- Format and print the values associated with the single object.
Data values can be primitive types like ints and strings, or can be an instance of a FormattableColumn for situations where the value is complex, and may need to be handled differently for human readable output vs. machine readable output.
- Parameters
- column_names -- names of the columns
- data -- iterable data source with values in order of column names
- stdout -- output stream where data should be written
- parsed_args -- argparse namespace from our local options
FOR CONTRIBUTORS
If you would like to contribute to cliff directly, these instructions should help you get started. Bug reports, and feature requests are all welcome through the Storyboard project.
Changes to cliff should be submitted for review via the Gerrit tool, following the workflow documented at https://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed under the Storyboard project.
NOTE:
Running Tests
The test suite for cliff uses tox, which must be installed separately (pip install tox).
To run the standard set of tests, run tox from the top level directory of the git repository.
To run a single environment, specify it using the -e parameter. For example:
$ tox -e pep8
Add new tests by modifying an existing file or creating new script in the tests directory.
Building Documentation
The documentation for cliff is written in reStructuredText and converted to HTML using Sphinx. Like tests, the documentation can be built using tox:
$ tox -e docs
The output version of the documentation ends up in ./docs/build/html.
Indices and tables
- genindex
- search
AUTHOR
Doug Hellmann
COPYRIGHT
2012-2023, Doug Hellmann
| November 29, 2023 |
