Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(204)

Side by Side Diff: third_party/handlebar/handlebar.py

Issue 61733018: Docserver: Update to the latest version of handlebar. Lots of re-syntaxifying. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: comments, rebase Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/handlebar/README.md ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2012 Benjamin Kalman 1 # Copyright 2012 Benjamin Kalman
2 # 2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); 3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License. 4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at 5 # You may obtain a copy of the License at
6 # 6 #
7 # http://www.apache.org/licenses/LICENSE-2.0 7 # http://www.apache.org/licenses/LICENSE-2.0
8 # 8 #
9 # Unless required by applicable law or agreed to in writing, software 9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, 10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and 12 # See the License for the specific language governing permissions and
13 # limitations under the License. 13 # limitations under the License.
14 14
15 # TODO: Some character other than {{{ }}} to print unescaped content? 15 # TODO: New name, not "handlebar".
16 # TODO: Only have @ while in a loop, and only defined in the top context of
17 # the loop.
18 # TODO: Consider trimming spaces around identifers like {{?t foo}}.
19 # TODO: Only transfer global contexts into partials, not the top local.
20 # TODO: Pragmas for asserting the presence of variables.
21 # TODO: Escaping control characters somehow. e.g. \{{, \{{-. 16 # TODO: Escaping control characters somehow. e.g. \{{, \{{-.
22 # TODO: Dump warnings-so-far into the output.
23 17
24 import json 18 import json
25 import re 19 import re
26 20
27 '''Handlebar templates are data binding templates more-than-loosely inspired by 21 '''Handlebar templates are data binding templates more-than-loosely inspired by
28 ctemplate. Use like: 22 ctemplate. Use like:
29 23
30 from handlebar import Handlebar 24 from handlebar import Handlebar
31 25
32 template = Handlebar('hello {{#foo}}{{bar}}{{/}} world') 26 template = Handlebar('hello {{#foo bar/}} world')
33 input = { 27 input = {
34 'foo': [ 28 'foo': [
35 { 'bar': 1 }, 29 { 'bar': 1 },
36 { 'bar': 2 }, 30 { 'bar': 2 },
37 { 'bar': 3 } 31 { 'bar': 3 }
38 ] 32 ]
39 } 33 }
40 print(template.render(input).text) 34 print(template.render(input).text)
41 35
42 Handlebar will use get() on contexts to return values, so to create custom 36 Handlebar will use get() on contexts to return values, so to create custom
(...skipping 15 matching lines...) Expand all
58 Exception.__init__(self, error) 52 Exception.__init__(self, error)
59 53
60 class RenderResult(object): 54 class RenderResult(object):
61 '''The result of a render operation. 55 '''The result of a render operation.
62 ''' 56 '''
63 def __init__(self, text, errors): 57 def __init__(self, text, errors):
64 self.text = text; 58 self.text = text;
65 self.errors = errors 59 self.errors = errors
66 60
67 def __repr__(self): 61 def __repr__(self):
68 return '%s(text=%s, errors=%s)' % ( 62 return '%s(text=%s, errors=%s)' % (type(self).__name__,
69 self.__class__.__name__, self.text, self.errors) 63 self.text,
64 self.errors)
70 65
71 def __str__(self): 66 def __str__(self):
72 return repr(self) 67 return repr(self)
73 68
74 class _StringBuilder(object): 69 class _StringBuilder(object):
75 '''Efficiently builds strings. 70 '''Efficiently builds strings.
76 ''' 71 '''
77 def __init__(self): 72 def __init__(self):
78 self._buf = [] 73 self._buf = []
79 74
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
153 def Push(self, context): 148 def Push(self, context):
154 self._nodes.append(_Contexts._Node(context)) 149 self._nodes.append(_Contexts._Node(context))
155 150
156 def Pop(self): 151 def Pop(self):
157 node = self._nodes.pop() 152 node = self._nodes.pop()
158 assert len(self._nodes) >= self._first_local 153 assert len(self._nodes) >= self._first_local
159 for found_key in node.GetKeys(): 154 for found_key in node.GetKeys():
160 # [0] is the stack of nodes that |found_key| has been found in. 155 # [0] is the stack of nodes that |found_key| has been found in.
161 self._value_info[found_key][0].pop() 156 self._value_info[found_key][0].pop()
162 157
163 def GetTopLocal(self):
164 if len(self._nodes) == self._first_local:
165 return None
166 return self._nodes[-1]._value
167
168 def Resolve(self, path): 158 def Resolve(self, path):
169 # This method is only efficient at finding |key|; if |tail| has a value (and 159 # This method is only efficient at finding |key|; if |tail| has a value (and
170 # |key| evaluates to an indexable value) we'll need to descend into that. 160 # |key| evaluates to an indexable value) we'll need to descend into that.
171 key, tail = path.split('.', 1) if '.' in path else (path, None) 161 key, tail = path.split('.', 1) if '.' in path else (path, None)
172 162 found = self._FindNodeValue(key)
173 if key == '@':
174 found = self._nodes[-1]._value
175 else:
176 found = self._FindNodeValue(key)
177
178 if tail is None: 163 if tail is None:
179 return found 164 return found
180
181 for part in tail.split('.'): 165 for part in tail.split('.'):
182 if not hasattr(found, 'get'): 166 if not hasattr(found, 'get'):
183 return None 167 return None
184 found = found.get(part) 168 found = found.get(part)
185 return found 169 return found
186 170
171 def Scope(self, context, fn, *args):
172 self.Push(context)
173 try:
174 return fn(*args)
175 finally:
176 self.Pop()
177
187 def _FindNodeValue(self, key): 178 def _FindNodeValue(self, key):
188 # |found_node_list| will be all the nodes that |key| has been found in. 179 # |found_node_list| will be all the nodes that |key| has been found in.
189 # |checked_node_set| are those that have been checked. 180 # |checked_node_set| are those that have been checked.
190 info = self._value_info.get(key) 181 info = self._value_info.get(key)
191 if info is None: 182 if info is None:
192 info = ([], set()) 183 info = ([], set())
193 self._value_info[key] = info 184 self._value_info[key] = info
194 found_node_list, checked_node_set = info 185 found_node_list, checked_node_set = info
195 186
196 # Check all the nodes not yet checked for |key|. 187 # Check all the nodes not yet checked for |key|.
(...skipping 21 matching lines...) Expand all
218 self.id_ = id_ 209 self.id_ = id_
219 210
220 def __init__(self, entries=[]): 211 def __init__(self, entries=[]):
221 self.entries = entries 212 self.entries = entries
222 213
223 def Descend(self, name, id_): 214 def Descend(self, name, id_):
224 descended = list(self.entries) 215 descended = list(self.entries)
225 descended.append(_Stack.Entry(name, id_)) 216 descended.append(_Stack.Entry(name, id_))
226 return _Stack(entries=descended) 217 return _Stack(entries=descended)
227 218
219 class _InternalContext(object):
220 def __init__(self):
221 self._render_state = None
222
223 def SetRenderState(self, render_state):
224 self._render_state = render_state
225
226 def get(self, key):
227 if key == 'errors':
228 errors = self._render_state._errors
229 return '\n'.join(errors) if errors else None
230 return None
231
228 class _RenderState(object): 232 class _RenderState(object):
229 '''The state of a render call. 233 '''The state of a render call.
230 ''' 234 '''
231 def __init__(self, name, contexts, _stack=_Stack()): 235 def __init__(self, name, contexts, _stack=_Stack()):
232 self.text = _StringBuilder() 236 self.text = _StringBuilder()
233 self.contexts = contexts 237 self.contexts = contexts
234 self._name = name 238 self._name = name
235 self._errors = [] 239 self._errors = []
236 self._stack = _stack 240 self._stack = _stack
237 241
238 def AddResolutionError(self, id_): 242 def AddResolutionError(self, id_, description=None):
239 self._errors.append( 243 message = id_.CreateResolutionErrorMessage(self._name, stack=self._stack)
240 id_.CreateResolutionErrorMessage(self._name, stack=self._stack)) 244 if description is not None:
245 message = '%s (%s)' % (message, description)
246 self._errors.append(message)
241 247
242 def Copy(self): 248 def Copy(self):
243 return _RenderState( 249 return _RenderState(
244 self._name, self.contexts, _stack=self._stack) 250 self._name, self.contexts, _stack=self._stack)
245 251
246 def ForkPartial(self, custom_name, id_): 252 def ForkPartial(self, custom_name, id_):
247 name = custom_name or id_.name 253 name = custom_name or id_.name
248 return _RenderState(name, 254 return _RenderState(name,
249 self.contexts.CreateFromGlobals(), 255 self.contexts.CreateFromGlobals(),
250 _stack=self._stack.Descend(name, id_)) 256 _stack=self._stack.Descend(name, id_))
251 257
252 def Merge(self, render_state, text_transform=None): 258 def Merge(self, render_state, text_transform=None):
253 self._errors.extend(render_state._errors) 259 self._errors.extend(render_state._errors)
254 text = render_state.text.ToString() 260 text = render_state.text.ToString()
255 if text_transform is not None: 261 if text_transform is not None:
256 text = text_transform(text) 262 text = text_transform(text)
257 self.text.Append(text) 263 self.text.Append(text)
258 264
259 def GetResult(self): 265 def GetResult(self):
260 return RenderResult(self.text.ToString(), self._errors); 266 return RenderResult(self.text.ToString(), self._errors);
261 267
262 class _Identifier(object): 268 class _Identifier(object):
263 ''' An identifier of the form '@', 'foo.bar.baz', or '@.foo.bar.baz'. 269 '''An identifier of the form 'foo', 'foo.bar.baz', 'foo-bar.baz', etc.
264 ''' 270 '''
271 _VALID_ID_MATCHER = re.compile(r'^[a-zA-Z0-9@_/-]+$')
272
265 def __init__(self, name, line, column): 273 def __init__(self, name, line, column):
266 self.name = name 274 self.name = name
267 self.line = line 275 self.line = line
268 self.column = column 276 self.column = column
269 if name == '': 277 if name == '':
270 raise ParseException('Empty identifier %s' % self.GetDescription()) 278 raise ParseException('Empty identifier %s' % self.GetDescription())
271 for part in name.split('.'): 279 for part in name.split('.'):
272 if part != '@' and not re.match('^[a-zA-Z0-9_/-]+$', part): 280 if not _Identifier._VALID_ID_MATCHER.match(part):
273 raise ParseException('Invalid identifier %s' % self.GetDescription()) 281 raise ParseException('Invalid identifier %s' % self.GetDescription())
274 282
275 def GetDescription(self): 283 def GetDescription(self):
276 return '\'%s\' at line %s column %s' % (self.name, self.line, self.column) 284 return '\'%s\' at line %s column %s' % (self.name, self.line, self.column)
277 285
278 def CreateResolutionErrorMessage(self, name, stack=None): 286 def CreateResolutionErrorMessage(self, name, stack=None):
279 message = _StringBuilder() 287 message = _StringBuilder()
280 message.Append('Failed to resolve %s in %s\n' % (self.GetDescription(), 288 message.Append('Failed to resolve %s in %s\n' % (self.GetDescription(),
281 name)) 289 name))
282 if stack is not None: 290 if stack is not None:
283 for entry in stack.entries: 291 for entry in reversed(stack.entries):
284 message.Append(' included as %s in %s\n' % (entry.id_.GetDescription(), 292 message.Append(' included as %s in %s\n' % (entry.id_.GetDescription(),
285 entry.name)) 293 entry.name))
286 return message.ToString() 294 return message.ToString().strip()
287 295
288 def __repr__(self): 296 def __repr__(self):
289 return self.name 297 return self.name
290 298
291 def __str__(self): 299 def __str__(self):
292 return repr(self) 300 return repr(self)
293 301
294 class _Line(object): 302 class _Node(object): pass
295 def __init__(self, number):
296 self.number = number
297 303
298 def __repr__(self): 304 class _LeafNode(_Node):
299 return str(self.number)
300
301 def __str__(self):
302 return repr(self)
303
304 class _LeafNode(object):
305 def __init__(self, start_line, end_line): 305 def __init__(self, start_line, end_line):
306 self._start_line = start_line 306 self._start_line = start_line
307 self._end_line = end_line 307 self._end_line = end_line
308 308
309 def StartsWithNewLine(self): 309 def StartsWithNewLine(self):
310 return False 310 return False
311 311
312 def TrimStartingNewLine(self): 312 def TrimStartingNewLine(self):
313 pass 313 pass
314 314
315 def TrimEndingSpaces(self): 315 def TrimEndingSpaces(self):
316 return 0 316 return 0
317 317
318 def TrimEndingNewLine(self): 318 def TrimEndingNewLine(self):
319 pass 319 pass
320 320
321 def EndsWithEmptyLine(self): 321 def EndsWithEmptyLine(self):
322 return False 322 return False
323 323
324 def GetStartLine(self): 324 def GetStartLine(self):
325 return self._start_line 325 return self._start_line
326 326
327 def GetEndLine(self): 327 def GetEndLine(self):
328 return self._end_line 328 return self._end_line
329 329
330 class _DecoratorNode(object): 330 def __str__(self):
331 return repr(self)
332
333 class _DecoratorNode(_Node):
331 def __init__(self, content): 334 def __init__(self, content):
332 self._content = content 335 self._content = content
333 336
334 def StartsWithNewLine(self): 337 def StartsWithNewLine(self):
335 return self._content.StartsWithNewLine() 338 return self._content.StartsWithNewLine()
336 339
337 def TrimStartingNewLine(self): 340 def TrimStartingNewLine(self):
338 self._content.TrimStartingNewLine() 341 self._content.TrimStartingNewLine()
339 342
340 def TrimEndingSpaces(self): 343 def TrimEndingSpaces(self):
341 return self._content.TrimEndingSpaces() 344 return self._content.TrimEndingSpaces()
342 345
343 def TrimEndingNewLine(self): 346 def TrimEndingNewLine(self):
344 self._content.TrimEndingNewLine() 347 self._content.TrimEndingNewLine()
345 348
346 def EndsWithEmptyLine(self): 349 def EndsWithEmptyLine(self):
347 return self._content.EndsWithEmptyLine() 350 return self._content.EndsWithEmptyLine()
348 351
349 def GetStartLine(self): 352 def GetStartLine(self):
350 return self._content.GetStartLine() 353 return self._content.GetStartLine()
351 354
352 def GetEndLine(self): 355 def GetEndLine(self):
353 return self._content.GetEndLine() 356 return self._content.GetEndLine()
354 357
355 def __repr__(self): 358 def __repr__(self):
356 return str(self._content) 359 return str(self._content)
357 360
358 def __str__(self): 361 def __str__(self):
359 return repr(self) 362 return repr(self)
360 363
361 class _InlineNode(_DecoratorNode): 364 class _InlineNode(_DecoratorNode):
362 def __init__(self, content): 365 def __init__(self, content):
363 _DecoratorNode.__init__(self, content) 366 _DecoratorNode.__init__(self, content)
364 367
365 def Render(self, render_state): 368 def Render(self, render_state):
366 content_render_state = render_state.Copy() 369 content_render_state = render_state.Copy()
367 self._content.Render(content_render_state) 370 self._content.Render(content_render_state)
368 render_state.Merge(content_render_state, 371 render_state.Merge(content_render_state,
369 text_transform=lambda text: text.replace('\n', '')) 372 text_transform=lambda text: text.replace('\n', ''))
370 373
371 class _IndentedNode(_DecoratorNode): 374 class _IndentedNode(_DecoratorNode):
372 def __init__(self, content, indentation): 375 def __init__(self, content, indentation):
373 _DecoratorNode.__init__(self, content) 376 _DecoratorNode.__init__(self, content)
374 self._indent_str = ' ' * indentation 377 self._indent_str = ' ' * indentation
375 378
376 def Render(self, render_state): 379 def Render(self, render_state):
377 if isinstance(self._content, _CommentNode): 380 if isinstance(self._content, _CommentNode):
378 return 381 return
379 content_render_state = render_state.Copy() 382 def inlinify(text):
380 self._content.Render(content_render_state) 383 if len(text) == 0: # avoid rendering a blank line
381 def AddIndentation(text): 384 return ''
382 buf = _StringBuilder() 385 buf = _StringBuilder()
383 buf.Append(self._indent_str) 386 buf.Append(self._indent_str)
384 buf.Append(text.replace('\n', '\n%s' % self._indent_str)) 387 buf.Append(text.replace('\n', '\n%s' % self._indent_str))
385 buf.Append('\n') 388 if not text.endswith('\n'): # partials will often already end in a \n
389 buf.Append('\n')
386 return buf.ToString() 390 return buf.ToString()
387 render_state.Merge(content_render_state, text_transform=AddIndentation) 391 content_render_state = render_state.Copy()
392 self._content.Render(content_render_state)
393 render_state.Merge(content_render_state, text_transform=inlinify)
388 394
389 class _BlockNode(_DecoratorNode): 395 class _BlockNode(_DecoratorNode):
390 def __init__(self, content): 396 def __init__(self, content):
391 _DecoratorNode.__init__(self, content) 397 _DecoratorNode.__init__(self, content)
392 content.TrimStartingNewLine() 398 content.TrimStartingNewLine()
393 content.TrimEndingSpaces() 399 content.TrimEndingSpaces()
394 400
395 def Render(self, render_state): 401 def Render(self, render_state):
396 self._content.Render(render_state) 402 self._content.Render(render_state)
397 403
398 class _NodeCollection(object): 404 class _NodeCollection(_Node):
399 def __init__(self, nodes): 405 def __init__(self, nodes):
400 assert nodes 406 assert nodes
401 self._nodes = nodes 407 self._nodes = nodes
402 408
403 def Render(self, render_state): 409 def Render(self, render_state):
404 for node in self._nodes: 410 for node in self._nodes:
405 node.Render(render_state) 411 node.Render(render_state)
406 412
407 def StartsWithNewLine(self): 413 def StartsWithNewLine(self):
408 return self._nodes[0].StartsWithNewLine() 414 return self._nodes[0].StartsWithNewLine()
(...skipping 12 matching lines...) Expand all
421 427
422 def GetStartLine(self): 428 def GetStartLine(self):
423 return self._nodes[0].GetStartLine() 429 return self._nodes[0].GetStartLine()
424 430
425 def GetEndLine(self): 431 def GetEndLine(self):
426 return self._nodes[-1].GetEndLine() 432 return self._nodes[-1].GetEndLine()
427 433
428 def __repr__(self): 434 def __repr__(self):
429 return ''.join(str(node) for node in self._nodes) 435 return ''.join(str(node) for node in self._nodes)
430 436
431 def __str__(self): 437 class _StringNode(_Node):
432 return repr(self) 438 '''Just a string.
433
434 class _StringNode(object):
435 ''' Just a string.
436 ''' 439 '''
437 def __init__(self, string, start_line, end_line): 440 def __init__(self, string, start_line, end_line):
438 self._string = string 441 self._string = string
439 self._start_line = start_line 442 self._start_line = start_line
440 self._end_line = end_line 443 self._end_line = end_line
441 444
442 def Render(self, render_state): 445 def Render(self, render_state):
443 render_state.text.Append(self._string) 446 render_state.text.Append(self._string)
444 447
445 def StartsWithNewLine(self): 448 def StartsWithNewLine(self):
(...skipping 24 matching lines...) Expand all
470 473
471 def GetStartLine(self): 474 def GetStartLine(self):
472 return self._start_line 475 return self._start_line
473 476
474 def GetEndLine(self): 477 def GetEndLine(self):
475 return self._end_line 478 return self._end_line
476 479
477 def __repr__(self): 480 def __repr__(self):
478 return self._string 481 return self._string
479 482
480 def __str__(self):
481 return repr(self)
482
483 class _EscapedVariableNode(_LeafNode): 483 class _EscapedVariableNode(_LeafNode):
484 ''' {{foo}} 484 '''{{foo}}
485 ''' 485 '''
486 def __init__(self, id_): 486 def __init__(self, id_):
487 _LeafNode.__init__(self, id_.line, id_.line) 487 _LeafNode.__init__(self, id_.line, id_.line)
488 self._id = id_ 488 self._id = id_
489 489
490 def Render(self, render_state): 490 def Render(self, render_state):
491 value = render_state.contexts.Resolve(self._id.name) 491 value = render_state.contexts.Resolve(self._id.name)
492 if value is None: 492 if value is None:
493 render_state.AddResolutionError(self._id) 493 render_state.AddResolutionError(self._id)
494 return 494 return
495 string = value if isinstance(value, basestring) else str(value) 495 string = value if isinstance(value, basestring) else str(value)
496 render_state.text.Append(string.replace('&', '&') 496 render_state.text.Append(string.replace('&', '&')
497 .replace('<', '&lt;') 497 .replace('<', '&lt;')
498 .replace('>', '&gt;')) 498 .replace('>', '&gt;'))
499 499
500 def __repr__(self): 500 def __repr__(self):
501 return '{{%s}}' % self._id 501 return '{{%s}}' % self._id
502 502
503 def __str__(self):
504 return repr(self)
505
506 class _UnescapedVariableNode(_LeafNode): 503 class _UnescapedVariableNode(_LeafNode):
507 ''' {{{foo}}} 504 '''{{{foo}}}
508 ''' 505 '''
509 def __init__(self, id_): 506 def __init__(self, id_):
510 _LeafNode.__init__(self, id_.line, id_.line) 507 _LeafNode.__init__(self, id_.line, id_.line)
511 self._id = id_ 508 self._id = id_
512 509
513 def Render(self, render_state): 510 def Render(self, render_state):
514 value = render_state.contexts.Resolve(self._id.name) 511 value = render_state.contexts.Resolve(self._id.name)
515 if value is None: 512 if value is None:
516 render_state.AddResolutionError(self._id) 513 render_state.AddResolutionError(self._id)
517 return 514 return
518 string = value if isinstance(value, basestring) else str(value) 515 string = value if isinstance(value, basestring) else str(value)
519 render_state.text.Append(string) 516 render_state.text.Append(string)
520 517
521 def __repr__(self): 518 def __repr__(self):
522 return '{{{%s}}}' % self._id 519 return '{{{%s}}}' % self._id
523 520
524 def __str__(self):
525 return repr(self)
526
527 class _CommentNode(_LeafNode): 521 class _CommentNode(_LeafNode):
528 '''{{- This is a comment -}} 522 '''{{- This is a comment -}}
529 An empty placeholder node for correct indented rendering behaviour. 523 An empty placeholder node for correct indented rendering behaviour.
530 ''' 524 '''
531 def __init__(self, start_line, end_line): 525 def __init__(self, start_line, end_line):
532 _LeafNode.__init__(self, start_line, end_line) 526 _LeafNode.__init__(self, start_line, end_line)
533 527
534 def Render(self, render_state): 528 def Render(self, render_state):
535 pass 529 pass
536 530
537 def __repr__(self): 531 def __repr__(self):
538 return '<comment>' 532 return '<comment>'
539 533
540 def __str__(self):
541 return repr(self)
542
543 class _SectionNode(_DecoratorNode): 534 class _SectionNode(_DecoratorNode):
544 ''' {{#foo}} ... {{/}} 535 '''{{#var:foo}} ... {{/foo}}
545 ''' 536 '''
546 def __init__(self, id_, content): 537 def __init__(self, bind_to, id_, content):
547 _DecoratorNode.__init__(self, content) 538 _DecoratorNode.__init__(self, content)
539 self._bind_to = bind_to
548 self._id = id_ 540 self._id = id_
549 541
550 def Render(self, render_state): 542 def Render(self, render_state):
551 value = render_state.contexts.Resolve(self._id.name) 543 value = render_state.contexts.Resolve(self._id.name)
552 if isinstance(value, list): 544 if isinstance(value, list):
553 for item in value: 545 for item in value:
554 # Always push context, even if it's not "valid", since we want to 546 if self._bind_to is not None:
555 # be able to refer to items in a list such as [1,2,3] via @. 547 render_state.contexts.Scope({self._bind_to.name: item},
556 render_state.contexts.Push(item) 548 self._content.Render, render_state)
557 self._content.Render(render_state) 549 else:
558 render_state.contexts.Pop() 550 self._content.Render(render_state)
559 elif hasattr(value, 'get'): 551 elif hasattr(value, 'get'):
560 render_state.contexts.Push(value) 552 if self._bind_to is not None:
561 self._content.Render(render_state) 553 render_state.contexts.Scope({self._bind_to.name: value},
562 render_state.contexts.Pop() 554 self._content.Render, render_state)
555 else:
556 render_state.contexts.Scope(value, self._content.Render, render_state)
563 else: 557 else:
564 render_state.AddResolutionError(self._id) 558 render_state.AddResolutionError(self._id)
565 559
566 def __repr__(self): 560 def __repr__(self):
567 return '{{#%s}}%s{{/%s}}' % ( 561 return '{{#%s}}%s{{/%s}}' % (
568 self._id, _DecoratorNode.__repr__(self), self._id) 562 self._id, _DecoratorNode.__repr__(self), self._id)
569 563
570 def __str__(self):
571 return repr(self)
572
573 class _VertedSectionNode(_DecoratorNode): 564 class _VertedSectionNode(_DecoratorNode):
574 ''' {{?foo}} ... {{/}} 565 '''{{?var:foo}} ... {{/foo}}
575 ''' 566 '''
576 def __init__(self, id_, content): 567 def __init__(self, bind_to, id_, content):
577 _DecoratorNode.__init__(self, content) 568 _DecoratorNode.__init__(self, content)
569 self._bind_to = bind_to
578 self._id = id_ 570 self._id = id_
579 571
580 def Render(self, render_state): 572 def Render(self, render_state):
581 value = render_state.contexts.Resolve(self._id.name) 573 value = render_state.contexts.Resolve(self._id.name)
582 if _VertedSectionNode.ShouldRender(value): 574 if _VertedSectionNode.ShouldRender(value):
583 render_state.contexts.Push(value) 575 if self._bind_to is not None:
584 self._content.Render(render_state) 576 render_state.contexts.Scope({self._bind_to.name: value},
585 render_state.contexts.Pop() 577 self._content.Render, render_state)
578 else:
579 self._content.Render(render_state)
586 580
587 def __repr__(self): 581 def __repr__(self):
588 return '{{?%s}}%s{{/%s}}' % ( 582 return '{{?%s}}%s{{/%s}}' % (
589 self._id, _DecoratorNode.__repr__(self), self._id) 583 self._id, _DecoratorNode.__repr__(self), self._id)
590 584
591 def __str__(self):
592 return repr(self)
593
594 @staticmethod 585 @staticmethod
595 def ShouldRender(value): 586 def ShouldRender(value):
596 if value is None: 587 if value is None:
597 return False 588 return False
598 if isinstance(value, bool): 589 if isinstance(value, bool):
599 return value 590 return value
600 if isinstance(value, list): 591 if isinstance(value, list):
601 return len(value) > 0 592 return len(value) > 0
602 return True 593 return True
603 594
604 class _InvertedSectionNode(_DecoratorNode): 595 class _InvertedSectionNode(_DecoratorNode):
605 ''' {{^foo}} ... {{/}} 596 '''{{^foo}} ... {{/foo}}
606 ''' 597 '''
607 def __init__(self, id_, content): 598 def __init__(self, bind_to, id_, content):
608 _DecoratorNode.__init__(self, content) 599 _DecoratorNode.__init__(self, content)
600 if bind_to is not None:
601 raise ParseException('{{^%s:%s}} does not support variable binding'
602 % (bind_to, id_))
609 self._id = id_ 603 self._id = id_
610 604
611 def Render(self, render_state): 605 def Render(self, render_state):
612 value = render_state.contexts.Resolve(self._id.name) 606 value = render_state.contexts.Resolve(self._id.name)
613 if not _VertedSectionNode.ShouldRender(value): 607 if not _VertedSectionNode.ShouldRender(value):
614 self._content.Render(render_state) 608 self._content.Render(render_state)
615 609
616 def __repr__(self): 610 def __repr__(self):
617 return '{{^%s}}%s{{/%s}}' % ( 611 return '{{^%s}}%s{{/%s}}' % (
618 self._id, _DecoratorNode.__repr__(self), self._id) 612 self._id, _DecoratorNode.__repr__(self), self._id)
619 613
620 def __str__(self): 614 class _AssertionNode(_LeafNode):
621 return repr(self) 615 '''{{!foo Some comment about foo}}
616 '''
617 def __init__(self, id_, description):
618 _LeafNode.__init__(self, id_.line, id_.line)
619 self._id = id_
620 self._description = description
621
622 def Render(self, render_state):
623 if render_state.contexts.Resolve(self._id.name) is None:
624 render_state.AddResolutionError(self._id, description=self._description)
625
626 def __repr__(self):
627 return '{{!%s %s}}' % (self._id, self._description)
622 628
623 class _JsonNode(_LeafNode): 629 class _JsonNode(_LeafNode):
624 ''' {{*foo}} 630 '''{{*foo}}
625 ''' 631 '''
626 def __init__(self, id_): 632 def __init__(self, id_):
627 _LeafNode.__init__(self, id_.line, id_.line) 633 _LeafNode.__init__(self, id_.line, id_.line)
628 self._id = id_ 634 self._id = id_
629 635
630 def Render(self, render_state): 636 def Render(self, render_state):
631 value = render_state.contexts.Resolve(self._id.name) 637 value = render_state.contexts.Resolve(self._id.name)
632 if value is None: 638 if value is None:
633 render_state.AddResolutionError(self._id) 639 render_state.AddResolutionError(self._id)
634 return 640 return
635 render_state.text.Append(json.dumps(value, separators=(',',':'))) 641 render_state.text.Append(json.dumps(value, separators=(',',':')))
636 642
637 def __repr__(self): 643 def __repr__(self):
638 return '{{*%s}}' % self._id 644 return '{{*%s}}' % self._id
639 645
640 def __str__(self): 646 class _PartialNode(_LeafNode):
641 return repr(self) 647 '''{{+var:foo}} ... {{/foo}}
648 '''
649 def __init__(self, bind_to, id_, content):
650 _LeafNode.__init__(self, id_.line, id_.line)
651 self._bind_to = bind_to
652 self._id = id_
653 self._content = content
654 self._resolved_args = None
655 self._args = None
656 self._pass_through_id = None
642 657
643 class _PartialNode(_LeafNode): 658 @classmethod
644 ''' {{+foo}} 659 def Inline(cls, id_):
645 ''' 660 return cls(None, id_, None)
646 def __init__(self, id_):
647 _LeafNode.__init__(self, id_.line, id_.line)
648 self._id = id_
649 self._args = None
650 self._local_context_id = None
651 661
652 def Render(self, render_state): 662 def Render(self, render_state):
653 value = render_state.contexts.Resolve(self._id.name) 663 value = render_state.contexts.Resolve(self._id.name)
654 if value is None: 664 if value is None:
655 render_state.AddResolutionError(self._id) 665 render_state.AddResolutionError(self._id)
656 return 666 return
657 if not isinstance(value, Handlebar): 667 if not isinstance(value, (Handlebar, _Node)):
658 render_state.AddResolutionError(self._id) 668 render_state.AddResolutionError(self._id, description='not a partial')
659 return 669 return
660 670
661 partial_render_state = render_state.ForkPartial(value._name, self._id) 671 if isinstance(value, Handlebar):
672 node, name = value._top_node, value._name
673 else:
674 node, name = value, None
662 675
663 # TODO: Don't do this. Force callers to do this by specifying an @ argument. 676 partial_render_state = render_state.ForkPartial(name, self._id)
664 top_local = render_state.contexts.GetTopLocal()
665 if top_local is not None:
666 partial_render_state.contexts.Push(top_local)
667 677
678 arg_context = {}
679 if self._pass_through_id is not None:
680 context = render_state.contexts.Resolve(self._pass_through_id.name)
681 if context is not None:
682 arg_context[self._pass_through_id.name] = context
683 if self._resolved_args is not None:
684 arg_context.update(self._resolved_args)
668 if self._args is not None: 685 if self._args is not None:
669 arg_context = {} 686 def resolve_args(args):
670 for key, value_id in self._args.items(): 687 resolved = {}
671 context = render_state.contexts.Resolve(value_id.name) 688 for key, value in args.iteritems():
672 if context is not None: 689 if isinstance(value, dict):
673 arg_context[key] = context 690 assert len(value.keys()) == 1
691 inner_id, inner_args = value.items()[0]
692 inner_partial = render_state.contexts.Resolve(inner_id.name)
693 if inner_partial is not None:
694 context = _PartialNode(None, inner_id, inner_partial)
695 context.SetResolvedArguments(resolve_args(inner_args))
696 resolved[key] = context
697 else:
698 context = render_state.contexts.Resolve(value.name)
699 if context is not None:
700 resolved[key] = context
701 return resolved
702 arg_context.update(resolve_args(self._args))
703 if self._bind_to and self._content:
704 arg_context[self._bind_to.name] = self._content
705 if arg_context:
674 partial_render_state.contexts.Push(arg_context) 706 partial_render_state.contexts.Push(arg_context)
675 707
676 if self._local_context_id is not None: 708 node.Render(partial_render_state)
677 local_context = render_state.contexts.Resolve(self._local_context_id.name)
678 if local_context is not None:
679 partial_render_state.contexts.Push(local_context)
680
681 value._top_node.Render(partial_render_state)
682 709
683 render_state.Merge( 710 render_state.Merge(
684 partial_render_state, 711 partial_render_state,
685 text_transform=lambda text: text[:-1] if text.endswith('\n') else text) 712 text_transform=lambda text: text[:-1] if text.endswith('\n') else text)
686 713
687 def AddArgument(self, key, id_): 714 def SetResolvedArguments(self, args):
688 if self._args is None: 715 self._resolved_args = args
689 self._args = {}
690 self._args[key] = id_
691 716
692 def SetLocalContext(self, id_): 717 def SetArguments(self, args):
693 self._local_context_id = id_ 718 self._args = args
719
720 def PassThroughArgument(self, id_):
721 self._pass_through_id = id_
694 722
695 def __repr__(self): 723 def __repr__(self):
696 return '{{+%s}}' % self._id 724 return '{{+%s}}' % self._id
697 725
698 def __str__(self):
699 return repr(self)
700
701 _TOKENS = {} 726 _TOKENS = {}
702 727
703 class _Token(object): 728 class _Token(object):
704 ''' The tokens that can appear in a template. 729 '''The tokens that can appear in a template.
705 ''' 730 '''
706 class Data(object): 731 class Data(object):
707 def __init__(self, name, text, clazz): 732 def __init__(self, name, text, clazz):
708 self.name = name 733 self.name = name
709 self.text = text 734 self.text = text
710 self.clazz = clazz 735 self.clazz = clazz
711 _TOKENS[text] = self 736 _TOKENS[text] = self
712 737
713 def ElseNodeClass(self): 738 def ElseNodeClass(self):
714 if self.clazz == _VertedSectionNode: 739 if self.clazz == _VertedSectionNode:
715 return _InvertedSectionNode 740 return _InvertedSectionNode
716 if self.clazz == _InvertedSectionNode: 741 if self.clazz == _InvertedSectionNode:
717 return _VertedSectionNode 742 return _VertedSectionNode
718 raise ValueError('%s cannot have an else clause.' % self.clazz) 743 return None
719 744
720 OPEN_START_SECTION = Data('OPEN_START_SECTION' , '{{#', _Sect ionNode) 745 def __repr__(self):
721 OPEN_START_VERTED_SECTION = Data('OPEN_START_VERTED_SECTION' , '{{?', _Vert edSectionNode) 746 return self.name
722 OPEN_START_INVERTED_SECTION = Data('OPEN_START_INVERTED_SECTION', '{{^', _Inve rtedSectionNode) 747
723 OPEN_START_JSON = Data('OPEN_START_JSON' , '{{*', _Json Node) 748 def __str__(self):
724 OPEN_START_PARTIAL = Data('OPEN_START_PARTIAL' , '{{+', _Part ialNode) 749 return repr(self)
725 OPEN_ELSE = Data('OPEN_ELSE' , '{{:', None) 750
726 OPEN_END_SECTION = Data('OPEN_END_SECTION' , '{{/', None) 751 OPEN_START_SECTION = Data(
727 INLINE_END_SECTION = Data('INLINE_END_SECTION' , '/}}', None) 752 'OPEN_START_SECTION' , '{{#', _SectionNode)
728 OPEN_UNESCAPED_VARIABLE = Data('OPEN_UNESCAPED_VARIABLE' , '{{{', _Unes capedVariableNode) 753 OPEN_START_VERTED_SECTION = Data(
729 CLOSE_MUSTACHE3 = Data('CLOSE_MUSTACHE3' , '}}}', None) 754 'OPEN_START_VERTED_SECTION' , '{{?', _VertedSectionNode)
730 OPEN_COMMENT = Data('OPEN_COMMENT' , '{{-', _Comm entNode) 755 OPEN_START_INVERTED_SECTION = Data(
731 CLOSE_COMMENT = Data('CLOSE_COMMENT' , '-}}', None) 756 'OPEN_START_INVERTED_SECTION', '{{^', _InvertedSectionNode)
732 OPEN_VARIABLE = Data('OPEN_VARIABLE' , '{{' , _Esca pedVariableNode) 757 OPEN_ASSERTION = Data(
733 CLOSE_MUSTACHE = Data('CLOSE_MUSTACHE' , '}}' , None) 758 'OPEN_ASSERTION' , '{{!', _AssertionNode)
734 CHARACTER = Data('CHARACTER' , '.' , None) 759 OPEN_JSON = Data(
760 'OPEN_JSON' , '{{*', _JsonNode)
761 OPEN_PARTIAL = Data(
762 'OPEN_PARTIAL' , '{{+', _PartialNode)
763 OPEN_ELSE = Data(
764 'OPEN_ELSE' , '{{:', None)
765 OPEN_END_SECTION = Data(
766 'OPEN_END_SECTION' , '{{/', None)
767 INLINE_END_SECTION = Data(
768 'INLINE_END_SECTION' , '/}}', None)
769 OPEN_UNESCAPED_VARIABLE = Data(
770 'OPEN_UNESCAPED_VARIABLE' , '{{{', _UnescapedVariableNode)
771 CLOSE_MUSTACHE3 = Data(
772 'CLOSE_MUSTACHE3' , '}}}', None)
773 OPEN_COMMENT = Data(
774 'OPEN_COMMENT' , '{{-', _CommentNode)
775 CLOSE_COMMENT = Data(
776 'CLOSE_COMMENT' , '-}}', None)
777 OPEN_VARIABLE = Data(
778 'OPEN_VARIABLE' , '{{' , _EscapedVariableNode)
779 CLOSE_MUSTACHE = Data(
780 'CLOSE_MUSTACHE' , '}}' , None)
781 CHARACTER = Data(
782 'CHARACTER' , '.' , None)
735 783
736 class _TokenStream(object): 784 class _TokenStream(object):
737 ''' Tokeniser for template parsing. 785 '''Tokeniser for template parsing.
738 ''' 786 '''
739 def __init__(self, string): 787 def __init__(self, string):
740 self.next_token = None 788 self.next_token = None
741 self.next_line = _Line(1) 789 self.next_line = 1
742 self.next_column = 0 790 self.next_column = 0
743 self._string = string 791 self._string = string
744 self._cursor = 0 792 self._cursor = 0
745 self.Advance() 793 self.Advance()
746 794
747 def HasNext(self): 795 def HasNext(self):
748 return self.next_token is not None 796 return self.next_token is not None
749 797
798 def NextCharacter(self):
799 if self.next_token is _Token.CHARACTER:
800 return self._string[self._cursor - 1]
801 return None
802
750 def Advance(self): 803 def Advance(self):
751 if self._cursor > 0 and self._string[self._cursor - 1] == '\n': 804 if self._cursor > 0 and self._string[self._cursor - 1] == '\n':
752 self.next_line = _Line(self.next_line.number + 1) 805 self.next_line += 1
753 self.next_column = 0 806 self.next_column = 0
754 elif self.next_token is not None: 807 elif self.next_token is not None:
755 self.next_column += len(self.next_token.text) 808 self.next_column += len(self.next_token.text)
756 809
757 self.next_token = None 810 self.next_token = None
758 811
759 if self._cursor == len(self._string): 812 if self._cursor == len(self._string):
760 return None 813 return None
761 assert self._cursor < len(self._string) 814 assert self._cursor < len(self._string)
762 815
763 if (self._cursor + 1 < len(self._string) and 816 if (self._cursor + 1 < len(self._string) and
764 self._string[self._cursor + 1] in '{}'): 817 self._string[self._cursor + 1] in '{}'):
765 self.next_token = ( 818 self.next_token = (
766 _TOKENS.get(self._string[self._cursor:self._cursor+3]) or 819 _TOKENS.get(self._string[self._cursor:self._cursor+3]) or
767 _TOKENS.get(self._string[self._cursor:self._cursor+2])) 820 _TOKENS.get(self._string[self._cursor:self._cursor+2]))
768 821
769 if self.next_token is None: 822 if self.next_token is None:
770 self.next_token = _Token.CHARACTER 823 self.next_token = _Token.CHARACTER
771 824
772 self._cursor += len(self.next_token.text) 825 self._cursor += len(self.next_token.text)
773 return self 826 return self
774 827
775 def AdvanceOver(self, token): 828 def AdvanceOver(self, token, description=None):
776 if self.next_token != token: 829 parse_error = None
777 raise ParseException( 830 if not self.next_token:
778 'Expecting token %s but got %s at line %s' % (token.name, 831 parse_error = 'Reached EOF but expected %s' % token.name
779 self.next_token.name, 832 elif self.next_token is not token:
780 self.next_line)) 833 parse_error = 'Expecting token %s but got %s at line %s' % (
834 token.name, self.next_token.name, self.next_line)
835 if parse_error:
836 parse_error += ' %s' % description or ''
837 raise ParseException(parse_error)
781 return self.Advance() 838 return self.Advance()
782 839
840 def AdvanceOverSeparator(self, char, description=None):
841 self.SkipWhitespace()
842 next_char = self.NextCharacter()
843 if next_char != char:
844 parse_error = 'Expected \'%s\'. got \'%s\'' % (char, next_char)
845 if description is not None:
846 parse_error += ' (%s)' % description
847 raise ParseException(parse_error)
848 self.AdvanceOver(_Token.CHARACTER)
849 self.SkipWhitespace()
850
783 def AdvanceOverNextString(self, excluded=''): 851 def AdvanceOverNextString(self, excluded=''):
784 start = self._cursor - len(self.next_token.text) 852 start = self._cursor - len(self.next_token.text)
785 while (self.next_token is _Token.CHARACTER and 853 while (self.next_token is _Token.CHARACTER and
786 # Can use -1 here because token length of CHARACTER is 1. 854 # Can use -1 here because token length of CHARACTER is 1.
787 self._string[self._cursor - 1] not in excluded): 855 self._string[self._cursor - 1] not in excluded):
788 self.Advance() 856 self.Advance()
789 end = self._cursor - (len(self.next_token.text) if self.next_token else 0) 857 end = self._cursor - (len(self.next_token.text) if self.next_token else 0)
790 return self._string[start:end] 858 return self._string[start:end]
791 859
792 def AdvanceToNextWhitespace(self): 860 def AdvanceToNextWhitespace(self):
793 return self.AdvanceOverNextString(excluded=' \n\r\t') 861 return self.AdvanceOverNextString(excluded=' \n\r\t')
794 862
795 def SkipWhitespace(self): 863 def SkipWhitespace(self):
796 while (self.next_token is _Token.CHARACTER and 864 while (self.next_token is _Token.CHARACTER and
797 # Can use -1 here because token length of CHARACTER is 1. 865 # Can use -1 here because token length of CHARACTER is 1.
798 self._string[self._cursor - 1] in ' \n\r\t'): 866 self._string[self._cursor - 1] in ' \n\r\t'):
799 self.Advance() 867 self.Advance()
800 868
869 def __repr__(self):
870 return '%s(next_token=%s, remainder=%s)' % (type(self).__name__,
871 self.next_token,
872 self._string[self._cursor:])
873
874 def __str__(self):
875 return repr(self)
876
801 class Handlebar(object): 877 class Handlebar(object):
802 ''' A handlebar template. 878 '''A handlebar template.
803 ''' 879 '''
804 def __init__(self, template, name=None): 880 def __init__(self, template, name=None):
805 self.source = template 881 self.source = template
806 self._name = name 882 self._name = name
807 tokens = _TokenStream(template) 883 tokens = _TokenStream(template)
808 self._top_node = self._ParseSection(tokens) 884 self._top_node = self._ParseSection(tokens)
809 if not self._top_node: 885 if not self._top_node:
810 raise ParseException('Template is empty') 886 raise ParseException('Template is empty')
811 if tokens.HasNext(): 887 if tokens.HasNext():
812 raise ParseException('There are still tokens remaining at %s, ' 888 raise ParseException('There are still tokens remaining at %s, '
813 'was there an end-section without a start-section?' 889 'was there an end-section without a start-section?' %
814 % tokens.next_line) 890 tokens.next_line)
815 891
816 def _ParseSection(self, tokens): 892 def _ParseSection(self, tokens):
817 nodes = [] 893 nodes = []
818 while tokens.HasNext(): 894 while tokens.HasNext():
819 if tokens.next_token in (_Token.OPEN_END_SECTION, 895 if tokens.next_token in (_Token.OPEN_END_SECTION,
820 _Token.OPEN_ELSE): 896 _Token.OPEN_ELSE):
821 # Handled after running parseSection within the SECTION cases, so this 897 # Handled after running parseSection within the SECTION cases, so this
822 # is a terminating condition. If there *is* an orphaned 898 # is a terminating condition. If there *is* an orphaned
823 # OPEN_END_SECTION, it will be caught by noticing that there are 899 # OPEN_END_SECTION, it will be caught by noticing that there are
824 # leftover tokens after termination. 900 # leftover tokens after termination.
(...skipping 11 matching lines...) Expand all
836 previous_node = nodes[i - 1] if i > 0 else None 912 previous_node = nodes[i - 1] if i > 0 else None
837 next_node = nodes[i + 1] if i < len(nodes) - 1 else None 913 next_node = nodes[i + 1] if i < len(nodes) - 1 else None
838 rendered_node = None 914 rendered_node = None
839 915
840 if node.GetStartLine() != node.GetEndLine(): 916 if node.GetStartLine() != node.GetEndLine():
841 rendered_node = _BlockNode(node) 917 rendered_node = _BlockNode(node)
842 if previous_node: 918 if previous_node:
843 previous_node.TrimEndingSpaces() 919 previous_node.TrimEndingSpaces()
844 if next_node: 920 if next_node:
845 next_node.TrimStartingNewLine() 921 next_node.TrimStartingNewLine()
846 elif (isinstance(node, _LeafNode) and 922 elif ((not previous_node or previous_node.EndsWithEmptyLine()) and
847 (not previous_node or previous_node.EndsWithEmptyLine()) and
848 (not next_node or next_node.StartsWithNewLine())): 923 (not next_node or next_node.StartsWithNewLine())):
849 indentation = 0 924 indentation = 0
850 if previous_node: 925 if previous_node:
851 indentation = previous_node.TrimEndingSpaces() 926 indentation = previous_node.TrimEndingSpaces()
852 if next_node: 927 if next_node:
853 next_node.TrimStartingNewLine() 928 next_node.TrimStartingNewLine()
854 rendered_node = _IndentedNode(node, indentation) 929 rendered_node = _IndentedNode(node, indentation)
855 else: 930 else:
856 rendered_node = _InlineNode(node) 931 rendered_node = _InlineNode(node)
857 932
858 nodes[i] = rendered_node 933 nodes[i] = rendered_node
859 934
860 if len(nodes) == 0: 935 if len(nodes) == 0:
861 return None 936 return None
862 if len(nodes) == 1: 937 if len(nodes) == 1:
863 return nodes[0] 938 return nodes[0]
864 return _NodeCollection(nodes) 939 return _NodeCollection(nodes)
865 940
866 def _ParseNextOpenToken(self, tokens): 941 def _ParseNextOpenToken(self, tokens):
867 next_token = tokens.next_token 942 next_token = tokens.next_token
868 943
869 if next_token is _Token.CHARACTER: 944 if next_token is _Token.CHARACTER:
945 # Plain strings.
870 start_line = tokens.next_line 946 start_line = tokens.next_line
871 string = tokens.AdvanceOverNextString() 947 string = tokens.AdvanceOverNextString()
872 return [_StringNode(string, start_line, tokens.next_line)] 948 return [_StringNode(string, start_line, tokens.next_line)]
873 elif next_token in (_Token.OPEN_VARIABLE, 949 elif next_token in (_Token.OPEN_VARIABLE,
874 _Token.OPEN_UNESCAPED_VARIABLE, 950 _Token.OPEN_UNESCAPED_VARIABLE,
875 _Token.OPEN_START_JSON): 951 _Token.OPEN_JSON):
876 id_, inline_value_id = self._OpenSectionOrTag(tokens) 952 # Inline nodes that don't take arguments.
877 if inline_value_id is not None: 953 tokens.Advance()
878 raise ParseException( 954 close_token = (_Token.CLOSE_MUSTACHE3
879 '%s cannot have an inline value' % id_.GetDescription()) 955 if next_token is _Token.OPEN_UNESCAPED_VARIABLE else
956 _Token.CLOSE_MUSTACHE)
957 id_ = self._NextIdentifier(tokens)
958 tokens.AdvanceOver(close_token)
880 return [next_token.clazz(id_)] 959 return [next_token.clazz(id_)]
881 elif next_token is _Token.OPEN_START_PARTIAL: 960 elif next_token is _Token.OPEN_ASSERTION:
961 # Inline nodes that take arguments.
882 tokens.Advance() 962 tokens.Advance()
883 column_start = tokens.next_column + 1 963 id_ = self._NextIdentifier(tokens)
884 id_ = _Identifier(tokens.AdvanceToNextWhitespace(), 964 node = next_token.clazz(id_, tokens.AdvanceOverNextString())
885 tokens.next_line, 965 tokens.AdvanceOver(_Token.CLOSE_MUSTACHE)
886 column_start) 966 return [node]
887 partial_node = _PartialNode(id_) 967 elif next_token in (_Token.OPEN_PARTIAL,
888 while tokens.next_token is _Token.CHARACTER: 968 _Token.OPEN_START_SECTION,
969 _Token.OPEN_START_VERTED_SECTION,
970 _Token.OPEN_START_INVERTED_SECTION):
971 # Block nodes, though they may have inline syntax like {{#foo bar /}}.
972 tokens.Advance()
973 bind_to, id_ = None, self._NextIdentifier(tokens)
974 if tokens.NextCharacter() == ':':
975 # This section has the format {{#bound:id}} as opposed to just {{id}}.
976 # That is, |id_| is actually the identifier to bind what the section
977 # is producing, not the identifier of where to find that content.
978 tokens.AdvanceOverSeparator(':')
979 bind_to, id_ = id_, self._NextIdentifier(tokens)
980 partial_args = None
981 if next_token is _Token.OPEN_PARTIAL:
982 partial_args = self._ParsePartialNodeArgs(tokens)
983 if tokens.next_token is not _Token.CLOSE_MUSTACHE:
984 # Inline syntax for partial types.
985 if bind_to is not None:
986 raise ParseException(
987 'Cannot bind %s to a self-closing partial' % bind_to)
988 tokens.AdvanceOver(_Token.INLINE_END_SECTION)
989 partial_node = _PartialNode.Inline(id_)
990 partial_node.SetArguments(partial_args)
991 return [partial_node]
992 elif tokens.next_token is not _Token.CLOSE_MUSTACHE:
993 # Inline syntax for non-partial types. Support select node types:
994 # variables, partials, JSON.
995 line, column = tokens.next_line, (tokens.next_column + 1)
996 name = tokens.AdvanceToNextWhitespace()
997 clazz = _UnescapedVariableNode
998 if name.startswith('*'):
999 clazz = _JsonNode
1000 elif name.startswith('+'):
1001 clazz = _PartialNode.Inline
1002 if clazz is not _UnescapedVariableNode:
1003 name = name[1:]
1004 column += 1
1005 inline_node = clazz(_Identifier(name, line, column))
1006 if isinstance(inline_node, _PartialNode):
1007 inline_node.SetArguments(self._ParsePartialNodeArgs(tokens))
1008 if bind_to is not None:
1009 inline_node.PassThroughArgument(bind_to)
889 tokens.SkipWhitespace() 1010 tokens.SkipWhitespace()
890 key = tokens.AdvanceOverNextString(excluded=':') 1011 tokens.AdvanceOver(_Token.INLINE_END_SECTION)
891 tokens.Advance() 1012 return [next_token.clazz(bind_to, id_, inline_node)]
892 column_start = tokens.next_column + 1 1013 # Block syntax.
893 id_ = _Identifier(tokens.AdvanceToNextWhitespace(),
894 tokens.next_line,
895 column_start)
896 if key == '@':
897 partial_node.SetLocalContext(id_)
898 else:
899 partial_node.AddArgument(key, id_)
900 tokens.AdvanceOver(_Token.CLOSE_MUSTACHE) 1014 tokens.AdvanceOver(_Token.CLOSE_MUSTACHE)
901 return [partial_node] 1015 section = self._ParseSection(tokens)
902 elif next_token is _Token.OPEN_START_SECTION: 1016 else_node_class = next_token.ElseNodeClass() # may not have one
903 id_, inline_node = self._OpenSectionOrTag(tokens) 1017 else_section = None
1018 if (else_node_class is not None and
1019 tokens.next_token is _Token.OPEN_ELSE):
1020 self._OpenElse(tokens, id_)
1021 else_section = self._ParseSection(tokens)
1022 self._CloseSection(tokens, id_)
904 nodes = [] 1023 nodes = []
905 if inline_node is None: 1024 if section is not None:
906 section = self._ParseSection(tokens) 1025 node = next_token.clazz(bind_to, id_, section)
907 self._CloseSection(tokens, id_) 1026 if partial_args:
908 nodes = [] 1027 node.SetArguments(partial_args)
909 if section is not None: 1028 nodes.append(node)
910 nodes.append(_SectionNode(id_, section)) 1029 if else_section is not None:
911 else: 1030 nodes.append(else_node_class(bind_to, id_, else_section))
912 nodes.append(_SectionNode(id_, inline_node))
913 return nodes
914 elif next_token in (_Token.OPEN_START_VERTED_SECTION,
915 _Token.OPEN_START_INVERTED_SECTION):
916 id_, inline_node = self._OpenSectionOrTag(tokens)
917 nodes = []
918 if inline_node is None:
919 section = self._ParseSection(tokens)
920 else_section = None
921 if tokens.next_token is _Token.OPEN_ELSE:
922 self._OpenElse(tokens, id_)
923 else_section = self._ParseSection(tokens)
924 self._CloseSection(tokens, id_)
925 if section:
926 nodes.append(next_token.clazz(id_, section))
927 if else_section:
928 nodes.append(next_token.ElseNodeClass()(id_, else_section))
929 else:
930 nodes.append(next_token.clazz(id_, inline_node))
931 return nodes 1031 return nodes
932 elif next_token is _Token.OPEN_COMMENT: 1032 elif next_token is _Token.OPEN_COMMENT:
1033 # Comments.
933 start_line = tokens.next_line 1034 start_line = tokens.next_line
934 self._AdvanceOverComment(tokens) 1035 self._AdvanceOverComment(tokens)
935 return [_CommentNode(start_line, tokens.next_line)] 1036 return [_CommentNode(start_line, tokens.next_line)]
936 1037
937 def _AdvanceOverComment(self, tokens): 1038 def _AdvanceOverComment(self, tokens):
938 tokens.AdvanceOver(_Token.OPEN_COMMENT) 1039 tokens.AdvanceOver(_Token.OPEN_COMMENT)
939 depth = 1 1040 depth = 1
940 while tokens.HasNext() and depth > 0: 1041 while tokens.HasNext() and depth > 0:
941 if tokens.next_token is _Token.OPEN_COMMENT: 1042 if tokens.next_token is _Token.OPEN_COMMENT:
942 depth += 1 1043 depth += 1
943 elif tokens.next_token is _Token.CLOSE_COMMENT: 1044 elif tokens.next_token is _Token.CLOSE_COMMENT:
944 depth -= 1 1045 depth -= 1
945 tokens.Advance() 1046 tokens.Advance()
946 1047
947 def _OpenSectionOrTag(self, tokens):
948 def NextIdentifierArgs():
949 tokens.SkipWhitespace()
950 line = tokens.next_line
951 column = tokens.next_column + 1
952 name = tokens.AdvanceToNextWhitespace()
953 tokens.SkipWhitespace()
954 return (name, line, column)
955 close_token = (_Token.CLOSE_MUSTACHE3
956 if tokens.next_token is _Token.OPEN_UNESCAPED_VARIABLE else
957 _Token.CLOSE_MUSTACHE)
958 tokens.Advance()
959 id_ = _Identifier(*NextIdentifierArgs())
960 if tokens.next_token is close_token:
961 tokens.AdvanceOver(close_token)
962 inline_node = None
963 else:
964 name, line, column = NextIdentifierArgs()
965 tokens.AdvanceOver(_Token.INLINE_END_SECTION)
966 # Support select other types of nodes, the most useful being partial.
967 clazz = _UnescapedVariableNode
968 if name.startswith('*'):
969 clazz = _JsonNode
970 elif name.startswith('+'):
971 clazz = _PartialNode
972 if clazz is not _UnescapedVariableNode:
973 name = name[1:]
974 column += 1
975 inline_node = clazz(_Identifier(name, line, column))
976 return (id_, inline_node)
977
978 def _CloseSection(self, tokens, id_): 1048 def _CloseSection(self, tokens, id_):
979 tokens.AdvanceOver(_Token.OPEN_END_SECTION) 1049 tokens.AdvanceOver(_Token.OPEN_END_SECTION,
1050 description='to match %s' % id_.GetDescription())
980 next_string = tokens.AdvanceOverNextString() 1051 next_string = tokens.AdvanceOverNextString()
981 if next_string != '' and next_string != id_.name: 1052 if next_string != '' and next_string != id_.name:
982 raise ParseException( 1053 raise ParseException(
983 'Start section %s doesn\'t match end %s' % (id_, next_string)) 1054 'Start section %s doesn\'t match end %s' % (id_, next_string))
984 tokens.AdvanceOver(_Token.CLOSE_MUSTACHE) 1055 tokens.AdvanceOver(_Token.CLOSE_MUSTACHE)
985 1056
986 def _OpenElse(self, tokens, id_): 1057 def _OpenElse(self, tokens, id_):
987 tokens.AdvanceOver(_Token.OPEN_ELSE) 1058 tokens.AdvanceOver(_Token.OPEN_ELSE)
988 next_string = tokens.AdvanceOverNextString() 1059 next_string = tokens.AdvanceOverNextString()
989 if next_string != '' and next_string != id_.name: 1060 if next_string != '' and next_string != id_.name:
990 raise ParseException( 1061 raise ParseException(
991 'Start section %s doesn\'t match else %s' % (id_, next_string)) 1062 'Start section %s doesn\'t match else %s' % (id_, next_string))
992 tokens.AdvanceOver(_Token.CLOSE_MUSTACHE) 1063 tokens.AdvanceOver(_Token.CLOSE_MUSTACHE)
993 1064
994 def Render(self, *contexts): 1065 def _ParsePartialNodeArgs(self, tokens):
1066 args = {}
1067 tokens.SkipWhitespace()
1068 while (tokens.next_token is _Token.CHARACTER and
1069 tokens.NextCharacter() != ')'):
1070 key = tokens.AdvanceOverNextString(excluded=':')
1071 tokens.AdvanceOverSeparator(':')
1072 if tokens.NextCharacter() == '(':
1073 tokens.AdvanceOverSeparator('(')
1074 inner_id = self._NextIdentifier(tokens)
1075 inner_args = self._ParsePartialNodeArgs(tokens)
1076 tokens.AdvanceOverSeparator(')')
1077 args[key] = {inner_id: inner_args}
1078 else:
1079 args[key] = self._NextIdentifier(tokens)
1080 return args or None
1081
1082 def _NextIdentifier(self, tokens):
1083 tokens.SkipWhitespace()
1084 column_start = tokens.next_column + 1
1085 id_ = _Identifier(tokens.AdvanceOverNextString(excluded=' \n\r\t:()'),
1086 tokens.next_line,
1087 column_start)
1088 tokens.SkipWhitespace()
1089 return id_
1090
1091 def Render(self, *user_contexts):
995 '''Renders this template given a variable number of contexts to read out 1092 '''Renders this template given a variable number of contexts to read out
996 values from (such as those appearing in {{foo}}). 1093 values from (such as those appearing in {{foo}}).
997 ''' 1094 '''
998 name = self._name or '<root>' 1095 name = self._name or '<root>'
999 render_state = _RenderState(name, _Contexts(contexts)) 1096 internal_context = _InternalContext()
1097 render_state = _RenderState(
1098 name, _Contexts([{'_': internal_context}] + list(user_contexts)))
1099 internal_context.SetRenderState(render_state)
1000 self._top_node.Render(render_state) 1100 self._top_node.Render(render_state)
1001 return render_state.GetResult() 1101 return render_state.GetResult()
1002 1102
1003 def render(self, *contexts): 1103 def render(self, *contexts):
1004 return self.Render(*contexts) 1104 return self.Render(*contexts)
1005 1105
1106 def __eq__(self, other):
1107 return self.source == other.source and self._name == other._name
1108
1109 def __ne__(self, other):
1110 return not (self == other)
1111
1006 def __repr__(self): 1112 def __repr__(self):
1007 return str('%s(%s)' % (self.__class__.__name__, self._top_node)) 1113 return str('%s(%s)' % (type(self).__name__, self._top_node))
1008 1114
1009 def __str__(self): 1115 def __str__(self):
1010 return repr(self) 1116 return repr(self)
OLDNEW
« no previous file with comments | « third_party/handlebar/README.md ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698