|
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 listener |
|
19 |
|
20 Contains class which interprets YAML events and forwards them to |
|
21 a handler object. |
|
22 """ |
|
23 |
|
24 |
|
25 from google.appengine.api import yaml_errors |
|
26 import yaml |
|
27 |
|
28 |
|
29 _EVENT_METHOD_MAP = { |
|
30 yaml.events.StreamStartEvent: 'StreamStart', |
|
31 yaml.events.StreamEndEvent: 'StreamEnd', |
|
32 yaml.events.DocumentStartEvent: 'DocumentStart', |
|
33 yaml.events.DocumentEndEvent: 'DocumentEnd', |
|
34 yaml.events.AliasEvent: 'Alias', |
|
35 yaml.events.ScalarEvent: 'Scalar', |
|
36 yaml.events.SequenceStartEvent: 'SequenceStart', |
|
37 yaml.events.SequenceEndEvent: 'SequenceEnd', |
|
38 yaml.events.MappingStartEvent: 'MappingStart', |
|
39 yaml.events.MappingEndEvent: 'MappingEnd', |
|
40 } |
|
41 |
|
42 |
|
43 class EventHandler(object): |
|
44 """Handler interface for parsing YAML files. |
|
45 |
|
46 Implement this interface to define specific YAML event handling class. |
|
47 Implementing classes instances are passed to the constructor of |
|
48 EventListener to act as a receiver of YAML parse events. |
|
49 """ |
|
50 def StreamStart(self, event, loader): |
|
51 """Handle start of stream event""" |
|
52 |
|
53 def StreamEnd(self, event, loader): |
|
54 """Handle end of stream event""" |
|
55 |
|
56 def DocumentStart(self, event, loader): |
|
57 """Handle start of document event""" |
|
58 |
|
59 def DocumentEnd(self, event, loader): |
|
60 """Handle end of document event""" |
|
61 |
|
62 def Alias(self, event, loader): |
|
63 """Handle alias event""" |
|
64 |
|
65 def Scalar(self, event, loader): |
|
66 """Handle scalar event""" |
|
67 |
|
68 def SequenceStart(self, event, loader): |
|
69 """Handle start of sequence event""" |
|
70 |
|
71 def SequenceEnd(self, event, loader): |
|
72 """Handle end of sequence event""" |
|
73 |
|
74 def MappingStart(self, event, loader): |
|
75 """Handle start of mappping event""" |
|
76 |
|
77 def MappingEnd(self, event, loader): |
|
78 """Handle end of mapping event""" |
|
79 |
|
80 |
|
81 class EventListener(object): |
|
82 """Helper class to re-map PyYAML events to method calls. |
|
83 |
|
84 By default, PyYAML generates its events via a Python generator. This class |
|
85 is a helper that iterates over the events from the PyYAML parser and forwards |
|
86 them to a handle class in the form of method calls. For simplicity, the |
|
87 underlying event is forwarded to the handler as a parameter to the call. |
|
88 |
|
89 This object does not itself produce iterable objects, but is really a mapping |
|
90 to a given handler instance. |
|
91 |
|
92 Example use: |
|
93 |
|
94 class PrintDocumentHandler(object): |
|
95 def DocumentStart(event): |
|
96 print "A new document has been started" |
|
97 |
|
98 EventListener(PrintDocumentHandler()).Parse(''' |
|
99 key1: value1 |
|
100 --- |
|
101 key2: value2 |
|
102 ''' |
|
103 |
|
104 >>> A new document has been started |
|
105 A new document has been started |
|
106 |
|
107 In the example above, the implemented handler class (PrintDocumentHandler) |
|
108 has a single method which reports each time a new document is started within |
|
109 a YAML file. It is not necessary to subclass the EventListener, merely it |
|
110 receives a PrintDocumentHandler instance. Every time a new document begins, |
|
111 PrintDocumentHandler.DocumentStart is called with the PyYAML event passed |
|
112 in as its parameter.. |
|
113 """ |
|
114 |
|
115 def __init__(self, event_handler): |
|
116 """Initialize PyYAML event listener. |
|
117 |
|
118 Constructs internal mapping directly from event type to method on actual |
|
119 handler. This prevents reflection being used during actual parse time. |
|
120 |
|
121 Args: |
|
122 event_handler: Event handler that will receive mapped events. Must |
|
123 implement at least one appropriate handler method named from |
|
124 the values of the _EVENT_METHOD_MAP. |
|
125 |
|
126 Raises: |
|
127 ListenerConfigurationError if event_handler is not an EventHandler. |
|
128 """ |
|
129 if not isinstance(event_handler, EventHandler): |
|
130 raise yaml_errors.ListenerConfigurationError( |
|
131 'Must provide event handler of type yaml_listener.EventHandler') |
|
132 self._event_method_map = {} |
|
133 for event, method in _EVENT_METHOD_MAP.iteritems(): |
|
134 self._event_method_map[event] = getattr(event_handler, method) |
|
135 |
|
136 def HandleEvent(self, event, loader=None): |
|
137 """Handle individual PyYAML event. |
|
138 |
|
139 Args: |
|
140 event: Event to forward to method call in method call. |
|
141 |
|
142 Raises: |
|
143 IllegalEvent when receives an unrecognized or unsupported event type. |
|
144 """ |
|
145 if event.__class__ not in _EVENT_METHOD_MAP: |
|
146 raise yaml_errors.IllegalEvent( |
|
147 "%s is not a valid PyYAML class" % event.__class__.__name__) |
|
148 if event.__class__ in self._event_method_map: |
|
149 self._event_method_map[event.__class__](event, loader) |
|
150 |
|
151 def _HandleEvents(self, events): |
|
152 """Iterate over all events and send them to handler. |
|
153 |
|
154 This method is not meant to be called from the interface. |
|
155 |
|
156 Only use in tests. |
|
157 |
|
158 Args: |
|
159 events: Iterator or generator containing events to process. |
|
160 raises: |
|
161 EventListenerParserError when a yaml.parser.ParserError is raised. |
|
162 EventError when an exception occurs during the handling of an event. |
|
163 """ |
|
164 for event in events: |
|
165 try: |
|
166 self.HandleEvent(*event) |
|
167 except Exception, e: |
|
168 event_object, loader = event |
|
169 raise yaml_errors.EventError(e, event_object) |
|
170 |
|
171 def _GenerateEventParameters(self, |
|
172 stream, |
|
173 loader_class=yaml.loader.SafeLoader): |
|
174 """Creates a generator that yields event, loader parameter pairs. |
|
175 |
|
176 For use as parameters to HandleEvent method for use by Parse method. |
|
177 During testing, _GenerateEventParameters is simulated by allowing |
|
178 the harness to pass in a list of pairs as the parameter. |
|
179 |
|
180 A list of (event, loader) pairs must be passed to _HandleEvents otherwise |
|
181 it is not possible to pass the loader instance to the handler. |
|
182 |
|
183 Also responsible for instantiating the loader from the Loader |
|
184 parameter. |
|
185 |
|
186 Args: |
|
187 stream: String document or open file object to process as per the |
|
188 yaml.parse method. Any object that implements a 'read()' method which |
|
189 returns a string document will work. |
|
190 Loader: Loader class to use as per the yaml.parse method. Used to |
|
191 instantiate new yaml.loader instance. |
|
192 |
|
193 Yields: |
|
194 Tuple(event, loader) where: |
|
195 event: Event emitted by PyYAML loader. |
|
196 loader_class: Used for dependency injection. |
|
197 """ |
|
198 assert loader_class is not None |
|
199 try: |
|
200 loader = loader_class(stream) |
|
201 while loader.check_event(): |
|
202 yield (loader.get_event(), loader) |
|
203 except yaml.error.YAMLError, e: |
|
204 raise yaml_errors.EventListenerYAMLError(e) |
|
205 |
|
206 def Parse(self, stream, loader_class=yaml.loader.SafeLoader): |
|
207 """Call YAML parser to generate and handle all events. |
|
208 |
|
209 Calls PyYAML parser and sends resulting generator to handle_event method |
|
210 for processing. |
|
211 |
|
212 Args: |
|
213 stream: String document or open file object to process as per the |
|
214 yaml.parse method. Any object that implements a 'read()' method which |
|
215 returns a string document will work with the YAML parser. |
|
216 loader_class: Used for dependency injection. |
|
217 """ |
|
218 self._HandleEvents(self._GenerateEventParameters(stream, loader_class)) |