| OLD | NEW |
| (Empty) |
| 1 #! /usr/bin/env python | |
| 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 | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 import itertools | |
| 7 import json | |
| 8 import os.path | |
| 9 import re | |
| 10 import sys | |
| 11 | |
| 12 from json_parse import OrderedDict | |
| 13 import schema_util | |
| 14 | |
| 15 # This file is a peer to json_schema.py. Each of these files understands a | |
| 16 # certain format describing APIs (either JSON or IDL), reads files written | |
| 17 # in that format into memory, and emits them as a Python array of objects | |
| 18 # corresponding to those APIs, where the objects are formatted in a way that | |
| 19 # the JSON schema compiler understands. compiler.py drives both idl_schema.py | |
| 20 # and json_schema.py. | |
| 21 | |
| 22 # idl_parser expects to be able to import certain files in its directory, | |
| 23 # so let's set things up the way it wants. | |
| 24 _idl_generators_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), | |
| 25 os.pardir, os.pardir, 'ppapi', 'generators') | |
| 26 if _idl_generators_path in sys.path: | |
| 27 import idl_parser | |
| 28 else: | |
| 29 sys.path.insert(0, _idl_generators_path) | |
| 30 try: | |
| 31 import idl_parser | |
| 32 finally: | |
| 33 sys.path.pop(0) | |
| 34 | |
| 35 def ProcessComment(comment): | |
| 36 ''' | |
| 37 Convert a comment into a parent comment and a list of parameter comments. | |
| 38 | |
| 39 Function comments are of the form: | |
| 40 Function documentation. May contain HTML and multiple lines. | |
| 41 | |
| 42 |arg1_name|: Description of arg1. Use <var>argument</var> to refer | |
| 43 to other arguments. | |
| 44 |arg2_name|: Description of arg2... | |
| 45 | |
| 46 Newlines are removed, and leading and trailing whitespace is stripped. | |
| 47 | |
| 48 Args: | |
| 49 comment: The string from a Comment node. | |
| 50 | |
| 51 Returns: A tuple that looks like: | |
| 52 ( | |
| 53 "The processed comment, minus all |parameter| mentions.", | |
| 54 { | |
| 55 'parameter_name_1': "The comment that followed |parameter_name_1|:", | |
| 56 ... | |
| 57 } | |
| 58 ) | |
| 59 ''' | |
| 60 # Find all the parameter comments of the form '|name|: comment'. | |
| 61 parameter_starts = list(re.finditer(r' *\|([^|]*)\| *: *', comment)) | |
| 62 | |
| 63 # Get the parent comment (everything before the first parameter comment. | |
| 64 first_parameter_location = (parameter_starts[0].start() | |
| 65 if parameter_starts else len(comment)) | |
| 66 parent_comment = comment[:first_parameter_location] | |
| 67 | |
| 68 # We replace \n\n with <br/><br/> here and below, because the documentation | |
| 69 # needs to know where the newlines should be, and this is easier than | |
| 70 # escaping \n. | |
| 71 parent_comment = (parent_comment.strip().replace('\n\n', '<br/><br/>') | |
| 72 .replace('\n', '')) | |
| 73 | |
| 74 params = OrderedDict() | |
| 75 for (cur_param, next_param) in itertools.izip_longest(parameter_starts, | |
| 76 parameter_starts[1:]): | |
| 77 param_name = cur_param.group(1) | |
| 78 | |
| 79 # A parameter's comment goes from the end of its introduction to the | |
| 80 # beginning of the next parameter's introduction. | |
| 81 param_comment_start = cur_param.end() | |
| 82 param_comment_end = next_param.start() if next_param else len(comment) | |
| 83 params[param_name] = (comment[param_comment_start:param_comment_end | |
| 84 ].strip().replace('\n\n', '<br/><br/>') | |
| 85 .replace('\n', '')) | |
| 86 return (parent_comment, params) | |
| 87 | |
| 88 class Callspec(object): | |
| 89 ''' | |
| 90 Given a Callspec node representing an IDL function declaration, converts into | |
| 91 a name/value pair where the value is a list of function parameters. | |
| 92 ''' | |
| 93 def __init__(self, callspec_node, comment): | |
| 94 self.node = callspec_node | |
| 95 self.comment = comment | |
| 96 | |
| 97 def process(self, callbacks): | |
| 98 parameters = [] | |
| 99 for node in self.node.children: | |
| 100 parameter = Param(node).process(callbacks) | |
| 101 if parameter['name'] in self.comment: | |
| 102 parameter['description'] = self.comment[parameter['name']] | |
| 103 parameters.append(parameter) | |
| 104 return self.node.GetName(), parameters | |
| 105 | |
| 106 class Param(object): | |
| 107 ''' | |
| 108 Given a Param node representing a function parameter, converts into a Python | |
| 109 dictionary that the JSON schema compiler expects to see. | |
| 110 ''' | |
| 111 def __init__(self, param_node): | |
| 112 self.node = param_node | |
| 113 | |
| 114 def process(self, callbacks): | |
| 115 return Typeref(self.node.GetProperty('TYPEREF'), | |
| 116 self.node, | |
| 117 {'name': self.node.GetName()}).process(callbacks) | |
| 118 | |
| 119 class Dictionary(object): | |
| 120 ''' | |
| 121 Given an IDL Dictionary node, converts into a Python dictionary that the JSON | |
| 122 schema compiler expects to see. | |
| 123 ''' | |
| 124 def __init__(self, dictionary_node): | |
| 125 self.node = dictionary_node | |
| 126 | |
| 127 def process(self, callbacks): | |
| 128 properties = OrderedDict() | |
| 129 for node in self.node.children: | |
| 130 if node.cls == 'Member': | |
| 131 k, v = Member(node).process(callbacks) | |
| 132 properties[k] = v | |
| 133 result = {'id': self.node.GetName(), | |
| 134 'properties': properties, | |
| 135 'type': 'object'} | |
| 136 if self.node.GetProperty('inline_doc'): | |
| 137 result['inline_doc'] = True | |
| 138 return result | |
| 139 | |
| 140 | |
| 141 class Member(object): | |
| 142 ''' | |
| 143 Given an IDL dictionary or interface member, converts into a name/value pair | |
| 144 where the value is a Python dictionary that the JSON schema compiler expects | |
| 145 to see. | |
| 146 ''' | |
| 147 def __init__(self, member_node): | |
| 148 self.node = member_node | |
| 149 | |
| 150 def process(self, callbacks): | |
| 151 properties = OrderedDict() | |
| 152 name = self.node.GetName() | |
| 153 for property_name in ('OPTIONAL', 'nodoc', 'nocompile'): | |
| 154 if self.node.GetProperty(property_name): | |
| 155 properties[property_name.lower()] = True | |
| 156 is_function = False | |
| 157 parameter_comments = OrderedDict() | |
| 158 for node in self.node.children: | |
| 159 if node.cls == 'Comment': | |
| 160 (parent_comment, parameter_comments) = ProcessComment(node.GetName()) | |
| 161 properties['description'] = parent_comment | |
| 162 elif node.cls == 'Callspec': | |
| 163 is_function = True | |
| 164 name, parameters = Callspec(node, parameter_comments).process(callbacks) | |
| 165 properties['parameters'] = parameters | |
| 166 properties['name'] = name | |
| 167 if is_function: | |
| 168 properties['type'] = 'function' | |
| 169 else: | |
| 170 properties = Typeref(self.node.GetProperty('TYPEREF'), | |
| 171 self.node, properties).process(callbacks) | |
| 172 enum_values = self.node.GetProperty('legalValues') | |
| 173 if enum_values: | |
| 174 if properties['type'] == 'integer': | |
| 175 enum_values = map(int, enum_values) | |
| 176 elif properties['type'] == 'double': | |
| 177 enum_values = map(float, enum_values) | |
| 178 properties['enum'] = enum_values | |
| 179 return name, properties | |
| 180 | |
| 181 class Typeref(object): | |
| 182 ''' | |
| 183 Given a TYPEREF property representing the type of dictionary member or | |
| 184 function parameter, converts into a Python dictionary that the JSON schema | |
| 185 compiler expects to see. | |
| 186 ''' | |
| 187 def __init__(self, typeref, parent, additional_properties=OrderedDict()): | |
| 188 self.typeref = typeref | |
| 189 self.parent = parent | |
| 190 self.additional_properties = additional_properties | |
| 191 | |
| 192 def process(self, callbacks): | |
| 193 properties = self.additional_properties | |
| 194 result = properties | |
| 195 | |
| 196 if self.parent.GetProperty('OPTIONAL', False): | |
| 197 properties['optional'] = True | |
| 198 | |
| 199 # The IDL parser denotes array types by adding a child 'Array' node onto | |
| 200 # the Param node in the Callspec. | |
| 201 for sibling in self.parent.GetChildren(): | |
| 202 if sibling.cls == 'Array' and sibling.GetName() == self.parent.GetName(): | |
| 203 properties['type'] = 'array' | |
| 204 properties['items'] = OrderedDict() | |
| 205 properties = properties['items'] | |
| 206 break | |
| 207 | |
| 208 if self.typeref == 'DOMString': | |
| 209 properties['type'] = 'string' | |
| 210 elif self.typeref == 'boolean': | |
| 211 properties['type'] = 'boolean' | |
| 212 elif self.typeref == 'double': | |
| 213 properties['type'] = 'number' | |
| 214 elif self.typeref == 'long': | |
| 215 properties['type'] = 'integer' | |
| 216 elif self.typeref == 'any': | |
| 217 properties['type'] = 'any' | |
| 218 elif self.typeref == 'object': | |
| 219 properties['type'] = 'object' | |
| 220 if 'additionalProperties' not in properties: | |
| 221 properties['additionalProperties'] = OrderedDict() | |
| 222 properties['additionalProperties']['type'] = 'any' | |
| 223 instance_of = self.parent.GetProperty('instanceOf') | |
| 224 if instance_of: | |
| 225 properties['isInstanceOf'] = instance_of | |
| 226 elif self.typeref == 'ArrayBuffer': | |
| 227 properties['type'] = 'binary' | |
| 228 properties['isInstanceOf'] = 'ArrayBuffer' | |
| 229 elif self.typeref is None: | |
| 230 properties['type'] = 'function' | |
| 231 else: | |
| 232 if self.typeref in callbacks: | |
| 233 # Do not override name and description if they are already specified. | |
| 234 name = properties.get('name', None) | |
| 235 description = properties.get('description', None) | |
| 236 properties.update(callbacks[self.typeref]) | |
| 237 if description is not None: | |
| 238 properties['description'] = description | |
| 239 if name is not None: | |
| 240 properties['name'] = name | |
| 241 else: | |
| 242 properties['$ref'] = self.typeref | |
| 243 return result | |
| 244 | |
| 245 | |
| 246 class Enum(object): | |
| 247 ''' | |
| 248 Given an IDL Enum node, converts into a Python dictionary that the JSON | |
| 249 schema compiler expects to see. | |
| 250 ''' | |
| 251 def __init__(self, enum_node): | |
| 252 self.node = enum_node | |
| 253 self.description = '' | |
| 254 | |
| 255 def process(self, callbacks): | |
| 256 enum = [] | |
| 257 for node in self.node.children: | |
| 258 if node.cls == 'EnumItem': | |
| 259 enum.append(node.GetName()) | |
| 260 elif node.cls == 'Comment': | |
| 261 self.description = ProcessComment(node.GetName())[0] | |
| 262 else: | |
| 263 sys.exit('Did not process %s %s' % (node.cls, node)) | |
| 264 result = {'id' : self.node.GetName(), | |
| 265 'description': self.description, | |
| 266 'type': 'string', | |
| 267 'enum': enum} | |
| 268 if self.node.GetProperty('inline_doc'): | |
| 269 result['inline_doc'] = True | |
| 270 return result | |
| 271 | |
| 272 | |
| 273 class Namespace(object): | |
| 274 ''' | |
| 275 Given an IDLNode representing an IDL namespace, converts into a Python | |
| 276 dictionary that the JSON schema compiler expects to see. | |
| 277 ''' | |
| 278 | |
| 279 def __init__(self, namespace_node, nodoc=False, permissions=None, | |
| 280 internal=False): | |
| 281 self.namespace = namespace_node | |
| 282 self.nodoc = nodoc | |
| 283 self.internal = internal | |
| 284 self.events = [] | |
| 285 self.functions = [] | |
| 286 self.types = [] | |
| 287 self.callbacks = OrderedDict() | |
| 288 self.permissions = permissions or [] | |
| 289 | |
| 290 def process(self): | |
| 291 for node in self.namespace.children: | |
| 292 if node.cls == 'Dictionary': | |
| 293 self.types.append(Dictionary(node).process(self.callbacks)) | |
| 294 elif node.cls == 'Callback': | |
| 295 k, v = Member(node).process(self.callbacks) | |
| 296 self.callbacks[k] = v | |
| 297 elif node.cls == 'Interface' and node.GetName() == 'Functions': | |
| 298 self.functions = self.process_interface(node) | |
| 299 elif node.cls == 'Interface' and node.GetName() == 'Events': | |
| 300 self.events = self.process_interface(node) | |
| 301 elif node.cls == 'Enum': | |
| 302 self.types.append(Enum(node).process(self.callbacks)) | |
| 303 else: | |
| 304 sys.exit('Did not process %s %s' % (node.cls, node)) | |
| 305 return {'namespace': self.namespace.GetName(), | |
| 306 'nodoc': self.nodoc, | |
| 307 'documentation_permissions_required': self.permissions, | |
| 308 'types': self.types, | |
| 309 'functions': self.functions, | |
| 310 'internal': self.internal, | |
| 311 'events': self.events} | |
| 312 | |
| 313 def process_interface(self, node): | |
| 314 members = [] | |
| 315 for member in node.children: | |
| 316 if member.cls == 'Member': | |
| 317 name, properties = Member(member).process(self.callbacks) | |
| 318 members.append(properties) | |
| 319 return members | |
| 320 | |
| 321 class IDLSchema(object): | |
| 322 ''' | |
| 323 Given a list of IDLNodes and IDLAttributes, converts into a Python list | |
| 324 of api_defs that the JSON schema compiler expects to see. | |
| 325 ''' | |
| 326 | |
| 327 def __init__(self, idl): | |
| 328 self.idl = idl | |
| 329 | |
| 330 def process(self): | |
| 331 namespaces = [] | |
| 332 nodoc = False | |
| 333 internal = False | |
| 334 permissions = None | |
| 335 for node in self.idl: | |
| 336 if node.cls == 'Namespace': | |
| 337 namespace = Namespace(node, nodoc, permissions, internal) | |
| 338 namespaces.append(namespace.process()) | |
| 339 nodoc = False | |
| 340 internal = False | |
| 341 elif node.cls == 'Copyright': | |
| 342 continue | |
| 343 elif node.cls == 'Comment': | |
| 344 continue | |
| 345 elif node.cls == 'ExtAttribute': | |
| 346 if node.name == 'nodoc': | |
| 347 nodoc = bool(node.value) | |
| 348 elif node.name == 'permissions': | |
| 349 permission = node.value.split(',') | |
| 350 elif node.name == 'internal': | |
| 351 internal = bool(node.value) | |
| 352 else: | |
| 353 continue | |
| 354 else: | |
| 355 sys.exit('Did not process %s %s' % (node.cls, node)) | |
| 356 return namespaces | |
| 357 | |
| 358 def Load(filename): | |
| 359 ''' | |
| 360 Given the filename of an IDL file, parses it and returns an equivalent | |
| 361 Python dictionary in a format that the JSON schema compiler expects to see. | |
| 362 ''' | |
| 363 | |
| 364 f = open(filename, 'r') | |
| 365 contents = f.read() | |
| 366 f.close() | |
| 367 | |
| 368 idl = idl_parser.IDLParser().ParseData(contents, filename) | |
| 369 idl_schema = IDLSchema(idl) | |
| 370 return idl_schema.process() | |
| 371 | |
| 372 def Main(): | |
| 373 ''' | |
| 374 Dump a json serialization of parse result for the IDL files whose names | |
| 375 were passed in on the command line. | |
| 376 ''' | |
| 377 for filename in sys.argv[1:]: | |
| 378 schema = Load(filename) | |
| 379 print json.dumps(schema, indent=2) | |
| 380 | |
| 381 if __name__ == '__main__': | |
| 382 Main() | |
| OLD | NEW |