From bcb7c533cde117f10ebeea3609bc27cca241459d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 11 Apr 2008 16:30:34 +0200 Subject: [PATCH] work on tha runtime --HG-- branch : trunk --- jdebug.py | 70 ------------------------------------------- jinja2/compiler.py | 13 ++++---- jinja2/environment.py | 53 +++++++++++++++++++++++++++++++- jinja2/lexer.py | 3 -- jinja2/loaders.py | 43 ++++++++++++++++++++++++-- jinja2/optimizer.py | 14 ++++----- jinja2/parser.py | 2 -- jinja2/runtime.py | 7 +++-- test.py | 21 ------------- test_optimizer.py | 34 --------------------- 10 files changed, 111 insertions(+), 149 deletions(-) delete mode 100644 jdebug.py delete mode 100644 test.py delete mode 100644 test_optimizer.py diff --git a/jdebug.py b/jdebug.py deleted file mode 100644 index f5463a6..0000000 --- a/jdebug.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jdebug - ~~~~~~ - - Helper module to simplify jinja debugging. Use - - :copyright: 2006 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" -import os -import sys -import gc -from jinja import Environment -from jinja.parser import Parser -from jinja.lexer import Lexer -from jinja.translators.python import PythonTranslator - - -__all__ = ['e', 't', 'p', 'l', 'm'] - - -_global_frame = sys._getframe() -e = Environment() -t = e.from_string - - -def p(x=None, f=None): - if x is None and f is not None: - x = e.loader.get_source(f) - print PythonTranslator(e, Parser(e, x, f).parse(), None).translate() - -def l(x): - for item in e.lexer.tokenize(x): - print '%5s %-20s %r' % (item.lineno, - item.type, - item.value) - -class MemoryGuard(object): - - def __init__(self): - self.guarded_objects = {} - self.clear = self.guarded_objects.clear - - def freeze(self): - self.clear() - for obj in gc.get_objects(): - self.guarded_objects[id(obj)] = True - - def get_delta(self): - frm = sys._getframe() - result = [] - for obj in gc.get_objects(): - if id(obj) not in self.guarded_objects and \ - obj is not frm and obj is not result: - result.append(obj) - return result - - -m = MemoryGuard() - - -if __name__ == '__main__': - if len(sys.argv) > 1: - from jinja import FileSystemLoader - e.loader = FileSystemLoader(sys.argv[1]) - if len(sys.argv) > 2: - p(f=sys.argv[2]) - else: - p(sys.stdin.read()) diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 66af667..6175916 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -30,6 +30,7 @@ operators = { def generate(node, environment, filename, stream=None): + """Generate the python source for a node tree.""" is_child = node.find(nodes.Extends) is not None generator = CodeGenerator(environment, is_child, filename, stream) generator.visit(node) @@ -341,14 +342,14 @@ class CodeGenerator(NodeVisitor): def visit_Block(self, node, frame): """Call a block and register it for the template.""" - # if we know that we are a child template, there is no need to - # check if we are one - if self.has_known_extends and frame.toplevel: - return if frame.toplevel: + # if we know that we are a child template, there is no need to + # check if we are one + if self.has_known_extends: + return self.writeline('if parent_root is None:') self.indent() - self.writeline('for event in context.blocks[-1][%r](context):' % node.name) + self.writeline('for event in context.blocks[0][%r](context):' % node.name) self.indent() self.writeline('yield event') self.outdent(1 + frame.toplevel) @@ -383,7 +384,7 @@ class CodeGenerator(NodeVisitor): self.writeline('parent_root = extends(', node, 1) self.visit(node.template, frame) - self.write(', context)') + self.write(', context, environment)') # if this extends statement was in the root level we can take # advantage of that information and simplify the generated code diff --git a/jinja2/environment.py b/jinja2/environment.py index 8ba4fce..a350620 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -10,6 +10,8 @@ """ from jinja2.lexer import Lexer from jinja2.parser import Parser +from jinja2.optimizer import optimize +from jinja2.compiler import generate from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE @@ -29,7 +31,8 @@ class Environment(object): comment_start_string='{#', comment_end_string='#}', trim_blocks=False, - template_charset='utf-8'): + template_charset='utf-8', + loader=None): """Here the possible initialization parameters: ========================= ============================================ @@ -47,6 +50,7 @@ class Environment(object): after a block is removed (block, not variable tag!). Defaults to ``False``. `template_charset` the charset of the templates. + `loader` the loader which should be used. ========================= ============================================ """ @@ -71,6 +75,9 @@ class Environment(object): if not hasattr(self, 'finalize'): self.finalize = unicode + # set the loader provided + self.loader = loader + # create lexer self.lexer = Lexer(self) @@ -91,3 +98,47 @@ class Environment(object): The tuples are returned in the form ``(lineno, token, value)``. """ return self.lexer.tokeniter(source, filename) + + def compile(self, source, filename=None, raw=False): + """Compile a node or source.""" + if isinstance(source, basestring): + source = self.parse(source, filename) + node = optimize(source, self) + source = generate(node, self) + if raw: + return source + if isinstance(filename, unicode): + filename = filename.encode('utf-8') + return compile(source, filename, 'exec') + + def join_path(self, template, parent): + """Join a template with the parent. By default all the lookups are + relative to the loader root, but if the paths should be relative this + function can be used to calculate the real filename.""" + return template + + def get_template(self, name, parent=None): + """Load a template.""" + if self.loader is None: + raise TypeError('no loader for this environment specified') + if parent is not None: + name = self.join_path(name, parent) + return self.loader.load(self, name) + + +class Template(object): + """Represents a template.""" + + def __init__(self, environment, code): + namespace = {'environment': environment} + exec code in namespace + self.environment = environment + self.root_render_func = namespace['root'] + self.blocks = namespace['blocks'] + + def render(self, *args, **kwargs): + return u''.join(self.stream(*args, **kwargs)) + + def stream(self, *args, **kwargs): + context = dict(*args, **kwargs) + return self.root_render_func(context) diff --git a/jinja2/lexer.py b/jinja2/lexer.py index 6e9fc89..b19e4ca 100644 --- a/jinja2/lexer.py +++ b/jinja2/lexer.py @@ -21,9 +21,6 @@ from jinja2.exceptions import TemplateSyntaxError from weakref import WeakValueDictionary -__all__ = ['Lexer', 'Failure', 'keywords'] - - # cache for the lexers. Exists in order to be able to have multiple # environments with the same lexer _lexer_cache = WeakValueDictionary() diff --git a/jinja2/loaders.py b/jinja2/loaders.py index 40bc1d7..796b49f 100644 --- a/jinja2/loaders.py +++ b/jinja2/loaders.py @@ -1,10 +1,47 @@ # -*- coding: utf-8 -*- """ - jinja.loaders - ~~~~~~~~~~~~~ + jinja2.loaders + ~~~~~~~~~~~~~~ Jinja loader classes. - :copyright: 2007 by Armin Ronacher, Bryan McLemore. + :copyright: 2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +from os import path +from jinja2.exceptions import TemplateNotFound +from jinja2.environment import Template + + +class BaseLoader(object): + + def get_source(self, environment, template): + raise TemplateNotFound() + + def load(self, environment, template): + source, filename = self.get_source(environment, template) + code = environment.compile(source, filename) + return Template(environment, code) + + +class FileSystemLoader(BaseLoader): + + def __init__(self, path, encoding='utf-8'): + self.path = path + self.encoding = encoding + + def get_source(self, environment, template): + pieces = [] + for piece in template.split('/'): + if piece == '..': + raise TemplateNotFound() + elif piece != '.': + pieces.append(piece) + filename = path.join(self.path, *pieces) + if not path.isfile(filename): + raise TemplateNotFound(template) + f = file(filename) + try: + return f.read().decode(self.encoding) + finally: + f.close() diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py index 167f6eb..bd97fa0 100644 --- a/jinja2/optimizer.py +++ b/jinja2/optimizer.py @@ -24,6 +24,13 @@ from jinja2.visitor import NodeVisitor, NodeTransformer from jinja2.runtime import subscribe, LoopContext +def optimize(node, environment, context_hint=None): + """The context hint can be used to perform an static optimization + based on the context given.""" + optimizer = Optimizer(environment) + return optimizer.visit(node, ContextStack(context_hint)) + + class ContextStack(object): """Simple compile time context implementation.""" undefined = object() @@ -206,10 +213,3 @@ class Optimizer(NodeTransformer): visit_Not = visit_Compare = visit_Subscript = visit_Call = \ visit_Filter = visit_Test = fold del fold - - -def optimize(node, environment, context_hint=None): - """The context hint can be used to perform an static optimization - based on the context given.""" - optimizer = Optimizer(environment) - return optimizer.visit(node, ContextStack(context_hint)) diff --git a/jinja2/parser.py b/jinja2/parser.py index 74cc421..8dc5d58 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -12,8 +12,6 @@ from jinja2 import nodes from jinja2.exceptions import TemplateSyntaxError -__all__ = ['Parser'] - _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print', 'macro', 'include']) _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in']) diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 097b9e4..82acd6c 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -18,9 +18,12 @@ __all__ = ['extends', 'subscribe', 'LoopContext', 'StaticLoopContext', 'TemplateContext', 'Macro', 'Undefined'] -def extends(template, context): +def extends(template_name, context, environment): """This loads a template (and evaluates it) and replaces the blocks.""" - context.stream_muted = True + template = environment.get_template(template_name, context.filename) + for name, block in template.blocks.iteritems(): + context.blocks.setdefault(name, []).append(block) + return template.root_render_func def subscribe(obj, argument): diff --git a/test.py b/test.py deleted file mode 100644 index 4b0b218..0000000 --- a/test.py +++ /dev/null @@ -1,21 +0,0 @@ -import sys -from jinja2 import Environment -from jinja2.compiler import generate - - -env = Environment() -ast = env.parse(""" -{% extends master_layout or "foo.html" %} - -{% baz = [1, 2, 3, 4] %} -{% macro foo() %} - blah -{% endmacro %} -{% blah = 42 %} - -{% block body %} - Das ist ein Test -{% endblock %} -""") -source = generate(ast, env, "foo.html") -print source diff --git a/test_optimizer.py b/test_optimizer.py deleted file mode 100644 index d0d8551..0000000 --- a/test_optimizer.py +++ /dev/null @@ -1,34 +0,0 @@ -from jinja2 import Environment -from jinja2.compiler import generate -from jinja2.optimizer import optimize - - -env = Environment() -forums = [ - {'id': 1, 'name': u'Example'}, - {'id': 2, 'name': u'Foobar'}, - {'id': 3, 'name': u'<42>'} -] -ast = env.parse(""" - Hi {{ ""|e }}, - how are you? - - {% for forum in forums %} - {{ readstatus(forum.id) }} {{ forum.id|e }} {{ forum.name|e }} - {% endfor %} - - {% navigation = [('#foo', 'Foo'), ('#bar', 'Bar'), ('#baz', 42 * 2 + 23)] %} - -""") -print ast -print -print generate(ast, env, "foo.html") -print -ast = optimize(ast, env, context_hint={'forums': forums}) -print ast -print -print generate(ast, env, "foo.html") -- 2.26.2