Index: client/dom/scripts/emitter.py |
=================================================================== |
--- client/dom/scripts/emitter.py (revision 5796) |
+++ client/dom/scripts/emitter.py (working copy) |
@@ -1,217 +0,0 @@ |
-#!/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. |
- |
-"""Templating to help generate structured text.""" |
- |
-import logging |
-import re |
- |
-_logger = logging.getLogger('emitter') |
- |
- |
-def Format(template, **parameters): |
- """Create a string using the same template syntax as Emitter.Emit.""" |
- e = Emitter() |
- e._Emit(template, parameters) |
- return ''.join(e.Fragments()) |
- |
- |
-class Emitter(object): |
- """An Emitter collects string fragments to be assembled into a single string. |
- """ |
- |
- def __init__(self, bindings=None): |
- self._items = [] # A new list |
- self._bindings = bindings or Emitter.Frame({}, None) |
- |
- def EmitRaw(self, item): |
- """Emits literal string with no substitition.""" |
- self._items.append(item) |
- |
- def Emit(self, template_source, **parameters): |
- """Emits a template, substituting named parameters and returning emitters to |
- fill the named holes. |
- |
- Ordinary substitution occurs at $NAME or $(NAME). If there is no parameter |
- called NAME, the text is left as-is. So long as you don't bind FOO as a |
- parameter, $FOO in the template will pass through to the generated text. |
- |
- Substitution of $?NAME and $(?NAME) yields an empty string if NAME is not a |
- parameter. |
- |
- Values passed as named parameters should be strings or simple integral |
- values (int or long). |
- |
- Named holes are created at $!NAME or $(!NAME). A hole marks a position in |
- the template that may be filled in later. An Emitter is returned for each |
- named hole in the template. The holes are filled by emitting to the |
- corresponding emitter. |
- |
- Emit returns either a single Emitter if the template contains one hole or a |
- tuple of emitters for several holes, in the order that the holes occur in |
- the template. |
- |
- The emitters for the holes remember the parameters passed to the initial |
- call to Emit. Holes can be used to provide a binding context. |
- """ |
- return self._Emit(template_source, parameters) |
- |
- def _Emit(self, template_source, parameters): |
- """Implementation of Emit, with map in place of named parameters.""" |
- template = self._ParseTemplate(template_source) |
- |
- parameter_bindings = self._bindings.Extend(parameters) |
- |
- hole_names = template._holes |
- |
- if hole_names: |
- hole_map = {} |
- replacements = {} |
- for name in hole_names: |
- emitter = Emitter(parameter_bindings) |
- replacements[name] = emitter._items |
- hole_map[name] = emitter |
- full_bindings = parameter_bindings.Extend(replacements) |
- else: |
- full_bindings = parameter_bindings |
- |
- self._ApplyTemplate(template, full_bindings) |
- |
- # Return None, a singleton or tuple of the hole names. |
- if not hole_names: |
- return None |
- if len(hole_names) == 1: |
- return hole_map[hole_names[0]] |
- else: |
- return tuple(hole_map[name] for name in hole_names) |
- |
- def Fragments(self): |
- """Returns a list of all the string fragments emitted.""" |
- def _FlattenTo(item, output): |
- if isinstance(item, list): |
- for subitem in item: |
- _FlattenTo(subitem, output) |
- elif isinstance(item, Emitter.DeferredLookup): |
- value = item._environment.Lookup(item._lookup._name, |
- item._lookup._value_if_missing) |
- _FlattenTo(value, output) |
- else: |
- output.append(str(item)) |
- |
- output = [] |
- _FlattenTo(self._items, output) |
- return output |
- |
- def Bind(self, var, template_source, **parameters): |
- """Adds a binding for var to this emitter.""" |
- template = self._ParseTemplate(template_source) |
- if template._holes: |
- raise RuntimeError('Cannot have holes in Emitter.Bind') |
- bindings = self._bindings.Extend(parameters) |
- value = Emitter(bindings) |
- value._ApplyTemplate(template, bindings) |
- self._bindings = self._bindings.Extend({var: value._items}) |
- return value |
- |
- def _ParseTemplate(self, source): |
- """Converts the template string into a Template object.""" |
- # TODO(sra): Cache the parsing. |
- items = [] |
- holes = [] |
- |
- # Break source into a sequence of text fragments and substitution lookups. |
- pos = 0 |
- while True: |
- match = Emitter._SUBST_RE.search(source, pos) |
- if not match: |
- items.append(source[pos:]) |
- break |
- text_fragment = source[pos : match.start()] |
- if text_fragment: |
- items.append(text_fragment) |
- pos = match.end() |
- term = match.group() |
- name = match.group(1) or match.group(2) # $NAME and $(NAME) |
- if name: |
- item = Emitter.Lookup(name, term, term) |
- items.append(item) |
- continue |
- name = match.group(3) or match.group(4) # $!NAME and $(!NAME) |
- if name: |
- item = Emitter.Lookup(name, term, term) |
- items.append(item) |
- holes.append(name) |
- continue |
- name = match.group(5) or match.group(6) # $?NAME and $(?NAME) |
- if name: |
- item = Emitter.Lookup(name, term, '') |
- items.append(item) |
- holes.append(name) |
- continue |
- raise RuntimeError('Unexpected group') |
- |
- if len(holes) != len(set(holes)): |
- raise RuntimeError('Cannot have repeated holes %s' % holes) |
- return Emitter.Template(items, holes) |
- |
- _SUBST_RE = re.compile( |
- # $FOO $(FOO) $!FOO $(!FOO) $?FOO $(?FOO) |
- r'\$(\w+)|\$\((\w+)\)|\$!(\w+)|\$\(!(\w+)\)|\$\?(\w+)|\$\(\?(\w+)\)') |
- |
- def _ApplyTemplate(self, template, bindings): |
- """Emits the items from the parsed template.""" |
- result = [] |
- for item in template._items: |
- if isinstance(item, str): |
- if item: |
- result.append(item) |
- elif isinstance(item, Emitter.Lookup): |
- # Bind lookup to the current environment (bindings) |
- # TODO(sra): More space efficient to do direct lookup. |
- result.append(Emitter.DeferredLookup(item, bindings)) |
- else: |
- raise RuntimeError('Unexpected template element') |
- # Collected fragments are in a sublist, so self._items contains one element |
- # (sublist) per template application. |
- self._items.append(result) |
- |
- class Lookup(object): |
- """An element of a parsed template.""" |
- def __init__(self, name, original, default): |
- self._name = name |
- self._original = original |
- self._value_if_missing = default |
- |
- class DeferredLookup(object): |
- """A lookup operation that is deferred until final string generation.""" |
- # TODO(sra): A deferred lookup will be useful when we add expansions that |
- # have behaviour condtional on the contents, e.g. adding separators between |
- # a list of items. |
- def __init__(self, lookup, environment): |
- self._lookup = lookup |
- self._environment = environment |
- |
- class Template(object): |
- """A parsed template.""" |
- def __init__(self, items, holes): |
- self._items = items # strings and lookups |
- self._holes = holes |
- |
- |
- class Frame(object): |
- """A Frame is a set of bindings derived from a parent.""" |
- def __init__(self, map, parent): |
- self._map = map |
- self._parent = parent |
- |
- def Lookup(self, name, default): |
- if name in self._map: |
- return self._map[name] |
- if self._parent: |
- return self._parent.Lookup(name, default) |
- return default |
- |
- def Extend(self, map): |
- return Emitter.Frame(map, self) |