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.
6 Utility functions for working with the Feature abstraction. Features are grouped
7 into a dictionary by name. Each Feature is guaranteed to have the following two
9 name - a string, the name of the feature
10 platform - a list containing 'apps' or 'extensions', both, or neither.
12 A Feature may have other keys from a _features.json file as well. Features with
13 a whitelist are ignored as they are only useful to specific apps or extensions.
16 from copy
import deepcopy
18 def _GetPlatformsForExtensionTypes(extension_types
):
20 if extension_types
== 'all' or 'platform_app' in extension_types
:
21 platforms
.append('apps')
22 if extension_types
== 'all' or 'extension' in extension_types
:
23 platforms
.append('extensions')
26 def Parse(features_json
):
27 '''Process JSON from a _features.json file, standardizing it into a dictionary
32 def ignore_feature(name
, value
):
33 '''Returns true if this feature should be ignored. This is defined by the
34 presence of a 'whitelist' property for non-private APIs. Private APIs
35 shouldn't have whitelisted features ignored since they're inherently
36 private. Logic elsewhere makes sure not to list private APIs.
38 return 'whitelist' in value
and not name
.endswith('Private')
40 for name
, value
in deepcopy(features_json
).iteritems():
41 # Some feature names correspond to a list, typically because they're
42 # whitelisted in stable for certain extensions and available in dev for
43 # everybody else. Force a list down to a single feature by attempting to
44 # remove the entries that don't affect the typical usage of an API.
45 if isinstance(value
, list):
46 available_values
= [subvalue
for subvalue
in value
47 if not ignore_feature(name
, subvalue
)]
48 if len(available_values
) == 0:
49 logging
.warning('No available values for feature "%s"' % name
)
51 elif len(available_values
) == 1:
52 value
= available_values
[0]
54 # Multiple available values probably implies different feature
55 # configurations for apps vs extensions. Currently, this is 'commands'.
56 # To get the ball rolling, add a hack to combine the extension types.
57 # See http://crbug.com/316194.
58 extension_types
= set()
59 for value
in available_values
:
60 extension_types
.update(value
['extension_types'])
61 value
= [subvalue
for subvalue
in available_values
62 if subvalue
['channel'] == 'stable'][0]
63 value
['extension_types'] = list(extension_types
)
65 if ignore_feature(name
, value
):
68 features
[name
] = { 'platforms': [] }
70 extension_types
= value
.pop('extension_types', None)
71 if extension_types
is not None:
72 features
[name
]['platforms'] = _GetPlatformsForExtensionTypes(
75 features
[name
]['name'] = name
76 features
[name
].update(value
)
80 def Filtered(features
, platform
=None):
81 '''Create a new Features dictionary from |features| that contains only items
82 relevant to |platform|. Items retained are deepcopied. Returns new features
85 filtered_features
= {}
87 for name
, feature
in features
.iteritems():
88 if not platform
or platform
in feature
['platforms']:
89 filtered_features
[name
] = deepcopy(feature
)
91 return filtered_features
93 def MergedWith(features
, other
):
94 '''Merge |features| with an additional dictionary to create a new features
95 dictionary. If a feature is common to both |features| and |other|, then it is
96 merged using the standard dictionary update instead of being overwritten.
97 Returns the new Features dictionary.
99 for key
, value
in other
.iteritems():
101 features
[key
].update(value
)
103 features
[key
] = value
105 # Ensure the Feature schema is enforced for all added items.
106 if not 'name' in features
[key
]:
107 features
[key
]['name'] = key
108 if not 'platforms' in features
[key
]:
109 features
[key
]['platforms'] = []