CLIFF(1)
๐ CLIFF(1) โ the cliff manual, bestie edition ๐
๐ NAME ๐
cliff โ cliff Documentation. Thatโs the official name, bestie, and yes, this whole page is the cliff Documentation.
cliff is a framework for building command line programs. It uses plugins to define sub-commands, output formatters, and other extensions. Periodt. ๐
๐ ๏ธ INSTALLATION ๐ ๏ธ
๐ Python Versions ๐
cliff is being developed under Python 3. Thatโs the vibe.
๐ฆ 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 โ the link is literally <http://pypi.python.org/pypi/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. Iconic behavior, honestly. โจ
๐ 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 to organize source code in any way they see fit. Slay for modularity. ๐งฉ
๐งฉ Cliff Objects ๐งฉ
cliff is organized around five objects that are combined to create a useful command line program.
๐ The Application ๐
A 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. Main character energy, lowkey. ๐
๐๏ธ The CommandManager ๐๏ธ
The cliff.commandmanager.CommandManager knows how to load individual command plugins. The default implementation uses entry points โ <https://packaging.python.org/specifications/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 โ <http://docs.python.org/library/argparse.html>) 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 a 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. Cute and convenient. ๐
๐ฎ 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().__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:
...and here's what that actually looks like when you run it, bestie ๐
(.venv)$ cliffdemo files
+---------------+------+
| Name | Size |
+---------------+------+
| build | 136 |
| cliffdemo.log | 2546 |
| Makefile | 5569 |
| source | 408 |
+---------------+------+
and then the same command with the formatter switched up ๐ฆ
(.venv)$ cliffdemo files -f csv
"Name","Size"
"build",136
"cliffdemo.log",2690
"Makefile",5569
"source",408
๐ show.py
show.py has exactly one command in it, and it's a ShowOne subclass <#cliff.show.ShowOne> โ so like, it prints out the properties of whatever file you point it at. iconic behaviour honestly โจ
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().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)
so File does the actual data-gathering (that's its whole job, no cap), and ShowOne is the one handling the output formatter and printing everything to the console โ teamwork, we love to see it ๐ซถ
(.venv)$ cliffdemo file setup.py
+---------------+--------------+
| Field | Value |
+---------------+--------------+
| Name | setup.py |
| Size | 5825 |
| UID | 502 |
| GID | 20 |
| Modified Time | 1335569964.0 |
+---------------+--------------+
๐ฆ setup.py
the demo app gets packaged with setuptools, like so:
#!/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').read()
except OSError:
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,
)
okay so the part that actually matters here (the main character, truly) is the entry_points settings ๐ every single command gets registered in the cliff.demo namespace. and here's the rule, bestie, write it down: each main program should define its own command namespace so it only ever loads the command plugins it's actually supposed to be managing. no crossover, no chaos ๐
๐ช Command Extension Hooks
plot twist: individual subcommands of an app can be extended through hooks that are registered as separate plugins. in the demo app, the hooked command has exactly one extension registered. cute ๐ธ
the namespace for hooks is a combo of the application namespace plus the command name. here that means the application namespace is cliff.demo and the command is hooked, so the extension namespace comes out as cliff.demo.hooked. and if the subcommand name has spaces in it? those get swapped for underscores ("_") when building the namespace. iykyk ๐ง
# 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')
so even though the hooked command itself doesn't add any arguments to the parser it creates, the help output proves the extension sneaks in a single --added-by-hook option. the way that works >>> ๐ญ
(.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: cliff.hooks.CommandHook <#cliff.hooks.CommandHook> โ the API for command hooks ๐
๐ Autogenerated Documentation
the following docs are generated using this 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, in all its glory:
cliffdemo [--version] [-v | -q] [--log-file LOG_FILE] [--debug]
--versionโ show the program's version number and exit โ๏ธ-v,--verboseโ crank up the verbosity of the output. you can repeat it, so go off, stack 'em ๐ฅ-q,--quietโ suppress output except warnings and errors. shhh ๐คซ--log-file<LOG_FILE>โ point at a file to log output into. disabled by default, so you gotta opt in ๐--debugโ show tracebacks when errors happen. super helpful when things go sideways ngl ๐ญ
โ๏ธ Command Options
๐ error
always raises an error. that's it. that's the command. it's giving chaos on purpose ๐ญ
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 totable๐-cCOLUMN,--columnCOLUMNโ specify which column(s) to include. repeat it to show multiple columns ๐--noindentโ whether to disable indenting the JSON ๐ฆ--prefix<PREFIX>โ add a prefix to all the variable names โจ--max-width<integer>โ maximum display width,<1to disable. you can also use theCLIFF_MAX_TERM_WIDTHenvironment variable, but heads up: the parameter takes precedence ๐--fit-widthโ fit the table to the display width. it's implied if--max-widthis greater than0. set the environment variableCLIFF_FIT_WIDTH=1to always enable it ๐--print-emptyโ print an empty table if there's no data to show ๐ซถfilenameโ the file you're asking about ๐
๐ files
show a list of files in the current directory. by default the file name and size are what gets printed ๐
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 totable๐-cCOLUMN,--columnCOLUMNโ specify which column(s) to include. repeat it to show multiple columns ๐--quote<QUOTE_MODE>โ when to include quotes, defaults tononnumeric๐ธ--noindentโ whether to disable indenting the JSON ๐ฆ--max-width<integer>โ maximum display width,<1to disable. you can also use theCLIFF_MAX_TERM_WIDTHenvironment variable, but the parameter takes precedence ๐--fit-widthโ fit the table to the display width. it's implied if--max-widthis greater than0. set the environment variableCLIFF_FIT_WIDTH=1to always enable it ๐--print-emptyโ print an empty table if there's no data to show ๐ซถ--sort-column SORT_COLUMNโ tells it which column(s) to sort by. The ones you list first get priority, columns that don't even exist are just like... ignored bestie, no drama. Names are case-insensitive and you can use underscores instead of spaces. You can repeat it as many times as you want ๐--sort-ascendingโ sorts the column(s) ascending (little โ big, A โ Z, that whole vibe).--sort-descendingโ sorts the column(s) descending. Iconic reversal behavior.
๐ช hooked
A command to demonstrate how the hooks work ๐
cliffdemo hooked
๐ list files
Shows you a list of files in the current directory. By default it prints the file name and the size โ that's it, that's the whole flex โจ
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 totable.-c COLUMN,--column COLUMNโ picks the column(s) to include. Repeat it to show multiple columns, go off ๐--quote <QUOTE_MODE>โ when to include quotes, defaults tononnumeric.--noindentโ whether to disable indenting the JSON (yes, the flag name and the description are lowkey opposites, we don't ask questions).--max-width <integer>โ maximum display width. Anything<1disables it. You can also set theCLIFF_MAX_TERM_WIDTHenvironment variable, but the parameter takes precedence, so the flag wins the argument fr fr.--fit-widthโ fits the table to the display width. It's implied if--max-widthis greater than0. Set the environment variableCLIFF_FIT_WIDTH=1to always enable it.--print-emptyโ prints an empty table if there's no data to show. No sad empty terminal energy ๐ซถ--sort-column SORT_COLUMNโ which column(s) to sort the data by. Columns specified first have priority, non-existing columns are ignored, names are case-insensitive and accept underscores for spaces, and you can repeat it.--sort-ascendingโ sort the column(s) in ascending order.--sort-descendingโ sort the column(s) in descending order.
๐ show file
Shows you details about a file. Detective mode: activated ๐ต๏ธโโ๏ธ
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 totable.-c COLUMN,--column COLUMNโ the column(s) to include, repeatable so you can show multiple columns.--noindentโ whether to disable indenting the JSON.--prefix <PREFIX>โ adds a prefix to all variable names. Very demure, very mindful naming ๐--max-width <integer>โ maximum display width,<1to disable. You can also use theCLIFF_MAX_TERM_WIDTHenvironment variable, but the parameter takes precedence.--fit-widthโ fit the table to the display width. Implied if--max-widthis greater than0. SetCLIFF_FIT_WIDTH=1to always enable.--print-emptyโ print an empty table if there's no data to show.filenameโ yeah, the file. No description given, it's giving mysterious main character energy ๐
๐ฌ simple
A simple command that prints a message. That's literally the whole description and honestly? Respect the minimalism โจ
cliffdemo simple
โ๏ธ two part
A simple command that prints a message. (Yes, same description as simple โ that's not a typo, that's canon.)
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 totable.-c COLUMN,--column COLUMNโ the column(s) to include, can be repeated to show multiple columns.--quote <QUOTE_MODE>โ when to include quotes, defaults tononnumeric.--noindentโ whether to disable indenting the JSON.--max-width <integer>โ maximum display width,<1to disable. You can also use theCLIFF_MAX_TERM_WIDTHenvironment variable, but the parameter takes precedence.--fit-widthโ fit the table to the display width. Implied if--max-widthis greater than0. Set the environment variableCLIFF_FIT_WIDTH=1to always enable.--print-emptyโ print empty table if there is no data to show.--sort-column SORT_COLUMNโ the column(s) to sort the data by โ columns specified first have priority, non-existing columns are ignored, names are case-insensitive and accept underscores for spaces, and it's repeatable.--sort-ascendingโ sort the column(s) in ascending order.--sort-descendingโ sort the column(s) in descending order.
๐ List Commands
Okay so one of the most common patterns with command line programs is that you need to print lists of data. And cliff is like, "I got you bestie" โ it provides a base class for commands of this type so they only have to prepare the data, and then the user gets to choose from one of several output formatter plugins to see the list of data in their preferred format. Slay. ๐
๐ง Lister
The cliff.lister.Lister <#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 <#demoapp-list> for the details. ๐
๐จ List Output Formatters
cliff ships with two output formatters for list commands. Lister adds a command line switch so the user can specify which formatter they want โ so you don't have to do any extra work in your application. Periodt, that's the whole point ๐
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. It's giving "I'm data and I'm ready for a glow-up" ๐
(.venv)$ cliffdemo files -f csv
"Name","Size"
"build",136
"cliffdemo.log",2690
"Makefile",5569
"source",408
table
The table formatter uses PrettyTable <https://pypi.org/project/prettytable/> to produce output formatted for human consumption. (Humans only. Sorry bots.) ๐
(.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. Clean, minimal, no notes โจ
(.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. Like, this is the whole reason it exists:
(.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 <http://pyyaml.org/> to produce a YAML sequence of mappings. Indentation girlies, this one's for you ๐ง
(.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. Curly braces and vibes ๐ฆ
(.venv)$ cliffdemo files -f json
[
{
"Name": "source",
"Size": 4096
},
{
"Name": "Makefile",
"Size": 5569
},
{
"Name": "build",
"Size": 4096
}
]
๐งฐ Other Formatters
A formatter using tablib <https://github.com/kennethreitz/tablib> to produce HTML is available as part of cliff-tablib <https://github.com/dreamhost/cliff-tablib>. HTML representation, we love to see it ๐ธ
๐ ๏ธ 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 <#cliff.formatters.base.ListFormatter> and registering the plugin in the cliff.formatter.list namespace. Build your own, bestie, I believe in you ๐ซถ
๐ Show Commands
One of the most common patterns with command line programs is needing to print properties of objects. cliff provides a base class for commands of this type so 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. Same energy as the list situation, different vibe ๐
๐ ShowOne
The cliff.show.ShowOne <#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 <#demoapp-show> for details. โจ
๐ ShowOneWithDetails
The cliff.show.ShowOneWithDetails <#cliff.show.ShowOneWithDetails> base class displays a single object's fields with additional columns โ things like descriptions, types, or sources. Unlike ShowOne <#cliff.show.ShowOne>, it does not limit table output to Field and Value columns. It said "expand the table, we have room" ๐ค
Subclasses implement take_action() <#cliff.show.ShowOneWithDetails.take_action> to return a two-member tuple containing:
- A sequence of output column names.
- An iterable of rows, each containing values in output column order. The first value in each row must be a string containing the resource's field name.
For example:
from cliff import show
class ShowResource(show.ShowOneWithDetails):
def take_action(self, parsed_args):
return (
('Field', 'Value', 'Description'),
(
('name', 'demo', 'Resource name'),
('status', 'ACTIVE', 'Current resource status'),
),
)
As with ShowOne, -c / --column selects resource fields (rows), not output columns. Selection is case-insensitive, ignores surrounding whitespace, and accepts underscores in place of spaces. Selected rows retain their original order and all their output columns. Unrecognized field names are ignored if at least one requested field is recognized; otherwise, selection raises an error. So like... pick a real one or the whole thing throws hands ๐ญ
okay so, for example, resource show -c status would give you this little table moment:
+--------+--------+-------------------------+
| Field | Value | Description |
+--------+--------+-------------------------+
| status | ACTIVE | Current resource status |
+--------+--------+-------------------------+
ShowOneWithDetails uses the cliff.formatter.list plugins we talked about in the List Commands <> section, with table as the default. So JSON and YAML output contain a list of row mappings โ not the single field-to-value mapping that ShowOne produces. Like, resource show -f json -c status would give you:
[
{
"Field": "status",
"Value": "ACTIVE",
"Description": "Current resource status"
}
]
CSV and value output also include every output column of each selected row. The show-only shell formatter and Lister sorting options are not available. not available, bestie, don't go looking for them ๐
โจ Show Output Formatters โจ
cliff is delivered with output formatters for show commands. ShowOne adds a command line switch so the user can pick the formatter they want โ so you don't have to do any extra work in your application. iconic ๐
๐ table ๐
The table formatter uses PrettyTable (https://pypi.org/project/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 (http://pyyaml.org/) 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 (https://github.com/kennethreitz/tablib) to produce HTML is available as part of cliff-tablib (https://github.com/dreamhost/cliff-tablib). slay, options for days โจ
๐ 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. build your own icon, bestie ๐
๐ซถ 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 (http://packages.python.org/cmd2/index.html) 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 (http://www.sphinx-doc.org/en/stable/extdev/markupapi.html). iykyk, docs bestie ๐ซถ
๐ 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 <#cliff.command.Command> or cliff.app.App <#cliff.app.App> including a description, usage summary, and overview of all options.
Important: There are two modes in this directive: command mode and app mode. The directive takes one required argument and the mode is determined based on the argument specified.
The command mode documents various information of a specified instance of cliff.command.Command <#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 <#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 insetup.cfgorsetup.pyalbeit with underscores. This is optional and fnmatch-style (https://docs.python.org/3/library/fnmatch.html) 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 optionautoprogram_cliff_applicationdescribed below. In most cases the global configuration is enough, but this option is useful if your sphinx document handles multiple cliff applications.
See also: The autoprogram_cliff_application configuration option. ๐ซถ
:ignored:โ a comma-separated list of options to exclude from the docs for this option. It's for options that are, like, low value, bestie โ no need to give every tiny thing main-character billing. Also see theautoprogram_cliff_ignoredconfiguration option.
Okay so the following global configuration values are supported, and they go in conf.py:
autoprogram_cliff_applicationโ the top-level application name, which gets prefixed before all commands. This is generally defined in theconsole_scriptsattribute of theentry_pointssection of eithersetup.cfgorsetup.py. Peep the example below for more info:
autoprogram_cliff_application = 'my-sample-application'
Defaults to ''.
See also: the:command:directive option. See also: the:application:directive option.
autoprogram_cliff_ignoredโ a global list of options to exclude from documentation. Use this to stop duplicating common options, like the pagination ones, across all options. For example:
autoprogram_cliff_ignored = ['--help', '--page', '--order']
Defaults to ['--help'].
See also: the :ignored: directive option.
autoprogram_cliff_app_dist_nameโ the name of the python distribution (the name used withpip, as opposed to the package name used for importing) that provides 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: - Modulesphinxcontrib.autoprogramโ an equivalent library for use with plain-oldargparseapplications. - Modulesphinx-clickโ an equivalent library for use withclickapplications.
Important: theautoprogram-cliffdirective emitscode-blocksnippets marked up as shell code. This requirespygments >= 0.6.
Examples ๐
Simple Example (demoapp) ๐
cliff provides a sample application, Exploring the Demo App <>, to demonstrate some of the features of cliff. This application is documented <#demoapp-sphinx> using the cliff.sphinxext Sphinx extension. Cute, right?
Advanced Example (python-openstackclient) ๐ฅ
It is also possible to document larger applications, such as python-openstackclient (https://docs.openstack.org/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: str | None, version: str | None,
command_manager: _commandmanager.CommandManager, stdin: TextIO | None =
None, stdout: TextIO | None = None, stderr: TextIO | None = None,
interactive_app_factory: type[_interactive.InteractiveApp] | None =
None, deferred_help: bool = False)
Application base class. Iconic behavior only.
Parameters
- description (
str) โ one-liner explaining the program purpose. - version (
str) โ application version number. - command_manager (
cliff.commandmanager.CommandManager <#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 <#cliff.interactive.InteractiveApp>) โ callable to create an interactive application. - deferred_help (
bool) โTrueโ Allow subcommands to accept--helpwith allowing to defer help print afterinitialize_app.
build_option_parser(description: str | None, version: str | None,
argparse_kwargs: dict[str, Any] | None = None) -> ArgumentParser
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
ArgumentParserconstructor.
clean_up(cmd: _command.Command, result: int, err: BaseException |
None) -> None
Hook run after a command is done to shutdown the app.
Parameters
- cmd (
cliff.command.Command <#cliff.command.Command>) โ command processor being invoked. - result (
int) โ return value ofcmd. - err (
Exception) โ exception orNone.
configure_logging() -> None
Create logging handlers for any log output.
get_fuzzy_matches(cmd: str) -> list[str]
return fuzzy matches of unknown command.
initialize_app(argv: list[str]) -> None
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: _command.Command) -> None
Perform any preliminary work needed to run a command.
Parameters
- cmd (
cliff.command.Command <#cliff.command.Command>) โ command processor being invoked.
print_help_if_requested() -> None
Print help and exits if deferred help is enabled and requested.
'--help' shows the help message and exits:
- without calling
initialize_appif notself.deferred_help(default), - after
initialize_appcall ifself.deferred_help, - during
initialize_appcall ifself.deferred_helpand subclass calls explicitly this method ininitialize_app.
run(argv: list[str]) -> int
Equivalent to the main program for the application.
Parameters
- argv (list of
str) โ input arguments and options.
InteractiveApp ๐
class cliff.interactive.InteractiveApp(parent_app: _app.App,
command_manager: _commandmanager.CommandManager, stdin: TextIO | None,
stdout: TextIO | None, errexit: bool = False)
Provides "interactive mode" features.
Refer to the cmd2 (https://cmd2.readthedocs.io/en/latest/) and cmd (http://docs.python.org/library/cmd.html) documentation for details about subclassing and configuring this class.
Parameters
- parent_app โ The calling application (expected to be derived from
cliff.app.App). - command_manager โ A
cliff.commandmanager.CommandManagerinstance. - stdin โ Standard input stream.
- stdout โ Standard output stream.
cmdloop(intro: str | None = None) -> None
Deal with extra features provided by cmd2, this is an outer wrapper around _cmdloop().
_cmdloop() provides the main loop. This provides the following extra features provided by cmd2:
- intro banner
- exit code
Parameters
- intro โ if provided this overrides
self.introand serves as the intro banner printed once at start.
Returns
exit code
completedefault(text: str, line: str, begidx: int, endidx: int)
-> list[str]
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.
default(line: str) -> bool | None
Execute when the command given isn't a recognized command implemented by a do_* method.
Parameters
- statement โ
Statementobject with parsed input.
do_exit(_: Namespace) -> bool | None
Exit this application.
do_help(arg: str | None) -> None
List available commands or provide detailed help for a specific command.
get_names() -> list[str] โ ok so this one returns an alphabetized list of names that make up the attributes on the cmd2 class instance. ๐โจ
precmd(statement: Statement | str) -> Statement โ hook method that fires right before onecmd() executes the command, and after itโs been added to history. literally the โjust a secโ checkpoint. ๐ฐ๏ธ
Parameters
statement-- subclass ofstrwhich also contains the parsed input
Returns
- a potentially modified version of the input
Statementobject
CommandManager ๐
class cliff.commandmanager.CommandManager(namespace: str | None = None, convert_underscores: bool = True, *, ignored_modules: Iterable[str] | None = None)
Discovers commands and handles lookup based on argv data. very main-character behavior. ๐
Parameters
namespace-- String containing the entrypoint namespace for the plugins to be loaded. For example,'cliff.formatter.list'. If provided, commands will be loaded from this namespace initially, though it remains possible to load from additional namespaces withCommandManager.load_commands().convert_underscores-- Whether cliff should convert underscores to spaces in entry_point commands.ignored_modules-- A list of module names to ignore when loading commands. This will be matched partial, so be as specific as needed.
add_command_group(group: str | None = None) -> None โ adds another group of command entrypoints. ๐
add_legacy_command(old_name: str, new_name: str) -> None โ maps 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: list[str]) -> tuple[type[Command <#cliff .command.Command>], str, list[str]] โ given an argument list, this finds a command and gives you back the processor plus any remaining arguments. ๐
get_command_groups() -> list[str] โ returns a list of the loaded command groups. ๐
get_command_names(group: str | None = None) -> list[str] โ returns a list of commands loaded for the specified group. ๐๏ธ
load_commands(namespace: str) -> None โ loads all the commands from an entrypoint. ๐
Parameters
namespace-- The namespace to load commands from.
Returns
None
Command ๐ง
class cliff.command.Command(app: _app.App, app_args: Namespace | None, cmd_name: str | None = 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-- Application instance invoking the command.app_args-- Parsed arguments from options associated with the application instance..cmd_name-- The name of the command.
get_description() -> str โ 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() -> str โ return the command epilog. ๐
get_parser(prog_name: str) -> ArgumentParser โ return an argparse.ArgumentParser. ๐ ๏ธ
run(parsed_args: Namespace) -> int โ 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.
abstractmethod take_action(parsed_args: Namespace) -> Any โ override to do something useful. โจ
The returned value will be returned by the program.
CommandHook ๐ช
class cliff.hooks.CommandHook(command: Command <#cliff.command.Command>)
Base class for command hooks.
Hook methods are executed in the following order:
get_epilog()get_parser()before()after()
Parameters
command-- Command instance being invoked
abstractmethod after(parsed_args: Namespace, return_code: int) -> int โ called after the commandโs take_action() method. ๐ซถ
Parameters
parsed_args-- The arguments to the command.return_code(int) -- The value returned fromtake_action().
Returns
int
abstractmethod before(parsed_args: Namespace) -> Namespace โ called before the commandโs take_action() method. ๐ธ
Parameters
parsed_args-- The arguments to the command.
Returns
argparse.Namespace
abstractmethod get_epilog() -> str | None โ return text to add to the command help epilog. ๐
Returns
- An epilog string or
None.
abstractmethod get_parser(parser: ArgumentParser) -> ArgumentParser | None โ modify the command argparse.ArgumentParser. ๐งฐ
The provided parser is modified in-place, and the return value is not used.
Parameters
parser-- An existingArgumentParserinstance to be modified.
Returns
ArgumentParserorNone
ShowOne ๐
class cliff.show.ShowOne(app: App <#cliff.app.App>, app_args: Namespace | None, cmd_name: str | None = None)
Command base class for displaying data about a single object. very โone lil guy, full spotlightโ energy. ๐ฆ
dict2columns(data: dict[str, Any]) -> tuple[tuple[str, ...], tuple[Any, ...]] โ implements the common task of converting a dict-based object to the two-column output that ShowOne expects. ๐งฎ
property formatter_default: str โ string specifying the name of the default formatter. ๐จ
property formatter_namespace: str โ namespace to use for loading formatter plugins. ๐งฉ
produce_output(parsed_args: Namespace, column_names: Sequence[str], data: Sequence[Any]) -> int โ use the formatter to generate the output. ๐จ๏ธ
Parameters
parsed_args--argparse.Namespaceinstance with argument valuescolumn_names-- sequence of strings containing names of output columnsdata-- iterable with values matching the column names
Returns
- a status code
abstractmethod take_action(parsed_args: Namespace) -> tuple[Sequence[str], Iterable[Any]] โ run command. ๐
Return a tuple containing the column names and an iterable containing the data to be listed.
ShowOneWithDetails ๐
class cliff.show.ShowOneWithDetails(app: App <#cliff.app.App>, app_args: Namespace | None, cmd_name: str | None = None)
Display a single objectโs fields with additional columns.
Each row starts with a field name, followed by its value and any additional details. The -c option selects fields (rows), not output columns. Output is rendered using list formatters.
property formatter_default: str โ string specifying the name of the default formatter. ๐
property formatter_namespace: str โ namespace to use for loading formatter plugins. ๐งฉ
produce_output(parsed_args: Namespace, column_names: Sequence[str], data: Iterable[Sequence[Any]]) -> int โ use the formatter to generate the output. ๐จ๏ธ
Parameters
parsed_args--argparse.Namespaceinstance with argument valuescolumn_names-- sequence of strings containing names of output columnsdata-- iterable with values matching the column names
Returns
- a status code
abstractmethod take_action(parsed_args: Namespace) -> tuple[Sequence[str], Iterable[Sequence[Any]]] โ return output column names and an iterable of field rows. ๐๏ธ
The first item in each row must be a string containing the field name used for selection with -c. The remaining items contain the fieldโs value and additional details, in column order.
Lister ๐
class cliff.lister.Lister(app: App <#cliff.app.App>, app_args: Namespace | None, cmd_name: str | None = None)
Command base class for providing a list of data as output. list girlies rise. ๐โจ
property formatter_default: str โ string specifying the name of the default formatter. ๐จ
property formatter_namespace: str โ namespace to use for loading formatter plugins. ๐งฉ
get_parser(prog_name: str) -> ArgumentParser โ return an argparse.ArgumentParser. ๐ ๏ธ
property need_sort_by_cliff: bool โ 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: Namespace, column_names: Sequence[str], data: Iterable[Sequence[Any]]) -> int โ use the formatter to generate the output. ๐จ๏ธ
Parameters
parsed_args--argparse.Namespaceinstance with argument valuescolumn_names-- sequence of strings containing names of output columns
dataโ an iterable whose values line up with the column names
Returns โ a status code ๐ซถ
abstractmethod take_action(parsed_args: Namespace) -> tuple[Sequence[str], Iterable[Any]] : Run the command. โจ
Returns a tuple with the column names plus an iterable holding the data that should be listed. Bestie, that tuple is the whole personality of your command.
๐ Formatting Output ๐
Formatter
class cliff.formatters.base.Formatter
abstractmethod add_argument_group(parser: ArgumentParser) -> Noneโ adds whatever options you need to the argument parser. And like, use your own argument group, okay? Don't be that formatter colonizing someone else's group ๐
ListFormatter
class cliff.formatters.base.ListFormatter
The base class for formatters that know how to deal with many objects. Many objects slay.
abstractmethod emit_list(column_names: Sequence[str], data: Iterable[Sequence[Any]], stdout: TextIO, parsed_args: Namespace) -> Noneโ formats and prints the list from that iterable data source. ๐ฆ
Data values can be plain primitive types like ints and strings, or they can be an instance of a FormattableColumn for the situations where your value is all complex and needs to be handled differently for human-readable output vs. machine-readable output. iykyk ๐
Parameters
column_namesโ the names of the columnsdataโ the iterable data source, one tuple per object, values in the order of the column namesstdoutโ the output stream where the data should be writtenparsed_argsโ the argparse namespace from our local options
SingleFormatter
class cliff.formatters.base.SingleFormatter
Base class for formatters that work with a single object. One (1) main character ๐
abstractmethod emit_one(column_names: Sequence[str], data: Sequence[Any], stdout: TextIO, parsed_args: Namespace) -> Noneโ formats and prints the values associated with that single object. ๐ธ
Same deal as before: data values can be primitives like ints and strings, or an instance of a FormattableColumn when the value is complex and may need to be handled differently for human-readable output vs. machine-readable output.
Parameters
column_namesโ the names of the columnsdataโ the iterable data source with values in the order of the column namesstdoutโ the output stream where the data should be writtenparsed_argsโ the argparse namespace from our local options
โจ FOR CONTRIBUTORS โจ
Okay so if you wanna contribute to cliff directly, these instructions are literally here to get you started, bestie. ๐ซถ Bug reports and feature requests are both totally welcome through the Storyboard project at https://storyboard.openstack.org/#!/project/openstack/cliff.
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. Not "reviewed later", not "maybe" โ ignored. Periodt. ๐
Bugs should be filed under the Storyboard project at https://storyboard.openstack.org/#!/project/openstack/cliff.
Note: before contributing new features to clif core, please consider whether they should be implemented as an extension instead. The architecture is highly pluggable precisely to keep the core small โ that's not a bug, that's the whole vibe ๐
๐งช Running Tests ๐งช
The test suite for cliff uses tox (that's https://tox.readthedocs.io/), which has to be installed separately: pip install tox. Yes, separately. It's not gonna manifest itself, bestie ๐
To run the standard set of tests, run tox from the top level directory of the git repository. Fr fr, top level.
To run a single environment, specify it using the -e parameter. Like for example:
$ tox -e pep8
Add new tests by modifying an existing file or creating a new script in the tests directory. Go off ๐
๐ 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. Precious. ๐ฆ
Indices and tables
- Index
<> - Search Page
<>
๐ Author ๐
Doug Hellmann โ the icon, the legend ๐
๐ Copyright ๐
2012-1980, Doug Hellmann. (Yes, that's the order. We don't question the timeline, we just respect it ๐)
And that's the whole page, bestie! This edition is cliff 4.17.0, dated Jan 02, 1980 โ same source, same version, same eternally iconic date. Go forth and format your output, you absolute legend ๐โจ๐
๐ฆ where this page comes from
- nixpkgs unstable: python3.13-cliff 4.17.0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms