public
Description: random pieces of code
Homepage: http://darrenf.org/
Clone URL: git://github.com/darrenf/odds-and-sods.git
odds-and-sods / pork.py
100755 205 lines (164 sloc) 6.795 kb
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#!/usr/bin/env python
 
"""
Use templates + yaml to generate files.
 
pork.py is a simple file generator which combines yaml and a templating
engine in a very stripped down way. Usage is simple:
 
./pork.py <yaml file> [<yaml file> <yaml file> ...]
 
The YAML file should contain 2 YAML documents. The first document is config
information, and the second a structure which corresponds with the variables
referenced in your template(s).
 
The config document must contain one key:
 
template -- full path to a template file on disk
 
By default pork.py sends the rendered results to standard output. However,
if the config document contains the optional ``target`` item, the value is
used as the file to write on disk:
 
target -- full path to the target file (the file you are generating)
 
A third key the config document recognises is ``engine``. This is the
name of the template engine you want to use. The default is django;
other supported values are jinja2, mako, and python (string.Template
interpolation).
 
engine -- name of the template engine to use (default 'django')
 
NB if you use django, no settings module is required, or used.
 
If your template refers to other templates (eg a django {% include %}),
those others must be in the same directory as the original.
 
The second document is loaded into a python dictionary and passed to the
chosen engine's rendering method (a dictionary, kwargs, etc).
 
pork.py only really does sanity checking on its own input. Any errors
caused by bad YAML, bad filenames, bad templates, etc, are dealt with
by python, yaml, or the engine itself.
 
See the YAML spec or the PyYAML docs for how to write YAML documents and
files. Here's a super-trivial example:
 
hello.yml:
 
---
template: hello.html
---
hello: world
 
hello.html:
 
Hello {{hello}}
 
$ ./pork.py hello.yml
Hello world
 
Oh, look, there are some laughably simple tests as well. Written
due to a mixture of boredom and a desire/need to keep my mind
occupied. Don't want to mope around feeling lonely and self-pitiful
while Ruth's in Pakistan. So, assuming you checked this code out
from github...
 
$ python tests/__init__.py
 
"""
 
# standard library
import os, sys
 
# 3rd parties
import yaml
 
_django_configured = False
 
engines = []
 
# decorator to maintain the list of engines from renderer methods
def engine(func):
    if func.__name__ not in engines:
        engines.append(func.__name__)
    return func
 
class Renderer:
    def __init__(self, config, values):
        # split the path into a directory and a filename and get it rendered
        self.head, self.tail = os.path.split(config["template"])
        self.values = values
        self.config = config
        self.engine = config.get('engine','django')
    def render(self):
        return eval("self.%s()" % self.engine)
    def spit(self, output=None):
        if output is None:
            output = self.render()
        if self.config.has_key("target"):
            # OK so look, here's the deal: we're not, after all, going to let
            # python throw any and all errors it wants to, because frankly
            # I think "the directory you want to make already exists" is a daft
            # error for it to throw. So we'll catch and ignore that one. Any
            # others, though ...
            try:
                os.makedirs(os.path.split(self.config["target"])[0])
            except OSError, e:
                import errno
                if e.errno == errno.EEXIST:
                    pass
            fh = open(self.config["target"],'w')
            fh.write(output)
            fh.close()
            print "### generated [%s]" % self.config["target"]
        else:
            print output
    # renderers
    @engine
    def django(self):
        # django
        from django.conf import settings
        from django.template.loader import render_to_string
        # use of django's templates without settings requires this
        # configury-pokery
        global _django_configured
        if not _django_configured:
            settings.configure()
            _django_configured=True
        settings.TEMPLATE_DIRS=[self.head]
        return render_to_string(self.tail, self.values)
 
    @engine
    def jinja2(self):
        # jinja2
        from jinja2 import Environment, FileSystemLoader
        env = Environment(loader=FileSystemLoader(self.head))
        template = env.get_template(self.tail)
        values = self.values
        return template.render(**values)
 
    @engine
    def mako(self):
        # mako
        from mako.lookup import TemplateLookup
        lookup = TemplateLookup(directories=[self.head], collection_size=1)
        template = lookup.get_template(self.tail)
        values = self.values
        return template.render(**values)
 
    @engine
    def python(self):
        # string.Template
        import string
        file = "%s/%s" % (self.head, self.tail)
        values=self.values
        return string.Template(open(file).read()).safe_substitute(values)
 
    @engine
    def cheetah(self):
        # cheetah
        from Cheetah.Template import Template as cheetahTemplate
        t = cheetahTemplate(file=self.config["template"],
                            searchList=[self.values])
        return str(t)
 
 
def usage():
    sys.stderr.write("Usage: pork.py <yaml file> [<yaml file> ...]\n")
    sys.exit()
 
def main():
    assert len(sys.argv) > 1, usage()
    for file in sys.argv[1:]:
        try:
            generate(file)
        except Exception, e:
            sys.stderr.write("### Problem with file [%s], error was:\n" % file)
            sys.stderr.write("### %s\n" % e)
 
def generate(file):
    # if this stuff barfs (bad yaml, no file, etc) we're just going to
    # let python + yaml throw their errors
    # NB: load_all returns a generator that can't be cast to a list, but I
    # want it as a list damn it, hence the comprehension
    docs = [doc for doc in yaml.load_all(open(file))]
 
    # now do all our assertions in the hope that we have clean data
    # all other errors can be caught by the template engines or python;
    # we only care about pork.py's requirements
    assert len(docs) == 2, "yaml file must contain only 2 documents"
    config, values = docs
    assert isinstance(config,dict), "config document must be dictionary"
    assert isinstance(values,dict), "values document must be dictionary"
    assert config.has_key("template"), "config document must contain template"
    assert config.get('engine','django') in engines, \
            "supported engines are %s" % engines
 
    # the class does everything now
    # initialises with the documents, .spit does the rendering and the
    # stdout/file output
    Renderer(config,values).spit()
 
if __name__ == '__main__':
    main()