screwing around with the exception formatting a bit
diff --git a/lib/mako/exceptions.py b/lib/mako/exceptions.py index 78c5730..ff53ba8 100644 --- a/lib/mako/exceptions.py +++ b/lib/mako/exceptions.py
@@ -49,6 +49,7 @@ # rawrecords = traceback.extract_stack() + rawrecords new_trcback = [] for filename, lineno, function, line in rawrecords: + #print "TB", filename, lineno, function, line try: (line_map, template_lines) = mods[filename] except KeyError: @@ -56,9 +57,9 @@ info = mako.template._get_module_info(filename) module_source = info.code template_source = info.source - template_filename = info.template_filename + template_filename = info.template_filename or filename except KeyError: - new_trcback.append((filename, lineno, function, line, None, None, None)) + new_trcback.append((filename, lineno, function, line, None, None, None, None)) continue template_ln = module_ln = 1 @@ -79,19 +80,16 @@ template_line = template_lines[template_ln - 1] else: template_line = None - new_trcback.append((filename, lineno, function, line, template_filename, template_ln, template_line)) + new_trcback.append((filename, lineno, function, line, template_filename, template_ln, template_line, template_source)) return (type, value, new_trcback) - -# TODO: this is scratch, make a module for exception reporting templates -def get_error_template(): + +def text_error_template(): import mako.template - return mako.template.Template(""" + return mako.template.Template(r""" <%! from mako.exceptions import rich_traceback %> -<html> -<body> - Error ! +Error ! <% (type, value, trcback) = rich_traceback() %> @@ -99,10 +97,43 @@ ${str(type)} - ${value} % for (filename, lineno, function, line, template_filename, template_ln, template_line) in trcback: + % if template_line: + ${template_filename} ${template_ln} ${template_line} + % else: + ${filename} ${lineno} ${line} + % endif +% endfor +""") + +def html_error_template(): + import mako.template + return mako.template.Template(r""" +<%! + from mako.exceptions import rich_traceback +%> +<html> +<body> + Error ! +<% + (errtype, value, trcback) = rich_traceback() + src = trcback[-1][7] + line = trcback[-1][5] + lines = src.split('\n') + trcback.reverse() + +%> + +${str(error)} + +<div> +${'\n'.join(lines[line-5:line+5])} +</div> + +% for (filename, lineno, function, line, template_filename, template_ln, template_line, src) in trcback: % if template_line: ${template_filename} ${template_ln} ${template_line} <br/> % else: - ${filename} ${lineno} ${line} ${template_filename} ${template_ln}<br/> + ${filename} ${lineno} ${line}<br/> % endif % endfor </body>
diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index 15b35bb..01a23a9 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py
@@ -6,7 +6,7 @@ """provides the Context class, the runtime namespace for templates.""" from mako import exceptions, util -import inspect +import inspect, sys class Context(object): """provides runtime namespace, output buffer, and various callstacks for templates.""" @@ -179,6 +179,7 @@ else: buf = util.StringIO() context = Context(buf, **data) + context._with_template = template kwargs = {} argspec = inspect.getargspec(callable_) namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None] @@ -186,10 +187,9 @@ if arg != 'context' and arg in data: kwargs[arg] = data[arg] _render_context(template, callable_, context, *args, **kwargs) - return buf.getvalue() + return context.pop_buffer().getvalue() def _render_context(template, callable_, context, *args, **kwargs): - context._with_template = template # create polymorphic 'self' namespace for this template with possibly updated context (inherit, lclcontext) = _populate_self_namespace(context, template) if callable_.__name__ == 'render': @@ -221,8 +221,9 @@ if not result: raise error else: - # TODO - friendly error formatting - source = _get_template_source(callable_) - raise error + context._buffer_stack = [util.StringIO()] + error_template = exceptions.html_error_template() + context._with_template = error_template + error_template.render_context(context, error=error) else: callable_(context, *args, **kwargs)
diff --git a/lib/mako/template.py b/lib/mako/template.py index 4313013..700a022 100644 --- a/lib/mako/template.py +++ b/lib/mako/template.py
@@ -12,31 +12,7 @@ from mako import runtime, util, exceptions import imp, time, weakref, tempfile, shutil, os, stat, posixpath, sys, re -_modules = weakref.WeakValueDictionary() -_inmemory_templates = weakref.WeakValueDictionary() -class _ModuleInfo(object): - def __init__(self, module, module_filename, template, template_filename, module_source, template_source): - self.module = module - self.module_filename = module_filename - self.template_filename = template_filename - self.module_source = module_source - self.template_source = template_source - _modules[module.__name__] = template._mmarker = self - if module_filename: - _modules[module_filename] = self - def _get_code(self): - if self.module_source is not None: - return self.module_source - else: - return file(self.module_filename).read() - code = property(_get_code) - def _get_source(self): - if self.template_source is not None: - return self.template_source - else: - return file(self.template_filename).read() - source = property(_get_source) class Template(object): """a compiled template""" @@ -61,10 +37,9 @@ if text is not None: (code, module) = _compile_text(text, self.identifier, filename) - _inmemory_templates[module.__name__] = self self._code = code self._source = text - _ModuleInfo(module, None, self, filename, code, text) + ModuleInfo(module, None, self, filename, code, text) elif filename is not None: if module_directory is not None: path = posixpath.join(module_directory, self.identifier + ".py") @@ -74,12 +49,12 @@ _compile_module_file(file(filename).read(), identifier, filename, path) module = imp.load_source(self.identifier, path, file(path)) del sys.modules[self.identifier] - _ModuleInfo(module, path, self, filename, None, None) + ModuleInfo(module, path, self, filename, None, None) else: (code, module) = _compile_text(file(filename).read(), self.identifier, filename) self._source = None self._code = code - _ModuleInfo(module, None, self, filename, code, None) + ModuleInfo(module, None, self, filename, code, None) else: raise exceptions.RuntimeException("Template requires text or filename") @@ -92,8 +67,8 @@ self.lookup = lookup self.output_encoding = output_encoding - source = property(lambda self:_get_template_source(self.callable_), doc="""return the template source code for this Template.""") - code = property(lambda self:_get_module_source_from_callable(self.callable_), doc="""return the module source code for this Template""") + source = property(lambda self:_get_module_info_from_callable(self.callable_).source, doc="""return the template source code for this Template.""") + code = property(lambda self:_get_module_info_from_callable(self.callable_).code, doc="""return the module source code for this Template""") def render(self, *args, **data): """render the output of this template as a string. @@ -113,6 +88,8 @@ """render this Template with the given context. the data is written to the context's buffer.""" + if getattr(context, '_with_template', None) is None: + context._with_template = self runtime._render_context(self, self.callable_, context, *args, **kwargs) def get_def(self, name): @@ -126,6 +103,32 @@ self.callable_ = callable_ def get_def(self, name): return self.parent.get_def(name) + +class ModuleInfo(object): + """stores information about a module currently loaded into memory.""" + _modules = weakref.WeakValueDictionary() + + def __init__(self, module, module_filename, template, template_filename, module_source, template_source): + self.module = module + self.module_filename = module_filename + self.template_filename = template_filename + self.module_source = module_source + self.template_source = template_source + self._modules[module.__name__] = template._mmarker = self + if module_filename: + self._modules[module_filename] = self + def _get_code(self): + if self.module_source is not None: + return self.module_source + else: + return file(self.module_filename).read() + code = property(_get_code) + def _get_source(self): + if self.template_source is not None: + return self.template_source + else: + return file(self.template_filename).read() + source = property(_get_source) def _compile_text(text, identifier, filename): node = Lexer(text, filename).parse() @@ -148,5 +151,5 @@ return _get_module_info(callable_.func_globals['__name__']) def _get_module_info(filename): - return _modules[filename] + return ModuleInfo._modules[filename]
diff --git a/test/lexer.py b/test/lexer.py index c4fd5f4..8b7fe67 100644 --- a/test/lexer.py +++ b/test/lexer.py
@@ -208,6 +208,13 @@ nodes = Lexer(template).parse() assert repr(nodes) == r"""TemplateNode({}, [Code("print 'hi %>' \n", False, (1, 1))])""" + template = r""" + <% + lines = src.split('\n') + %> +""" + nodes = Lexer(template).parse() + def test_control_lines(self): template = """ text text la la
diff --git a/test/template.py b/test/template.py index 6e1b48e..6c9c4ef 100644 --- a/test/template.py +++ b/test/template.py
@@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- from mako.template import Template -import unittest +import unittest, re from util import flatten_result, result_lines class EncodingTest(unittest.TestCase): @@ -41,6 +41,19 @@ y is ${y} """) assert t.render().strip() == "y is hi" + +class FormatExceptionTest(unittest.TestCase): + def test_html(self): + t = Template(""" + hi there. + <% + raise "hello" + %> + """, format_exceptions=True) + res = t.render() + print res + + if __name__ == '__main__': unittest.main()