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 5 from copy import copy 6 import logging 7 import re 8 9 from file_system import FileNotFoundError 10 from third_party.json_schema_compiler.model import PropertyType 11 12 13 def _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 36 def _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 48 class 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 GetLink(self, ref, namespace=None, title=None): 111 """Resolve $ref |ref| in namespace |namespace| if not None, returning None 112 if it cannot be resolved. 113 """ 114 db_key = _MakeKey(namespace, ref) 115 link = self._object_store.Get(db_key).Get() 116 if link is None: 117 api_list = self._api_models.GetNames() 118 link = self._GetRefLink(ref, api_list, namespace) 119 if link is None and namespace is not None: 120 # Try to resolve the ref in the current namespace if there is one. 121 link = self._GetRefLink('%s.%s' % (namespace, ref), api_list, namespace) 122 if link is None: 123 return None 124 self._object_store.Set(db_key, link) 125 126 if title is not None: 127 link = copy(link) 128 link['text'] = title 129 130 return link 131 132 def SafeGetLink(self, ref, namespace=None, title=None): 133 """Resolve $ref |ref| in namespace |namespace|, or globally if None. If it 134 cannot be resolved, pretend like it is a link to a type. 135 """ 136 ref_data = self.GetLink(ref, namespace=namespace, title=title) 137 if ref_data is not None: 138 return ref_data 139 logging.error('$ref %s could not be resolved in namespace %s.' % 140 (ref, namespace)) 141 type_name = ref.rsplit('.', 1)[-1] 142 return { 143 'href': '#type-%s' % type_name, 144 'text': title or ref, 145 'name': ref 146 } 147