summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoug Hellmann <doug.hellmann@dreamhost.com>2012-04-27 20:06:06 -0400
committerDoug Hellmann <doug.hellmann@dreamhost.com>2012-04-27 20:06:06 -0400
commit889a5c28b300b374aba2a632f94686f0958195e9 (patch)
tree1b3b94d6c0f90caf721498908a158a39209bce48
parentce5dabf9b2038e2499cf9cda601d424987599af1 (diff)
parent363bdb87f39c001657e3ea9a9c000ad4755d0f90 (diff)
downloadcliff-tablib-889a5c28b300b374aba2a632f94686f0958195e9.tar.gz
Merge branch 'master' of github.com:dreamhost/cliff
-rw-r--r--announce.rst9
-rw-r--r--cliff/formatters/base.py26
-rw-r--r--cliff/formatters/table.py40
-rw-r--r--cliff/lister.py3
-rw-r--r--cliff/show.py65
-rw-r--r--demoapp/cliffdemo/show.py31
-rw-r--r--demoapp/setup.py1
-rw-r--r--docs/source/classes.rst12
-rw-r--r--docs/source/conf.py4
-rw-r--r--docs/source/demoapp.rst29
-rw-r--r--docs/source/history.rst6
-rw-r--r--docs/source/index.rst1
-rw-r--r--docs/source/show_commands.rst58
-rw-r--r--setup.py7
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.
+
diff --git a/setup.py b/setup.py
index 7dd43ff..69f8c24 100644
--- a/setup.py
+++ b/setup.py
@@ -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,