blob: a73a9712e54506e48d0381aab0c191793d053735 [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001# 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
5import copy
Torne (Richard Coles)58218062012-11-14 11:43:16 +00006import logging
7import os
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01008from collections import defaultdict, Mapping
Torne (Richard Coles)58218062012-11-14 11:43:16 +00009
Ben Murdochca12bfa2013-07-23 11:17:05 +010010from branch_utility import BranchUtility
11import svn_constants
12from third_party.handlebar import Handlebar
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000013import third_party.json_schema_compiler.json_parse as json_parse
Torne (Richard Coles)58218062012-11-14 11:43:16 +000014import third_party.json_schema_compiler.model as model
15import third_party.json_schema_compiler.idl_schema as idl_schema
16import third_party.json_schema_compiler.idl_parser as idl_parser
17
Torne (Richard Coles)58218062012-11-14 11:43:16 +000018def _RemoveNoDocs(item):
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000019 if json_parse.IsDict(item):
Torne (Richard Coles)58218062012-11-14 11:43:16 +000020 if item.get('nodoc', False):
21 return True
Torne (Richard Coles)58218062012-11-14 11:43:16 +000022 for key, value in item.items():
23 if _RemoveNoDocs(value):
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010024 del item[key]
Torne (Richard Coles)58218062012-11-14 11:43:16 +000025 elif type(item) == list:
26 to_remove = []
27 for i in item:
28 if _RemoveNoDocs(i):
29 to_remove.append(i)
30 for i in to_remove:
31 item.remove(i)
32 return False
33
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010034def _DetectInlineableTypes(schema):
35 """Look for documents that are only referenced once and mark them as inline.
36 Actual inlining is done by _InlineDocs.
37 """
38 if not schema.get('types'):
39 return
40
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010041 ignore = frozenset(('value', 'choices'))
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010042 refcounts = defaultdict(int)
43 # Use an explicit stack instead of recursion.
44 stack = [schema]
45
46 while stack:
47 node = stack.pop()
48 if isinstance(node, list):
49 stack.extend(node)
50 elif isinstance(node, Mapping):
51 if '$ref' in node:
52 refcounts[node['$ref']] += 1
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010053 stack.extend(v for k, v in node.iteritems() if k not in ignore)
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010054
55 for type_ in schema['types']:
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010056 if not 'noinline_doc' in type_:
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010057 if refcounts[type_['id']] == 1:
58 type_['inline_doc'] = True
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010059
60def _InlineDocs(schema):
61 """Replace '$ref's that refer to inline_docs with the json for those docs.
62 """
63 types = schema.get('types')
64 if types is None:
65 return
66
67 inline_docs = {}
68 types_without_inline_doc = []
69
70 # Gather the types with inline_doc.
71 for type_ in types:
72 if type_.get('inline_doc'):
73 inline_docs[type_['id']] = type_
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010074 for k in ('description', 'id', 'inline_doc'):
75 type_.pop(k, None)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010076 else:
77 types_without_inline_doc.append(type_)
78 schema['types'] = types_without_inline_doc
79
80 def apply_inline(node):
81 if isinstance(node, list):
82 for i in node:
83 apply_inline(i)
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010084 elif isinstance(node, Mapping):
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010085 ref = node.get('$ref')
86 if ref and ref in inline_docs:
87 node.update(inline_docs[ref])
88 del node['$ref']
89 for k, v in node.iteritems():
90 apply_inline(v)
91
92 apply_inline(schema)
93
Torne (Richard Coles)58218062012-11-14 11:43:16 +000094def _CreateId(node, prefix):
95 if node.parent is not None and not isinstance(node.parent, model.Namespace):
96 return '-'.join([prefix, node.parent.simple_name, node.simple_name])
97 return '-'.join([prefix, node.simple_name])
98
99def _FormatValue(value):
100 """Inserts commas every three digits for integer values. It is magic.
101 """
102 s = str(value)
103 return ','.join([s[max(0, i - 3):i] for i in range(len(s), 0, -3)][::-1])
104
105class _JSCModel(object):
106 """Uses a Model from the JSON Schema Compiler and generates a dict that
107 a Handlebar template can use for a data source.
108 """
Ben Murdochca12bfa2013-07-23 11:17:05 +0100109 def __init__(self,
110 json,
111 ref_resolver,
112 disable_refs,
113 availability_finder,
Ben Murdoch558790d2013-07-30 15:19:42 +0100114 parse_cache,
Ben Murdochca12bfa2013-07-23 11:17:05 +0100115 template_data_source,
116 idl=False):
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000117 self._ref_resolver = ref_resolver
118 self._disable_refs = disable_refs
Ben Murdochca12bfa2013-07-23 11:17:05 +0100119 self._availability_finder = availability_finder
Ben Murdoch558790d2013-07-30 15:19:42 +0100120 self._intro_tables = parse_cache.GetFromFile(
Ben Murdochca12bfa2013-07-23 11:17:05 +0100121 '%s/intro_tables.json' % svn_constants.JSON_PATH)
Ben Murdoch558790d2013-07-30 15:19:42 +0100122 self._api_features = parse_cache.GetFromFile(
123 '%s/_api_features.json' % svn_constants.API_PATH)
Ben Murdochca12bfa2013-07-23 11:17:05 +0100124 self._template_data_source = template_data_source
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000125 clean_json = copy.deepcopy(json)
126 if _RemoveNoDocs(clean_json):
127 self._namespace = None
128 else:
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100129 if idl:
130 _DetectInlineableTypes(clean_json)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100131 _InlineDocs(clean_json)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000132 self._namespace = model.Namespace(clean_json, clean_json['namespace'])
133
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000134 def _FormatDescription(self, description):
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000135 if self._disable_refs:
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000136 return description
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000137 return self._ref_resolver.ResolveAllLinks(description,
138 namespace=self._namespace.name)
139
140 def _GetLink(self, link):
141 if self._disable_refs:
142 type_name = link.split('.', 1)[-1]
143 return { 'href': '#type-%s' % type_name, 'text': link, 'name': link }
144 return self._ref_resolver.SafeGetLink(link, namespace=self._namespace.name)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000145
146 def ToDict(self):
147 if self._namespace is None:
148 return {}
149 return {
150 'name': self._namespace.name,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000151 'types': self._GenerateTypes(self._namespace.types.values()),
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000152 'functions': self._GenerateFunctions(self._namespace.functions),
153 'events': self._GenerateEvents(self._namespace.events),
Ben Murdochca12bfa2013-07-23 11:17:05 +0100154 'properties': self._GenerateProperties(self._namespace.properties),
155 'intro_list': self._GetIntroTableList(),
156 'channel_warning': self._GetChannelWarning()
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000157 }
158
Ben Murdochca12bfa2013-07-23 11:17:05 +0100159 def _GetIntroTableList(self):
160 """Create a generic data structure that can be traversed by the templates
161 to create an API intro table.
162 """
Ben Murdoch558790d2013-07-30 15:19:42 +0100163 intro_rows = [
164 self._GetIntroDescriptionRow(),
165 self._GetIntroAvailabilityRow()
166 ] + self._GetIntroDependencyRows()
167
168 # Add rows using data from intro_tables.json, overriding any existing rows
169 # if they share the same 'title' attribute.
170 row_titles = [row['title'] for row in intro_rows]
171 for misc_row in self._GetMiscIntroRows():
172 if misc_row['title'] in row_titles:
173 intro_rows[row_titles.index(misc_row['title'])] = misc_row
174 else:
175 intro_rows.append(misc_row)
176
177 return intro_rows
178
179 def _GetIntroDescriptionRow(self):
180 """ Generates the 'Description' row data for an API intro table.
181 """
182 return {
Ben Murdochca12bfa2013-07-23 11:17:05 +0100183 'title': 'Description',
184 'content': [
185 { 'text': self._FormatDescription(self._namespace.description) }
186 ]
Ben Murdoch558790d2013-07-30 15:19:42 +0100187 }
Ben Murdochca12bfa2013-07-23 11:17:05 +0100188
Ben Murdoch558790d2013-07-30 15:19:42 +0100189 def _GetIntroAvailabilityRow(self):
190 """ Generates the 'Availability' row data for an API intro table.
191 """
Ben Murdochca12bfa2013-07-23 11:17:05 +0100192 if self._IsExperimental():
193 status = 'experimental'
194 version = None
195 else:
196 availability = self._GetApiAvailability()
197 status = availability.channel
198 version = availability.version
Ben Murdoch558790d2013-07-30 15:19:42 +0100199 return {
Ben Murdochca12bfa2013-07-23 11:17:05 +0100200 'title': 'Availability',
Ben Murdoch558790d2013-07-30 15:19:42 +0100201 'content': [{
202 'partial': self._template_data_source.get(
203 'intro_tables/%s_message.html' % status),
204 'version': version
205 }]
206 }
Ben Murdochca12bfa2013-07-23 11:17:05 +0100207
Ben Murdoch558790d2013-07-30 15:19:42 +0100208 def _GetIntroDependencyRows(self):
209 # Devtools aren't in _api_features. If we're dealing with devtools, bail.
210 if 'devtools' in self._namespace.name:
211 return []
212 feature = self._api_features.get(self._namespace.name)
213 assert feature, ('"%s" not found in _api_features.json.'
214 % self._namespace.name)
215
216 dependencies = feature.get('dependencies')
217 if dependencies is None:
218 return []
219
220 def make_code_node(text):
221 return { 'class': 'code', 'text': text }
222
223 permissions_content = []
224 manifest_content = []
225
Ben Murdoch32409262013-08-07 11:04:47 +0100226 def categorize_dependency(dependency):
Ben Murdoch558790d2013-07-30 15:19:42 +0100227 context, name = dependency.split(':', 1)
228 if context == 'permission':
229 permissions_content.append(make_code_node('"%s"' % name))
230 elif context == 'manifest':
231 manifest_content.append(make_code_node('"%s": {...}' % name))
Ben Murdoch32409262013-08-07 11:04:47 +0100232 elif context == 'api':
233 transitive_dependencies = (
234 self._api_features.get(context, {}).get('dependencies', []))
235 for transitive_dependency in transitive_dependencies:
236 categorize_dependency(transitive_dependency)
Ben Murdoch558790d2013-07-30 15:19:42 +0100237 else:
Ben Murdoch32409262013-08-07 11:04:47 +0100238 raise ValueError('Unrecognized dependency for %s: %s' % (
239 self._namespace.name, context))
240
241 for dependency in dependencies:
242 categorize_dependency(dependency)
Ben Murdoch558790d2013-07-30 15:19:42 +0100243
244 dependency_rows = []
245 if permissions_content:
246 dependency_rows.append({
247 'title': 'Permissions',
248 'content': permissions_content
249 })
250 if manifest_content:
251 dependency_rows.append({
252 'title': 'Manifest',
253 'content': manifest_content
254 })
255 return dependency_rows
256
257 def _GetMiscIntroRows(self):
258 """ Generates miscellaneous intro table row data, such as 'Permissions',
259 'Samples', and 'Learn More', using intro_tables.json.
260 """
261 misc_rows = []
262 # Look up the API name in intro_tables.json, which is structured
263 # similarly to the data structure being created. If the name is found, loop
264 # through the attributes and add them to this structure.
Ben Murdochca12bfa2013-07-23 11:17:05 +0100265 table_info = self._intro_tables.get(self._namespace.name)
266 if table_info is None:
Ben Murdoch558790d2013-07-30 15:19:42 +0100267 return misc_rows
Ben Murdochca12bfa2013-07-23 11:17:05 +0100268
Ben Murdoch558790d2013-07-30 15:19:42 +0100269 for category in table_info.keys():
270 content = copy.deepcopy(table_info[category])
Ben Murdochca12bfa2013-07-23 11:17:05 +0100271 for node in content:
272 # If there is a 'partial' argument and it hasn't already been
273 # converted to a Handlebar object, transform it to a template.
Ben Murdoch558790d2013-07-30 15:19:42 +0100274 if 'partial' in node:
Ben Murdochca12bfa2013-07-23 11:17:05 +0100275 node['partial'] = self._template_data_source.get(node['partial'])
Ben Murdoch558790d2013-07-30 15:19:42 +0100276 misc_rows.append({ 'title': category, 'content': content })
277 return misc_rows
Ben Murdochca12bfa2013-07-23 11:17:05 +0100278
279 def _GetApiAvailability(self):
280 return self._availability_finder.GetApiAvailability(self._namespace.name)
281
282 def _GetChannelWarning(self):
283 if not self._IsExperimental():
284 return { self._GetApiAvailability().channel: True }
285 return None
286
287 def _IsExperimental(self):
288 return self._namespace.name.startswith('experimental')
289
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000290 def _GenerateTypes(self, types):
291 return [self._GenerateType(t) for t in types]
292
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000293 def _GenerateType(self, type_):
294 type_dict = {
295 'name': type_.simple_name,
296 'description': self._FormatDescription(type_.description),
297 'properties': self._GenerateProperties(type_.properties),
298 'functions': self._GenerateFunctions(type_.functions),
299 'events': self._GenerateEvents(type_.events),
300 'id': _CreateId(type_, 'type')
301 }
302 self._RenderTypeInformation(type_, type_dict)
303 return type_dict
304
305 def _GenerateFunctions(self, functions):
306 return [self._GenerateFunction(f) for f in functions.values()]
307
308 def _GenerateFunction(self, function):
309 function_dict = {
310 'name': function.simple_name,
311 'description': self._FormatDescription(function.description),
312 'callback': self._GenerateCallback(function.callback),
313 'parameters': [],
314 'returns': None,
315 'id': _CreateId(function, 'method')
316 }
317 if (function.parent is not None and
318 not isinstance(function.parent, model.Namespace)):
Ben Murdochbb1529c2013-08-08 10:24:53 +0100319 function_dict['parentName'] = function.parent.simple_name
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000320 if function.returns:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000321 function_dict['returns'] = self._GenerateType(function.returns)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000322 for param in function.params:
323 function_dict['parameters'].append(self._GenerateProperty(param))
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000324 if function.callback is not None:
325 # Show the callback as an extra parameter.
326 function_dict['parameters'].append(
327 self._GenerateCallbackProperty(function.callback))
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000328 if len(function_dict['parameters']) > 0:
329 function_dict['parameters'][-1]['last'] = True
330 return function_dict
331
332 def _GenerateEvents(self, events):
333 return [self._GenerateEvent(e) for e in events.values()]
334
335 def _GenerateEvent(self, event):
336 event_dict = {
337 'name': event.simple_name,
338 'description': self._FormatDescription(event.description),
339 'parameters': [self._GenerateProperty(p) for p in event.params],
340 'callback': self._GenerateCallback(event.callback),
341 'filters': [self._GenerateProperty(f) for f in event.filters],
342 'conditions': [self._GetLink(condition)
343 for condition in event.conditions],
344 'actions': [self._GetLink(action) for action in event.actions],
345 'supportsRules': event.supports_rules,
346 'id': _CreateId(event, 'event')
347 }
348 if (event.parent is not None and
349 not isinstance(event.parent, model.Namespace)):
Ben Murdochbb1529c2013-08-08 10:24:53 +0100350 event_dict['parentName'] = event.parent.simple_name
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000351 if event.callback is not None:
352 # Show the callback as an extra parameter.
353 event_dict['parameters'].append(
354 self._GenerateCallbackProperty(event.callback))
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000355 if len(event_dict['parameters']) > 0:
356 event_dict['parameters'][-1]['last'] = True
357 return event_dict
358
359 def _GenerateCallback(self, callback):
360 if not callback:
361 return None
362 callback_dict = {
363 'name': callback.simple_name,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000364 'simple_type': {'simple_type': 'function'},
365 'optional': callback.optional,
366 'parameters': []
367 }
368 for param in callback.params:
369 callback_dict['parameters'].append(self._GenerateProperty(param))
370 if (len(callback_dict['parameters']) > 0):
371 callback_dict['parameters'][-1]['last'] = True
372 return callback_dict
373
374 def _GenerateProperties(self, properties):
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000375 return [self._GenerateProperty(v) for v in properties.values()]
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000376
377 def _GenerateProperty(self, property_):
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000378 if not hasattr(property_, 'type_'):
379 for d in dir(property_):
380 if not d.startswith('_'):
381 print ('%s -> %s' % (d, getattr(property_, d)))
382 type_ = property_.type_
383
384 # Make sure we generate property info for arrays, too.
385 # TODO(kalman): what about choices?
386 if type_.property_type == model.PropertyType.ARRAY:
387 properties = type_.item_type.properties
388 else:
389 properties = type_.properties
390
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000391 property_dict = {
392 'name': property_.simple_name,
393 'optional': property_.optional,
394 'description': self._FormatDescription(property_.description),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000395 'properties': self._GenerateProperties(type_.properties),
396 'functions': self._GenerateFunctions(type_.functions),
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000397 'parameters': [],
398 'returns': None,
399 'id': _CreateId(property_, 'property')
400 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000401
402 if type_.property_type == model.PropertyType.FUNCTION:
403 function = type_.function
404 for param in function.params:
405 property_dict['parameters'].append(self._GenerateProperty(param))
406 if function.returns:
407 property_dict['returns'] = self._GenerateType(function.returns)
408
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000409 if (property_.parent is not None and
410 not isinstance(property_.parent, model.Namespace)):
Ben Murdochbb1529c2013-08-08 10:24:53 +0100411 property_dict['parentName'] = property_.parent.simple_name
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000412
413 value = property_.value
414 if value is not None:
415 if isinstance(value, int):
416 property_dict['value'] = _FormatValue(value)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000417 else:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000418 property_dict['value'] = value
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000419 else:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000420 self._RenderTypeInformation(type_, property_dict)
421
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000422 return property_dict
423
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000424 def _GenerateCallbackProperty(self, callback):
425 property_dict = {
426 'name': callback.simple_name,
427 'description': self._FormatDescription(callback.description),
428 'optional': callback.optional,
429 'id': _CreateId(callback, 'property'),
430 'simple_type': 'function',
431 }
432 if (callback.parent is not None and
433 not isinstance(callback.parent, model.Namespace)):
Ben Murdochbb1529c2013-08-08 10:24:53 +0100434 property_dict['parentName'] = callback.parent.simple_name
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000435 return property_dict
436
437 def _RenderTypeInformation(self, type_, dst_dict):
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100438 dst_dict['is_object'] = type_.property_type == model.PropertyType.OBJECT
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000439 if type_.property_type == model.PropertyType.CHOICES:
440 dst_dict['choices'] = self._GenerateTypes(type_.choices)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100441 # We keep track of which == last for knowing when to add "or" between
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000442 # choices in templates.
443 if len(dst_dict['choices']) > 0:
444 dst_dict['choices'][-1]['last'] = True
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000445 elif type_.property_type == model.PropertyType.REF:
446 dst_dict['link'] = self._GetLink(type_.ref_type)
447 elif type_.property_type == model.PropertyType.ARRAY:
448 dst_dict['array'] = self._GenerateType(type_.item_type)
449 elif type_.property_type == model.PropertyType.ENUM:
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000450 dst_dict['enum_values'] = []
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000451 for enum_value in type_.enum_values:
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000452 dst_dict['enum_values'].append({'name': enum_value})
453 if len(dst_dict['enum_values']) > 0:
454 dst_dict['enum_values'][-1]['last'] = True
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000455 elif type_.instance_of is not None:
456 dst_dict['simple_type'] = type_.instance_of.lower()
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000457 else:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000458 dst_dict['simple_type'] = type_.property_type.name.lower()
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000459
460class _LazySamplesGetter(object):
461 """This class is needed so that an extensions API page does not have to fetch
462 the apps samples page and vice versa.
463 """
464 def __init__(self, api_name, samples):
465 self._api_name = api_name
466 self._samples = samples
467
468 def get(self, key):
469 return self._samples.FilterSamples(key, self._api_name)
470
471class APIDataSource(object):
472 """This class fetches and loads JSON APIs from the FileSystem passed in with
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100473 |compiled_fs_factory|, so the APIs can be plugged into templates.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000474 """
475 class Factory(object):
Ben Murdochca12bfa2013-07-23 11:17:05 +0100476 def __init__(self,
477 compiled_fs_factory,
478 base_path,
479 availability_finder_factory):
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100480 def create_compiled_fs(fn, category):
481 return compiled_fs_factory.Create(fn, APIDataSource, category=category)
482
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100483 self._json_cache = create_compiled_fs(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000484 lambda api_name, api: self._LoadJsonAPI(api, False),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100485 'json')
486 self._idl_cache = create_compiled_fs(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000487 lambda api_name, api: self._LoadIdlAPI(api, False),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100488 'idl')
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000489
490 # These caches are used if an APIDataSource does not want to resolve the
491 # $refs in an API. This is needed to prevent infinite recursion in
492 # ReferenceResolver.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100493 self._json_cache_no_refs = create_compiled_fs(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000494 lambda api_name, api: self._LoadJsonAPI(api, True),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100495 'json-no-refs')
496 self._idl_cache_no_refs = create_compiled_fs(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000497 lambda api_name, api: self._LoadIdlAPI(api, True),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100498 'idl-no-refs')
499
500 self._idl_names_cache = create_compiled_fs(self._GetIDLNames, 'idl-names')
501 self._names_cache = create_compiled_fs(self._GetAllNames, 'names')
502
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000503 self._base_path = base_path
Ben Murdochca12bfa2013-07-23 11:17:05 +0100504 self._availability_finder = availability_finder_factory.Create()
Ben Murdoch558790d2013-07-30 15:19:42 +0100505 self._parse_cache = create_compiled_fs(
Ben Murdochca12bfa2013-07-23 11:17:05 +0100506 lambda _, json: json_parse.Parse(json),
507 'intro-cache')
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000508 # These must be set later via the SetFooDataSourceFactory methods.
509 self._ref_resolver_factory = None
510 self._samples_data_source_factory = None
511
512 def SetSamplesDataSourceFactory(self, samples_data_source_factory):
513 self._samples_data_source_factory = samples_data_source_factory
514
515 def SetReferenceResolverFactory(self, ref_resolver_factory):
516 self._ref_resolver_factory = ref_resolver_factory
517
Ben Murdochca12bfa2013-07-23 11:17:05 +0100518 def SetTemplateDataSource(self, template_data_source_factory):
519 # This TemplateDataSource is only being used for fetching template data.
520 self._template_data_source = template_data_source_factory.Create(None, '')
521
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000522 def Create(self, request, disable_refs=False):
523 """Create an APIDataSource. |disable_refs| specifies whether $ref's in
524 APIs being processed by the |ToDict| method of _JSCModel follows $ref's
525 in the API. This prevents endless recursion in ReferenceResolver.
526 """
527 if self._samples_data_source_factory is None:
528 # Only error if there is a request, which means this APIDataSource is
529 # actually being used to render a page.
530 if request is not None:
531 logging.error('SamplesDataSource.Factory was never set in '
532 'APIDataSource.Factory.')
533 samples = None
534 else:
535 samples = self._samples_data_source_factory.Create(request)
536 if not disable_refs and self._ref_resolver_factory is None:
537 logging.error('ReferenceResolver.Factory was never set in '
538 'APIDataSource.Factory.')
Ben Murdochca12bfa2013-07-23 11:17:05 +0100539 return APIDataSource(self._json_cache,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000540 self._idl_cache,
541 self._json_cache_no_refs,
542 self._idl_cache_no_refs,
543 self._names_cache,
544 self._idl_names_cache,
545 self._base_path,
546 samples,
547 disable_refs)
548
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000549 def _LoadJsonAPI(self, api, disable_refs):
550 return _JSCModel(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000551 json_parse.Parse(api)[0],
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000552 self._ref_resolver_factory.Create() if not disable_refs else None,
Ben Murdochca12bfa2013-07-23 11:17:05 +0100553 disable_refs,
554 self._availability_finder,
Ben Murdoch558790d2013-07-30 15:19:42 +0100555 self._parse_cache,
Ben Murdochca12bfa2013-07-23 11:17:05 +0100556 self._template_data_source).ToDict()
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000557
558 def _LoadIdlAPI(self, api, disable_refs):
559 idl = idl_parser.IDLParser().ParseData(api)
560 return _JSCModel(
561 idl_schema.IDLSchema(idl).process()[0],
562 self._ref_resolver_factory.Create() if not disable_refs else None,
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100563 disable_refs,
Ben Murdochca12bfa2013-07-23 11:17:05 +0100564 self._availability_finder,
Ben Murdoch558790d2013-07-30 15:19:42 +0100565 self._parse_cache,
Ben Murdochca12bfa2013-07-23 11:17:05 +0100566 self._template_data_source,
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100567 idl=True).ToDict()
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000568
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000569 def _GetIDLNames(self, base_dir, apis):
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100570 return self._GetExtNames(apis, ['idl'])
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000571
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000572 def _GetAllNames(self, base_dir, apis):
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100573 return self._GetExtNames(apis, ['json', 'idl'])
574
575 def _GetExtNames(self, apis, exts):
576 return [model.UnixName(os.path.splitext(api)[0]) for api in apis
577 if os.path.splitext(api)[1][1:] in exts]
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000578
579 def __init__(self,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000580 json_cache,
581 idl_cache,
582 json_cache_no_refs,
583 idl_cache_no_refs,
584 names_cache,
585 idl_names_cache,
586 base_path,
587 samples,
588 disable_refs):
589 self._base_path = base_path
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000590 self._json_cache = json_cache
591 self._idl_cache = idl_cache
592 self._json_cache_no_refs = json_cache_no_refs
593 self._idl_cache_no_refs = idl_cache_no_refs
594 self._names_cache = names_cache
595 self._idl_names_cache = idl_names_cache
596 self._samples = samples
597 self._disable_refs = disable_refs
598
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000599 def _GenerateHandlebarContext(self, handlebar_dict, path):
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000600 handlebar_dict['samples'] = _LazySamplesGetter(path, self._samples)
601 return handlebar_dict
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000602
603 def _GetAsSubdirectory(self, name):
604 if name.startswith('experimental_'):
605 parts = name[len('experimental_'):].split('_', 1)
Ben Murdochca12bfa2013-07-23 11:17:05 +0100606 if len(parts) > 1:
607 parts[1] = 'experimental_%s' % parts[1]
608 return '/'.join(parts)
609 return '%s/%s' % (parts[0], name)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000610 return name.replace('_', '/', 1)
611
612 def get(self, key):
613 if key.endswith('.html') or key.endswith('.json') or key.endswith('.idl'):
614 path, ext = os.path.splitext(key)
615 else:
616 path = key
617 unix_name = model.UnixName(path)
618 idl_names = self._idl_names_cache.GetFromFileListing(self._base_path)
619 names = self._names_cache.GetFromFileListing(self._base_path)
620 if unix_name not in names and self._GetAsSubdirectory(unix_name) in names:
621 unix_name = self._GetAsSubdirectory(unix_name)
622
623 if self._disable_refs:
624 cache, ext = (
625 (self._idl_cache_no_refs, '.idl') if (unix_name in idl_names) else
626 (self._json_cache_no_refs, '.json'))
627 else:
628 cache, ext = ((self._idl_cache, '.idl') if (unix_name in idl_names) else
629 (self._json_cache, '.json'))
630 return self._GenerateHandlebarContext(
631 cache.GetFromFile('%s/%s%s' % (self._base_path, unix_name, ext)),
632 path)