traceback formatting
diff --git a/doc/build/genhtml.py b/doc/build/genhtml.py index 2e08c09..02975c2 100644 --- a/doc/build/genhtml.py +++ b/doc/build/genhtml.py
@@ -28,7 +28,7 @@ template_dirs = ['./templates', './output'] output = os.path.dirname(os.getcwd()) -lookup = TemplateLookup(template_dirs) +lookup = TemplateLookup(template_dirs, module_directory='./modules') def genfile(name, toc): infile = name + ".html" @@ -38,7 +38,12 @@ outfile.write(lookup.get_template(infile).render(toc=toc, extension='html')) for filename in files: - genfile(filename, root) + try: + genfile(filename, root) + except Exception, e: + import mako.exceptions + print mako.exceptions.get_error_template().render(error=e) + break
diff --git a/doc/build/templates/base.html b/doc/build/templates/base.html index c272b14..a0c4f6e 100644 --- a/doc/build/templates/base.html +++ b/doc/build/templates/base.html
@@ -4,10 +4,10 @@ # bootstrap TOC structure from request args, or pickled file if not present. import cPickle as pickle import os, time - print "base.myt generating from table of contents for file %s" % ("<todo: get the file>") + print "%s generating from table of contents for file %s" % (local.filename, self.filename) toc = context.get('toc') if toc is None: - filename = os.path.join(os.path.dirname(self.template.filename), 'table_of_contents.pickle') + filename = os.path.join(os.path.dirname(self.filename), 'table_of_contents.pickle') toc = pickle.load(file(filename)) version = toc.version last_updated = toc.last_updated @@ -23,8 +23,8 @@ <div class="docheader"> -<h1><% toc.root.doctitle %></h1> -<div class="">Version: <% version %> Last Updated: <% time.strftime('%x %X', time.localtime(last_updated)) %></div> +<h1>${toc.root.doctitle}</h1> +<div class="">Version: ${version} Last Updated: ${time.strftime('%x %X', time.localtime(last_updated))}</div> </div> ${next.body(toc=toc)}
diff --git a/doc/build/templates/nav.html b/doc/build/templates/nav.html index 82c8cf0..bbc5480 100644 --- a/doc/build/templates/nav.html +++ b/doc/build/templates/nav.html
@@ -55,11 +55,6 @@ <div class="topnavmain"> <div class="topnavheader">${ item.description }</div> <div class="topnavitems"> - % if tocns is UNDEFINED: - <% - raise "its undefined" - %> - % endif ${tocns.printtoc(root=item, current=None, full=True, anchor_toplevel=True)} </div> </div>
diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index a817a32..a0ec91d 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py
@@ -20,11 +20,8 @@ def render(self): buf = util.FastEncodingBuffer() printer = PythonPrinter(buf) - _GenerateRenderMethod(printer, self, self.node) - return buf.getvalue() - class _GenerateRenderMethod(object): def __init__(self, printer, compiler, node): @@ -87,12 +84,11 @@ module_identifiers.declared = module_ident # module-level names, python code - self.printer.writeline("from mako import runtime") + self.printer.writeline("from mako import runtime, filters") + self.printer.writeline("UNDEFINED = runtime.UNDEFINED") self.printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER)) self.printer.writeline("_modified_time = %s" % repr(time.time())) self.printer.writeline("_template_filename=%s" % repr(self.compiler.filename)) - self.printer.writeline("UNDEFINED = runtime.UNDEFINED") - self.printer.writeline("from mako import filters") main_identifiers = module_identifiers.branch(self.node) module_identifiers.topleveldefs = module_identifiers.topleveldefs.union(main_identifiers.topleveldefs) @@ -138,22 +134,22 @@ self.printer.write_indented_block(n.text) def write_inherit(self, node): - self.printer.writeline("def _inherit(context):") - self.printer.writeline("generate_namespaces(context)") + self.printer.writeline("def _mako_inherit(context):") + self.printer.writeline("_mako_generate_namespaces(context)") self.printer.writeline("return runtime.inherit_from(context, %s)" % (repr(node.attributes['file']))) self.printer.writeline(None) def write_namespaces(self, namespaces): self.printer.writelines( - "def get_namespace(context, name):", + "def _mako_get_namespace(context, name):", "try:", "return context.namespaces[(render, name)]", "except KeyError:", - "generate_namespaces(context)", + "_mako_generate_namespaces(context)", "return context.namespaces[(render, name)]", None,None ) - self.printer.writeline("def generate_namespaces(context):") + self.printer.writeline("def _mako_generate_namespaces(context):") for node in namespaces.values(): self.write_source_comment(node) if len(node.nodes): @@ -221,7 +217,7 @@ else: self.write_inline_def(comp, identifiers) elif ident in self.compiler.namespaces: - self.printer.writeline("%s = get_namespace(context, %s)" % (ident, repr(ident))) + self.printer.writeline("%s = _mako_get_namespace(context, %s)" % (ident, repr(ident))) else: if first is not None: self.printer.writeline("%s = %s.get(%s, context.get(%s, UNDEFINED))" % (ident, first, repr(ident), repr(ident))) @@ -230,7 +226,7 @@ def write_source_comment(self, node): if self.last_source_line != node.lineno: - self.printer.writeline("# SOURCE LINE %d" % node.lineno, is_comment=True) + self.printer.writeline("# SOURCE LINE %d" % node.lineno) self.last_source_line = node.lineno def write_def_decl(self, node, identifiers): @@ -331,7 +327,6 @@ if not self.in_def and len(self.identifiers.locally_assigned) > 0: # if we are the "template" def, fudge locally declared/modified variables into the "__locals" dictionary, # which is used for def calls within the same template, to simulate "enclosing scope" - #self.printer.writeline('__locals.update(%s)' % (",".join(["%s=%s" % (x, x) for x in node.declared_identifiers()]))) self.printer.writeline('__locals.update(dict([(k, v) for k, v in locals().iteritems() if k in [%s]]))' % ','.join([repr(x) for x in node.declared_identifiers()])) def visitIncludeTag(self, node):
diff --git a/lib/mako/exceptions.py b/lib/mako/exceptions.py index b15f737..c9640e6 100644 --- a/lib/mako/exceptions.py +++ b/lib/mako/exceptions.py
@@ -6,6 +6,8 @@ """exception classes""" +import traceback, sys, re + class MakoException(Exception): pass @@ -32,4 +34,73 @@ self.filename = filename class TemplateLookupException(MakoException): - pass \ No newline at end of file + pass + +def rich_traceback(): + """format a traceback from sys.exc_info() into 7-item tuples, containing + the regular four traceback tuple items, plus the original template + filename, the line number adjusted relative to the template source, and + code line from that line number of the template.""" + import mako.template + mods = {} + (type, value, trcback) = sys.exc_info() + rawrecords = traceback.extract_tb(trcback) + # this line extends the stack all the way back....shouldnt be needed... + # rawrecords = traceback.extract_stack() + rawrecords + new_trcback = [] + for filename, lineno, function, line in rawrecords: + try: + (line_map, template_lines) = mods[filename] + except KeyError: + try: + info = mako.template._get_module_info(filename) + module_source = info.code + template_source = info.source + template_filename = info.template_filename + except KeyError: + new_trcback.append((filename, lineno, function, line, None, None, None)) + continue + + template_ln = module_ln = 1 + line_map = {} + for line in module_source.split("\n"): + match = re.match(r'\s*# SOURCE LINE (\d+)', line) + if match: + template_ln = int(match.group(1)) + else: + template_ln += 1 + module_ln += 1 + line_map[module_ln] = template_ln + template_lines = [line for line in template_source.split("\n")] + mods[filename] = (line_map, template_lines) + + template_ln = line_map[lineno] + template_line = template_lines[template_ln - 1] + + new_trcback.append((filename, lineno, function, line, template_filename, template_ln, template_line)) + return (type, value, new_trcback) + +# TODO: this is scratch, make a module for exception reporting templates +def get_error_template(): + import mako.template + return mako.template.Template(""" +<%! + from mako.exceptions import rich_traceback +%> +<html> +<body> + Error ! +<% + (type, value, trcback) = rich_traceback() +%> + +${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} <br/> + % endif +% endfor +</body> +</html> +""") \ No newline at end of file
diff --git a/lib/mako/lookup.py b/lib/mako/lookup.py index c3bddb1..6babbc7 100644 --- a/lib/mako/lookup.py +++ b/lib/mako/lookup.py
@@ -4,7 +4,7 @@ # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -import os, stat, posixpath, re +import os, stat, posixpath from mako import exceptions, util from mako.template import Template @@ -24,7 +24,7 @@ raise NotImplementedError() class TemplateLookup(TemplateCollection): - def __init__(self, directories=None, module_directory=None, filesystem_checks=False, collection_size=60, format_exceptions=False, error_handler=None, output_encoding=None): + def __init__(self, directories=None, module_directory=None, filesystem_checks=False, collection_size=-1, format_exceptions=False, error_handler=None, output_encoding=None): self.directories = directories or [] self.module_directory = module_directory self.filesystem_checks = filesystem_checks @@ -50,9 +50,6 @@ else: raise exceptions.TemplateLookupException("Cant locate template for uri '%s'" % uri) - def __ident_from_uri(self, uri): - return re.sub(r"\W", "_", uri) - def __load(self, filename, uri): self._mutex.acquire() try: @@ -62,7 +59,7 @@ except KeyError: pass try: - self.__collection[uri] = Template(identifier=self.__ident_from_uri(uri), description=uri, filename=filename, lookup=self, **self.template_args) + self.__collection[uri] = Template(identifier=uri, description=uri, filename=filename, lookup=self, **self.template_args) return self.__collection[uri] except: self.__collection.pop(uri, None)
diff --git a/lib/mako/pygen.py b/lib/mako/pygen.py index 310d1f3..24b1655 100644 --- a/lib/mako/pygen.py +++ b/lib/mako/pygen.py
@@ -38,7 +38,7 @@ self.stream.write(text) def write_indented_block(self, block): - """print a line or lines of python which already contains indentation. + """print a line or lines of python which already contain indentation. The indentation of the total block of lines will be adjusted to that of the current indent level.""" @@ -47,10 +47,11 @@ self.line_buffer.append(l) def writelines(self, *lines): + """print a series of lines of python.""" for line in lines: self.writeline(line) - def writeline(self, line, is_comment=False): + def writeline(self, line): """print a line of python, indenting it according to the current indent level. this also adjusts the indentation counter according to the content of the line.""" @@ -69,6 +70,8 @@ else: hastext = True + is_comment = line and len(line) and line[0] == '#' + # see if this line should decrease the indentation level if (not decreased_indent and not is_comment and
diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index bf01269..15b35bb 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py
@@ -9,14 +9,14 @@ import inspect class Context(object): - """provides runtime namespace and output buffer for templates.""" + """provides runtime namespace, output buffer, and various callstacks for templates.""" def __init__(self, buffer, **data): self._buffer_stack = [buffer] self._data = data self._with_template = None self.namespaces = {} - # "capture" function to buffer def calls + # "capture" function which proxies to the generic "capture" function data['capture'] = lambda x, *args, **kwargs: capture(self, x, *args, **kwargs) # "caller" stack used by def calls with content @@ -30,8 +30,10 @@ def _put(self, key, value): self._data[key] = value def push_buffer(self): + """push a capturing buffer onto this Context.""" self._buffer_stack.append(util.FastEncodingBuffer()) def pop_buffer(self): + """pop the most recent capturing buffer from this Context.""" return self._buffer_stack.pop() def get(self, key, default=None): return self._data.get(key, default) @@ -52,6 +54,7 @@ c._data.update(d) return c def clean_inheritance_tokens(self): + """create a new copy of this Context with tokens related to inheritance state removed.""" c = self._copy() x = c._data x.pop('self', None) @@ -66,28 +69,21 @@ return getattr(self.target[-1], key) class Undefined(object): - """represtents undefined values""" + """represents an undefined value in a template.""" def __str__(self): raise NameError("Undefined") UNDEFINED = Undefined() - -class _AttrGateway(object): - def __init__(self,ns): - self.ns = ns - def __getattr__(self, key): - return getattr(self.ns.template.module, key) - + class Namespace(object): """provides access to collections of rendering methods, which can be local, from other templates, or from imported modules""" def __init__(self, name, context, module=None, template=None, templateuri=None, callables=None, inherits=None, populate_self=True): self.name = name - self.module = module + self._module = module if templateuri is not None: self.template = _lookup_template(context, templateuri) else: self.template = template - self.attributes = _AttrGateway(self) self.context = context self.inherits = inherits if callables is not None: @@ -96,7 +92,10 @@ self.callables = None if populate_self and self.template is not None: (lclcallable, self.context) = _populate_self_namespace(context, self.template, self_ns=self) - + + module = property(lambda s:s._module or s.template.module) + filename = property(lambda s:s._module and s._module.__file__ or s.template.filename) + def __getattr__(self, key): if self.callables is not None: try: @@ -113,9 +112,9 @@ callable_ = None if callable_ is not None: return lambda *args, **kwargs:callable_(self.context, *args, **kwargs) - if self.module is not None: + if self._module is not None: try: - callable_ = getattr(self.module, key) + callable_ = getattr(self._module, key) return lambda *args, **kwargs:callable_(self.context, *args, **kwargs) except AttributeError: pass @@ -124,6 +123,7 @@ raise exceptions.RuntimeException("Namespace '%s' has no member '%s'" % (self.name, key)) def capture(context, callable_, *args, **kwargs): + """execute the given template def, capturing the output into a buffer.""" context.push_buffer() try: callable_(*args, **kwargs) @@ -132,11 +132,14 @@ return buf.getvalue() def include_file(context, uri, import_symbols): + """locate the template from the given uri and include it in the current output.""" template = _lookup_template(context, uri) (callable_, ctx) = _populate_self_namespace(context.clean_inheritance_tokens(), template) callable_(ctx) def inherit_from(context, uri): + """called by the _inherit method in template modules to set up the inheritance chain at the start + of a template's execution.""" template = _lookup_template(context, uri) self_ns = context['self'] ih = self_ns @@ -144,12 +147,12 @@ ih = ih.inherits lclcontext = context.locals_({'next':ih}) ih.inherits = Namespace("self:%s" % template.description, lclcontext, template = template, populate_self=False) - context._data['parent'] = ih.inherits - callable_ = getattr(template.module, '_inherit', None) + context._data['parent'] = lclcontext._data['local'] = ih.inherits + callable_ = getattr(template.module, '_mako_inherit', None) if callable_ is not None: return callable_(lclcontext) else: - gen_ns = getattr(template.module, 'generate_namespaces', None) + gen_ns = getattr(template.module, '_mako_generate_namespaces', None) if gen_ns is not None: gen_ns(context) return (template.callable_, lclcontext) @@ -161,14 +164,14 @@ def _populate_self_namespace(context, template, self_ns=None): if self_ns is None: self_ns = Namespace('self:%s' % template.description, context, template=template, populate_self=False) - context._data['self'] = self_ns - if hasattr(template.module, '_inherit'): - return template.module._inherit(context) + context._data['self'] = context._data['local'] = self_ns + if hasattr(template.module, '_mako_inherit'): + return template.module._mako_inherit(context) else: return (template.callable_, context) def _render(template, callable_, args, data, as_unicode=False): - """given a Template and a callable_ from that template, create a Context and return the string output.""" + """create a Context and return the string output of the given template and template callable.""" if as_unicode: buf = util.FastEncodingBuffer() elif template.output_encoding: @@ -218,7 +221,7 @@ if not result: raise error else: - # TODO + # TODO - friendly error formatting source = _get_template_source(callable_) raise error else:
diff --git a/lib/mako/template.py b/lib/mako/template.py index 1787c01..4313013 100644 --- a/lib/mako/template.py +++ b/lib/mako/template.py
@@ -10,50 +10,76 @@ from mako.lexer import Lexer from mako.codegen import Compiler from mako import runtime, util, exceptions -import imp, time, weakref, tempfile, shutil, os, stat, posixpath, sys +import imp, time, weakref, tempfile, shutil, os, stat, posixpath, sys, re _modules = weakref.WeakValueDictionary() _inmemory_templates = weakref.WeakValueDictionary() -class _ModuleMarker(object): - """enables weak-referencing to module instances""" - def __init__(self, module): +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""" - def __init__(self, text=None,identifier=None, description=None, filename=None, format_exceptions=False, error_handler=None, lookup=None, output_encoding=None, module_directory=None): + def __init__(self, text=None, identifier=None, description=None, filename=None, format_exceptions=False, error_handler=None, lookup=None, output_encoding=None, module_directory=None): """construct a new Template instance using either literal template text, or a previously loaded template module text - textual template source, or None if a module is to be provided - identifier - the "id" of this template. defaults to the identifier of the given module, or for text - the hex string of this Template's object id + identifier - the "id" of this template. defaults to the + full filename given, or "memory:(hex id of this Template)" if no filename - filename - filename of the source template, compiled into the module. + filename - filename of the source template, if any - format_exceptions - if caught exceptions should be formatted as template output, including a stack - trace adjusted to the source template + format_exceptions - catch exceptions and format them into an error display template """ - self.identifier = identifier or "memory:" + hex(id(self)) + if identifier: + self.identifier = re.sub(r'\W', "_", identifier) + elif filename: + self.identifier = re.sub(r'\W', "_", filename) + else: + self.identifier = "memory:" + hex(id(self)) + 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) elif filename is not None: if module_directory is not None: - path = posixpath.join(module_directory, identifier + ".py") + path = posixpath.join(module_directory, self.identifier + ".py") filemtime = os.stat(filename)[stat.ST_MTIME] if not os.access(path, os.F_OK) or os.stat(path)[stat.ST_MTIME] < filemtime: util.verify_directory(module_directory) _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) else: (code, module) = _compile_text(file(filename).read(), self.identifier, filename) self._source = None self._code = code + _ModuleInfo(module, None, self, filename, code, None) else: raise exceptions.RuntimeException("Template requires text or filename") @@ -65,10 +91,9 @@ self.error_handler = error_handler self.lookup = lookup self.output_encoding = output_encoding - _modules[module.__name__] = _ModuleMarker(module) 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(self.callable_), doc="""return the module 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""") def render(self, *args, **data): """render the output of this template as a string. @@ -105,13 +130,9 @@ def _compile_text(text, identifier, filename): node = Lexer(text, filename).parse() source = Compiler(node, filename).render() -# if filename is not None: -# file(filename + ".py", "w").write(source) -# else: -# print source cid = identifier module = imp.new_module(cid) - code = compile(source, cid + " " + str(filename), 'exec') + code = compile(source, cid, 'exec') exec code in module.__dict__, module.__dict__ return (source, module) @@ -123,34 +144,9 @@ os.close(dest) shutil.move(name, outputpath) -def _get_template_source(callable_): - """return the source code for the template that produced the given rendering callable""" - name = callable_.func_globals['__name__'] - try: - template = _inmemory_templates[name] - if template._source is not None: - return template._source - except KeyError: - pass - module = _modules[name].module - filename = module._template_filename - if filename is None: - if not filename: - raise exceptions.RuntimeException("Cant get source code or template filename for template: %s" % name) - return file(filename).read() - -def _get_module_source(callable_): - name = callable_.func_globals['__name__'] - try: - template = _inmemory_templates[name] - if template._code is not None: - return template._code - except KeyError: - pass - module = _modules[name].module - filename = module.__file__ - if filename is None: - if not filename: - raise exceptions.RuntimeException("Cant get module source code or module filename for template: %s" % name) - return file(filename).read() - +def _get_module_info_from_callable(callable_): + return _get_module_info(callable_.func_globals['__name__']) + +def _get_module_info(filename): + return _modules[filename] +