1# Copyright (c) 2012 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 copy import copy 6import logging 7import re 8 9from file_system import FileNotFoundError 10from third_party.json_schema_compiler.model import PropertyType 11 12 13def _ClassifySchemaNode(node_name, node): 14 """Attempt to classify |node_name| in an API, determining whether |node_name| 15 refers to a type, function, event, or property in |api|. 16 """ 17 if '.' in node_name: 18 node_name, rest = node_name.split('.', 1) 19 else: 20 rest = None 21 for key, group in [('types', 'type'), 22 ('functions', 'method'), 23 ('events', 'event'), 24 ('properties', 'property')]: 25 for item in getattr(node, key, {}).itervalues(): 26 if item.simple_name == node_name: 27 if rest is not None: 28 ret = _ClassifySchemaNode(rest, item) 29 if ret is not None: 30 return ret 31 else: 32 return group, node_name 33 return None 34 35 36def _MakeKey(namespace, ref): 37 key = '%s/%s' % (namespace, ref) 38 # AppEngine doesn't like keys > 500, but there will be some other stuff 39 # that goes into this key, so truncate it earlier. This shoudn't be 40 # happening anyway unless there's a bug, such as http://crbug.com/314102. 41 max_size = 256 42 if len(key) > max_size: 43 logging.error('Key was >%s characters: %s' % (max_size, key)) 44 key = key[:max_size] 45 return key 46 47 48class ReferenceResolver(object): 49 """Resolves references to $ref's by searching through the APIs to find the 50 correct node. See document_renderer.py for more information on $ref syntax. 51 """ 52 def __init__(self, api_models, object_store): 53 self._api_models = api_models 54 self._object_store = object_store 55 56 def _GetRefLink(self, ref, api_list, namespace): 57 # Check nodes within each API the ref might refer to. 58 parts = ref.split('.') 59 for i in xrange(1, len(parts)): 60 api_name = '.'.join(parts[:i]) 61 if api_name not in api_list: 62 continue 63 try: 64 api_model = self._api_models.GetModel(api_name).Get() 65 except FileNotFoundError: 66 continue 67 name = '.'.join(parts[i:]) 68 # Attempt to find |name| in the API. 69 node_info = _ClassifySchemaNode(name, api_model) 70 if node_info is None: 71 # Check to see if this ref is a property. If it is, we want the ref to 72 # the underlying type the property is referencing. 73 for prop in api_model.properties.itervalues(): 74 # If the name of this property is in the ref text, replace the 75 # property with its type, and attempt to classify it. 76 if prop.name in name and prop.type_.property_type == PropertyType.REF: 77 name_as_prop_type = name.replace(prop.name, prop.type_.ref_type) 78 node_info = _ClassifySchemaNode(name_as_prop_type, api_model) 79 if node_info is not None: 80 name = name_as_prop_type 81 text = ref.replace(prop.name, prop.type_.ref_type) 82 break 83 if node_info is None: 84 continue 85 else: 86 text = ref 87 category, node_name = node_info 88 if namespace is not None and text.startswith('%s.' % namespace): 89 text = text[len('%s.' % namespace):] 90 api_model = self._api_models.GetModel(api_name).Get() 91 filename = api_model.documentation_options.get('documented_in', api_name) 92 return { 93 'href': '%s#%s-%s' % (filename, category, name.replace('.', '-')), 94 'text': text, 95 'name': node_name 96 } 97 98 # If it's not a reference to an API node it might just be a reference to an 99 # API. Check this last so that links within APIs take precedence over links 100 # to other APIs. 101 if ref in api_list: 102 return { 103 'href': '%s' % ref, 104 'text': ref, 105 'name': ref 106 } 107 108 return None 109 110 def GetRefModel(self, ref, api_list): 111 """Tries to resolve |ref| from the namespaces given in api_list. If ref 112 is found in one of those namespaces, return a tuple (api_model, node_info), 113 where api_model is a model.Namespace class and node info is a tuple 114 (group, name) where group is one of 'type', 'method', 'event', 'property' 115 describing the type of the reference, and name is the name of the reference 116 without the namespace. 117 """ 118 # Check nodes within each API the ref might refer to. 119 parts = ref.split('.') 120 for i in xrange(1, len(parts)): 121 api_name = '.'.join(parts[:i]) 122 if api_name not in api_list: 123 continue 124 try: 125 api_model = self._api_models.GetModel(api_name).Get() 126 except FileNotFoundError: 127 continue 128 name = '.'.join(parts[i:]) 129 # Attempt to find |name| in the API. 130 node_info = _ClassifySchemaNode(name, api_model) 131 if node_info is None: 132 # Check to see if this ref is a property. If it is, we want the ref to 133 # the underlying type the property is referencing. 134 for prop in api_model.properties.itervalues(): 135 # If the name of this property is in the ref text, replace the 136 # property with its type, and attempt to classify it. 137 if prop.name in name and prop.type_.property_type == PropertyType.REF: 138 name_as_prop_type = name.replace(prop.name, prop.type_.ref_type) 139 node_info = _ClassifySchemaNode(name_as_prop_type, api_model) 140 if node_info is None: 141 continue 142 return api_model, node_info 143 return None, None 144 145 def GetLink(self, ref, namespace=None, title=None): 146 """Resolve $ref |ref| in namespace |namespace| if not None, returning None 147 if it cannot be resolved. 148 """ 149 db_key = _MakeKey(namespace, ref) 150 link = self._object_store.Get(db_key).Get() 151 if link is None: 152 api_list = self._api_models.GetNames() 153 link = self._GetRefLink(ref, api_list, namespace) 154 if link is None and namespace is not None: 155 # Try to resolve the ref in the current namespace if there is one. 156 api_list = self._api_models.GetNames() 157 link = self._GetRefLink('%s.%s' % (namespace, ref), 158 api_list, 159 namespace) 160 if link is None: 161 return None 162 self._object_store.Set(db_key, link) 163 164 if title is not None: 165 link = copy(link) 166 link['text'] = title 167 168 return link 169 170 def SafeGetLink(self, ref, namespace=None, title=None, path=None): 171 """Resolve $ref |ref| in namespace |namespace|, or globally if None. If it 172 cannot be resolved, pretend like it is a link to a type. 173 """ 174 ref_data = self.GetLink(ref, namespace=namespace, title=title) 175 if ref_data is not None: 176 return ref_data 177 logging.warning('Could not resolve $ref %s in namespace %s on %s.' % 178 (ref, namespace, path)) 179 type_name = ref.rsplit('.', 1)[-1] 180 return { 181 'href': '#type-%s' % type_name, 182 'text': title or ref, 183 'name': ref 184 } 185