diff options
Diffstat (limited to 'creactiweb/_templates/lib/werkzeug/templates.py')
-rw-r--r-- | creactiweb/_templates/lib/werkzeug/templates.py | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/creactiweb/_templates/lib/werkzeug/templates.py b/creactiweb/_templates/lib/werkzeug/templates.py new file mode 100644 index 0000000..5f82ba8 --- /dev/null +++ b/creactiweb/_templates/lib/werkzeug/templates.py @@ -0,0 +1,392 @@ +# -*- coding: utf-8 -*- +r""" + werkzeug.templates + ~~~~~~~~~~~~~~~~~~ + + A minimal template engine. + + :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details. + :license: BSD License. +""" +import sys +import re +import __builtin__ as builtins +from compiler import ast, parse +from compiler.pycodegen import ModuleCodeGenerator +from tokenize import PseudoToken +from werkzeug import utils, urls +from werkzeug._internal import _decode_unicode + + +# Copyright notice: The `parse_data` method uses the string interpolation +# algorithm by Ka-Ping Yee which originally was part of `Itpl20.py`_. +# +# .. _Itpl20.py: http://lfw.org/python/Itpl20.py + + +token_re = re.compile('%s|%s(?s)' % ( + r'[uU]?[rR]?("""|\'\'\')((?<!\\)\\\1|.)*?\1', + PseudoToken +)) +directive_re = re.compile(r'(?<!\\)<%(?:(#)|(py(?:thon)?\b)|' + r'(?:\s*(\w+))\s*)(.*?)\s*%>\n?(?s)') +escape_re = re.compile(r'\\\n|\\(\\|<%)') +namestart_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' +undefined = type('UndefinedType', (object,), { + '__iter__': lambda x: iter(()), + '__repr__': lambda x: 'Undefined', + '__str__': lambda x: '' +})() +runtime_vars = dict.fromkeys(('Undefined', '__to_unicode', '__context', + '__write', '__write_many')) + + +def call_stmt(func, args, lineno): + return ast.CallFunc(ast.Name(func, lineno=lineno), + args, lineno=lineno) + + +def tokenize(source, filename): + escape = escape_re.sub + escape_repl = lambda m: m.group(1) or '' + lineno = 1 + pos = 0 + + for match in directive_re.finditer(source): + start, end = match.span() + if start > pos: + data = source[pos:start] + yield lineno, 'data', escape(escape_repl, data) + lineno += data.count('\n') + is_comment, is_code, cmd, args = match.groups() + if is_code: + yield lineno, 'code', args + elif not is_comment: + yield lineno, 'cmd', (cmd, args) + lineno += source[start:end].count('\n') + pos = end + + if pos < len(source): + yield lineno, 'data', escape(escape_repl, source[pos:]) + + +def transform(node, filename): + root = ast.Module(None, node, lineno=1) + nodes = [root] + while nodes: + node = nodes.pop() + node.filename = filename + if node.__class__ in (ast.Printnl, ast.Print): + node.dest = ast.Name('__context') + elif node.__class__ is ast.Const and isinstance(node.value, str): + try: + node.value.decode('ascii') + except UnicodeError: + node.value = node.value.decode('utf-8') + nodes.extend(node.getChildNodes()) + return root + + +class TemplateSyntaxError(SyntaxError): + + def __init__(self, msg, filename, lineno): + from linecache import getline + l = getline(filename, lineno) + SyntaxError.__init__(self, msg, (filename, lineno, len(l) or 1, l)) + + +class Parser(object): + + def __init__(self, gen, filename): + self.gen = gen + self.filename = filename + self.lineno = 1 + + def fail(self, msg): + raise TemplateSyntaxError(msg, self.filename, self.lineno) + + def parse_python(self, expr, type='exec'): + if isinstance(expr, unicode): + expr = '\xef\xbb\xbf' + expr.encode('utf-8') + try: + node = parse(expr, type) + except SyntaxError, e: + raise TemplateSyntaxError(str(e), self.filename, + self.lineno + e.lineno - 1) + nodes = [node] + while nodes: + n = nodes.pop() + if hasattr(n, 'lineno'): + n.lineno = (n.lineno or 1) + self.lineno - 1 + nodes.extend(n.getChildNodes()) + return node.node + + def parse(self, needle=()): + start_lineno = self.lineno + result = [] + add = result.append + for self.lineno, token, value in self.gen: + if token == 'data': + add(self.parse_data(value)) + elif token == 'code': + add(self.parse_code(value.splitlines())) + elif token == 'cmd': + name, args = value + if name in needle: + return name, args, ast.Stmt(result, lineno=start_lineno) + if name in ('for', 'while'): + add(self.parse_loop(args, name)) + elif name == 'if': + add(self.parse_if(args)) + else: + self.fail('unknown directive %s' % name) + if needle: + self.fail('unexpected end of template') + return ast.Stmt(result, lineno=start_lineno) + + def parse_loop(self, args, type): + rv = self.parse_python('%s %s: pass' % (type, args), 'exec').nodes[0] + tag, value, rv.body = self.parse(('end' + type, 'else')) + if value: + self.fail('unexpected data after ' + tag) + if tag == 'else': + tag, value, rv.else_ = self.parse(('end' + type,)) + if value: + self.fail('unexpected data after else') + return rv + + def parse_if(self, args): + cond = self.parse_python('if %s: pass' % args).nodes[0] + tag, value, body = self.parse(('else', 'elif', 'endif')) + cond.tests[0] = (cond.tests[0][0], body) + while 1: + if tag == 'else': + if value: + self.fail('unexpected data after else') + tag, value, cond.else_ = self.parse(('endif',)) + elif tag == 'elif': + expr = self.parse_python(value, 'eval') + tag, value, body = self.parse(('else', 'elif', 'endif')) + cond.tests.append((expr, body)) + continue + break + if value: + self.fail('unexpected data after endif') + return cond + + def parse_code(self, lines): + margin = sys.maxint + for line in lines[1:]: + content = len(line.lstrip()) + if content: + indent = len(line) - content + margin = min(margin, indent) + if lines: + lines[0] = lines[0].lstrip() + if margin < sys.maxint: + for i in xrange(1, len(lines)): + lines[i] = lines[i][margin:] + while lines and not lines[-1]: + lines.pop() + while lines and not lines[0]: + lines.pop(0) + return self.parse_python('\n'.join(lines)) + + def parse_data(self, text): + start_lineno = lineno = self.lineno + pos = 0 + end = len(text) + nodes = [] + + def match_or_fail(pos): + match = token_re.match(text, pos) + if match is None: + self.fail('invalid syntax') + return match.group().strip(), match.end() + + def write_expr(code): + node = self.parse_python(code, 'eval') + nodes.append(call_stmt('__to_unicode', [node], lineno)) + return code.count('\n') + + def write_data(value): + if value: + nodes.append(ast.Const(value, lineno=lineno)) + return value.count('\n') + return 0 + + while 1: + offset = text.find('$', pos) + if offset < 0: + break + next = text[offset + 1] + + if next == '{': + lineno += write_data(text[pos:offset]) + pos = offset + 2 + level = 1 + while level: + token, pos = match_or_fail(pos) + if token in ('{', '}'): + level += token == '{' and 1 or -1 + lineno += write_expr(text[offset + 2:pos - 1]) + elif next in namestart_chars: + lineno += write_data(text[pos:offset]) + token, pos = match_or_fail(offset + 1) + while pos < end: + if text[pos] == '.' and pos + 1 < end and \ + text[pos + 1] in namestart_chars: + token, pos = match_or_fail(pos + 1) + elif text[pos] in '([': + pos += 1 + level = 1 + while level: + token, pos = match_or_fail(pos) + if token in ('(', ')', '[', ']'): + level += token in '([' and 1 or -1 + else: + break + lineno += write_expr(text[offset + 1:pos]) + else: + lineno += write_data(text[pos:offset + 1]) + pos = offset + 1 + (next == '$') + write_data(text[pos:]) + + return ast.Discard(call_stmt(len(nodes) == 1 and '__write' or + '__write_many', nodes, start_lineno), + lineno=start_lineno) + + +class Context(object): + + def __init__(self, namespace, charset, errors): + self.charset = charset + self.errors = errors + self._namespace = namespace + self._buffer = [] + self._write = self._buffer.append + _extend = self._buffer.extend + self.runtime = dict( + Undefined=undefined, + __to_unicode=self.to_unicode, + __context=self, + __write=self._write, + __write_many=lambda *a: _extend(a) + ) + + def write(self, value): + self._write(self.to_unicode(value)) + + def to_unicode(self, value): + if isinstance(value, str): + return _decode_unicode(value, self.charset, self.errors) + return unicode(value) + + def get_value(self, as_unicode=True): + rv = u''.join(self._buffer) + if not as_unicode: + return rv.encode(self.charset, self.errors) + return rv + + def __getitem__(self, key, default=undefined): + try: + return self._namespace[key] + except KeyError: + return getattr(builtins, key, default) + + def get(self, key, default=None): + return self.__getitem__(key, default) + + def __setitem__(self, key, value): + self._namespace[key] = value + + def __delitem__(self, key): + del self._namespace[key] + + +class TemplateCodeGenerator(ModuleCodeGenerator): + + def __init__(self, node, filename): + ModuleCodeGenerator.__init__(self, transform(node, filename)) + + def _nameOp(self, prefix, name): + if name in runtime_vars: + return self.emit(prefix + '_GLOBAL', name) + return ModuleCodeGenerator._nameOp(self, prefix, name) + + +class Template(object): + """Represents a simple text based template. It's a good idea to load such + templates from files on the file system to get better debug output. + """ + + default_context = { + 'escape': utils.escape, + 'url_quote': urls.url_quote, + 'url_quote_plus': urls.url_quote_plus, + 'url_encode': urls.url_encode + } + + def __init__(self, source, filename='<template>', charset='utf-8', + errors='strict', unicode_mode=True): + if isinstance(source, str): + source = _decode_unicode(source, charset, errors) + if isinstance(filename, unicode): + filename = filename.encode('utf-8') + node = Parser(tokenize(u'\n'.join(source.splitlines()), + filename), filename).parse() + self.code = TemplateCodeGenerator(node, filename).getCode() + self.filename = filename + self.charset = charset + self.errors = errors + self.unicode_mode = unicode_mode + + @classmethod + def from_file(cls, file, charset='utf-8', errors='strict', + unicode_mode=True): + """Load a template from a file. + + .. versionchanged:: 0.5 + The encoding parameter was renamed to charset. + + :param file: a filename or file object to load the template from. + :param charset: the charset of the template to load. + :param errors: the error behavior of the charset decoding. + :param unicode_mode: set to `False` to disable unicode mode. + :return: a template + """ + close = False + if isinstance(file, basestring): + f = open(file, 'r') + close = True + try: + data = _decode_unicode(f.read(), charset, errors) + finally: + if close: + f.close() + return cls(data, getattr(f, 'name', '<template>'), charset, + errors, unicode_mode) + + def render(self, *args, **kwargs): + """This function accepts either a dict or some keyword arguments which + will then be the context the template is evaluated in. The return + value will be the rendered template. + + :param context: the function accepts the same arguments as the + :class:`dict` constructor. + :return: the rendered template as string + """ + ns = self.default_context.copy() + if len(args) == 1 and isinstance(args[0], utils.MultiDict): + ns.update(args[0].to_dict(flat=True)) + else: + ns.update(dict(*args)) + if kwargs: + ns.update(kwargs) + context = Context(ns, self.charset, self.errors) + exec self.code in context.runtime, context + return context.get_value(self.unicode_mode) + + def substitute(self, *args, **kwargs): + """For API compatibility with `string.Template`.""" + return self.render(*args, **kwargs) |