Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / server2 / jsc_view.py
1 # Copyright 2014 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 copy
6 import logging
7 import posixpath
8
9 from api_models import GetNodeCategories
10 from api_schema_graph import APINodeCursor
11 from docs_server_utils import MarkFirstAndLast
12 from extensions_paths import JSON_TEMPLATES, PRIVATE_TEMPLATES
13 from operator import itemgetter
14 from platform_util import PlatformToExtensionType
15 import third_party.json_schema_compiler.model as model
16
17
18 def GetEventByNameFromEvents(events):
19   '''Parses the dictionary |events| to find the definitions of members of the
20   type Event.  Returns a dictionary mapping the name of a member to that
21   member's definition.
22   '''
23   assert 'types' in events, \
24       'The dictionary |events| must contain the key "types".'
25   event_list = [t for t in events['types'] if t.get('name') == 'Event']
26   assert len(event_list) == 1, 'Exactly one type must be called "Event".'
27   return _GetByNameDict(event_list[0])
28
29
30 def _GetByNameDict(namespace):
31   '''Returns a dictionary mapping names to named items from |namespace|.
32
33   This lets us render specific API entities rather than the whole thing at once,
34   for example {{apis.manifestTypes.byName.ExternallyConnectable}}.
35
36   Includes items from namespace['types'], namespace['functions'],
37   namespace['events'], and namespace['properties'].
38   '''
39   by_name = {}
40   for item_type in GetNodeCategories():
41     if item_type in namespace:
42       old_size = len(by_name)
43       by_name.update(
44           (item['name'], item) for item in namespace[item_type])
45       assert len(by_name) == old_size + len(namespace[item_type]), (
46           'Duplicate name in %r' % namespace)
47   return by_name
48
49
50 def _CreateId(node, prefix):
51   if node.parent is not None and not isinstance(node.parent, model.Namespace):
52     return '-'.join([prefix, node.parent.simple_name, node.simple_name])
53   return '-'.join([prefix, node.simple_name])
54
55
56 def _FormatValue(value):
57   '''Inserts commas every three digits for integer values. It is magic.
58   '''
59   s = str(value)
60   return ','.join([s[max(0, i - 3):i] for i in range(len(s), 0, -3)][::-1])
61
62
63 class JSCView(object):
64   '''Uses a Model from the JSON Schema Compiler and generates a dict that
65   a Motemplate template can use for a data source.
66   '''
67
68   def __init__(self,
69                content_script_apis,
70                jsc_model,
71                availability_finder,
72                json_cache,
73                template_cache,
74                features_bundle,
75                event_byname_future,
76                platform):
77     self._content_script_apis = content_script_apis
78     self._availability = availability_finder.GetAPIAvailability(jsc_model.name)
79     self._current_node = APINodeCursor(availability_finder, jsc_model.name)
80     self._api_availabilities = json_cache.GetFromFile(
81         posixpath.join(JSON_TEMPLATES, 'api_availabilities.json'))
82     self._intro_tables = json_cache.GetFromFile(
83         posixpath.join(JSON_TEMPLATES, 'intro_tables.json'))
84     self._api_features = features_bundle.GetAPIFeatures()
85     self._template_cache = template_cache
86     self._event_byname_future = event_byname_future
87     self._jsc_model = jsc_model
88     self._platform = platform
89
90   def _GetLink(self, link):
91     ref = link if '.' in link else (self._jsc_model.name + '.' + link)
92     return { 'ref': ref, 'text': link, 'name': link }
93
94   def ToDict(self):
95     '''Returns a dictionary representation of |self._jsc_model|, which
96     is a Namespace object from JSON Schema Compiler.
97     '''
98     assert self._jsc_model is not None
99     chrome_dot_name = 'chrome.%s' % self._jsc_model.name
100     as_dict = {
101       'name': self._jsc_model.name,
102       'namespace': self._jsc_model.documentation_options.get('namespace',
103                                                              chrome_dot_name),
104       'title': self._jsc_model.documentation_options.get('title',
105                                                          chrome_dot_name),
106       'documentationOptions': self._jsc_model.documentation_options,
107       'types': self._GenerateTypes(self._jsc_model.types.values()),
108       'functions': self._GenerateFunctions(self._jsc_model.functions),
109       'events': self._GenerateEvents(self._jsc_model.events),
110       'domEvents': self._GenerateDomEvents(self._jsc_model.events),
111       'properties': self._GenerateProperties(self._jsc_model.properties),
112       'introList': self._GetIntroTableList(),
113       'channelWarning': self._GetChannelWarning(),
114     }
115     if self._jsc_model.deprecated:
116       as_dict['deprecated'] = self._jsc_model.deprecated
117
118     as_dict['byName'] = _GetByNameDict(as_dict)
119
120     return as_dict
121
122   def _IsExperimental(self):
123     return self._jsc_model.name.startswith('experimental')
124
125   def _GetChannelWarning(self):
126     if not self._IsExperimental():
127       return {
128         self._availability.channel_info.channel: True
129       }
130     return None
131
132   def _GenerateCallback(self, callback):
133     '''Returns a dictionary representation of a callback suitable
134     for consumption by templates.
135     '''
136     if not callback:
137       return None
138     callback_dict = {
139       'name': callback.simple_name,
140       'simple_type': {'simple_type': 'function'},
141       'optional': callback.optional,
142       'parameters': []
143     }
144     with self._current_node.Descend('parameters',
145                                     callback.simple_name,
146                                     'parameters'):
147       for param in callback.params:
148         callback_dict['parameters'].append(self._GenerateProperty(param))
149     if (len(callback_dict['parameters']) > 0):
150       callback_dict['parameters'][-1]['last'] = True
151     return callback_dict
152
153   def _GenerateCallbackProperty(self, callback, callback_dict):
154     '''Returns a dictionary representation of a callback property
155     suitable for consumption by templates.
156     '''
157     property_dict = {
158       'name': callback.simple_name,
159       'description': callback.description,
160       'optional': callback.optional,
161       'isCallback': True,
162       'asFunction': callback_dict,
163       'id': _CreateId(callback, 'property'),
164       'simple_type': 'function',
165     }
166     if (callback.parent is not None and
167         not isinstance(callback.parent, model.Namespace)):
168       property_dict['parentName'] = callback.parent.simple_name
169     return property_dict
170
171   def _GenerateTypes(self, types):
172     '''Returns a list of dictionaries representing this Model's types.
173     '''
174     with self._current_node.Descend('types'):
175       return [self._GenerateType(t) for t in types]
176
177   def _GenerateType(self, type_):
178     '''Returns a dictionary representation of a type from JSON Schema Compiler.
179     '''
180     with self._current_node.Descend(type_.simple_name):
181       type_dict = {
182         'name': type_.simple_name,
183         'description': type_.description,
184         'properties': self._GenerateProperties(type_.properties),
185         'functions': self._GenerateFunctions(type_.functions),
186         'events': self._GenerateEvents(type_.events),
187         'id': _CreateId(type_, 'type'),
188         'availability': self._GetAvailabilityTemplate()
189       }
190       self._RenderTypeInformation(type_, type_dict)
191       return type_dict
192
193   def _GenerateFunctions(self, functions):
194     '''Returns a list of dictionaries representing this Model's functions.
195     '''
196     with self._current_node.Descend('functions'):
197       return [self._GenerateFunction(f) for f in functions.values()]
198
199   def _GenerateFunction(self, function):
200     '''Returns a dictionary representation of a function from
201     JSON Schema Compiler.
202     '''
203     # When ignoring types, properties must be ignored as well.
204     with self._current_node.Descend(function.simple_name,
205                                     ignore=('types', 'properties')):
206       function_dict = {
207         'name': function.simple_name,
208         'description': function.description,
209         'callback': self._GenerateCallback(function.callback),
210         'parameters': [],
211         'returns': None,
212         'id': _CreateId(function, 'method'),
213         'availability': self._GetAvailabilityTemplate()
214       }
215       self._AddCommonProperties(function_dict, function)
216       if function.returns:
217         function_dict['returns'] = self._GenerateType(function.returns)
218
219     with self._current_node.Descend(function.simple_name, 'parameters'):
220       for param in function.params:
221         function_dict['parameters'].append(self._GenerateProperty(param))
222     if function.callback is not None:
223       # Show the callback as an extra parameter.
224       function_dict['parameters'].append(
225           self._GenerateCallbackProperty(function.callback,
226                                          function_dict['callback']))
227     if len(function_dict['parameters']) > 0:
228       function_dict['parameters'][-1]['last'] = True
229     return function_dict
230
231   def _GenerateEvents(self, events):
232     '''Returns a list of dictionaries representing this Model's events.
233     '''
234     with self._current_node.Descend('events'):
235       return [self._GenerateEvent(e) for e in events.values()
236               if not e.supports_dom]
237
238   def _GenerateDomEvents(self, events):
239     '''Returns a list of dictionaries representing this Model's DOM events.
240     '''
241     with self._current_node.Descend('events'):
242       return [self._GenerateEvent(e) for e in events.values()
243               if e.supports_dom]
244
245   def _GenerateEvent(self, event):
246     '''Returns a dictionary representation of an event from
247     JSON Schema Compiler. Note that although events are modeled as functions
248     in JSON Schema Compiler, we model them differently for the templates.
249     '''
250     with self._current_node.Descend(event.simple_name, ignore=('properties',)):
251       event_dict = {
252         'name': event.simple_name,
253         'description': event.description,
254         'filters': [self._GenerateProperty(f) for f in event.filters],
255         'conditions': [self._GetLink(condition)
256                        for condition in event.conditions],
257         'actions': [self._GetLink(action) for action in event.actions],
258         'supportsRules': event.supports_rules,
259         'supportsListeners': event.supports_listeners,
260         'properties': [],
261         'id': _CreateId(event, 'event'),
262         'byName': {},
263         'availability': self._GetAvailabilityTemplate()
264       }
265     self._AddCommonProperties(event_dict, event)
266     # Add the Event members to each event in this object.
267     if self._event_byname_future:
268       event_dict['byName'].update(self._event_byname_future.Get())
269     # We need to create the method description for addListener based on the
270     # information stored in |event|.
271     if event.supports_listeners:
272       callback_object = model.Function(parent=event,
273                                        name='callback',
274                                        json={},
275                                        namespace=event.parent,
276                                        origin='')
277       callback_object.params = event.params
278       if event.callback:
279         callback_object.callback = event.callback
280
281       with self._current_node.Descend(event.simple_name):
282         callback = self._GenerateFunction(callback_object)
283       callback_parameter = self._GenerateCallbackProperty(callback_object,
284                                                           callback)
285       callback_parameter['last'] = True
286       event_dict['byName']['addListener'] = {
287         'name': 'addListener',
288         'callback': callback,
289         'parameters': [callback_parameter]
290       }
291     if event.supports_dom:
292       # Treat params as properties of the custom Event object associated with
293       # this DOM Event.
294       with self._current_node.Descend(event.simple_name,
295                                       ignore=('properties',)):
296         event_dict['properties'] += [self._GenerateProperty(param)
297                                      for param in event.params]
298     return event_dict
299
300   def _GenerateProperties(self, properties):
301     '''Returns a list of dictionaries representing this Model's properites.
302     '''
303     with self._current_node.Descend('properties'):
304       return [self._GenerateProperty(v) for v in properties.values()]
305
306   def _GenerateProperty(self, property_):
307     '''Returns a dictionary representation of a property from
308     JSON Schema Compiler.
309     '''
310     if not hasattr(property_, 'type_'):
311       for d in dir(property_):
312         if not d.startswith('_'):
313           print ('%s -> %s' % (d, getattr(property_, d)))
314     type_ = property_.type_
315
316     # Make sure we generate property info for arrays, too.
317     # TODO(kalman): what about choices?
318     if type_.property_type == model.PropertyType.ARRAY:
319       properties = type_.item_type.properties
320     else:
321       properties = type_.properties
322
323     with self._current_node.Descend(property_.simple_name):
324       property_dict = {
325         'name': property_.simple_name,
326         'optional': property_.optional,
327         'description': property_.description,
328         'properties': self._GenerateProperties(type_.properties),
329         'functions': self._GenerateFunctions(type_.functions),
330         'parameters': [],
331         'returns': None,
332         'id': _CreateId(property_, 'property'),
333         'availability': self._GetAvailabilityTemplate()
334       }
335       self._AddCommonProperties(property_dict, property_)
336
337       if type_.property_type == model.PropertyType.FUNCTION:
338         function = type_.function
339         with self._current_node.Descend('parameters'):
340           for param in function.params:
341             property_dict['parameters'].append(self._GenerateProperty(param))
342         if function.returns:
343           with self._current_node.Descend(ignore=('types', 'properties')):
344             property_dict['returns'] = self._GenerateType(function.returns)
345
346     value = property_.value
347     if value is not None:
348       if isinstance(value, int):
349         property_dict['value'] = _FormatValue(value)
350       else:
351         property_dict['value'] = value
352     else:
353       self._RenderTypeInformation(type_, property_dict)
354
355     return property_dict
356
357   def _AddCommonProperties(self, target, src):
358     if src.deprecated is not None:
359       target['deprecated'] = src.deprecated
360     if (src.parent is not None and
361         not isinstance(src.parent, model.Namespace)):
362       target['parentName'] = src.parent.simple_name
363
364   def _RenderTypeInformation(self, type_, dst_dict):
365     with self._current_node.Descend(ignore=('types', 'properties')):
366       dst_dict['is_object'] = type_.property_type == model.PropertyType.OBJECT
367       if type_.property_type == model.PropertyType.CHOICES:
368         dst_dict['choices'] = self._GenerateTypes(type_.choices)
369         # We keep track of which == last for knowing when to add "or" between
370         # choices in templates.
371         if len(dst_dict['choices']) > 0:
372           dst_dict['choices'][-1]['last'] = True
373       elif type_.property_type == model.PropertyType.REF:
374         dst_dict['link'] = self._GetLink(type_.ref_type)
375       elif type_.property_type == model.PropertyType.ARRAY:
376         dst_dict['array'] = self._GenerateType(type_.item_type)
377       elif type_.property_type == model.PropertyType.ENUM:
378         dst_dict['enum_values'] = [
379             {'name': value.name, 'description': value.description}
380             for value in type_.enum_values]
381         if len(dst_dict['enum_values']) > 0:
382           dst_dict['enum_values'][-1]['last'] = True
383       elif type_.instance_of is not None:
384         dst_dict['simple_type'] = type_.instance_of
385       else:
386         dst_dict['simple_type'] = type_.property_type.name
387
388   def _CreateAvailabilityTemplate(self, status, scheduled, version):
389     '''Returns an object suitable for use in templates to display availability
390     information.
391     '''
392     # TODO(rockot): Temporary hack. Remove this very soon.
393     if status == 'master':
394       status = 'trunk'
395     return {
396       'partial': self._template_cache.GetFromFile(
397           '%sintro_tables/%s_message.html' % (PRIVATE_TEMPLATES, status)).Get(),
398       'scheduled': scheduled,
399       'version': version
400     }
401
402   def _GetAvailabilityTemplate(self):
403     '''Gets availability for the current node and returns an appropriate
404     template object.
405     '''
406     # Displaying deprecated status takes precedence over when the API
407     # became stable.
408     availability_info = self._current_node.GetDeprecated()
409     if availability_info is not None:
410       status = 'deprecated'
411     else:
412       availability_info = self._current_node.GetAvailability()
413       if availability_info is None:
414         return None
415       status = availability_info.channel_info.channel
416     return self._CreateAvailabilityTemplate(
417         status,
418         availability_info.scheduled,
419         availability_info.channel_info.version)
420
421   def _GetIntroTableList(self):
422     '''Create a generic data structure that can be traversed by the templates
423     to create an API intro table.
424     '''
425     intro_rows = [
426       self._GetIntroDescriptionRow(),
427       self._GetIntroAvailabilityRow()
428     ] + self._GetIntroDependencyRows() + self._GetIntroContentScriptRow()
429
430     # Add rows using data from intro_tables.json, overriding any existing rows
431     # if they share the same 'title' attribute.
432     row_titles = [row['title'] for row in intro_rows]
433     for misc_row in self._GetMiscIntroRows():
434       if misc_row['title'] in row_titles:
435         intro_rows[row_titles.index(misc_row['title'])] = misc_row
436       else:
437         intro_rows.append(misc_row)
438
439     return intro_rows
440
441   def _GetIntroContentScriptRow(self):
442     '''Generates the 'Content Script' row data for an API intro table.
443     '''
444     content_script_support = self._content_script_apis.get(self._jsc_model.name)
445     if content_script_support is None:
446       return []
447     if content_script_support.restrictedTo:
448       content_script_support.restrictedTo.sort(key=itemgetter('node'))
449       MarkFirstAndLast(content_script_support.restrictedTo)
450     return [{
451       'title': 'Content Scripts',
452       'content': [{
453         'partial': self._template_cache.GetFromFile(
454             posixpath.join(PRIVATE_TEMPLATES,
455                            'intro_tables',
456                            'content_scripts.html')).Get(),
457         'contentScriptSupport': content_script_support.__dict__
458       }]
459     }]
460
461   def _GetIntroDescriptionRow(self):
462     ''' Generates the 'Description' row data for an API intro table.
463     '''
464     return {
465       'title': 'Description',
466       'content': [
467         { 'text': self._jsc_model.description }
468       ]
469     }
470
471   def _GetIntroAvailabilityRow(self):
472     ''' Generates the 'Availability' row data for an API intro table.
473     '''
474     if self._IsExperimental():
475       status = 'experimental'
476       scheduled = None
477       version = None
478     else:
479       status = self._availability.channel_info.channel
480       scheduled = self._availability.scheduled
481       version = self._availability.channel_info.version
482     return {
483       'title': 'Availability',
484       'content': [
485         self._CreateAvailabilityTemplate(status, scheduled, version)
486       ]
487     }
488
489   def _GetIntroDependencyRows(self):
490     # Devtools aren't in _api_features. If we're dealing with devtools, bail.
491     if 'devtools' in self._jsc_model.name:
492       return []
493
494     api_feature = self._api_features.Get().get(self._jsc_model.name)
495     if not api_feature:
496       logging.error('"%s" not found in _api_features.json' %
497                     self._jsc_model.name)
498       return []
499
500     permissions_content = []
501     manifest_content = []
502
503     def categorize_dependency(dependency):
504       def make_code_node(text):
505         return { 'class': 'code', 'text': text }
506
507       context, name = dependency.split(':', 1)
508       if context == 'permission':
509         permissions_content.append(make_code_node('"%s"' % name))
510       elif context == 'manifest':
511         manifest_content.append(make_code_node('"%s": {...}' % name))
512       elif context == 'api':
513         transitive_dependencies = (
514             self._api_features.Get().get(name, {}).get('dependencies', []))
515         for transitive_dependency in transitive_dependencies:
516           categorize_dependency(transitive_dependency)
517       else:
518         logging.error('Unrecognized dependency for %s: %s' %
519                       (self._jsc_model.name, context))
520
521     for dependency in api_feature.get('dependencies', ()):
522       categorize_dependency(dependency)
523
524     dependency_rows = []
525     if permissions_content:
526       dependency_rows.append({
527         'title': 'Permissions',
528         'content': permissions_content
529       })
530     if manifest_content:
531       dependency_rows.append({
532         'title': 'Manifest',
533         'content': manifest_content
534       })
535     return dependency_rows
536
537   def _GetMiscIntroRows(self):
538     ''' Generates miscellaneous intro table row data, such as 'Permissions',
539     'Samples', and 'Learn More', using intro_tables.json.
540     '''
541     misc_rows = []
542     # Look up the API name in intro_tables.json, which is structured
543     # similarly to the data structure being created. If the name is found, loop
544     # through the attributes and add them to this structure.
545     table_info = self._intro_tables.Get().get(self._jsc_model.name)
546     if table_info is None:
547       return misc_rows
548
549     for category in table_info.iterkeys():
550       content = []
551       for node in table_info[category]:
552         ext_type = PlatformToExtensionType(self._platform)
553         # Don't display nodes restricted to a different platform.
554         if ext_type not in node.get('extension_types', (ext_type,)):
555           continue
556         # If there is a 'partial' argument and it hasn't already been
557         # converted to a Motemplate object, transform it to a template.
558         if 'partial' in node:
559           # Note: it's enough to copy() not deepcopy() because only a single
560           # top-level key is being modified.
561           node = copy(node)
562           node['partial'] = self._template_cache.GetFromFile(
563               posixpath.join(PRIVATE_TEMPLATES, node['partial'])).Get()
564         content.append(node)
565       misc_rows.append({ 'title': category, 'content': content })
566     return misc_rows