- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / server2 / reference_resolver.py
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 deepcopy
6 import logging
7 import re
8 import string
9
10 from file_system import FileNotFoundError
11
12
13 def _ClassifySchemaNode(node_name, api):
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 api.get(key, []):
26       if item['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.
51
52   $ref's have two forms:
53
54     $ref:api.node - Replaces the $ref with a link to node on the API page. The
55                     title is set to the name of the node.
56
57     $ref:[api.node The Title] - Same as the previous form but title is set to
58                                 "The Title".
59   """
60
61   # Matches after a $ref: that doesn't have []s.
62   _bare_ref = re.compile('\w+(\.\w+)*')
63
64   class Factory(object):
65     def __init__(self,
66                  api_data_source_factory,
67                  api_models,
68                  object_store_creator):
69       self._api_data_source_factory = api_data_source_factory
70       self._api_models = api_models
71       self._object_store_creator = object_store_creator
72
73     def Create(self):
74       return ReferenceResolver(
75           self._api_data_source_factory.Create(None),
76           self._api_models,
77           self._object_store_creator.Create(ReferenceResolver))
78
79   def __init__(self, api_data_source, api_models, object_store):
80     self._api_data_source = api_data_source
81     self._api_models = api_models
82     self._object_store = object_store
83
84   def _GetRefLink(self, ref, api_list, namespace):
85     # Check nodes within each API the ref might refer to.
86     parts = ref.split('.')
87     for i, part in enumerate(parts):
88       api_name = '.'.join(parts[:i])
89       if api_name not in api_list:
90         continue
91       try:
92         api = self._api_data_source.get(api_name, disable_refs=True)
93       except FileNotFoundError:
94         continue
95       name = '.'.join(parts[i:])
96       # Attempt to find |name| in the API.
97       node_info = _ClassifySchemaNode(name, api)
98       if node_info is None:
99         # Check to see if this ref is a property. If it is, we want the ref to
100         # the underlying type the property is referencing.
101         for prop in api.get('properties', []):
102           # If the name of this property is in the ref text, replace the
103           # property with its type, and attempt to classify it.
104           if prop['name'] in name and 'link' in prop:
105             name_as_prop_type = name.replace(prop['name'], prop['link']['name'])
106             node_info = _ClassifySchemaNode(name_as_prop_type, api)
107             if node_info is not None:
108               name = name_as_prop_type
109               text = ref.replace(prop['name'], prop['link']['name'])
110               break
111         if node_info is None:
112           continue
113       else:
114         text = ref
115       category, node_name = node_info
116       if namespace is not None and text.startswith('%s.' % namespace):
117         text = text[len('%s.' % namespace):]
118       return {
119         'href': '%s.html#%s-%s' % (api_name, category, name.replace('.', '-')),
120         'text': text,
121         'name': node_name
122       }
123
124     # If it's not a reference to an API node it might just be a reference to an
125     # API. Check this last so that links within APIs take precedence over links
126     # to other APIs.
127     if ref in api_list:
128       return {
129         'href': '%s.html' % ref,
130         'text': ref,
131         'name': ref
132       }
133
134     return None
135
136   def GetLink(self, ref, namespace=None, title=None):
137     """Resolve $ref |ref| in namespace |namespace| if not None, returning None
138     if it cannot be resolved.
139     """
140     db_key = _MakeKey(namespace, ref)
141     link = self._object_store.Get(db_key).Get()
142     if link is None:
143       api_list = self._api_models.GetNames()
144       link = self._GetRefLink(ref, api_list, namespace)
145       if link is None and namespace is not None:
146         # Try to resolve the ref in the current namespace if there is one.
147         link = self._GetRefLink('%s.%s' % (namespace, ref), api_list, namespace)
148       if link is None:
149         return None
150       self._object_store.Set(db_key, link)
151     else:
152       link = deepcopy(link)
153     if title is not None:
154       link['text'] = title
155     return link
156
157   def SafeGetLink(self, ref, namespace=None, title=None):
158     """Resolve $ref |ref| in namespace |namespace|, or globally if None. If it
159     cannot be resolved, pretend like it is a link to a type.
160     """
161     ref_data = self.GetLink(ref, namespace=namespace, title=title)
162     if ref_data is not None:
163       return ref_data
164     logging.error('$ref %s could not be resolved in namespace %s.' %
165         (ref, namespace))
166     type_name = ref.rsplit('.', 1)[-1]
167     return {
168       'href': '#type-%s' % type_name,
169       'text': title or ref,
170       'name': ref
171     }
172
173   def ResolveAllLinks(self, text, namespace=None):
174     """This method will resolve all $ref links in |text| using namespace
175     |namespace| if not None. Any links that cannot be resolved will be replaced
176     using the default link format that |SafeGetLink| uses.
177     """
178     if text is None or '$ref:' not in text:
179       return text
180     split_text = text.split('$ref:')
181     # |split_text| is an array of text chunks that all start with the
182     # argument to '$ref:'.
183     formatted_text = [split_text[0]]
184     for ref_and_rest in split_text[1:]:
185       title = None
186       if ref_and_rest.startswith('[') and ']' in ref_and_rest:
187         # Text was '$ref:[foo.bar maybe title] other stuff'.
188         ref_with_title, rest = ref_and_rest[1:].split(']', 1)
189         ref_with_title = ref_with_title.split(None, 1)
190         if len(ref_with_title) == 1:
191           # Text was '$ref:[foo.bar] other stuff'.
192           ref = ref_with_title[0]
193         else:
194           # Text was '$ref:[foo.bar title] other stuff'.
195           ref, title = ref_with_title
196       else:
197         # Text was '$ref:foo.bar other stuff'.
198         match = self._bare_ref.match(ref_and_rest)
199         if match is None:
200           ref = ''
201           rest = ref_and_rest
202         else:
203           ref = match.group()
204           rest = ref_and_rest[match.end():]
205
206       ref_dict = self.SafeGetLink(ref, namespace=namespace, title=title)
207       formatted_text.append('<a href="%(href)s">%(text)s</a>%(rest)s' %
208           { 'href': ref_dict['href'], 'text': ref_dict['text'], 'rest': rest })
209     return ''.join(formatted_text)