reorganizing files, including devtools/test

This commit is contained in:
BJ Dierkes 2010-12-07 18:04:43 -06:00
parent befba3ead2
commit 2a10982704
147 changed files with 3314 additions and 17 deletions

View File

@ -3,6 +3,8 @@
-----------------------------------------------------------------------------
+ Development Release 0.8.13
+ Moving cement.devtools and cement.test back under main project
+ Reorganized project files, python packages are under ./src
0.8.12 - Dec 06, 2010

View File

@ -20,3 +20,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

14
README
View File

@ -63,16 +63,6 @@ Development versions of Cement can be checked out of Git:
::
$ git clone git://github.com/derks/cement.git
$ cd cement
$ python setup.py install
$ git clone git://github.com/derks/cement.devtools.git
$ cd cement.devtools
$ python setup.py install
With the 'devtools' package, Cement applications, and plugins can be
created via PasteScript. Once cement and cement.devtools are installed,
@ -86,7 +76,3 @@ The following command will create an external plugin for your application:
::
$ paster cement-plugin myapp myplugin
Have an external helper library you want to make plugable?
::
$ paster cement-helper myapp myhelper

View File

@ -0,0 +1,23 @@
The MIT License:
Copyright (c) 2009-2010 BJ Dierkes
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1 @@
recursive-include cement/paste/templates *

View File

@ -0,0 +1,40 @@
NAME: cement.devtools
CREATOR/MAINTAINER: BJ Dierkes <wdierkes@5dollarwhitebox.org>
DESCRIPTION:
Cement is an advanced CLI Application Framework for Python. The 'devtools'
package provides tools and libraries needed for developing applications that
are built on Cement.
The Cement CLI Application Framework is Open Source and is distributed under
The MIT License.
GETTING STARTED:
Stable versions of Cement can be installed via the cheeze shop:
::
$ easy_install cement
$ easy_install cement.devtools
Development versions of Cement can be checked out of Git:
::
$ git clone git://github.com/derks/cement.git
With the 'devtools' package, Cement applications, and plugins can be
created via PasteScript. Once cement.core and cement.devtools are installed,
the following command will create a command line application built on top of
the Cement Framework:
::
$ paster cement-app myapp
The following command will create an external plugin for your application:
::
$ paster cement-plugin myapp myplugin

View File

@ -0,0 +1,2 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,25 @@
"""
The module handles the integration with PasteScript enabling the following
plugins to the 'paste' command line utility:
cement-app
Generates a base application built on Cement.
cement-plugin
Generates an external plugin for an application built on Cement.
cement-helper
Generates an external helper for an application built on Cement.
Usage:
.. code-block::
$ paster cement-app helloworld
$ paster cement-plugin helloworld myplugin
$ paster cement-helper helloworld myhelper
"""
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,272 @@
"""
A significant portion of this file was derived from the tg.devtools software
which is licensed under the MIT license. The following license applies to
the work in *this* file only, and not any other part of the Cement Framework
unless otherwise noted:
-----------------------------------------------------------------------------
Copyright (c) 2008 TurboGears Team
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-----------------------------------------------------------------------------
Paste commands to generate a new Cement projects and plugins.
"""
# Taken mostly from tg.devtools... Thank you!
import sys, os
import re
import pkg_resources
import optparse
from paste.script import command
from paste.script import create_distro
beginning_letter = re.compile(r"^[^a-z]*")
valid_only = re.compile(r"[^a-z0-9_\.]")
CEMENT_VERSION = pkg_resources.get_distribution('cement').version
(base, major) = CEMENT_VERSION.split('.')[:2]
CEMENT_MAJOR_VERSION = '.'.join(CEMENT_VERSION.split('.')[:2])
if int(major)%2==0:
CEMENT_NEXT_VERSION = float(CEMENT_MAJOR_VERSION) + 0.1
else:
CEMENT_NEXT_VERSION = float(CEMENT_MAJOR_VERSION) + 0.2
class CementAppCommand(command.Command):
"""Create a new CLI Application using the Cement Framework.
Example usage::
$ paster cement-app yourproject
"""
version = CEMENT_VERSION
summary = __doc__.splitlines()[0]
usage = '\n' + __doc__
name = None
group_name = "Cement"
dry_run = False
templates = "cementapp"
parser = command.Command.standard_parser(quiet=True)
parser = optparse.OptionParser(
usage="%prog cement-app [options] [project name]",
version="%prog " + version
)
parser.add_option("-t", "--templates",
help="user specific templates",
dest="templates", default=templates
)
parser.add_option("--dry-run",
help="dry run (don't actually do anything)",
action="store_true", dest="dry_run"
)
def command(self):
"""LayCement for the new project."""
self.__dict__.update(self.options.__dict__)
if self.args:
self.name = self.args[0]
while not self.name:
self.name = raw_input("Enter project name: ").strip()
self.name = pkg_resources.safe_name(self.name)
package = self.name.lower()
package = beginning_letter.sub("", package)
package = valid_only.sub("_", package)
self.package = raw_input(
"Enter module name [%s]: " % package).strip() or package
self.description = raw_input(
"Project Description: ").strip()
self.creator = raw_input(
"Project Creator: ").strip()
self.creator_email = raw_input(
"Project Creator Email: ").strip()
self.url = raw_input(
"Project URL: ").strip()
self.license = raw_input(
"Project License: ").strip()
env = pkg_resources.Environment()
if self.name.lower() in env:
print 'The name "%s" is already in use by ' % self.name,
for dist in env[self.name]:
print dist
return
import imp
try:
if imp.find_module(self.package):
print 'The package name "%s" is already in use' % self.package
return
except ImportError:
pass
if os.path.exists(self.name):
print 'A directory called "%s" already exists. Exiting.' % self.name
return
command = create_distro.CreateDistroCommand("create")
cmd_args = []
for template in self.templates.split():
cmd_args.append("--template=%s" % template)
cmd_args.append(self.name)
cmd_args.append("package=%s" % self.package)
cmd_args.append("cement_version=%s" % CEMENT_VERSION)
cmd_args.append("cement_next_version=%s" % CEMENT_NEXT_VERSION)
cmd_args.append("description=%s" % self.description)
cmd_args.append("creator=%s" % self.creator)
cmd_args.append("creator_email=%s" % self.creator_email)
cmd_args.append("url=%s" % self.url)
cmd_args.append("license=%s" % self.license)
command.run(cmd_args)
if not self.dry_run:
sys.argv = ["setup.py", "egg_info"]
# dirty hack to allow "empty" dirs
for base, path, files in os.walk("./"):
for file in files:
if file == "empty":
os.remove(os.path.join(base, file))
class CementPluginCommand(command.Command):
"""Create a plugin for an application using the Cement Framework.
Example usage::
$ paster cement-plugin yourproject yourplugin
"""
version = CEMENT_VERSION
summary = __doc__.splitlines()[0]
usage = '\n' + __doc__
name = None
group_name = "Cement"
dry_run = False
templates = "cementplugin"
project = None
plugin = None
parser = command.Command.standard_parser(quiet=True)
parser = optparse.OptionParser(
usage="%prog cement-plugin [options] [project name] [plugin_name]",
version="%prog " + version
)
parser.add_option("-t", "--templates",
help="user specific templates",
dest="templates", default=templates
)
parser.add_option("--dry-run",
help="dry run (don't actually do anything)",
action="store_true", dest="dry_run"
)
def command(self):
"""LayCement for the new project plugin."""
self.__dict__.update(self.options.__dict__)
if self.args:
try:
self.project = self.args[0].lower()
self.plugin = self.args[1].lower()
except IndexError:
pass
while not self.project:
self.project = raw_input("Enter project name: ").lower()
package = "%s" % self.project
package = beginning_letter.sub("", package)
package = valid_only.sub("_", package)
self.package = raw_input(
"Enter module name [%s]: " % package).strip() or package
while not self.plugin:
self.plugin = raw_input("Enter plugin name: ").lower()
self.plugin = beginning_letter.sub("", self.plugin)
self.plugin = valid_only.sub("_", self.plugin)
self.creator = raw_input(
"Project Creator: ").strip()
self.creator_email = raw_input(
"Project Creator Email: ").strip()
self.url = raw_input(
"Project URL: ").strip()
self.license = raw_input(
"Project License: ").strip()
name = "%s.%s" % (self.project, self.plugin)
self.name = pkg_resources.safe_name(name)
env = pkg_resources.Environment()
if self.name.lower() in env:
print 'The name "%s" is already in use by' % self.name,
for dist in env[self.name]:
print dist
return
if os.path.exists(self.name):
print 'A directory called "%s" already exists. Exiting.' % self.name
return
command = create_distro.CreateDistroCommand("create")
cmd_args = []
for template in self.templates.split():
cmd_args.append("--template=%s" % template)
cmd_args.append(self.name)
cmd_args.append("project=%s" % self.project)
cmd_args.append("package=%s" % self.package)
cmd_args.append("plugin=%s" % self.plugin)
cmd_args.append("cement_version=%s" % CEMENT_VERSION)
cmd_args.append("cement_next_version=%s" % CEMENT_NEXT_VERSION)
cmd_args.append("creator=%s" % self.creator)
cmd_args.append("creator_email=%s" % self.creator_email)
cmd_args.append("url=%s" % self.url)
cmd_args.append("license=%s" % self.license)
command.run(cmd_args)
if not self.dry_run:
sys.argv = ["setup.py", "egg_info"]
# dirty hack to allow "empty" dirs
for base, path, files in os.walk("./"):
for file in files:
if file == "empty":
os.remove(os.path.join(base, file))

