Screen Orientation Controller allow rotation for some orientations
[chromium-blink-merge.git] / tools / json_schema_compiler / idl_schema.py
blob45f0609e49bc3878e5df3328a2995d2021b61b87
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.
6 import itertools
7 import json
8 import os.path
9 import re
10 import sys
12 from json_parse import OrderedDict
14 # 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 # 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 # the JSON schema compiler understands. compiler.py drives both idl_schema.py
19 # and json_schema.py.
21 # idl_parser expects to be able to import certain files in its directory,
22 # so let's set things up the way it wants.
23 _idl_generators_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
24 os.pardir, os.pardir, 'ppapi', 'generators')
25 if _idl_generators_path in sys.path:
26 import idl_parser
27 else:
28 sys.path.insert(0, _idl_generators_path)
29 try:
30 import idl_parser
31 finally:
32 sys.path.pop(0)
34 def ProcessComment(comment):
35 '''
36 Convert a comment into a parent comment and a list of parameter comments.
38 Function comments are of the form:
39 Function documentation. May contain HTML and multiple lines.
41 |arg1_name|: Description of arg1. Use <var>argument</var> to refer
42 to other arguments.
43 |arg2_name|: Description of arg2...
45 Newlines are removed, and leading and trailing whitespace is stripped.
47 Args:
48 comment: The string from a Comment node.
50 Returns: A tuple that looks like:
52 "The processed comment, minus all |parameter| mentions.",
54 'parameter_name_1': "The comment that followed |parameter_name_1|:",
55 ...
58 '''
59 def add_paragraphs(content):
60 paragraphs = content.split('\n\n')
61 if len(paragraphs) < 2:
62 return content
63 return '<p>' + '</p><p>'.join(p.strip() for p in paragraphs) + '</p>'
65 # Find all the parameter comments of the form '|name|: comment'.
66 parameter_starts = list(re.finditer(r' *\|([^|]*)\| *: *', comment))
68 # Get the parent comment (everything before the first parameter comment.
69 first_parameter_location = (parameter_starts[0].start()
70 if parameter_starts else len(comment))
71 parent_comment = (add_paragraphs(comment[:first_parameter_location].strip())
72 .replace('\n', ''))
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)
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] = (
84 add_paragraphs(comment[param_comment_start:param_comment_end].strip())
85 .replace('\n', ''))
87 return (parent_comment, params)
90 class Callspec(object):
91 '''
92 Given a Callspec node representing an IDL function declaration, converts into
93 a tuple:
94 (name, list of function parameters, return type)
95 '''
96 def __init__(self, callspec_node, comment):
97 self.node = callspec_node
98 self.comment = comment
100 def process(self, callbacks):
101 parameters = []
102 return_type = None
103 if self.node.GetProperty('TYPEREF') not in ('void', None):
104 return_type = Typeref(self.node.GetProperty('TYPEREF'),
105 self.node.parent,
106 {'name': self.node.GetName()}).process(callbacks)
107 # The IDL parser doesn't allow specifying return types as optional.
108 # Instead we infer any object return values to be optional.
109 # TODO(asargent): fix the IDL parser to support optional return types.
110 if return_type.get('type') == 'object' or '$ref' in return_type:
111 return_type['optional'] = True
112 for node in self.node.GetChildren():
113 parameter = Param(node).process(callbacks)
114 if parameter['name'] in self.comment:
115 parameter['description'] = self.comment[parameter['name']]
116 parameters.append(parameter)
117 return (self.node.GetName(), parameters, return_type)
120 class Param(object):
122 Given a Param node representing a function parameter, converts into a Python
123 dictionary that the JSON schema compiler expects to see.
125 def __init__(self, param_node):
126 self.node = param_node
128 def process(self, callbacks):
129 return Typeref(self.node.GetProperty('TYPEREF'),
130 self.node,
131 {'name': self.node.GetName()}).process(callbacks)
134 class Dictionary(object):
136 Given an IDL Dictionary node, converts into a Python dictionary that the JSON
137 schema compiler expects to see.
139 def __init__(self, dictionary_node):
140 self.node = dictionary_node
142 def process(self, callbacks):
143 properties = OrderedDict()
144 for node in self.node.GetChildren():
145 if node.cls == 'Member':
146 k, v = Member(node).process(callbacks)
147 properties[k] = v
148 result = {'id': self.node.GetName(),
149 'properties': properties,
150 'type': 'object'}
151 if self.node.GetProperty('nodoc'):
152 result['nodoc'] = True
153 elif self.node.GetProperty('inline_doc'):
154 result['inline_doc'] = True
155 elif self.node.GetProperty('noinline_doc'):
156 result['noinline_doc'] = True
157 return result
161 class Member(object):
163 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 to see.
167 def __init__(self, member_node):
168 self.node = member_node
170 def process(self, callbacks):
171 properties = OrderedDict()
172 name = self.node.GetName()
173 if self.node.GetProperty('deprecated'):
174 properties['deprecated'] = self.node.GetProperty('deprecated')
175 if self.node.GetProperty('allowAmbiguousOptionalArguments'):
176 properties['allowAmbiguousOptionalArguments'] = True
177 for property_name in ('OPTIONAL', 'nodoc', 'nocompile', 'nodart'):
178 if self.node.GetProperty(property_name):
179 properties[property_name.lower()] = True
180 for option_name, sanitizer in [
181 ('maxListeners', int),
182 ('supportsFilters', lambda s: s == 'true'),
183 ('supportsListeners', lambda s: s == 'true'),
184 ('supportsRules', lambda s: s == 'true')]:
185 if self.node.GetProperty(option_name):
186 if 'options' not in properties:
187 properties['options'] = {}
188 properties['options'][option_name] = sanitizer(self.node.GetProperty(
189 option_name))
190 is_function = False
191 parameter_comments = OrderedDict()
192 for node in self.node.GetChildren():
193 if node.cls == 'Comment':
194 (parent_comment, parameter_comments) = ProcessComment(node.GetName())
195 properties['description'] = parent_comment
196 elif node.cls == 'Callspec':
197 is_function = True
198 name, parameters, return_type = (Callspec(node, parameter_comments)
199 .process(callbacks))
200 properties['parameters'] = parameters
201 if return_type is not None:
202 properties['returns'] = return_type
203 properties['name'] = name
204 if is_function:
205 properties['type'] = 'function'
206 else:
207 properties = Typeref(self.node.GetProperty('TYPEREF'),
208 self.node, properties).process(callbacks)
209 enum_values = self.node.GetProperty('legalValues')
210 if enum_values:
211 if properties['type'] == 'integer':
212 enum_values = map(int, enum_values)
213 elif properties['type'] == 'double':
214 enum_values = map(float, enum_values)
215 properties['enum'] = enum_values
216 return name, properties
219 class Typeref(object):
221 Given a TYPEREF property representing the type of dictionary member or
222 function parameter, converts into a Python dictionary that the JSON schema
223 compiler expects to see.
225 def __init__(self, typeref, parent, additional_properties):
226 self.typeref = typeref
227 self.parent = parent
228 self.additional_properties = additional_properties
230 def process(self, callbacks):
231 properties = self.additional_properties
232 result = properties
234 if self.parent.GetPropertyLocal('OPTIONAL'):
235 properties['optional'] = True
237 # The IDL parser denotes array types by adding a child 'Array' node onto
238 # the Param node in the Callspec.
239 for sibling in self.parent.GetChildren():
240 if sibling.cls == 'Array' and sibling.GetName() == self.parent.GetName():
241 properties['type'] = 'array'
242 properties['items'] = OrderedDict()
243 properties = properties['items']
244 break
246 if self.typeref == 'DOMString':
247 properties['type'] = 'string'
248 elif self.typeref == 'boolean':
249 properties['type'] = 'boolean'
250 elif self.typeref == 'double':
251 properties['type'] = 'number'
252 elif self.typeref == 'long':
253 properties['type'] = 'integer'
254 elif self.typeref == 'any':
255 properties['type'] = 'any'
256 elif self.typeref == 'object':
257 properties['type'] = 'object'
258 if 'additionalProperties' not in properties:
259 properties['additionalProperties'] = OrderedDict()
260 properties['additionalProperties']['type'] = 'any'
261 instance_of = self.parent.GetProperty('instanceOf')
262 if instance_of:
263 properties['isInstanceOf'] = instance_of
264 elif self.typeref == 'ArrayBuffer':
265 properties['type'] = 'binary'
266 properties['isInstanceOf'] = 'ArrayBuffer'
267 elif self.typeref == 'FileEntry':
268 properties['type'] = 'object'
269 properties['isInstanceOf'] = 'FileEntry'
270 if 'additionalProperties' not in properties:
271 properties['additionalProperties'] = OrderedDict()
272 properties['additionalProperties']['type'] = 'any'
273 elif self.parent.GetPropertyLocal('Union'):
274 properties['choices'] = [Typeref(node.GetProperty('TYPEREF'),
275 node,
276 OrderedDict()).process(callbacks)
277 for node in self.parent.GetChildren()
278 if node.cls == 'Option']
279 elif self.typeref is None:
280 properties['type'] = 'function'
281 else:
282 if self.typeref in callbacks:
283 # Do not override name and description if they are already specified.
284 name = properties.get('name', None)
285 description = properties.get('description', None)
286 properties.update(callbacks[self.typeref])
287 if description is not None:
288 properties['description'] = description
289 if name is not None:
290 properties['name'] = name
291 else:
292 properties['$ref'] = self.typeref
293 return result
296 class Enum(object):
298 Given an IDL Enum node, converts into a Python dictionary that the JSON
299 schema compiler expects to see.
301 def __init__(self, enum_node):
302 self.node = enum_node
303 self.description = ''
305 def process(self):
306 enum = []
307 for node in self.node.GetChildren():
308 if node.cls == 'EnumItem':
309 enum_value = {'name': node.GetName()}
310 for child in node.GetChildren():
311 if child.cls == 'Comment':
312 enum_value['description'] = ProcessComment(child.GetName())[0]
313 else:
314 raise ValueError('Did not process %s %s' % (child.cls, child))
315 enum.append(enum_value)
316 elif node.cls == 'Comment':
317 self.description = ProcessComment(node.GetName())[0]
318 else:
319 sys.exit('Did not process %s %s' % (node.cls, node))
320 result = {'id' : self.node.GetName(),
321 'description': self.description,
322 'type': 'string',
323 'enum': enum}
324 for property_name in (
325 'inline_doc', 'noinline_doc', 'nodoc', 'cpp_enum_prefix_override',):
326 if self.node.GetProperty(property_name):
327 result[property_name] = self.node.GetProperty(property_name)
328 if self.node.GetProperty('deprecated'):
329 result['deprecated'] = self.node.GetProperty('deprecated')
330 return result
333 class Namespace(object):
335 Given an IDLNode representing an IDL namespace, converts into a Python
336 dictionary that the JSON schema compiler expects to see.
339 def __init__(self,
340 namespace_node,
341 description,
342 nodoc=False,
343 internal=False,
344 platforms=None,
345 compiler_options=None,
346 deprecated=None):
347 self.namespace = namespace_node
348 self.nodoc = nodoc
349 self.internal = internal
350 self.platforms = platforms
351 self.compiler_options = compiler_options
352 self.events = []
353 self.functions = []
354 self.types = []
355 self.callbacks = OrderedDict()
356 self.description = description
357 self.deprecated = deprecated
359 def process(self):
360 for node in self.namespace.GetChildren():
361 if node.cls == 'Dictionary':
362 self.types.append(Dictionary(node).process(self.callbacks))
363 elif node.cls == 'Callback':
364 k, v = Member(node).process(self.callbacks)
365 self.callbacks[k] = v
366 elif node.cls == 'Interface' and node.GetName() == 'Functions':
367 self.functions = self.process_interface(node)
368 elif node.cls == 'Interface' and node.GetName() == 'Events':
369 self.events = self.process_interface(node)
370 elif node.cls == 'Enum':
371 self.types.append(Enum(node).process())
372 else:
373 sys.exit('Did not process %s %s' % (node.cls, node))
374 if self.compiler_options is not None:
375 compiler_options = self.compiler_options
376 else:
377 compiler_options = {}
378 return {'namespace': self.namespace.GetName(),
379 'description': self.description,
380 'nodoc': self.nodoc,
381 'types': self.types,
382 'functions': self.functions,
383 'internal': self.internal,
384 'events': self.events,
385 'platforms': self.platforms,
386 'compiler_options': compiler_options,
387 'deprecated': self.deprecated}
389 def process_interface(self, node):
390 members = []
391 for member in node.GetChildren():
392 if member.cls == 'Member':
393 _, properties = Member(member).process(self.callbacks)
394 members.append(properties)
395 return members
398 class IDLSchema(object):
400 Given a list of IDLNodes and IDLAttributes, converts into a Python list
401 of api_defs that the JSON schema compiler expects to see.
404 def __init__(self, idl):
405 self.idl = idl
407 def process(self):
408 namespaces = []
409 nodoc = False
410 internal = False
411 description = None
412 platforms = None
413 compiler_options = {}
414 deprecated = None
415 for node in self.idl:
416 if node.cls == 'Namespace':
417 if not description:
418 # TODO(kalman): Go back to throwing an error here.
419 print('%s must have a namespace-level comment. This will '
420 'appear on the API summary page.' % node.GetName())
421 description = ''
422 namespace = Namespace(node, description, nodoc, internal,
423 platforms=platforms,
424 compiler_options=compiler_options or None,
425 deprecated=deprecated)
426 namespaces.append(namespace.process())
427 nodoc = False
428 internal = False
429 platforms = None
430 compiler_options = None
431 elif node.cls == 'Copyright':
432 continue
433 elif node.cls == 'Comment':
434 description = node.GetName()
435 elif node.cls == 'ExtAttribute':
436 if node.name == 'nodoc':
437 nodoc = bool(node.value)
438 elif node.name == 'internal':
439 internal = bool(node.value)
440 elif node.name == 'platforms':
441 platforms = list(node.value)
442 elif node.name == 'implemented_in':
443 compiler_options['implemented_in'] = node.value
444 elif node.name == 'camel_case_enum_to_string':
445 compiler_options['camel_case_enum_to_string'] = node.value
446 elif node.name == 'deprecated':
447 deprecated = str(node.value)
448 else:
449 continue
450 else:
451 sys.exit('Did not process %s %s' % (node.cls, node))
452 return namespaces
455 def Load(filename):
457 Given the filename of an IDL file, parses it and returns an equivalent
458 Python dictionary in a format that the JSON schema compiler expects to see.
461 f = open(filename, 'r')
462 contents = f.read()
463 f.close()
465 idl = idl_parser.IDLParser().ParseData(contents, filename)
466 idl_schema = IDLSchema(idl)
467 return idl_schema.process()
470 def Main():
472 Dump a json serialization of parse result for the IDL files whose names
473 were passed in on the command line.
475 if len(sys.argv) > 1:
476 for filename in sys.argv[1:]:
477 schema = Load(filename)
478 print json.dumps(schema, indent=2)
479 else:
480 contents = sys.stdin.read()
481 idl = idl_parser.IDLParser().ParseData(contents, '<stdin>')
482 schema = IDLSchema(idl).process()
483 print json.dumps(schema, indent=2)
486 if __name__ == '__main__':
487 Main()