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.
9 from file_system
import FileNotFoundError
10 from third_party
.json_schema_compiler
.model
import PropertyType
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|.
18 node_name
, rest
= node_name
.split('.', 1)
21 for key
, group
in [('types', 'type'),
22 ('functions', 'method'),
24 ('properties', 'property')]:
25 for item
in getattr(node
, key
, {}).itervalues():
26 if item
.simple_name
== node_name
:
28 ret
= _ClassifySchemaNode(rest
, item
)
32 return group
, node_name
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.
42 if len(key
) > max_size
:
43 logging
.error('Key was >%s characters: %s' % (max_size
, key
))
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.
52 def __init__(self
, api_models
, object_store
):
53 self
._api
_models
= api_models
54 self
._object
_store
= object_store
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
:
64 api_model
= self
._api
_models
.GetModel(api_name
).Get()
65 except FileNotFoundError
:
67 name
= '.'.join(parts
[i
:])
68 # Attempt to find |name| in the API.
69 node_info
= _ClassifySchemaNode(name
, api_model
)
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
)
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
)
93 'href': '%s#%s-%s' % (filename
, category
, name
.replace('.', '-')),
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
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.
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
:
125 api_model
= self
._api
_models
.GetModel(api_name
).Get()
126 except FileNotFoundError
:
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:
142 return api_model
, node_info
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.
149 db_key
= _MakeKey(namespace
, ref
)
150 link
= self
._object
_store
.Get(db_key
).Get()
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
),
162 self
._object
_store
.Set(db_key
, link
)
164 if title
is not None:
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.
174 ref_data
= self
.GetLink(ref
, namespace
=namespace
, title
=title
)
175 if ref_data
is not None:
177 logging
.warning('Could not resolve $ref %s in namespace %s on %s.' %
178 (ref
, namespace
, path
))
179 type_name
= ref
.rsplit('.', 1)[-1]
181 'href': '#type-%s' % type_name
,
182 'text': title
or ref
,