summaryrefslogtreecommitdiff
path: root/pecan/scaffolds/__init__.py
blob: ac22464b85fc426a2ac88f69111444025e64fdd4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import sys
import os
import re
import pkg_resources
from string import Template

import six

DEFAULT_SCAFFOLD = 'base'
_bad_chars_re = re.compile('[^a-zA-Z0-9_]')


class PecanScaffold(object):
    """
    A base Pecan scaffold.  New scaffolded implementations should extend this
    class and define a ``_scaffold_dir`` attribute, e.g.,

    class CoolAddOnScaffold(PecanScaffold):

        _scaffold_dir = ('package', os.path.join('scaffolds', 'scaffold_name'))

    ...where...

        pkg_resources.resource_listdir(_scaffold_dir[0], _scaffold_dir[1]))

    ...points to some scaffold directory root.
    """

    def normalize_output_dir(self, dest):
        return os.path.abspath(os.path.normpath(dest))

    def normalize_pkg_name(self, dest):
        return _bad_chars_re.sub('', dest.lower())

    def copy_to(self, dest, **kw):
        output_dir = self.normalize_output_dir(dest)
        pkg_name = self.normalize_pkg_name(dest)
        copy_dir(self._scaffold_dir, output_dir, {'package': pkg_name}, **kw)


class BaseScaffold(PecanScaffold):
    _scaffold_dir = ('pecan', os.path.join('scaffolds', 'base'))


def copy_dir(source, dest, variables, out_=sys.stdout, i=0):
    """
    Copies the ``source`` directory to the ``dest`` directory, where
    ``source`` is some tuple representing an installed package and a
    subdirectory in the package, e.g.,

    ('pecan', os.path.join('scaffolds', 'base'))
    ('pecan_extension', os.path.join('scaffolds', 'scaffold_name'))

    ``variables``: A dictionary of variables to use in any substitutions.
    Substitution is performed via ``string.Template``.

    ``out_``: File object to write to (default is sys.stdout).
    """
    def out(msg):
        out_.write('%s%s' % (' ' * (i * 2), msg))
        out_.write('\n')
        out_.flush()

    names = sorted(pkg_resources.resource_listdir(source[0], source[1]))
    if not os.path.exists(dest):
        out('Creating %s' % dest)
        makedirs(dest)
    else:
        out('%s already exists' % dest)
        return

    for name in names:

        full = '/'.join([source[1], 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 pkg_resources.resource_isdir(source[0], full):
            out('Recursing into %s' % os.path.basename(full))
            copy_dir((source[0], full), dest_full, variables, out_, i + 1)
            continue
        else:
            content = pkg_resources.resource_string(source[0], full)

        if sub_file:
            content = render_template(content, variables)
            if content is None:
                continue  # pragma: no cover

        out('Copying %s to %s' % (full, dest_full))

        f = open(dest_full, 'wb')
        f.write(content)
        f.close()


def makedirs(directory):
    """ Resursively create a named 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):
    """ Substitute +variables+ in file directory names. """
    for var, value in variables.items():
        fn = fn.replace('+%s+' % var, str(value))
    return fn


def render_template(content, variables):
    """
    Return a bytestring representing a templated file based on the
    input (content) and the variable names defined (vars).
    """
    fsenc = sys.getfilesystemencoding()

    def to_native(s, encoding='latin-1', errors='strict'):
        if six.PY3:
            if isinstance(s, six.text_type):
                return s
            return str(s, encoding, errors)
        else:
            if isinstance(s, six.text_type):
                return s.encode(encoding, errors)
            return str(s)

    output = Template(
        to_native(content, fsenc)
    ).substitute(variables)
    if isinstance(output, six.text_type):
        output = output.encode(fsenc, 'strict')
    return output