Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(182)

Side by Side Diff: sdk/lib/html/scripts/emitter.py

Issue 11691009: Moved most of html lib generating scripts into tools. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/html/scripts/databasebuilder_test.py ('k') | sdk/lib/html/scripts/emitter_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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)
OLDNEW
« no previous file with comments | « sdk/lib/html/scripts/databasebuilder_test.py ('k') | sdk/lib/html/scripts/emitter_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698