View File

@ -0,0 +1,89 @@
"""
Definition for Cement laycement templates.
A significant portion of this file was derived from the tg.devtools software
which is licensed under the MIT license. The following license applies to
the work in *this* file only, and not any other part of the Cement Framework
unless otherwise noted:
-----------------------------------------------------------------------------
Copyright (c) 2008 TurboGears Team
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-----------------------------------------------------------------------------
"""
from paste.script import templates
from tempita import paste_script_template_renderer
import pkg_resources
from pkg_resources import get_distribution
class CementAppTemplate(templates.Template):
"""
Cement default paste template class
"""
_template_dir = 'templates/cementapp'
template_renderer = staticmethod(paste_script_template_renderer)
summary = 'Cement Standard Template'
egg_plugins = ['PasteScript', 'Cement']
vars = [
templates.var("package", "Package module name", default=''),
templates.var("cement_version", "Cement version", default=None),
templates.var("cement_next_version", "Cement Next Version",
default=None),
templates.var("description", "Description", default=''),
templates.var("creator", "Creator", default=''),
templates.var("creator_email", "Creator Email", default=''),
templates.var("url", "URL", default=''),
templates.var("license", "License", default=''),
]
def pre(self, command, output_dir, vars):
"""Called before template is applied."""
pass
class CementPluginTemplate(templates.Template):
"""
Cement plugin default paste template class.
"""
_template_dir = 'templates/cementplugin'
template_renderer = staticmethod(paste_script_template_renderer)
summary = 'Cement Plugin Standard Template'
egg_plugins = ['PasteScript', 'Cement']
vars = [
templates.var("plugin", "cement plugin name", default=None),
templates.var("project", "Parent application this plugin is for",
default=None),
templates.var("package", "Package module name", default=''),
templates.var("cement_version", "Cement version", default=None),
templates.var("cement_next_version", "Cement Next Version",
default=None),
templates.var("creator", "Creator", default=''),
templates.var("creator_email", "Creator Email", default=''),
templates.var("url", "URL", default=''),
templates.var("license", "License", default=''),
]
def pre(self, command, output_dir, vars):
"""Called before template is applied."""
pass

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,39 @@
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(
label='example',
controller='ExampleController',
description='Example Plugin for {{project}}',
provider='{{project}}'
)
# 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)

View File

@ -0,0 +1,32 @@
"""
The bootstrap module should be used to setup parts of your application
that need to exist before all controllers are loaded. It is best used to
define hooks, setup namespaces, and the like. The root namespace is
already bootstrapped by Cement, however you can extend that functionality
by importing additional bootstrap files here.
"""
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)
# Import all additional (non-plugin) bootstrap libraries here
#
# from {{package}}.bootstrap import example
#

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,17 @@
"""{{package}} controller."""
from cement.core.namespace import get_config
from cement.core.log import get_logger
from cement.core.controller import CementController, expose
from cement.core.hook import run_hooks
from {{package}}.model.example import ExampleModel
log = get_logger(__name__)
config = get_config()
class ExampleController(CementController):
@expose('{{package}}.templates.example.cmd', namespace='example')
def cmd(self):
# do something here
pass

View File

@ -0,0 +1,108 @@
"""
This is the ExampleController for the {{project}} application. This can be used
to expose commands to the example namespace which will be accessible under:
$ {{package}} example --help
"""
from cement.core.namespace import get_config
from cement.core.log import get_logger
from cement.core.controller import CementController, expose
from cement.core.hook import run_hooks
from {{package}}.model.example import ExampleModel
log = get_logger(__name__)
class ExampleController(CementController):
@expose(namespace='example') # no template
def ex1(self):
"""
This is how to expose a subcommand because it will be under the
'example' namespace. You would access this subcommand as:
$ {{package}} example ex1
"""
# You can get the root application config like this:
config = get_config('root')
# Or you can get your example namespace config like this:
config = get_config('example')
# You can print or log output however you like since this function
# does not render out to a template.
# Controllers are all passed the opts and args from the command line.
# So if you have added cli options in your {{project}}.bootstrap.example
# file, you could access them here as:
#
# self.cli_opts.<your_option>
# self.cli_args[0] # first argument *after* your command
#
# Here we show how to run a hook that we've defined in
# {{package}}.bootstrap.example:
for res in run_hooks('my_example_hook'):
print res
# This command has no template, but if we return something we
# can still access the json output via --json.
return dict(foo='bar')
@expose(namespace='example')
def ex1_help(self):
"""
Help methods are found by way of <command>_help. This would be
executed when you run:
$ {{package}} example ex1-help
"""
print "This is the help method for ex1."
@expose('{{package}}.templates.example.ex2', namespace='example')
def ex2(self):
"""
This is another command, also in the 'example' namespace but that is
rendered by a genshi template.
Notice that you can specify the namespace via the decorator parameters.
If a plugin has any non-root commands they are grouped under a
single command to the base cli application. For example, you will
see root commands and namespaces* when you execute:
$ {{package}} --help
If 'example' has local commands, you will see 'example*' show up in
the root commands list, and then the subcommands will be seen under:
$ {{package}} myplugin --help
This is done to give different options in how your application works.
"""
# Here we are using our Example model, and then returning a dictionary
# to our @expose() decorator where it will be rendered with Genshi.
example = ExampleModel()
example.label = 'This is my Example Model'
# You can see if options where passed. These are set in
# {{package}}/bootstrap/example.py:
if self.cli_opts.foo:
# --foo was passed, do something
log.info('%s passed by --foo option' % self.cli_opts.foo)
return dict(foo=self.cli_opts.foo, example=example, items=['one', 'two', 'three'])
@expose(namespace='root')
def cmd2(self):
"""This command will be displayed under the root namespace."""
foo = "In {{package}}.controllers.example.cmd2()"
print foo
return dict(foo=foo)

