A first pass at simple paste.script style templates.

Committing my work before this code hoses my filesystem.
This commit is contained in:
Ryan Petrello
2012-03-14 16:32:03 -07:00
parent 74100f2733
commit 6ede5714ca
23 changed files with 200 additions and 31 deletions

View File

@@ -9,11 +9,27 @@ from pecan import load_app
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class HelpfulArgumentParser(argparse.ArgumentParser):
def error(self, message):
"""error(message: string)
Prints a usage message incorporating the message to stderr and
exits.
If you override this in a subclass, it should not return -- it
should either exit or raise an exception.
"""
self.print_help(sys.stderr)
self._print_message('\n')
self.exit(2, '%s: %s\n' % (self.prog, message))
class CommandManager(object): class CommandManager(object):
""" Used to discover `pecan.command` entry points. """ """ Used to discover `pecan.command` entry points. """
def __init__(self): def __init__(self):
self.commands_ = {} self.commands = {}
self.load_commands() self.load_commands()
def load_commands(self): def load_commands(self):
@@ -28,11 +44,7 @@ class CommandManager(object):
self.add({ep.name: cmd}) self.add({ep.name: cmd})
def add(self, cmd): def add(self, cmd):
self.commands_.update(cmd) self.commands.update(cmd)
@property
def commands(self):
return self.commands_
class CommandRunner(object): class CommandRunner(object):
@@ -40,13 +52,13 @@ class CommandRunner(object):
def __init__(self): def __init__(self):
self.manager = CommandManager() self.manager = CommandManager()
self.parser = argparse.ArgumentParser( self.parser = HelpfulArgumentParser(
version='Pecan %s' % self.version, version='Pecan %s' % self.version,
add_help=True add_help=True
) )
self.parse_commands() self.parse_sub_commands()
def parse_commands(self): def parse_sub_commands(self):
subparsers = self.parser.add_subparsers( subparsers = self.parser.add_subparsers(
dest='command_name', dest='command_name',
metavar='command' metavar='command'
@@ -67,8 +79,7 @@ class CommandRunner(object):
@classmethod @classmethod
def handle_command_line(cls): def handle_command_line(cls):
runner = CommandRunner() runner = CommandRunner()
exit_code = runner.run(sys.argv[1:]) runner.run(sys.argv[1:])
sys.exit(exit_code)
@property @property
def version(self): def version(self):
@@ -83,7 +94,7 @@ class CommandRunner(object):
@property @property
def commands(self): def commands(self):
return self.manager.commands_ return self.manager.commands
class BaseCommand(object): class BaseCommand(object):

View File

@@ -2,29 +2,23 @@
Create command for Pecan Create command for Pecan
""" """
from pecan.commands import BaseCommand from pecan.commands import BaseCommand
from pecan.templates import DEFAULT_TEMPLATE from pecan.scaffolds import DEFAULT_SCAFFOLD, BaseScaffold
import copy
import sys
class CreateCommand(BaseCommand): class CreateCommand(BaseCommand):
""" """
Creates the file layout for a new Pecan distribution. Creates the file layout for a new Pecan scaffolded project.
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.
""" """
arguments = ({ arguments = ({
'command': 'template_name', 'command': 'template_name',
'help': 'a registered Pecan template', 'help': 'a registered Pecan template',
'nargs': '?', 'nargs': '?',
'default': DEFAULT_TEMPLATE 'default': DEFAULT_SCAFFOLD
},) },)
def run(self, args): def run(self, args):
super(CreateCommand, self).run(args) super(CreateCommand, self).run(args)
print "NOT IMPLEMENTED" print "NOT IMPLEMENTED"
print args.template_name print args.template_name
BaseScaffold().copy_to(args.template_name)

40
pecan/compat.py Normal file
View File

@@ -0,0 +1,40 @@
import sys
# True if we are running on Python 3.
PY3 = sys.version_info[0] == 3
if PY3: # pragma: no cover
text_type = str
else:
text_type = unicode
def bytes_(s, encoding='latin-1', errors='strict'):
""" If ``s`` is an instance of ``text_type``, return
``s.encode(encoding, errors)``, otherwise return ``s``"""
if isinstance(s, text_type): # pragma: no cover
return s.encode(encoding, errors)
return s
if PY3: # pragma: no cover
def native_(s, encoding='latin-1', errors='strict'):
""" If ``s`` is an instance of ``text_type``, return
``s``, otherwise return ``str(s, encoding, errors)``"""
if isinstance(s, text_type):
return s
return str(s, encoding, errors)
else:
def native_(s, encoding='latin-1', errors='strict'): # noqa
""" If ``s`` is an instance of ``text_type``, return
``s.encode(encoding, errors)``, otherwise return ``str(s)``"""
if isinstance(s, text_type):
return s.encode(encoding, errors)
return str(s)
native_.__doc__ = """
Python 3: If ``s`` is an instance of ``text_type``, return ``s``, otherwise
return ``str(s, encoding, errors)``
Python 2: If ``s`` is an instance of ``text_type``, return
``s.encode(encoding, errors)``, otherwise return ``str(s)``
"""

133
pecan/scaffolds/__init__.py Normal file
View File

@@ -0,0 +1,133 @@
import sys
import os
import re
import pkg_resources
from pecan.compat import native_, bytes_
DEFAULT_SCAFFOLD = 'base'
class PecanScaffold(object):
@property
def template_dir(self):
if isinstance(self._scaffold_dir, tuple):
return self._scaffold_dir
else:
return os.path.join(self.module_dir, self._scaffold_dir)
@property
def module_dir(self):
mod = sys.modules[self.__class__.__module__]
return os.path.dirname(mod.__file__)
@property
def variables(self):
return {}
def copy_to(self, dest, **kwargs):
copy_dir(self.template_dir, dest, self.variables)
class BaseScaffold(PecanScaffold):
_scaffold_dir = 'base'
def copy_dir(source, dest, variables, out_=sys.stdout):
"""
Copies the ``source`` directory to the ``dest`` directory.
``variables``: A dictionary of variables to use in any substitutions.
``out_``: File object to write to
"""
def out(msg):
out_.write(msg)
out_.write('\n')
out_.flush()
use_pkg_resources = isinstance(source, tuple)
if use_pkg_resources:
names = sorted(pkg_resources.resource_listdir(source[0], source[1]))
else:
names = sorted(os.listdir(source))
if not os.path.exists(dest):
out('Creating %s' % dest)
makedirs(dest)
else:
out('Directory %s already exists' % dest)
return
for name in names:
if use_pkg_resources:
full = '/'.join([source[1], name])
else:
full = os.path.join(source, name)
dest_full = os.path.join(dest, substitute_filename(name, variables))
sub_file = False
if dest_full.endswith('_tmpl'):
dest_full = dest_full[:-5]
sub_file = True
if use_pkg_resources and pkg_resources.resource_isdir(source[0], full):
out('Recursing into %s' % os.path.basename(full))
copy_dir((source[0], full), dest_full, variables, out_)
continue
elif not use_pkg_resources and os.path.isdir(full):
out('Recursing into %s' % os.path.basename(full))
copy_dir(full, dest_full, variables, out_)
continue
elif use_pkg_resources:
content = pkg_resources.resource_string(source[0], full)
else:
f = open(full, 'rb')
content = f.read()
f.close()
if sub_file:
content = render_template(content, variables)
if content is None:
continue # pragma: no cover
if use_pkg_resources:
out('Copying %s to %s' % (full, dest_full))
else:
out('Copying %s to %s' % (
os.path.basename(full),
dest_full)
)
def makedirs(directory):
parent = os.path.dirname(os.path.abspath(directory))
if not os.path.exists(parent):
makedirs(parent)
os.mkdir(directory)
def substitute_filename(fn, variables):
for var, value in variables.items():
fn = fn.replace('+%s+' % var, str(value))
return fn
def render_template(self, content, variables):
""" Return a bytestring representing a templated file based on the
input (content) and the variable names defined (vars)."""
fsenc = sys.getfilesystemencoding()
content = native_(content, fsenc)
return bytes_(
substitute_double_braces(content, variables), fsenc)
def substitute_double_braces(content, values):
double_brace_pattern = re.compile(r'{{(?P<braced>.*?)}}')
def double_bracerepl(match):
value = match.group('braced').strip()
return values[value]
return double_brace_pattern.sub(double_bracerepl, content)

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,9 +0,0 @@
from paste.script.templates import Template
DEFAULT_TEMPLATE = 'base'
class BaseTemplate(Template):
summary = 'Template for creating a basic Pecan project'
_template_dir = 'project'
egg_plugins = ['Pecan']