21 """An Emitter collects string fragments to be assembled into a single string.
26 self.
_bindings = bindings
or Emitter.Frame({},
None)
29 """Emits literal string with no substitution."""
32 def Emit(self, template_source, **parameters):
33 """Emits a template, substituting named parameters and returning emitters to
36 Ordinary substitution occurs at $NAME or $(NAME). If there is no parameter
37 called NAME, the text is left as-is. So long as you don't bind FOO as a
38 parameter, $FOO in the template will pass through to the generated text.
40 Substitution of $?NAME and $(?NAME) yields an empty string if NAME is not a
43 Values passed as named parameters should be strings or simple integral
46 Named holes are created at $!NAME or $(!NAME). A hole marks a position in
47 the template that may be filled in later. An Emitter is returned for each
48 named hole in the template. The holes are filled by emitting to the
49 corresponding emitter.
51 Subtemplates can be created by using $#NAME(...), where text can be placed
52 inside of the parentheses and will conditionally expand depending on if
53 NAME is set to True or False. The text inside the parentheses may use
54 further $#NAME and $NAME substitutions, but is not permitted to create
57 Emit returns either a single Emitter if the template contains one hole or a
58 tuple of emitters for several holes, in the order that the holes occur in
61 The emitters for the holes remember the parameters passed to the initial
62 call to Emit. Holes can be used to provide a binding context.
64 return self.
_Emit(template_source, parameters)
66 def _Emit(self, template_source, parameters):
67 """Implementation of Emit, with map in place of named parameters."""
69 parameter_bindings = self.
_bindings.Extend(parameters)
71 hole_names = template._holes
76 for name
in hole_names:
77 emitter =
Emitter(parameter_bindings)
78 replacements[name] = emitter._items
79 hole_map[name] = emitter
80 full_bindings = parameter_bindings.Extend(replacements)
82 full_bindings = parameter_bindings
84 self._ApplyTemplate(template, full_bindings, self.
_items)
89 if len(hole_names) == 1:
90 return hole_map[hole_names[0]]
92 return tuple(hole_map[name]
for name
in hole_names)
95 """Returns a list of all the string fragments emitted."""
97 def _FlattenTo(item, output):
98 if isinstance(item, list):
100 _FlattenTo(subitem, output)
101 elif isinstance(item, Emitter.DeferredLookup):
102 value = item._environment.Lookup(item._lookup._name,
103 item._lookup._value_if_missing)
104 if item._lookup._subtemplate:
105 _FlattenSubtemplate(item, value, output)
107 _FlattenTo(value, output)
109 output.append(str(item))
111 def _FlattenSubtemplate(item, value, output):
112 """Handles subtemplates created by $#NAME(...)"""
115 _FlattenTo(item._lookup._subitems, output)
116 elif value
is not False:
117 if value != item._lookup._value_if_missing:
119 'Value for NAME in $#NAME(...) syntax must be a boolean'
123 _FlattenTo(value, output)
124 _FlattenTo(item._lookup._subitems, output)
125 _FlattenTo(
')', output)
128 _FlattenTo(self.
_items, output)
131 def Bind(self, var, template_source, **parameters):
132 """Adds a binding for var to this emitter."""
135 raise RuntimeError(
'Cannot have holes in Emitter.Bind')
136 bindings = self.
_bindings.Extend(parameters)
138 value._ApplyTemplate(template, bindings, self.
_items)
143 """Converts the template string into a Template object."""
151 match = Emitter._SUBST_RE.search(source, pos)
153 items.append(source[pos:])
155 text_fragment = source[pos:match.start()]
157 items.append(text_fragment)
160 name = match.group(1)
or match.group(2)
162 item = Emitter.Lookup(name, term, term)
165 name = match.group(3)
or match.group(4)
167 item = Emitter.Lookup(name, term, term)
171 name = match.group(5)
or match.group(6)
173 item = Emitter.Lookup(name, term,
'')
177 name = match.group(7)
183 while curr_pos < len(source):
184 if source[curr_pos] ==
')':
188 elif source[curr_pos] ==
'(':
192 if curr_pos == len(source):
198 if len(matched_template._holes) > 0:
200 '$#NAME syntax cannot contains holes in its arguments')
201 item = Emitter.Lookup(name, term, term, matched_template)
206 raise RuntimeError(
'Unexpected group')
208 if len(holes) != len(set(holes)):
209 raise RuntimeError(
'Cannot have repeated holes %s' % holes)
210 return Emitter.Template(items, holes)
212 _SUBST_RE = re.compile(
214 r'\$(\w+)|\$\((\w+)\)|\$!(\w+)|\$\(!(\w+)\)|\$\?(\w+)|\$\(\?(\w+)\)|\$#(\w+)\('
217 def _ApplyTemplate(self, template, bindings, items_list):
218 """Emits the items from the parsed template."""
220 for item
in template._items:
221 if isinstance(item, str):
224 elif isinstance(item, Emitter.Lookup):
227 result.append(Emitter.DeferredLookup(item, bindings))
230 if item._subtemplate:
231 self._ApplyTemplate(item._subtemplate, bindings,
234 raise RuntimeError(
'Unexpected template element')
237 items_list.append(result)
239 class Lookup(object):
240 """An element of a parsed template."""
242 def __init__(self, name, original, default, subtemplate=None):
244 self._original = original
245 self._value_if_missing = default
246 self._subtemplate = subtemplate
249 class DeferredLookup(object):
250 """A lookup operation that is deferred until final string generation."""
255 def __init__(self, lookup, environment):
256 self._lookup = lookup
257 self._environment = environment
259 class Template(object):
260 """A parsed template."""
267 """A Frame is a set of bindings derived from a parent."""
271 self._parent = parent
273 def Lookup(self, name, default):
274 if name
in self._map:
275 return self._map[name]
277 return self._parent.Lookup(name, default)
280 def Extend(self, map):
281 return Emitter.Frame(map, self)