View File

@ -0,0 +1,93 @@
"""
This is the RootController for the {{project}} application. This can be used
to expose commands to the root namespace which will be accessible under:
$ {{package}} --help
"""
from cement.core.controller import CementController, expose
from cement.core.namespace import get_config
from cement.core.log import get_logger
from {{package}}.core.exc import {{package.capitalize()}}ArgumentError
log = get_logger(__name__)
config = get_config()
class RootController(CementController):
@expose('{{package}}.templates.root.error', is_hidden=True)
def error(self, errors=[]):
"""
This can be called when catching exceptions giving the developer a
clean way of presenting errors to the user.
Required Arguments:
errors
A list of tuples in the form of [('ErrorCode', 'Error message')].
The best way to use this is with an 'abort' function... something like:
.. code-block:: python
from cement.core.controller import run_controller_command
def abort(errors):
run_controller_command('root', 'error', errors=errors)
errors = []
# do something, if error
errors.append(('MyErrorCode', 'My Error Message'))
abort(errors)
# continue work (no errors)
"""
return dict(errors=errors)
@expose(is_hidden=True)
def nosetests(self):
"""This method is added for nose testing."""
pass
@expose(is_hidden=True)
def default(self):
"""
This is the default command method. If no commands are passed to
{{package}}, this one will be executed. By default it raises an
exception.
"""
raise {{project.capitalize()}}ArgumentError, "A command is required. See --help?"
@expose('{{package}}.templates.root.cmd1')
def cmd1(self):
"""This is an example 'root' command."""
foo = 'bar'
items = ['one', 'two', 'three']
return dict(foo=foo, items=items)
@expose()
def cmd1_help(self):
"""This is an example 'root' -help command. It should be replaced."""
foo = 'In {{package}}.controllers.root.cmd1_help()'
return dict(foo=foo)
@expose('{{package}}.templates.root.get-started')
def get_started(self):
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"
return dict(config=config, features=features, genshi_link=genshi_link)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,100 @@
"""
This is the application's core code. Unless you know the "ins-and-outs" of
The Cement CLI Application Framework, you probably should not modify the
main() function of this file.
"""
import sys
from pkg_resources import get_distribution
from cement.core.exc import CementArgumentError, CementConfigError
from cement.core.exc import CementRuntimeError
from cement.core.log import get_logger
from cement.core.app_setup import lay_cement
from cement.core.command import run_command
from {{package}}.core.config import default_config
from {{package}}.core.exc import {{package.capitalize()}}ArgumentError, {{package.capitalize()}}ConfigError
from {{package}}.core.exc import {{package.capitalize()}}RuntimeError
VERSION = get_distribution('{{project}}').version
BANNER = """
{{project}} version %s
""" % VERSION
def main(args=None):
try:
if not args:
args = sys.argv
lay_cement(config=default_config, banner=BANNER, args=args,
version=VERSION)
log = get_logger(__name__)
log.debug("Cement Framework Initialized!")
if not len(args) > 1:
args.append('default')
run_command(args[1])
except CementArgumentError, e:
# Display the apps exception names instead for the Cement exceptions.
print("{{package.capitalize()}}ArgumentError > %s" % e)
sys.exit(e.code)
except CementConfigError, e:
print("{{package.capitalize()}}ConfigError > %s" % e)
sys.exit(e.code)
except CementRuntimeError, e:
print("{{package.capitalize()}}RuntimeError > %s" % e)
sys.exit(e.code)
except {{package.capitalize()}}ArgumentError, e:
print("{{package.capitalize()}}ArgumentError > %s" % e)
sys.exit(e.code)
except {{package.capitalize()}}ConfigError, e:
print("{{package.capitalize()}}ConfigError > %s" % e)
sys.exit(e.code)
except {{package.capitalize()}}RuntimeError, e:
print("{{package.capitalize()}}RuntimeError > %s" % e)
sys.exit(e.code)
sys.exit(0)
def nose_main(args, test_config):
"""
This function provides an alternative to main() that is more friendly for
nose tests as it doesn't catch any exceptions.
Required Arguments:
args
The args to pass to lay_cement
test_config
A test config to pass to lay_cement
Usage:
.. code-block:: python
from {{package}}.core.appmain import nose_main
from {{package}}.core.config import get_nose_config
args = [__file__, 'nosetests', '--quiet']
(res_dict, output_text) = nose_main(args, get_nose_config())
"""
lay_cement(config=test_config, banner=BANNER, args=args)
log = get_logger(__name__)
log.debug("Cement Framework Initialized!")
if not len(args) > 1:
args.append('default')
(res, output_txt) = run_command(args[1])
return (res, output_txt)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,70 @@
"""
This is the applications default configuration. You can feel free to alter
this file to your needs, however it should be noted that all these variables
are overridden by settings in /etc/{{package}}/{{package}}.conf and/or
~/.{{package}}.conf (by default) and therefore, any default configurations
you want to make obvious to your users should be set in your default config
file as packaged with the software.
"""
import os
from configobj import ConfigObj
from cement.core.exc import CementConfigError
# This is a sane default for development/no config
prefix = os.path.join(os.environ['HOME'], '.{{package}}')
dcf = ConfigObj() # default config
dcf['config_source'] = ['defaults']
dcf['app_name'] = '{{project}}' # name for cli like /etc/<app_name>
dcf['app_egg_name'] = '{{project}}' # name from setup.py
dcf['app_module'] = '{{package}}' # name of the library dir
dcf['enabled_plugins'] = [] # no default plugins, add via the config file
dcf['debug'] = False
dcf['datadir'] = os.path.join(prefix, 'data')
dcf['tmpdir'] = os.path.join(prefix, 'tmp')
dcf['log_file'] = os.path.join(prefix, 'log', dcf['app_name'])
dcf['plugin_config_dir'] = os.path.join(prefix, 'etc', 'plugins.d')
dcf['log_to_console'] = True
dcf['output_handler'] = 'genshi'
dcf['show_plugin_load'] = False
# By default look in /etc and ~/ for config files. Developers for non *nix
# audiences will want to change this.
dcf['config_files'] = [
os.path.join('/etc', dcf['app_name'], '%s.conf' % dcf['app_name']),
os.path.join(prefix, 'etc', '%s.conf' % dcf['app_name']),
os.path.join(os.environ['HOME'], '.%s.conf' % dcf['app_name']),
]
default_config = dcf
def get_default_config():
"""Return the default application config."""
return default_config
def get_nose_config(prefix=None):
if not prefix:
from tempfile import mkdtemp
prefix = mkdtemp()
tcf = ConfigObj() # test config
tcf['config_files'] = [os.path.abspath('./config/{{package}}.conf-test')]
tcf['config_source'] = ['defaults']
tcf['app_name'] = '{{project}}'
tcf['app_egg_name'] = '{{project}}'
tcf['app_module'] = '{{package}}'
tcf['enabled_plugins'] = []
tcf['debug'] = False
tcf['datadir'] = '%s/data' % prefix
tcf['tmpdir'] = '%s/tmp' % prefix
tcf['log_file'] = '%s/log/%s.log' % (prefix, tcf['app_name'])
tcf['plugin_config_dir'] = './config/plugins.d'
tcf['log_to_console'] = False
tcf['output_engine'] = 'genshi'
tcf['show_plugin_load'] = False
return tcf

View File

