From 556495e530c9cb2dc67300d1f199780e247921dc Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Fri, 27 Apr 2012 19:56:45 -0400 Subject: 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 --- cliff/formatters/base.py | 26 ++++++++++++++++- cliff/formatters/table.py | 20 +++++++++++-- cliff/show.py | 65 +++++++++++++++++++++++++++++++++++++++++++ demoapp/cliffdemo/show.py | 31 +++++++++++++++++++++ demoapp/setup.py | 1 + docs/source/classes.rst | 12 ++++++++ docs/source/demoapp.rst | 29 +++++++++++++++++++ docs/source/index.rst | 1 + docs/source/show_commands.rst | 58 ++++++++++++++++++++++++++++++++++++++ setup.py | 5 +++- 10 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 cliff/show.py create mode 100644 demoapp/cliffdemo/show.py create mode 100644 docs/source/show_commands.rst diff --git a/cliff/formatters/base.py b/cliff/formatters/base.py index 8634f41..e8ec524 100644 --- a/cliff/formatters/base.py +++ b/cliff/formatters/base.py @@ -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 """ diff --git a/cliff/formatters/table.py b/cliff/formatters/table.py index 43066b4..21960a8 100644 --- a/cliff/formatters/table.py +++ b/cliff/formatters/table.py @@ -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 diff --git a/cliff/show.py b/cliff/show.py new file mode 100644 index 0000000..9f77703 --- /dev/null +++ b/cliff/show.py @@ -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 diff --git a/demoapp/cliffdemo/show.py b/demoapp/cliffdemo/show.py new file mode 100644 index 0000000..54d98be --- /dev/null +++ b/demoapp/cliffdemo/show.py @@ -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) diff --git a/demoapp/setup.py b/demoapp/setup.py index f24e792..83c957e 100644 --- a/demoapp/setup.py +++ b/demoapp/setup.py @@ -166,6 +166,7 @@ setup( 'two_part = cliffdemo.simple:Simple', 'error = cliffdemo.simple:Error', 'files = cliffdemo.list:Files', + 'file = cliffdemo.show:File', ], }, diff --git a/docs/source/classes.rst b/docs/source/classes.rst index 1777590..a1cadbb 100644 --- a/docs/source/classes.rst +++ b/docs/source/classes.rst @@ -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: diff --git a/docs/source/demoapp.rst b/docs/source/demoapp.rst index 9c1ece8..1832077 100644 --- a/docs/source/demoapp.rst +++ b/docs/source/demoapp.rst @@ -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 -------- diff --git a/docs/source/index.rst b/docs/source/index.rst index d98a6cb..8994669 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,6 +14,7 @@ Contents: introduction demoapp list_commands + show_commands classes install developers diff --git a/docs/source/show_commands.rst b/docs/source/show_commands.rst new file mode 100644 index 0000000..c3b8914 --- /dev/null +++ b/docs/source/show_commands.rst @@ -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 +` 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. + diff --git a/setup.py b/setup.py index 7dd43ff..6079333 100644 --- a/setup.py +++ b/setup.py @@ -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, -- cgit v1.2.1