# -*- coding: utf-8 -*- """KidWriter Write Python source code from XML. """ __revision__ = "$Rev: 496 $" __date__ = "$Date: 2007-07-15 18:14:35 -0400 (Sun, 15 Jul 2007) $" __author__ = "Ryan Tomayko (rtomayko@gmail.com)" __copyright__ = "Copyright 2004-2005, Ryan Tomayko" __license__ = "MIT " import sys, re from os.path import splitext from traceback import extract_tb, format_exception_only from kid import __version__, Namespace from kid.parser import document, START, END, TEXT, XML_DECL, DOCTYPE, LOCATION from kid.element import namespaces, Comment, ProcessingInstruction __all__ = ['KID_XMLNS', 'KID_PREFIX', 'kidns', 'raise_template_error'] # the Kid XML namespace KID_XMLNS = "http://purl.org/kid/ns#" KID_PREFIX = 'py' kidns = Namespace(KID_XMLNS) QNAME_FOR = kidns['for'] QNAME_IF = kidns['if'] QNAME_DEF = kidns['def'] QNAME_SLOT = kidns['slot'] QNAME_CONTENT = kidns['content'] QNAME_REPLACE = kidns['replace'] QNAME_MATCH = kidns['match'] QNAME_STRIP = kidns['strip'] QNAME_ATTRIBUTES = kidns['attrs'] QNAME_EXTENDS = kidns['extends'] QNAME_LAYOUT = kidns['layout'] # deprectaed QNAME_OMIT = kidns['omit'] QNAME_REPEAT = kidns['repeat'] # the Kid processing instruction name KID_PI = 'python' KID_ALT_PI = 'py' KID_OLD_PI = 'kid' def parse(source, encoding=None, filename=None, entity_map=None): doc = document(source, encoding=encoding, filename=filename, entity_map=entity_map) return KidWriter(doc, encoding, filename).parse() def parse_file(filename, encoding=None, entity_map=None): """Parse the file specified. filename -- the name of a file. fp -- an optional file like object to read from. If not specified, filename is opened. """ source = open(filename, 'rb') try: return parse(source, encoding, filename, entity_map) finally: source.close() def error_location(filename, encoding=None, entity_map=None, lineno=None): if lineno: try: source = open(filename, 'rb') try: doc = document(source, encoding=encoding, filename=filename, entity_map=entity_map, debug=True) writer = KidWriter(doc, encoding, filename, lineno) return writer.parse() finally: source.close() except Exception: pass def TemplateExceptionError(error, add_message): """Get exception with additional error message.""" Error = error.__class__ class TemplateExceptionError(Error): def __init__(self): for arg in dir(error): if not arg.startswith('_'): setattr(self, arg, getattr(error, arg)) def __str__(self): return str(error) + '\n' + add_message TemplateExceptionError.__name__ = Error.__name__ TemplateExceptionError.__module__ = Error.__module__ return TemplateExceptionError() def raise_template_error(module=None, filename=None, encoding=None): """Raise template error along with additional context information. If the module containing the erroneous code has been compiled from a Kid template, try to compile that template with additional debug information, and display the location in the Kid template file corresponding to the erroneous code. """ if module and not (filename and encoding): if module in sys.modules: mod = sys.modules[module] if hasattr(mod, 'encoding'): encoding = mod.encoding if hasattr(mod, 'kid_file'): filename = mod.kid_file if not filename or filename == '': raise if not encoding: encoding = 'utf-8' py_file = splitext(filename)[0] + '.py' exc_type, exc_value = sys.exc_info()[:2] if exc_type == SyntaxError: tb = [(py_file, exc_value.lineno)] else: tb = extract_tb(sys.exc_info()[2]) tb.reverse() for t in tb: if py_file != t[0] or not t[1]: continue location = error_location(filename, encoding, lineno=t[1]) if not location: continue (start_line, start_col), (end_line, end_col) = location if start_line > end_line: continue s = [] if not end_col and end_line > start_line: end_line -= 1 end_col = -1 if start_line == end_line: s.append('on line %d' % start_line) if start_col == end_col: s.append(', column %d' % start_col) elif start_col: if end_col > start_col: s.append(' between columns %d and %d' % (start_col, end_col)) else: s.append(' after column %d' % start_col) elif end_col > 0: s.append(' before column %d' % end_col) else: s.append('between line %d' % start_line) if start_col: s.append(', column %d' % start_col) s.append(' and line %d' % end_line) if end_col > 0: s.append(', column %d' % end_col) if s: s = ''.join(s) try: start_line -= 1 end_line -= 1 error_line, error_text = [], [] for line, text in enumerate(open(filename)): if line < start_line: continue text = text.rstrip() if text: if line == start_line and start_col: if text[:start_col].rstrip(): text = text[start_col:].lstrip() if text: text = '... ' + text if line == end_line and end_col > 0: if text[end_col:].lstrip(): if end_col > 75: end_col = 75 text = text[:end_col].rstrip() if text: text += ' ...' else: text = text[:end_col].rstrip() if len(text) > 79: text = text[:75].rstrip() + ' ...' if text: if len(error_line) < 3: error_line.append(line) error_text.append(text) else: error_line[2] = line error_text[2] = text if line >= end_line: break if not error_line: raise LookupError, 'error line not found' if len(error_line) == 2: if error_line[1] - error_line[0] > 1: error_text.insert(1, '...') elif len(error_line) == 3: if error_line[2] - error_line[0] > 2: error_text[1] = '...' s = [s + ':'] + error_text except Exception, e: s = [s, '(cannot acquire source text: %s)' % str(e)] s.insert(0, 'Error location in template file %r' % filename) break else: s = ['Error in code generated from template file %r' % filename] s = ''.join(format_exception_only(exc_type, exc_value)[:-1]) + '\n'.join(s) if isinstance(exc_type, str): exc_type += '\n' + s else: exc_value = TemplateExceptionError(exc_value, s) exc_type = exc_value.__class__ raise exc_type, exc_value, sys.exc_info()[2] class KidWriter(object): def __init__(self, stream, encoding=None, filename=None, lineno=None): self.stream = stream self.encoding = encoding or 'utf-8' self.filename = filename self.depth = 0 self.lineno = lineno self.location = None self.locations = [] self.module_code = self.codegen() self.class_code = self.codegen() self.expand_code = self.codegen(level=1) self.end_module_code = self.codegen() self.module_defs = [] self.inst_defs = [] def codegen(self, code=None, level=0, tab='\t'): if self.lineno: return LocationGenerator(code, self.getloc) else: return CodeGenerator(code, level, tab) def getloc(self): return self.location def parse(self): self.begin() self.proc_stream(self.module_code) self.end() parts = [] parts += self.module_code.code for c in self.module_defs: parts += c.code parts += self.class_code.code parts += self.expand_code.code for c in self.inst_defs: parts += c.code parts += self.end_module_code.code if self.lineno: lineno = self.lineno - 1 if not 0 <= lineno < len(parts): return None pos = parts[lineno] if not pos: return None pos, is_start = pos if not 0 <= pos < len(self.locations): return None start_loc = self.locations[pos] pos += is_start and 1 or -1 if 0 <= pos < len(self.locations): end_loc = self.locations[pos] else: end_line = start_loc[0] if is_start: end_line += 1 end_loc = (end_line, 0) if not is_start: start_loc, end_loc = end_loc, start_loc return start_loc, end_loc return '\n'.join(parts) def begin(self): code = self.module_code # Start with PEP 0263 encoding declaration code.line('# -*- coding: %s -*-' % self.encoding, '# Kid template module', # version against which the file has been compiled 'kid_version = %r' % __version__, # source from which the file has been compiled 'kid_file = %r' % self.filename, # imports 'import kid', 'from kid.template_util import *', 'import kid.template_util as template_util', '_def_names = []', # default variables (can be overridden by template) 'encoding = "%s"' % self.encoding, 'doctype = None', 'omit_namespaces = [kid.KID_XMLNS]', 'layout_params = {}', # module methods 'def pull(**kw): return Template(**kw).pull()', "def generate(encoding=encoding, fragment=False," " output=None, format=None, **kw):" " return Template(**kw).generate(encoding=encoding," " fragment=fragment, output=output, format=format)", "def serialize(encoding=encoding, fragment=False," " output=None, format=None, **kw):" " return Template(**kw).serialize(encoding=encoding," " fragment=fragment, output=output, format=format)", "def write(file, encoding=encoding, fragment=False," " output=None, format=None, **kw):" " return Template(**kw).write(file, encoding=encoding," " fragment=fragment, output=output, format=format)", 'def initialize(template): pass', 'BaseTemplate = kid.BaseTemplate') # expand code code = self.expand_code code.start_block('def initialize(self):') code.line('rslt = initialize(self)', 'if rslt != 0: super(Template, self).initialize()') code.end_block() code.start_block('def _pull(self):') # XXX hack: nasty kluge for making kwargs locals code.line("exec template_util.get_locals(self, locals())", 'current, ancestors = None, []', 'if doctype: yield DOCTYPE, doctype') code = self.end_module_code code.line('') def end(self): self.expand_code.end_block() def proc_stream(self, code): for ev, item in self.stream: if ev == START: if item.tag == Comment: text = item.text.strip() if text.startswith('!'): continue # swallow comment if code is self.module_code: line = self.expand_code.line else: line = code.line if text.startswith('[') or text.startswith('', 'exec') # if it works, line does not start new block except SyntaxError: # unexpected EOF while parsing? try: # try to compile the whole block block = '\n'.join(lines) + '\n' compile(block, '', 'exec') # if it works, line does not start new block except IndentationError: # expected an indented block? # so try to add some indentation: lines2 = lines[:1] + [tab + line for line in lines[1:]] block = '\n'.join(lines2) + '\n' # try again to compile the whole block: compile(block, '', 'exec') lines = lines2 # if it works, keep the indentation except: pass # leave it as it is except: pass # leave it as it is return lines