@ -0,0 +1,32 @@
"""{{project}} exception classes."""
class {{package.capitalize()}}Error(Exception):
"""Generic errors."""
def __init__(self, value, code=1):
Exception.__init__(self)
self.msg = value
self.code = code
def __str__(self):
return self.msg
def __unicode__(self):
return unicode(self.msg)
class {{package.capitalize()}}ConfigError({{package.capitalize()}}Error):
"""Config parsing and setup errors."""
def __init__(self, value):
code = 10
{{package.capitalize()}}Error.__init__(self, value, code)
class {{package.capitalize()}}RuntimeError({{package.capitalize()}}Error):
"""Runtime errors."""
def __init__(self, value):
code = 20
{{package.capitalize()}}Error.__init__(self, value, code)
class {{package.capitalize()}}ArgumentError({{package.capitalize()}}Error):
"""Argument errors."""
def __init__(self, value):
code = 40
{{package.capitalize()}}Error.__init__(self, value, code)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,13 @@
"""
This is an example model. This can be anything, just a straight class
or perhaps an SQLAlchemy DeclarativeBase, etc.
"""
from cement.core.log import get_logger
log = get_logger(__name__)
class ExampleModel(object):
id = int()
label = u''
description = u''

View File

@ -0,0 +1,28 @@
"""
The root model can be used to consolidate all of your models under one.
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()
"""
# from helloworld.model.example import Example

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,28 @@
{# 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')}

View File

@ -0,0 +1,2 @@
{# --------------------- 78 character baseline --------------------------- #}\
$foo

View File

@ -0,0 +1,7 @@
{# --------------------- 78 character baseline --------------------------- #}\
The following errors were encountered:
-----------------------------------------------------------------------------
{% for error in errors %}\
* ${error[0]} => ${error[1]}
{% end %}

View File

@ -0,0 +1,62 @@
{# --------------------- 78 character baseline --------------------------- #}\
Welcome to ${config['app_name']}.
This application is built on Cement, and is ready to rock! Out of the box you
already have:
{% for feature in features %}\
* $feature
{% end %}\
If your config file is setup right, you should notice that your app is already
loading an 'example' plugin. Plugins are enabled by adding a plugin config
file in your plugin_config_dir (see {{package}}.conf). The plugin config
must have a [<plugin_name>] section and under that section you must set the
following setting
* enable_plugin=true
This will tell Cement to load that plugin. You can also load plugins from
other projects built on Cement by also adding the following setting:
* provider = someotherapp
Where 'someotherapp' is the package name of the application providing the
plugin.
The included example plugin is a great starting point to learn how to build on
top of the Cement Framework. The following files and directories should be
explored:
* ./{{package}}/bootstrap/example.py
* ./{{package}}/controllers/example.py
* ./{{package}}/model/example.py
* ./{{package}}/templates/example/
This command is defined in the RootController in:
{{package}}/controllers/root.py
Additionally, the output of this command is rendered by the Genshi templating
engine. The template is at {{package}}/templates/root/get-started.txt.
You can find documentation here on the template syntax here:
* $genshi_link
That said, you can also render the output of commands as JSON. Don't believe
me? Just run it again with the --json flag.
To make the included 'example' plugin a permanent part of your application you
simply need to add the following to {{package}}/bootstrap/root.py:
from {{package}}.bootstrap import example
That said, by following the example plugin you can easily create built in code
or additional plugins in the same fashion.
Go forth, and code!

View File

@ -0,0 +1,11 @@
SOURCE INSTALLATION:
$ sudo python setup.py install
$ sudo cp -a ./etc /etc/{{package}}
USAGE:
$ {{package}} --help

View File

@ -0,0 +1,2 @@
recursive-include {{package}}/templates *
recursive-include tests *

View File

@ -0,0 +1,64 @@
NAME: {{package}}
AUTHOR:
DESCRIPTION:
Describe your application here.
{{project}} is built on the Cement CLI Application Framework.
USAGE:
$ {{package}} --help
CONFIGURATION:
Application configuration files are parsed in the following order:
* /etc/{{package}}/{{package}}.conf
* ~/.{{package}}.conf
PLUGIN DEVELOPMENT:
{{project}} supports external plugins via the Cement Framework. To quick
start a new plugin for {{project}} you can use the paster utility.
Setup a virtual environment for development:
$ virtualenv --no-site-package /path/to/env
$ source /path/to/env/bin/activate
Install {{project}}:
$ cd /path/to/{{project}}
$ python setup.py develop
Create and install the plugin:
$ mkdir plugins
$ cd plugins
$ paster cement-plugin {{package}} myplugin
$ cd {{package}}.myplugin
$ python setup.py develop
Once your plugin is installed, you can enable it by adding a [plugin] block
to a plugin config in /etc/{{package}}/plugins.d/myplugin.conf. You should
then see some example commands/options show up with:
$ {{package}} --help
Then, code away!

View File

@ -0,0 +1,67 @@
[root]
# This is an example application config using cement.
# PLUGINS
#
# Plugins are enabled under their [plugin] config either in this
# file or in the plugins.d config file for that plugin. An example plugin
# config looks like:
#
# [example]
# enable_plugin = true
# provider = rosendale
#
#
# The 'provider' is the package that provides it. If it is an internal plugin
# this can be left blank.
#
# DIRECTORIES
#
# these should probably be changed for production installations if you
# follow the FHS (and you should).
datadir = ~/{{project}}/data
tmpdir = ~/{{project}}/tmp
# This should be the path to your development tree ...
#
# i.e. /path/to/{{package}}/etc/plugins.d
#
plugin_config_dir = ~/devel/{{project}}/config/plugins.d
# This is just a cosmetic option... whether to show 'loading plugin...'
# on application startup.
#
show_plugin_load = false
# LOGGING
#
# Log file path, comment out if no log is required.
log_file = ~/{{project}}/log/{{project}}.log
# Toggle debug output... can be true, false
debug = false
# Toggle the log level... can be info, warn, error, fatal, debug
log_level = warn
# Whether or not to log to console (this is overridden by 'debug')
log_to_console = true
# Max bytes to rotate log file on. Comment out to not rotate log file.
#
# 512000000 = 512M
#
#log_max_bytes = 512000000
#log_max_files = 4
# ROOT APPLICATION SETTINGS
#
# Add any config options you'd like here
#
# myoption = this is my option
# [namespace]
# Additional namespace/plugin configurations can go here.

View File

@ -0,0 +1,8 @@
# This configuration is specifically meant to be used for testing
[root]
# This is how you'd enable a plugin for testing
[example]
enable_plugin = true
provider = {{package}}
foo = 'some value'

View File

@ -0,0 +1,61 @@
[root]
# This is an example application config using cement.
# PLUGINS
#
# Plugins are enabled under their [plugin] config either in this
# file or in the plugins.d config file for that plugin. An example plugin
# config looks like:
#
# [example]
# enable_plugin = true
# provider = rosendale
#
#
# The 'provider' is the package that provides it. If it is an internal plugin
# this can be left blank.
#
# DIRECTORIES
#
# These settings tend to follow the FHS, and may need to be modified for your
# environment.
datadir = /var/lib/{{project}}
tmpdir = /tmp
plugin_config_dir = /etc/{{project}}/plugins.d
# This is just a cosmetic option... whether to show 'loading plugin...'
# on application startup.
#
show_plugin_load = false
# LOGGING
#
# Log file path, comment out if no log is required.
log_file = /var/log/{{project}}/{{project}}.log
# Toggle debug output... can be true, false
debug = false
# Toggle the log level... can be info, warn, error, fatal, debug
log_level = warn
# Whether or not to log to console (this is overridden by 'debug')
log_to_console = true
# Max bytes to rotate log file on. Comment out to not rotate log file.
#
# 512000000 = 512M
#
#log_max_bytes = 512000000
#log_max_files = 4
# ROOT APPLICATION SETTINGS
#
# Add any config options you'd like here
#
# myoption = this is my option
# [namespace]
# Additional namespace/plugin configurations can go here.

View File

@ -0,0 +1,29 @@
[example]
# This is an example of a cement plugin configuration file. Once parsed, the
# values from here will be accessible from:
#
# namespaces[{{plugin}}].config['param'] = value
#
#
# Some options are built into cement (such as merge_root_options) and
# some are not.
#
# Whether to enable plugin or not
enable_plugin = true
# Whether or not to merge root options
merge_root_options = true
debug = false
param1 = value1
param2 = value two
# equates to True in Python
param3 = true
# equates to False in python
param4 = false
# equates to ['my', 'list', 'of', 'values']
param5 = my, list, of, values,

View File

@ -0,0 +1,40 @@
from setuptools import setup, find_packages
import sys, os
setup(name='{{project}}',
version='0.1',
description='{{description}}',
classifiers=[],
keywords='',
author='{{creator}}',
author_email='{{creator_email}}',
url='{{url}}',
license='{{license}}',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe=False,
install_requires=[
"configobj",
# remove if not using genshi templating
"genshi",
"cement >={{cement_version}}, <{{cement_next_version}}",
],
setup_requires=[
# uncomment for nose testing
# "nose",
],
test_suite='nose.collector',
entry_points="""
[console_scripts]
{{project}} = {{package}}.core.appmain:main
""",
namespace_packages=[
'{{package}}',
'{{package}}.lib',
'{{package}}.bootstrap',
'{{package}}.controllers',
'{{package}}.model',
'{{package}}.helpers',
'{{package}}.templates',
],
)

View File

@ -0,0 +1,19 @@
"""
Initialize the application for nose testing, must be loaded first before
other tests.
"""
from tempfile import mkdtemp
from {{package}}.core.config import get_nose_config
from {{package}}.core.appmain import nose_main
# use an altername config for testing
config = get_nose_config(mkdtemp())
# run the initial main, which bootstraps the base application
nose_main([__file__, 'nosetests', '--quiet'], config)
def test_initialize():
# We put this here just to ensure nose picks up this file.
pass

View File

@ -0,0 +1,64 @@
"""
The purpose of this module is to test application functionality. It is here
as an example of how one might perform nose testing on an application built
on top of the Cement Framework. It is not a fully comprehensive test, of the
application... and needs to be expanded as the application grows.
The initial file loaded by nose runs the application. Additional calls are
then made via the 'simulate' function rather than needing to reload the
application for every test.
"""
import os
from nose.tools import raises, with_setup, eq_, ok_
from cement import namespaces
from cement.core.namespace import get_config
from cement.core.testing import simulate
from cement.core.exc import CementRuntimeError
from {{package}}.core.exc import {{project.capitalize()}}ArgumentError
config = get_config()
def setup_func():
"""Setup operations before every test."""
pass
def teardown_func():
"""Teardown operations after every test."""
pass
@with_setup(setup_func, teardown_func)
def test_cmd1_output():
# Simulate returns the result dictionary, and the render output when
# running the controller command. This can be used to validate that the
# command ran successfully.
(res_dict, output_txt) = simulate([__file__, 'cmd1'])
# You can test that the rendered output is what we expected
eq_(output_txt, 'bar')
# You can test values in the result dictionary directly
ok_(res_dict['foo'])
@raises({{project.capitalize()}}ArgumentError)
@with_setup(setup_func, teardown_func)
def test_default_cmd():
# The default action is to raise an application error if an unknown
# command is called. This is how we test that the exception is raised.
simulate([__file__, 'default'])
@with_setup(setup_func, teardown_func)
def test_config_cli_options():
# Using the test configuration file in ./config we can test config options
# this way. We are also using the example plugin for this test
# First it is set by what is in the config file
eq_(config['example']['foo'], 'some value')
# Then overridden by cli_opts
simulate([__file__, 'example', 'ex1', '--foo=bar'])
# now we validate that the config option was set by the cli_opt --foo
eq_(config['example']['foo'], 'bar')

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,34 @@
"""
This bootstrap module should be used to setup parts of the {{plugin}} plugin
that need to exist before all controllers are loaded. It is best used to
define/register hooks, setup namespaces, and the like.
"""
from pkg_resources import get_distribution
from cement.core.namespace import CementNamespace, register_namespace
VERSION = get_distribution('{{project}}.{{plugin}}').version
# Setup the '{{plugin}}' namespace object
{{plugin}} = CementNamespace(
label='{{plugin}}',
description='{{plugin.capitalize()}} Plugin for {{project.capitalize()}}',
version=VERSION,
controller='{{plugin.capitalize()}}Controller',
provider='{{package}}'
)
# Add a config option to the {{plugin}} namespace. This is effectively the
# default setting for the config option. Overridden by config files, and then
# cli options.
{{plugin}}.config['foo'] = 'bar'
# Add a cli option to the {{plugin}} namespace. This overrides the
# coresponding config option if passed
{{plugin}}.options.add_option('-F', '--foo', action='store', dest='foo',
help='example {{plugin}} option')
# Officialize and register the namespace
register_namespace({{plugin}})

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,35 @@
"""{{plugin}} controller class to expose commands for {{project}}."""
from cement.core.controller import CementController, expose
from {{package}}.model.{{plugin}} import {{plugin.capitalize()}}Model
class {{plugin.capitalize()}}Controller(CementController):
@expose()
def {{plugin}}_command(self):
"""Register root command that doesn't use a template."""
foo = 'bar'
# Even if you're not using a template, return relevant data so that
# you can still use the --json engine, or similar.
return dict(foo=foo)
@expose()
def {{plugin}}_command_help(self):
"""help methods are accessed as 'command-help'."""
print "{{plugin}} root command help method."
@expose('{{project}}.templates.{{plugin}}.{{plugin}}_command')
def {{plugin}}_command2(self, *args, **kw):
"""Register root command, with Genshi templating."""
foo = "Hello"
bar = "World"
return dict(foo=foo, bar=bar)
@expose(namespace='{{plugin}}')
def {{plugin}}_sub_command(self):
"""Register sub command for the {{plugin}} namespace."""
foo = 'bar'
print foo
return dict(foo=foo)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,5 @@
"""{{plugin}} model classes."""
class {{plugin.capitalize()}}Model(object):
# define model class
pass

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,9 @@
{# This is an example Genshi Text Template. Documentation is available #}\
{# at: #}\
{# #}\
{# http://genshi.edgewall.org/wiki/Documentation/text-templates.html #}\
{# #}\
\
\
{# --------------------- 78 character baseline --------------------------- #}\
${foo} ${bar}

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,15 @@
SOURCE INSTALLATION:
$ sudo python setup.py install
# The following is one command
$ sudo cp -a ./etc/{{project}}/plugins.d/{{plugin}}.conf \
/etc/{{project}}/plugins.d/
USAGE:
$ {{project}} --help
$ {{project}} {{plugin}} --help

View File

@ -0,0 +1,2 @@
recursive-include {{project}}/templates/{{plugin}} *
recursive-include tests *

View File

@ -0,0 +1,8 @@
# This configuration is specifically meant to be used for testing
[root]
# This is how you'd enable a plugin for testing
[{{plugin}}]
enable_plugin = true
provider = {{package}}
foo = 'some value'

View File

@ -0,0 +1,14 @@
# {{plugin.capitalize()}} Configuration Settings
#
# Note: Default values can be set via <yourapp>.bootstrap.<plugin>.py
#
[{{plugin}}]
enable_plugin = true
# The module name of the provider application
provider = {{package}}
# Add any configurations here, note that defaults should also be added
# to this plugins bootstrap file.
foo = 'some value'

View File

@ -0,0 +1,36 @@
from setuptools import setup, find_packages
import sys, os
# You probably want to change the name, this is a healthy default for paster
setup(name='{{project}}.{{plugin}}',
version='0.1',
description='{{plugin.capitalize()}} plugin for {{project.capitalize()}}',
classifiers=[],
keywords='',
author='{{creator}}',
author_email='{{creator_email}}',
url='{{url}}',
license='{{license}}',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe=False,
install_requires=[
"genshi",
"cement >={{cement_version}}, <{{cement_next_version}}",
"{{project}}",
],
setup_requires=[
],
test_suite='nose.collector',
entry_points="""
""",
namespace_packages=[
'{{package}}',
'{{package}}.lib',
'{{package}}.bootstrap',
'{{package}}.controllers',
'{{package}}.model',
'{{package}}.helpers',
'{{package}}.templates',
],
)

View File

@ -0,0 +1,65 @@
"""
The purpose of this module is to test plugin functionality. It is here
as an example of how one might perform nose testing on an application built
on top of the Cement Framework. It is not a fully comprehensive test, of the
application... and needs to be expanded as the application and plugin grow.
The initial file loaded by nose runs the application. Additional calls are
then made via the 'simulate' function rather than needing to reload the
application for every test.
"""
import os
from nose.tools import raises, with_setup, eq_, ok_
from cement import namespaces
from cement.core.namespace import get_config
from cement.core.testing import simulate
from cement.core.exc import CementRuntimeError
from {{package}}.core.exc import {{project.capitalize()}}ArgumentError
config = get_config()
def setup_func():
"""Setup operations before every test."""
pass
def teardown_func():
"""Teardown operations after every test."""
pass
@with_setup(setup_func, teardown_func)
def test_{{plugin}}_cmd_output():
# Simulate returns the result dictionary, and the render output when
# running the controller command. This can be used to validate that the
# command ran successfully.
(res_dict, output_txt) = simulate([__file__, '{{plugin}}-command2'])
# You can test that the rendered output is what we expected
eq_(output_txt, 'Hello World\n')
# You can test values in the result dictionary directly
ok_(res_dict['foo'])
@raises({{project.capitalize()}}ArgumentError)
@with_setup(setup_func, teardown_func)
def test_default_cmd():
# The default action is to raise an application error if an unknown
# command is called. This is how we test that the exception is raised.
simulate([__file__, 'default'])
@with_setup(setup_func, teardown_func)
def test_config_cli_options():
# Using the test configuration file in ./config dir of the base
# application we can test config options this way. We are also using the
# example plugin for this test
# First it is set by what is in the config file
eq_(config['{{plugin}}']['foo'], 'some value')
# Then overridden by cli_opts
simulate([__file__, '{{plugin}}', '{{plugin}}-sub-command', '--foo=bar'])
# now we validate that the config option was set by the cli_opt --foo
eq_(config['{{plugin}}']['foo'], 'bar')

View File

@ -0,0 +1,19 @@
"""
Initialize the base application for nose testing, must be loaded first before
other tests.
"""
from tempfile import mkdtemp
from {{package}}.core.config import get_nose_config
from {{package}}.core.appmain import nose_main
# use an altername config for testing
config = get_nose_config(mkdtemp())
# run the initial main, which bootstraps the base application
nose_main([__file__, 'nosetests', '--quiet'], config)
def test_initialize():
# We put this here just to ensure nose picks up this file.
pass

View File

@ -0,0 +1,84 @@
from setuptools import setup, find_packages
import sys, os
VERSION = '0.8.13'
LONG = """
Cement is an advanced CLI Application Framework for Python. The 'devtools'
package provides tools and libraries needed for developing applications that
are built on Cement.
The Cement CLI Application Framework is Open Source and is distributed under
The MIT License.
GETTING STARTED:
Stable versions of Cement can be installed via the cheeze shop:
::
$ easy_install cement
$ easy_install cement.devtools
Development versions of Cement can be checked out of Git:
::
$ git clone git://github.com/derks/cement.git
$ cd cement/
$ python setup.py install
$ git clone git://github.com/derks/cement.devtools.git
$ cd cement.devtools/
$ python setup.py install
With the 'devtools' package, Cement applications, and plugins can be
created via PasteScript. Once cement.core and cement.devtools are installed,
the following command will create a command line application built on top of
the Cement Framework:
::
$ paster cement-app myapp
The following command will create an external plugin for your application:
::
$ paster cement-plugin myapp myplugin
"""
setup(name='cement.devtools',
version=VERSION,
description="Development Tools for the Cement CLI Application Framework",
long_description=LONG,
classifiers=[],
keywords='cli framework',
author='BJ Dierkes',
author_email='wdierkes@5dollarwhitebox.org',
url='http://builtoncement.org',
license='MIT',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe=False,
install_requires=[
"PasteScript",
"tempita",
"cement == %s" % VERSION,
],
setup_requires=[
],
entry_points="""
[paste.global_paster_command]
cement-app = cement.paste.commands:CementAppCommand
cement-plugin = cement.paste.commands:CementPluginCommand
[paste.paster_create_template]
cementapp = cement.paste.template:CementAppTemplate
cementplugin = cement.paste.template:CementPluginTemplate
""",
namespace_packages=[],
)

23
src/cement.test/LICENSE Normal file
View File

@ -0,0 +1,23 @@
The MIT License:
Copyright (c) 2009-2010 BJ Dierkes
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,2 @@
recursive-include cement_test/templates *
recursive-include tests *

36
src/cement.test/README Normal file
View File

@ -0,0 +1,36 @@
NAME: cement-test
AUTHOR: BJ Dierkes <wdierkes@5dollarwhitebox.org>
DESCRIPTION:
The Cement Test application was created to facilitate proper testing of the
Cement CLI Application Framework. Most of the features in Cement rely on
a loaded application to be access them, making testing a bit more
complex than simply running nose on the cement module.
The Cement Test application is simply a generic/useless application created
via the cement.devtools paster template, with a few added lines of code to
reach more of the framework and assist in testing coverage. The primary
use of cement-test is for the nose tests in ./tests.
The Cement Test application is Open Source and is distributed under
The MIT License.
USAGE:
From within the cement.test source directory execute:
$ nosetests --verbosity 3
If testing for coverage run:
$ coverage run /path/to/nosetests && coverage html
CONFIGURATION:
The application is configured to only read './config/cement-test.conf'.

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,33 @@
from cement.core.hook import register_hook, define_hook
from cement.core.namespace import CementNamespace, register_namespace
# Setup the 'example' namespace object
example = CementNamespace(
label='example',
controller='ExampleController',
description='Example Plugin for Cement Test',
required_api='0.7-0.8:20100210',
provider='cement_test'
)
example.config['foo'] = 'bar'
example.options.add_option('-F', '--foo', action='store',
dest='foo', default=None, help='Example Foo Option'
)
register_namespace(example)
@register_hook(weight=99)
def my_example_hook():
return 99
@register_hook(name='my_example_hook')
def some_other_hook_name():
return 0
@register_hook(weight=-100)
def my_example_hook():
return -100

View File

@ -0,0 +1,34 @@
from cement.core.hook import register_hook, define_hook
from cement.core.namespace import CementNamespace, register_namespace
# Setup the 'example' namespace object
example = CementNamespace(
label='example2',
controller='Example2Controller',
description='Example Plugin for Cement Test',
required_api='0.7-0.8:20100210',
provider='cement_test',
is_hidden=True,
)
example.config['foo'] = 'bar'
example.options.add_option('-F', '--foo', action='store',
dest='foo', default=None, help='Example Foo Option'
)
register_namespace(example)
@register_hook(weight=99)
def my_example_hook():
return 99
@register_hook(name='my_example_hook')
def some_other_hook_name():
return 0
@register_hook(weight=-100)
def my_example_hook():
return -100

View File

@ -0,0 +1,33 @@
from cement.core.hook import register_hook, define_hook
from cement.core.namespace import CementNamespace, register_namespace
# Setup the 'example' namespace object
example = CementNamespace(
label='example3',
controller='Example3Controller',
description='Example Plugin for Cement Test',
required_api='0.7-0.8:20100210',
provider='cement_test'
)
example.config['foo'] = 'bar'
example.options.add_option('-F', '--foo', action='store',
dest='foo', default=None, help='Example Foo Option'
)
register_namespace(example)
@register_hook(weight=99)
def my_example_hook():
return 99
@register_hook(name='my_example_hook')
def some_other_hook_name():
return 0
@register_hook(weight=-100)
def my_example_hook():
return -100

View File

@ -0,0 +1,33 @@
from cement.core.hook import register_hook, define_hook
from cement.core.namespace import CementNamespace, register_namespace
# Setup the 'example' namespace object
example = CementNamespace(
label='example4',
controller='Example4Controller',
description='Example Plugin for Cement Test',
required_api='0.7-0.8:20100210',
provider='cement_test'
)
example.config['foo'] = 'bar'
example.options.add_option('-F', '--foo', action='store',
dest='foo', default=None, help='Example Foo Option'
)
register_namespace(example)
@register_hook(weight=99)
def my_example_hook():
return 99
@register_hook(name='my_example_hook')
def some_other_hook_name():
return 0
@register_hook(weight=-100)
def my_example_hook():
return -100

View File

@ -0,0 +1,32 @@
from cement.core.hook import register_hook, define_hook
from cement.core.namespace import CementNamespace, register_namespace
# Setup the 'example' namespace object
example = CementNamespace(
label='example_five',
controller='ExampleFiveController',
description='Example Plugin for Cement Test',
provider='cement_test'
)
example.config['foo'] = 'bar'
example.options.add_option('-F', '--foo', action='store',
dest='foo', default=None, help='Example Foo Option'
)
register_namespace(example)
@register_hook(weight=99)
def my_example_hook():
return 99
@register_hook(name='my_example_hook')
def some_other_hook_name():
return 0
@register_hook(weight=-100)
def my_example_hook():
return -100

View File

@ -0,0 +1,61 @@
"""
The bootstrap module should be used to setup parts of your application
that need to exist before all controllers are loaded. It is best used to
define hooks, setup namespaces, and the like. The root namespace is
already bootstrapped by Cement, however you can extend that functionality
by importing additional bootstrap files here.
"""
from cement.core.opt import init_parser
from cement.core.hook import register_hook, define_hook
define_hook('my_example_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')
root_options.add_option('--test-option', action='store',
dest='test_option', default=None, help='test option')
return ('root', root_options)
@register_hook()
def post_bootstrap_hook():
pass
@register_hook()
def validate_config_hook(config):
pass
@register_hook()
def pre_plugins_hook():
pass
#@register_hook(name='options_hook')
#def bogus_namespace():
# parser = init_parser()
# parser.add_option('--bogus-option', action='store', dest='bogus_option')
# return ('bogus_namespace', parser)
@register_hook()
def post_plugins_hook():
pass
@register_hook()
def post_options_hook(cli_opts, cli_args):
pass
# Import all additional (non-plugin) bootstrap libraries here
#
# from cement_test.bootstrap import example

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,28 @@
"""
This is the ExampleController for the cement_test application. This can be used
to expose commands to the example namespace which will be accessible under:
$ cement_test example --help
"""
import sys
from cement.core.namespace import get_config
from cement.core.log import get_logger
from cement.core.controller import CementController, expose
from cement.core.hook import run_hooks
from cement_test.model.example import ExampleModel
log = get_logger(__name__)
class ExampleController(CementController):
@expose(namespace='example')
def cmd1(self):
foo='bar'
print foo
return dict(foo=foo)

View File

@ -0,0 +1,23 @@
"""
This is the ExampleController for the cement_test application. This can be used
to expose commands to the example namespace which will be accessible under:
$ cement_test example --help
"""
import sys
from cement.core.namespace import get_config
from cement.core.log import get_logger
from cement.core.controller import CementController, expose
from cement.core.hook import run_hooks
log = get_logger(__name__)
class Example2Controller(CementController):
@expose(namespace='example2')
def cmd1(self):
foo='bar'
print foo
return dict(foo=foo)

View File

@ -0,0 +1,24 @@
"""
This is the ExampleController for the cement_test application. This can be used
to expose commands to the example namespace which will be accessible under:
$ cement_test example --help
"""
import sys
from cement.core.namespace import get_config
from cement.core.log import get_logger
from cement.core.controller import CementController, expose
from cement.core.hook import run_hooks
log = get_logger(__name__)
class Example3Controller(CementController):
@expose(namespace='example3')
def cmd1(self):
foo='bar'
print foo
return dict(foo=foo)

View File

@ -0,0 +1,24 @@
"""
This is the ExampleController for the cement_test application. This can be used
to expose commands to the example namespace which will be accessible under:
$ cement_test example --help
"""
import sys
from cement.core.namespace import get_config
from cement.core.log import get_logger
from cement.core.controller import CementController, expose
from cement.core.hook import run_hooks
log = get_logger(__name__)
class Example4Controller(CementController):
@expose(namespace='example4', is_hidden=True)
def cmd1(self):
foo='bar'
print foo
return dict(foo=foo)

View File

@ -0,0 +1,24 @@
"""
This is the ExampleController for the cement_test application. This can be used
to expose commands to the example namespace which will be accessible under:
$ cement_test example --help
"""
import sys
from cement.core.namespace import get_config
from cement.core.log import get_logger
from cement.core.controller import CementController, expose
from cement.core.hook import run_hooks
log = get_logger(__name__)
class ExampleFiveController(CementController):
@expose(namespace='example_five', is_hidden=False)
def cmd1(self):
foo='bar'
print foo
return dict(foo=foo)

View File

@ -0,0 +1,99 @@
"""
This is the RootController for the cement_test application. This can be used
to expose commands to the root namespace which will be accessible under:
$ cement_test --help
"""
from tempfile import mkstemp
from cement.core.controller import CementController, expose
from cement.core.namespace import get_config
from cement.core.log import get_logger
from cement_test.core.exc import CementTestArgumentError
log = get_logger(__name__)
config = get_config()
class RootController(CementController):
@expose(is_hidden=True)
def nosetests(self, *args, **kw):
pass
@expose('cement_test.templates.root.error', is_hidden=True)
def error(self, errors=[]):
"""
This can be called when catching exceptions giving the developer a
clean way of presenting errors to the user.
Required Arguments:
errors
A list of tuples in the form of [('ErrorCode', 'Error message')].
The best way to use this is with an 'abort' function, such as the one
provided by the Rosendale Project.
.. code-block:: python
from rosendale.helpers.error import abort_on_error
errors = []
# do something, if error
errors.append(('MyErrorCode', 'My Error Message'))
abort_on_error(errors)
# continue work (no errors)
"""
return dict(errors=errors)
@expose(is_hidden=True)
def default(self):
"""
This is the default command method. If no commands are passed to
cement_test, this one will be executed. By default it raises an
exception.
"""
raise CementTestArgumentError, "A command is required. See --help?"
@expose('cement_test.templates.root.cmd1')
def cmd1(self):
"""This is an example 'root' command. It should be replaced."""
items = ['one', 'two', 'three']
return dict(items=items)
@expose()
def send_to_file(self):
f = mkstemp()[1]
return dict(output_file=f, foo='bar')
@expose()
def cmd1_help(self):
"""This is an example 'root' -help command. It should be replaced."""
foo = 'In cement_test.controllers.root.cmd1_help()'
return dict(foo=foo)
@expose('genshi:cement_test.templates.root.get-started')
def get_started(self):
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"
return dict(config=config, features=features, genshi_link=genshi_link)
@expose('bogus_handler:cement_test.templates.root.cmd1')
def bogus_cmd1(self):
pass

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,78 @@
"""
This is the application's core code. Unless you know the "ins-and-outs" of
The Cement CLI Application Framework, you probably should not modify the
main() function of this file.
"""
import sys
from pkg_resources import get_distribution
from cement.core.exc import CementArgumentError, CementConfigError, \
CementRuntimeError
from cement.core.log import get_logger
from cement.core.app_setup import lay_cement
from cement.core.configuration import ensure_api_compat
from cement.core.command import run_command
from cement_test.core.config import default_config
from cement_test.core.exc import CementTestArgumentError, CementTestConfigError, \
CementTestRuntimeError
VERSION = get_distribution('cement_test').version
BANNER = """
cement_test version %s
""" % VERSION
def main(args=None):
try:
lay_cement(config=default_config, banner=BANNER, args=args)
log = get_logger(__name__)
log.debug("Cement Framework Initialized!")
if not len(sys.argv) > 1:
sys.argv.append('default')
run_command(sys.argv[1])
except CementArgumentError, e:
# Display the apps exception names instead for the Cement exceptions.
print("CementTestArgumentError > %s" % e)
sys.exit(e.code)
except CementConfigError, e:
print("CementTestConfigError > %s" % e)
sys.exit(e.code)
except CementRuntimeError, e:
print("CementTestRuntimeError > %s" % e)
sys.exit(e.code)
except CementTestArgumentError, e:
print("CementTestArgumentError > %s" % e)
sys.exit(e.code)
except CementTestConfigError, e:
print("CementTestConfigError > %s" % e)
sys.exit(e.code)
except CementTestRuntimeError, e:
print("CementTestRuntimeError > %s" % e)
sys.exit(e.code)
sys.exit(0)
def nose_main(args, test_config):
"""
This function provides an alternative to main() that is more friendly for
nose tests as it doesn't catch any exceptions.
"""
lay_cement(config=test_config, banner=BANNER, args=args)
log = get_logger(__name__)
log.debug("Cement Framework Initialized!")
if not len(args) > 1:
args.append('default')
(res, output_txt) = run_command(args[1])
return (res, output_txt)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,67 @@
"""
This is the applications default configuration. You can feel free to alter
this file to your needs, however it should be noted that all these variables
are overridden by settings in /etc/cement_test/cement_test.conf and/or
~/.cement_test.conf (by default) and therefore, any default configurations
you want to make obvious to your users should be set in your default config
file as packaged with the software.
"""
import os
from tempfile import mkdtemp
from configobj import ConfigObj
from cement.core.exc import CementConfigError
# This is a sane default for development/no config
prefix = os.path.join(os.environ['HOME'], '.cement_test')
dcf = ConfigObj() # default config
dcf['config_source'] = ['defaults']
dcf['app_name'] = 'cement-test' # name for cli like /etc/<app_name>
dcf['app_egg_name'] = 'cement-test' # name from setup.py
dcf['app_module'] = 'cement_test' # name of the library dir
dcf['enabled_plugins'] = [] # no default plugins, add via the config file
dcf['debug'] = False
dcf['datadir'] = '%s/data' % prefix
dcf['tmpdir'] = '%s/tmp' % prefix
dcf['log_file'] = '%s/log/%s.log' % (prefix, dcf['app_name'])
dcf['plugin_config_dir'] = '%s/etc/plugins.d' % prefix
dcf['log_to_console'] = True
dcf['output_engine'] = 'genshi'
dcf['show_plugin_load'] = False
# By default look in /etc and ~/ for config files. You should probably
# symlink /etc/<your_app> => ./etc/<your_app> for easy development.
dcf['config_files'] = [
os.path.join('/etc', dcf['app_name'], '%s.conf' % dcf['app_name']),
os.path.join(prefix, 'etc', '%s.conf' % dcf['app_name']),
os.path.join(os.environ['HOME'], '.%s.conf' % dcf['app_name']),
]
default_config = dcf
def get_nose_config(prefix=None):
if not prefix:
prefix = mkdtemp()
tcf = ConfigObj() # test config
tcf['config_files'] = [os.path.abspath('./config/cement-test.conf')]
tcf['config_source'] = ['defaults']
tcf['app_name'] = 'cement_test'
tcf['app_egg_name'] = 'cement_test'
tcf['app_module'] = 'cement_test'
tcf['app_basepath'] = os.path.dirname(__file__)
tcf['nosetests'] = True
tcf['enabled_plugins'] = []
tcf['debug'] = False
tcf['datadir'] = '%s/data' % prefix
tcf['tmpdir'] = '%s/tmp' % prefix
tcf['log_file'] = '%s/log/%s.log' % (prefix, tcf['app_name'])
tcf['plugin_config_dir'] = './config/plugins.d'
tcf['log_to_console'] = False
tcf['output_engine'] = 'genshi'
tcf['show_plugin_load'] = False
tcf['ignore_duplicate_framework_exceptions'] = True
return tcf

View File

@ -0,0 +1,32 @@
"""cement_test exception classes."""
class CementTestError(Exception):
"""Generic errors."""
def __init__(self, value, code=1):
Exception.__init__(self)
self.msg = value
self.code = code
def __str__(self):
return self.msg
def __unicode__(self):
return unicode(self.msg)
class CementTestConfigError(CementTestError):
"""Config parsing and setup errors."""
def __init__(self, value):
code = 10
CementTestError.__init__(self, value, code)
class CementTestRuntimeError(CementTestError):
"""Runtime errors."""
def __init__(self, value):
code = 20
CementTestError.__init__(self, value, code)
class CementTestArgumentError(CementTestError):
"""Argument errors."""
def __init__(self, value):
code = 40
CementTestError.__init__(self, value, code)

View File

@ -0,0 +1,16 @@
import os
import shutil
from tempfile import mkdtemp
from cement import namespaces
def setup_func():
"""Setup operations before every test."""
namespaces['root'].config['datadir'] = mkdtemp()
if not os.path.exists(namespaces['root'].config['datadir']):
os.makedirs(namespaces['root'].config['datadir'])
def teardown_func():
"""Teardown operations after every test."""
if os.path.exists(namespaces['root'].config['datadir']):
shutil.rmtree(namespaces['root'].config['datadir'])

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -0,0 +1,13 @@
"""
This is an example model. This can be anything, just a straight class
or perhaps an SQLAlchemy DeclarativeBase, etc.
"""
from cement.core.log import get_logger
log = get_logger(__name__)
class ExampleModel(object):
id = int()
label = u''
description = u''

View File

@ -0,0 +1,28 @@
"""
The root model can be used to consolidate all of your models under one.
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()
"""
# from helloworld.model.example import Example

View File

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

Some files were not shown because too many files have changed in this diff Show More