mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 13:42:03 +00:00
fresh doc
This commit is contained in:
parent
0da49b7df5
commit
aaf39b9036
6
README
6
README
@ -10,7 +10,7 @@ rewrite of Cement version 0.x/1.x. Its goal is to introduce a standard, and
|
||||
feature-full platform for both simple and complex command line applications as
|
||||
well as support rapid development needs without sacrificing quality.
|
||||
|
||||
Cement2 Core features include (but not limited to):
|
||||
Cement2 Core features include (but are not limited to):
|
||||
|
||||
* Core pieces of the framework are customizable via handlers/interfaces
|
||||
* Extension handler interface to easily extend framework functionality
|
||||
@ -23,6 +23,7 @@ Cement2 Core features include (but not limited to):
|
||||
* Output handler interface renders return dictionaries to console
|
||||
* Core library and extensions have zero external dependencies
|
||||
* Extensions with external dependencies packaged separately
|
||||
* Controller handler supports sub-commands, and nested controllers
|
||||
* Significant Nose test coverage
|
||||
* Extensive Sphinx documentation, complete with examples
|
||||
* Supported on Python 2.6+ and 3!
|
||||
@ -45,4 +46,5 @@ All documentation is available from the official website:
|
||||
LICENSE:
|
||||
|
||||
The Cement CLI Application Framework is Open Source and is distributed under
|
||||
the MIT License. Please see the LICENSE file included with this software.
|
||||
the BSD License (three clause). Please see the LICENSE file included with
|
||||
this software.
|
||||
|
||||
89
doc/Makefile
89
doc/Makefile
@ -1,89 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/TheCementCLIApplicationFramework.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/TheCementCLIApplicationFramework.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
@ -1,11 +0,0 @@
|
||||
Cement API Documentation
|
||||
========================
|
||||
|
||||
This page contains the internal API documentation for the Cement Framework.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 20
|
||||
|
||||
api/source
|
||||
api/core
|
||||
api/versions
|
||||
@ -1,78 +0,0 @@
|
||||
Cement Core API Documentation
|
||||
=============================
|
||||
|
||||
:mod:`cement.core.app_setup`
|
||||
--------------------------------
|
||||
|
||||
.. automodule:: cement.core.app_setup
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.command`
|
||||
------------------------------
|
||||
|
||||
.. automodule:: cement.core.command
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.configuration`
|
||||
------------------------------------
|
||||
|
||||
.. automodule:: cement.core.configuration
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.controller`
|
||||
---------------------------------
|
||||
|
||||
.. automodule:: cement.core.controller
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.exc`
|
||||
--------------------------
|
||||
|
||||
.. automodule:: cement.core.exc
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.hook`
|
||||
---------------------------
|
||||
|
||||
.. automodule:: cement.core.hook
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.log`
|
||||
--------------------------
|
||||
|
||||
.. automodule:: cement.core.log
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.namespace`
|
||||
--------------------------------
|
||||
|
||||
.. automodule:: cement.core.namespace
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.opt`
|
||||
--------------------------
|
||||
|
||||
.. automodule:: cement.core.opt
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.plugin`
|
||||
-----------------------------
|
||||
|
||||
.. automodule:: cement.core.plugin
|
||||
:members:
|
||||
|
||||
|
||||
:mod:`cement.core.view`
|
||||
---------------------------
|
||||
|
||||
.. automodule:: cement.core.view
|
||||
:members:
|
||||
@ -1,29 +0,0 @@
|
||||
A Look at The Source Code
|
||||
=========================
|
||||
|
||||
All source 'packages' are available under the './src' directory. There are
|
||||
the following python packages:
|
||||
|
||||
./src/cement/
|
||||
This package provides the core framework required to run an
|
||||
application built on top of Cement.
|
||||
|
||||
./src/cement.devtools/
|
||||
This package provides development tools for creating and developing
|
||||
applications built on Cement.
|
||||
|
||||
./src/cement.test/
|
||||
This package provides an external application, built on top of
|
||||
Cement, that also provides the nose tests to run and test the
|
||||
framework. Testing requires a 'running' application to really cover
|
||||
all bits of the framework code, and this is that 'running'
|
||||
application.
|
||||
|
||||
|
||||
Additional directories include:
|
||||
|
||||
./doc/
|
||||
Sphinx documentation (what you're reading now).
|
||||
|
||||
./util/
|
||||
Utilities/scripts/etc that help with development of Cement.
|
||||
@ -1,57 +0,0 @@
|
||||
Versioning and Compatibility
|
||||
----------------------------
|
||||
|
||||
This outline uses fictitious version numbers to avoid confusion between
|
||||
actual releases and this doc. Cement is versioned as follows, using the
|
||||
version 0.2.4 as the example:
|
||||
|
||||
* 0 = Code Base
|
||||
* 2 = Major Release Version
|
||||
* 4 = Minor Release Version
|
||||
|
||||
That said, the Major Release Version and the Minor Release Version both
|
||||
honor the following scheme:
|
||||
|
||||
* Even = Stable
|
||||
* Odd = Development
|
||||
|
||||
Therefore, if 0.2.4 (even, even) is the current stable release of the '0'
|
||||
branch, then 0.2.5 (even, odd) is the 'in progress' version in the Git
|
||||
'master' branch. Once 0.2.5 reaches releasability it would be released as
|
||||
0.4.6 stable (even, even).
|
||||
|
||||
Development versions also follow the same scheme. The next major release
|
||||
that breaks API compatibility would be versioned as 0.3.1 (odd, odd). Once a
|
||||
'stable enough' version of the development branch reaches releasability, it
|
||||
will be released as 0.3.2 (odd, even) meaning the branch is development, but
|
||||
it is a semi-stable release.
|
||||
|
||||
Any time API compatibility changes we will up the Major Release Version. We
|
||||
handle this in setup.py of Cement applications by doing something like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
install_requires=[
|
||||
"cement.core >=0.2.4, <0.3"
|
||||
]
|
||||
|
||||
This means, if you write an application on top of cement == 0.2.4 then your
|
||||
application should be compatible with all versions of 0.2.x, however would not
|
||||
be compatible with anything >=0.3 because 0.3 is the next development version
|
||||
where API compatibility changes. For that reason the next major development
|
||||
branch is 0.3 (odd) currently, and the next major stable branch of cement will
|
||||
be 0.4 (even). Both 0.3 (development) and 0.4 (stable) break compatibility
|
||||
with previous versions of Cement < 0.3.
|
||||
|
||||
The 'Code Base' version more or less designates a 'full rewrite'. Meaning
|
||||
that within the same code base (i.e. '0') even in the next major version that
|
||||
break compatibility, you should be able to make your application work with
|
||||
only a few changes. Where as, in the next code base (i.e. 1) it is likely
|
||||
that you will need to make significant changes, or possibly rewrite your
|
||||
application to work on the newer code base.
|
||||
|
||||
To keep API compatible, and non compatible development separate we work out of
|
||||
two Git repos.
|
||||
|
||||
* master: development that is API compatible with current stable.
|
||||
* portland: development that is API incompatible with current stable.
|
||||
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The Cement CLI Application Framework documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Jan 28 02:44:35 2010.
|
||||
# Cement documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Aug 22 17:52:04 2011.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
@ -12,19 +12,17 @@
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
from pkg_resources import get_distribution
|
||||
|
||||
VERSION = '0.8'
|
||||
RELEASE = '0.8.16'
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.append(os.path.abspath('../../src/cement/'))
|
||||
sys.path.append(os.path.abspath('../../src/cement.devtools/'))
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc']
|
||||
@ -36,23 +34,23 @@ templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8'
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Cement'
|
||||
copyright = u'2010, BJ Dierkes'
|
||||
copyright = u'2011, BJ Dierkes'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = VERSION
|
||||
version = '1.9.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = RELEASE
|
||||
release = '1.9.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@ -64,12 +62,9 @@ release = RELEASE
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = []
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
@ -94,8 +89,8 @@ pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
@ -143,7 +138,7 @@ html_static_path = ['_static']
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
@ -154,13 +149,19 @@ html_static_path = ['_static']
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Cementdoc'
|
||||
@ -172,7 +173,7 @@ htmlhelp_basename = 'Cementdoc'
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
latex_font_size = '10pt'
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
@ -189,6 +190,12 @@ latex_documents = [
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
@ -196,4 +203,14 @@ latex_documents = [
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# 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)
|
||||
]
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
Developer Documentation
|
||||
=======================
|
||||
|
||||
This page contains documentation for developers building their application
|
||||
on Cement. It outlines the features of the Cement Framework and how to use
|
||||
them in your applications.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 20
|
||||
|
||||
dev/features
|
||||
dev/quickstart
|
||||
dev/understanding_your_application
|
||||
dev/namespaces
|
||||
dev/hooks
|
||||
dev/handlers
|
||||
dev/mvc
|
||||
dev/options_and_arguments
|
||||
dev/logging
|
||||
dev/plugin_support
|
||||
dev/templates
|
||||
dev/cli_api
|
||||
dev/testing
|
||||
|
||||
@ -1,127 +0,0 @@
|
||||
Cement CLI-API (Rendered JSON Output)
|
||||
=====================================
|
||||
|
||||
Every Cement Application has a built in CLI-API that allows other programs,
|
||||
regardless of their language, to access your command line interface and get
|
||||
back Json output. This is extremely powerful in mixed environments where you
|
||||
might not have control over legacy applications, or other departments code.
|
||||
If the language can speak Json, they can make a system call to your program
|
||||
and not have to parse through unreliable STDOUT to get the information they
|
||||
need.
|
||||
|
||||
The the following for example:
|
||||
|
||||
**./helloworld/controllers/example.py**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@expose('helloworld.templates.example.ex2', namespace='root')
|
||||
@register_hook()
|
||||
def ex2(self, cli_opts, cli_args):
|
||||
example = ExampleModel()
|
||||
example.label = 'This is my Example Model'
|
||||
|
||||
if cli_opts.my_option:
|
||||
print '%s passed by --my-option' % cli_opts.my_option
|
||||
|
||||
return dict(foo=True, example=example, items=['one', 'two', 'three'])
|
||||
|
||||
|
||||
The following would be run by:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld ex2 --my-option
|
||||
loading example plugin
|
||||
loading clibasic plugin
|
||||
True passed by --my-option
|
||||
|
||||
There are a number of things you can do such as conditional statements:
|
||||
|
||||
Label: This is my Example Model
|
||||
|
||||
Or a for loop:
|
||||
|
||||
* one
|
||||
* two
|
||||
* three
|
||||
|
||||
And functions:
|
||||
|
||||
Hello, World!
|
||||
Hello, Edward!
|
||||
|
||||
You can see that, we passed the '--my-option' that triggerd a print statement.
|
||||
Then our return dictionary was rendered via Genshi. Now lets see what that
|
||||
looks like with our Json engine:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld ex2 --my-option --json
|
||||
{"items": ["one", "two", "three"], "foo": true, "example": {"label": "This is my Example Model"}, "stderr": "", "stdout": "True passed by --my-option\n"}
|
||||
|
||||
The return data is rendered as Json, as well as STDOUT and STDERR. All other
|
||||
output is suppressed, meaning the application calling this will get back just
|
||||
the Json, a standard format from which they can use the data more reliably and
|
||||
much more easily.
|
||||
|
||||
|
||||
Pretty Printing JSON
|
||||
--------------------
|
||||
|
||||
A very handy tool that comes with Python 2.6+ is json.tool. This allows you
|
||||
to "pretty print" JSON output at the console.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld get-started --json | python -mjson.tool
|
||||
{
|
||||
"config": {
|
||||
"app_egg_name": "helloworld",
|
||||
"app_module": "helloworld",
|
||||
"app_name": "helloworld",
|
||||
"config_files": [
|
||||
"/etc/helloworld/helloworld.conf",
|
||||
"~/.helloworld/etc/helloworld.conf",
|
||||
"~/.helloworld.conf"
|
||||
],
|
||||
"config_source": [
|
||||
"defaults"
|
||||
],
|
||||
"datadir": "~/.helloworld/data",
|
||||
"debug": false,
|
||||
"enabled_plugins": [
|
||||
"helloworld.plugin.example"
|
||||
],
|
||||
"example": {
|
||||
"config_source": [
|
||||
"defaults"
|
||||
],
|
||||
"enable_plugin": true,
|
||||
"foo": "bar",
|
||||
"merge_root_options": true
|
||||
},
|
||||
"log_file": "~/log/helloworld.log",
|
||||
"log_level": "warn",
|
||||
"log_to_console": true,
|
||||
"merge_root_options": true,
|
||||
"output_engine": "json",
|
||||
"plugin_config_dir": "~/.helloworld/etc/plugins.d",
|
||||
"show_plugin_load": false,
|
||||
"tmpdir": "~/.helloworld/tmp"
|
||||
},
|
||||
"features": [
|
||||
"Multiple Configuration file parsing (default: /etc, ~/)",
|
||||
"Command line argument and option parsing",
|
||||
"Dual Console/File Logging Support",
|
||||
"Full Internal and External (3rd Party) Plugin support",
|
||||
"Basic \"hook\" support",
|
||||
"Full MVC support for advanced application design",
|
||||
"Text output rendering with Genshi templates",
|
||||
"Json output rendering allows other programs to access your CLI-API"
|
||||
],
|
||||
"genshi_link": "http://genshi.edgewall.org/wiki/Documentation/text-templates.html",
|
||||
"stderr": "",
|
||||
"stdout": ""
|
||||
}
|
||||
|
||||
@ -1,214 +0,0 @@
|
||||
An Overview of Features
|
||||
=======================
|
||||
|
||||
The Cement CLI Application Framework provides the following features for
|
||||
every application (out of the box):
|
||||
|
||||
* Multiple Configuration file parsing (default: /etc, ~/)
|
||||
* Command line argument and option parsing
|
||||
* Dual Console/File Logging Support
|
||||
* Internal and External (3rd Party) Plugin support
|
||||
* Basic "hook" support
|
||||
* MVC support for advanced application design
|
||||
* Text output rendering with Genshi templates
|
||||
* Json output rendering - The Cement CLI-API
|
||||
|
||||
|
||||
Config File Parsing
|
||||
-------------------
|
||||
|
||||
Config file parsing is provided by the standard ConfigObj. The configuration
|
||||
object is stored in the root namespaces config member and can be accessed as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.namespace import get_config
|
||||
config = get_config()
|
||||
|
||||
|
||||
The global config dictionary is generated [and overridden] in the following
|
||||
order:
|
||||
|
||||
* defaults (set in ./yourapp/core/config.py)
|
||||
* /etc/yourapp/yourapp.conf
|
||||
* %(prefix)/etc/yourapp.conf
|
||||
* ~/.yourapp.conf
|
||||
|
||||
|
||||
In the above, '%(prefix)' is a hard coded setting which points to
|
||||
'~/.yourapp/' by default (sane for development/no config situation). Some
|
||||
applications need a set location that all files belong in such as
|
||||
/var/lib/yourapp in which case the hard coded prefix can be changed.
|
||||
|
||||
|
||||
Command Line Argument and Option Parsing
|
||||
----------------------------------------
|
||||
|
||||
Command line arguments and options are parsed via OptParse for each namespace.
|
||||
The base application owns the 'root' namespace, where as additional namespaces
|
||||
branch off of that as illustrated below.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
# options and commands for root namespace
|
||||
$ helloworld --help
|
||||
|
||||
# options and subcommands for 'example' namespace
|
||||
$ helloworld example --help
|
||||
|
||||
|
||||
Namespaces have an option to 'merge_root_options' where their OptParse object
|
||||
will merge in all of the options from the root namespace. For example, the
|
||||
options --debug, --quiet, --json are all root namespace options that are
|
||||
merged into the 'example' namespace by default.
|
||||
|
||||
Options parsed from the command line will overwrite config options if the
|
||||
name of the option matches the config option. For example, when passing the
|
||||
'--debug' option at command line, the root_config['debug'] setting is set to
|
||||
True.
|
||||
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
Logging is provided by the standard 'logging' facility. By default Cement
|
||||
configures both a console log, as well as a file log. This is fully
|
||||
configurable however in your applications configuration. If no log_file is
|
||||
specified, none will be created. If 'log_to_console' is false, nothing will
|
||||
be logged to console.
|
||||
|
||||
The log level is determined by 'log_level' in your configuration, and is one
|
||||
of the following:
|
||||
|
||||
* info
|
||||
* warn
|
||||
* error
|
||||
* fatal
|
||||
* debug
|
||||
|
||||
Note that there are built in command line options that over ride these as well.
|
||||
The --quiet option forces no console output at all (including print
|
||||
statements) and the --debug option forces console output, and full logging
|
||||
at every level. Debug should only be enabled for troubleshooting.
|
||||
|
||||
The logger can be accessed anywhere in your application by the following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.log import get_logger
|
||||
|
||||
log = get_logger(__name__)
|
||||
log.info("This is an info message")
|
||||
log.error("This is an error message")
|
||||
log.debug("KAPLA!")
|
||||
|
||||
|
||||
Plugin Support
|
||||
--------------
|
||||
|
||||
Cement provides support for both internal and external (third party) plugins.
|
||||
Internal plugins are those build as part of your base application, meaning
|
||||
they will ship with your application and exist under ./yourapp/ source tree.
|
||||
External plugins exist outside of your application and allow third parties to
|
||||
build plugins that tie into your application.
|
||||
|
||||
External applications can be generated for your application via the paster
|
||||
utility:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ paster cement-plugin yourapp myplugin
|
||||
|
||||
$ cd yourapp-plugin-myplugin
|
||||
|
||||
$ python setup.py develop
|
||||
|
||||
|
||||
Plugins provide additional controllers, models, helpers, and templates to
|
||||
your application and function as if they were built into it directly. The
|
||||
plugin system is also designed to allow and encourage portability. A plugin
|
||||
can be imported into any other application built on cement. Additionally,
|
||||
The Rosendale Project was created to specifically build shared plugins for
|
||||
applications built on Cement.
|
||||
|
||||
|
||||
Hook Support
|
||||
------------
|
||||
|
||||
Cement provides hook support for both the Cement framework, as well as your
|
||||
applications and plugins. Hooks are easily defined, registered, and run:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.hook import define_hook, register_hook, run_hooks
|
||||
|
||||
define_hook('myhook_hook')
|
||||
|
||||
@register_hook(weight=10)
|
||||
def myhook_hook(*args, **kwargs):
|
||||
# do something
|
||||
return True
|
||||
|
||||
@register_hook(weight=-99)
|
||||
def myhook_hook(*args, **kwargs):
|
||||
# do something else, but do it first, because I need to run before
|
||||
# everyone else... my weight is -99.
|
||||
return True
|
||||
|
||||
for res in run_hooks('myhook_hook'):
|
||||
# do something with res
|
||||
pass
|
||||
|
||||
|
||||
This is a simple example, but the idea is... hooks can be defined either in
|
||||
your application, or in plugins. You can then register a function into that
|
||||
hook meaning when the hook is called, that function will be executed in the
|
||||
order of 'weight'. Finally, to run all functions that have been defined for
|
||||
that hook, we use the run_hooks() method.
|
||||
|
||||
*Note: run_hooks() yields its results, therefore you must iterate over it.*
|
||||
|
||||
|
||||
Model, View, Controller Design
|
||||
------------------------------
|
||||
|
||||
Cement encourages good programmatic design and habits by organizing your
|
||||
application into separate model, view, controller pieces.
|
||||
|
||||
model
|
||||
The model can be any arbitrary object class, or can be something like
|
||||
an SQLAlchemy declarative base.
|
||||
|
||||
view
|
||||
The view is generated by the Genshi Text Template engine, allowing
|
||||
you to keep your controller clean and free of unnecessary print
|
||||
statements.
|
||||
|
||||
controller
|
||||
The controller provides an outlet to expose commands to the
|
||||
application.
|
||||
|
||||
|
||||
Json Output Rendering - The Cement CLI-API
|
||||
------------------------------------------
|
||||
|
||||
Now, sit back down and let me explain before you ask "Why in the world would
|
||||
you output Json from a command line application?". It might not make sense
|
||||
at first, but it does to me. As a Linux Engineer bringing a number of
|
||||
utilities together to generate the output you want is always a fun task. Be
|
||||
it using sed, awk, grep, etc... we're always having to mangle STDOUT and
|
||||
format it for our needs at that time.
|
||||
|
||||
That said, parsing output is not only unpredictable, it doesn't scale. Now
|
||||
imagine a world where every command line application [optionally] spit out
|
||||
Json? There is so much more you can do with a standard format such as Json,
|
||||
Than parsing random output which differs from application to application.
|
||||
|
||||
All reasoning aside, Cement builds in an optional engine that renders command
|
||||
output as Json to the console. This is triggered by the '--json' command
|
||||
line option, and is what we like to call the Cement CLI-API. Regardless of
|
||||
the language, be it PERL, Ruby, Etc... if they can speak Json then they can
|
||||
access your application directly via a system call and get back data that they
|
||||
can use without having to tie into your python libraries.
|
||||
|
||||
|
||||
@ -1,153 +0,0 @@
|
||||
Handlers
|
||||
========
|
||||
|
||||
Cement sets up and provides a handlers object that is used to make pieces
|
||||
of the framework (and your application) both pluggable, and customizable.
|
||||
Currently this is a new feature, and only 'output' handling has been ported
|
||||
to the handler system. That said, it is a perfect example of how it is used.
|
||||
At application bootstrap, Cement defines the 'output' handler type, and then
|
||||
registers the default output handlers to that type (genshi, and json). As you
|
||||
can see, this is useful in that functions will return data to a genshi
|
||||
template, but if the '--json' option is passed it is rendered as Json.
|
||||
Developers can add additional handlers via plugins such as the
|
||||
Rosendale YAML Plugin which adds an output handler called 'yaml', and is called
|
||||
when the user passes '--yaml'.
|
||||
|
||||
Generally, a handler is a Class or Function of some kind, and provides some
|
||||
functionality in more or less a 'standardized' way. Meaning, all handlers
|
||||
of type 'output' should function the same way. It is a very loose, but
|
||||
versatile method of configuring and using handlers. How a handler functions
|
||||
is up to the developer, and should be documented well. It is recommended that
|
||||
a base 'Handler' class be constructed, and documented so that other developers
|
||||
know what is expected of that Handler (and so they can sub-class from it).
|
||||
|
||||
|
||||
Defining a Handler
|
||||
------------------
|
||||
|
||||
By default, as of 0.8.9, the only default handler type is called 'output'
|
||||
and can be accessed as the following (from within a loaded cement app):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement import handlers
|
||||
handlers['output']
|
||||
|
||||
|
||||
Handlers should be defined within your bootstrap process, generally in the
|
||||
root bootstrap. To define a handler type, add something similar to the
|
||||
following:
|
||||
|
||||
**helloworld/bootstrap/root.py**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.handler import define_handler
|
||||
define_handler('my_handler')
|
||||
|
||||
|
||||
Registering a Handler
|
||||
---------------------
|
||||
|
||||
Handlers should also be registered during the bootstrap process. The
|
||||
following is an example from rosendale.yaml and shows how to register
|
||||
an output handler (keep in mind this is only a snippit of the code in that
|
||||
file):
|
||||
|
||||
**rosendale.bootstrap.yaml_output**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from rosendale.lib.yaml_output import YamlOutputHandler
|
||||
register_handler('output', 'yaml', YamlOutputHandler)
|
||||
|
||||
|
||||
In this example, the first argument (output) is the handler type, the second
|
||||
(yaml) is the label/name of the output handler you are registering, and finally
|
||||
the last argument is the function or class object to store as the handler.
|
||||
|
||||
**Note**: The handler can be store as an instantiated function/class or not.
|
||||
This all depends on how the handler is to be used. For example, you might want
|
||||
to use handlers to create a stateful object (instantiated once) or not
|
||||
(instantiated every time it is called). The 'output' handler is an example
|
||||
of a handler that is not instantiated, because it is only a function that
|
||||
relies on different arguments everytime it is called. However, a database
|
||||
handler might only be instantiated once (same database, same info, same args)
|
||||
|
||||
Example Usage
|
||||
-------------
|
||||
|
||||
How a handler is accessed depends on how the handler is defined. Does it
|
||||
expect arguments? Does it return data? This is all for the developer of the
|
||||
application to determine, and document. As an example, lets say we have a
|
||||
database handler. We want to use handlers to setup and provide access to
|
||||
two different databases. One for read operations, and one for write
|
||||
operations. Please note, this is a psuedo example and will not have any real
|
||||
database interaction.
|
||||
|
||||
helloworld/core/database.py
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Database(object):
|
||||
def __init__(self, uri):
|
||||
self.uri = uri
|
||||
|
||||
def connect(self):
|
||||
# do something and establish a connection
|
||||
raise NotImplementedError, "Database.connect() must be subclassed."
|
||||
|
||||
def query(self, query_string):
|
||||
# do something and return query_results
|
||||
raise NotImplementedError, "Database.query() must be subclassed."
|
||||
|
||||
|
||||
helloworld/lib/database/mysql.py
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from helloworld.core.database import Database
|
||||
|
||||
class MySQLDatabase(Database)
|
||||
def connect(self):
|
||||
# do something to connect to self.uri
|
||||
pass
|
||||
|
||||
def query(self, query_string):
|
||||
# do something with query_string
|
||||
return query_results
|
||||
|
||||
|
||||
helloworld/bootstrap/root.py
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.handler import define_handler
|
||||
from helloworld.lib.database.mysql import MySQLDatabase
|
||||
|
||||
define_handler('database')
|
||||
|
||||
# setup a persistant database object, one for read one for write
|
||||
read_db = MySQLDatabase('some_db_uri')
|
||||
write_db = MySQLDatabase('some_other_db_uri')
|
||||
register_handler('database', 'read_db', read_db)
|
||||
register_handler('database', 'write_db', write_db)
|
||||
|
||||
|
||||
helloworld/controller/root.py
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.handler import get_handler
|
||||
|
||||
class RootController(CementController):
|
||||
def query_database(self)
|
||||
# read from the readonly database server
|
||||
db = get_handler('database', 'read_db')
|
||||
res = db.query('some SQL query')
|
||||
# do something with res
|
||||
|
||||
def update_something(self):
|
||||
# do some operation on the write database server
|
||||
db = get_handler('database', 'write_db')
|
||||
db.query('some query to update something')
|
||||
@ -1,206 +0,0 @@
|
||||
Hooks
|
||||
=====
|
||||
|
||||
Hooks allow the developers to tie into different pieces of the application.
|
||||
A hook can be defined anywhere, be it internally in the application, or in a
|
||||
plugin. Once a hook is defined, functions can be registered to that hook so
|
||||
that when the hook is called, all functions registered to that hook will be
|
||||
run. By defining a hook, you are saying that you are going to honor that hook
|
||||
somewhere in your application. Using descriptive hook names are good for
|
||||
clarity. For example, 'pre_database_connect_hook' is obviously a hook that
|
||||
will be run before a database connection is attempted.
|
||||
|
||||
Cement has a number of hooks that tie into the Cement Framework:
|
||||
|
||||
Hook definitions:
|
||||
|
||||
options_hook
|
||||
Used to add options to a namespaces options object
|
||||
|
||||
post_options_hook
|
||||
Run after all options have been setup and merged
|
||||
|
||||
post_bootstrap_hook
|
||||
Run just after the root bootstrap is loaded. Note that Plugins can
|
||||
not use this hook because it runs before plugins are loaded. Use
|
||||
post_plugins_hook instead.
|
||||
|
||||
validate_config_hook
|
||||
Run after config options are setup
|
||||
|
||||
pre_plugins_hook
|
||||
Run just before all plugins are loaded (run once)
|
||||
|
||||
post_plugins_hook
|
||||
Run just after all plugins are loaded (run once)
|
||||
|
||||
|
||||
Defining a Hook
|
||||
---------------
|
||||
|
||||
A hook can be defined as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.hook import define_hook
|
||||
|
||||
define_hook('my_example_hook')
|
||||
|
||||
|
||||
Hooks are defined during the bootstrap process, and should be added to the
|
||||
bootstrap file for the namespace they relate to.
|
||||
|
||||
**./helloworld/bootstrap/example.py**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.hook import define_hook
|
||||
from cement.core.plugin import CementPlugin, register_plugin
|
||||
|
||||
define_hook('my_example_hook')
|
||||
...
|
||||
|
||||
|
||||
Registering Functions to a Hook
|
||||
-------------------------------
|
||||
|
||||
A hook is just an identifier, but the functions registered to that hook are
|
||||
what get run when the hook is called. Registering a hook should also be done
|
||||
during the bootstrap process The following is how to register a hook from a
|
||||
controller file:
|
||||
|
||||
**./helloworld/bootstrap/someothernamespace.py**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.hook import register_hook
|
||||
|
||||
@register_hook()
|
||||
def my_example_hook(*args, **kwargs):
|
||||
# do something
|
||||
something = "The result of my hook."
|
||||
return something
|
||||
|
||||
@register_hook(name='some_other_hook_name')
|
||||
def my_function(*args, **kw):
|
||||
# do something
|
||||
...
|
||||
|
||||
|
||||
The @register_hook() decorator uses the name of the function you are
|
||||
decorating to determine the hook you are registering for, or you can pass the
|
||||
name parameter. Note that if combining with other decorators you must pass
|
||||
the name parameter. It should also be noted, you probably don't want to
|
||||
decorate a command function [one that is @expose()'d] as a hook.
|
||||
|
||||
What you return depends on what the developer defining the hook is expecting.
|
||||
Each hook is different, and the nature of the hook determines whether you need
|
||||
to return anything or not. That is up to the developer. Also, the args and
|
||||
kwargs coming in depend on the developer. You have to be familiar with
|
||||
the purpose of the defined hook in order to know whether you are receiving any
|
||||
args or kwargs, but either way you ant to accept them.
|
||||
|
||||
Registering a hook just puts the function into the hook list. This will be an
|
||||
unbound function, so if you register a function that is a class method keep in
|
||||
mind that 'self' doesn't exist in the context of when the hook is run. For the
|
||||
most part, standard unbound functions are best for hooks rather than controller
|
||||
or other class methods.
|
||||
|
||||
|
||||
Running a hook
|
||||
--------------
|
||||
|
||||
Now that a hook is defined, and functions have been registered to that hook
|
||||
all that is left is to run it. Keep in mind, you don't want to run a hook
|
||||
until after the application load process... meaning, after all plugins and
|
||||
controllers are loaded. For the most part, you don't have much control over
|
||||
this as that is all handled by Cement, however if you get an error that the
|
||||
hook doesn't exist then you are probably running it too early.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.hook import run_hooks
|
||||
|
||||
for res in run_hooks('my_example_hook'):
|
||||
# do something with res
|
||||
pass
|
||||
|
||||
As you can see we iterate over the hook, rather than just calling
|
||||
'run_hooks()'. This is necessary because run_hooks() yields the results from
|
||||
each hook. Hooks can be run anywhere *after* the hook is defined, and hooks
|
||||
are registered to that hook.
|
||||
|
||||
|
||||
Controlling Hook Run Order
|
||||
--------------------------
|
||||
|
||||
Sometimes you might have a very specific purpose in mind for a hook, and need
|
||||
it to run before or after other functions in the same hook. For that reason
|
||||
there is an optional 'weight' option that can be passed when registering a
|
||||
hook function.
|
||||
|
||||
First I'm going to define the hook, and also create an example command here
|
||||
that will run the hook.
|
||||
|
||||
**./helloworld/controllers/root.py**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.hook import define_hook, run_hooks
|
||||
from cement.core.controller import CementController, expose
|
||||
|
||||
define_hook('my_example_hook')
|
||||
|
||||
class RootController(CementController):
|
||||
@expose()
|
||||
def hook_example(self):
|
||||
for res in run_hooks('my_example_hook'):
|
||||
pass
|
||||
|
||||
|
||||
Then, we need to register functions into that hook, which we will do from
|
||||
another controller:
|
||||
|
||||
**./helloworld/controllers/example.py**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.hook import register_hook
|
||||
|
||||
@register_hook(weight=99)
|
||||
def my_example_hook(*args, **kwargs):
|
||||
print "In example_hook number 1, weight = 99"
|
||||
|
||||
@register_hook(weight=-1000)
|
||||
def my_example_hook(*args, **kwargs):
|
||||
print "In example_hook number 2, weight = -1000"
|
||||
|
||||
@register_hook()
|
||||
def my_example_hook(*args, **kwargs):
|
||||
print "In example_hook number 3, weight = 0 (defaullt)"
|
||||
|
||||
# snipped the rest of the file
|
||||
|
||||
|
||||
We probably wouldn’t register the same hook from the same place, but I wanted
|
||||
to in order to show how hooks are ordered by weight.
|
||||
|
||||
Note, you must iterate over run_hooks as it yields the results of the
|
||||
function. And the result?
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld hook-example
|
||||
loading example plugin
|
||||
loading clibasic plugin
|
||||
In example_hook number 2, weight = -1000
|
||||
In example_hook number 3, weight = 0 (defaullt)
|
||||
In example_hook number 1, weight = 99
|
||||
|
||||
|
||||
As you can see, it doesn’t matter what order we register the hook, the
|
||||
weight runs then in order from lowest to highest. Hooks are awesome and
|
||||
provide a little bit of magic to your application. Be sure to properly
|
||||
document any hooks you define, what their purpose is and where they will
|
||||
be run.
|
||||
|
||||
@ -1,81 +0,0 @@
|
||||
Logging
|
||||
=======
|
||||
|
||||
Cement applications are setup with the standard logging facility for both
|
||||
file and console logging.
|
||||
|
||||
Configuration Settings
|
||||
----------------------
|
||||
|
||||
The following configuration options under your applications [root] namespace
|
||||
are honored:
|
||||
|
||||
log_file
|
||||
A path to a log file (if none is set, file logging is disabled)
|
||||
|
||||
log_level
|
||||
Log level (info, warn, error, fatal, debug)
|
||||
|
||||
log_to_console
|
||||
Whether or not to log to console.
|
||||
|
||||
logging_config_file
|
||||
A logging configuration file that allows you to override the default
|
||||
logging configuration. File format and usage can be found here:
|
||||
http://docs.python.org/library/logging.html#logging.fileConfig
|
||||
|
||||
log_max_bytes
|
||||
Maximum number of bytes to keep in a log file (default: no limit).
|
||||
|
||||
log_max_files
|
||||
Maximum number of log files to keep in rotation (default: no rotation)
|
||||
|
||||
|
||||
Using the Logger
|
||||
----------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.log import get_logger
|
||||
|
||||
log = get_logger(__name__)
|
||||
log.info('this is an info message')
|
||||
log.warn('this is a warning')
|
||||
log.error('this is an error')
|
||||
log.fatal('this is a critical error')
|
||||
log.debug('KAPLA!!!!!!')
|
||||
|
||||
|
||||
Configuring Logging via a Config File
|
||||
-------------------------------------
|
||||
|
||||
An example logging configuration file might look like:
|
||||
|
||||
*/etc/yourapp/yourapp-logging.conf*
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
[loggers]
|
||||
keys = root
|
||||
|
||||
[handlers]
|
||||
keys = hand01
|
||||
|
||||
[formatters]
|
||||
keys = form01
|
||||
|
||||
[logger_root]
|
||||
level=DEBUG
|
||||
handlers=hand01
|
||||
|
||||
[handler_hand01]
|
||||
class=StreamHandler
|
||||
level=NOTSET
|
||||
formatter=form01
|
||||
args=(sys.stdout,)
|
||||
|
||||
[formatter_form01]
|
||||
format=F1 %(asctime)s %(levelname)s %(message)s
|
||||
datefmt=
|
||||
class=logging.Formatter
|
||||
|
||||
@ -1,179 +0,0 @@
|
||||
Model, View, Controller Overview
|
||||
================================
|
||||
|
||||
The Cement Framework creates applications that encourage the Model, View,
|
||||
Controller design. Each piece of your application should be separated this
|
||||
way. For example, if you add a plugin called 'myplugin' you should work out
|
||||
of the following files:
|
||||
|
||||
* helloworld/bootstrap/myplugin.py
|
||||
* helloworld/model/myplugin.py
|
||||
* helloworld/controllers/myplugin.py
|
||||
* helloworld/templates/myplugin/
|
||||
|
||||
|
||||
As always, review the 'example' plugin included with your application to see
|
||||
how this all works. Additionally, a great explanation of a typical MVC design
|
||||
can be found on `Wikipedia <http://en.wikipedia.org/wiki/Model–view–controller>`_.
|
||||
|
||||
|
||||
The Model
|
||||
^^^^^^^^^
|
||||
|
||||
The Model represents the data that you are working with. This might be a
|
||||
User class, or a Product, etc. The class might be an SQLAlchemy class tied
|
||||
to a database, or can just simply be an object allowing you to organize data.
|
||||
|
||||
**helloworld/model/user.py**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class User(object):
|
||||
def __init__(self, first, last, **kwargs):
|
||||
self.first_name = first
|
||||
self.last_name = last
|
||||
self.address = kwargs.get('address', None)
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
return "%s %s" % (self.first_name, self.last_name)
|
||||
|
||||
|
||||
The model should always be associated with 'data' and should rarely perform
|
||||
operations or tasks outside of creating/editing/saving/deleting/etc the
|
||||
data associated with that model.
|
||||
|
||||
A recommended way of accessing your model throughout your application is to
|
||||
import all model classes into the 'root' model file like so:
|
||||
|
||||
**helloworld/model/root.py**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from helloworld.model.example import Example
|
||||
from helloworld.model.user import User
|
||||
from helloworld.model.product import Product
|
||||
|
||||
|
||||
Then, throughout your application you can access all of you module objects
|
||||
like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from helloworld.model import root as model
|
||||
|
||||
user = model.User()
|
||||
product = model.Product()
|
||||
|
||||
|
||||
The Controller
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The controller is primarily used to expose commands to your application. Note
|
||||
that you can expose a command function to any namespace that has been defined.
|
||||
By default all commands are exposed to the 'root' namespace and will display
|
||||
when you execute:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld --help
|
||||
|
||||
When you expose to another namespace, like say 'greeting' then your command
|
||||
will show up under:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld greeting --help
|
||||
|
||||
|
||||
A typical example of this would be
|
||||
|
||||
**helloworld/controllers/greeting.py**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.controller import CementController, expose
|
||||
from helloworld.model import root as model
|
||||
|
||||
class GreetingController(CementController):
|
||||
@expose('helloworld.templates.greetings.sayhi', namespace='root')
|
||||
def sayhi(self):
|
||||
user = model.User(first=self.cli_opts.first_name,
|
||||
last=self.cli_opts.last_name)
|
||||
return dict(user=user)
|
||||
|
||||
|
||||
The method 'GreetingController.sayhi' is exposed to the 'root' namespace, and
|
||||
will be called when the following command is run:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld sayhi --firstname="John" --lastname="Doe"
|
||||
|
||||
|
||||
The user object is then returned in a dictionary and rendered by Genshi with
|
||||
the template 'helloworld.templates.greetings.sayhi' or what equates to
|
||||
'helloworld/templates/greetings/sayhi.txt' on the filesystem (as an example).
|
||||
The return dictionary can contain strings, lists, tuples, dicts, class objects
|
||||
and similar data. It should never return functions or other non-serializable
|
||||
objects.
|
||||
|
||||
*Note: You can also tell Cement to write output to a file rather than STDOUT
|
||||
by passing "output_file='/path/to/file'" in your return dict().*
|
||||
|
||||
Controllers are very flexible. Some people won't want to use Genshi
|
||||
templating, which is perfectly fine. The following exposes a command without
|
||||
template rendering:
|
||||
|
||||
**helloworld/controllers/greeting.py**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.controller import CementController, expose
|
||||
from helloworld.model import root as model
|
||||
|
||||
class GreetingController(CementController):
|
||||
@expose()
|
||||
def sayhi(self):
|
||||
user = model.User(first=self.cli_opts.first_name,
|
||||
last=self.cli_opts.last_name)
|
||||
print 'Hello %s!' % user.display_name
|
||||
return dict(user=user)
|
||||
|
||||
Notice how we don't need to specify a template path, though the command is
|
||||
still exposed. That said, you should always return any relevant data even
|
||||
if not rendering a template. This is because every command automatically
|
||||
has a Json output engine. By adding '--json' to the end of your command, all
|
||||
output is suppressed and only the return data is rendered via Json. In
|
||||
addition stdout, and stderr are also added to the Json output.
|
||||
|
||||
|
||||
The View
|
||||
^^^^^^^^
|
||||
|
||||
Note that the templates directory *must* have a directory for each namespace
|
||||
that contains your template file (more on templating later). Templating is not
|
||||
necessary if you prefer to simply use the print statement, that said for
|
||||
larger applications that provide a lot of console output learning the Genshi
|
||||
Text Template syntax will significantly clean up your controllers and provide
|
||||
more robust output to the user.
|
||||
|
||||
Our 'sayhi' template would look like:
|
||||
|
||||
**helloworld/templates/greetings/sayhi.txt**
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{# This is an example Genshi Text Template. Documentation is at: #}\
|
||||
{# #}\
|
||||
{# http://genshi.edgewall.org/wiki/Documentation/text-templates.html #}\
|
||||
{# #}\
|
||||
\
|
||||
\
|
||||
{# --------------------- 78 character baseline --------------------------- #}\
|
||||
|
||||
Hello ${user.display_name}
|
||||
|
||||
|
||||
Using the '78 character baseline' comment in your templates is useful so that
|
||||
you ensure your output remains within that limit when possible.
|
||||
@ -1,130 +0,0 @@
|
||||
|
||||
Namespaces
|
||||
==========
|
||||
|
||||
The Cement Framework establishes a global 'namespaces' dictionary that stores
|
||||
information for each namespace (and/or plugin). A Cement namespace should
|
||||
not be confused with a Python namespace. We use namespaces in reference to
|
||||
where commands, configurations, command line options/arguments, etc are
|
||||
accessible from.
|
||||
|
||||
All namespaces have the following members, that are available under the
|
||||
global *namespaces['namespace']* dictionary:
|
||||
|
||||
config
|
||||
A ConfigObj object, also accessible as a dict. For the 'root' namespace
|
||||
which is the base application itself, this holds all the critical
|
||||
configurations for your application. The 'root' namespace config
|
||||
is generated from a default set, and then overridden by config files
|
||||
in either /etc/yourapp/yourapp.conf (global) or ~/.yourapp.conf (per
|
||||
user). For namespace specific configs, the configuration is generated
|
||||
from a default set and then overridden by the plugin config file at
|
||||
/etc/yourapp/plugins.d/yourplugin.conf or from a [namespace] block
|
||||
from the main applications configuration file.
|
||||
|
||||
label
|
||||
The name of the namespace (single word). For complex namespaces, or
|
||||
those that are better fit for two words, you must use an underscore
|
||||
'_'. All python modules/files, config files, and config blocks
|
||||
'[you_namespace]' must also use underscores. That said, Cement will
|
||||
display this namespaces *with* dashes in the --help output and will
|
||||
be called as 'your-namespace' which is more proper for command line
|
||||
access.
|
||||
|
||||
version
|
||||
The version of the applicaton ('root') or of the plugin. If not
|
||||
specified this version will be inherited from the root namespace.
|
||||
|
||||
description
|
||||
A brief summary of the plugin or namespace.
|
||||
|
||||
commands
|
||||
A dictionary of commands exposed into this namespace. This is
|
||||
different than commands exposed by the namespaces controller.
|
||||
Controllers from any namespace can expose commands into other
|
||||
namespaces, which will be added to the commands dictionary of the
|
||||
destination namespace (yes, it's confusing).
|
||||
|
||||
controller
|
||||
The CementController object for the namespace. Set as a string
|
||||
when initializing a namespace, but is instantiated as the object
|
||||
when the namespace is registered.
|
||||
|
||||
options
|
||||
An OptParse object used to set options that are local to this
|
||||
namespace only. For example, for a subcommand 'cmd2' of the 'example'
|
||||
namespace, the option '--my-opt' would only appear under
|
||||
'myapp example cmd2 --my-opt' but would not be available under
|
||||
'myapp cmd1 --my-opt'.
|
||||
|
||||
is_hidden
|
||||
Boolean, determines whether or not to display the namespace in the
|
||||
output of 'myapp --help'. By default, if the namespace does not
|
||||
have any visible/exposed commands, the namespace will not display.
|
||||
|
||||
|
||||
Namespaces are generally used to breakup your applicaton into smaller parts.
|
||||
For example, if you have 50 commands under the root namespace and all show
|
||||
up under 'myapp --help' you're users are going to hate you. Namespaces allow
|
||||
you to breaking up commands into smaller, related sections.
|
||||
|
||||
**./helloworld/bootstap/example.py**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.hook import define_hook
|
||||
from cement.core.namespace import CementNamespace, register_namespace
|
||||
|
||||
define_hook('my_example_hook')
|
||||
|
||||
# Setup the 'example' namespace object
|
||||
example = CementNamespace('example', controller='ExampleController')
|
||||
|
||||
# Example namespace default configurations, overwritten by the [example]
|
||||
# section of the applications config file(s). Once registered, this dict is
|
||||
# accessible as:
|
||||
#
|
||||
# from cement.core.namespace import get_config
|
||||
# example_config = get_config('example')
|
||||
#
|
||||
# Or simply as:
|
||||
#
|
||||
# root_config = get_config()
|
||||
# root_config['example']
|
||||
#
|
||||
example.config['foo'] = 'bar'
|
||||
|
||||
# Example namespace options. These options show up under:
|
||||
#
|
||||
# $ {{package}} example --help
|
||||
#
|
||||
example.options.add_option('-F', '--foo', action='store',
|
||||
dest='foo', default=None, help='Example Foo Option'
|
||||
)
|
||||
|
||||
# Officialize and register the namespace
|
||||
register_namespace(example)
|
||||
|
||||
|
||||
Namespaces are always defined and registered in an associated bootstrap file,
|
||||
as in the above example we registered the 'example' namespace from the file
|
||||
'helloworld/bootstrap/example.py'.
|
||||
|
||||
|
||||
Accessing Namespaces
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Accessing the namespaces dictionary directly is not recommended from outside
|
||||
the Cement Framework code. That said, there might be a sitation you would
|
||||
want to and well, we can't stop you can we?
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement import namespaces
|
||||
|
||||
my_namespace = namespaces['my_namespace']
|
||||
my_namespace.config
|
||||
my_namespace.commands
|
||||
my_namespace.version
|
||||
|
||||
|
||||
@ -1,203 +0,0 @@
|
||||
Command Line Options and Arguments
|
||||
==================================
|
||||
|
||||
Cement fully configures command line option and argument parsing via the
|
||||
`OptionParser <http://docs.python.org/library/optparse.html>`_ library. The following outlines how to create options, and
|
||||
access the options and arguments passed to your application.
|
||||
|
||||
Adding Options To a Namespace
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Typically, options are added directly to a namespace when that namespace is
|
||||
registered. The following comes from the example plugin:
|
||||
|
||||
**helloworld/bootstrap/example.py**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.namespace import CementNamespace, register_namespace
|
||||
|
||||
example = CementNamespace(
|
||||
label='example',
|
||||
controller='ExampleController',
|
||||
description='Example Plugin for helloworld',
|
||||
provider='helloworld'
|
||||
)
|
||||
|
||||
example.config['foo'] = 'bar'
|
||||
|
||||
example.options.add_option('-F', '--foo', action='store',
|
||||
dest='foo', default=None, help='Example Foo Option'
|
||||
)
|
||||
|
||||
register_namespace(example)
|
||||
|
||||
|
||||
The option added above shows up under the example namespace like so:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld example --help
|
||||
Usage: helloworld example <SUBCOMMAND> [ARGS] --(OPTIONS)
|
||||
|
||||
Sub-Commands:
|
||||
ex2, ex1
|
||||
|
||||
Help? try '[SUBCOMMAND]-help'
|
||||
|
||||
Options:
|
||||
--version show program's version number and exit
|
||||
-h, --help show this help message and exit
|
||||
-F FOO, --foo=FOO Example Foo Option
|
||||
-R, --root-option Example root option
|
||||
--json render output as json (CLI-API)
|
||||
--debug toggle debug output
|
||||
--quiet disable console logging
|
||||
--yaml render output as yaml
|
||||
|
||||
|
||||
The '-F/--foo' option does *not* show up under the root namespace
|
||||
(helloworld --help). In most cases, an option also aligns with a config
|
||||
option as you can see in the example above. When the '--foo' option is passed,
|
||||
if the config['foo'] option exists, it will override the value with that of
|
||||
the value passed at the command line.
|
||||
|
||||
You will also notice in the above that all of our root options also show up
|
||||
under our 'example' namespace. This is configurable by the
|
||||
'merge_root_options' plugin configuration option. Take the following example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.namespace import CementNamespace, register_namespace
|
||||
|
||||
example = CementNamespace(
|
||||
label='example',
|
||||
controller='ExampleController',
|
||||
description='Example Plugin for helloworld',
|
||||
provider='helloworld'
|
||||
)
|
||||
|
||||
example.config['foo'] = 'bar'
|
||||
example.config['merge_root_options'] = False
|
||||
|
||||
example.options.add_option('-F', '--foo', action='store',
|
||||
dest='foo', default=None, help='Example Foo Option'
|
||||
)
|
||||
|
||||
register_namespace(example)
|
||||
|
||||
|
||||
And the output:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld example --help
|
||||
Usage: helloworld example <SUBCOMMAND> [ARGS] --(OPTIONS)
|
||||
|
||||
Sub-Commands:
|
||||
ex2, ex1
|
||||
|
||||
Help? try '[SUBCOMMAND]-help'
|
||||
|
||||
Options:
|
||||
--version show program's version number and exit
|
||||
-h, --help show this help message and exit
|
||||
-F FOO, --foo=FOO Example Foo Option
|
||||
|
||||
If 'merge_root_options' is set to False, only the options added to this
|
||||
namespace directly will be configured.
|
||||
|
||||
|
||||
Adding Options To Another Namespace
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Options can be added *to* any namespace *from* any namespace bootstrap by way
|
||||
of the built in 'options_hook'. For example, you will see something like the
|
||||
following in your applications root bootstrap:
|
||||
|
||||
**helloworld/bootstrap/root.py**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.opt import init_parser
|
||||
from cement.core.hook import register_hook
|
||||
|
||||
# Register root options
|
||||
@register_hook()
|
||||
def options_hook(*args, **kwargs):
|
||||
# This hook allows us to append options to the root namespace
|
||||
root_options = init_parser()
|
||||
root_options.add_option('-R', '--root-option', action ='store_true',
|
||||
dest='root_option', default=None, help='Example root option')
|
||||
root_options.add_option('--json', action='store_true',
|
||||
dest='enable_json', default=None,
|
||||
help='render output as json (CLI-API)')
|
||||
root_options.add_option('--debug', action='store_true',
|
||||
dest='debug', default=None, help='toggle debug output')
|
||||
root_options.add_option('--quiet', action='store_true',
|
||||
dest='quiet', default=None, help='disable console logging')
|
||||
return ('root', root_options)
|
||||
|
||||
|
||||
The 'options_hook' expects a tuple in return when it runs that hook, and the
|
||||
tuple is made up of (namespace_name, optparse_object). Code similar to the
|
||||
above can also be used to inject options into any other namespace allowing
|
||||
plugins to build off of, and add functionality to other plugins or other
|
||||
built in namespaces in your application.
|
||||
|
||||
|
||||
Accessing Options and Arguments
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
All options and arguments passed at command line are accessible via the
|
||||
attributes 'self.cli_opts' and 'self.cli_args' from within every
|
||||
CementController. For example:
|
||||
|
||||
**helloworld/controllers/example.py**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ExampleController(CementController):
|
||||
@expose(namespace='root')
|
||||
def cmd2(self):
|
||||
print "args[1] => ", self.cli_args[1]
|
||||
print "root_option => ", self.cli_opts.root_option
|
||||
return dict()
|
||||
|
||||
The output is:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld cmd2 --root-option arg1 arg2
|
||||
args[1] => bar
|
||||
root_option => True
|
||||
|
||||
|
||||
Alternate Option Examples
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
All options are standard OptParse options, however the following are some
|
||||
examples.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
example.options.add_option('--prompt', action='store_true', dest='prompt',
|
||||
help='toggle prompting')
|
||||
|
||||
The above sets namespaces['example'].config['prompt'] to True, as well as
|
||||
self.cli_opts.prompt. The action is can be either 'store' or 'store_true'
|
||||
which means store the value passed with the option, or just store the option
|
||||
as True. dest is the variable name that the option value is stored as. help
|
||||
is what is displayed in --help.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
example.options.add_option('-F', '--foo', action='store', dest='foo',
|
||||
help='pass value to foo', metavar='STR')
|
||||
|
||||
The above sets namespaces['example'].config['foo'] to the value passed at
|
||||
command line (helloworld --foo=bar), and also sets self.cli_opts.foo the same.
|
||||
metavar is an extra option that alters the display in --help (-F STR, --foo=STR).
|
||||
|
||||
|
||||
@ -1,128 +0,0 @@
|
||||
Plugin Support
|
||||
==============
|
||||
|
||||
Plugins are made possible by namespaces, therefore you should read and
|
||||
be familiar with the Namespaces section of the documentation. There
|
||||
is no difference between internal controller/model/bootstrap/etc code and
|
||||
plugin code. The difference is that plugins are optional and only loaded if
|
||||
'enable_plugin=true' in the plugins configuration. Internal code is
|
||||
bootstrapped and imported directly by 'helloworld/bootstrap/root.py' so that
|
||||
it is always loaded by your application.
|
||||
|
||||
The Cement Framework automatically builds plugin support into your application.
|
||||
Plugins can be either internal, or external. Internal plugins are shipped
|
||||
with your application and are more or less a convenient way of maintaining
|
||||
optional code within your application. External plugins are either for
|
||||
third parties to build new features into your application, or perhaps for you
|
||||
yourself to build extended support maybe under a different license, or in
|
||||
order to not interfere with your stable application.
|
||||
|
||||
Because users can override the default application configuration in their
|
||||
home dir ~/.yourapp.conf, they can optionally enable/disable plugins catered
|
||||
to their actual needs of the application. Plugins are a great way for them
|
||||
to add functionality that the system administrator might not want to enable
|
||||
globally.
|
||||
|
||||
|
||||
A Look at an Internal Plugin
|
||||
----------------------------
|
||||
|
||||
An internal plugin would consist of the following files:
|
||||
|
||||
* ./yourapp/bootstrap/your_plugin.py
|
||||
* ./yourapp/controllers/your_plugin.py
|
||||
* ./yourapp/model/your_plugin.py
|
||||
* ./yourapp/templates/your_plugin/
|
||||
* ./yourapp/etc/yourapp/plugins.d/your_plugin.conf
|
||||
|
||||
As you can see, plugins have the same layout as the standard application which
|
||||
utilizes a Model, View, Controller design as well as a bootstrap file. For
|
||||
that reason we aren't going to cover much in this section because the plugin
|
||||
code is exactly the same as your application. The only difference is that
|
||||
you do not import the plugin's 'bootstrap' file into the root bootstrap like
|
||||
you do with the rest of your application, but rather enable the plugin in the
|
||||
your-plugin.conf within plugins.d.
|
||||
|
||||
|
||||
Important Note on Naming Conventions
|
||||
------------------------------------
|
||||
|
||||
In general, single word namespace and plugin names are preferred. That said
|
||||
sometimes separating the words is necessary. Meaning, "yourplugin" versus
|
||||
"your_plugin". In this case, only underscores '_' are allowed, not dashes.
|
||||
|
||||
|
||||
External Plugins
|
||||
----------------
|
||||
|
||||
External plugins are the same as internal plugins, however they are created
|
||||
outside of the main applications source tree. To make this process as easy as
|
||||
possible, we created a Paster plugin allowing you to create plugins for
|
||||
applications built on cement. Therefore, if your applications name is
|
||||
helloworld, the following creates an external plugin for helloworld:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
$ paster cement-plugin helloworld myplugin
|
||||
|
||||
$ cd helloworld-plugins-myplugin
|
||||
|
||||
$ python setup.py develop
|
||||
|
||||
|
||||
Once the plugin is installed you simply need to enable it. An external plugin
|
||||
functions by way of pkg_resources and shared library paths. Meaning, even
|
||||
though the code is outside the main applications source tree the code is still
|
||||
installed under the applications library path in site-packages. Take a look
|
||||
at the files created by Paster and you will see that the tree is almost
|
||||
the exact same as the main applications source tree.
|
||||
|
||||
|
||||
Enabling Internal/External Plugins
|
||||
----------------------------------
|
||||
|
||||
Plugins are enabled by first installing them, and then creating a plugin.conf
|
||||
within your applications plugins.d directory (set by plugin_config_dir).
|
||||
Plugin code is only loaded when 'enable_plugin=yes'.
|
||||
|
||||
**/etc/yourapp/plugins.d/your-plugin.conf**
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
[your-plugin]
|
||||
enable_plugin = true
|
||||
some_option = some value
|
||||
foo = bar
|
||||
|
||||
|
||||
|
||||
Shared Plugin Support
|
||||
---------------------
|
||||
|
||||
Another form of plugin, is a shared plugin from another application. For
|
||||
example, you can have a parent (company wide) application that has shared
|
||||
functionality and re-usable code. Those plugins, from and for a completely
|
||||
different application, can be loaded into your application to extend
|
||||
functionality.
|
||||
|
||||
A perfect example of using shared plugins is via The Rosendale Project. This
|
||||
project is specifically geared toward building shared plugins for applications
|
||||
that are built on the Cement Framework. Where internal, and external plugins
|
||||
are built specifically for your application, shared plugins are loaded from
|
||||
another application.
|
||||
|
||||
Using the 'clibasic' plugin from The Rosendale Project as an example, the
|
||||
following outlines how to load it as part of your application.
|
||||
|
||||
**/etc/yourapp/plugins.d/clibasic.conf**
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
[clibasic]
|
||||
enable_plugin = true
|
||||
provider = rosendale
|
||||
|
||||
|
||||
The plugin will be loaded from the rosendale namespace, but will function as
|
||||
if it were built specifically for your application. Yes, we know... this is
|
||||
pretty awesome... you're right.
|
||||
@ -1,265 +0,0 @@
|
||||
Quick Starting a New CLI Application
|
||||
====================================
|
||||
|
||||
The following outlines how to create a new application built on The Cement
|
||||
CLI Application Framework. Throughout this documentation we reference an
|
||||
application called 'helloworld'. For almost all cases, you can replace
|
||||
helloworld with the package name of your application.
|
||||
|
||||
|
||||
Raw Commands For The Impatient
|
||||
------------------------------
|
||||
|
||||
This is for development. Please note that in production you will likely be
|
||||
installing system wide (with root access), and that you only need 'cement' in
|
||||
production (not cement.devtools).
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
### install
|
||||
|
||||
$ virtualenv --no-site-packages ~/env/helloworld
|
||||
|
||||
$ source ~/env/helloworld/bin/activate
|
||||
|
||||
$ easy_install cement.devtools
|
||||
|
||||
|
||||
### create app
|
||||
|
||||
$ paster cement-app helloworld
|
||||
|
||||
$ cd helloworld
|
||||
|
||||
$ python setup.py develop
|
||||
|
||||
|
||||
### setup local (user) config
|
||||
|
||||
$ cp -a ./etc/helloworld.conf-dev ~/.helloworld.conf
|
||||
|
||||
$ vi ~/.helloworld.conf
|
||||
|
||||
$ helloworld --help
|
||||
|
||||
|
||||
### create an external plugin
|
||||
|
||||
$ mkdir plugins
|
||||
|
||||
$ cd plugins
|
||||
|
||||
$ paster cement-plugin helloworld myplugin
|
||||
|
||||
$ cd helloworld.myplugin
|
||||
|
||||
$ python setup.py develop
|
||||
|
||||
$ cp -a ./etc/plugins.d/myplugin.conf ~/path/to/plugin_config_dir
|
||||
|
||||
$ helloworld --help
|
||||
|
||||
|
||||
Installing Cement
|
||||
-----------------
|
||||
|
||||
This section outlines how to install Cement. By preference, we do so by way
|
||||
of installing to a virtualenv. This is not necessary if you have root access
|
||||
on your system and want to install system wide. That said, for development
|
||||
purposes (i.e. building your application) you should be working out of a
|
||||
virtualenv.
|
||||
|
||||
Before installing Cement, setup your virtual environment:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ virtualenv --no-site-packages ~/devel/env/helloworld
|
||||
|
||||
$ source ~/devel/env/helloworld/bin/activate
|
||||
|
||||
(helloworld) $
|
||||
|
||||
|
||||
Your virtual environment is now active. Anything you install will be
|
||||
installed to this location and not system wide. To leave the environment, run
|
||||
the following:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
(helloworld) $ deactivate
|
||||
|
||||
$
|
||||
|
||||
|
||||
Stable
|
||||
^^^^^^
|
||||
|
||||
Stable versions of Cement can be installed from the CheezeShop via
|
||||
easy_install (devtools installs cement as a dependency):
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ easy_install cement.devtools
|
||||
|
||||
|
||||
Development
|
||||
^^^^^^^^^^^
|
||||
|
||||
Development versions of Cement can be cloned from GitHub:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ git clone git://github.com/derks/cement.git
|
||||
|
||||
# install cement core framework
|
||||
$ cd cement/src/cement
|
||||
$ python setup.py install
|
||||
|
||||
# install devtools
|
||||
$ cd ../cement.devtools
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
|
||||
The 'master' branch tracks development that is compatible with the stable
|
||||
API. However, development on the next major version which will break API
|
||||
compatibility is tracked in the 'portland' branch. The portland branch
|
||||
can be checkout by:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ git checkout --track -b portland origin/portland
|
||||
|
||||
|
||||
Creating The HelloWorld Application
|
||||
-----------------------------------
|
||||
|
||||
Now that the Cement Framework is installed, we can create our application
|
||||
from templates via PasteScript (which is installed as a dependency when you
|
||||
install Cement). The following creates and installs a new CLI Application
|
||||
called HelloWorld, and copies a 'development' config file to your home
|
||||
directory path. Note that the -dev config is geared towards 'local' file
|
||||
paths for your user where as the other config is geared towards a system wide
|
||||
production install:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ paster cement-app helloworld
|
||||
|
||||
$ cd helloworld
|
||||
|
||||
$ python setup.py develop
|
||||
|
||||
$ cp -a etc/helloworld.conf-dev ~/.helloworld.conf
|
||||
|
||||
|
||||
**Note:** You need to look at ~/.helloworld.conf and edit any settings. For
|
||||
most cases, the only thing you might want to edit is the 'plugin_config_dir'
|
||||
path to point it to '/path/to/helloworld/etc/plugins.d'. Your application by
|
||||
default searches for configs in the following order:
|
||||
|
||||
* /etc/helloworld/helloworld.conf
|
||||
* ~/.helloworld/etc/helloworld.conf
|
||||
* ~/.helloworld.conf
|
||||
|
||||
The second is a hard set location based on the 'prefix' in your applications
|
||||
'helloworld/core/config.py' and is not often relied on. Now that helloworld
|
||||
is installed, lets see what it looks like:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld --help
|
||||
loading example plugin
|
||||
Usage: helloworld [COMMAND] --(OPTIONS)
|
||||
|
||||
Commands:
|
||||
get-started, cmd1, cmd2, example*
|
||||
|
||||
|
||||
Help? try [COMMAND]-help
|
||||
|
||||
Options:
|
||||
--version show program's version number and exit
|
||||
-h, --help show this help message and exit
|
||||
-R, --root-option Example root option
|
||||
--json render output as json (Cement CLI-API)
|
||||
--debug toggle debug output
|
||||
--quiet disable console logging
|
||||
|
||||
|
||||
Go ahead and run the get-started command:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld get-started
|
||||
|
||||
|
||||
It is more or less the same information you are reading here, however it is
|
||||
also a functional command that is rendered by Genshi and a template. We've
|
||||
put it there to show how commands are created and rendered. Go ahead and
|
||||
take a look at the following files to see where and how that command is setup:
|
||||
|
||||
* helloworld/controllers/root.py
|
||||
* helloworld/templates/root/get-started.txt
|
||||
|
||||
|
||||
You will also notice that your app is already loading an 'example' plugin.
|
||||
Plugins are enabled under their [plugin] config either in your main
|
||||
application configuration file, or in the plugins.d/<plugin_name>.conf file for
|
||||
that plugin. An example plugin config looks like:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
[example]
|
||||
enable_plugin = true
|
||||
provider = helloworld
|
||||
|
||||
|
||||
The 'provider' is the package that provides it and can be omitted for plugins
|
||||
that are a part of your application. However, you can load plugins from any
|
||||
other application that is built on Cement by adding them as the provider.
|
||||
The plugin has to be written in a 'generic' fashion of course. For more
|
||||
information on shared plugins check our The Rosendale Project which provides
|
||||
plugins explicitly for re-usability in other applications built on Cement.
|
||||
|
||||
The included example plugin is a great starting point to learn how to build an
|
||||
application on top of the Cement Framework. The following files and
|
||||
directories should be explored:
|
||||
|
||||
* ./helloworld/bootstrap/example.py
|
||||
* ./helloworld/controllers/example.py
|
||||
* ./helloworld/model/example.py
|
||||
* ./helloworld/templates/example/
|
||||
|
||||
It should be noted that the only difference between a plugin, and a built in
|
||||
part of your application is that a plugin is optional, and only loaded if
|
||||
enabled via the configuration. You can make the example plugin part of your
|
||||
application by adding the following to 'helloworld/bootstrap/root.py'
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from helloworld.bootstrap import example
|
||||
|
||||
|
||||
All modules imported into the root bootstrap become a part of the application
|
||||
permanently (meaning its not loaded as an optional plugin). You then want to
|
||||
move the plugins configuration from a separate plugin config to your primary
|
||||
applications configuration and remove 'enable_plugin' setting.
|
||||
|
||||
Once you're ready to start coding, you can disable the 'example' plugin by
|
||||
setting 'enable_plugin=false' in plugins.d/example.conf. That said, it is
|
||||
recommended to keep the example plugin included with our application, as this
|
||||
also provides a starting point for developers wanting to build external plugins
|
||||
for your application (explained later on).
|
||||
|
||||
By default, the base application has a command named 'cmd1' created in the
|
||||
controller and the options -R/--root-option, --debug, --quiet, --json which
|
||||
are created in the bootstrap file. You can remove these from the bootstrap
|
||||
file so that they don't show up under '--help', however please note that
|
||||
--debug, --quiet, and --json are hard coded in the Cement framework and will
|
||||
still function if the user passes them at command line.
|
||||
|
||||
The example plugin provides the 'example*' namespace, which has two commands
|
||||
under it called 'ex1', and 'ex2' created in the controller, as well as the
|
||||
'-F/--foo' option created in the bootstrap file. The controller also exposes
|
||||
a root command called 'cmd2'.
|
||||
@ -1,226 +0,0 @@
|
||||
Genshi Templating Engine
|
||||
========================
|
||||
|
||||
Cement applications use a Model, View, Controller design. By separating the
|
||||
view, or in this case what is printed to STDOUT, you can significantly clean
|
||||
up your controller code. Cement configures new applications to use the
|
||||
Genshi text templating language by default. This is configured in your
|
||||
applications 'core.config' module via the setting 'output_handler'. Note
|
||||
that for developers not interested in having output rendered from template
|
||||
or any other kind of output rendering... this setting can be set to None.
|
||||
|
||||
A sample controller that exports its data to a Genshi template:
|
||||
|
||||
**./helloworld/controllers/example.py**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from helloworld.model.example import ExampleModel
|
||||
|
||||
@expose('helloworld.templates.example.ex2', namespace='root')
|
||||
def ex2(self, cli_opts, cli_args):
|
||||
example = ExampleModel()
|
||||
example.label = 'This is my Example Model'
|
||||
|
||||
if cli_opts.my_option:
|
||||
print '%s passed by --my-option' % cli_opts.root_option
|
||||
|
||||
return dict(foo=True, example=example, items=['one', 'two', 'three'])
|
||||
|
||||
It should be noted that the output_handler is implied in the above code, and
|
||||
tells Cement to use the default that is configured in your apps core.config
|
||||
module under 'output_handler'. To specify an alternate handler, you can do
|
||||
the following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@expose('jinja2:helloworld.templates.example.ex2', namespace='root')
|
||||
...
|
||||
|
||||
Where 'jinja2' is the alternate output_handler to use for that command only,
|
||||
assuming that a plugin is installed that provides an output_handler called
|
||||
'jinja2'.
|
||||
|
||||
The template looks like:
|
||||
|
||||
**./helloworld/templates/example/ex2.txt**:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{# This is an example Genshi Text Template. Documentation is available #}\
|
||||
{# at: #}\
|
||||
{# #}\
|
||||
{# http://genshi.edgewall.org/wiki/Documentation/text-templates.html #}\
|
||||
{# #}\
|
||||
\
|
||||
\
|
||||
{# --------------------- 78 character baseline --------------------------- #}\
|
||||
|
||||
There are a number of things you can do such as conditional statements:
|
||||
|
||||
{% if foo %}\
|
||||
Label: ${example.label}
|
||||
{% end %}\
|
||||
|
||||
Or a for loop:
|
||||
|
||||
{% for item in items %}\
|
||||
* ${item}
|
||||
{% end %}\
|
||||
|
||||
|
||||
And functions:
|
||||
|
||||
{% def greeting(name) %}\
|
||||
Hello, ${name}!
|
||||
{% end %}\
|
||||
${greeting('World')}\
|
||||
${greeting('Edward')}
|
||||
|
||||
|
||||
Admittedly, the syntax is a bit cumbersome. But once you get the hang of it
|
||||
there is a lot you can do with it, and your controller code looks so much
|
||||
better. When rendered, this looks like:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld ex2
|
||||
loading example plugin
|
||||
loading clibasic plugin
|
||||
|
||||
There are a number of things you can do such as conditional statements:
|
||||
|
||||
Label: This is my Example Model
|
||||
|
||||
Or a for loop:
|
||||
|
||||
* one
|
||||
* two
|
||||
* three
|
||||
|
||||
And functions:
|
||||
|
||||
Hello, World!
|
||||
Hello, Edward!
|
||||
|
||||
|
||||
For simple methods that don't print much data or maybe don't print at all, you
|
||||
can simply skip the templating engine. The same method without rendering would
|
||||
be:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from helloworld.model.example import ExampleModel
|
||||
|
||||
@expose(namespace='root')
|
||||
def ex2(self, cli_opts, cli_args):
|
||||
example = ExampleModel()
|
||||
example.label = 'This is my Example Model'
|
||||
|
||||
if cli_opts.my_option:
|
||||
print '%s passed by --my-option' % cli_opts.root_option
|
||||
|
||||
return dict(foo=True, example=example, items=['one', 'two', 'three'])
|
||||
|
||||
Now, nothing is rendered by Genshi and no output will be printed to the
|
||||
console unless you print it out yourself. That said, because we are still
|
||||
returning our dictionary, we can still use our '--json' and output Json via
|
||||
the CLI-API.
|
||||
|
||||
Genshi Syntax Basics
|
||||
--------------------
|
||||
|
||||
As noted in the example template, documentation on Genshi Text Templating can
|
||||
be found at:
|
||||
|
||||
http://genshi.edgewall.org/wiki/Documentation/text-templates.html
|
||||
|
||||
**Printing Variables**
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Hello ${user_name}
|
||||
|
||||
Where 'user_name' is a variable returned from the controller. Will display:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Hello Johnny
|
||||
|
||||
|
||||
**if statements**
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{% if foo %}\
|
||||
Label: ${example.label}
|
||||
{% end %}\
|
||||
|
||||
Will only output 'Label: <label>' if foo == True.
|
||||
|
||||
|
||||
**for loops**
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{% for item in items %}\
|
||||
- ${item}
|
||||
{% end %}\
|
||||
|
||||
Where 'items' is a list returned from the controller. Will display:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
- list item 1
|
||||
- list item 2
|
||||
- list item 3
|
||||
|
||||
**Functions**
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{% def greeting(name) %}\
|
||||
Hello, ${name}!
|
||||
{% end %}\
|
||||
${greeting('World')}\
|
||||
${greeting('Edward')}
|
||||
|
||||
|
||||
Will output:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Hello, World!
|
||||
Hello, Edward!
|
||||
|
||||
|
||||
**Formatted Columns**
|
||||
|
||||
The following example comes from the 'list-plugins' controller command in the
|
||||
clibasic plugin of The Rosendale Project:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{# --------------------- 78 character baseline --------------------------- #}\
|
||||
|
||||
plugin ver description
|
||||
================== ======== ================================================
|
||||
{% for plugin in plugins %}\
|
||||
${"%-18s" % plugin.label} ${"%-8s" % plugin.version} ${"%-48s" % plugin.description}
|
||||
{% end %}
|
||||
|
||||
|
||||
Output looks like:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ helloworld list-plugins
|
||||
loading example plugin
|
||||
loading clibasic plugin
|
||||
|
||||
plugin ver description
|
||||
================== ======== ================================================
|
||||
example 0.1 Example plugin for helloworld
|
||||
clibasic 0.5r2 Basic CLI Commands for Cement Applications
|
||||
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
Nose Testing Your Application
|
||||
=============================
|
||||
|
||||
Because the majority of features in Cement rely on a loaded application to
|
||||
access them, testing is a bit more complex than simply running nose on your
|
||||
source tree. There are some features built into Cement and the devtools
|
||||
templates that provide a semi-standard means of testing our application
|
||||
|
||||
Obviously, there are other means of testing besides Nose but it is a very
|
||||
common standard for testing. For more information on Nose please see their
|
||||
`website <http://somethingaboutorange.com/mrl/projects/nose/0.11.2/>`_.
|
||||
|
||||
Configuring Nose Tests
|
||||
----------------------
|
||||
|
||||
The primary thing to note about nose testing is that your base application
|
||||
needs to be loaded in order to test it, however because nose runs all tests
|
||||
in one stream it means that the application is only loaded once.. and not
|
||||
everytime for each test (and for each command you are testing). For that
|
||||
reason we have developed a scheme for testing that allows the application
|
||||
to be loaded once, but then having the ability to simulate running commands
|
||||
from command line.
|
||||
|
||||
As of 0.8.9, creating applications using the cement.devtools and paster
|
||||
templates generate a base ./tests directory with functional nose tests
|
||||
that serve as an example and starting point for adding more tests. You will
|
||||
see two files:
|
||||
|
||||
* tests/00_initialize_tests.py
|
||||
* tests/example_tests.py
|
||||
|
||||
|
||||
The 00_initialize_tests.py file must be loaded first (which is why it starts
|
||||
with 00), which runs the 'nose_main()' function from your application. This
|
||||
is an alternative to using 'main()' and does not catch any exceptions
|
||||
(allowing you to test for them). There is also an alternative
|
||||
'get_nose_config()' function in yourapp.core.config that has a configuration
|
||||
specifically for testing and assumes you are running from within the root
|
||||
of your sources directory. Finally, in your ./config directory there is a
|
||||
configuration file called 'yourapp.conf-test' which is meant for testing
|
||||
only.
|
||||
|
||||
It is important to note that nosetests must be run from the root of your
|
||||
applications sources (by default)... and that as the application grows you
|
||||
must add tests to ./tests to test new features.
|
||||
|
||||
|
||||
Creating a Nose Test
|
||||
--------------------
|
||||
|
||||
All nose testing is standard, however how you call parts of your application
|
||||
is Cement specific. Take the following example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from nose.tools import raises, with_setup, eq_, ok_
|
||||
from cement.core.testing import simulate
|
||||
|
||||
def setup_func():
|
||||
# do something before every test
|
||||
pass
|
||||
|
||||
def teardown_func():
|
||||
# do something after every test
|
||||
pass
|
||||
|
||||
@with_setup(setup_func, teardown_func)
|
||||
def test_some_functionality():
|
||||
(res_dict, output_txt) = simulate([__file__, 'some-cmd', '--foo=bar'])
|
||||
# do something to test the results
|
||||
|
||||
As you can see, simulate is used to 'simulate' running a command at the
|
||||
command line. It takes a list of args which it expects to be in the same
|
||||
fashion as it would be as 'sys.argv'. The first argument is '__file__'
|
||||
simply because from command line this would be the name of your cli app,
|
||||
however in testing it can be useful to know what __file__ the call is coming
|
||||
from.
|
||||
|
||||
The simulate function returns a tuple of (result_dictionary, output_txt) which
|
||||
is the 'dict' as returned by the controller function, and the output text as
|
||||
returned from the output handler. It should be noted that this is not the
|
||||
output of say 'print()', but only output rendered by the output handler
|
||||
(genshi, json, etc).
|
||||
|
||||
There are many internals within cement that can be used directly such as the
|
||||
global namespaces, hooks, handlers, etc but that is outside the scope of this
|
||||
doc. How to test your applications features is very dependent on what the
|
||||
application and those features do... however using simulate is a solid
|
||||
starting point to getting basic testing of your application going.
|
||||
|
||||
Be sure to look in the ./tests directory of your application to see a working
|
||||
example of this documentation (as of 0.8.9). Additionally, you can review the
|
||||
code of the `Cement Test <http://github.com/derks/cement.test>`_ application which provides 95% test coverage of the
|
||||
Cement framework.
|
||||
|
||||
@ -1,129 +0,0 @@
|
||||
Understanding Your Application
|
||||
==============================
|
||||
|
||||
Your application is broken up into specific directories, each with their own
|
||||
purpose. Using our helloworld example we have the following modules and
|
||||
directories:
|
||||
|
||||
* helloworld.core
|
||||
* helloworld.bootstrap
|
||||
* helloworld.controllers
|
||||
* helloworld.lib
|
||||
* helloworld.model
|
||||
* helloworld.templates
|
||||
* helloworld.helpers
|
||||
|
||||
We are going to briefly explain each, and go into more detail later in the
|
||||
documentation.
|
||||
|
||||
|
||||
helloworld.core
|
||||
---------------
|
||||
|
||||
This module is the 'core' of your application and is the first phase of the
|
||||
application runtime where the Cement Framework is initialized. By default this
|
||||
includes the appmain.py and config.py files. Both of which can be modified,
|
||||
but you don't need to. The core module should be used for any code that sets
|
||||
up the base of your application, or provides libraries and functions that are
|
||||
not tied to any commands (commands are created in the controllers module). An
|
||||
example of a library that might go into the core module would be code that sets
|
||||
up an xmlrpclib proxy object to talk to a remote server. Most likely you want
|
||||
all controllers to access the same proxy object once it is setup.
|
||||
|
||||
It should be noted that plugins should not create files/libraries in this
|
||||
namespace.
|
||||
|
||||
|
||||
helloworld.bootstrap
|
||||
--------------------
|
||||
|
||||
The bootstrap module is the second phase of the application runtime and is
|
||||
used to initialize parts of the application that are required before any
|
||||
controllers or code is loaded. Typical uses of the bootstrap module are for:
|
||||
|
||||
* Creating namespaces
|
||||
* Defining and registering hooks
|
||||
|
||||
The helloworld/bootstrap/root.py is the only bootstrap called by the Cement
|
||||
Framework, however the 'root' namespace is already setup by Cement and does
|
||||
not get created in this bootstrap. That said, any additional bootstrap files
|
||||
need to be imported into the root bootstrap. For example, if you are creating
|
||||
a namespace for 'system' you would create a file at
|
||||
helloworld/bootstrap/system.py where you would define and configure that
|
||||
namespace. Then, in helloworld/bootstrap/root.py you would import it like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from helloworld.bootstrap import system
|
||||
|
||||
|
||||
This triggers the system bootstrap which is responsible for setting up the
|
||||
system namespace.
|
||||
|
||||
|
||||
helloworld.controllers
|
||||
----------------------
|
||||
|
||||
The controllers module is used primarily for creating commands. A controller
|
||||
is attached to a namespace when that namespace is bootstrapped, therefore
|
||||
you should not import a controller directly anywhere in your application.
|
||||
|
||||
Controllers are used to expose commands to your application, and then perform
|
||||
operations when that command is called. Ideally it should not present
|
||||
output to the user at all, as this is handled by your templates however some
|
||||
scenarios don't lend themselves well to templating so making print statements
|
||||
is also possible. Each command should perform an action and then return a
|
||||
dictionary of data. This dictionary is then used to be rendered into either
|
||||
Json, or text by the Genshi Engine. That said, some people will not care to
|
||||
use templating and would rather just print output to the console. This is
|
||||
perfectly fine, but may clutter up your controller code with excessive print
|
||||
statements and janky formatting.
|
||||
|
||||
|
||||
helloworld.lib
|
||||
--------------
|
||||
|
||||
The lib module is a common place for code that doesn't necessarily fit into
|
||||
the 'controller', or 'model' modules, but is not necessarily 'core' code.
|
||||
Such code might be part of a plugin (which shouldn't create any code in the
|
||||
core namespace).
|
||||
|
||||
|
||||
helloworld.model
|
||||
----------------
|
||||
|
||||
The model module is used to define objects related to data. This might be
|
||||
an SQLAlchemy DeclarativeBase object, or similar data abstractions. It can
|
||||
be anything you want, but should be strictly for interfacing with data and
|
||||
not interfacing with the user.
|
||||
|
||||
Generally, the controller will use a model object to store data that it is
|
||||
operating on. This allows you to separate the code that defines the model
|
||||
from the other parts of the application that use that model.
|
||||
|
||||
|
||||
helloworld.templates
|
||||
--------------------
|
||||
|
||||
The templates module is a data directory containing nothing but txt template
|
||||
files. Note that the first level must be directories related to a namespace.
|
||||
For example 'helloworld/templates/root' would be the directory with txt
|
||||
files used for templating commands that are exposed from the root controller.
|
||||
|
||||
Cement uses the Genshi Text Template Engine to render the dictionary of data
|
||||
that your controller function returns. Documentation can be found at:
|
||||
|
||||
* http://genshi.edgewall.org/wiki/Documentation/text-templates.html
|
||||
|
||||
Note that every directory under 'helloworld.templates' must have a __init__.py
|
||||
file or your application will fail to load templates from it.
|
||||
|
||||
|
||||
helloworld.helpers
|
||||
------------------
|
||||
|
||||
Finally, the helpers module is used for miscellaneous code that is used
|
||||
throughout your application. This code should not be namespace or plugin
|
||||
specific but should be able to be called from anywhere in your application.
|
||||
Helpers are often used for quick functions that just don't fit anywhere else.
|
||||
|
||||
@ -1,41 +1,20 @@
|
||||
.. The Cement CLI Application Framework documentation master file, created by
|
||||
sphinx-quickstart on Thu Jan 28 02:44:35 2010.
|
||||
.. Cement documentation master file, created by
|
||||
sphinx-quickstart on Mon Aug 22 17:52:04 2011.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Cement CLI Application Framework Documentation
|
||||
==============================================
|
||||
Welcome to Cement's documentation!
|
||||
==================================
|
||||
|
||||
Cement is an advanced CLI Application Framework for Python. This documentation
|
||||
is a guide for developers wishing to build their applications on top of the
|
||||
Cement Framework.
|
||||
|
||||
* Doc: http://builtoncement.org/cement/0.8/doc/
|
||||
* Download: http://builtoncement.org/cement/0.8/source/
|
||||
* Code: http://github.com/derks/cement
|
||||
|
||||
|
||||
The Python packages are available separately via PyPi:
|
||||
|
||||
* http://pypi.python.org/pypi/cement
|
||||
* http://pypi.python.org/pypi/cement.devtools
|
||||
|
||||
|
||||
The Rosendale Project
|
||||
---------------------
|
||||
|
||||
The Rosendale Project is geared towards providing shared plugins for all
|
||||
applications built on Cement.
|
||||
|
||||
* http://builtoncement.org/rosendale/1.0/doc/
|
||||
|
||||
|
||||
Contents
|
||||
--------
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
dev
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user