| 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)
 |