| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/python | |
| 2 # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
| 3 # for details. All rights reserved. Use of this source code is governed by a | |
| 4 # BSD-style license that can be found in the LICENSE file. | |
| 5 | |
| 6 """Templating to help generate structured text.""" | |
| 7 | |
| 8 import logging | |
| 9 import re | |
| 10 | |
| 11 _logger = logging.getLogger('emitter') | |
| 12 | |
| 13 | |
| 14 def Format(template, **parameters): | |
| 15 """Create a string using the same template syntax as Emitter.Emit.""" | |
| 16 e = Emitter() | |
| 17 e._Emit(template, parameters) | |
| 18 return ''.join(e.Fragments()) | |
| 19 | |
| 20 | |
| 21 class Emitter(object): | |
| 22 """An Emitter collects string fragments to be assembled into a single string. | |
| 23 """ | |
| 24 | |
| 25 def __init__(self, bindings=None): | |
| 26 self._items = [] # A new list | |
| 27 self._bindings = bindings or Emitter.Frame({}, None) | |
| 28 | |
| 29 def EmitRaw(self, item): | |
| 30 """Emits literal string with no substitition.""" | |
| 31 self._items.append(item) | |
| 32 | |
| 33 def Emit(self, template_source, **parameters): | |
| 34 """Emits a template, substituting named parameters and returning emitters to | |
| 35 fill the named holes. | |
| 36 | |
| 37 Ordinary substitution occurs at $NAME or $(NAME). If there is no parameter | |
| 38 called NAME, the text is left as-is. So long as you don't bind FOO as a | |
| 39 parameter, $FOO in the template will pass through to the generated text. | |
| 40 | |
| 41 Substitution of $?NAME and $(?NAME) yields an empty string if NAME is not a | |
| 42 parameter. | |
| 43 | |
| 44 Values passed as named parameters should be strings or simple integral | |
| 45 values (int or long). | |
| 46 | |
| 47 Named holes are created at $!NAME or $(!NAME). A hole marks a position in | |
| 48 the template that may be filled in later. An Emitter is returned for each | |
| 49 named hole in the template. The holes are filled by emitting to the | |
| 50 corresponding emitter. | |
| 51 | |
| 52 Emit returns either a single Emitter if the template contains one hole or a | |
| 53 tuple of emitters for several holes, in the order that the holes occur in | |
| 54 the template. | |
| 55 | |
| 56 The emitters for the holes remember the parameters passed to the initial | |
| 57 call to Emit. Holes can be used to provide a binding context. | |
| 58 """ | |
| 59 return self._Emit(template_source, parameters) | |
| 60 | |
| 61 def _Emit(self, template_source, parameters): | |
| 62 """Implementation of Emit, with map in place of named parameters.""" | |
| 63 template = self._ParseTemplate(template_source) | |
| 64 | |
| 65 parameter_bindings = self._bindings.Extend(parameters) | |
| 66 | |
| 67 hole_names = template._holes | |
| 68 | |
| 69 if hole_names: | |
| 70 hole_map = {} | |
| 71 replacements = {} | |
| 72 for name in hole_names: | |
| 73 emitter = Emitter(parameter_bindings) | |
| 74 replacements[name] = emitter._items | |
| 75 hole_map[name] = emitter | |
| 76 full_bindings = parameter_bindings.Extend(replacements) | |
| 77 else: | |
| 78 full_bindings = parameter_bindings | |
| 79 | |
| 80 self._ApplyTemplate(template, full_bindings) | |
| 81 | |
| 82 # Return None, a singleton or tuple of the hole names. | |
| 83 if not hole_names: | |
| 84 return None | |
| 85 if len(hole_names) == 1: | |
| 86 return hole_map[hole_names[0]] | |
| 87 else: | |
| 88 return tuple(hole_map[name] for name in hole_names) | |
| 89 | |
| 90 def Fragments(self): | |
| 91 """Returns a list of all the string fragments emitted.""" | |
| 92 def _FlattenTo(item, output): | |
| 93 if isinstance(item, list): | |
| 94 for subitem in item: | |
| 95 _FlattenTo(subitem, output) | |
| 96 elif isinstance(item, Emitter.DeferredLookup): | |
| 97 value = item._environment.Lookup(item._lookup._name, | |
| 98 item._lookup._value_if_missing) | |
| 99 _FlattenTo(value, output) | |
| 100 else: | |
| 101 output.append(str(item)) | |
| 102 | |
| 103 output = [] | |
| 104 _FlattenTo(self._items, output) | |
| 105 return output | |
| 106 | |
| 107 def Bind(self, var, template_source, **parameters): | |
| 108 """Adds a binding for var to this emitter.""" | |
| 109 template = self._ParseTemplate(template_source) | |
| 110 if template._holes: | |
| 111 raise RuntimeError('Cannot have holes in Emitter.Bind') | |
| 112 bindings = self._bindings.Extend(parameters) | |
| 113 value = Emitter(bindings) | |
| 114 value._ApplyTemplate(template, bindings) | |
| 115 self._bindings = self._bindings.Extend({var: value._items}) | |
| 116 return value | |
| 117 | |
| 118 def _ParseTemplate(self, source): | |
| 119 """Converts the template string into a Template object.""" | |
| 120 # TODO(sra): Cache the parsing. | |
| 121 items = [] | |
| 122 holes = [] | |
| 123 | |
| 124 # Break source into a sequence of text fragments and substitution lookups. | |
| 125 pos = 0 | |
| 126 while True: | |
| 127 match = Emitter._SUBST_RE.search(source, pos) | |
| 128 if not match: | |
| 129 items.append(source[pos:]) | |
| 130 break | |
| 131 text_fragment = source[pos : match.start()] | |
| 132 if text_fragment: | |
| 133 items.append(text_fragment) | |
| 134 pos = match.end() | |
| 135 term = match.group() | |
| 136 name = match.group(1) or match.group(2) # $NAME and $(NAME) | |
| 137 if name: | |
| 138 item = Emitter.Lookup(name, term, term) | |
| 139 items.append(item) | |
| 140 continue | |
| 141 name = match.group(3) or match.group(4) # $!NAME and $(!NAME) | |
| 142 if name: | |
| 143 item = Emitter.Lookup(name, term, term) | |
| 144 items.append(item) | |
| 145 holes.append(name) | |
| 146 continue | |
| 147 name = match.group(5) or match.group(6) # $?NAME and $(?NAME) | |
| 148 if name: | |
| 149 item = Emitter.Lookup(name, term, '') | |
| 150 items.append(item) | |
| 151 holes.append(name) | |
| 152 continue | |
| 153 raise RuntimeError('Unexpected group') | |
| 154 | |
| 155 if len(holes) != len(set(holes)): | |
| 156 raise RuntimeError('Cannot have repeated holes %s' % holes) | |
| 157 return Emitter.Template(items, holes) | |
| 158 | |
| 159 _SUBST_RE = re.compile( | |
| 160 # $FOO $(FOO) $!FOO $(!FOO) $?FOO $(?FOO) | |
| 161 r'\$(\w+)|\$\((\w+)\)|\$!(\w+)|\$\(!(\w+)\)|\$\?(\w+)|\$\(\?(\w+)\)') | |
| 162 | |
| 163 def _ApplyTemplate(self, template, bindings): | |
| 164 """Emits the items from the parsed template.""" | |
| 165 result = [] | |
| 166 for item in template._items: | |
| 167 if isinstance(item, str): | |
| 168 if item: | |
| 169 result.append(item) | |
| 170 elif isinstance(item, Emitter.Lookup): | |
| 171 # Bind lookup to the current environment (bindings) | |
| 172 # TODO(sra): More space efficient to do direct lookup. | |
| 173 result.append(Emitter.DeferredLookup(item, bindings)) | |
| 174 else: | |
| 175 raise RuntimeError('Unexpected template element') | |
| 176 # Collected fragments are in a sublist, so self._items contains one element | |
| 177 # (sublist) per template application. | |
| 178 self._items.append(result) | |
| 179 | |
| 180 class Lookup(object): | |
| 181 """An element of a parsed template.""" | |
| 182 def __init__(self, name, original, default): | |
| 183 self._name = name | |
| 184 self._original = original | |
| 185 self._value_if_missing = default | |
| 186 | |
| 187 class DeferredLookup(object): | |
| 188 """A lookup operation that is deferred until final string generation.""" | |
| 189 # TODO(sra): A deferred lookup will be useful when we add expansions that | |
| 190 # have behaviour condtional on the contents, e.g. adding separators between | |
| 191 # a list of items. | |
| 192 def __init__(self, lookup, environment): | |
| 193 self._lookup = lookup | |
| 194 self._environment = environment | |
| 195 | |
| 196 class Template(object): | |
| 197 """A parsed template.""" | |
| 198 def __init__(self, items, holes): | |
| 199 self._items = items # strings and lookups | |
| 200 self._holes = holes | |
| 201 | |
| 202 | |
| 203 class Frame(object): | |
| 204 """A Frame is a set of bindings derived from a parent.""" | |
| 205 def __init__(self, map, parent): | |
| 206 self._map = map | |
| 207 self._parent = parent | |
| 208 | |
| 209 def Lookup(self, name, default): | |
| 210 if name in self._map: | |
| 211 return self._map[name] | |
| 212 if self._parent: | |
| 213 return self._parent.Lookup(name, default) | |
| 214 return default | |
| 215 | |
| 216 def Extend(self, map): | |
| 217 return Emitter.Frame(map, self) | |
| OLD | NEW |