Rewriting command support to use PasteScript since we're keeping that dependency for simplicity's sake
This commit is contained in:
@@ -1,26 +1,34 @@
|
||||
"""
|
||||
Commands for Pecan, heavily inspired by Paste Script commands.
|
||||
PasteScript commands for Pecan.
|
||||
"""
|
||||
from configuration import _runtime_conf, set_config
|
||||
from paste.script import command as paste_command
|
||||
from paste.script.create_distro import CreateDistroCommand
|
||||
from webtest import TestApp
|
||||
|
||||
from templates import Template
|
||||
from templates import DEFAULT_TEMPLATE
|
||||
|
||||
import imp
|
||||
import copy
|
||||
import optparse
|
||||
import os
|
||||
import pkg_resources
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
import warnings
|
||||
|
||||
|
||||
class CommandRunner(object):
|
||||
"""
|
||||
Dispatches command execution requests.
|
||||
|
||||
This is a custom PasteScript command runner that is specific to Pecan
|
||||
commands. For a command to show up, its name must begin with "pecan-".
|
||||
It is also recommended that its group name be set to "Pecan" so that it
|
||||
shows up under that group when using ``paster`` directly.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# set up the parser
|
||||
self.parser = optparse.OptionParser(add_help_option=False,
|
||||
version='Pecan %s' % self.get_version(),
|
||||
usage='%prog [options] COMMAND [command_options]')
|
||||
@@ -29,6 +37,17 @@ class CommandRunner(object):
|
||||
action='store_true',
|
||||
dest='show_help',
|
||||
help='show detailed help message')
|
||||
|
||||
# suppress BaseException.message warnings for BadCommand
|
||||
if sys.version_info < (2, 7):
|
||||
warnings.filterwarnings(
|
||||
'ignore',
|
||||
'BaseException\.message has been deprecated as of Python 2\.6',
|
||||
DeprecationWarning,
|
||||
paste_command.__name__.replace('.', '\\.'))
|
||||
|
||||
# register Pecan as a system plugin when using the custom runner
|
||||
paste_command.system_plugins.append('Pecan')
|
||||
|
||||
def get_command_template(self, command_names):
|
||||
if not command_names:
|
||||
@@ -37,14 +56,11 @@ class CommandRunner(object):
|
||||
max_length = max([len(name) for name in command_names])
|
||||
return ' %%-%ds %%s\n' % max_length
|
||||
|
||||
def get_commands(self, cls=None):
|
||||
def get_commands(self):
|
||||
commands = {}
|
||||
if not cls:
|
||||
cls = Command
|
||||
for command in cls.__subclasses__():
|
||||
if hasattr(command, 'name'):
|
||||
commands[command.name] = command
|
||||
commands.update(self.get_commands(command))
|
||||
for name, command in paste_command.get_commands().iteritems():
|
||||
if name.startswith('pecan-'):
|
||||
commands[name[6:]] = command.load()
|
||||
return commands
|
||||
|
||||
def get_version(self):
|
||||
@@ -67,7 +83,10 @@ class CommandRunner(object):
|
||||
return
|
||||
command_template = self.get_command_template(commands.keys())
|
||||
for name, command in commands.iteritems():
|
||||
command_groups.setdefault(command.group_name, {})[name] = command
|
||||
group_name = command.group_name
|
||||
if group_name.lower() == 'pecan':
|
||||
group_name = ''
|
||||
command_groups.setdefault(group_name, {})[name] = command
|
||||
command_groups = sorted(command_groups.items())
|
||||
for i, (group, commands) in enumerate(command_groups):
|
||||
file.write('%s:\n' % (group or 'Commands'))
|
||||
@@ -99,155 +118,48 @@ class CommandRunner(object):
|
||||
self.print_known_commands()
|
||||
return 1
|
||||
else:
|
||||
command = commands[command_name]()
|
||||
command = commands[command_name](command_name)
|
||||
if options.show_help:
|
||||
return command('-h')
|
||||
return command.run(['-h'])
|
||||
else:
|
||||
return command(*args)
|
||||
return command.run(args)
|
||||
|
||||
@classmethod
|
||||
def handle_command_line(cls):
|
||||
try:
|
||||
runner = CommandRunner()
|
||||
exit_code = runner.run(sys.argv[1:])
|
||||
except CommandException, ex:
|
||||
except paste_command.BadCommand, ex:
|
||||
sys.stderr.write('%s\n' % ex)
|
||||
exit_code = ex.exit_code
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
class Command(object):
|
||||
class Command(paste_command.Command):
|
||||
"""
|
||||
Base class for Pecan commands.
|
||||
|
||||
All commands should inherit from this class or one of its subclasses. To
|
||||
be exposed, subclasses must at least define a `name` class attribute.
|
||||
This provides some standard functionality for interacting with Pecan
|
||||
applications and handles some of the basic PasteScript command cruft.
|
||||
|
||||
In addition, subclasses can define the following class attributes:
|
||||
|
||||
- `group_name`: Group the command under this category in help messages.
|
||||
|
||||
- `usage`: Usage information for the command. This is particularly useful
|
||||
if your command accepts positional arguments in addition to standard
|
||||
options.
|
||||
|
||||
- `summary`: Short description for the command that appears in help
|
||||
messages.
|
||||
|
||||
- `description`: Longer description for the command that appears in its
|
||||
help message only.
|
||||
|
||||
- `required_options`: Tuple of 2-element tuples with the destination
|
||||
variable name and option name for required options. If any of these are
|
||||
not found after parsing the options, the command will abort.
|
||||
|
||||
- `required_options_error`: The error message to display when a required
|
||||
option is missing. This gets the variable name as `name` and the option
|
||||
name as `option`.
|
||||
|
||||
- `required_args`: Tuple of required argument names. If the provided list
|
||||
of positional arguments is shorter than this, the command will abort.
|
||||
|
||||
- `required_args_error`: The error message to display when a required
|
||||
positional argument is missing. This gets the number of actual arguments
|
||||
as `actual`, the number of missing arguments as `missing`, and the names
|
||||
of missing arguments, comma separated, as `missing_names`.
|
||||
|
||||
- `maximum_args`: The maximum number of arguments this command accepts.
|
||||
If not `None` and the number of arguments exceeds this number, the
|
||||
command will abort.
|
||||
|
||||
- `maximum_args_error`: The error message to display when the number of
|
||||
positional arguments exceeds the maximum number for the command. This
|
||||
gets the number of actual arguments as `actual` and the maximum number
|
||||
of arguments as `max`.
|
||||
|
||||
- `default_verbosity`: The default verbosity for the command. This gets
|
||||
increased by 1 for every `-v` option and decreased by 1 for every `-q`
|
||||
option using the default option parser and stored as the `verbosity`
|
||||
instance attribute. If you override how `-v` and `-q` work, the default
|
||||
remains unchanged.
|
||||
|
||||
- `return_code`: The default return for the command. If a subclass's
|
||||
implementation of `run` doesn't return a value, this gets returned.
|
||||
|
||||
Subclasses should override `run` to provide a command's specific
|
||||
implementation. `run` should return a valid exit code (i.e., usually 0
|
||||
for successful). See also `return_code` above.
|
||||
|
||||
If a command has custom options, its subclass should override `get_parser`
|
||||
to provide a custom `OptionParser`. It is recommended to call the parent
|
||||
`get_parser` to get the default verbosity options, but this is not
|
||||
required for a command to work.
|
||||
|
||||
No other methods should be overriden by subclasses.
|
||||
See ``paste.script.command.Command`` for more information.
|
||||
"""
|
||||
|
||||
# command information
|
||||
group_name = ''
|
||||
usage = ''
|
||||
group_name = 'Pecan'
|
||||
summary = ''
|
||||
description = ''
|
||||
|
||||
# command options/arguments
|
||||
required_options = ()
|
||||
required_options_error = 'You must provide the option %(option)s'
|
||||
required_args = ()
|
||||
required_args_error = 'You must provide the following arguments: %(missing_names)s'
|
||||
maximum_args = None
|
||||
maximum_args_error = 'You must provide no more than %(max)s arguments'
|
||||
# command parser
|
||||
parser = paste_command.Command.standard_parser()
|
||||
|
||||
# command execution
|
||||
default_verbosity = 0
|
||||
return_code = 0
|
||||
|
||||
def __call__(self, *args):
|
||||
|
||||
# parse the arguments
|
||||
self.parser = self.get_parser()
|
||||
options, args = self.parse_args(list(args))
|
||||
|
||||
# determine the verbosity
|
||||
for name, value in [('quiet', 0), ('verbose', 0)]:
|
||||
if not hasattr(options, name):
|
||||
setattr(options, name, value)
|
||||
self.verbosity = self.default_verbosity
|
||||
if isinstance(options.verbose, int):
|
||||
self.verbosity += options.verbose
|
||||
if isinstance(options.quiet, int):
|
||||
self.verbosity -= options.quiet
|
||||
|
||||
# make sure all required options were provided
|
||||
for name, option in self.required_options:
|
||||
if not hasattr(options, name):
|
||||
message = self.required_options_error % {'name': name, 'option': option}
|
||||
raise CommandException(self.parser.error(message))
|
||||
|
||||
# make sure all required arguments were provided
|
||||
if len(args) < len(self.required_args):
|
||||
missing = self.required_args[len(args):]
|
||||
message = self.required_args_error % {'actual': len(args),
|
||||
'missing': len(missing),
|
||||
'missing_names': ', '.join(missing)}
|
||||
raise CommandException(self.parser.error(message))
|
||||
|
||||
# make sure not too many arguments were provided if there's a limit
|
||||
if self.maximum_args and len(args) > self.maximum_args:
|
||||
message = self.maximum_args_error % {'actual': len(args),
|
||||
'max': self.maximum_args}
|
||||
raise CommandException(self.parser.error(message))
|
||||
|
||||
# execute the command
|
||||
result = self.run(options, args)
|
||||
if result is None:
|
||||
result = self.return_code
|
||||
return result
|
||||
def run(self, args):
|
||||
try:
|
||||
return paste_command.Command.run(self, args)
|
||||
except paste_command.BadCommand, ex:
|
||||
ex.args[0] = self.parser.error(ex.args[0])
|
||||
raise
|
||||
|
||||
def can_import(self, name):
|
||||
"""
|
||||
Attempt to __import__ the specified package/module, returning
|
||||
True when succeeding, otherwise False.
|
||||
"""
|
||||
try:
|
||||
__import__(name)
|
||||
return True
|
||||
@@ -259,18 +171,6 @@ class Command(object):
|
||||
return []
|
||||
return [module.__name__ for module in config.app.modules if hasattr(module, '__name__')]
|
||||
|
||||
def get_parser(self):
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('-v', '--verbose',
|
||||
action='count',
|
||||
dest='verbose',
|
||||
help='increase verbosity')
|
||||
parser.add_option('-q', '--quiet',
|
||||
action='count',
|
||||
dest='quiet',
|
||||
help='decrease verbosity')
|
||||
return parser
|
||||
|
||||
def load_configuration(self, name):
|
||||
set_config(name)
|
||||
return _runtime_conf
|
||||
@@ -282,7 +182,7 @@ class Command(object):
|
||||
module = sys.modules[module_name]
|
||||
if hasattr(module, 'setup_app'):
|
||||
return module.setup_app(config)
|
||||
raise CommandException('No app.setup_app found in any of the configured app.modules')
|
||||
raise paste_command.BadCommand('No app.setup_app found in any of the configured app.modules')
|
||||
|
||||
def load_model(self, config):
|
||||
for package_name in self.get_package_names(config):
|
||||
@@ -291,51 +191,27 @@ class Command(object):
|
||||
return sys.modules[module_name]
|
||||
return None
|
||||
|
||||
def parse_args(self, args):
|
||||
if self.usage:
|
||||
usage = ' ' + self.usage
|
||||
else:
|
||||
usage = ''
|
||||
self.parser.usage = "%%prog %s [options]%s\n%s" % (self.name, usage, self.summary)
|
||||
if self.description:
|
||||
self.parser.description = textwrap.dedent(self.description)
|
||||
return self.parser.parse_args(args)
|
||||
|
||||
def run(self, options, args):
|
||||
def command(self):
|
||||
pass
|
||||
|
||||
|
||||
class CommandException(Exception):
|
||||
"""
|
||||
Raised when a command fails. Use CommandException in commands instead of
|
||||
Exception so that these are correctly reported when running from the
|
||||
command line. The optional `exit_code`, which defaults to 1, will be used
|
||||
as the system exit code when running from the command line.
|
||||
"""
|
||||
|
||||
def __init__(self, message, exit_code=1):
|
||||
Exception.__init__(self, message)
|
||||
self.exit_code = exit_code
|
||||
|
||||
|
||||
class ServeCommand(Command):
|
||||
"""
|
||||
Serve the described application.
|
||||
"""
|
||||
|
||||
# command information
|
||||
name = 'serve'
|
||||
usage = 'CONFIG_NAME'
|
||||
summary = __doc__.strip().splitlines()[0].rstrip('.')
|
||||
|
||||
# command options/arguments
|
||||
required_args = ('CONFIG_NAME', )
|
||||
maximum_args = 1
|
||||
min_args = 1
|
||||
max_args = 1
|
||||
|
||||
def run(self, options, args):
|
||||
def command(self):
|
||||
|
||||
# load the application
|
||||
config = self.load_configuration(args[0])
|
||||
config = self.load_configuration(self.args[0])
|
||||
app = self.load_app(config)
|
||||
|
||||
from paste import httpserver
|
||||
@@ -351,18 +227,17 @@ class ShellCommand(Command):
|
||||
"""
|
||||
|
||||
# command information
|
||||
name = 'shell'
|
||||
usage = 'CONFIG_NAME'
|
||||
summary = __doc__.strip().splitlines()[0].rstrip('.')
|
||||
|
||||
# command options/arguments
|
||||
required_args = ('CONFIG_NAME', )
|
||||
maximum_args = 1
|
||||
min_args = 1
|
||||
max_args = 1
|
||||
|
||||
def run(self, options, args):
|
||||
def command(self):
|
||||
|
||||
# load the application
|
||||
config = self.load_configuration(args[0])
|
||||
config = self.load_configuration(self.args[0])
|
||||
setattr(config.app, 'reload', False)
|
||||
app = self.load_app(config)
|
||||
|
||||
@@ -406,105 +281,35 @@ class ShellCommand(Command):
|
||||
shell.interact(shell_banner + banner)
|
||||
|
||||
|
||||
class CreateCommand(Command):
|
||||
class CreateCommand(CreateDistroCommand, Command):
|
||||
"""
|
||||
Creates a new Pecan package using a template.
|
||||
Creates the file layout for a new Pecan distribution.
|
||||
|
||||
For a template to show up when using this command, its name must begin
|
||||
with "pecan-". Although not required, it should also include the "Pecan"
|
||||
egg plugin for user convenience.
|
||||
"""
|
||||
|
||||
# command information
|
||||
name = 'create'
|
||||
usage = 'PACKAGE_NAME'
|
||||
summary = __doc__.strip().splitlines()[0].rstrip('.')
|
||||
description = None
|
||||
|
||||
# command options/arguments
|
||||
maximum_args = 1
|
||||
def command(self):
|
||||
if not self.options.list_templates:
|
||||
if not self.options.templates:
|
||||
self.options.templates = [DEFAULT_TEMPLATE]
|
||||
try:
|
||||
return CreateDistroCommand.command(self)
|
||||
except LookupError, ex:
|
||||
sys.stderr.write('%s\n\n' % ex)
|
||||
CreateDistroCommand.list_templates(self)
|
||||
return 2
|
||||
|
||||
# regex for package names
|
||||
BAD_CHARS_RE = re.compile(r'[^a-zA-Z0-9_]')
|
||||
|
||||
def run(self, options, args):
|
||||
|
||||
# if listing templates, list and return
|
||||
if options.list_templates:
|
||||
self.list_templates()
|
||||
return
|
||||
|
||||
# check the specified template
|
||||
template_name = options.template or 'default'
|
||||
template = Template.get_templates().get(template_name)
|
||||
if not template:
|
||||
message = 'Template "%s" could not be found' % template_name
|
||||
raise CommandException(self.parser.error(message))
|
||||
|
||||
# make sure a package name was specified
|
||||
if not args:
|
||||
message = 'You must provide a package name'
|
||||
raise CommandException(self.parser.error(message))
|
||||
|
||||
# prepare the variables
|
||||
template = template()
|
||||
dist_name = args[0].lstrip(os.path.sep)
|
||||
output_dir = os.path.join(options.output_dir, dist_name)
|
||||
pkg_name = self.BAD_CHARS_RE.sub('', dist_name.lower())
|
||||
egg_name = pkg_resources.to_filename(pkg_resources.safe_name(dist_name))
|
||||
vars = {
|
||||
'project': dist_name,
|
||||
'package': pkg_name,
|
||||
'egg': egg_name
|
||||
}
|
||||
|
||||
# display the vars if verbose
|
||||
if self.verbosity:
|
||||
self.display_vars(vars)
|
||||
|
||||
# create the template
|
||||
self.create_template(template, output_dir, vars, overwrite=options.overwrite)
|
||||
|
||||
def get_parser(self):
|
||||
parser = Command.get_parser(self)
|
||||
parser.add_option('-t', '--template',
|
||||
dest='template',
|
||||
metavar='TEMPLATE',
|
||||
help='template to use (uses default if not specified)')
|
||||
parser.add_option('-o', '--output-dir',
|
||||
dest='output_dir',
|
||||
metavar='DIR',
|
||||
default='.',
|
||||
help='output to DIR (defaults to current directory)')
|
||||
parser.add_option('--list-templates',
|
||||
dest='list_templates',
|
||||
action='store_true',
|
||||
help='show all available templates')
|
||||
parser.add_option('-f', '--overwrite',
|
||||
dest='overwrite',
|
||||
action='store_true',
|
||||
help='Overwrite files',
|
||||
default=False)
|
||||
return parser
|
||||
|
||||
def create_template(self, template, output_dir, vars, overwrite=False):
|
||||
if self.verbosity:
|
||||
print 'Creating template %s' % template.name
|
||||
template.run(output_dir, vars, verbosity=self.verbosity, overwrite=overwrite)
|
||||
|
||||
def display_vars(self, vars, file=sys.stdout):
|
||||
vars = sorted(vars.items())
|
||||
file.write('Variables:\n')
|
||||
if not vars:
|
||||
return
|
||||
max_length = max([len(name) for name, value in vars])
|
||||
for name, value in vars:
|
||||
file.write(' %s:%s %s\n' % (name, ' ' * (max_length - len(name)), value))
|
||||
|
||||
def list_templates(self, file=sys.stdout):
|
||||
templates = Template.get_templates()
|
||||
if not templates:
|
||||
file.write('No templates registered.\n')
|
||||
return
|
||||
template_names = sorted(templates.keys())
|
||||
max_length = max([len(name) for name in template_names])
|
||||
file.write('Available templates:\n')
|
||||
for name in template_names:
|
||||
file.write(' %s:%s %s\n' % (name,
|
||||
' ' * (max_length - len(name)),
|
||||
templates[name].summary))
|
||||
def all_entry_points(self):
|
||||
entry_points = []
|
||||
for entry in CreateDistroCommand.all_entry_points(self):
|
||||
if entry.name.startswith('pecan-'):
|
||||
entry = copy.copy(entry)
|
||||
entry_points.append(entry)
|
||||
entry.name = entry.name[6:]
|
||||
return entry_points
|
||||
|
||||
@@ -1,262 +1,8 @@
|
||||
import cgi
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
import urllib
|
||||
from paste.script.templates import Template
|
||||
|
||||
class LaxTemplate(string.Template):
|
||||
# This change of pattern allows for anything in braces, but
|
||||
# only identifiers outside of braces:
|
||||
pattern = r"""
|
||||
\$(?:
|
||||
(?P<escaped>\$) | # Escape sequence of two delimiters
|
||||
(?P<named>[_a-z][_a-z0-9]*) | # delimiter and a Python identifier
|
||||
{(?P<braced>.*?)} | # delimiter and a braced identifier
|
||||
(?P<invalid>) # Other ill-formed delimiter exprs
|
||||
)
|
||||
"""
|
||||
DEFAULT_TEMPLATE = 'base'
|
||||
|
||||
|
||||
class TypeMapper(dict):
|
||||
def __getitem__(self, item):
|
||||
options = item.split('|')
|
||||
for op in options[:-1]:
|
||||
try:
|
||||
value = eval(op, dict(self.items()))
|
||||
break
|
||||
except (NameError, KeyError):
|
||||
pass
|
||||
except Exception, ex:
|
||||
TemplateProcessor.add_exception_info(ex, 'in expression %r' % op)
|
||||
raise
|
||||
else:
|
||||
value = eval(options[-1], dict(self.items()))
|
||||
if value is None:
|
||||
return ''
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
|
||||
class TemplateProcessor(object):
|
||||
|
||||
def __init__(self, vars, verbosity=0, overwrite=False):
|
||||
self.vars = vars.copy()
|
||||
self.vars.setdefault('dot', '.')
|
||||
self.vars.setdefault('plus', '+')
|
||||
self.verbosity = verbosity
|
||||
self.overwrite = overwrite
|
||||
self.standard_vars = self._create_standard_vars(vars)
|
||||
|
||||
def process(self, template, output_dir, indent=0):
|
||||
self._process_directory(template.template_directory, output_dir, indent=indent)
|
||||
|
||||
def _create_standard_vars(self, extra_vars={}):
|
||||
|
||||
# method to skip a template file
|
||||
def skip_file(condition=True, *args):
|
||||
if condition:
|
||||
raise SkipFileException(*args)
|
||||
|
||||
# build the dictionary of standard vars
|
||||
standard_vars = {
|
||||
'nothing': None,
|
||||
'html_quote': lambda s: s is None and '' or cgi.escape(str(s), 1),
|
||||
'url_quote': lambda s: s is None and '' or urllib.quote(str(s)),
|
||||
'empty': '""',
|
||||
'test': lambda check, true, false=None: check and true or false,
|
||||
'repr': repr,
|
||||
'str': str,
|
||||
'bool': bool,
|
||||
'SkipFileException': SkipFileException,
|
||||
'skip_file': skip_file,
|
||||
}
|
||||
|
||||
# add in the extra vars
|
||||
standard_vars.update(extra_vars)
|
||||
|
||||
return standard_vars
|
||||
|
||||
def _process_directory(self, source, dest, indent=0):
|
||||
|
||||
# determine the output padding
|
||||
pad = ' ' * (indent * 2)
|
||||
|
||||
# create the destination directory
|
||||
if not os.path.exists(dest):
|
||||
if self.verbosity >= 1:
|
||||
print '%sCreating %s/' % (pad, dest)
|
||||
self._create_directory_tree(dest)
|
||||
elif self.verbosity >= 2:
|
||||
print '%sDirectory %s exists' % (pad, dest)
|
||||
|
||||
# step through the source files/directories
|
||||
for name in sorted(os.listdir(source)):
|
||||
|
||||
# get the full path
|
||||
full = os.path.join(source, name)
|
||||
|
||||
# check if the file should be skipped
|
||||
reason = self._should_skip_file(name)
|
||||
if reason:
|
||||
if self.verbosity >= 2:
|
||||
print pad + reason % {'filename': full}
|
||||
continue
|
||||
|
||||
# get the destination filename
|
||||
dest_full = os.path.join(dest, self._substitute_filename(name))
|
||||
|
||||
# if a directory, recurse
|
||||
if os.path.isdir(full):
|
||||
if self.verbosity:
|
||||
print '%sRecursing into %s' % (pad, os.path.basename(full))
|
||||
self._process_directory(full, dest_full, indent=indent + 1)
|
||||
continue
|
||||
|
||||
# check if we should substitute content
|
||||
sub_file = False
|
||||
if dest_full.endswith('_tmpl'):
|
||||
dest_full = dest_full[:-5]
|
||||
sub_file = True
|
||||
|
||||
# read the file contents
|
||||
f = open(full, 'rb')
|
||||
content = f.read()
|
||||
f.close()
|
||||
|
||||
# perform the substitution
|
||||
if sub_file:
|
||||
try:
|
||||
content = self._substitute_content(content, full)
|
||||
except SkipFileException:
|
||||
continue
|
||||
if content is None:
|
||||
continue
|
||||
|
||||
# check if the file already exists
|
||||
already_exists = os.path.exists(dest_full)
|
||||
if already_exists:
|
||||
f = open(dest_full, 'rb')
|
||||
old_content = f.read()
|
||||
f.close()
|
||||
if old_content == content:
|
||||
if self.verbosity:
|
||||
print '%s%s already exists (same content)' % (pad, dest_full)
|
||||
continue
|
||||
if not self.overwrite:
|
||||
continue
|
||||
|
||||
# write out the new file
|
||||
if self.verbosity:
|
||||
print '%sCopying %s to %s' % (pad, os.path.basename(full), dest_full)
|
||||
f = open(dest_full, 'wb')
|
||||
f.write(content)
|
||||
f.close()
|
||||
|
||||
def _should_skip_file(self, name):
|
||||
"""
|
||||
Checks if a file should be skipped based on its name.
|
||||
|
||||
If it should be skipped, returns the reason, otherwise returns
|
||||
None.
|
||||
"""
|
||||
if name.startswith('.'):
|
||||
return 'Skipping hidden file %(filename)s'
|
||||
if name.endswith('~') or name.endswith('.bak'):
|
||||
return 'Skipping backup file %(filename)s'
|
||||
if name.endswith('.pyc'):
|
||||
return 'Skipping .pyc file %(filename)s'
|
||||
if name.endswith('$py.class'):
|
||||
return 'Skipping $py.class file %(filename)s'
|
||||
if name in ('CVS', '_darcs'):
|
||||
return 'Skipping version control directory %(filename)s'
|
||||
return None
|
||||
|
||||
def _create_directory_tree(self, dir):
|
||||
parent = os.path.dirname(os.path.abspath(dir))
|
||||
if not os.path.exists(parent):
|
||||
self._create_directory_tree(parent)
|
||||
os.mkdir(dir)
|
||||
|
||||
def _substitute_filename(self, fn):
|
||||
for var, value in self.vars.items():
|
||||
fn = fn.replace('+%s+' % var, str(value))
|
||||
return fn
|
||||
|
||||
def _substitute_content(self, content, filename):
|
||||
tmpl = LaxTemplate(content)
|
||||
try:
|
||||
return tmpl.substitute(TypeMapper(self.standard_vars.copy()))
|
||||
except Exception, ex:
|
||||
TemplateProcessor.add_exception_info(ex, ' in file %s' % filename)
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def add_exception_info(exc, info):
|
||||
if not hasattr(exc, 'args') or exc.args is None:
|
||||
return
|
||||
args = list(exc.args)
|
||||
if args:
|
||||
args[0] += ' ' + info
|
||||
else:
|
||||
args = [info]
|
||||
exc.args = tuple(args)
|
||||
|
||||
|
||||
class SkipFileException(Exception):
|
||||
"""
|
||||
Raised to indicate that the file should not be copied over.
|
||||
Raise this exception during the substitution of your file.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Template(object):
|
||||
|
||||
# template information
|
||||
summary = ''
|
||||
|
||||
@property
|
||||
def module_directory(self):
|
||||
module = sys.modules[self.__class__.__module__]
|
||||
return os.path.dirname(module.__file__)
|
||||
|
||||
@property
|
||||
def template_directory(self):
|
||||
if getattr(self, 'directory', None) is None:
|
||||
raise Exception('Template "%s" did not set directory' % self.name)
|
||||
return os.path.join(self.module_directory, self.directory)
|
||||
|
||||
def run(self, output_dir, vars, **options):
|
||||
self.pre(output_dir, vars, **options)
|
||||
self.write_files(output_dir, vars, **options)
|
||||
self.post(output_dir, vars, **options)
|
||||
|
||||
def pre(self, output_dir, vars, **options):
|
||||
pass
|
||||
|
||||
def write_files(self, output_dir, vars, **options):
|
||||
processor = TemplateProcessor(vars, options.get('verbosity', 0),
|
||||
options.get('overwrite', False))
|
||||
processor.process(self, output_dir, indent=1)
|
||||
|
||||
def post(self, output_dir, vars, **options):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_templates(cls, parent=None):
|
||||
templates = {}
|
||||
if not parent:
|
||||
parent = cls
|
||||
for template in parent.__subclasses__():
|
||||
if hasattr(template, 'name'):
|
||||
templates[template.name] = template
|
||||
templates.update(cls.get_templates(template))
|
||||
return templates
|
||||
|
||||
|
||||
class DefaultTemplate(Template):
|
||||
|
||||
# template information
|
||||
name = 'default'
|
||||
summary = 'Template for creating a basic Pecan package'
|
||||
directory = 'project'
|
||||
class BaseTemplate(Template):
|
||||
summary = 'Template for creating a basic Pecan project'
|
||||
_template_dir = 'project'
|
||||
egg_plugins = ['Pecan']
|
||||
|
||||
@@ -7,15 +7,16 @@ except ImportError:
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='${project}',
|
||||
version='0.1',
|
||||
description='',
|
||||
author='',
|
||||
author_email='',
|
||||
install_requires=[
|
||||
name = '${project}',
|
||||
version = '0.1',
|
||||
description = '',
|
||||
author = '',
|
||||
author_email = '',
|
||||
install_requires = [
|
||||
"pecan",
|
||||
],
|
||||
zip_safe = False,
|
||||
paster_plugins = ${egg_plugins},
|
||||
include_package_data = True,
|
||||
packages=find_packages(exclude=['ez_setup'])
|
||||
packages = find_packages(exclude=['ez_setup'])
|
||||
)
|
||||
|
||||
7
setup.py
7
setup.py
@@ -61,8 +61,13 @@ setup(
|
||||
cmdclass = {'test': PyTest},
|
||||
install_requires = requirements,
|
||||
entry_points = """
|
||||
[paste.paster_command]
|
||||
pecan-serve = pecan.commands:ServeCommand
|
||||
pecan-shell = pecan.commands:ShellCommand
|
||||
pecan-create = pecan.commands:CreateCommand
|
||||
|
||||
[paste.paster_create_template]
|
||||
pecan-base = templates:NewProjectTemplate
|
||||
pecan-base = pecan.templates:BaseTemplate
|
||||
|
||||
[console_scripts]
|
||||
pecan = pecan.commands:CommandRunner.handle_command_line
|
||||
|
||||
Reference in New Issue
Block a user