|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright 2007 Google Inc. |
|
4 # |
|
5 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 # you may not use this file except in compliance with the License. |
|
7 # You may obtain a copy of the License at |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
11 # Unless required by applicable law or agreed to in writing, software |
|
12 # distributed under the License is distributed on an "AS IS" BASIS, |
|
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 # See the License for the specific language governing permissions and |
|
15 # limitations under the License. |
|
16 # |
|
17 |
|
18 """PyYAML event builder handler |
|
19 |
|
20 Receives events from YAML listener and forwards them to a builder |
|
21 object so that it can construct a properly structured object. |
|
22 """ |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 from google.appengine.api import yaml_errors |
|
29 from google.appengine.api import yaml_listener |
|
30 |
|
31 import yaml |
|
32 |
|
33 _TOKEN_DOCUMENT = 'document' |
|
34 _TOKEN_SEQUENCE = 'sequence' |
|
35 _TOKEN_MAPPING = 'mapping' |
|
36 _TOKEN_KEY = 'key' |
|
37 _TOKEN_VALUES = frozenset(( |
|
38 _TOKEN_DOCUMENT, |
|
39 _TOKEN_SEQUENCE, |
|
40 _TOKEN_MAPPING, |
|
41 _TOKEN_KEY)) |
|
42 |
|
43 |
|
44 class Builder(object): |
|
45 """Interface for building documents and type from YAML events. |
|
46 |
|
47 Implement this interface to create a new builder. Builders are |
|
48 passed to the BuilderHandler and used as a factory and assembler |
|
49 for creating concrete representations of YAML files. |
|
50 """ |
|
51 |
|
52 def BuildDocument(self): |
|
53 """Build new document. |
|
54 |
|
55 The object built by this method becomes the top level entity |
|
56 that the builder handler constructs. The actual type is |
|
57 determined by the sub-class of the Builder class and can essentially |
|
58 be any type at all. This method is always called when the parser |
|
59 encounters the start of a new document. |
|
60 |
|
61 Returns: |
|
62 New object instance representing concrete document which is |
|
63 returned to user via BuilderHandler.GetResults(). |
|
64 """ |
|
65 |
|
66 def InitializeDocument(self, document, value): |
|
67 """Initialize document with value from top level of document. |
|
68 |
|
69 This method is called when the root document element is encountered at |
|
70 the top level of a YAML document. It should get called immediately |
|
71 after BuildDocument. |
|
72 |
|
73 Receiving the None value indicates the empty document. |
|
74 |
|
75 Args: |
|
76 document: Document as constructed in BuildDocument. |
|
77 value: Scalar value to initialize the document with. |
|
78 """ |
|
79 |
|
80 def BuildMapping(self, top_value): |
|
81 """Build a new mapping representation. |
|
82 |
|
83 Called when StartMapping event received. Type of object is determined |
|
84 by Builder sub-class. |
|
85 |
|
86 Args: |
|
87 top_value: Object which will be new mappings parant. Will be object |
|
88 returned from previous call to BuildMapping or BuildSequence. |
|
89 |
|
90 Returns: |
|
91 Instance of new object that represents a mapping type in target model. |
|
92 """ |
|
93 |
|
94 def EndMapping(self, top_value, mapping): |
|
95 """Previously constructed mapping scope is at an end. |
|
96 |
|
97 Called when the end of a mapping block is encountered. Useful for |
|
98 additional clean up or end of scope validation. |
|
99 |
|
100 Args: |
|
101 top_value: Value which is parent of the mapping. |
|
102 mapping: Mapping which is at the end of its scope. |
|
103 """ |
|
104 |
|
105 def BuildSequence(self, top_value): |
|
106 """Build a new sequence representation. |
|
107 |
|
108 Called when StartSequence event received. Type of object is determined |
|
109 by Builder sub-class. |
|
110 |
|
111 Args: |
|
112 top_value: Object which will be new sequences parant. Will be object |
|
113 returned from previous call to BuildMapping or BuildSequence. |
|
114 |
|
115 Returns: |
|
116 Instance of new object that represents a sequence type in target model. |
|
117 """ |
|
118 |
|
119 def EndSequence(self, top_value, sequence): |
|
120 """Previously constructed sequence scope is at an end. |
|
121 |
|
122 Called when the end of a sequence block is encountered. Useful for |
|
123 additional clean up or end of scope validation. |
|
124 |
|
125 Args: |
|
126 top_value: Value which is parent of the sequence. |
|
127 sequence: Sequence which is at the end of its scope. |
|
128 """ |
|
129 |
|
130 def MapTo(self, subject, key, value): |
|
131 """Map value to a mapping representation. |
|
132 |
|
133 Implementation is defined by sub-class of Builder. |
|
134 |
|
135 Args: |
|
136 subject: Object that represents mapping. Value returned from |
|
137 BuildMapping. |
|
138 key: Key used to map value to subject. Can be any scalar value. |
|
139 value: Value which is mapped to subject. Can be any kind of value. |
|
140 """ |
|
141 |
|
142 def AppendTo(self, subject, value): |
|
143 """Append value to a sequence representation. |
|
144 |
|
145 Implementation is defined by sub-class of Builder. |
|
146 |
|
147 Args: |
|
148 subject: Object that represents sequence. Value returned from |
|
149 BuildSequence |
|
150 value: Value to be appended to subject. Can be any kind of value. |
|
151 """ |
|
152 |
|
153 |
|
154 class BuilderHandler(yaml_listener.EventHandler): |
|
155 """PyYAML event handler used to build objects. |
|
156 |
|
157 Maintains state information as it receives parse events so that object |
|
158 nesting is maintained. Uses provided builder object to construct and |
|
159 assemble objects as it goes. |
|
160 |
|
161 As it receives events from the YAML parser, it builds a stack of data |
|
162 representing structural tokens. As the scope of documents, mappings |
|
163 and sequences end, those token, value pairs are popped from the top of |
|
164 the stack so that the original scope can resume processing. |
|
165 |
|
166 A special case is made for the _KEY token. It represents a temporary |
|
167 value which only occurs inside mappings. It is immediately popped off |
|
168 the stack when it's associated value is encountered in the parse stream. |
|
169 It is necessary to do this because the YAML parser does not combine |
|
170 key and value information in to a single event. |
|
171 """ |
|
172 |
|
173 def __init__(self, builder): |
|
174 """Initialization for builder handler. |
|
175 |
|
176 Args: |
|
177 builder: Instance of Builder class. |
|
178 |
|
179 Raises: |
|
180 ListenerConfigurationError when builder is not a Builder class. |
|
181 """ |
|
182 if not isinstance(builder, Builder): |
|
183 raise yaml_errors.ListenerConfigurationError( |
|
184 'Must provide builder of type yaml_listener.Builder') |
|
185 self._builder = builder |
|
186 self._stack = None |
|
187 self._top = None |
|
188 self._results = [] |
|
189 |
|
190 def _Push(self, token, value): |
|
191 """Push values to stack at start of nesting. |
|
192 |
|
193 When a new object scope is beginning, will push the token (type of scope) |
|
194 along with the new objects value, the latter of which is provided through |
|
195 the various build methods of the builder. |
|
196 |
|
197 Args: |
|
198 token: Token indicating the type of scope which is being created; must |
|
199 belong to _TOKEN_VALUES. |
|
200 value: Value to associate with given token. Construction of value is |
|
201 determined by the builder provided to this handler at construction. |
|
202 """ |
|
203 self._top = (token, value) |
|
204 self._stack.append(self._top) |
|
205 |
|
206 def _Pop(self): |
|
207 """Pop values from stack at end of nesting. |
|
208 |
|
209 Called to indicate the end of a nested scope. |
|
210 |
|
211 Returns: |
|
212 Previously pushed value at the top of the stack. |
|
213 """ |
|
214 assert self._stack != [] and self._stack is not None |
|
215 token, value = self._stack.pop() |
|
216 if self._stack: |
|
217 self._top = self._stack[-1] |
|
218 else: |
|
219 self._top = None |
|
220 return value |
|
221 |
|
222 def _HandleAnchor(self, event): |
|
223 """Handle anchor attached to event. |
|
224 |
|
225 Currently will raise an error if anchor is used. Anchors are used to |
|
226 define a document wide tag to a given value (scalar, mapping or sequence). |
|
227 |
|
228 Args: |
|
229 event: Event which may have anchor property set. |
|
230 |
|
231 Raises: |
|
232 NotImplementedError if event attempts to use an anchor. |
|
233 """ |
|
234 if hasattr(event, 'anchor') and event.anchor is not None: |
|
235 raise NotImplementedError, 'Anchors not supported in this handler' |
|
236 |
|
237 def _HandleValue(self, value): |
|
238 """Handle given value based on state of parser |
|
239 |
|
240 This method handles the various values that are created by the builder |
|
241 at the beginning of scope events (such as mappings and sequences) or |
|
242 when a scalar value is received. |
|
243 |
|
244 Method is called when handler receives a parser, MappingStart or |
|
245 SequenceStart. |
|
246 |
|
247 Args: |
|
248 value: Value received as scalar value or newly constructed mapping or |
|
249 sequence instance. |
|
250 |
|
251 Raises: |
|
252 InternalError if the building process encounters an unexpected token. |
|
253 This is an indication of an implementation error in BuilderHandler. |
|
254 """ |
|
255 token, top_value = self._top |
|
256 |
|
257 if token == _TOKEN_KEY: |
|
258 key = self._Pop() |
|
259 mapping_token, mapping = self._top |
|
260 assert _TOKEN_MAPPING == mapping_token |
|
261 self._builder.MapTo(mapping, key, value) |
|
262 |
|
263 elif token == _TOKEN_MAPPING: |
|
264 self._Push(_TOKEN_KEY, value) |
|
265 |
|
266 elif token == _TOKEN_SEQUENCE: |
|
267 self._builder.AppendTo(top_value, value) |
|
268 |
|
269 elif token == _TOKEN_DOCUMENT: |
|
270 self._builder.InitializeDocument(top_value, value) |
|
271 |
|
272 else: |
|
273 raise yaml_errors.InternalError('Unrecognized builder token:\n%s' % token) |
|
274 |
|
275 def StreamStart(self, event, loader): |
|
276 """Initializes internal state of handler |
|
277 |
|
278 Args: |
|
279 event: Ignored. |
|
280 """ |
|
281 assert self._stack is None |
|
282 self._stack = [] |
|
283 self._top = None |
|
284 self._results = [] |
|
285 |
|
286 def StreamEnd(self, event, loader): |
|
287 """Cleans up internal state of handler after parsing |
|
288 |
|
289 Args: |
|
290 event: Ignored. |
|
291 """ |
|
292 assert self._stack == [] and self._top is None |
|
293 self._stack = None |
|
294 |
|
295 def DocumentStart(self, event, loader): |
|
296 """Build new document. |
|
297 |
|
298 Pushes new document on to stack. |
|
299 |
|
300 Args: |
|
301 event: Ignored. |
|
302 """ |
|
303 assert self._stack == [] |
|
304 self._Push(_TOKEN_DOCUMENT, self._builder.BuildDocument()) |
|
305 |
|
306 def DocumentEnd(self, event, loader): |
|
307 """End of document. |
|
308 |
|
309 Args: |
|
310 event: Ignored. |
|
311 """ |
|
312 assert self._top[0] == _TOKEN_DOCUMENT |
|
313 self._results.append(self._Pop()) |
|
314 |
|
315 def Alias(self, event, loader): |
|
316 """Not implemented yet. |
|
317 |
|
318 Args: |
|
319 event: Ignored. |
|
320 """ |
|
321 raise NotImplementedError('Anchors not supported in this handler') |
|
322 |
|
323 def Scalar(self, event, loader): |
|
324 """Handle scalar value |
|
325 |
|
326 Since scalars are simple values that are passed directly in by the |
|
327 parser, handle like any value with no additional processing. |
|
328 |
|
329 Of course, key values will be handles specially. A key value is recognized |
|
330 when the top token is _TOKEN_MAPPING. |
|
331 |
|
332 Args: |
|
333 event: Event containing scalar value. |
|
334 """ |
|
335 self._HandleAnchor(event) |
|
336 if event.tag is None and self._top[0] != _TOKEN_MAPPING: |
|
337 try: |
|
338 tag = loader.resolve(yaml.nodes.ScalarNode, |
|
339 event.value, event.implicit) |
|
340 except IndexError: |
|
341 tag = loader.DEFAULT_SCALAR_TAG |
|
342 else: |
|
343 tag = event.tag |
|
344 |
|
345 if tag is None: |
|
346 value = event.value |
|
347 else: |
|
348 node = yaml.nodes.ScalarNode(tag, |
|
349 event.value, |
|
350 event.start_mark, |
|
351 event.end_mark, |
|
352 event.style) |
|
353 value = loader.construct_object(node) |
|
354 self._HandleValue(value) |
|
355 |
|
356 def SequenceStart(self, event, loader): |
|
357 """Start of sequence scope |
|
358 |
|
359 Create a new sequence from the builder and then handle in the context |
|
360 of its parent. |
|
361 |
|
362 Args: |
|
363 event: SequenceStartEvent generated by loader. |
|
364 loader: Loader that generated event. |
|
365 """ |
|
366 self._HandleAnchor(event) |
|
367 token, parent = self._top |
|
368 |
|
369 if token == _TOKEN_KEY: |
|
370 token, parent = self._stack[-2] |
|
371 sequence = self._builder.BuildSequence(parent) |
|
372 self._HandleValue(sequence) |
|
373 self._Push(_TOKEN_SEQUENCE, sequence) |
|
374 |
|
375 def SequenceEnd(self, event, loader): |
|
376 """End of sequence. |
|
377 |
|
378 Args: |
|
379 event: Ignored |
|
380 loader: Ignored. |
|
381 """ |
|
382 assert self._top[0] == _TOKEN_SEQUENCE |
|
383 end_object = self._Pop() |
|
384 top_value = self._top[1] |
|
385 self._builder.EndSequence(top_value, end_object) |
|
386 |
|
387 def MappingStart(self, event, loader): |
|
388 """Start of mapping scope. |
|
389 |
|
390 Create a mapping from builder and then handle in the context of its |
|
391 parent. |
|
392 |
|
393 Args: |
|
394 event: MappingStartEvent generated by loader. |
|
395 loader: Loader that generated event. |
|
396 """ |
|
397 self._HandleAnchor(event) |
|
398 token, parent = self._top |
|
399 |
|
400 if token == _TOKEN_KEY: |
|
401 token, parent = self._stack[-2] |
|
402 mapping = self._builder.BuildMapping(parent) |
|
403 self._HandleValue(mapping) |
|
404 self._Push(_TOKEN_MAPPING, mapping) |
|
405 |
|
406 def MappingEnd(self, event, loader): |
|
407 """End of mapping |
|
408 |
|
409 Args: |
|
410 event: Ignored. |
|
411 loader: Ignored. |
|
412 """ |
|
413 assert self._top[0] == _TOKEN_MAPPING |
|
414 end_object = self._Pop() |
|
415 top_value = self._top[1] |
|
416 self._builder.EndMapping(top_value, end_object) |
|
417 |
|
418 def GetResults(self): |
|
419 """Get results of document stream processing. |
|
420 |
|
421 This method can be invoked after fully parsing the entire YAML file |
|
422 to retrieve constructed contents of YAML file. Called after EndStream. |
|
423 |
|
424 Returns: |
|
425 A tuple of all document objects that were parsed from YAML stream. |
|
426 |
|
427 Raises: |
|
428 InternalError if the builder stack is not empty by the end of parsing. |
|
429 """ |
|
430 if self._stack is not None: |
|
431 raise yaml_errors.InternalError('Builder stack is not empty.') |
|
432 return tuple(self._results) |