~soren/nova/iptables-security-groups

« back to all changes in this revision

Viewing changes to vendor/tornado/tornado/template.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# Copyright 2009 Facebook
 
4
#
 
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 
6
# not use this file except in compliance with the License. You may obtain
 
7
# a copy of the License at
 
8
#
 
9
#     http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
11
# Unless required by applicable law or agreed to in writing, software
 
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
14
# License for the specific language governing permissions and limitations
 
15
# under the License.
 
16
 
 
17
"""A simple template system that compiles templates to Python code.
 
18
 
 
19
Basic usage looks like:
 
20
 
 
21
    t = template.Template("<html>{{ myvalue }}</html>")
 
22
    print t.generate(myvalue="XXX")
 
23
 
 
24
Loader is a class that loads templates from a root directory and caches
 
25
the compiled templates:
 
26
 
 
27
    loader = template.Loader("/home/btaylor")
 
28
    print loader.load("test.html").generate(myvalue="XXX")
 
29
 
 
30
We compile all templates to raw Python. Error-reporting is currently... uh,
 
31
interesting. Syntax for the templates
 
32
 
 
33
    ### base.html
 
34
    <html>
 
35
      <head>
 
36
        <title>{% block title %}Default title{% end %}</title>
 
37
      </head>
 
38
      <body>
 
39
        <ul>
 
40
          {% for student in students %}
 
41
            {% block student %}
 
42
              <li>{{ escape(student.name) }}</li>
 
43
            {% end %}
 
44
          {% end %}
 
45
        </ul>
 
46
      </body>
 
47
    </html>
 
48
 
 
49
    ### bold.html
 
50
    {% extends "base.html" %}
 
51
 
 
52
    {% block title %}A bolder title{% end %}
 
53
 
 
54
    {% block student %}
 
55
      <li><span style="bold">{{ escape(student.name) }}</span></li>
 
56
    {% block %}
 
57
 
 
58
Unlike most other template systems, we do not put any restrictions on the
 
59
expressions you can include in your statements. if and for blocks get
 
60
translated exactly into Python, do you can do complex expressions like:
 
61
 
 
62
   {% for student in [p for p in people if p.student and p.age > 23] %}
 
63
     <li>{{ escape(student.name) }}</li>
 
64
   {% end %}
 
65
 
 
66
Translating directly to Python means you can apply functions to expressions
 
67
easily, like the escape() function in the examples above. You can pass
 
68
functions in to your template just like any other variable:
 
69
 
 
70
   ### Python code
 
71
   def add(x, y):
 
72
      return x + y
 
73
   template.execute(add=add)
 
74
 
 
75
   ### The template
 
76
   {{ add(1, 2) }}
 
77
 
 
78
We provide the functions escape(), url_escape(), json_encode(), and squeeze()
 
79
to all templates by default.
 
80
"""
 
81
 
 
82
from __future__ import with_statement
 
83
 
 
84
import cStringIO
 
85
import datetime
 
86
import escape
 
87
import logging
 
88
import os.path
 
89
import re
 
90
 
 
91
_log = logging.getLogger('tornado.template')
 
92
 
 
93
class Template(object):
 
94
    """A compiled template.
 
95
 
 
96
    We compile into Python from the given template_string. You can generate
 
97
    the template from variables with generate().
 
98
    """
 
99
    def __init__(self, template_string, name="<string>", loader=None,
 
100
                 compress_whitespace=None):
 
101
        self.name = name
 
102
        if compress_whitespace is None:
 
103
            compress_whitespace = name.endswith(".html") or \
 
104
                name.endswith(".js")
 
105
        reader = _TemplateReader(name, template_string)
 
106
        self.file = _File(_parse(reader))
 
107
        self.code = self._generate_python(loader, compress_whitespace)
 
108
        try:
 
109
            self.compiled = compile(self.code, self.name, "exec")
 
110
        except:
 
111
            formatted_code = _format_code(self.code).rstrip()
 
112
            _log.error("%s code:\n%s", self.name, formatted_code)
 
113
            raise
 
114
 
 
115
    def generate(self, **kwargs):
 
116
        """Generate this template with the given arguments."""
 
117
        namespace = {
 
118
            "escape": escape.xhtml_escape,
 
119
            "url_escape": escape.url_escape,
 
120
            "json_encode": escape.json_encode,
 
121
            "squeeze": escape.squeeze,
 
122
            "datetime": datetime,
 
123
        }
 
