Chromium Code Reviews| 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 |