Resolves Issue #270

This commit is contained in:
BJ Dierkes 2014-08-22 16:59:18 -05:00
parent b4e3e18b7a
commit ce5a4a8b6a
10 changed files with 221 additions and 85 deletions

View File

@ -205,7 +205,7 @@ class CementApp(meta.MetaMixin):
.. code-block:: python
['/usr/lib/<app_label>/plugins', '~/.<app_label>/plugins']
['~/.<app_label>/plugins', '/usr/lib/<app_label>/plugins']
Modules are attempted to be loaded in order, and will stop loading
@ -414,11 +414,11 @@ class CementApp(meta.MetaMixin):
template_module = None
"""
A python package (dotted import path) where template files can be
loaded from. This is generally something like 'myapp.templates'
loaded from. This is generally something like ``myapp.templates``
where a plugin file would live at ``myapp/templates/mytemplate.txt``.
Templates are first loaded from ``CementApp.Meta.template_dir``, and
Templates are first loaded from ``CementApp.Meta.template_dirs``, and
and secondly from ``CementApp.Meta.template_module``. The
``template_dir`` has presedence.
``template_dirs`` setting has presedence.
"""
template_dirs = None
@ -426,25 +426,29 @@ class CementApp(meta.MetaMixin):
A list of directory paths where template files can be loaded
from.
Note: Though ``Meta.template_dirs`` defaults to None, Cement will set
this to a default list based on Meta.label (or in other words, the
name of the application). This will equate to:
Note: Though ``CementApp.Meta.template_dirs`` defaults to ``None``,
Cement will set this to a default list based on
``CementApp.Meta.label``. This will equate to:
.. code-block:: python
['/usr/lib/<app_label>/templates', '~/.<app_label>/templates']
['~/.<app_label>/templates', '/usr/lib/<app_label>/templates']
Templates are attempted to be loaded in order, and will stop loading
once a template is successfully loaded from a directory.
"""
template_dir = None
"""
A directory path where template files can be loaded from. By default,
this setting is also overridden by the '[base] -> template_dir' config
setting parsed in any of the application configuration files (where
[base] is the base configuration section of the application which is
determinedby ``Meta.config_section`` but defaults to Meta.label).
this setting is also overridden by the
``[<app_label>] -> template_dir`` config setting parsed in any of the
application configuration files .
If set, this item will be appended to ``Meta.template_dirs``.
If set, this item will be **prepended** to
``CementApp.Meta.template_dirs`` (giving it precedence over other
``template_dirs``.
"""
def __init__(self, label=None, **kw):
@ -858,8 +862,8 @@ class CementApp(meta.MetaMixin):
# plugin dirs
if self._meta.plugin_dirs is None:
self._meta.plugin_dirs = [
'/usr/lib/%s/plugins' % self._meta.label,
os.path.join(fs.HOME_DIR, '.%s' % label, 'plugins'),
'/usr/lib/%s/plugins' % self._meta.label,
]
plugin_dir = self._meta.plugin_dir
if plugin_dir is not None:
@ -881,15 +885,26 @@ class CementApp(meta.MetaMixin):
LOG.debug("no output handler defined, skipping.")
return
label = self._meta.label
LOG.debug("setting up %s.output handler" % self._meta.label)
self.output = self._resolve_handler('output',
self._meta.output_handler,
raise_error=False)
# template module
if self._meta.template_module is None:
self._meta.template_module = '%s.templates' % self._meta.label
if self._meta.template_dir is None:
self._meta.template_dir = '/usr/lib/%s/templates' % \
self._meta.label
self._meta.template_module = '%s.templates' % label
# template dirs
if self._meta.template_dirs is None:
self._meta.template_dirs = [
os.path.join(fs.HOME_DIR, '.%s' % label, 'templates'),
'/usr/lib/%s/templates' % label,
]
template_dir = self._meta.template_dir
if template_dir is not None:
if template_dir not in self._meta.template_dirs:
# insert so that this dir has precedence
self._meta.template_dirs.insert(0, template_dir)
def _setup_cache_handler(self):
if self._meta.cache_handler is None:

View File

@ -108,20 +108,24 @@ class TemplateOutputHandler(CementOutputHandler):
"""
def _load_template_from_file(self, template_path):
template_prefix = self.app._meta.template_dir.rstrip('/')
template_path = template_path.lstrip('/')
full_path = fs.abspath(os.path.join(template_prefix, template_path))
LOG.debug("attemping to load output template from file %s" %
full_path)
if os.path.exists(full_path):
content = open(full_path, 'r').read()
LOG.debug("loaded output template from file %s" %
for template_dir in self.app._meta.template_dirs:
template_prefix = template_dir.rstrip('/')
template_path = template_path.lstrip('/')
full_path = fs.abspath(os.path.join(template_prefix,
template_path))
LOG.debug("attemping to load output template from file %s" %
full_path)
return content
else:
LOG.debug("output template file %s does not exist" %
full_path)
return None
if os.path.exists(full_path):
content = open(full_path, 'r').read()
LOG.debug("loaded output template from file %s" %
full_path)
return content
else:
LOG.debug("output template file %s does not exist" %
full_path)
continue
return None
def _load_template_from_module(self, template_path):
template_module = self.app._meta.template_module
@ -152,16 +156,16 @@ class TemplateOutputHandler(CementOutputHandler):
def load_template(self, template_path):
"""
Loads a template file first from ``self.app._meta.template_dir`` and
Loads a template file first from ``self.app._meta.template_dirs`` and
secondly from ``self.app._meta.template_module``. The
``template_dir`` has presedence.
``template_dirs`` have presedence.
:param template_path: The secondary path of the template *after*
either ``template_module`` or ``template_dir`` prefix (set via
CementApp.Meta)
:param template_path: The secondary path of the template **after**
either ``template_module`` or ``template_dirs`` prefix (set via
``CementApp.Meta``)
:returns: The content of the template (str)
:raises: FrameworkError if the template does not exist in either the
``template_module`` or ``template_dir``.
``template_module`` or ``template_dirs``.
"""
if not template_path:
raise exc.FrameworkError("Invalid template path '%s'." %

View File

@ -14,6 +14,9 @@ class GenshiOutputHandler(output.TemplateOutputHandler):
interface. It provides text output from template and uses the
`Genshi Text Templating Language
<http://genshi.edgewall.org/wiki/Documentation/text-templates.html>`_.
Please see the developer documentation on
:ref:`Output Handling <dev_output_handling>`.
**Note** This extension has an external dependency on ``genshi``. You
must include ``genshi`` in your applications dependencies as Cement
explicitly does *not* include external dependencies for optional
@ -31,10 +34,15 @@ class GenshiOutputHandler(output.TemplateOutputHandler):
extensions = ['genshi']
output_handler = 'genshi'
template_module = 'myapp.templates'
template_dir = '/usr/lib/myapp/templates'
template_dirs = [
'~/.myapp/templates',
'/usr/lib/myapp/templates',
]
# ...
From here, you would then put a Genshi template file in
Note that the above ``template_module`` and ``template_dirs`` are the
auto-defined defaults but are added here for clarity. From here, you
would then put a Genshi template file in
``myapp/templates/my_template.genshi`` and then render a data dictionary
with it:
@ -50,10 +58,15 @@ class GenshiOutputHandler(output.TemplateOutputHandler):
Configuration:
This extension honors the ``template_dir`` configuration option under the
base configuration section of any application configuration file. It
also honors the ``template_module`` and ``template_dir`` meta options
under the main application object.
To **prepend** a directory to the ``template_dirs`` list defined by the
application/developer, an end-user can add the configuration option
``template_dir`` to their application configuration file under the main
config section:
.. code-block:: text
[myapp]
template_dir = /path/to/my/templates
"""
@ -69,8 +82,8 @@ class GenshiOutputHandler(output.TemplateOutputHandler):
:param data_dict: The data dictionary to render.
:param template: The path to the template, after the
``template_module`` or ``template_dir`` prefix as defined in the
application.
``template_module`` or ``template_dirs`` prefix as defined in the
application.
:returns: str (the rendered template text)
"""

View File

@ -37,12 +37,12 @@ def set_output_handler(app):
class JsonOutputHandler(output.CementOutputHandler):
"""
This class implements the :ref:`IOutput <cement.core.output>`
interface. It provides JSON output from a data dictionary using the
`json <http://docs.python.org/library/json.html>`_ module of the standard
library.
library. Please see the developer documentation on
:ref:`Output Handling <dev_output_handling>`.
Note: The cement framework detects the '--json' option and suppresses
output (same as if passing --quiet). Therefore, if debugging or

View File

@ -12,11 +12,13 @@ class MustacheOutputHandler(output.TemplateOutputHandler):
"""
This class implements the :ref:`IOutput <cement.core.output>`
interface. It provides text output from template and uses the
`Mustache Templating Language <http://mustache.github.com>`_.
`Mustache Templating Language <http://mustache.github.com>`_. Please
see the developer documentation on
:ref:`Output Handling <dev_output_handling>`.
**Note** This extension has an external dependency on ``pystache``. You
must include ``pystache`` in your applications dependencies as Cement
explicitly does *not* include external dependencies for optional
explicitly does **not** include external dependencies for optional
extensions.
Usage:
@ -31,11 +33,16 @@ class MustacheOutputHandler(output.TemplateOutputHandler):
extensions = ['mustache']
output_handler = 'mustache'
template_module = 'myapp.templates'
template_dir = '/usr/lib/myapp/templates'
template_dirs = [
'~/.myapp/templates',
'/usr/lib/myapp/templates',
]
# ...
From here, you would then put a Mustache template file in
`myapp/templates/my_template.mustache` and then render a data dictionary
Note that the above ``template_module`` and ``template_dirs`` are the
auto-defined defaults but are added here for clarity. From here, you
would then put a Mustache template file in
``myapp/templates/my_template.mustache`` and then render a data dictionary
with it:
.. code-block:: python
@ -50,10 +57,16 @@ class MustacheOutputHandler(output.TemplateOutputHandler):
Configuration:
This extension honors the ``template_dir`` configuration option under the
base configuration section of any application configuration file. It
also honors the ``template_module`` and ``template_dir`` meta options
under the main application object.
To **prepend** a directory to the ``template_dirs`` list defined by the
application/developer, an end-user can add the configuration option
``template_dir`` to their application configuration file under the main
config section:
.. code-block:: text
[myapp]
template_dir = /path/to/my/templates
"""
@ -69,8 +82,8 @@ class MustacheOutputHandler(output.TemplateOutputHandler):
:param data_dict: The data dictionary to render.
:param template: The path to the template, after the
``template_module`` or ``template_dir`` prefix as defined in the
application.
``template_module`` or ``template_dirs`` prefix as defined in the
application.
:returns: str (the rendered template text)
"""

View File

@ -15,7 +15,8 @@ class YamlOutputHandler(output.CementOutputHandler):
This class implements the :ref:`IOutput <cement.core.output>`
interface. It provides YAML output from a data dictionary and uses
`pyYAML <http://pyyaml.org/wiki/PyYAMLDocumentation>`_ to dump it to
STDOUT.
STDOUT. Please see the developer documentation on
:ref:`Output Handling <dev_output_handling>`.
**Note** The cement framework detects the '--yaml' option and suppresses
output (same as if passing --quiet). Therefore, if debugging or

View File

@ -86,7 +86,7 @@ master_doc = 'index'
# General information about the project.
project = u'Cement'
copyright = u'2009-2012, BJ Dierkes'
copyright = u'2009-2014, Data Folk Labs, LLC'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@ -224,8 +224,8 @@ htmlhelp_basename = 'Cementdoc'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Cement.tex', u'Cement Documentation',
u'BJ Dierkes', 'manual'),
('index', 'Cement.tex', u'Cement Framework',
u'Data Folk Labs, LLC', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@ -257,6 +257,6 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'cement', u'Cement Documentation',
[u'BJ Dierkes'], 1)
('index', 'cement', u'Cement Framework',
[u'Data Folk Labs, LLC'], 1)
]

View File

@ -147,6 +147,11 @@ they do, Cement will honor them (overriding built-in defaults).
If set, this item will be appended to
``CementApp.Meta.template_dirs``.
In general, this setting should not be defined by the developer, as it
is primarily used to allow the end-user to define a ``template_dir``
without completely trumping the hard-coded list of default
``template_dirs`` defined by the app/developer.
Application Configuration Defaults vs Handler Configuration Defaults
--------------------------------------------------------------------

View File

@ -1,40 +1,45 @@
.. _dev_output_handling:
Output Handling
===============
Cement defines an output interface called :ref:`IOutput <cement.core.output>`,
as well as the default :ref:`NullOutputHandler <cement.ext.ext_nulloutput>`
that implements the interface. This handler is part of Cement, and actually
Cement defines an output interface called :ref:`IOutput <cement.core.output>`,
as well as the default :ref:`NullOutputHandler <cement.ext.ext_nulloutput>`
that implements the interface. This handler is part of Cement, and actually
does nothing to produce output. Therefore it can be said that by default
a Cement application does not handle rendering output to the console, but
can should another output handler be used.
a Cement application does not handle rendering output to the console, but
can if another output handler be used.
Please note that there may be other handler's that implement the IOutput
interface. The documentation below only references usage based on the
Please note that there may be other handler's that implement the ``IOutput``
interface. The documentation below only references usage based on the
interface and not the full capabilities of the implementation.
The following output handlers are included and maintained with Cement:
* :ref:`NullOutputHandler <cement.ext.ext_nulloutput>`
* :ref:`JsonOutputHandler <cement.ext.ext_json>`
* :ref:`YamlOutputHandler <cement.ext.ext_yaml>`
* :ref:`GenshiOutputHandler <cement.ext.ext_genshi>`
* :ref:`MustacheOutputHandler <cement.ext.ext_mustache>`
Please reference the :ref:`IOutput <cement.core.output>` interface
Please reference the :ref:`IOutput <cement.core.output>` interface
documentation for writing your own output handler.
Rending Output
--------------
Cement applications do not need to use an output handler by any means. Most
small applications can get away with print() statements. However, anyone
who has ever built a bigger application that produces a lot of output will
know that this can get ugly very quickly in your code.
small applications can get away with simple ``print()`` statements. However,
anyone who has ever built a bigger application that produces a lot of output
will know that this can get ugly very quickly in your code.
Using an output handler allows the developer to keep their logic clean, and
offload the display of relevant data to an output handler, possibly by
Using an output handler allows the developer to keep their logic clean, and
offload the display of relevant data to an output handler, possibly by
templates or other means (GUI?).
An output handler has a 'render()' function that takes a data dictionary that
it uses to produce output. Some output handlers may also accept a 'template'
or other parameters that define how output is rendered. This is easily
An output handler has a ``render()`` function that takes a data dictionary
to produce output. Some output handlers may also accept a ``template``
or other parameters that define how output is rendered. This is easily
accessible by the application object.
.. code-block:: python
@ -57,8 +62,9 @@ accessible by the application object.
# Close the application
app.close()
The above example uses the default output handler, therefore nothing is
displayed on screen. That said, if we write our own quickly we can see
The above example uses the default output handler, therefore nothing is
displayed on screen. That said, if we write our own quickly we can see
something happen:
.. code-block:: python
@ -70,16 +76,95 @@ something happen:
class Meta:
label = 'myoutput'
def render(self, data, template=None):
def render(self, data):
for key in data:
print "%s => %s" % (key, data[key])
app = foundation.CementApp('myapp', output_handler=MyOutputHandler)
...
Which looks like:
.. code-block:: text
$ python test.py
foo => bar
Rendering Output Via Templates
------------------------------
An extremely powerful feature of Cement is the ability to offload console
output to a template output handler. Several are inluded with Cement but not
enabled by default (listed above). The following example shows the use of
the Mustache templating langugage, as well as Json output handling.
**myapp.py**
.. code-block:: python
from cement.core.foundation import CementApp
from cement.core.controller import CementBaseController, expose
class MyBaseController(CementBaseController):
class Meta:
label = 'base'
description = 'MyApp Does Amazing Things'
@expose(hide=True)
def default(self):
data = dict(foo='bar')
print self.app.render(data, 'default.m')
# always return the data, some output handlers require this
# such as Json/Yaml (which don't use templates)
return data
class MyApp(CementApp):
class Meta:
label = 'myapp'
base_controller = MyBaseController
extensions = ['mustache', 'json']
# default output handler
output_handler = 'mustache'
app = MyApp()
try:
app.setup()
app.run()
finally:
app.close()
**/usr/lib/myapp/templates/default.m**
.. code-block:: text
This is the output of the MyBaseController.default() command.
The value of the 'foo' variable is => '{{foo}}'
And this looks like:
.. code-block:: text
$ python myapp.py
This is the output of the MyBaseController.default() command.
The value of the 'foo' variable is => 'bar'
Optionally, we can use the ``JsonOutputHandler`` via ``--json`` to trigger
just Json output (supressing all other output) using our return dictionary:
.. code-block:: text
$ python myapp.py --json
{"foo": "bar"}

View File

@ -80,7 +80,7 @@ plugin_dirs = ``None``
.. code-block:: python
['/usr/lib/<app_label>/plugins', '~/.<app_label>/plugins']
['~/.<app_label>/plugins', '/usr/lib/<app_label>/plugins']
Modules are attempted to be loaded in order, and will stop loading