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 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