124
        namespace.update(kwargs)
 
125
        exec self.compiled in namespace
 
126
        execute = namespace["_execute"]
 
127
        try:
 
128
            return execute()
 
129
        except:
 
130
            formatted_code = _format_code(self.code).rstrip()
 
131
            _log.error("%s code:\n%s", self.name, formatted_code)
 
132
            raise
 
133
 
 
134
    def _generate_python(self, loader, compress_whitespace):
 
135
        buffer = cStringIO.StringIO()
 
136
        try:
 
137
            named_blocks = {}
 
138
            ancestors = self._get_ancestors(loader)
 
139
            ancestors.reverse()
 
140
            for ancestor in ancestors:
 
141
                ancestor.find_named_blocks(loader, named_blocks)
 
142
            self.file.find_named_blocks(loader, named_blocks)
 
143
            writer = _CodeWriter(buffer, named_blocks, loader, self,
 
144
                                 compress_whitespace)
 
145
            ancestors[0].generate(writer)
 
146
            return buffer.getvalue()
 
147
        finally:
 
148
            buffer.close()
 
149
 
 
150
    def _get_ancestors(self, loader):
 
151
        ancestors = [self.file]
 
152
        for chunk in self.file.body.chunks:
 
153
            if isinstance(chunk, _ExtendsBlock):
 
154
                if not loader:
 
155
                    raise ParseError("{% extends %} block found, but no "
 
156
                                     "template loader")
 
157
                template = loader.load(chunk.name, self.name)
 
158
                ancestors.extend(template._get_ancestors(loader))
 
159
        return ancestors
 
160
 
 
161
 
 
162
class Loader(object):
 
163
    """A template loader that loads from a single root directory.
 
164
 
 
165
    You must use a template loader to use template constructs like
 
166
    {% extends %} and {% include %}. Loader caches all templates after
 
167
    they are loaded the first time.
 
168
    """
 
169
    def __init__(self, root_directory):
 
170
        self.root = os.path.abspath(root_directory)
 
171
        self.templates = {}
 
172
 
 
173
    def reset(self):
 
174
      self.templates = {}
 
175
 
 
176
    def resolve_path(self, name, parent_path=None):
 
177
        if parent_path and not parent_path.startswith("<") and \
 
178
           not parent_path.startswith("/") and \
 
179
           not name.startswith("/"):
 
180
            current_path = os.path.join(self.root, parent_path)
 
181
            file_dir = os.path.dirname(os.path.abspath(current_path))
 
182
            relative_path = os.path.abspath(os.path.join(file_dir, name))
 
183
            if relative_path.startswith(self.root):
 
184
                name = relative_path[len(self.root) + 1:]
 
185
        return name
 
186
 
 
187
    def load(self, name, parent_path=None):
 
188
        name = self.resolve_path(name, parent_path=parent_path)
 
189
        if name not in self.templates:
 
190
            path = os.path.join(self.root, name)
 
191
            f = open(path, "r")
 
192
            self.templates[name] = Template(f.read(), name=name, loader=self)
 
193
            f.close()
 
194
        return self.templates[name]
 
195
 
 
196
 
 
197
class _Node(object):
 
198
    def each_child(self):
 
199
        return ()
 
200
 
 
201
    def generate(self, writer):
 
202
        raise NotImplementedError()
 
203
 
 
204
    def find_named_blocks(self, loader, named_blocks):
 
205
        for child in self.each_child():
 
206
            child.find_named_blocks(loader, named_blocks)
 
207
 
 
208
 
 
209
class _File(_Node):
 
210
    def __init__(self, body):
 
211
        self.body = body
 
212
 
 
213
    def generate(self, writer):
 
214
        writer.write_line("def _execute():")
 
215
        with writer.indent():
 
216
            writer.write_line("_buffer = []")
 
217
            self.body.generate(writer)
 
218
            writer.write_line("return ''.join(_buffer)")
 
219
 
 
220
    def each_child(self):
 
221
        return (self.body,)
 
222
 
 
223
 
 
224
 
 
225
class _ChunkList(_Node):
 
226
    def __init__(self, chunks):
 
227
        self.chunks = chunks
 
228
 
 
229
    def generate(self, writer):
 
230
        for chunk in self.chunks:
 
231
            chunk.generate(writer)
 
232
 
 
233
    def each_child(self):
 
234
        return self.chunks
 
