1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from collections import defaultdict, Mapping 6 7from third_party.json_schema_compiler import json_parse, idl_schema, idl_parser 8 9 10def RemoveNoDocs(item): 11 '''Removes nodes that should not be rendered from an API schema. 12 ''' 13 if json_parse.IsDict(item): 14 if item.get('nodoc', False): 15 return True 16 for key, value in item.items(): 17 if RemoveNoDocs(value): 18 del item[key] 19 elif type(item) == list: 20 to_remove = [] 21 for i in item: 22 if RemoveNoDocs(i): 23 to_remove.append(i) 24 for i in to_remove: 25 item.remove(i) 26 return False 27 28 29def DetectInlineableTypes(schema): 30 '''Look for documents that are only referenced once and mark them as inline. 31 Actual inlining is done by _InlineDocs. 32 ''' 33 if not schema.get('types'): 34 return 35 36 ignore = frozenset(('value', 'choices')) 37 refcounts = defaultdict(int) 38 # Use an explicit stack instead of recursion. 39 stack = [schema] 40 41 while stack: 42 node = stack.pop() 43 if isinstance(node, list): 44 stack.extend(node) 45 elif isinstance(node, Mapping): 46 if '$ref' in node: 47 refcounts[node['$ref']] += 1 48 stack.extend(v for k, v in node.iteritems() if k not in ignore) 49 50 for type_ in schema['types']: 51 if not 'noinline_doc' in type_: 52 if refcounts[type_['id']] == 1: 53 type_['inline_doc'] = True 54 55 56def InlineDocs(schema): 57 '''Replace '$ref's that refer to inline_docs with the json for those docs. 58 ''' 59 types = schema.get('types') 60 if types is None: 61 return 62 63 inline_docs = {} 64 types_without_inline_doc = [] 65 66 # Gather the types with inline_doc. 67 for type_ in types: 68 if type_.get('inline_doc'): 69 inline_docs[type_['id']] = type_ 70 for k in ('description', 'id', 'inline_doc'): 71 type_.pop(k, None) 72 else: 73 types_without_inline_doc.append(type_) 74 schema['types'] = types_without_inline_doc 75 76 def apply_inline(node): 77 if isinstance(node, list): 78 for i in node: 79 apply_inline(i) 80 elif isinstance(node, Mapping): 81 ref = node.get('$ref') 82 if ref and ref in inline_docs: 83 node.update(inline_docs[ref]) 84 del node['$ref'] 85 for k, v in node.iteritems(): 86 apply_inline(v) 87 88 apply_inline(schema) 89 90 91def ProcessSchema(path, file_data): 92 '''Parses |file_data| using a method determined by checking the 93 extension of the file at the given |path|. Then, trims 'nodoc' and handles 94 inlineable types from the parsed schema data. 95 ''' 96 def trim_and_inline(schema, is_idl=False): 97 '''Modifies an API schema in place by removing nodes that shouldn't be 98 documented and inlining schema types that are only referenced once. 99 ''' 100 if RemoveNoDocs(schema): 101 # A return of True signifies that the entire schema should not be 102 # documented. Otherwise, only nodes that request 'nodoc' are removed. 103 return None 104 if is_idl: 105 DetectInlineableTypes(schema) 106 InlineDocs(schema) 107 return schema 108 109 if path.endswith('.idl'): 110 idl = idl_schema.IDLSchema(idl_parser.IDLParser().ParseData(file_data)) 111 # Wrap the result in a list so that it behaves like JSON API data. 112 return [trim_and_inline(idl.process()[0], is_idl=True)] 113 114 schemas = json_parse.Parse(file_data) 115 for schema in schemas: 116 # Schemas could consist of one API schema (data for a specific API file) 117 # or multiple (data from extension_api.json). 118 trim_and_inline(schema) 119 return schemas 120