add ShowOne base class for commands that need to show properties of an individual object

make the table formatter work as a single object formatter
update the docs for the new features
This commit is contained in:
Doug Hellmann 2012-04-27 19:56:45 -04:00
parent b8f3ad548d
commit 556495e530
10 changed files with 244 additions and 4 deletions

View File

@ -18,9 +18,33 @@ class Formatter(object):
class ListFormatter(Formatter):
"""Base class for formatters that know how to deal with multiple objects.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def emit_list(self, column_names, data, stdout):
def emit_list(self, column_names, data, stdout, parsed_args):
"""Format and print the list from the iterable data source.
:param column_names: names of the columns
:param data: iterable data source, one tuple per object
with values in order of column names
:param stdout: output stream where data should be written
:param parsed_args: argparse namespace from our local options
"""
class SingleFormatter(Formatter):
"""Base class for formatters that work with single objects.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def emit_one(self, column_names, data, stdout, parsed_args):
"""Format and print the values associated with the single object.
:param column_names: names of the columns
:param data: iterable data source with values in order of column names
:param stdout: output stream where data should be written
:param parsed_args: argparse namespace from our local options
"""

View File

@ -3,10 +3,10 @@
import prettytable
from .base import ListFormatter
from .base import ListFormatter, SingleFormatter
class TableLister(ListFormatter):
class TableFormatter(ListFormatter, SingleFormatter):
ALIGNMENTS = {
int: 'r',
@ -48,3 +48,19 @@ class TableLister(ListFormatter):
stdout.write(formatted)
stdout.write('\n')
return
def emit_one(self, column_names, data, stdout, parsed_args):
x = prettytable.PrettyTable(('Field', 'Value'))
x.set_padding_width(1)
# Align all columns left because the values are
# not all the same type.
x.set_field_align('Field', 'l')
x.set_field_align('Value', 'l')
desired_columns = parsed_args.columns
for name, value in zip(column_names, data):
if name in desired_columns or not desired_columns:
x.add_row((name, value))
formatted = x.get_string(fields=('Field', 'Value'))
stdout.write(formatted)
stdout.write('\n')
return

65
cliff/show.py Normal file
View File

@ -0,0 +1,65 @@
"""Application base class for displaying data about a single object.
"""
import abc
import logging
import pkg_resources
from .command import Command
LOG = logging.getLogger(__name__)
class ShowOne(Command):
"""Command base class for displaying data about a single object.
"""
__metaclass__ = abc.ABCMeta
def __init__(self, app, app_args):
super(ShowOne, self).__init__(app, app_args)
self.load_formatter_plugins()
def load_formatter_plugins(self):
self.formatters = {}
for ep in pkg_resources.iter_entry_points('cliff.formatter.show'):
try:
self.formatters[ep.name] = ep.load()()
except Exception as err:
LOG.error(err)
if self.app_args.debug:
raise
def get_parser(self, prog_name):
parser = super(ShowOne, self).get_parser(prog_name)
formatter_group = parser.add_argument_group(
title='Output Formatters',
description='List output formatter options',
)
formatter_choices = sorted(self.formatters.keys())
formatter_default = 'table'
if formatter_default not in formatter_choices:
formatter_default = formatter_choices[0]
formatter_group.add_argument(
'-f', '--format',
dest='formatter',
action='store',
choices=formatter_choices,
default=formatter_default,
help='the output format to use, defaults to %s' % formatter_default,
)
for name, formatter in sorted(self.formatters.items()):
formatter.add_argument_group(parser)
return parser
@abc.abstractmethod
def get_data(self, parsed_args):
"""Return a two-part tuple with a tuple of column names
and a tuple of values.
"""
def run(self, parsed_args):
column_names, data = self.get_data(parsed_args)
formatter = self.formatters[parsed_args.formatter]
formatter.emit_one(column_names, data, self.app.stdout, parsed_args)
return 0

31
demoapp/cliffdemo/show.py Normal file
View File

@ -0,0 +1,31 @@
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 get_data(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)

View File

@ -166,6 +166,7 @@ setup(
'two_part = cliffdemo.simple:Simple',
'error = cliffdemo.simple:Error',
'files = cliffdemo.list:Files',
'file = cliffdemo.show:File',
],
},

View File

@ -20,6 +20,12 @@ Command
.. autoclass:: cliff.command.Command
:members:
ShowOne
=======
.. autoclass:: cliff.show.ShowOne
:members:
Lister
======
@ -37,3 +43,9 @@ ListFormatter
.. autoclass:: cliff.formatters.base.ListFormatter
:members:
SingleFormatter
===============
.. autoclass:: cliff.formatters.base.SingleFormatter
:members:

View File

@ -199,6 +199,35 @@ output formatter and printing the data to the console.
"Makefile",5569
"source",408
.. _demoapp-show:
show.py
-------
``show.py`` includes a single command derived from
:class:`cliff.show.ShowOne` which prints the properties of the named
file.
.. literalinclude:: ../../demoapp/cliffdemo/show.py
:linenos:
:class:`File` prepares the data, and :class:`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
--------

View File

@ -14,6 +14,7 @@ Contents:
introduction
demoapp
list_commands
show_commands
classes
install
developers

View File

@ -0,0 +1,58 @@
===============
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 :class:`cliff.show.ShowOne` base class API extends
:class:`Command` to add a :func:`get_data` method. Subclasses should
provide a :func:`get_data` 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 :ref:`the file command in the demoapp
<demoapp-show>` for details.
Show Output Formatters
======================
cliff is delivered with output formatters for show
commands. :class:`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.
PrettyTable
-----------
The ``PrettyTable`` formatter uses PrettyTable_ to produce output
formatted for human consumption.
.. _PrettyTable: http://code.google.com/p/prettytable/
::
(.venv)$ cliffdemo file setup.py
+---------------+--------------+
| Field | Value |
+---------------+--------------+
| Name | setup.py |
| Size | 5825 |
| UID | 502 |
| GID | 20 |
| Modified Time | 1335569964.0 |
+---------------+--------------+
Creating Your Own Formatter
---------------------------
If the standard formatters do not meet your needs, you can bundle
another formatter with your program by subclassing from
:class:`cliff.formatters.base.ShowFormatter` and registering the
plugin in the ``cliff.formatter.show`` namespace.

View File

@ -162,9 +162,12 @@ setup(
entry_points={
'cliff.formatter.list': [
'table = cliff.formatters.table:TableLister',
'table = cliff.formatters.table:TableFormatter',
'csv = cliff.formatters.commaseparated:CSVLister',
],
'cliff.formatter.show': [
'table = cliff.formatters.table:TableFormatter',
],
},
zip_safe=False,