235
 
 
236
 
 
237
class _NamedBlock(_Node):
 
238
    def __init__(self, name, body=None):
 
239
        self.name = name
 
240
        self.body = body
 
241
 
 
242
    def each_child(self):
 
243
        return (self.body,)
 
244
 
 
245
    def generate(self, writer):
 
246
        writer.named_blocks[self.name].generate(writer)
 
247
 
 
248
    def find_named_blocks(self, loader, named_blocks):
 
249
        named_blocks[self.name] = self.body
 
250
        _Node.find_named_blocks(self, loader, named_blocks)
 
251
 
 
252
 
 
253
class _ExtendsBlock(_Node):
 
254
    def __init__(self, name):
 
255
        self.name = name
 
256
 
 
257
 
 
258
class _IncludeBlock(_Node):
 
259
    def __init__(self, name, reader):
 
260
        self.name = name
 
261
        self.template_name = reader.name
 
262
 
 
263
    def find_named_blocks(self, loader, named_blocks):
 
264
        included = loader.load(self.name, self.template_name)
 
265
        included.file.find_named_blocks(loader, named_blocks)
 
266
 
 
267
    def generate(self, writer):
 
268
        included = writer.loader.load(self.name, self.template_name)
 
269
        old = writer.current_template
 
270
        writer.current_template = included
 
271
        included.file.body.generate(writer)
 
272
        writer.current_template = old
 
273
 
 
274
 
 
275
class _ApplyBlock(_Node):
 
276
    def __init__(self, method, body=None):
 
277
        self.method = method
 
278
        self.body = body
 
279
 
 
280
    def each_child(self):
 
281
        return (self.body,)
 
282
 
 
283
    def generate(self, writer):
 
284
        method_name = "apply%d" % writer.apply_counter
 
285
        writer.apply_counter += 1
 
286
        writer.write_line("def %s():" % method_name)
 
287
        with writer.indent():
 
288
            writer.write_line("_buffer = []")
 
289
            self.body.generate(writer)
 
290
            writer.write_line("return ''.join(_buffer)")
 
291
        writer.write_line("_buffer.append(%s(%s()))" % (
 
292
            self.method, method_name))
 
293
 
 
294
 
 
295
class _ControlBlock(_Node):
 
296
    def __init__(self, statement, body=None):
 
297
        self.statement = statement
 
298
        self.body = body
 
299
 
 
300
    def each_child(self):
 
301
        return (self.body,)
 
302
 
 
303
    def generate(self, writer):
 
304
        writer.write_line("%s:" % self.statement)
 
305
        with writer.indent():
 
306
            self.body.generate(writer)
 
307
 
 
308
 
 
309
class _IntermediateControlBlock(_Node):
 
310
    def __init__(self, statement):
 
311
        self.statement = statement
 
312
 
 
313
    def generate(self, writer):
 
314
        writer.write_line("%s:" % self.statement, writer.indent_size() - 1)
 
315
 
 
316
 
 
317
class _Statement(_Node):
 
318
    def __init__(self, statement):
 
319
        self.statement = statement
 
320
 
 
321
    def generate(self, writer):
 
322
        writer.write_line(self.statement)
 
323
 
 
324
 
 
325
class _Expression(_Node):
 
326
    def __init__(self, expression):
 
327
        self.expression = expression
 
328
 
 
329
    def generate(self, writer):
 
330
        writer.write_line("_tmp = %s" % self.expression)
 
331
        writer.write_line("if isinstance(_tmp, str): _buffer.append(_tmp)")
 
332
        writer.write_line("elif isinstance(_tmp, unicode): "
 
333
                          "_buffer.append(_tmp.encode('utf-8'))")
 
334
        writer.write_line("else: _buffer.append(str(_tmp))")
 
335
 
 
336
 
 
337
class _Text(_Node):
 
338
    def __init__(self, value):
 
339
        self.value = value
 
340
 
 
341
    def generate(self, writer):
 
342
        value = self.value
 
343
 
 
344
        # Compress lots of white space to a single character. If the whitespace
 
345
        # breaks a line, have it continue to break a line, but just with a
 
346
        # single \n character
 
347
        if writer.compress_whitespace and "<pre>" not in value:
 
348
            value = re.sub(r"([\t ]+)", " ", value)
 
349
            value = re.sub(r"(\s*\n\s*)", "\n", value)
 
350
 
 
351
        if value:
 
