| #!/usr/bin/env python3 |
| # 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) |