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