352
            writer.write_line('_buffer.append(%r)' % value)
 
353
 
 
354
 
 
355
class ParseError(Exception):
 
356
    """Raised for template syntax errors."""
 
357
    pass
 
358
 
 
359
 
 
360
class _CodeWriter(object):
 
361
    def __init__(self, file, named_blocks, loader, current_template,
 
362
                 compress_whitespace):
 
363
        self.file = file
 
364
        self.named_blocks = named_blocks
 
365
        self.loader = loader
 
366
        self.current_template = current_template
 
367
        self.compress_whitespace = compress_whitespace
 
368
        self.apply_counter = 0
 
369
        self._indent = 0
 
370
 
 
371
    def indent(self):
 
372
        return self
 
373
 
 
374
    def indent_size(self):
 
375
        return self._indent
 
376
 
 
377
    def __enter__(self):
 
378
        self._indent += 1
 
379
        return self
 
380
 
 
381
    def __exit__(self, *args):
 
382
        assert self._indent > 0
 
383
        self._indent -= 1
 
384
 
 
385
    def write_line(self, line, indent=None):
 
386
        if indent == None:
 
387
            indent = self._indent
 
388
        for i in xrange(indent):
 
389
            self.file.write("    ")
 
390
        print >> self.file, line
 
391
 
 
392
 
 
393
class _TemplateReader(object):
 
394
    def __init__(self, name, text):
 
395
        self.name = name
 
396
        self.text = text
 
397
        self.line = 0
 
398
        self.pos = 0
 
399
 
 
400
    def find(self, needle, start=0, end=None):
 
401
        assert start >= 0, start
 
402
        pos = self.pos
 
403
        start += pos
 
404
        if end is None:
 
405
            index = self.text.find(needle, start)
 
406
        else:
 
407
            end += pos
 
408
            assert end >= start
 
409
            index = self.text.find(needle, start, end)
 
410
        if index != -1:
 
411
            index -= pos
 
412
        return index
 
413
 
 
414
    def consume(self, count=None):
 
415
        if count is None:
 
416
            count = len(self.text) - self.pos
 
417
        newpos = self.pos + count
 
418
        self.line += self.text.count("\n", self.pos, newpos)
 
419
        s = self.text[self.pos:newpos]
 
420
        self.pos = newpos
 
421
        return s
 
422
 
 
423
    def remaining(self):
 
424
        return len(self.text) - self.pos
 
425
 
 
426
    def __len__(self):
 
427
        return self.remaining()
 
428
 
 
429
    def __getitem__(self, key):
 
430
        if type(key) is slice:
 
431
            size = len(self)
 
432
            start, stop, step = slice.indices(size)
 
433
            if start is None: start = self.pos
 
434
            else: start += self.pos
 
435
            if stop is not None: stop += self.pos
 
436
            return self.text[slice(start, stop, step)]
 
437
        elif key < 0:
 
438
            return self.text[key]
 
439
        else:
 
440
            return self.text[self.pos + key]
 
441
 
 
442
    def __str__(self):
 
443
        return self.text[self.pos:]
 
444
 
 
445
 
 
446
def _format_code(code):
 
447
    lines = code.splitlines()
 
448
    format = "%%%dd  %%s\n" % len(repr(len(lines) + 1))
 
449
    return "".join([format % (i + 1, line) for (i, line) in enumerate(lines)])
 
450
 
 
451
 
 
452
def _parse(reader, in_block=None):
 
453
    body = _ChunkList([])
 
454
    while True:
 
455
        # Find next template directive
 
456
        curly = 0
 
457
        while True:
 
458
            curly = reader.find("{", curly)
 
459
            if curly == -1 or curly + 1 == reader.remaining():
 
460
                # EOF
 
461
                if in_block:
 
462
                    raise ParseError("Missing {%% end %%} block for %s" %
 
463
                                     in_block)
 
464
                body.chunks.append(_Text(reader.consume()))
 
465
                return body
 
466
            # If the first curly brace is not the start of a special token,
 
467
            # start searching from the character after it
 
468
            if reader[curly + 1] not in ("{", "%"):
 
469
                curly += 1
 
470
                continue
 
471
            # When there are more than 2 curlies in a row, use the
 
472
            # innermost ones.  This is useful when generating languages
 
473
            # like latex where curlies are also meaningful
 
