blob: 0418dda580b0bbc24544701e5e3e9c5e8f6a8a6d [file] [log] [blame]
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from copy import copy
import logging
import re
from file_system import FileNotFoundError
from third_party.json_schema_compiler.model import PropertyType
def _ClassifySchemaNode(node_name, node):
"""Attempt to classify |node_name| in an API, determining whether |node_name|
refers to a type, function, event, or property in |api|.
"""
if '.' in node_name:
node_name, rest = node_name.split('.', 1)
else:
rest = None
for key, group in [('types', 'type'),
('functions', 'method'),
('events', 'event'),
('properties', 'property')]:
for item in getattr(node, key, {}).itervalues():
if item.simple_name == node_name:
if rest is not None:
ret = _ClassifySchemaNode(rest, item)
if ret is not None:
return ret
else:
return group, node_name
return None
def _MakeKey(namespace, ref):
key = '%s/%s' % (namespace, ref)
# AppEngine doesn't like keys > 500, but there will be some other stuff
# that goes into this key, so truncate it earlier. This shoudn't be
# happening anyway unless there's a bug, such as http://crbug.com/314102.
max_size = 256
if len(key) > max_size:
logging.error('Key was >%s characters: %s' % (max_size, key))
key = key[:max_size]
return key
class ReferenceResolver(object):
"""Resolves references to $ref's by searching through the APIs to find the
correct node. See document_renderer.py for more information on $ref syntax.
"""
def __init__(self, api_models, object_store):
self._api_models = api_models
self._object_store = object_store
def _GetRefLink(self, ref, api_list, namespace):
# Check nodes within each API the ref might refer to.
parts = ref.split('.')
for i in xrange(1, len(parts)):
api_name = '.'.join(parts[:i])
if api_name not in api_list:
continue
try:
api_model = self._api_models.GetModel(api_name).Get()
except FileNotFoundError:
continue
name = '.'.join(parts[i:])
# Attempt to find |name| in the API.
node_info = _ClassifySchemaNode(name, api_model)
if node_info is None:
# Check to see if this ref is a property. If it is, we want the ref to
# the underlying type the property is referencing.
for prop in api_model.properties.itervalues():
# If the name of this property is in the ref text, replace the
# property with its type, and attempt to classify it.
if prop.name in name and prop.type_.property_type == PropertyType.REF:
name_as_prop_type = name.replace(prop.name, prop.type_.ref_type)
node_info = _ClassifySchemaNode(name_as_prop_type, api_model)
if node_info is not None:
name = name_as_prop_type
text = ref.replace(prop.name, prop.type_.ref_type)
break
if node_info is None:
continue
else:
text = ref
category, node_name = node_info
if namespace is not None and text.startswith('%s.' % namespace):
text = text[len('%s.' % namespace):]
api_model = self._api_models.GetModel(api_name).Get()
filename = api_model.documentation_options.get('documented_in', api_name)
return {
'href': '%s#%s-%s' % (filename, category, name.replace('.', '-')),
'text': text,
'name': node_name
}
# If it's not a reference to an API node it might just be a reference to an
# API. Check this last so that links within APIs take precedence over links
# to other APIs.
if ref in api_list:
return {
'href': '%s' % ref,
'text': ref,
'name': ref
}
return None
def GetRefModel(self, ref, api_list):
"""Tries to resolve |ref| from the namespaces given in api_list. If ref
is found in one of those namespaces, return a tuple (api_model, node_info),
where api_model is a model.Namespace class and node info is a tuple
(group, name) where group is one of 'type', 'method', 'event', 'property'
describing the type of the reference, and name is the name of the reference
without the namespace.
"""
# Check nodes within each API the ref might refer to.
parts = ref.split('.')
for i in xrange(1, len(parts)):
api_name = '.'.join(parts[:i])
if api_name not in api_list:
continue
try:
api_model = self._api_models.GetModel(api_name).Get()
except FileNotFoundError:
continue
name = '.'.join(parts[i:])
# Attempt to find |name| in the API.
node_info = _ClassifySchemaNode(name, api_model)
if node_info is None:
# Check to see if this ref is a property. If it is, we want the ref to
# the underlying type the property is referencing.
for prop in api_model.properties.itervalues():
# If the name of this property is in the ref text, replace the
# property with its type, and attempt to classify it.
if prop.name in name and prop.type_.property_type == PropertyType.REF:
name_as_prop_type = name.replace(prop.name, prop.type_.ref_type)
node_info = _ClassifySchemaNode(name_as_prop_type, api_model)
if node_info is None:
continue
return api_model, node_info
return None, None
def GetLink(self, ref, namespace=None, title=None):
"""Resolve $ref |ref| in namespace |namespace| if not None, returning None
if it cannot be resolved.
"""
db_key = _MakeKey(namespace, ref)
link = self._object_store.Get(db_key).Get()
if link is None:
api_list = self._api_models.GetNames()
link = self._GetRefLink(ref, api_list, namespace)
if link is None and namespace is not None:
# Try to resolve the ref in the current namespace if there is one.
api_list = self._api_models.GetNames()
link = self._GetRefLink('%s.%s' % (namespace, ref),
api_list,
namespace)
if link is None:
return None
self._object_store.Set(db_key, link)
if title is not None:
link = copy(link)
link['text'] = title
return link
def SafeGetLink(self, ref, namespace=None, title=None, path=None):
"""Resolve $ref |ref| in namespace |namespace|, or globally if None. If it
cannot be resolved, pretend like it is a link to a type.
"""
ref_data = self.GetLink(ref, namespace=namespace, title=title)
if ref_data is not None:
return ref_data
logging.warning('Could not resolve $ref %s in namespace %s on %s.' %
(ref, namespace, path))
type_name = ref.rsplit('.', 1)[-1]
return {
'href': '#type-%s' % type_name,
'text': title or ref,
'name': ref
}