1 | # Copyright (C) 2009 Andy Chu
|
2 | #
|
3 | # Licensed under the Apache License, Version 2.0 (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
|
6 | #
|
7 | # http://www.apache.org/licenses/LICENSE-2.0
|
8 | #
|
9 | # Unless required by applicable law or agreed to in writing, software
|
10 | # distributed under the License is distributed on an "AS IS" BASIS,
|
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 | # See the License for the specific language governing permissions and
|
13 | # limitations under the License.
|
14 |
|
15 | """Python implementation of json-template.
|
16 |
|
17 | JSON Template is a minimal and powerful templating language for transforming a
|
18 | JSON dictionary to arbitrary text.
|
19 |
|
20 | To use this module, you will typically use the Template constructor, and catch
|
21 | various exceptions thrown. You may also want to use the FromFile/FromString
|
22 | methods, which allow Template constructor options to be embedded in the template
|
23 | string itself.
|
24 |
|
25 | Other functions are exposed for tools which may want to process templates.
|
26 |
|
27 | Unicode
|
28 | -------
|
29 |
|
30 | JSON Template can work on unicode strings or byte strings. The template parser
|
31 | and expansion loop don't care which type they get.
|
32 |
|
33 | However, it's up to the caller to ensure that they get *compatible* types.
|
34 | Python auto-conversion can make this a bit confusing.
|
35 |
|
36 | If you have a byte string template and a dictionary with byte strings, expansion
|
37 | will work:
|
38 |
|
39 | 'Hello {name}' + {name: '\xb5'} -> 'Hello \xb5'
|
40 |
|
41 | If you have a unicode template and unicode data in the dictionary, it will work:
|
42 |
|
43 | u'Hello {name}' + {name: u'\u00B5'} -> u'Hello \u00B5'
|
44 |
|
45 | If you have a unicode template and byte string data, Python will try to decode
|
46 | the byte strings using the utf-8 encoding. This may not be possible, in which
|
47 | case you'll get a UnicodeDecodeError.
|
48 |
|
49 | u'Hello {name}' + {name: 'there'} -> 'Hello there'
|
50 | u'Hello {name}' + {name: '\xb5'} -> ERROR: \xb5 is not decodable as ASCII
|
51 |
|
52 | Mixing types may incur a performance penalty.
|
53 | """
|
54 |
|
55 | __author__ = 'Andy Chu'
|
56 |
|
57 | __all__ = [
|
58 | # Errors
|
59 | 'Error', 'CompilationError', 'EvaluationError', 'BadFormatter',
|
60 | 'BadPredicate', 'MissingFormatter', 'ConfigurationError',
|
61 | 'TemplateSyntaxError', 'UndefinedVariable',
|
62 | # API
|
63 | 'FromString', 'FromFile', 'Template', 'expand', 'Trace', 'FunctionRegistry',
|
64 | 'MakeTemplateGroup',
|
65 | # Function API
|
66 | 'SIMPLE_FUNC', 'ENHANCED_FUNC']
|
67 |
|
68 | import StringIO
|
69 | import pprint
|
70 | import re
|
71 | import sys
|
72 |
|
73 | # For formatters
|
74 | import cgi # cgi.escape
|
75 | import time # for strftime
|
76 | import urllib # for urllib.encode
|
77 | import urlparse # for urljoin
|
78 |
|
79 |
|
80 | class Error(Exception):
|
81 | """Base class for all exceptions in this module.
|
82 |
|
83 | Thus you can "except jsontemplate.Error: to catch all exceptions thrown by
|
84 | this module.
|
85 | """
|
86 |
|
87 | def __str__(self):
|
88 | """This helps people debug their templates.
|
89 |
|
90 | If a variable isn't defined, then some context is shown in the traceback.
|
91 | TODO: Attach context for other errors.
|
92 | """
|
93 | if hasattr(self, 'near'):
|
94 | return '%s\n\nNear: %s' % (self.args[0], pprint.pformat(self.near))
|
95 | else:
|
96 | return self.args[0]
|
97 |
|
98 |
|
99 | class UsageError(Error):
|
100 | """Errors in using the API, not a result of compilation or evaluation."""
|
101 |
|
102 |
|
103 | class CompilationError(Error):
|
104 | """Base class for errors that happen during the compilation stage."""
|
105 |
|
106 |
|
107 | class EvaluationError(Error):
|
108 | """Base class for errors that happen when expanding the template.
|
109 |
|
110 | This class of errors generally involve the data dictionary or the execution of
|
111 | the formatters.
|
112 | """
|
113 | def __init__(self, msg, original_exc_info=None):
|
114 | Error.__init__(self, msg)
|
115 | self.original_exc_info = original_exc_info
|
116 |
|
117 |
|
118 | class BadFormatter(CompilationError):
|
119 | """A bad formatter was specified, e.g. {variable|BAD}"""
|
120 |
|
121 | class BadPredicate(CompilationError):
|
122 | """A bad predicate was specified, e.g. {.BAD?}"""
|
123 |
|
124 | class MissingFormatter(CompilationError):
|
125 | """
|
126 | Raised when formatters are required, and a variable is missing a formatter.
|
127 | """
|
128 |
|
129 | class ConfigurationError(CompilationError):
|
130 | """
|
131 | Raised when the Template options are invalid and it can't even be compiled.
|
132 | """
|
133 |
|
134 | class TemplateSyntaxError(CompilationError):
|
135 | """Syntax error in the template text."""
|
136 |
|
137 | class UndefinedVariable(EvaluationError):
|
138 | """The template contains a variable name not defined by the data dictionary."""
|
139 |
|
140 |
|
141 | # The last group is of the form 'name | f1 | f2 ...'
|
142 | _SECTION_RE = re.compile(r'(repeated)?\s*section\s+(.+)')
|
143 |
|
144 | # Some formatters and predicates need to look up values in the whole context,
|
145 | # rather than just the current node. 'Node functions' start with a lowercase
|
146 | # letter; 'Context functions' start with any other character.
|
147 | SIMPLE_FUNC, ENHANCED_FUNC = 0, 1
|
148 |
|
149 | # Templates are a third kind of function, but only for formatters currently
|
150 | TEMPLATE_FORMATTER = 2
|
151 |
|
152 |
|
153 | class FunctionRegistry(object):
|
154 | """Abstract class for looking up formatters or predicates at compile time.
|
155 |
|
156 | Users should implement either Lookup or LookupWithType, and then the
|
157 | implementation calls LookupWithType.
|
158 | """
|
159 |
|
160 | def Lookup(self, user_str):
|
161 | """Lookup a function.
|
162 |
|
163 | Args:
|
164 | user_str: A raw string from the user, which may include uninterpreted
|
165 | arguments. For example, 'pluralize person people' or 'test? admin'
|
166 |
|
167 | Returns:
|
168 | A 2-tuple of (function, args)
|
169 | function: Callable that formats data as a string
|
170 | args: Extra arguments to be passed to the function at expansion time
|
171 | Should be None to pass NO arguments, since it can pass a 0-tuple too.
|
172 | """
|
173 | raise NotImplementedError
|
174 |
|
175 | def LookupWithType(self, user_str):
|
176 | func, args = self.Lookup(user_str)
|
177 | # If users need the complexity of FunctionRegistry, then they get the
|
178 | # 3-arguments formatter signature (value, context, args)
|
179 | return func, args, ENHANCED_FUNC
|
180 |
|
181 |
|
182 | def _DecideFuncType(user_str):
|
183 | """
|
184 | By default, formatters/predicates which start with a non-lowercase letter take
|
185 | contexts rather than just the cursor.
|
186 | """
|
187 | if user_str[0].islower():
|
188 | return SIMPLE_FUNC
|
189 | else:
|
190 | return ENHANCED_FUNC
|
191 |
|
192 |
|
193 | class DictRegistry(FunctionRegistry):
|
194 | """Look up functions in a simple dictionary."""
|
195 |
|
196 | def __init__(self, func_dict):
|
197 | self.func_dict = func_dict
|
198 |
|
199 | def LookupWithType(self, user_str):
|
200 | return self.func_dict.get(user_str), None, _DecideFuncType(user_str)
|
201 |
|
202 |
|
203 | class CallableRegistry(FunctionRegistry):
|
204 | """Look up functions in a (higher-order) function."""
|
205 |
|
206 | def __init__(self, func):
|
207 | self.func = func
|
208 |
|
209 | def LookupWithType(self, user_str):
|
210 | return self.func(user_str), None, _DecideFuncType(user_str)
|
211 |
|
212 |
|
213 | class PrefixRegistry(FunctionRegistry):
|
214 | """Lookup functions with arguments.
|
215 |
|
216 | The function name is identified by a prefix. The character after the prefix,
|
217 | usually a space, is considered the argument delimiter (similar to sed/perl's
|
218 | s/foo/bar s|foo|bar syntax).
|
219 | """
|
220 |
|
221 | def __init__(self, functions):
|
222 | """
|
223 | Args:
|
224 | functions: List of 2-tuples (prefix, function), e.g.
|
225 | [('pluralize', _Pluralize), ('cycle', _Cycle)]
|
226 | """
|
227 | self.functions = functions
|
228 |
|
229 | def Lookup(self, user_str):
|
230 | for prefix, func in self.functions:
|
231 | if user_str.startswith(prefix):
|
232 | i = len(prefix)
|
233 |
|
234 | # Usually a space, but could be something else
|
235 | try:
|
236 | splitchar = user_str[i]
|
237 | except IndexError:
|
238 | args = () # No arguments
|
239 | else:
|
240 | args = user_str.split(splitchar)[1:]
|
241 |
|
242 | return func, args
|
243 | return None, ()
|
244 |
|
245 |
|
246 | class _TemplateRef(object):
|
247 | """A reference from one template to another.
|
248 |
|
249 | The _TemplateRef sits statically in the program tree as one of the formatters.
|
250 | At runtime, _DoSubstitute calls Resolve() with the group being used.
|
251 | """
|
252 | def __init__(self, name=None, template=None):
|
253 | self.name = name
|
254 | self.template = template # a template that's already been resolved
|
255 |
|
256 | def Resolve(self, context):
|
257 | if self.template:
|
258 | return self.template
|
259 | if context.group:
|
260 | return context.group.get(self.name)
|
261 | else:
|
262 | raise EvaluationError(
|
263 | "Couldn't find template with name %r (create a template group?)"
|
264 | % self.name)
|
265 |
|
266 |
|
267 | class _TemplateRegistry(FunctionRegistry):
|
268 | """Each template owns a _TemplateRegistry.
|
269 |
|
270 | LookupWithType always returns a TemplateRef to the template compiler
|
271 | (_ProgramBuilder). At runtime, which may be after MakeTemplateGroup is
|
272 | called, the names can be resolved.
|
273 | """
|
274 | def __init__(self, owner):
|
275 | """
|
276 | Args:
|
277 | owner: The Template instance that owns this formatter. (There should be
|
278 | exactly one)
|
279 | """
|
280 | self.owner = owner
|
281 |
|
282 | def LookupWithType(self, user_str):
|
283 | """
|
284 | Returns:
|
285 | ref: Either a template instance (itself) or _TemplateRef
|
286 | """
|
287 | prefix = 'template '
|
288 | ref = None # fail the lookup by default
|
289 | if user_str.startswith(prefix):
|
290 | name = user_str[len(prefix):]
|
291 | if name == 'SELF':
|
292 | # we can resolve this right away
|
293 | ref = _TemplateRef(template=self.owner) # special value
|
294 | else:
|
295 | ref = _TemplateRef(name)
|
296 |
|
297 | return ref, (), TEMPLATE_FORMATTER
|
298 |
|
299 |
|
300 | class ChainedRegistry(FunctionRegistry):
|
301 | """Look up functions in chain of other FunctionRegistry instances."""
|
302 |
|
303 | def __init__(self, registries):
|
304 | self.registries = registries
|
305 |
|
306 | def LookupWithType(self, user_str):
|
307 | for registry in self.registries:
|
308 | func, args, func_type = registry.LookupWithType(user_str)
|
309 | if func:
|
310 | return func, args, func_type
|
311 |
|
312 | # Nothing found
|
313 | return None, None, SIMPLE_FUNC
|
314 |
|
315 |
|
316 | class _ProgramBuilder(object):
|
317 | """
|
318 | Receives method calls from the parser, and constructs a tree of _Section()
|
319 | instances.
|
320 | """
|
321 |
|
322 | def __init__(self, formatters, predicates, template_registry):
|
323 | """
|
324 | Args:
|
325 | formatters: See docstring for _CompileTemplate
|
326 | predicates: See docstring for _CompileTemplate
|
327 | """
|
328 | self.current_section = _Section(None)
|
329 | self.stack = [self.current_section]
|
330 |
|
331 | # Passing a dictionary or a function is often more convenient than making a
|
332 | # FunctionRegistry
|
333 | if isinstance(formatters, dict):
|
334 | formatters = DictRegistry(formatters)
|
335 | elif callable(formatters):
|
336 | formatters = CallableRegistry(formatters)
|
337 |
|
338 | # default formatters with arguments
|
339 | default_formatters = PrefixRegistry([
|
340 | ('pluralize', _Pluralize),
|
341 | ('cycle', _Cycle),
|
342 | # These have to go first because 'strftime' is a prefix of
|
343 | # strftime-local/gm!
|
344 | ('strftime-local', _StrftimeLocal), # local
|
345 | ('strftime-gm', _StrftimeGm), # world
|
346 | ('strftime', _StrftimeLocal), # local by default
|
347 | ])
|
348 |
|
349 | # First consult user formatters, then templates enabled by
|
350 | # MakeTemplateGroup, then the default formatters
|
351 | self.formatters = ChainedRegistry([
|
352 | formatters,
|
353 | template_registry, # returns _TemplateRef instances
|
354 | DictRegistry(_DEFAULT_FORMATTERS),
|
355 | default_formatters])
|
356 |
|
357 | # Same for predicates
|
358 | if isinstance(predicates, dict):
|
359 | predicates = DictRegistry(predicates)
|
360 | elif callable(predicates):
|
361 | predicates = CallableRegistry(predicates)
|
362 |
|
363 | # default predicates with arguments
|
364 | default_predicates = PrefixRegistry([
|
365 | ('test', _TestAttribute),
|
366 | ('template', _TemplateExists),
|
367 | ])
|
368 |
|
369 | self.predicates = ChainedRegistry(
|
370 | [predicates, DictRegistry(_DEFAULT_PREDICATES), default_predicates])
|
371 |
|
372 | def Append(self, statement):
|
373 | """
|
374 | Args:
|
375 | statement: Append a literal
|
376 | """
|
377 | self.current_section.Append(statement)
|
378 |
|
379 | def _GetFormatter(self, format_str):
|
380 | """
|
381 | The user's formatters are consulted first, then the default formatters.
|
382 | """
|
383 | formatter, args, func_type = self.formatters.LookupWithType(format_str)
|
384 | if formatter:
|
385 | return formatter, args, func_type
|
386 | else:
|
387 | raise BadFormatter('%r is not a valid formatter' % format_str)
|
388 |
|
389 | def _GetPredicate(self, pred_str, test_attr=False):
|
390 | """
|
391 | The user's predicates are consulted first, then the default predicates.
|
392 | """
|
393 | predicate, args, func_type = self.predicates.LookupWithType(pred_str)
|
394 | if predicate:
|
395 | pred = predicate, args, func_type
|
396 | else:
|
397 | # Nicer syntax, {.debug?} is shorthand for {.if test debug}.
|
398 | # Currently there is not if/elif chain; just use
|
399 | # {.if test debug} {.or test release} {.or} {.end}
|
400 | if test_attr:
|
401 | assert pred_str.endswith('?')
|
402 | # func, args, func_type
|
403 | pred = (_TestAttribute, (pred_str[:-1],), ENHANCED_FUNC)
|
404 | else:
|
405 | raise BadPredicate('%r is not a valid predicate' % pred_str)
|
406 | return pred
|
407 |
|
408 | def AppendSubstitution(self, name, formatters):
|
409 | formatters = [self._GetFormatter(f) for f in formatters]
|
410 | self.current_section.Append((_DoSubstitute, (name, formatters)))
|
411 |
|
412 | def AppendTemplateSubstitution(self, name):
|
413 | # {.template BODY} is semantically something like {$|template BODY}, where $
|
414 | # is the root
|
415 | formatters = [self._GetFormatter('template ' + name)]
|
416 | # None as the name indicates we use the context.Root()
|
417 | self.current_section.Append((_DoSubstitute, (None, formatters)))
|
418 |
|
419 | def _NewSection(self, func, new_block):
|
420 | self.current_section.Append((func, new_block))
|
421 | self.stack.append(new_block)
|
422 | self.current_section = new_block
|
423 |
|
424 | def NewSection(self, token_type, section_name, pre_formatters):
|
425 | """For sections or repeated sections."""
|
426 | pre_formatters = [self._GetFormatter(f) for f in pre_formatters]
|
427 |
|
428 | # TODO: Consider getting rid of this dispatching, and turn _Do* into methods
|
429 | if token_type == REPEATED_SECTION_TOKEN:
|
430 | new_block = _RepeatedSection(section_name, pre_formatters)
|
431 | func = _DoRepeatedSection
|
432 | elif token_type == SECTION_TOKEN:
|
433 | new_block = _Section(section_name, pre_formatters)
|
434 | func = _DoSection
|
435 | elif token_type == DEF_TOKEN:
|
436 | new_block = _Section(section_name, [])
|
437 | func = _DoDef
|
438 | else:
|
439 | raise AssertionError('Invalid token type %s' % token_type)
|
440 |
|
441 | self._NewSection(func, new_block)
|
442 |
|
443 | def NewOrClause(self, pred_str):
|
444 | """
|
445 | {.or ...} Can appear inside predicate blocks or section blocks, with
|
446 | slightly different meaning.
|
447 | """
|
448 | if pred_str:
|
449 | pred = self._GetPredicate(pred_str, test_attr=False)
|
450 | else:
|
451 | pred = None
|
452 | self.current_section.NewOrClause(pred)
|
453 |
|
454 | def AlternatesWith(self):
|
455 | self.current_section.AlternatesWith()
|
456 |
|
457 | def NewPredicateSection(self, pred_str, test_attr=False):
|
458 | """For chains of predicate clauses."""
|
459 | pred = self._GetPredicate(pred_str, test_attr=test_attr)
|
460 | block = _PredicateSection()
|
461 | block.NewOrClause(pred)
|
462 |
|
463 | self._NewSection(_DoPredicates, block)
|
464 |
|
465 | def EndSection(self):
|
466 | self.stack.pop()
|
467 | self.current_section = self.stack[-1]
|
468 |
|
469 | def Root(self):
|
470 | # It's assumed that we call this at the end of the program
|
471 | return self.current_section
|
472 |
|
473 |
|
474 | class _AbstractSection(object):
|
475 |
|
476 | def __init__(self):
|
477 | # Pairs of func, args, or a literal string
|
478 | self.current_clause = []
|
479 |
|
480 | def Append(self, statement):
|
481 | """Append a statement to this block."""
|
482 | self.current_clause.append(statement)
|
483 |
|
484 | def AlternatesWith(self):
|
485 | raise TemplateSyntaxError(
|
486 | '{.alternates with} can only appear with in {.repeated section ...}')
|
487 |
|
488 | def NewOrClause(self, pred_str):
|
489 | raise NotImplementedError
|
490 |
|
491 |
|
492 | class _Section(_AbstractSection):
|
493 | """Represents a (repeated) section."""
|
494 |
|
495 | def __init__(self, section_name, pre_formatters=[]):
|
496 | """
|
497 | Args:
|
498 | section_name: name given as an argument to the section, None for the root
|
499 | section
|
500 | pre_formatters: List of formatters to be applied to the data dictinoary
|
501 | before the expansion
|
502 | """
|
503 | _AbstractSection.__init__(self)
|
504 | self.section_name = section_name
|
505 | self.pre_formatters = pre_formatters
|
506 |
|
507 | # Clauses is just a string and a list of statements.
|
508 | self.statements = {'default': self.current_clause}
|
509 |
|
510 | def __repr__(self):
|
511 | return '<Section %s>' % self.section_name
|
512 |
|
513 | def Statements(self, clause='default'):
|
514 | return self.statements.get(clause, [])
|
515 |
|
516 | def NewOrClause(self, pred):
|
517 | if pred:
|
518 | raise TemplateSyntaxError(
|
519 | '{.or} clause only takes a predicate inside predicate blocks')
|
520 | self.current_clause = []
|
521 | self.statements['or'] = self.current_clause
|
522 |
|
523 |
|
524 | class _RepeatedSection(_Section):
|
525 | """Repeated section is like section, but it supports {.alternates with}"""
|
526 |
|
527 | def AlternatesWith(self):
|
528 | self.current_clause = []
|
529 | self.statements['alternates with'] = self.current_clause
|
530 |
|
531 |
|
532 | class _PredicateSection(_AbstractSection):
|
533 | """Represents a sequence of predicate clauses."""
|
534 |
|
535 | def __init__(self):
|
536 | _AbstractSection.__init__(self)
|
537 | # List of func, statements
|
538 | self.clauses = []
|
539 |
|
540 | def NewOrClause(self, pred):
|
541 | # {.or} always executes if reached
|
542 | pred = pred or (lambda x: True, None, SIMPLE_FUNC) # 3-tuple
|
543 | self.current_clause = []
|
544 | self.clauses.append((pred, self.current_clause))
|
545 |
|
546 |
|
547 | class _Frame(object):
|
548 | """A stack frame."""
|
549 |
|
550 | def __init__(self, context, index=-1):
|
551 | # Public attributes
|
552 | self.context = context
|
553 | self.index = index # An iteration index. -1 means we're NOT iterating.
|
554 |
|
555 | def __str__(self):
|
556 | return 'Frame %s (%s)' % (self.context, self.index)
|
557 |
|
558 |
|
559 | class _ScopedContext(object):
|
560 | """Allows scoped lookup of variables.
|
561 |
|
562 | If the variable isn't in the current context, then we search up the stack.
|
563 | This object also stores the group.
|
564 | """
|
565 |
|
566 | def __init__(self, context, undefined_str, group=None):
|
567 | """
|
568 | Args:
|
569 | context: The root context
|
570 | undefined_str: See Template() constructor.
|
571 | group: Used by the {.if template FOO} predicate, and _DoSubstitute
|
572 | which is passed the context.
|
573 | """
|
574 | self.stack = [_Frame(context)]
|
575 | self.undefined_str = undefined_str
|
576 | self.group = group # used by _DoSubstitute?
|
577 | self.root = context
|
578 |
|
579 | def Root(self):
|
580 | """For {.template FOO} substitution."""
|
581 | return self.root
|
582 |
|
583 | def HasTemplate(self, name):
|
584 | if not self.group: # Could be None?
|
585 | return False
|
586 | return name in self.group
|
587 |
|
588 | def PushSection(self, name, pre_formatters):
|
589 | """Given a section name, push it on the top of the stack.
|
590 |
|
591 | Returns:
|
592 | The new section, or None if there is no such section.
|
593 | """
|
594 | if name == '@':
|
595 | value = self.stack[-1].context
|
596 | else:
|
597 | top = self.stack[-1].context
|
598 | try:
|
599 | value = top.get(name)
|
600 | except AttributeError: # no .get()
|
601 | raise EvaluationError(
|
602 | "Can't get name %r from top value %s" % (name, top))
|
603 |
|
604 | # Apply pre-formatters
|
605 | for i, (f, args, formatter_type) in enumerate(pre_formatters):
|
606 | if formatter_type == ENHANCED_FUNC:
|
607 | value = f(value, self, args)
|
608 | elif formatter_type == SIMPLE_FUNC:
|
609 | value = f(value)
|
610 | else:
|
611 | assert False, 'Invalid formatter type %r' % formatter_type
|
612 |
|
613 | self.stack.append(_Frame(value))
|
614 | return value
|
615 |
|
616 | def Pop(self):
|
617 | self.stack.pop()
|
618 |
|
619 | def Next(self):
|
620 | """Advance to the next item in a repeated section.
|
621 |
|
622 | Raises:
|
623 | StopIteration if there are no more elements
|
624 | """
|
625 | stacktop = self.stack[-1]
|
626 |
|
627 | # Now we're iterating -- push a new mutable object onto the stack
|
628 | if stacktop.index == -1:
|
629 | stacktop = _Frame(None, index=0)
|
630 | self.stack.append(stacktop)
|
631 |
|
632 | context_array = self.stack[-2].context
|
633 |
|
634 | if stacktop.index == len(context_array):
|
635 | self.stack.pop()
|
636 | raise StopIteration
|
637 |
|
638 | stacktop.context = context_array[stacktop.index]
|
639 | stacktop.index += 1
|
640 |
|
641 | return True # OK, we mutated the stack
|
642 |
|
643 | def _Undefined(self, name):
|
644 | if self.undefined_str is None:
|
645 | raise UndefinedVariable('%r is not defined' % name)
|
646 | else:
|
647 | return self.undefined_str
|
648 |
|
649 | def _LookUpStack(self, name):
|
650 | """Look up the stack for the given name."""
|
651 | i = len(self.stack) - 1
|
652 | while 1:
|
653 | frame = self.stack[i]
|
654 | if name == '@index':
|
655 | if frame.index != -1: # -1 is undefined
|
656 | return frame.index # @index is 1-based
|
657 | else:
|
658 | context = frame.context
|
659 | if hasattr(context, 'get'): # Can't look up names in a list or atom
|
660 | try:
|
661 | return context[name]
|
662 | except KeyError:
|
663 | pass
|
664 |
|
665 | i -= 1 # Next frame
|
666 | if i <= -1: # Couldn't find it anywhere
|
667 | return self._Undefined(name)
|
668 |
|
669 | def Lookup(self, name):
|
670 | """Get the value associated with a name in the current context.
|
671 |
|
672 | The current context could be an dictionary in a list, or a dictionary
|
673 | outside a list.
|
674 |
|
675 | Args:
|
676 | name: name to lookup, e.g. 'foo' or 'foo.bar.baz'
|
677 |
|
678 | Returns:
|
679 | The value, or self.undefined_str
|
680 |
|
681 | Raises:
|
682 | UndefinedVariable if self.undefined_str is not set
|
683 | """
|
684 | if name == '@':
|
685 | return self.stack[-1].context
|
686 |
|
687 | parts = name.split('.')
|
688 | value = self._LookUpStack(parts[0])
|
689 |
|
690 | # Now do simple lookups of the rest of the parts
|
691 | for part in parts[1:]:
|
692 | try:
|
693 | value = value[part]
|
694 | except (KeyError, TypeError): # TypeError for non-dictionaries
|
695 | return self._Undefined(part)
|
696 |
|
697 | return value
|
698 |
|
699 |
|
700 | def _ToString(x):
|
701 | """The default default formatter!."""
|
702 | # Some cross-language values for primitives. This is tested in
|
703 | # jsontemplate_test.py.
|
704 | if x is None:
|
705 | return 'null'
|
706 | if isinstance(x, basestring):
|
707 | return x
|
708 | return pprint.pformat(x)
|
709 |
|
710 |
|
711 | # NOTE: We could consider making formatters act on strings only avoid this
|
712 | # repetitiveness. But I wanted to leave open the possibility of doing stuff
|
713 | # like {number|increment-by 1}, where formatters take and return integers.
|
714 | def _Html(x):
|
715 | # If it's not string or unicode, make it a string
|
716 | if not isinstance(x, basestring):
|
717 | x = str(x)
|
718 | return cgi.escape(x)
|
719 |
|
720 |
|
721 | def _HtmlAttrValue(x):
|
722 | # If it's not string or unicode, make it a string
|
723 | if not isinstance(x, basestring):
|
724 | x = str(x)
|
725 | return cgi.escape(x, quote=True)
|
726 |
|
727 |
|
728 | def _AbsUrl(relative_url, context, unused_args):
|
729 | """Returns an absolute URL, given the current node as a relative URL.
|
730 |
|
731 | Assumes that the context has a value named 'base-url'. This is a little like
|
732 | the HTML <base> tag, but implemented with HTML generation.
|
733 |
|
734 | Raises:
|
735 | UndefinedVariable if 'base-url' doesn't exist
|
736 | """
|
737 | # urljoin is flexible about trailing/leading slashes -- it will add or de-dupe
|
738 | # them
|
739 | return urlparse.urljoin(context.Lookup('base-url'), relative_url)
|
740 |
|
741 |
|
742 | def _Reverse(x):
|
743 | """
|
744 | We use this on lists as section pre-formatters; it probably works for
|
745 | strings too.
|
746 | """
|
747 | return list(reversed(x))
|
748 |
|
749 |
|
750 | def _Pairs(data):
|
751 | """dictionary -> list of pairs"""
|
752 | keys = sorted(data)
|
753 | return [{'@key': k, '@value': data[k]} for k in keys]
|
754 |
|
755 |
|
756 | # See http://google-ctemplate.googlecode.com/svn/trunk/doc/howto.html for more
|
757 | # escape types.
|
758 | #
|
759 | # Also, we might want to take a look at Django filters.
|
760 | #
|
761 | # This is a *public* constant, so that callers can use it construct their own
|
762 | # formatter lookup dictionaries, and pass them in to Template.
|
763 | _DEFAULT_FORMATTERS = {
|
764 | # Because "html" is often the default formatter, we want to let
|
765 | # numbers/boolean/etc. pass through, so we add 'str' first.
|
766 | 'html': _Html,
|
767 |
|
768 | # The 'htmltag' name is deprecated. The html-attr-value name is preferred
|
769 | # because it can be read with "as":
|
770 | # {url|html-attr-value} means:
|
771 | # "substitute 'url' as an HTML attribute value"
|
772 | 'html-attr-value': _HtmlAttrValue,
|
773 | 'htmltag': _HtmlAttrValue,
|
774 |
|
775 | 'raw': lambda x: x,
|
776 | # Used for the length of a list. Can be used for the size of a dictionary
|
777 | # too, though I haven't run into that use case.
|
778 | 'size': lambda value: str(len(value)),
|
779 |
|
780 | # The argument is a dictionary, and we get a a=1&b=2 string back.
|
781 | 'url-params': lambda x: urllib.urlencode(x, doseq=True),
|
782 |
|
783 | # The argument is an atom, and it takes 'Search query?' -> 'Search+query%3F'
|
784 | 'url-param-value': urllib.quote_plus, # param is an atom
|
785 |
|
786 | # The default formatter, when no other default is specifier. For debugging,
|
787 | # this could be lambda x: json.dumps(x, indent=2), but here we want to be
|
788 | # compatible to Python 2.4.
|
789 | 'str': _ToString,
|
790 | # Python-specific representation for safely debugging any value as an ASCII
|
791 | # string (unicode can cause issues when using 'str')
|
792 | 'repr': repr,
|
793 |
|
794 | 'upper': lambda x: x.upper(),
|
795 | 'lower': lambda x: x.lower(),
|
796 |
|
797 | # Just show a plain URL on an HTML page (without anchor text).
|
798 | 'plain-url': lambda x: '<a href="%s">%s</a>' % (
|
799 | cgi.escape(x, quote=True), cgi.escape(x)),
|
800 |
|
801 | # A context formatter
|
802 | 'AbsUrl': _AbsUrl,
|
803 |
|
804 | # Placeholders for "standard names". We're not including them by default
|
805 | # since they require additional dependencies. We can provide a part of the
|
806 | # "lookup chain" in formatters.py for people people want the dependency.
|
807 |
|
808 | # 'json' formats arbitrary data dictionary nodes as JSON strings. 'json'
|
809 | # and 'js-string' are identical (since a JavaScript string *is* JSON). The
|
810 | # latter is meant to be serve as extra documentation when you want a string
|
811 | # argument only, which is a common case.
|
812 | 'json': None,
|
813 | 'js-string': None,
|
814 |
|
815 | 'reverse': _Reverse,
|
816 |
|
817 | # Given a dictinonary, returns a *list* of key-value pairs. Used for
|
818 | # section formatters. See jsontemplate_test.py for usage.
|
819 | #
|
820 | # The name "pairs" is meant to be language-independent, so it makes sense
|
821 | # in JavaScript, etc.
|
822 | 'pairs': _Pairs,
|
823 | }
|
824 |
|
825 |
|
826 | def _Pluralize(value, unused_context, args):
|
827 | """Formatter to pluralize words."""
|
828 |
|
829 | if len(args) == 0:
|
830 | s, p = '', 's'
|
831 | elif len(args) == 1:
|
832 | s, p = '', args[0]
|
833 | elif len(args) == 2:
|
834 | s, p = args
|
835 | else:
|
836 | # Should have been checked at compile time
|
837 | raise AssertionError
|
838 |
|
839 | if value > 1:
|
840 | return p
|
841 | else:
|
842 | return s
|
843 |
|
844 |
|
845 | def _Cycle(value, unused_context, args):
|
846 | """Cycle between various values on consecutive integers."""
|
847 | # @index starts from 1, so used 1-based indexing
|
848 | return args[(value - 1) % len(args)]
|
849 |
|
850 |
|
851 | def _StrftimeHelper(args, time_tuple):
|
852 | try:
|
853 | format_str = args[0]
|
854 | except IndexError:
|
855 | # If no format string, use some reasonable text format
|
856 | return time.asctime(time_tuple)
|
857 | else:
|
858 | return time.strftime(format_str, time_tuple)
|
859 |
|
860 |
|
861 | def _StrftimeGm(value, unused_context, args):
|
862 | """Convert a timestamp in seconds to a string based on the format string.
|
863 |
|
864 | Returns GM time.
|
865 | """
|
866 | time_tuple = time.gmtime(value)
|
867 | return _StrftimeHelper(args, time_tuple)
|
868 |
|
869 |
|
870 | def _StrftimeLocal(value, unused_context, args):
|
871 | """Convert a timestamp in seconds to a string based on the format string.
|
872 |
|
873 | Returns local time.
|
874 | """
|
875 | time_tuple = time.localtime(value)
|
876 | return _StrftimeHelper(args, time_tuple)
|
877 |
|
878 |
|
879 | def _IsDebugMode(unused_value, context, unused_args):
|
880 | return _TestAttribute(unused_value, context, ('debug',))
|
881 |
|
882 |
|
883 | def _TestAttribute(unused_value, context, args):
|
884 | """Cycle between various values on consecutive integers."""
|
885 | try:
|
886 | name = args[0] # can used dotted syntax too, e.g. 'foo.bar'
|
887 | except IndexError:
|
888 | raise EvaluationError('The "test" predicate requires an argument.')
|
889 | try:
|
890 | return bool(context.Lookup(name))
|
891 | except UndefinedVariable:
|
892 | return False
|
893 |
|
894 |
|
895 | def _TemplateExists(unused_value, context, args):
|
896 | """Returns whether the given name is in the current Template's template group."""
|
897 | try:
|
898 | name = args[0]
|
899 | except IndexError:
|
900 | raise EvaluationError('The "template" predicate requires an argument.')
|
901 | return context.HasTemplate(name)
|
902 |
|
903 |
|
904 | _SINGULAR = lambda x: x == 1
|
905 | _PLURAL = lambda x: x > 1
|
906 |
|
907 | _DEFAULT_PREDICATES = {
|
908 | # OLD, for backward compatibility: these are discouraged
|
909 | 'singular?': _SINGULAR,
|
910 | 'plural?': _PLURAL,
|
911 | 'Debug?': _IsDebugMode, # Also OLD
|
912 |
|
913 | 'singular': _SINGULAR,
|
914 | 'plural': _PLURAL,
|
915 | }
|
916 |
|
917 |
|
918 | def SplitMeta(meta):
|
919 | """Split and validate metacharacters.
|
920 |
|
921 | Example: '{}' -> ('{', '}')
|
922 |
|
923 | This is public so the syntax highlighter and other tools can use it.
|
924 | """
|
925 | n = len(meta)
|
926 | if n % 2 == 1:
|
927 | raise ConfigurationError(
|
928 | '%r has an odd number of metacharacters' % meta)
|
929 | return meta[:n/2], meta[n/2:]
|
930 |
|
931 |
|
932 | _token_re_cache = {}
|
933 |
|
934 | def MakeTokenRegex(meta_left, meta_right):
|
935 | """Return a (compiled) regular expression for tokenization.
|
936 |
|
937 | Args:
|
938 | meta_left, meta_right: e.g. '{' and '}'
|
939 |
|
940 | - The regular expressions are memoized.
|
941 | - This function is public so the syntax highlighter can use it.
|
942 | """
|
943 | key = meta_left, meta_right
|
944 | if key not in _token_re_cache:
|
945 | # - Need () grouping for re.split
|
946 | # - The first character must be a non-space. This allows us to ignore
|
947 | # literals like function() { return 1; } when
|
948 | # - There must be at least one (non-space) character inside {}
|
949 | _token_re_cache[key] = re.compile(
|
950 | r'(' +
|
951 | re.escape(meta_left) +
|
952 | r'\S.*?' +
|
953 | re.escape(meta_right) +
|
954 | r')')
|
955 | return _token_re_cache[key]
|
956 |
|
957 |
|
958 | # Examples:
|
959 |
|
960 | ( LITERAL_TOKEN, # "Hi"
|
961 | META_LITERAL_TOKEN, # {.space}, etc.
|
962 |
|
963 | SUBST_TOKEN, # {var|html}
|
964 | SECTION_TOKEN, # {.section name}
|
965 | REPEATED_SECTION_TOKEN, # {.repeated section name}
|
966 | PREDICATE_TOKEN, # {.predicate?}
|
967 | IF_TOKEN, # {.if predicate}
|
968 | ALTERNATES_TOKEN, # {.or}
|
969 | OR_TOKEN, # {.or}
|
970 | END_TOKEN, # {.end}
|
971 |
|
972 | SUBST_TEMPLATE_TOKEN, # {.template TITLE}
|
973 | DEF_TOKEN, # {.define TITLE}
|
974 |
|
975 | COMMENT_BEGIN_TOKEN, # {##BEGIN}
|
976 | COMMENT_END_TOKEN, # {##END}
|
977 | ) = range(14)
|
978 |
|
979 | COMMENT_BEGIN = '##BEGIN'
|
980 | COMMENT_END = '##END'
|
981 |
|
982 | OPTION_STRIP_LINE = '.OPTION strip-line'
|
983 | OPTION_END = '.END'
|
984 |
|
985 |
|
986 | def _MatchDirective(token):
|
987 | """Helper function for matching certain directives."""
|
988 | # Tokens below must start with '.'
|
989 | if token.startswith('.'):
|
990 | token = token[1:]
|
991 | else:
|
992 | return None, None
|
993 |
|
994 | if token == 'end':
|
995 | return END_TOKEN, None
|
996 |
|
997 | if token == 'alternates with':
|
998 | return ALTERNATES_TOKEN, token
|
999 |
|
1000 | if token.startswith('or'):
|
1001 | if token.strip() == 'or':
|
1002 | return OR_TOKEN, None
|
1003 | else:
|
1004 | pred_str = token[2:].strip()
|
1005 | return OR_TOKEN, pred_str
|
1006 |
|
1007 | match = _SECTION_RE.match(token)
|
1008 | if match:
|
1009 | repeated, section_name = match.groups()
|
1010 | if repeated:
|
1011 | return REPEATED_SECTION_TOKEN, section_name
|
1012 | else:
|
1013 | return SECTION_TOKEN, section_name
|
1014 |
|
1015 | if token.startswith('template '):
|
1016 | return SUBST_TEMPLATE_TOKEN, token[9:].strip()
|
1017 | if token.startswith('define '):
|
1018 | return DEF_TOKEN, token[7:].strip()
|
1019 |
|
1020 | if token.startswith('if '):
|
1021 | return IF_TOKEN, token[3:].strip()
|
1022 | if token.endswith('?'):
|
1023 | return PREDICATE_TOKEN, token
|
1024 |
|
1025 | return None, None # no match
|
1026 |
|
1027 |
|
1028 | def _Tokenize(template_str, meta_left, meta_right, whitespace):
|
1029 | """Yields tokens, which are 2-tuples (TOKEN_TYPE, token_string)."""
|
1030 |
|
1031 | trimlen = len(meta_left)
|
1032 | token_re = MakeTokenRegex(meta_left, meta_right)
|
1033 | do_strip = (whitespace == 'strip-line') # Do this outside loop
|
1034 | do_strip_part = False
|
1035 |
|
1036 | for line in template_str.splitlines(True): # retain newlines
|
1037 | if do_strip or do_strip_part:
|
1038 | line = line.strip()
|
1039 |
|
1040 | tokens = token_re.split(line)
|
1041 |
|
1042 | # Check for a special case first. If a comment or "block" directive is on a
|
1043 | # line by itself (with only space surrounding it), then the space is
|
1044 | # omitted. For simplicity, we don't handle the case where we have 2
|
1045 | # directives, say '{.end} # {#comment}' on a line.
|
1046 | if len(tokens) == 3:
|
1047 | # ''.isspace() == False, so work around that
|
1048 | if (tokens[0].isspace() or not tokens[0]) and \
|
1049 | (tokens[2].isspace() or not tokens[2]):
|
1050 | token = tokens[1][trimlen : -trimlen]
|
1051 |
|
1052 | # Check the ones that begin with ## before #
|
1053 | if token == COMMENT_BEGIN:
|
1054 | yield COMMENT_BEGIN_TOKEN, None
|
1055 | continue
|
1056 | if token == COMMENT_END:
|
1057 | yield COMMENT_END_TOKEN, None
|
1058 | continue
|
1059 | if token == OPTION_STRIP_LINE:
|
1060 | do_strip_part = True
|
1061 | continue
|
1062 | if token == OPTION_END:
|
1063 | do_strip_part = False
|
1064 | continue
|
1065 |
|
1066 | if token.startswith('#'):
|
1067 | continue # The whole line is omitted
|
1068 |
|
1069 | token_type, token = _MatchDirective(token)
|
1070 | if token_type is not None:
|
1071 | yield token_type, token # Only yield the token, not space
|
1072 | continue
|
1073 |
|
1074 | # The line isn't special; process it normally.
|
1075 | for i, token in enumerate(tokens):
|
1076 | if i % 2 == 0:
|
1077 | yield LITERAL_TOKEN, token
|
1078 |
|
1079 | else: # It's a "directive" in metachracters
|
1080 | assert token.startswith(meta_left), repr(token)
|
1081 | assert token.endswith(meta_right), repr(token)
|
1082 | token = token[trimlen : -trimlen]
|
1083 |
|
1084 | # Check the ones that begin with ## before #
|
1085 | if token == COMMENT_BEGIN:
|
1086 | yield COMMENT_BEGIN_TOKEN, None
|
1087 | continue
|
1088 | if token == COMMENT_END:
|
1089 | yield COMMENT_END_TOKEN, None
|
1090 | continue
|
1091 | if token == OPTION_STRIP_LINE:
|
1092 | do_strip_part = True
|
1093 | continue
|
1094 | if token == OPTION_END:
|
1095 | do_strip_part = False
|
1096 | continue
|
1097 |
|
1098 | # A single-line comment
|
1099 | if token.startswith('#'):
|
1100 | continue
|
1101 |
|
1102 | if token.startswith('.'):
|
1103 | literal = {
|
1104 | '.meta-left': meta_left,
|
1105 | '.meta-right': meta_right,
|
1106 | '.space': ' ',
|
1107 | '.tab': '\t',
|
1108 | '.newline': '\n',
|
1109 | }.get(token)
|
1110 |
|
1111 | if literal is not None:
|
1112 | yield META_LITERAL_TOKEN, literal
|
1113 | continue
|
1114 |
|
1115 | token_type, token = _MatchDirective(token)
|
1116 | if token_type is not None:
|
1117 | yield token_type, token
|
1118 |
|
1119 | else: # Now we know the directive is a substitution.
|
1120 | yield SUBST_TOKEN, token
|
1121 |
|
1122 |
|
1123 | def _CompileTemplate(
|
1124 | template_str, builder, meta='{}', format_char='|', default_formatter='str',
|
1125 | whitespace='smart'):
|
1126 | """Compile the template string, calling methods on the 'program builder'.
|
1127 |
|
1128 | Args:
|
1129 | template_str: The template string. It should not have any compilation
|
1130 | options in the header -- those are parsed by FromString/FromFile
|
1131 |
|
1132 | builder: The interface of _ProgramBuilder isn't fixed. Use at your own
|
1133 | risk.
|
1134 |
|
1135 | meta: The metacharacters to use, e.g. '{}', '[]'.
|
1136 |
|
1137 | default_formatter: The formatter to use for substitutions that are missing a
|
1138 | formatter. The 'str' formatter the "default default" -- it just tries
|
1139 | to convert the context value to a string in some unspecified manner.
|
1140 |
|
1141 | whitespace: 'smart' or 'strip-line'. In smart mode, if a directive is alone
|
1142 | on a line, with only whitespace on either side, then the whitespace is
|
1143 | removed. In 'strip-line' mode, every line is stripped of its
|
1144 | leading and trailing whitespace.
|
1145 |
|
1146 | Returns:
|
1147 | The compiled program (obtained from the builder)
|
1148 |
|
1149 | Raises:
|
1150 | The various subclasses of CompilationError. For example, if
|
1151 | default_formatter=None, and a variable is missing a formatter, then
|
1152 | MissingFormatter is raised.
|
1153 |
|
1154 | This function is public so it can be used by other tools, e.g. a syntax
|
1155 | checking tool run before submitting a template to source control.
|
1156 | """
|
1157 | meta_left, meta_right = SplitMeta(meta)
|
1158 |
|
1159 | # : is meant to look like Python 3000 formatting {foo:.3f}. According to
|
1160 | # PEP 3101, that's also what .NET uses.
|
1161 | # | is more readable, but, more importantly, reminiscent of pipes, which is
|
1162 | # useful for multiple formatters, e.g. {name|js-string|html}
|
1163 | if format_char not in (':', '|'):
|
1164 | raise ConfigurationError(
|
1165 | 'Only format characters : and | are accepted (got %r)' % format_char)
|
1166 |
|
1167 | if whitespace not in ('smart', 'strip-line'):
|
1168 | raise ConfigurationError('Invalid whitespace mode %r' % whitespace)
|
1169 |
|
1170 | # If we go to -1, then we got too many {end}. If end at 1, then we're missing
|
1171 | # an {end}.
|
1172 | balance_counter = 0
|
1173 | comment_counter = 0 # ditto for ##BEGIN/##END
|
1174 |
|
1175 | has_defines = False
|
1176 |
|
1177 | for token_type, token in _Tokenize(template_str, meta_left, meta_right,
|
1178 | whitespace):
|
1179 | if token_type == COMMENT_BEGIN_TOKEN:
|
1180 | comment_counter += 1
|
1181 | continue
|
1182 | if token_type == COMMENT_END_TOKEN:
|
1183 | comment_counter -= 1
|
1184 | if comment_counter < 0:
|
1185 | raise CompilationError('Got too many ##END markers')
|
1186 | continue
|
1187 | # Don't process any tokens
|
1188 | if comment_counter > 0:
|
1189 | continue
|
1190 |
|
1191 | if token_type in (LITERAL_TOKEN, META_LITERAL_TOKEN):
|
1192 | if token:
|
1193 | builder.Append(token)
|
1194 | continue
|
1195 |
|
1196 | if token_type in (SECTION_TOKEN, REPEATED_SECTION_TOKEN, DEF_TOKEN):
|
1197 | parts = [p.strip() for p in token.split(format_char)]
|
1198 | if len(parts) == 1:
|
1199 | name = parts[0]
|
1200 | formatters = []
|
1201 | else:
|
1202 | name = parts[0]
|
1203 | formatters = parts[1:]
|
1204 | builder.NewSection(token_type, name, formatters)
|
1205 | balance_counter += 1
|
1206 | if token_type == DEF_TOKEN:
|
1207 | has_defines = True
|
1208 | continue
|
1209 |
|
1210 | if token_type == PREDICATE_TOKEN:
|
1211 | # {.attr?} lookups
|
1212 | builder.NewPredicateSection(token, test_attr=True)
|
1213 | balance_counter += 1
|
1214 | continue
|
1215 |
|
1216 | if token_type == IF_TOKEN:
|
1217 | builder.NewPredicateSection(token, test_attr=False)
|
1218 | balance_counter += 1
|
1219 | continue
|
1220 |
|
1221 | if token_type == OR_TOKEN:
|
1222 | builder.NewOrClause(token)
|
1223 | continue
|
1224 |
|
1225 | if token_type == ALTERNATES_TOKEN:
|
1226 | builder.AlternatesWith()
|
1227 | continue
|
1228 |
|
1229 | if token_type == END_TOKEN:
|
1230 | balance_counter -= 1
|
1231 | if balance_counter < 0:
|
1232 | # TODO: Show some context for errors
|
1233 | raise TemplateSyntaxError(
|
1234 | 'Got too many %send%s statements. You may have mistyped an '
|
1235 | "earlier 'section' or 'repeated section' directive."
|
1236 | % (meta_left, meta_right))
|
1237 | builder.EndSection()
|
1238 | continue
|
1239 |
|
1240 | if token_type == SUBST_TOKEN:
|
1241 | parts = [p.strip() for p in token.split(format_char)]
|
1242 | if len(parts) == 1:
|
1243 | if default_formatter is None:
|
1244 | raise MissingFormatter('This template requires explicit formatters.')
|
1245 | # If no formatter is specified, the default is the 'str' formatter,
|
1246 | # which the user can define however they desire.
|
1247 | name = token
|
1248 | formatters = [default_formatter]
|
1249 | else:
|
1250 | name = parts[0]
|
1251 | formatters = parts[1:]
|
1252 |
|
1253 | builder.AppendSubstitution(name, formatters)
|
1254 | continue
|
1255 |
|
1256 | if token_type == SUBST_TEMPLATE_TOKEN:
|
1257 | # no formatters
|
1258 | builder.AppendTemplateSubstitution(token)
|
1259 | continue
|
1260 |
|
1261 | if balance_counter != 0:
|
1262 | raise TemplateSyntaxError('Got too few %send%s statements' %
|
1263 | (meta_left, meta_right))
|
1264 | if comment_counter != 0:
|
1265 | raise CompilationError('Got %d more {##BEGIN}s than {##END}s' % comment_counter)
|
1266 |
|
1267 | return builder.Root(), has_defines
|
1268 |
|
1269 |
|
1270 | _OPTION_RE = re.compile(r'^([a-zA-Z\-]+):\s*(.*)')
|
1271 | _OPTION_NAMES = ['meta', 'format-char', 'default-formatter', 'undefined-str',
|
1272 | 'whitespace']
|
1273 |
|
1274 |
|
1275 | def FromString(s, **kwargs):
|
1276 | """Like FromFile, but takes a string."""
|
1277 |
|
1278 | f = StringIO.StringIO(s)
|
1279 | return FromFile(f, **kwargs)
|
1280 |
|
1281 |
|
1282 | def FromFile(f, more_formatters=lambda x: None, more_predicates=lambda x: None,
|
1283 | _constructor=None):
|
1284 | """Parse a template from a file, using a simple file format.
|
1285 |
|
1286 | This is useful when you want to include template options in a data file,
|
1287 | rather than in the source code.
|
1288 |
|
1289 | The format is similar to HTTP or E-mail headers. The first lines of the file
|
1290 | can specify template options, such as the metacharacters to use. One blank
|
1291 | line must separate the options from the template body.
|
1292 |
|
1293 | Example:
|
1294 |
|
1295 | default-formatter: none
|
1296 | meta: {{}}
|
1297 | format-char: :
|
1298 | <blank line required>
|
1299 | Template goes here: {{variable:html}}
|
1300 |
|
1301 | Args:
|
1302 | f: A file handle to read from. Caller is responsible for opening and
|
1303 | closing it.
|
1304 | """
|
1305 | _constructor = _constructor or Template
|
1306 |
|
1307 | options = {}
|
1308 |
|
1309 | # Parse lines until the first one that doesn't look like an option
|
1310 | while 1:
|
1311 | line = f.readline()
|
1312 | match = _OPTION_RE.match(line)
|
1313 | if match:
|
1314 | name, value = match.group(1), match.group(2)
|
1315 |
|
1316 | # Accept something like 'Default-Formatter: raw'. This syntax is like
|
1317 | # HTTP/E-mail headers.
|
1318 | name = name.lower()
|
1319 | # In Python 2.4, kwargs must be plain strings
|
1320 | name = name.encode('utf-8')
|
1321 |
|
1322 | if name in _OPTION_NAMES:
|
1323 | name = name.replace('-', '_')
|
1324 | value = value.strip()
|
1325 | if name == 'default_formatter' and value.lower() == 'none':
|
1326 | value = None
|
1327 | options[name] = value
|
1328 | else:
|
1329 | break
|
1330 | else:
|
1331 | break
|
1332 |
|
1333 | if options:
|
1334 | if line.strip():
|
1335 | raise CompilationError(
|
1336 | 'Must be one blank line between template options and body (got %r)'
|
1337 | % line)
|
1338 | body = f.read()
|
1339 | else:
|
1340 | # There were no options, so no blank line is necessary.
|
1341 | body = line + f.read()
|
1342 |
|
1343 | return _constructor(body,
|
1344 | more_formatters=more_formatters,
|
1345 | more_predicates=more_predicates,
|
1346 | **options)
|
1347 |
|
1348 |
|
1349 | class Template(object):
|
1350 | """Represents a compiled template.
|
1351 |
|
1352 | Like many template systems, the template string is compiled into a program,
|
1353 | and then it can be expanded any number of times. For example, in a web app,
|
1354 | you can compile the templates once at server startup, and use the expand()
|
1355 | method at request handling time. expand() uses the compiled representation.
|
1356 |
|
1357 | There are various options for controlling parsing -- see _CompileTemplate.
|
1358 | Don't go crazy with metacharacters. {}, [], {{}} or <> should cover nearly
|
1359 | any circumstance, e.g. generating HTML, CSS XML, JavaScript, C programs, text
|
1360 | files, etc.
|
1361 | """
|
1362 |
|
1363 | def __init__(self, template_str,
|
1364 | more_formatters=lambda x: None,
|
1365 | more_predicates=lambda x: None,
|
1366 | undefined_str=None,
|
1367 | **compile_options):
|
1368 | """
|
1369 | Args:
|
1370 | template_str: The template string.
|
1371 |
|
1372 | more_formatters:
|
1373 | Something that can map format strings to formatter functions. One of:
|
1374 | - A plain dictionary of names -> functions e.g. {'html': cgi.escape}
|
1375 | - A higher-order function which takes format strings and returns
|
1376 | formatter functions. Useful for when formatters have parsed
|
1377 | arguments.
|
1378 | - A FunctionRegistry instance, giving the most control. This allows
|
1379 | formatters which takes contexts as well.
|
1380 |
|
1381 | more_predicates:
|
1382 | Like more_formatters, but for predicates.
|
1383 |
|
1384 | undefined_str: A string to appear in the output when a variable to be
|
1385 | substituted is missing. If None, UndefinedVariable is raised.
|
1386 | (Note: This is not really a compilation option, because affects
|
1387 | template expansion rather than compilation. Nonetheless we make it a
|
1388 | constructor argument rather than an .expand() argument for
|
1389 | simplicity.)
|
1390 |
|
1391 | It also accepts all the compile options that _CompileTemplate does.
|
1392 | """
|
1393 | r = _TemplateRegistry(self)
|
1394 | self.undefined_str = undefined_str
|
1395 | self.group = {} # optionally updated by _UpdateTemplateGroup
|
1396 | builder = _ProgramBuilder(more_formatters, more_predicates, r)
|
1397 | # None used by _FromSection
|
1398 | if template_str is not None:
|
1399 | self._program, self.has_defines = _CompileTemplate(
|
1400 | template_str, builder, **compile_options)
|
1401 | self.group = _MakeGroupFromRootSection(self._program, self.undefined_str)
|
1402 |
|
1403 | @staticmethod
|
1404 | def _FromSection(section, group, undefined_str):
|
1405 | t = Template(None, undefined_str=undefined_str)
|
1406 | t._program = section
|
1407 | t.has_defines = False
|
1408 | # This "subtemplate" needs the group too for its own references
|
1409 | t.group = group
|
1410 | return t
|
1411 |
|
1412 | def _Statements(self):
|
1413 | # for execute_with_style
|
1414 | return self._program.Statements()
|
1415 |
|
1416 | def _UpdateTemplateGroup(self, group):
|
1417 | """Allow this template to reference templates in the group.
|
1418 |
|
1419 | Args:
|
1420 | group: dictionary of template name -> compiled Template instance
|
1421 | """
|
1422 | # TODO: Re-enable when Poly is converted
|
1423 | #if self.has_defines:
|
1424 | # raise UsageError(
|
1425 | # "Can't make a template group out of a template with {.define}.")
|
1426 | bad = []
|
1427 | for name in group:
|
1428 | if name in self.group:
|
1429 | bad.append(name)
|
1430 | if bad:
|
1431 | raise UsageError(
|
1432 | "This template already has these named templates defined: %s" % bad)
|
1433 | self.group.update(group)
|
1434 |
|
1435 | def _CheckRefs(self):
|
1436 | """Check that the template names referenced in this template exist."""
|
1437 | # TODO: Implement this.
|
1438 | # This is called by MakeTemplateGroup.
|
1439 | # We would walk the program Statements() tree, look for name=None
|
1440 | # substitutions, with a template formatter, and call Resolve().
|
1441 |
|
1442 | #
|
1443 | # Public API
|
1444 | #
|
1445 |
|
1446 | def execute(self, data_dict, callback, group=None, trace=None):
|
1447 | """Low level method to expand the template piece by piece.
|
1448 |
|
1449 | Args:
|
1450 | data_dict: The JSON data dictionary.
|
1451 | callback: A callback which should be called with each expanded token.
|
1452 | group: Dictionary of name -> Template instance (for styles)
|
1453 |
|
1454 | Example: You can pass 'f.write' as the callback to write directly to a file
|
1455 | handle.
|
1456 | """
|
1457 | # First try the passed in version, then the one set by _UpdateTemplateGroup.
|
1458 | # May be None. Only one of these should be set.
|
1459 | group = group or self.group
|
1460 | context = _ScopedContext(data_dict, self.undefined_str, group=group)
|
1461 | _Execute(self._program.Statements(), context, callback, trace)
|
1462 |
|
1463 | render = execute # Alias for backward compatibility
|
1464 |
|
1465 | def expand(self, *args, **kwargs):
|
1466 | """Expands the template with the given data dictionary, returning a string.
|
1467 |
|
1468 | This is a small wrapper around execute(), and is the most convenient
|
1469 | interface.
|
1470 |
|
1471 | Args:
|
1472 | data_dict: The JSON data dictionary. Like the builtin dict() constructor,
|
1473 | it can take a single dictionary as a positional argument, or arbitrary
|
1474 | keyword arguments.
|
1475 | trace: Trace object for debugging
|
1476 | style: Template instance to be treated as a style for this template (the
|
1477 | "outside")
|
1478 |
|
1479 | Returns:
|
1480 | The return value could be a str() or unicode() instance, depending on the
|
1481 | the type of the template string passed in, and what the types the strings
|
1482 | in the dictionary are.
|
1483 | """
|
1484 | if args:
|
1485 | if len(args) == 1:
|
1486 | data_dict = args[0]
|
1487 | trace = kwargs.get('trace')
|
1488 | style = kwargs.get('style')
|
1489 | group = kwargs.get('group')
|
1490 | else:
|
1491 | raise TypeError(
|
1492 | 'expand() only takes 1 positional argument (got %s)' % args)
|
1493 | else:
|
1494 | # NOTE: A problem with this style is that passing invalid kwargs is
|
1495 | # silently ignored. It should be expand_with(foo=bar)
|
1496 |
|
1497 | data_dict = kwargs
|
1498 | trace = None # Can't use trace= with the kwargs style
|
1499 | style = None
|
1500 | group = None
|
1501 |
|
1502 | # Try the argument first, then the thing set by MakeTemplateGroup
|
1503 | g = group or self.group
|
1504 |
|
1505 | tokens = []
|
1506 | if style:
|
1507 | style.execute(data_dict, tokens.append, group=g, trace=trace)
|
1508 | else:
|
1509 | # Needs a group to reference its OWN {.define}s
|
1510 | self.execute(data_dict, tokens.append, group=g, trace=trace)
|
1511 |
|
1512 | return JoinTokens(tokens)
|
1513 |
|
1514 | def tokenstream(self, data_dict):
|
1515 | """Yields a list of tokens resulting from expansion.
|
1516 |
|
1517 | This may be useful for WSGI apps. NOTE: In the current implementation, the
|
1518 | entire expanded template must be stored memory.
|
1519 |
|
1520 | NOTE: This is a generator, but JavaScript doesn't have generators.
|
1521 | """
|
1522 | tokens = []
|
1523 | self.execute(data_dict, tokens.append)
|
1524 | for token in tokens:
|
1525 | yield token
|
1526 |
|
1527 |
|
1528 | class Trace(object):
|
1529 | """Trace of execution for JSON Template.
|
1530 |
|
1531 | This object should be passed into the execute/expand() function.
|
1532 |
|
1533 | Useful for debugging, especially for templates which reference other
|
1534 | templates.
|
1535 | """
|
1536 | def __init__(self):
|
1537 | # Public mutable attributes
|
1538 | self.exec_depth = 0
|
1539 | self.template_depth = 0
|
1540 | self.stack = []
|
1541 |
|
1542 | def Push(self, obj):
|
1543 | self.stack.append(obj)
|
1544 |
|
1545 | def Pop(self):
|
1546 | self.stack.pop()
|
1547 |
|
1548 | def __str__(self):
|
1549 | return 'Trace %s %s' % (self.exec_depth, self.template_depth)
|
1550 |
|
1551 |
|
1552 | def _MakeGroupFromRootSection(root_section, undefined_str):
|
1553 | """Construct a dictionary { template name -> Template() instance }
|
1554 |
|
1555 | Args:
|
1556 | root_section: _Section instance -- root of the original parse tree
|
1557 | """
|
1558 | group = {}
|
1559 | for statement in root_section.Statements():
|
1560 | if isinstance(statement, basestring):
|
1561 | continue
|
1562 | func, args = statement
|
1563 | # here the function acts as ID for the block type
|
1564 | if func is _DoDef and isinstance(args, _Section):
|
1565 | section = args
|
1566 | # Construct a Template instance from a this _Section subtree
|
1567 | t = Template._FromSection(section, group, undefined_str)
|
1568 | group[section.section_name] = t
|
1569 | return group
|
1570 |
|
1571 |
|
1572 | def MakeTemplateGroup(group):
|
1573 | """Wire templates together so that they can reference each other by name.
|
1574 |
|
1575 | This is a public API.
|
1576 |
|
1577 | The templates becomes formatters with the 'template' prefix. For example:
|
1578 | {var|template NAME} formats the node 'var' with the template 'NAME'
|
1579 |
|
1580 | Templates may be mutually recursive.
|
1581 |
|
1582 | This function *mutates* all the templates, so you shouldn't call it multiple
|
1583 | times on a single Template() instance. It's possible to put a single template
|
1584 | in multiple groups by creating multiple Template() instances from it.
|
1585 |
|
1586 | Args:
|
1587 | group: dictionary of template name -> compiled Template instance
|
1588 | """
|
1589 | # mutate all of the templates so that they can reference each other
|
1590 | for t in group.itervalues():
|
1591 | t._UpdateTemplateGroup(group)
|
1592 | #t._CheckRefs()
|
1593 |
|
1594 |
|
1595 | def JoinTokens(tokens):
|
1596 | """Join tokens (which may be a mix of unicode and str values).
|
1597 |
|
1598 | See notes on unicode at the top. This function allows mixing encoded utf-8
|
1599 | byte string tokens with unicode tokens. (Python's default encoding is ASCII,
|
1600 | and we don't want to change that.)
|
1601 |
|
1602 | We also want to support pure byte strings, so we can't get rid of the
|
1603 | try/except. Two tries necessary.
|
1604 |
|
1605 | If someone really wanted to use another encoding, they could monkey patch
|
1606 | jsontemplate.JoinTokens (this function).
|
1607 | """
|
1608 | try:
|
1609 | return ''.join(tokens)
|
1610 | except UnicodeDecodeError:
|
1611 | # This can still raise UnicodeDecodeError if that data isn't utf-8.
|
1612 | return ''.join(t.decode('utf-8') for t in tokens)
|
1613 |
|
1614 |
|
1615 | def _DoRepeatedSection(args, context, callback, trace):
|
1616 | """{.repeated section foo}"""
|
1617 |
|
1618 | block = args
|
1619 |
|
1620 | items = context.PushSection(block.section_name, block.pre_formatters)
|
1621 | if items:
|
1622 | if not isinstance(items, list):
|
1623 | raise EvaluationError('Expected a list; got %s' % type(items))
|
1624 |
|
1625 | last_index = len(items) - 1
|
1626 | statements = block.Statements()
|
1627 | alt_statements = block.Statements('alternates with')
|
1628 | try:
|
1629 | i = 0
|
1630 | while True:
|
1631 | context.Next()
|
1632 | # Execute the statements in the block for every item in the list.
|
1633 | # Execute the alternate block on every iteration except the last. Each
|
1634 | # item could be an atom (string, integer, etc.) or a dictionary.
|
1635 | _Execute(statements, context, callback, trace)
|
1636 | if i != last_index:
|
1637 | _Execute(alt_statements, context, callback, trace)
|
1638 | i += 1
|
1639 | except StopIteration:
|
1640 | pass
|
1641 |
|
1642 | else:
|
1643 | _Execute(block.Statements('or'), context, callback, trace)
|
1644 |
|
1645 | context.Pop()
|
1646 |
|
1647 |
|
1648 | def _DoSection(args, context, callback, trace):
|
1649 | """{.section foo}"""
|
1650 | block = args
|
1651 | # If a section present and "true", push the dictionary onto the stack as the
|
1652 | # new context, and show it
|
1653 | if context.PushSection(block.section_name, block.pre_formatters):
|
1654 | _Execute(block.Statements(), context, callback, trace)
|
1655 | context.Pop()
|
1656 | else: # missing or "false" -- show the {.or} section
|
1657 | context.Pop()
|
1658 | _Execute(block.Statements('or'), context, callback, trace)
|
1659 |
|
1660 |
|
1661 | def _DoPredicates(args, context, callback, trace):
|
1662 | """{.predicate?}
|
1663 |
|
1664 | Here we execute the first clause that evaluates to true, and then stop.
|
1665 | """
|
1666 | block = args
|
1667 | value = context.Lookup('@')
|
1668 | for (predicate, args, func_type), statements in block.clauses:
|
1669 | if func_type == ENHANCED_FUNC:
|
1670 | do_clause = predicate(value, context, args)
|
1671 | else:
|
1672 | do_clause = predicate(value)
|
1673 |
|
1674 | if do_clause:
|
1675 | if trace: trace.Push(predicate)
|
1676 | _Execute(statements, context, callback, trace)
|
1677 | if trace: trace.Pop()
|
1678 | break
|
1679 |
|
1680 |
|
1681 | def _DoDef(args, context, callback, trace):
|
1682 | """{.define TITLE}"""
|
1683 | # We do nothing here -- the block is parsed into the template tree, turned
|
1684 | # into a Template() instance, and then the template is called as a formatter
|
1685 | # in _DoSubstitute.
|
1686 |
|
1687 |
|
1688 | def _DoSubstitute(args, context, callback, trace):
|
1689 | """Variable substitution, i.e. {foo}
|
1690 |
|
1691 | We also implement template formatters here, i.e. {foo|template bar} as well
|
1692 | as {.template FOO} for templates that operate on the root of the data dict
|
1693 | rather than a subtree.
|
1694 | """
|
1695 | name, formatters = args
|
1696 |
|
1697 | if name is None:
|
1698 | value = context.Root() # don't use the cursor
|
1699 | else:
|
1700 | try:
|
1701 | value = context.Lookup(name)
|
1702 | except TypeError, e:
|
1703 | raise EvaluationError(
|
1704 | 'Error evaluating %r in context %r: %r' % (name, context, e))
|
1705 |
|
1706 | last_index = len(formatters) - 1
|
1707 | for i, (f, args, formatter_type) in enumerate(formatters):
|
1708 | try:
|
1709 | if formatter_type == TEMPLATE_FORMATTER:
|
1710 | template = f.Resolve(context)
|
1711 | if i == last_index:
|
1712 | # In order to keep less template output in memory, we can just let the
|
1713 | # other template write to our callback directly, and then stop.
|
1714 | template.execute(value, callback, trace=trace)
|
1715 | return # EARLY RETURN
|
1716 | else:
|
1717 | # We have more formatters to apply, so explicitly construct 'value'
|
1718 | tokens = []
|
1719 | template.execute(value, tokens.append, trace=trace)
|
1720 | value = JoinTokens(tokens)
|
1721 |
|
1722 | elif formatter_type == ENHANCED_FUNC:
|
1723 | value = f(value, context, args)
|
1724 |
|
1725 | elif formatter_type == SIMPLE_FUNC:
|
1726 | value = f(value)
|
1727 |
|
1728 | else:
|
1729 | assert False, 'Invalid formatter type %r' % formatter_type
|
1730 |
|
1731 | except (KeyboardInterrupt, EvaluationError):
|
1732 | # Don't "wrap" recursive EvaluationErrors
|
1733 | raise
|
1734 |
|
1735 | except Exception, e:
|
1736 | if formatter_type == TEMPLATE_FORMATTER:
|
1737 | raise # in this case we want to see the original exception
|
1738 | raise EvaluationError(
|
1739 | 'Formatting name %r, value %r with formatter %s raised exception: %r '
|
1740 | '-- see e.original_exc_info' % (name, value, f, e),
|
1741 | original_exc_info=sys.exc_info())
|
1742 |
|
1743 | # TODO: Require a string/unicode instance here?
|
1744 | if value is None:
|
1745 | raise EvaluationError('Evaluating %r gave None value' % name)
|
1746 | callback(value)
|
1747 |
|
1748 |
|
1749 | def _Execute(statements, context, callback, trace):
|
1750 | """Execute a bunch of template statements in a ScopedContext.
|
1751 |
|
1752 | Args:
|
1753 | callback: Strings are "written" to this callback function.
|
1754 | trace: Trace object, or None
|
1755 |
|
1756 | This is called in a mutually recursive fashion.
|
1757 | """
|
1758 | # Every time we call _Execute, increase this depth
|
1759 | if trace:
|
1760 | trace.exec_depth += 1
|
1761 | for i, statement in enumerate(statements):
|
1762 | if isinstance(statement, basestring):
|
1763 | callback(statement)
|
1764 | else:
|
1765 | # In the case of a substitution, args is a pair (name, formatters).
|
1766 | # In the case of a section, it's a _Section instance.
|
1767 | try:
|
1768 | func, args = statement
|
1769 | func(args, context, callback, trace)
|
1770 | except UndefinedVariable, e:
|
1771 | # Show context for statements
|
1772 | start = max(0, i-3)
|
1773 | end = i+3
|
1774 | e.near = statements[start:end]
|
1775 | e.trace = trace # Attach caller's trace (could be None)
|
1776 | raise
|
1777 |
|
1778 |
|
1779 | def expand(template_str, dictionary, **kwargs):
|
1780 | """Free function to expands a template string with a data dictionary.
|
1781 |
|
1782 | This is useful for cases where you don't care about saving the result of
|
1783 | compilation (similar to re.match('.*', s) vs DOT_STAR.match(s))
|
1784 | """
|
1785 | t = Template(template_str, **kwargs)
|
1786 | return t.expand(dictionary)
|
1787 |
|
1788 |
|
1789 | ###
|
1790 | # TODO: DELETE
|
1791 | ###
|
1792 | def _FlattenToCallback(tokens, callback):
|
1793 | """Takes a nested list structure and flattens it.
|
1794 |
|
1795 | ['a', ['b', 'c']] -> callback('a'); callback('b'); callback('c');
|
1796 | """
|
1797 | for t in tokens:
|
1798 | if isinstance(t, basestring):
|
1799 | callback(t)
|
1800 | else:
|
1801 | _FlattenToCallback(t, callback)
|
1802 |
|
1803 | ###
|
1804 | # TODO: DELETE execute_with_style_LEGACY after old apps cleaned up
|
1805 | ####
|
1806 | def execute_with_style_LEGACY(template, style, data, callback, body_subtree='body'):
|
1807 | """OBSOLETE old API."""
|
1808 | try:
|
1809 | body_data = data[body_subtree]
|
1810 | except KeyError:
|
1811 | raise EvaluationError('Data dictionary has no subtree %r' % body_subtree)
|
1812 | tokens_body = []
|
1813 | template.execute(body_data, tokens_body.append)
|
1814 | data[body_subtree] = tokens_body
|
1815 | tokens = []
|
1816 | style.execute(data, tokens.append)
|
1817 | _FlattenToCallback(tokens, callback)
|
1818 |
|
1819 |
|
1820 | def expand_with_style(template, style, data, body_subtree='body'):
|
1821 | """Expand a data dictionary with a template AND a style.
|
1822 |
|
1823 | DEPRECATED -- Remove this entire function in favor of expand(d, style=style)
|
1824 |
|
1825 | A style is a Template instance that factors out the common strings in several
|
1826 | "body" templates.
|
1827 |
|
1828 | Args:
|
1829 | template: Template instance for the inner "page content"
|
1830 | style: Template instance for the outer "page style"
|
1831 | data: Data dictionary, with a 'body' key (or body_subtree
|
1832 | """
|
1833 | if template.has_defines:
|
1834 | return template.expand(data, style=style)
|
1835 | else:
|
1836 | tokens = []
|
1837 | execute_with_style_LEGACY(template, style, data, tokens.append,
|
1838 | body_subtree=body_subtree)
|
1839 | return JoinTokens(tokens)
|