474
            if (curly + 2 < reader.remaining() and
 
475
                reader[curly + 1] == '{' and reader[curly + 2] == '{'):
 
476
                curly += 1
 
477
                continue
 
478
            break
 
479
 
 
480
        # Append any text before the special token
 
481
        if curly > 0:
 
482
            body.chunks.append(_Text(reader.consume(curly)))
 
483
 
 
484
        start_brace = reader.consume(2)
 
485
        line = reader.line
 
486
 
 
487
        # Expression
 
488
        if start_brace == "{{":
 
489
            end = reader.find("}}")
 
490
            if end == -1 or reader.find("\n", 0, end) != -1:
 
491
                raise ParseError("Missing end expression }} on line %d" % line)
 
492
            contents = reader.consume(end).strip()
 
493
            reader.consume(2)
 
494
            if not contents:
 
495
                raise ParseError("Empty expression on line %d" % line)
 
496
            body.chunks.append(_Expression(contents))
 
497
            continue
 
498
 
 
499
        # Block
 
500
        assert start_brace == "{%", start_brace
 
501
        end = reader.find("%}")
 
502
        if end == -1 or reader.find("\n", 0, end) != -1:
 
503
            raise ParseError("Missing end block %%} on line %d" % line)
 
504
        contents = reader.consume(end).strip()
 
505
        reader.consume(2)
 
506
        if not contents:
 
507
            raise ParseError("Empty block tag ({%% %%}) on line %d" % line)
 
508
 
 
509
        operator, space, suffix = contents.partition(" ")
 
510
        suffix = suffix.strip()
 
511
 
 
512
        # Intermediate ("else", "elif", etc) blocks
 
513
        intermediate_blocks = {
 
514
            "else": set(["if", "for", "while"]),
 
515
            "elif": set(["if"]),
 
516
            "except": set(["try"]),
 
517
            "finally": set(["try"]),
 
518
        }
 
519
        allowed_parents = intermediate_blocks.get(operator)
 
520
        if allowed_parents is not None:
 
521
            if not in_block:
 
522
                raise ParseError("%s outside %s block" %
 
523
                            (operator, allowed_parents))
 
524
            if in_block not in allowed_parents:
 
525
                raise ParseError("%s block cannot be attached to %s block" % (operator, in_block))
 
526
            body.chunks.append(_IntermediateControlBlock(contents))
 
527
            continue
 
528
 
 
529
        # End tag
 
530
        elif operator == "end":
 
531
            if not in_block:
 
532
                raise ParseError("Extra {%% end %%} block on line %d" % line)
 
533
            return body
 
534
 
 
535
        elif operator in ("extends", "include", "set", "import", "comment"):
 
536
            if operator == "comment":
 
537
                continue
 
538
            if operator == "extends":
 
539
                suffix = suffix.strip('"').strip("'")
 
540
                if not suffix:
 
541
                    raise ParseError("extends missing file path on line %d" % line)
 
542
                block = _ExtendsBlock(suffix)
 
543
            elif operator == "import":
 
544
                if not suffix:
 
545
                    raise ParseError("import missing statement on line %d" % line)
 
546
                block = _Statement(contents)
 
547
            elif operator == "include":
 
548
                suffix = suffix.strip('"').strip("'")
 
549
                if not suffix:
 
550
                    raise ParseError("include missing file path on line %d" % line)
 
551
                block = _IncludeBlock(suffix, reader)
 
552
            elif operator == "set":
 
553
                if not suffix:
 
554
                    raise ParseError("set missing statement on line %d" % line)
 
555
                block = _Statement(suffix)
 
556
            body.chunks.append(block)
 
557
            continue
 
558
 
 
559
        elif operator in ("apply", "block", "try", "if", "for", "while"):
 
560
            # parse inner body recursively
 
561
            block_body = _parse(reader, operator)
 
562
            if operator == "apply":
 
563
                if not suffix:
 
564
                    raise ParseError("apply missing method name on line %d" % line)
 
565
                block = _ApplyBlock(suffix, block_body)
 
566
            elif operator == "block":
 
567
                if not suffix:
 
568
                    raise ParseError("block missing name on line %d" % line)
 
569
                block = _NamedBlock(suffix, block_body)
 
570
            else:
 
571
                block = _ControlBlock(contents, block_body)
 
572
            body.chunks.append(block)
 
573
            continue
 
574
 
 
575
        else:
 
576
            raise ParseError("unknown operator: %r" % operator)