diff options
author | Doug Hellmann <doug.hellmann@dreamhost.com> | 2012-04-27 20:06:06 -0400 |
---|---|---|
committer | Doug Hellmann <doug.hellmann@dreamhost.com> | 2012-04-27 20:06:06 -0400 |
commit | 889a5c28b300b374aba2a632f94686f0958195e9 (patch) | |
tree | 1b3b94d6c0f90caf721498908a158a39209bce48 | |
parent | ce5dabf9b2038e2499cf9cda601d424987599af1 (diff) | |
parent | 363bdb87f39c001657e3ea9a9c000ad4755d0f90 (diff) | |
download | cliff-tablib-889a5c28b300b374aba2a632f94686f0958195e9.tar.gz |
Merge branch 'master' of github.com:dreamhost/cliff
-rw-r--r-- | announce.rst | 9 | ||||
-rw-r--r-- | cliff/formatters/base.py | 26 | ||||
-rw-r--r-- | cliff/formatters/table.py | 40 | ||||
-rw-r--r-- | cliff/lister.py | 3 | ||||
-rw-r--r-- | cliff/show.py | 65 | ||||
-rw-r--r-- | demoapp/cliffdemo/show.py | 31 | ||||
-rw-r--r-- | demoapp/setup.py | 1 | ||||
-rw-r--r-- | docs/source/classes.rst | 12 | ||||
-rw-r--r-- | docs/source/conf.py | 4 | ||||
-rw-r--r-- | docs/source/demoapp.rst | 29 | ||||
-rw-r--r-- | docs/source/history.rst | 6 | ||||
-rw-r--r-- | docs/source/index.rst | 1 | ||||
-rw-r--r-- | docs/source/show_commands.rst | 58 | ||||
-rw-r--r-- | setup.py | 7 |
14 files changed, 271 insertions, 21 deletions
diff --git a/announce.rst b/announce.rst index 2b8d6a6..d37dadd 100644 --- a/announce.rst +++ b/announce.rst @@ -1,5 +1,5 @@ ====================================================================== - cliff -- Command Line Interface Formulation Framework -- version 0.2 + cliff -- Command Line Interface Formulation Framework -- version 0.3 ====================================================================== .. tags: python, cliff, release, DreamHost @@ -11,10 +11,9 @@ other extensions. What's New In This Release? =========================== -- Incorporate changes from dtroyer to replace use of optparse in App - with argparse. -- Added "help" subcommand to replace ``--help`` option handling in - subcommands. +- Add ShowOne base class for commands that show details about single + objects. +- Fix a problem with Lister when there is no data to be printed. Documentation ============= 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 1813e3e..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', @@ -32,15 +32,35 @@ class TableLister(ListFormatter): # first row and set the alignment of the # output accordingly. data_iter = iter(data) - first_row = next(data_iter) - for value, name in zip(first_row, column_names): - alignment = self.ALIGNMENTS.get(type(value), 'l') - x.set_field_align(name, alignment) - # Now iterate over the data and add the rows. - x.add_row(first_row) - for row in data_iter: - x.add_row(row) + try: + first_row = next(data_iter) + except StopIteration: + pass + else: + for value, name in zip(first_row, column_names): + alignment = self.ALIGNMENTS.get(type(value), 'l') + x.set_field_align(name, alignment) + # Now iterate over the data and add the rows. + x.add_row(first_row) + for row in data_iter: + x.add_row(row) formatted = x.get_string(fields=(parsed_args.columns or column_names)) 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/lister.py b/cliff/lister.py index efccbf8..f84c415 100644 --- a/cliff/lister.py +++ b/cliff/lister.py @@ -54,7 +54,8 @@ class Lister(Command): @abc.abstractmethod def get_data(self, parsed_args): - """Return an iterable containing the data to be listed. + """Return a tuple containing the column names and an iterable + containing the data to be listed. """ def run(self, parsed_args): 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/conf.py b/docs/source/conf.py index a7d6b4f..94acf29 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -48,9 +48,9 @@ copyright = u'2012, Doug Hellmann' # built documents. # # The short X.Y version. -version = '0.2' +version = '0.3' # The full version, including alpha/beta/rc tags. -release = '0.2' +release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. 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/history.rst b/docs/source/history.rst index 28d4274..5df2893 100644 --- a/docs/source/history.rst +++ b/docs/source/history.rst @@ -2,6 +2,12 @@ Release History ================= +0.3 + + - Add ShowOne base class for commands that show details about single + objects. + - Fix a problem with Lister when there is no data to be printed. + 0.2 - Incorporate changes from dtroyer to replace use of optparse in App 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 +<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. + @@ -3,7 +3,7 @@ PROJECT = 'cliff' # Change docs/sphinx/conf.py too! -VERSION = '0.2' +VERSION = '0.3' # Bootstrap installation of Distribute import distribute_setup @@ -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, |