#!/usr/bin/python # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file # for details. All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. # Template loader and preprocessor. # # Preprocessor language: # # //$ Comment line removed by preprocessor # $if VAR # $else # $endif # # VAR must be defined in the conditions dictionary. import os class TemplateLoader(object): """Loads template files from a path.""" def __init__(self, root, subpaths, conditions = {}): """Initializes loader. Args: root - a string, the directory under which the templates are stored. subpaths - a list of strings, subpaths of root in search order. conditions - a dictionay from strings to booleans. Any conditional expression must be a key in the map. """ self._root = root self._subpaths = subpaths self._conditions = conditions self._cache = {} def TryLoad(self, name, more_conditions={}): """Returns content of template file as a string, or None of not found.""" conditions = dict(self._conditions, **more_conditions) cache_key = (name, tuple(sorted(conditions.items()))) if cache_key in self._cache: return self._cache[cache_key] for subpath in self._subpaths: template_file = os.path.join(self._root, subpath, name) if os.path.exists(template_file): template = ''.join(open(template_file).readlines()) template = self._Preprocess(template, template_file, conditions) self._cache[cache_key] = template return template return None def Load(self, name, more_conditions={}): """Returns contents of template file as a string, or raises an exception.""" template = self.TryLoad(name, more_conditions) if template is not None: # Can be empty string return template raise Exception("Could not find template '%s' on %s / %s" % ( name, self._root, self._subpaths)) def _Preprocess(self, template, filename, conditions): def error(lineno, message): raise Exception('%s:%s: %s' % (filename, lineno, message)) lines = template.splitlines(True) out = [] condition_stack = [] active = True seen_else = False for (lineno, full_line) in enumerate(lines): line = full_line.strip() if line.startswith('$'): words = line.split() directive = words[0] if directive == '$if': if len(words) != 2: error(lineno, '$if does not have single variable') variable = words[1] if variable in conditions: condition_stack.append((active, seen_else)) active = active and conditions[variable] seen_else = False else: error(lineno, "Unknown $if variable '%s'" % variable) elif directive == '$else': if not condition_stack: error(lineno, '$else without $if') if seen_else: raise error(lineno, 'Double $else') seen_else = True (parentactive, _) = condition_stack[len(condition_stack) - 1] active = not active and parentactive elif directive == '$endif': if not condition_stack: error(lineno, '$endif without $if') (active, seen_else) = condition_stack.pop() else: # Something else, like '$!MEMBERS' if active: out.append(full_line) elif line.startswith('//$'): pass # Ignore pre-processor comment. else: if active: out.append(full_line) continue if condition_stack: error(len(lines), 'Unterminated $if') return ''.join(out)