• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 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