OLD | NEW |
---|---|
1 #! /usr/bin/env python | 1 #! /usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 import itertools | 6 import itertools |
7 import json | 7 import json |
8 import os.path | 8 import os.path |
9 import pprint | |
9 import re | 10 import re |
10 import sys | 11 import sys |
11 | 12 |
12 from json_parse import OrderedDict | 13 from json_parse import OrderedDict |
13 | 14 |
14 # This file is a peer to json_schema.py. Each of these files understands a | 15 # This file is a peer to json_schema.py. Each of these files understands a |
15 # certain format describing APIs (either JSON or IDL), reads files written | 16 # certain format describing APIs (either JSON or IDL), reads files written |
16 # in that format into memory, and emits them as a Python array of objects | 17 # in that format into memory, and emits them as a Python array of objects |
17 # corresponding to those APIs, where the objects are formatted in a way that | 18 # corresponding to those APIs, where the objects are formatted in a way that |
18 # the JSON schema compiler understands. compiler.py drives both idl_schema.py | 19 # the JSON schema compiler understands. compiler.py drives both idl_schema.py |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
160 | 161 |
161 class Member(object): | 162 class Member(object): |
162 ''' | 163 ''' |
163 Given an IDL dictionary or interface member, converts into a name/value pair | 164 Given an IDL dictionary or interface member, converts into a name/value pair |
164 where the value is a Python dictionary that the JSON schema compiler expects | 165 where the value is a Python dictionary that the JSON schema compiler expects |
165 to see. | 166 to see. |
166 ''' | 167 ''' |
167 def __init__(self, member_node): | 168 def __init__(self, member_node): |
168 self.node = member_node | 169 self.node = member_node |
169 | 170 |
170 def process(self, callbacks): | 171 def process(self, callbacks, functions_are_properties=False): |
171 properties = OrderedDict() | 172 properties = OrderedDict() |
172 name = self.node.GetName() | 173 name = self.node.GetName() |
173 if self.node.GetProperty('deprecated'): | 174 if self.node.GetProperty('deprecated'): |
174 properties['deprecated'] = self.node.GetProperty('deprecated') | 175 properties['deprecated'] = self.node.GetProperty('deprecated') |
175 if self.node.GetProperty('allowAmbiguousOptionalArguments'): | 176 if self.node.GetProperty('allowAmbiguousOptionalArguments'): |
176 properties['allowAmbiguousOptionalArguments'] = True | 177 properties['allowAmbiguousOptionalArguments'] = True |
177 for property_name in ('OPTIONAL', 'nodoc', 'nocompile', 'nodart'): | 178 for property_name in ('OPTIONAL', 'nodoc', 'nocompile', 'nodart'): |
178 if self.node.GetProperty(property_name): | 179 if self.node.GetProperty(property_name): |
179 properties[property_name.lower()] = True | 180 properties[property_name.lower()] = True |
180 for option_name, sanitizer in [ | 181 for option_name, sanitizer in [ |
181 ('maxListeners', int), | 182 ('maxListeners', int), |
182 ('supportsFilters', lambda s: s == 'true'), | 183 ('supportsFilters', lambda s: s == 'true'), |
183 ('supportsListeners', lambda s: s == 'true'), | 184 ('supportsListeners', lambda s: s == 'true'), |
184 ('supportsRules', lambda s: s == 'true')]: | 185 ('supportsRules', lambda s: s == 'true')]: |
185 if self.node.GetProperty(option_name): | 186 if self.node.GetProperty(option_name): |
186 if 'options' not in properties: | 187 if 'options' not in properties: |
187 properties['options'] = {} | 188 properties['options'] = {} |
188 properties['options'][option_name] = sanitizer(self.node.GetProperty( | 189 properties['options'][option_name] = sanitizer(self.node.GetProperty( |
189 option_name)) | 190 option_name)) |
190 is_function = False | 191 type_override = None |
191 parameter_comments = OrderedDict() | 192 parameter_comments = OrderedDict() |
192 for node in self.node.GetChildren(): | 193 for node in self.node.GetChildren(): |
193 if node.cls == 'Comment': | 194 if node.cls == 'Comment': |
194 (parent_comment, parameter_comments) = ProcessComment(node.GetName()) | 195 (parent_comment, parameter_comments) = ProcessComment(node.GetName()) |
195 properties['description'] = parent_comment | 196 properties['description'] = parent_comment |
196 elif node.cls == 'Callspec': | 197 elif node.cls == 'Callspec': |
197 is_function = True | |
198 name, parameters, return_type = (Callspec(node, parameter_comments) | 198 name, parameters, return_type = (Callspec(node, parameter_comments) |
199 .process(callbacks)) | 199 .process(callbacks)) |
200 properties['parameters'] = parameters | 200 if functions_are_properties: |
201 if return_type is not None: | 201 # If functions are treated as properties (which will happen if the |
202 properties['returns'] = return_type | 202 # interface is named Properties) then this isn't a function, it's a |
203 # property which is encoded as a function with no arguments. The | |
204 # property type is the return type. This is an egregious hack in lieu | |
205 # of the IDL parser supporting 'const'. | |
206 assert parameters == [], ( | |
207 'Properties must be no-argument functions ' | |
208 'with a non-void return type') | |
Devlin
2015/04/01 22:46:19
including "name" here would probably be helpful if
not at google - send to devlin
2015/04/01 23:44:00
Done.
| |
209 assert return_type is not None, ( | |
210 'Properties must be no-argument functions ' | |
211 'with a non-void return type') | |
212 assert 'type' in return_type, ( | |
213 'Return type "%s" from "%s" must specify a fundamental IDL ' | |
214 'type.' % (pprint.pformat(return_type), name)) | |
215 type_override = return_type['type'] | |
216 else: | |
217 type_override = 'function' | |
218 properties['parameters'] = parameters | |
219 if return_type is not None: | |
220 properties['returns'] = return_type | |
203 properties['name'] = name | 221 properties['name'] = name |
204 if is_function: | 222 if type_override is not None: |
205 properties['type'] = 'function' | 223 properties['type'] = type_override |
206 else: | 224 else: |
207 properties = Typeref(self.node.GetProperty('TYPEREF'), | 225 properties = Typeref(self.node.GetProperty('TYPEREF'), |
208 self.node, properties).process(callbacks) | 226 self.node, properties).process(callbacks) |
227 value = self.node.GetProperty('value') | |
228 if value is not None: | |
229 # IDL always returns values as strings, so cast to their real type. | |
230 properties['value'] = self.cast_from_json_type(properties['type'], value) | |
209 enum_values = self.node.GetProperty('legalValues') | 231 enum_values = self.node.GetProperty('legalValues') |
210 if enum_values: | 232 if enum_values: |
211 if properties['type'] == 'integer': | 233 # IDL always returns enum values as strings, so cast to their real type. |
212 enum_values = map(int, enum_values) | 234 properties['enum'] = [self.cast_from_json_type(properties['type'], enum) |
Devlin
2015/04/01 22:46:19
why not:
map(lambda e: self.cast_from_json_type(pr
not at google - send to devlin
2015/04/01 23:44:00
List comprehensions are preferred over map for any
| |
213 elif properties['type'] == 'double': | 235 for enum in enum_values] |
214 enum_values = map(float, enum_values) | |
215 properties['enum'] = enum_values | |
216 return name, properties | 236 return name, properties |
217 | 237 |
238 def cast_from_json_type(self, json_type, string_value): | |
239 '''Casts from string |string_value| to a real Python type based on a JSON | |
240 Schema type |json_type|. For example, a string value of '42' and a JSON | |
241 Schema type 'integer' will cast to int('42') ==> 42. | |
242 ''' | |
243 if json_type == 'integer': | |
244 return int(string_value) | |
245 if json_type == 'number': | |
246 return float(string_value) | |
247 # Add more as necessary. | |
248 assert json_type == 'string', ( | |
249 'No rule exists to cast JSON Schema type "%s" to its equivalent ' | |
250 'Python type for value "%s". You must add a new rule here.' % | |
251 (json_type, string_value)) | |
252 return string_value | |
253 | |
218 | 254 |
219 class Typeref(object): | 255 class Typeref(object): |
220 ''' | 256 ''' |
221 Given a TYPEREF property representing the type of dictionary member or | 257 Given a TYPEREF property representing the type of dictionary member or |
222 function parameter, converts into a Python dictionary that the JSON schema | 258 function parameter, converts into a Python dictionary that the JSON schema |
223 compiler expects to see. | 259 compiler expects to see. |
224 ''' | 260 ''' |
225 def __init__(self, typeref, parent, additional_properties): | 261 def __init__(self, typeref, parent, additional_properties): |
226 self.typeref = typeref | 262 self.typeref = typeref |
227 self.parent = parent | 263 self.parent = parent |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
345 compiler_options=None, | 381 compiler_options=None, |
346 deprecated=None, | 382 deprecated=None, |
347 documentation_options=None): | 383 documentation_options=None): |
348 self.namespace = namespace_node | 384 self.namespace = namespace_node |
349 self.nodoc = nodoc | 385 self.nodoc = nodoc |
350 self.internal = internal | 386 self.internal = internal |
351 self.platforms = platforms | 387 self.platforms = platforms |
352 self.compiler_options = compiler_options | 388 self.compiler_options = compiler_options |
353 self.events = [] | 389 self.events = [] |
354 self.functions = [] | 390 self.functions = [] |
391 self.properties = OrderedDict() | |
355 self.types = [] | 392 self.types = [] |
356 self.callbacks = OrderedDict() | 393 self.callbacks = OrderedDict() |
357 self.description = description | 394 self.description = description |
358 self.deprecated = deprecated | 395 self.deprecated = deprecated |
359 self.documentation_options = documentation_options | 396 self.documentation_options = documentation_options |
360 | 397 |
361 def process(self): | 398 def process(self): |
362 for node in self.namespace.GetChildren(): | 399 for node in self.namespace.GetChildren(): |
363 if node.cls == 'Dictionary': | 400 if node.cls == 'Dictionary': |
364 self.types.append(Dictionary(node).process(self.callbacks)) | 401 self.types.append(Dictionary(node).process(self.callbacks)) |
365 elif node.cls == 'Callback': | 402 elif node.cls == 'Callback': |
366 k, v = Member(node).process(self.callbacks) | 403 k, v = Member(node).process(self.callbacks) |
367 self.callbacks[k] = v | 404 self.callbacks[k] = v |
368 elif node.cls == 'Interface' and node.GetName() == 'Functions': | 405 elif node.cls == 'Interface' and node.GetName() == 'Functions': |
369 self.functions = self.process_interface(node) | 406 self.functions = self.process_interface(node) |
370 elif node.cls == 'Interface' and node.GetName() == 'Events': | 407 elif node.cls == 'Interface' and node.GetName() == 'Events': |
371 self.events = self.process_interface(node) | 408 self.events = self.process_interface(node) |
409 elif node.cls == 'Interface' and node.GetName() == 'Properties': | |
410 # Properties are given as key-value pairs, but IDL will parse | |
411 # it as a list. Convert back to key-value pairs. | |
Devlin
2015/04/01 22:46:19
Nit: put this comment two lines lower (above where
not at google - send to devlin
2015/04/01 23:44:00
you could argue it either way (the reason why it's
| |
412 properties_as_list = self.process_interface( | |
413 node, functions_are_properties=True) | |
414 for prop in properties_as_list: | |
Devlin
2015/04/01 22:46:19
Would this be crazy?
map(lambda p: self.properties
not at google - send to devlin
2015/04/01 23:44:00
Yes, that would be crazy, however - good point, I
| |
415 name = prop.pop('name') | |
416 self.properties[name] = prop | |
Red Daly
2015/04/01 23:12:54
Does the IDL parser ensure there are no duplicate
not at google - send to devlin
2015/04/01 23:44:00
Good question - and I just tested, and it doesn't.
Devlin
2015/04/01 23:53:12
Enh, why not assert that we don't. Doesn't hurt.
not at google - send to devlin
2015/04/02 00:10:33
Done.
| |
372 elif node.cls == 'Enum': | 417 elif node.cls == 'Enum': |
373 self.types.append(Enum(node).process()) | 418 self.types.append(Enum(node).process()) |
374 else: | 419 else: |
375 sys.exit('Did not process %s %s' % (node.cls, node)) | 420 sys.exit('Did not process %s %s' % (node.cls, node)) |
376 compiler_options = self.compiler_options or {} | 421 compiler_options = self.compiler_options or {} |
377 documentation_options = self.documentation_options or {} | 422 documentation_options = self.documentation_options or {} |
378 return {'namespace': self.namespace.GetName(), | 423 return {'namespace': self.namespace.GetName(), |
379 'description': self.description, | 424 'description': self.description, |
380 'nodoc': self.nodoc, | 425 'nodoc': self.nodoc, |
381 'types': self.types, | 426 'types': self.types, |
382 'functions': self.functions, | 427 'functions': self.functions, |
428 'properties': self.properties, | |
383 'internal': self.internal, | 429 'internal': self.internal, |
384 'events': self.events, | 430 'events': self.events, |
385 'platforms': self.platforms, | 431 'platforms': self.platforms, |
386 'compiler_options': compiler_options, | 432 'compiler_options': compiler_options, |
387 'deprecated': self.deprecated, | 433 'deprecated': self.deprecated, |
388 'documentation_options': documentation_options} | 434 'documentation_options': documentation_options} |
389 | 435 |
390 def process_interface(self, node): | 436 def process_interface(self, node, functions_are_properties=False): |
391 members = [] | 437 members = [] |
392 for member in node.GetChildren(): | 438 for member in node.GetChildren(): |
393 if member.cls == 'Member': | 439 if member.cls == 'Member': |
394 _, properties = Member(member).process(self.callbacks) | 440 _, properties = Member(member).process( |
441 self.callbacks, | |
442 functions_are_properties=functions_are_properties) | |
395 members.append(properties) | 443 members.append(properties) |
396 return members | 444 return members |
397 | 445 |
398 | 446 |
399 class IDLSchema(object): | 447 class IDLSchema(object): |
400 ''' | 448 ''' |
401 Given a list of IDLNodes and IDLAttributes, converts into a Python list | 449 Given a list of IDLNodes and IDLAttributes, converts into a Python list |
402 of api_defs that the JSON schema compiler expects to see. | 450 of api_defs that the JSON schema compiler expects to see. |
403 ''' | 451 ''' |
404 | 452 |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
497 print json.dumps(schema, indent=2) | 545 print json.dumps(schema, indent=2) |
498 else: | 546 else: |
499 contents = sys.stdin.read() | 547 contents = sys.stdin.read() |
500 idl = idl_parser.IDLParser().ParseData(contents, '<stdin>') | 548 idl = idl_parser.IDLParser().ParseData(contents, '<stdin>') |
501 schema = IDLSchema(idl).process() | 549 schema = IDLSchema(idl).process() |
502 print json.dumps(schema, indent=2) | 550 print json.dumps(schema, indent=2) |
503 | 551 |
504 | 552 |
505 if __name__ == '__main__': | 553 if __name__ == '__main__': |
506 Main() | 554 Main() |
OLD | NEW |