15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from file_system import FileNotFoundError 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import logging 72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import re 82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import string 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _ClassifySchemaNode(node_name, api): 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Attempt to classify |node_name| in an API, determining whether |node_name| 122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) refers to a type, function, event, or property in |api|. 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """ 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if '.' in node_name: 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) node_name, rest = node_name.split('.', 1) 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) rest = None 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for key, group in [('types', 'type'), 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ('functions', 'method'), 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ('events', 'event'), 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ('properties', 'property')]: 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for item in api.get(key, []): 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if item['name'] == node_name: 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if rest is not None: 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ret = _ClassifySchemaNode(rest, item) 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if ret is not None: 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return ret 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return group, node_name 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return None 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _MakeKey(namespace, ref, title): 332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return '%s.%s.%s' % (namespace, ref, title) 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ReferenceResolver(object): 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Resolves references to $ref's by searching through the APIs to find the 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) correct node. 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) $ref's have two forms: 402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) $ref:api.node - Replaces the $ref with a link to node on the API page. The 422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) title is set to the name of the node. 432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) $ref:[api.node The Title] - Same as the previous form but title is set to 452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "The Title". 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Matches after a $ref: that doesn't have []s. 492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) _bare_ref = re.compile('\w+(\.\w+)*') 502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) class Factory(object): 522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def __init__(self, 532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) api_data_source_factory, 542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) api_list_data_source_factory, 55b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) object_store_creator): 562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._api_data_source_factory = api_data_source_factory 572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._api_list_data_source_factory = api_list_data_source_factory 58b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) self._object_store_creator = object_store_creator 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def Create(self): 612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return ReferenceResolver( 622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._api_data_source_factory.Create(None, disable_refs=True), 632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._api_list_data_source_factory.Create(), 64b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) self._object_store_creator.Create(ReferenceResolver)) 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def __init__(self, api_data_source, api_list_data_source, object_store): 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._api_data_source = api_data_source 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._api_list_data_source = api_list_data_source 692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._object_store = object_store 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def _GetRefLink(self, ref, api_list, namespace, title): 722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Check nodes within each API the ref might refer to. 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parts = ref.split('.') 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for i, part in enumerate(parts): 752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) api_name = '.'.join(parts[:i]) 762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if api_name not in api_list: 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) api = self._api_data_source.get(api_name) 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except FileNotFoundError: 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) name = '.'.join(parts[i:]) 832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Attempt to find |name| in the API. 842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) node_info = _ClassifySchemaNode(name, api) 852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if node_info is None: 862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Check to see if this ref is a property. If it is, we want the ref to 872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # the underlying type the property is referencing. 882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for prop in api.get('properties', []): 892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # If the name of this property is in the ref text, replace the 902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # property with its type, and attempt to classify it. 912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if prop['name'] in name and 'link' in prop: 922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) name_as_prop_type = name.replace(prop['name'], prop['link']['name']) 932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) node_info = _ClassifySchemaNode(name_as_prop_type, api) 942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if node_info is not None: 952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) name = name_as_prop_type 962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) text = ref.replace(prop['name'], prop['link']['name']) 972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) break 982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if node_info is None: 992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) continue 1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) text = ref 1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) category, node_name = node_info 1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if namespace is not None and text.startswith('%s.' % namespace): 1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) text = text[len('%s.' % namespace):] 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return { 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'href': '%s.html#%s-%s' % (api_name, category, name.replace('.', '-')), 1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'text': title if title else text, 1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'name': node_name 1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # If it's not a reference to an API node it might just be a reference to an 1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # API. Check this last so that links within APIs take precedence over links 1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # to other APIs. 1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if ref in api_list: 1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return { 1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'href': '%s.html' % ref, 1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'text': title if title else ref, 1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'name': ref 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return None 1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def GetLink(self, ref, namespace=None, title=None): 1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Resolve $ref |ref| in namespace |namespace| if not None, returning None 1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if it cannot be resolved. 1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """ 127c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) link = self._object_store.Get(_MakeKey(namespace, ref, title)).Get() 1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if link is not None: 1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return link 1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) api_list = self._api_list_data_source.GetAllNames() 1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) link = self._GetRefLink(ref, api_list, namespace, title) 1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if link is None and namespace is not None: 1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Try to resolve the ref in the current namespace if there is one. 1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) link = self._GetRefLink('%s.%s' % (namespace, ref), 1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) api_list, 1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) namespace, 1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) title) 1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if link is not None: 142c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self._object_store.Set(_MakeKey(namespace, ref, title), link) 1432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return link 1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def SafeGetLink(self, ref, namespace=None, title=None): 1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Resolve $ref |ref| in namespace |namespace|, or globally if None. If it 1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) cannot be resolved, pretend like it is a link to a type. 1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """ 1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ref_data = self.GetLink(ref, namespace=namespace, title=title) 1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if ref_data is not None: 1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return ref_data 1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) logging.error('$ref %s could not be resolved in namespace %s.' % 153c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) (ref, namespace)) 1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) type_name = ref.rsplit('.', 1)[-1] 1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return { 1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'href': '#type-%s' % type_name, 1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'text': title if title else ref, 1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'name': ref 1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def ResolveAllLinks(self, text, namespace=None): 1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """This method will resolve all $ref links in |text| using namespace 1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) |namespace| if not None. Any links that cannot be resolved will be replaced 1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) using the default link format that |SafeGetLink| uses. 1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """ 1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if text is None or '$ref:' not in text: 1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return text 1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) split_text = text.split('$ref:') 1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # |split_text| is an array of text chunks that all start with the 1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # argument to '$ref:'. 1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) formatted_text = [split_text[0]] 1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for ref_and_rest in split_text[1:]: 1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) title = None 1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if ref_and_rest.startswith('[') and ']' in ref_and_rest: 1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Text was '$ref:[foo.bar maybe title] other stuff'. 1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ref_with_title, rest = ref_and_rest[1:].split(']', 1) 1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ref_with_title = ref_with_title.split(None, 1) 1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if len(ref_with_title) == 1: 1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Text was '$ref:[foo.bar] other stuff'. 1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ref = ref_with_title[0] 1812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Text was '$ref:[foo.bar title] other stuff'. 1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ref, title = ref_with_title 1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Text was '$ref:foo.bar other stuff'. 1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) match = self._bare_ref.match(ref_and_rest) 1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if match is None: 1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ref = '' 1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) rest = ref_and_rest 1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ref = match.group() 1922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) rest = ref_and_rest[match.end():] 1932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ref_dict = self.SafeGetLink(ref, namespace=namespace, title=title) 1952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) formatted_text.append('<a href="%(href)s">%(text)s</a>%(rest)s' % 1962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) { 'href': ref_dict['href'], 'text': ref_dict['text'], 'rest': rest }) 1972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return ''.join(formatted_text) 198