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.
5 from copy
import deepcopy
9 from file_system
import FileNotFoundError
12 def _ClassifySchemaNode(node_name
, api
):
13 """Attempt to classify |node_name| in an API, determining whether |node_name|
14 refers to a type, function, event, or property in |api|.
17 node_name
, rest
= node_name
.split('.', 1)
20 for key
, group
in [('types', 'type'),
21 ('functions', 'method'),
23 ('properties', 'property')]:
24 for item
in api
.get(key
, []):
25 if item
['name'] == node_name
:
27 ret
= _ClassifySchemaNode(rest
, item
)
31 return group
, node_name
35 def _MakeKey(namespace
, ref
):
36 key
= '%s/%s' % (namespace
, ref
)
37 # AppEngine doesn't like keys > 500, but there will be some other stuff
38 # that goes into this key, so truncate it earlier. This shoudn't be
39 # happening anyway unless there's a bug, such as http://crbug.com/314102.
41 if len(key
) > max_size
:
42 logging
.error('Key was >%s characters: %s' % (max_size
, key
))
47 class ReferenceResolver(object):
48 """Resolves references to $ref's by searching through the APIs to find the
51 $ref's have two forms:
53 $ref:api.node - Replaces the $ref with a link to node on the API page. The
54 title is set to the name of the node.
56 $ref:[api.node The Title] - Same as the previous form but title is set to
60 # Matches after a $ref: that doesn't have []s.
61 _bare_ref
= re
.compile('\w+(\.\w+)*')
63 class Factory(object):
65 api_data_source_factory
,
67 object_store_creator
):
68 self
._api
_data
_source
_factory
= api_data_source_factory
69 self
._api
_models
= api_models
70 self
._object
_store
_creator
= object_store_creator
73 return ReferenceResolver(
74 self
._api
_data
_source
_factory
.Create(None),
76 self
._object
_store
_creator
.Create(ReferenceResolver
))
78 def __init__(self
, api_data_source
, api_models
, object_store
):
79 self
._api
_data
_source
= api_data_source
80 self
._api
_models
= api_models
81 self
._object
_store
= object_store
83 def _GetRefLink(self
, ref
, api_list
, namespace
):
84 # Check nodes within each API the ref might refer to.
85 parts
= ref
.split('.')
86 for i
, part
in enumerate(parts
):
87 api_name
= '.'.join(parts
[:i
])
88 if api_name
not in api_list
:
91 api
= self
._api
_data
_source
.get(api_name
, disable_refs
=True)
92 except FileNotFoundError
:
94 name
= '.'.join(parts
[i
:])
95 # Attempt to find |name| in the API.
96 node_info
= _ClassifySchemaNode(name
, api
)
98 # Check to see if this ref is a property. If it is, we want the ref to
99 # the underlying type the property is referencing.
100 for prop
in api
.get('properties', []):
101 # If the name of this property is in the ref text, replace the
102 # property with its type, and attempt to classify it.
103 if prop
['name'] in name
and 'link' in prop
:
104 name_as_prop_type
= name
.replace(prop
['name'], prop
['link']['name'])
105 node_info
= _ClassifySchemaNode(name_as_prop_type
, api
)
106 if node_info
is not None:
107 name
= name_as_prop_type
108 text
= ref
.replace(prop
['name'], prop
['link']['name'])
110 if node_info
is None:
114 category
, node_name
= node_info
115 if namespace
is not None and text
.startswith('%s.' % namespace
):
116 text
= text
[len('%s.' % namespace
):]
118 'href': '%s.html#%s-%s' % (api_name
, category
, name
.replace('.', '-')),
123 # If it's not a reference to an API node it might just be a reference to an
124 # API. Check this last so that links within APIs take precedence over links
128 'href': '%s.html' % ref
,
135 def GetLink(self
, ref
, namespace
=None, title
=None):
136 """Resolve $ref |ref| in namespace |namespace| if not None, returning None
137 if it cannot be resolved.
139 db_key
= _MakeKey(namespace
, ref
)
140 link
= self
._object
_store
.Get(db_key
).Get()
142 api_list
= self
._api
_models
.GetNames()
143 link
= self
._GetRefLink
(ref
, api_list
, namespace
)
144 if link
is None and namespace
is not None:
145 # Try to resolve the ref in the current namespace if there is one.
146 link
= self
._GetRefLink
('%s.%s' % (namespace
, ref
), api_list
, namespace
)
149 self
._object
_store
.Set(db_key
, link
)
151 link
= deepcopy(link
)
152 if title
is not None:
156 def SafeGetLink(self
, ref
, namespace
=None, title
=None):
157 """Resolve $ref |ref| in namespace |namespace|, or globally if None. If it
158 cannot be resolved, pretend like it is a link to a type.
160 ref_data
= self
.GetLink(ref
, namespace
=namespace
, title
=title
)
161 if ref_data
is not None:
163 logging
.error('$ref %s could not be resolved in namespace %s.' %
165 type_name
= ref
.rsplit('.', 1)[-1]
167 'href': '#type-%s' % type_name
,
168 'text': title
or ref
,
172 def ResolveAllLinks(self
, text
, relative_to
='', namespace
=None):
173 """This method will resolve all $ref links in |text| using namespace
174 |namespace| if not None. Any links that cannot be resolved will be replaced
175 using the default link format that |SafeGetLink| uses.
176 The links will be generated relative to |relative_to|.
178 if text
is None or '$ref:' not in text
:
181 # requestPath should be of the form (apps|extensions)/...../page.html.
182 # link_prefix should that the target will point to
183 # (apps|extensions)/target.html. Note multiplying a string by a negative
184 # number gives the empty string.
185 link_prefix
= '../' * (relative_to
.count('/') - 1)
186 split_text
= text
.split('$ref:')
187 # |split_text| is an array of text chunks that all start with the
188 # argument to '$ref:'.
189 formatted_text
= [split_text
[0]]
190 for ref_and_rest
in split_text
[1:]:
192 if ref_and_rest
.startswith('[') and ']' in ref_and_rest
:
193 # Text was '$ref:[foo.bar maybe title] other stuff'.
194 ref_with_title
, rest
= ref_and_rest
[1:].split(']', 1)
195 ref_with_title
= ref_with_title
.split(None, 1)
196 if len(ref_with_title
) == 1:
197 # Text was '$ref:[foo.bar] other stuff'.
198 ref
= ref_with_title
[0]
200 # Text was '$ref:[foo.bar title] other stuff'.
201 ref
, title
= ref_with_title
203 # Text was '$ref:foo.bar other stuff'.
204 match
= self
._bare
_ref
.match(ref_and_rest
)
210 rest
= ref_and_rest
[match
.end():]
212 ref_dict
= self
.SafeGetLink(ref
, namespace
=namespace
, title
=title
)
213 formatted_text
.append('<a href="%s%s">%s</a>%s' %
214 (link_prefix
, ref_dict
['href'], ref_dict
['text'], rest
))
215 return ''.join(formatted_text
)