templates.py (11839B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\werkzeug\templates.py 2 import sys 3 import re 4 import __builtin__ as builtins 5 from compiler import ast, parse 6 from compiler.pycodegen import ModuleCodeGenerator 7 from tokenize import PseudoToken 8 from werkzeug import utils, urls 9 from werkzeug._internal import _decode_unicode 10 token_re = re.compile('%s|%s(?s)' % ('[uU]?[rR]?("""|\\\'\\\'\\\')((?<!\\\\)\\\\\\1|.)*?\\1', PseudoToken)) 11 directive_re = re.compile('(?<!\\\\)<%(?:(#)|(py(?:thon)?\\b)|(?:\\s*(\\w+))\\s*)(.*?)\\s*%>\\n?(?s)') 12 escape_re = re.compile('\\\\\\n|\\\\(\\\\|<%)') 13 namestart_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' 14 undefined = type('UndefinedType', (object,), {'__iter__': lambda x: iter(()), 15 '__repr__': lambda x: 'Undefined', 16 '__str__': lambda x: ''})() 17 runtime_vars = dict.fromkeys(('Undefined', '__to_unicode', '__context', '__write', '__write_many')) 18 19 def call_stmt(func, args, lineno): 20 return ast.CallFunc(ast.Name(func, lineno=lineno), args, lineno=lineno) 21 22 23 def tokenize(source, filename): 24 escape = escape_re.sub 25 escape_repl = lambda m: m.group(1) or '' 26 lineno = 1 27 pos = 0 28 for match in directive_re.finditer(source): 29 start, end = match.span() 30 if start > pos: 31 data = source[pos:start] 32 yield (lineno, 'data', escape(escape_repl, data)) 33 lineno += data.count('\n') 34 is_comment, is_code, cmd, args = match.groups() 35 if is_code: 36 yield (lineno, 'code', args) 37 elif not is_comment: 38 yield (lineno, 'cmd', (cmd, args)) 39 lineno += source[start:end].count('\n') 40 pos = end 41 42 if pos < len(source): 43 yield (lineno, 'data', escape(escape_repl, source[pos:])) 44 45 46 def transform(node, filename): 47 root = ast.Module(None, node, lineno=1) 48 nodes = [root] 49 while nodes: 50 node = nodes.pop() 51 node.filename = filename 52 if node.__class__ in (ast.Printnl, ast.Print): 53 node.dest = ast.Name('__context') 54 elif node.__class__ is ast.Const and isinstance(node.value, str): 55 try: 56 node.value.decode('ascii') 57 except UnicodeError: 58 node.value = node.value.decode('utf-8') 59 60 nodes.extend(node.getChildNodes()) 61 62 return root 63 64 65 class TemplateSyntaxError(SyntaxError): 66 67 def __init__(self, msg, filename, lineno): 68 from linecache import getline 69 l = getline(filename, lineno) 70 SyntaxError.__init__(self, msg, (filename, 71 lineno, 72 len(l) or 1, 73 l)) 74 75 76 class Parser(object): 77 78 def __init__(self, gen, filename): 79 self.gen = gen 80 self.filename = filename 81 self.lineno = 1 82 83 def fail(self, msg): 84 raise TemplateSyntaxError(msg, self.filename, self.lineno) 85 86 def parse_python(self, expr, type = 'exec'): 87 if isinstance(expr, unicode): 88 expr = '\xef\xbb\xbf' + expr.encode('utf-8') 89 try: 90 node = parse(expr, type) 91 except SyntaxError as e: 92 raise TemplateSyntaxError(str(e), self.filename, self.lineno + e.lineno - 1) 93 94 nodes = [node] 95 while nodes: 96 n = nodes.pop() 97 if hasattr(n, 'lineno'): 98 n.lineno = (n.lineno or 1) + self.lineno - 1 99 nodes.extend(n.getChildNodes()) 100 101 return node.node 102 103 def parse(self, needle = ()): 104 start_lineno = self.lineno 105 result = [] 106 add = result.append 107 for self.lineno, token, value in self.gen: 108 if token == 'data': 109 add(self.parse_data(value)) 110 elif token == 'code': 111 add(self.parse_code(value.splitlines())) 112 elif token == 'cmd': 113 name, args = value 114 if name in needle: 115 return (name, args, ast.Stmt(result, lineno=start_lineno)) 116 if name in ('for', 'while'): 117 add(self.parse_loop(args, name)) 118 elif name == 'if': 119 add(self.parse_if(args)) 120 else: 121 self.fail('unknown directive %s' % name) 122 123 if needle: 124 self.fail('unexpected end of template') 125 return ast.Stmt(result, lineno=start_lineno) 126 127 def parse_loop(self, args, type): 128 rv = self.parse_python('%s %s: pass' % (type, args), 'exec').nodes[0] 129 tag, value, rv.body = self.parse(('end' + type, 'else')) 130 if value: 131 self.fail('unexpected data after ' + tag) 132 if tag == 'else': 133 tag, value, rv.else_ = self.parse(('end' + type,)) 134 if value: 135 self.fail('unexpected data after else') 136 return rv 137 138 def parse_if(self, args): 139 cond = self.parse_python('if %s: pass' % args).nodes[0] 140 tag, value, body = self.parse(('else', 'elif', 'endif')) 141 cond.tests[0] = (cond.tests[0][0], body) 142 while 1: 143 if tag == 'else': 144 if value: 145 self.fail('unexpected data after else') 146 tag, value, cond.else_ = self.parse(('endif',)) 147 elif tag == 'elif': 148 expr = self.parse_python(value, 'eval') 149 tag, value, body = self.parse(('else', 'elif', 'endif')) 150 cond.tests.append((expr, body)) 151 continue 152 break 153 154 if value: 155 self.fail('unexpected data after endif') 156 return cond 157 158 def parse_code(self, lines): 159 margin = sys.maxint 160 for line in lines[1:]: 161 content = len(line.lstrip()) 162 if content: 163 indent = len(line) - content 164 margin = min(margin, indent) 165 166 if lines: 167 lines[0] = lines[0].lstrip() 168 if margin < sys.maxint: 169 for i in xrange(1, len(lines)): 170 lines[i] = lines[i][margin:] 171 172 while lines and not lines[-1]: 173 lines.pop() 174 175 while lines and not lines[0]: 176 lines.pop(0) 177 178 return self.parse_python('\n'.join(lines)) 179 180 def parse_data(self, text): 181 start_lineno = lineno = self.lineno 182 pos = 0 183 end = len(text) 184 nodes = [] 185 186 def match_or_fail(pos): 187 match = token_re.match(text, pos) 188 if match is None: 189 self.fail('invalid syntax') 190 return (match.group().strip(), match.end()) 191 192 def write_expr(code): 193 node = self.parse_python(code, 'eval') 194 nodes.append(call_stmt('__to_unicode', [node], lineno)) 195 return code.count('\n') 196 197 def write_data(value): 198 if value: 199 nodes.append(ast.Const(value, lineno=lineno)) 200 return value.count('\n') 201 return 0 202 203 while 1: 204 offset = text.find('$', pos) 205 if offset < 0: 206 break 207 next = text[offset + 1] 208 if next == '{': 209 lineno += write_data(text[pos:offset]) 210 pos = offset + 2 211 level = 1 212 while level: 213 token, pos = match_or_fail(pos) 214 if token in ('{', '}'): 215 level += token == '{' and 1 or -1 216 217 lineno += write_expr(text[offset + 2:pos - 1]) 218 elif next in namestart_chars: 219 lineno += write_data(text[pos:offset]) 220 token, pos = match_or_fail(offset + 1) 221 while pos < end: 222 if text[pos] == '.' and pos + 1 < end and text[pos + 1] in namestart_chars: 223 token, pos = match_or_fail(pos + 1) 224 elif text[pos] in '([': 225 pos += 1 226 level = 1 227 while level: 228 token, pos = match_or_fail(pos) 229 if token in ('(', ')', '[', ']'): 230 level += token in '([' and 1 or -1 231 232 else: 233 break 234 235 lineno += write_expr(text[offset + 1:pos]) 236 else: 237 lineno += write_data(text[pos:offset + 1]) 238 pos = offset + 1 + (next == '$') 239 240 write_data(text[pos:]) 241 return ast.Discard(call_stmt(len(nodes) == 1 and '__write' or '__write_many', nodes, start_lineno), lineno=start_lineno) 242 243 244 class Context(object): 245 246 def __init__(self, namespace, charset, errors): 247 self.charset = charset 248 self.errors = errors 249 self._namespace = namespace 250 self._buffer = [] 251 self._write = self._buffer.append 252 _extend = self._buffer.extend 253 self.runtime = dict(Undefined=undefined, __to_unicode=self.to_unicode, __context=self, __write=self._write, __write_many=lambda *a: _extend(a)) 254 255 def write(self, value): 256 self._write(self.to_unicode(value)) 257 258 def to_unicode(self, value): 259 if isinstance(value, str): 260 return _decode_unicode(value, self.charset, self.errors) 261 return unicode(value) 262 263 def get_value(self, as_unicode = True): 264 rv = u''.join(self._buffer) 265 if not as_unicode: 266 return rv.encode(self.charset, self.errors) 267 return rv 268 269 def __getitem__(self, key, default = undefined): 270 try: 271 return self._namespace[key] 272 except KeyError: 273 return getattr(builtins, key, default) 274 275 def get(self, key, default = None): 276 return self.__getitem__(key, default) 277 278 def __setitem__(self, key, value): 279 self._namespace[key] = value 280 281 def __delitem__(self, key): 282 del self._namespace[key] 283 284 285 class TemplateCodeGenerator(ModuleCodeGenerator): 286 287 def __init__(self, node, filename): 288 ModuleCodeGenerator.__init__(self, transform(node, filename)) 289 290 def _nameOp(self, prefix, name): 291 if name in runtime_vars: 292 return self.emit(prefix + '_GLOBAL', name) 293 return ModuleCodeGenerator._nameOp(self, prefix, name) 294 295 296 class Template(object): 297 default_context = {'escape': utils.escape, 298 'url_quote': urls.url_quote, 299 'url_quote_plus': urls.url_quote_plus, 300 'url_encode': urls.url_encode} 301 302 def __init__(self, source, filename = '<template>', charset = 'utf-8', errors = 'strict', unicode_mode = True): 303 if isinstance(source, str): 304 source = _decode_unicode(source, charset, errors) 305 if isinstance(filename, unicode): 306 filename = filename.encode('utf-8') 307 node = Parser(tokenize(u'\n'.join(source.splitlines()), filename), filename).parse() 308 self.code = TemplateCodeGenerator(node, filename).getCode() 309 self.filename = filename 310 self.charset = charset 311 self.errors = errors 312 self.unicode_mode = unicode_mode 313 314 @classmethod 315 def from_file(cls, file, charset = 'utf-8', errors = 'strict', unicode_mode = True): 316 close = False 317 if isinstance(file, basestring): 318 f = open(file, 'r') 319 close = True 320 try: 321 data = _decode_unicode(f.read(), charset, errors) 322 finally: 323 if close: 324 f.close() 325 326 return cls(data, getattr(f, 'name', '<template>'), charset, errors, unicode_mode) 327 328 def render(self, *args, **kwargs): 329 ns = self.default_context.copy() 330 if len(args) == 1 and isinstance(args[0], utils.MultiDict): 331 ns.update(args[0].to_dict(flat=True)) 332 else: 333 ns.update(dict(*args)) 334 if kwargs: 335 ns.update(kwargs) 336 context = Context(ns, self.charset, self.errors) 337 exec self.code in context.runtime, context 338 return context.get_value(self.unicode_mode) 339 340 def substitute(self, *args, **kwargs): 341 return self.render(*args, **kwargs)