1# Copyright 2013 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
5from copy import deepcopy
6import json
7
8from data_source import DataSource
9from future import Future
10from manifest_features import ConvertDottedKeysToNested
11from platform_util import GetPlatforms, PluralToSingular
12
13
14def _ListifyAndSortDocs(features, app_name):
15  '''Convert a |feautres| dictionary, and all 'children' dictionaries, into
16  lists recursively. Sort lists first by 'level' then by name.
17  '''
18  def sort_key(item):
19    '''Key function to sort items primarily by level (according to index into
20    levels) then subsort by name.
21    '''
22    levels = ('required', 'recommended', 'only_one', 'optional')
23
24    return (levels.index(item.get('level', 'optional')), item['name'])
25
26  def coerce_example_to_feature(feature):
27    '''To display json in examples more clearly, convert the example of
28    |feature| into the feature format, with a name and children, to be rendered
29    by the templates. Only applicable to examples that are dictionaries.
30    '''
31    if not isinstance(feature.get('example'), dict):
32      if 'example' in feature:
33        feature['example'] = json.dumps(feature['example'])
34      return
35    # Add any keys/value pairs in the dict as children
36    for key, value in feature['example'].iteritems():
37      if not 'children' in feature:
38        feature['children'] = {}
39      feature['children'][key] = { 'name': key, 'example': value }
40    del feature['example']
41    del feature['has_example']
42
43  def convert_and_sort(features):
44    for key, value in features.items():
45      if 'example' in value:
46        value['has_example'] = True
47        example = json.dumps(value['example'])
48        if example == '{}':
49          value['example'] = '{...}'
50        elif example == '[]':
51          value['example'] = '[...]'
52        elif example == '[{}]':
53          value['example'] = '[{...}]'
54        else:
55          coerce_example_to_feature(value)
56      if 'children' in value:
57        features[key]['children'] = convert_and_sort(value['children'])
58    return sorted(features.values(), key=sort_key)
59
60  # Replace {{platform}} in the 'name' manifest property example with
61  # |app_name|, the convention that the normal template rendering uses.
62  # TODO(kalman): Make the example a template and pass this through there.
63  if 'name' in features:
64    name = features['name']
65    name['example'] = name['example'].replace('{{platform}}', app_name)
66
67  features = convert_and_sort(features)
68
69  return features
70
71def _AddLevelAnnotations(features):
72  '''Add level annotations to |features|. |features| and children lists must be
73  sorted by 'level'. Annotations are added to the first item in a group of
74  features of the same 'level'.
75
76  The last item in a list has 'is_last' set to True.
77  '''
78  annotations = {
79    'required': 'Required',
80    'recommended': 'Recommended',
81    'only_one': 'Pick one (or none)',
82    'optional': 'Optional'
83  }
84
85  def add_annotation(item, annotation):
86    if not 'annotations' in item:
87      item['annotations'] = []
88    item['annotations'].insert(0, annotation)
89
90  def annotate(parent_level, features):
91    current_level = parent_level
92    for item in features:
93      level = item.get('level', 'optional')
94      if level != current_level:
95        add_annotation(item, annotations[level])
96        current_level = level
97      if 'children' in item:
98        annotate(level, item['children'])
99    if features:
100      features[-1]['is_last'] = True
101
102  annotate('required', features)
103  return features
104
105class ManifestDataSource(DataSource):
106  '''Provides access to the properties in manifest features.
107  '''
108  def __init__(self, server_instance, _):
109    self._platform_bundle = server_instance.platform_bundle
110    self._object_store = server_instance.object_store_creator.Create(
111        ManifestDataSource)
112
113  def _CreateManifestDataForPlatform(self, platform):
114    future_manifest_features = self._platform_bundle.GetFeaturesBundle(
115        platform).GetManifestFeatures()
116    def resolve():
117      manifest_features = future_manifest_features.Get()
118      return _AddLevelAnnotations(_ListifyAndSortDocs(
119          ConvertDottedKeysToNested(deepcopy(manifest_features)),
120          app_name=PluralToSingular(platform).capitalize()))
121    return Future(callback=resolve)
122
123  def _CreateManifestData(self):
124    manifest_data_futures = dict((p, self._CreateManifestDataForPlatform(p))
125                                 for p in GetPlatforms())
126    def resolve():
127      return dict((platform, future.Get())
128                  for platform, future in manifest_data_futures.iteritems())
129    return Future(callback=resolve)
130
131  def _GetCachedManifestData(self):
132    data = self._object_store.Get('manifest_data').Get()
133    if data is None:
134      data = self._CreateManifestData().Get()
135      self._object_store.Set('manifest_data', data)
136    return data
137
138  def get(self, key):
139    return self._GetCachedManifestData().get(key)
140
141  def Refresh(self, path):
142    return self._CreateManifestData()
143