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