fresh doc

This commit is contained in:
BJ Dierkes 2011-08-22 17:52:50 -05:00
parent 0da49b7df5
commit aaf39b9036
22 changed files with 57 additions and 2483 deletions

6
README
View File

@ -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.

View File

@ -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."

View File

@ -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

View File

@ -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:

View File

@ -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.

View File

@ -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.

View File

@ -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)
]

View File

@ -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

View File

@ -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": ""
}

View File

@ -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.

View File

@ -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')

View File

@ -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 wouldnt 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 doesnt 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.

View File

@ -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

View File

@ -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/Modelviewcontroller>`_.
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.

View File

@ -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

View File

@ -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).

View File

@ -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.

View File

@ -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'.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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`