11e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved. 21e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 31e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)# found in the LICENSE file. 41e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 51e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)from collections import defaultdict, Mapping 65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import traceback 71e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 81e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)from third_party.json_schema_compiler import json_parse, idl_schema, idl_parser 91e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 101e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)def RemoveNoDocs(item): 121e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) '''Removes nodes that should not be rendered from an API schema. 131e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) ''' 141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if json_parse.IsDict(item): 151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if item.get('nodoc', False): 161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return True 171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) for key, value in item.items(): 181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if RemoveNoDocs(value): 191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) del item[key] 201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) elif type(item) == list: 211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) to_remove = [] 221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) for i in item: 231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if RemoveNoDocs(i): 241e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) to_remove.append(i) 251e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) for i in to_remove: 261e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) item.remove(i) 271e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return False 281e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 291e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 301e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)def DetectInlineableTypes(schema): 311e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) '''Look for documents that are only referenced once and mark them as inline. 321e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) Actual inlining is done by _InlineDocs. 331e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) ''' 341e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if not schema.get('types'): 351e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return 361e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 371e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) ignore = frozenset(('value', 'choices')) 381e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) refcounts = defaultdict(int) 391e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) # Use an explicit stack instead of recursion. 401e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) stack = [schema] 411e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 421e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) while stack: 431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) node = stack.pop() 441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if isinstance(node, list): 451e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) stack.extend(node) 461e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) elif isinstance(node, Mapping): 471e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if '$ref' in node: 481e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) refcounts[node['$ref']] += 1 491e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) stack.extend(v for k, v in node.iteritems() if k not in ignore) 501e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 511e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) for type_ in schema['types']: 521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if not 'noinline_doc' in type_: 531e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if refcounts[type_['id']] == 1: 541e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) type_['inline_doc'] = True 551e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 561e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 571e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)def InlineDocs(schema): 581e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) '''Replace '$ref's that refer to inline_docs with the json for those docs. 591e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) ''' 601e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) types = schema.get('types') 611e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if types is None: 621e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return 631e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 641e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) inline_docs = {} 651e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) types_without_inline_doc = [] 661e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 671e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) # Gather the types with inline_doc. 681e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) for type_ in types: 691e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if type_.get('inline_doc'): 701e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) inline_docs[type_['id']] = type_ 711e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) for k in ('description', 'id', 'inline_doc'): 721e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) type_.pop(k, None) 731e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) else: 741e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) types_without_inline_doc.append(type_) 751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) schema['types'] = types_without_inline_doc 761e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 771e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) def apply_inline(node): 781e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if isinstance(node, list): 791e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) for i in node: 801e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) apply_inline(i) 811e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) elif isinstance(node, Mapping): 821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) ref = node.get('$ref') 831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if ref and ref in inline_docs: 841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) node.update(inline_docs[ref]) 851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) del node['$ref'] 861e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) for k, v in node.iteritems(): 871e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) apply_inline(v) 881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 891e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) apply_inline(schema) 901e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 911e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 921e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)def ProcessSchema(path, file_data): 931e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) '''Parses |file_data| using a method determined by checking the 941e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) extension of the file at the given |path|. Then, trims 'nodoc' and handles 951e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) inlineable types from the parsed schema data. 961e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) ''' 971e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) def trim_and_inline(schema, is_idl=False): 981e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) '''Modifies an API schema in place by removing nodes that shouldn't be 991e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) documented and inlining schema types that are only referenced once. 1001e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) ''' 1011e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if RemoveNoDocs(schema): 1021e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) # A return of True signifies that the entire schema should not be 1031e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) # documented. Otherwise, only nodes that request 'nodoc' are removed. 1041e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return None 1051e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if is_idl: 1061e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) DetectInlineableTypes(schema) 1071e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) InlineDocs(schema) 1081e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return schema 1091e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1101e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if path.endswith('.idl'): 1111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) idl = idl_schema.IDLSchema(idl_parser.IDLParser().ParseData(file_data)) 112a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) # Wrap the result in a list so that it behaves like JSON API data. 113a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) return [trim_and_inline(idl.process()[0], is_idl=True)] 1141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) try: 1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) schemas = json_parse.Parse(file_data) 1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) except: 1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) raise ValueError('Cannot parse "%s" as JSON:\n%s' % 1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) (path, traceback.format_exc())) 1201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) for schema in schemas: 1211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) # Schemas could consist of one API schema (data for a specific API file) 1221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) # or multiple (data from extension_api.json). 1231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) trim_and_inline(schema) 1241e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return schemas 125