Support policy registration using a preobtained access token.
[chromium-blink-merge.git] / tools / json_schema_compiler / idl_schema.py
blob991b1c6bb9d060ce44ca842555b345fd2dc509e6
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 # Find all the parameter comments of the form '|name|: comment'.
60 parameter_starts = list(re.finditer(r' *\|([^|]*)\| *: *', comment))
62 # Get the parent comment (everything before the first parameter comment.
63 first_parameter_location = (parameter_starts[0].start()
64 if parameter_starts else len(comment))
65 parent_comment = comment[:first_parameter_location]
67 # We replace \n\n with <br/><br/> here and below, because the documentation
68 # needs to know where the newlines should be, and this is easier than
69 # escaping \n.
70 parent_comment = (parent_comment.strip().replace('\n\n', '<br/><br/>')
71 .replace('\n', ''))
73 params = OrderedDict()
74 for (cur_param, next_param) in itertools.izip_longest(parameter_starts,
75 parameter_starts[1:]):
76 param_name = cur_param.group(1)
78 # A parameter's comment goes from the end of its introduction to the
79 # beginning of the next parameter's introduction.
80 param_comment_start = cur_param.end()
81 param_comment_end = next_param.start() if next_param else len(comment)
82 params[param_name] = (comment[param_comment_start:param_comment_end
83 ].strip().replace('\n\n', '<br/><br/>')
84 .replace('\n', ''))
85 return (parent_comment, params)
88 class Callspec(object):
89 '''
90 Given a Callspec node representing an IDL function declaration, converts into
91 a tuple:
92 (name, list of function parameters, return type)
93 '''
94 def __init__(self, callspec_node, comment):
95 self.node = callspec_node
96 self.comment = comment
98 def process(self, callbacks):
99 parameters = []
100 return_type = None
101 if self.node.GetProperty('TYPEREF') not in ('void', None):
102 return_type = Typeref(self.node.GetProperty('TYPEREF'),
103 self.node.parent,
104 {'name': self.node.GetName()}).process(callbacks)
105 # The IDL parser doesn't allow specifying return types as optional.
106 # Instead we infer any object return values to be optional.
107 # TODO(asargent): fix the IDL parser to support optional return types.
108 if return_type.get('type') == 'object' or '$ref' in return_type:
109 return_type['optional'] = True
110 for node in self.node.GetChildren():
111 parameter = Param(node).process(callbacks)
112 if parameter['name'] in self.comment:
113 parameter['description'] = self.comment[parameter['name']]
114 parameters.append(parameter)
115 return (self.node.GetName(), parameters, return_type)
118 class Param(object):
120 Given a Param node representing a function parameter, converts into a Python
121 dictionary that the JSON schema compiler expects to see.
123 def __init__(self, param_node):
124 self.node = param_node
126 def process(self, callbacks):
127 return Typeref(self.node.GetProperty('TYPEREF'),
128 self.node,
129 {'name': self.node.GetName()}).process(callbacks)
132 class Dictionary(object):
134 Given an IDL Dictionary node, converts into a Python dictionary that the JSON
135 schema compiler expects to see.
137 def __init__(self, dictionary_node):
138 self.node = dictionary_node
140 def process(self, callbacks):
141 properties = OrderedDict()
142 for node in self.node.GetChildren():
143 if node.cls == 'Member':
144 k, v = Member(node).process(callbacks)
145 properties[k] = v
146 result = {'id': self.node.GetName(),
147 'properties': properties,
148 'type': 'object'}
149 if self.node.GetProperty('nodoc'):
150 result['nodoc'] = True
151 elif self.node.GetProperty('inline_doc'):
152 result['inline_doc'] = True
153 elif self.node.GetProperty('noinline_doc'):
154 result['noinline_doc'] = True
155 return result
159 class Member(object):
161 Given an IDL dictionary or interface member, converts into a name/value pair
162 where the value is a Python dictionary that the JSON schema compiler expects
163 to see.
165 def __init__(self, member_node):
166 self.node = member_node
168 def process(self, callbacks):
169 properties = OrderedDict()
170 name = self.node.GetName()
171 if self.node.GetProperty('deprecated'):
172 properties['deprecated'] = self.node.GetProperty('deprecated')
173 for property_name in ('OPTIONAL', 'nodoc', 'nocompile', 'nodart'):
174 if self.node.GetProperty(property_name):
175 properties[property_name.lower()] = True
176 for option_name, sanitizer in [
177 ('maxListeners', int),
178 ('supportsFilters', lambda s: s == 'true'),
179 ('supportsListeners', lambda s: s == 'true'),
180 ('supportsRules', lambda s: s == 'true')]:
181 if self.node.GetProperty(option_name):
182 if 'options' not in properties:
183 properties['options'] = {}
184 properties['options'][option_name] = sanitizer(self.node.GetProperty(
185 option_name))
186 is_function = False
187 parameter_comments = OrderedDict()
188 for node in self.node.GetChildren():
189 if node.cls == 'Comment':
190 (parent_comment, parameter_comments) = ProcessComment(node.GetName())
191 properties['description'] = parent_comment
192 elif node.cls == 'Callspec':
193 is_function = True
194 name, parameters, return_type = (Callspec(node, parameter_comments)
195 .process(callbacks))
196 properties['parameters'] = parameters
197 if return_type is not None:
198 properties['returns'] = return_type
199 properties['name'] = name
200 if is_function:
201 properties['type'] = 'function'
202 else:
203 properties = Typeref(self.node.GetProperty('TYPEREF'),
204 self.node, properties).process(callbacks)
205 enum_values = self.node.GetProperty('legalValues')
206 if enum_values:
207 if properties['type'] == 'integer':
208 enum_values = map(int, enum_values)
209 elif properties['type'] == 'double':
210 enum_values = map(float, enum_values)
211 properties['enum'] = enum_values
212 return name, properties
215 class Typeref(object):
217 Given a TYPEREF property representing the type of dictionary member or
218 function parameter, converts into a Python dictionary that the JSON schema
219 compiler expects to see.
221 def __init__(self, typeref, parent, additional_properties):
222 self.typeref = typeref
223 self.parent = parent
224 self.additional_properties = additional_properties
226 def process(self, callbacks):
227 properties = self.additional_properties
228 result = properties
230 if self.parent.GetPropertyLocal('OPTIONAL'):
231 properties['optional'] = True
233 # The IDL parser denotes array types by adding a child 'Array' node onto
234 # the Param node in the Callspec.
235 for sibling in self.parent.GetChildren():
236 if sibling.cls == 'Array' and sibling.GetName() == self.parent.GetName():
237 properties['type'] = 'array'
238 properties['items'] = OrderedDict()
239 properties = properties['items']
240 break
242 if self.typeref == 'DOMString':
243 properties['type'] = 'string'
244 elif self.typeref == 'boolean':
245 properties['type'] = 'boolean'
246 elif self.typeref == 'double':
247 properties['type'] = 'number'
248 elif self.typeref == 'long':
249 properties['type'] = 'integer'
250 elif self.typeref == 'any':
251 properties['type'] = 'any'
252 elif self.typeref == 'object':
253 properties['type'] = 'object'
254 if 'additionalProperties' not in properties:
255 properties['additionalProperties'] = OrderedDict()
256 properties['additionalProperties']['type'] = 'any'
257 instance_of = self.parent.GetProperty('instanceOf')
258 if instance_of:
259 properties['isInstanceOf'] = instance_of
260 elif self.typeref == 'ArrayBuffer':
261 properties['type'] = 'binary'
262 properties['isInstanceOf'] = 'ArrayBuffer'
263 elif self.typeref == 'FileEntry':
264 properties['type'] = 'object'
265 properties['isInstanceOf'] = 'FileEntry'
266 if 'additionalProperties' not in properties:
267 properties['additionalProperties'] = OrderedDict()
268 properties['additionalProperties']['type'] = 'any'
269 elif self.parent.GetPropertyLocal('Union'):
270 choices = []
271 properties['choices'] = [Typeref(node.GetProperty('TYPEREF'),
272 node,
273 OrderedDict()).process(callbacks)
274 for node in self.parent.GetChildren()
275 if node.cls == 'Option']
276 elif self.typeref is None:
277 properties['type'] = 'function'
278 else:
279 if self.typeref in callbacks:
280 # Do not override name and description if they are already specified.
281 name = properties.get('name', None)
282 description = properties.get('description', None)
283 properties.update(callbacks[self.typeref])
284 if description is not None:
285 properties['description'] = description
286 if name is not None:
287 properties['name'] = name
288 else:
289 properties['$ref'] = self.typeref
290 return result
293 class Enum(object):
295 Given an IDL Enum node, converts into a Python dictionary that the JSON
296 schema compiler expects to see.
298 def __init__(self, enum_node):
299 self.node = enum_node
300 self.description = ''
302 def process(self, callbacks):
303 enum = []
304 for node in self.node.GetChildren():
305 if node.cls == 'EnumItem':
306 enum_value = {'name': node.GetName()}
307 for child in node.GetChildren():
308 if child.cls == 'Comment':
309 enum_value['description'] = ProcessComment(child.GetName())[0]
310 else:
311 raise ValueError('Did not process %s %s' % (child.cls, child))
312 enum.append(enum_value)
313 elif node.cls == 'Comment':
314 self.description = ProcessComment(node.GetName())[0]
315 else:
316 sys.exit('Did not process %s %s' % (node.cls, node))
317 result = {'id' : self.node.GetName(),
318 'description': self.description,
319 'type': 'string',
320 'enum': enum}
321 for property_name in (
322 'inline_doc', 'noinline_doc', 'nodoc', 'cpp_omit_enum_type',):
323 if self.node.GetProperty(property_name):
324 result[property_name] = True
325 if self.node.GetProperty('deprecated'):
326 result[deprecated] = self.node.GetProperty('deprecated')
327 return result
330 class Namespace(object):
332 Given an IDLNode representing an IDL namespace, converts into a Python
333 dictionary that the JSON schema compiler expects to see.
336 def __init__(self,
337 namespace_node,
338 description,
339 nodoc=False,
340 internal=False,
341 platforms=None,
342 compiler_options=None,
343 deprecated=None):
344 self.namespace = namespace_node
345 self.nodoc = nodoc
346 self.internal = internal
347 self.platforms = platforms
348 self.compiler_options = compiler_options
349 self.events = []
350 self.functions = []
351 self.types = []
352 self.callbacks = OrderedDict()
353 self.description = description
354 self.deprecated = deprecated
356 def process(self):
357 for node in self.namespace.GetChildren():
358 if node.cls == 'Dictionary':
359 self.types.append(Dictionary(node).process(self.callbacks))
360 elif node.cls == 'Callback':
361 k, v = Member(node).process(self.callbacks)
362 self.callbacks[k] = v
363 elif node.cls == 'Interface' and node.GetName() == 'Functions':
364 self.functions = self.process_interface(node)
365 elif node.cls == 'Interface' and node.GetName() == 'Events':
366 self.events = self.process_interface(node)
367 elif node.cls == 'Enum':
368 self.types.append(Enum(node).process(self.callbacks))
369 else:
370 sys.exit('Did not process %s %s' % (node.cls, node))
371 if self.compiler_options is not None:
372 compiler_options = self.compiler_options
373 else:
374 compiler_options = {}
375 return {'namespace': self.namespace.GetName(),
376 'description': self.description,
377 'nodoc': self.nodoc,
378 'types': self.types,
379 'functions': self.functions,
380 'internal': self.internal,
381 'events': self.events,
382 'platforms': self.platforms,
383 'compiler_options': compiler_options,
384 'deprecated': self.deprecated}
386 def process_interface(self, node):
387 members = []
388 for member in node.GetChildren():
389 if member.cls == 'Member':
390 name, properties = Member(member).process(self.callbacks)
391 members.append(properties)
392 return members
395 class IDLSchema(object):
397 Given a list of IDLNodes and IDLAttributes, converts into a Python list
398 of api_defs that the JSON schema compiler expects to see.
401 def __init__(self, idl):
402 self.idl = idl
404 def process(self):
405 namespaces = []
406 nodoc = False
407 internal = False
408 description = None
409 platforms = None
410 compiler_options = None
411 deprecated = None
412 for node in self.idl:
413 if node.cls == 'Namespace':
414 if not description:
415 # TODO(kalman): Go back to throwing an error here.
416 print('%s must have a namespace-level comment. This will '
417 'appear on the API summary page.' % node.GetName())
418 description = ''
419 namespace = Namespace(node, description, nodoc, internal,
420 platforms=platforms,
421 compiler_options=compiler_options,
422 deprecated=deprecated)
423 namespaces.append(namespace.process())
424 nodoc = False
425 internal = False
426 platforms = None
427 compiler_options = None
428 elif node.cls == 'Copyright':
429 continue
430 elif node.cls == 'Comment':
431 description = node.GetName()
432 elif node.cls == 'ExtAttribute':
433 if node.name == 'nodoc':
434 nodoc = bool(node.value)
435 elif node.name == 'internal':
436 internal = bool(node.value)
437 elif node.name == 'platforms':
438 platforms = list(node.value)
439 elif node.name == 'implemented_in':
440 compiler_options = {'implemented_in': node.value}
441 elif node.name == 'deprecated':
442 deprecated = str(node.value)
443 else:
444 continue
445 else:
446 sys.exit('Did not process %s %s' % (node.cls, node))
447 return namespaces
450 def Load(filename):
452 Given the filename of an IDL file, parses it and returns an equivalent
453 Python dictionary in a format that the JSON schema compiler expects to see.
456 f = open(filename, 'r')
457 contents = f.read()
458 f.close()
460 idl = idl_parser.IDLParser().ParseData(contents, filename)
461 idl_schema = IDLSchema(idl)
462 return idl_schema.process()
465 def Main():
467 Dump a json serialization of parse result for the IDL files whose names
468 were passed in on the command line.
470 for filename in sys.argv[1:]:
471 schema = Load(filename)
472 print json.dumps(schema, indent=2)
475 if __name__ == '__main__':
476 Main()