Ticket #603: django-template-errors.patch
File django-template-errors.patch, 10.6 KB (added by , 19 years ago) |
---|
-
template_loader.py
1 1 "Wrapper for loading templates from storage of some sort (e.g. files or db)" 2 2 import template 3 from template_file import load_template_source3 from template_file import find_template_source 4 4 5 5 class ExtendsError(Exception): 6 6 pass … … 10 10 Returns a compiled template.Template object for the given template name, 11 11 handling template inheritance recursively. 12 12 """ 13 return get_template_from_string( load_template_source(template_name))13 return get_template_from_string(*find_template_source(template_name)) 14 14 15 def get_template_from_string(source ):15 def get_template_from_string(source, filename=template.UNKNOWN_SOURCE): 16 16 """ 17 17 Returns a compiled template.Template object for the given template code, 18 18 handling template inheritance recursively. 19 19 """ 20 return template.Template(source )20 return template.Template(source, filename) 21 21 22 22 def render_to_string(template_name, dictionary=None, context_instance=None): 23 23 """ … … 90 90 error_msg += " Got this from the %r variable." % self.parent_name_var 91 91 raise template.TemplateSyntaxError, error_msg 92 92 try: 93 return get_template_from_string( load_template_source(parent, self.template_dirs))93 return get_template_from_string(*find_template_source(parent, self.template_dirs)) 94 94 except template.TemplateDoesNotExist: 95 95 raise template.TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent 96 96 … … 142 142 143 143 This tag may be used in two ways: ``{% extends "base" %}`` (with quotes) 144 144 uses the literal value "base" as the name of the parent template to extend, 145 or ``{% e ntends variable %}`` uses the value of ``variable`` as the name145 or ``{% extends variable %}`` uses the value of ``variable`` as the name 146 146 of the parent template to extend. 147 147 """ 148 148 bits = token.contents.split() -
template_file.py
4 4 from django.core.template import TemplateDoesNotExist 5 5 import os 6 6 7 def load_template_source(template_name, template_dirs=None): 7 8 def find_template_source(template_name, template_dirs=None): 9 "Returns a tuple of (template_string, filepath)." 8 10 if not template_dirs: 9 11 template_dirs = TEMPLATE_DIRS 10 12 tried = [] 11 13 for template_dir in template_dirs: 12 14 filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION 13 15 try: 14 return open(filepath).read()16 return (open(filepath).read(), filepath) 15 17 except IOError: 16 18 tried.append(filepath) 17 19 if template_dirs: … … 19 21 else: 20 22 error_msg = "Your TEMPLATE_DIRS settings is empty. Change it to point to at least one template directory." 21 23 raise TemplateDoesNotExist, error_msg 24 25 26 def load_template_source(template_name, template_dirs=None): 27 return find_template_source(template_name, template_dirs)[0] -
template.py
74 74 75 75 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.' 76 76 77 #What to report as the origin of templates that come from non file sources (eg strings) 78 UNKNOWN_SOURCE="<unknown source>" 79 80 81 #match starts of lines 82 newline_re = re.compile("^", re.M); 83 77 84 # match a variable or block tag and capture the entire tag, including start/end delimiters 78 85 tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 79 86 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) 80 87 88 81 89 # global dict used by register_tag; maps custom tags to callback functions 82 90 registered_tags = {} 83 91 … … 102 110 pass 103 111 104 112 class Template: 105 def __init__(self, template_string ):113 def __init__(self, template_string, filename=UNKNOWN_SOURCE): 106 114 "Compilation stage" 107 self.nodelist = compile_string(template_string )108 115 self.nodelist = compile_string(template_string, filename) 116 109 117 def __iter__(self): 110 118 for node in self.nodelist: 111 119 for subnode in node: … … 115 123 "Display stage -- can be called many times" 116 124 return self.nodelist.render(context) 117 125 118 def compile_string(template_string ):126 def compile_string(template_string, filename): 119 127 "Compiles template_string into NodeList ready for rendering" 120 tokens = tokenize(template_string )128 tokens = tokenize(template_string, filename) 121 129 parser = Parser(tokens) 122 130 return parser.parse() 123 131 … … 168 176 self.dicts = [other_dict] + self.dicts 169 177 170 178 class Token: 171 def __init__(self, token_type, contents ):179 def __init__(self, token_type, contents, source): 172 180 "The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK" 173 181 self.token_type, self.contents = token_type, contents 182 self.source = source 174 183 175 184 def __str__(self): 176 return '<%s token: "%s..." >' % (185 return '<%s token: "%s..." from %s, line %d>' % ( 177 186 {TOKEN_TEXT:'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type], 178 self.contents[:20].replace('\n', '') 187 self.contents[:20].replace('\n', ''), 188 self.source[0], self.source[1] 179 189 ) 180 190 181 def tokenize(template_string): 191 def find_linebreaks(template_string): 192 for match in newline_re.finditer(template_string): 193 yield match.start() 194 195 def tokenize(template_string, filename): 182 196 "Return a list of tokens from a given template_string" 183 # remove all empty strings, because the regex has a tendency to add them 184 bits = filter(None, tag_re.split(template_string)) 185 return map(create_token, bits) 197 198 token_tups = [] 199 upto = 0 200 line = 1 201 #TODO:Py2.4 generator expression 202 linebreaks = find_linebreaks(template_string) 203 next_linebreak = linebreaks.next() 204 try: 205 for match in tag_re.finditer(template_string): 206 start, end = match.span() 207 if start > upto: 208 token_tups.append( (template_string[upto:start], line) ) 209 upto = start 210 211 while next_linebreak <= upto: 212 next_linebreak = linebreaks.next() 213 line += 1 214 215 token_tups.append( (template_string[start:end], line) ) 216 upto = end 217 218 while next_linebreak <= upto: 219 next_linebreak = linebreaks.next() 220 line += 1 221 except StopIteration: 222 pass 223 224 last_bit = template_string[upto:] 225 if len(last_bit): 226 token_tups.append( (last_bit, line) ) 227 228 return [ create_token(tok, (filename, line)) for tok, line in token_tups] 186 229 187 def create_token(token_string ):230 def create_token(token_string, source): 188 231 "Convert the given token string into a new Token object and return it" 189 232 if token_string.startswith(VARIABLE_TAG_START): 190 return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip() )233 return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip(), source) 191 234 elif token_string.startswith(BLOCK_TAG_START): 192 return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip() )235 return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip(), source) 193 236 else: 194 return Token(TOKEN_TEXT, token_string )237 return Token(TOKEN_TEXT, token_string, source) 195 238 239 196 240 class Parser: 197 241 def __init__(self, tokens): 198 242 self.tokens = tokens 243 self.command_stack = [] 199 244 200 245 def parse(self, parse_until=[]): 201 246 nodelist = NodeList() 202 247 while self.tokens: 203 248 token = self.next_token() 204 249 if token.token_type == TOKEN_TEXT: 205 nodelist.append(TextNode(token.contents) )250 nodelist.append(TextNode(token.contents), token) 206 251 elif token.token_type == TOKEN_VAR: 207 252 if not token.contents: 208 raise TemplateSyntaxError, "Empty variable tag "209 nodelist.append(VariableNode(token.contents) )253 raise TemplateSyntaxError, "Empty variable tag at %s, line %d" % (token.source[0], token.source[1]) 254 nodelist.append(VariableNode(token.contents), token) 210 255 elif token.token_type == TOKEN_BLOCK: 211 256 if token.contents in parse_until: 212 257 # put token back on token list so calling code knows why it terminated … … 218 263 raise TemplateSyntaxError, "Empty block tag" 219 264 try: 220 265 # execute callback function for this tag and append resulting node 221 nodelist.append(registered_tags[command](self, token)) 266 self.command_stack.append( (command, token.source) ) 267 nodelist.append(registered_tags[command](self, token), token) 268 self.command_stack.pop() 222 269 except KeyError: 223 raise TemplateSyntaxError, "Invalid block tag: '%s' " % command270 raise TemplateSyntaxError, "Invalid block tag: '%s' at %s, line %d" % (command, token.source[0], token.source[1]) 224 271 if parse_until: 225 raise TemplateSyntaxError, "Unclosed tag(s): '%s'" % ', '.join(parse_until) 272 (command, (file,line)) = self.command_stack.pop() 273 msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \ 274 (command, file, line, ', '.join(parse_until) ) 275 raise TemplateSyntaxError, msg 226 276 return nodelist 227 277 228 278 def next_token(self): … … 434 484 if hasattr(self, 'nodelist'): 435 485 nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) 436 486 return nodes 487 437 488 438 489 class NodeList(list): 439 490 def render(self, context): … … 451 502 for node in self: 452 503 nodes.extend(node.get_nodes_by_type(nodetype)) 453 504 return nodes 505 506 def append(self, node, token = None): 507 if token: 508 node.source = token.source 509 super(NodeList, self).append(node) 454 510 455 511 class TextNode(Node): 456 512 def __init__(self, s):