OLD | NEW |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 from copy import deepcopy | 5 import json |
6 from operator import itemgetter | |
7 | 6 |
8 from third_party.json_schema_compiler.json_parse import OrderedDict, Parse | 7 import features_utility as features |
| 8 from third_party.json_schema_compiler.json_parse import Parse |
| 9 |
| 10 def _ListifyAndSortDocs(features, app_name): |
| 11 '''Convert a |feautres| dictionary, and all 'children' dictionaries, into |
| 12 lists recursively. Sort lists first by 'level' then by name. |
| 13 ''' |
| 14 def sort_key(item): |
| 15 '''Key function to sort items primarily by level (according to index into |
| 16 levels) then subsort by name. |
| 17 ''' |
| 18 levels = ('required', 'recommended', 'only_one', 'optional') |
| 19 |
| 20 return (levels.index(item.get('level', 'optional')), item['name']) |
| 21 |
| 22 def convert_and_sort(features): |
| 23 for key, value in features.items(): |
| 24 if 'example' in value: |
| 25 value['has_example'] = True |
| 26 example = json.dumps(value['example']) |
| 27 if example == '{}': |
| 28 value['example'] = '{...}' |
| 29 elif example == '[]': |
| 30 value['example'] = '[...]' |
| 31 else: |
| 32 value['example'] = example |
| 33 |
| 34 if 'children' in value: |
| 35 features[key]['children'] = convert_and_sort(value['children']) |
| 36 |
| 37 return sorted(features.values(), key=sort_key) |
| 38 |
| 39 name = features['name'] |
| 40 name['example'] = name['example'].replace('{{title}}', app_name) |
| 41 |
| 42 return convert_and_sort(features) |
| 43 |
| 44 def _AddLevelAnnotations(features): |
| 45 '''Add level annotations to |features|. |features| and children lists must be |
| 46 sorted by 'level'. Annotations are added to the first item in a group of |
| 47 features of the same 'level'. |
| 48 |
| 49 The last item in a list has 'is_last' set to True. |
| 50 ''' |
| 51 annotations = { |
| 52 'required': 'Required', |
| 53 'recommended': 'Recommended', |
| 54 'only_one': 'Pick one (or none)', |
| 55 'optional': 'Optional' |
| 56 } |
| 57 |
| 58 def add_annotation(item, annotation): |
| 59 if not 'annotations' in item: |
| 60 item['annotations'] = [] |
| 61 item['annotations'].insert(0, annotation) |
| 62 |
| 63 def annotate(parent_level, features): |
| 64 current_level = parent_level |
| 65 for item in features: |
| 66 level = item.get('level', 'optional') |
| 67 if level != current_level: |
| 68 add_annotation(item, annotations[level]) |
| 69 current_level = level |
| 70 if 'children' in item: |
| 71 annotate(level, item['children']) |
| 72 if features: |
| 73 features[-1]['is_last'] = True |
| 74 |
| 75 annotate('required', features) |
| 76 return features |
| 77 |
| 78 def _RestructureChildren(features): |
| 79 '''Features whose names are of the form 'parent.child' are moved to be part |
| 80 of the 'parent' dictionary under the key 'children'. Names are changed to |
| 81 the 'child' section of the original name. Applied recursively so that |
| 82 children can have children. |
| 83 ''' |
| 84 def add_child(features, parent, child_name, value): |
| 85 value['name'] = child_name |
| 86 if not 'children' in features[parent]: |
| 87 features[parent]['children'] = {} |
| 88 features[parent]['children'][child_name] = value |
| 89 |
| 90 def insert_children(features): |
| 91 for name in features.keys(): |
| 92 if '.' in name: |
| 93 value = features.pop(name) |
| 94 parent, child_name = name.split('.', 1) |
| 95 add_child(features, parent, child_name, value) |
| 96 |
| 97 for value in features.values(): |
| 98 if 'children' in value: |
| 99 insert_children(value['children']) |
| 100 |
| 101 insert_children(features) |
| 102 return features |
9 | 103 |
10 class ManifestDataSource(object): | 104 class ManifestDataSource(object): |
11 '''Provides a template with access to manifest properties specific to apps or | 105 '''Provides access to the properties in manifest features. |
12 extensions. | |
13 ''' | 106 ''' |
14 def __init__(self, | 107 def __init__(self, |
15 compiled_fs_factory, | 108 compiled_fs_factory, |
16 file_system, | 109 file_system, |
17 manifest_path, | 110 manifest_path, |
18 features_path): | 111 features_path): |
19 self._manifest_path = manifest_path | 112 self._manifest_path = manifest_path |
20 self._features_path = features_path | 113 self._features_path = features_path |
21 self._file_system = file_system | 114 self._file_system = file_system |
22 self._cache = compiled_fs_factory.Create( | 115 self._cache = compiled_fs_factory.Create( |
23 self._CreateManifestData, ManifestDataSource) | 116 self._CreateManifestData, ManifestDataSource) |
24 | 117 |
25 def _ApplyAppsTransformations(self, manifest): | 118 def GetFeatures(self): |
26 manifest['required'][0]['example'] = 'Application' | 119 '''Returns a dictionary of the contents of |_features_path| merged with |
27 manifest['optional'][-1]['is_last'] = True | 120 |_manifest_path|. |
| 121 ''' |
| 122 manifest_json = Parse(self._file_system.ReadSingle(self._manifest_path)) |
| 123 manifest_features = FeaturesModel.FromJson( |
| 124 Parse(self._file_system.ReadSingle(self._features_path))) |
28 | 125 |
29 def _ApplyExtensionsTransformations(self, manifest): | 126 return manifest_features.MergeWith(manifest_json) |
30 manifest['optional'][-1]['is_last'] = True | 127 |
31 | 128 |
32 def _CreateManifestData(self, _, content): | 129 def _CreateManifestData(self, _, content): |
33 '''Take the contents of |_manifest_path| and create apps and extensions | 130 '''Combine the contents of |_manifest_path| and |_features_path| and filter |
34 versions of a manifest example based on the contents of |_features_path|. | 131 the results into lists specific to apps or extensions for templates. Marks |
| 132 up features with annotations. |
35 ''' | 133 ''' |
36 def create_manifest_dict(): | 134 def for_templates(manifest_features, platform): |
37 manifest_dict = OrderedDict() | 135 return _AddLevelAnnotations( |
38 for category in ('required', 'only_one', 'recommended', 'optional'): | 136 _ListifyAndSortDocs( |
39 manifest_dict[category] = [] | 137 _RestructureChildren( |
40 return manifest_dict | 138 features.Filtered(manifest_features, platform)), |
| 139 app_name=platform.capitalize())) |
41 | 140 |
42 apps = create_manifest_dict() | 141 manifest_json = Parse(self._file_system.ReadSingle(self._manifest_path)) |
43 extensions = create_manifest_dict() | 142 manifest_features = features.MergedWith( |
| 143 features.Parse(Parse(content)), manifest_json) |
44 | 144 |
45 manifest_json = Parse(content) | 145 return { |
46 features_json = Parse(self._file_system.ReadSingle( | 146 'apps': for_templates(manifest_features, 'app'), |
47 self._features_path)) | 147 'extensions': for_templates(manifest_features, 'extension') |
48 | 148 } |
49 def add_property(feature, manifest_key, category): | |
50 '''If |feature|, from features_json, has the correct extension_types, add | |
51 |manifest_key| to either apps or extensions. | |
52 ''' | |
53 added = False | |
54 extension_types = feature['extension_types'] | |
55 if extension_types == 'all' or 'platform_app' in extension_types: | |
56 apps[category].append(deepcopy(manifest_key)) | |
57 added = True | |
58 if extension_types == 'all' or 'extension' in extension_types: | |
59 extensions[category].append(deepcopy(manifest_key)) | |
60 added = True | |
61 return added | |
62 | |
63 # Property types are: required, only_one, recommended, and optional. | |
64 for category in manifest_json: | |
65 for manifest_key in manifest_json[category]: | |
66 # If a property is in manifest.json but not _manifest_features, this | |
67 # will cause an error. | |
68 feature = features_json[manifest_key['name']] | |
69 if add_property(feature, manifest_key, category): | |
70 del features_json[manifest_key['name']] | |
71 | |
72 # All of the properties left in features_json are assumed to be optional. | |
73 for feature in features_json.keys(): | |
74 item = features_json[feature] | |
75 # Handles instances where a features entry is a union with a whitelist. | |
76 if isinstance(item, list): | |
77 item = item[0] | |
78 add_property(item, {'name': feature}, 'optional') | |
79 | |
80 apps['optional'].sort(key=itemgetter('name')) | |
81 extensions['optional'].sort(key=itemgetter('name')) | |
82 | |
83 self._ApplyAppsTransformations(apps) | |
84 self._ApplyExtensionsTransformations(extensions) | |
85 | |
86 return {'apps': apps, 'extensions': extensions} | |
87 | 149 |
88 def get(self, key): | 150 def get(self, key): |
89 return self._cache.GetFromFile(self._manifest_path)[key] | 151 return self._cache.GetFromFile(self._features_path)[key] |
OLD | NEW |