Home | History | Annotate | Download | only in server2
      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 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