| OLD | NEW | 
|    1 # Copyright 2016 The Chromium Authors. All rights reserved. |    1 # Copyright 2016 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 import argparse |    5 import argparse | 
|    6 import copy |    6 import copy | 
|    7 from datetime import datetime |    7 from datetime import datetime | 
|    8 from functools import partial |    8 from functools import partial | 
|    9 import os |    9 import os | 
|   10  |   10  | 
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|   91 #               version_info::Channel::STABLE to channel. The keys in this map |   91 #               version_info::Channel::STABLE to channel. The keys in this map | 
|   92 #               also serve as a list all of possible values. |   92 #               also serve as a list all of possible values. | 
|   93 #   'allow_all': Only applicable for lists. If present, this will check for |   93 #   'allow_all': Only applicable for lists. If present, this will check for | 
|   94 #                a value of "all" for a list value, and will replace it with |   94 #                a value of "all" for a list value, and will replace it with | 
|   95 #                the collection of all possible values. For instance, if a |   95 #                the collection of all possible values. For instance, if a | 
|   96 #                feature specifies 'contexts': 'all', the generated C++ will |   96 #                feature specifies 'contexts': 'all', the generated C++ will | 
|   97 #                assign the list of Feature::BLESSED_EXTENSION_CONTEXT, |   97 #                assign the list of Feature::BLESSED_EXTENSION_CONTEXT, | 
|   98 #                Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not |   98 #                Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not | 
|   99 #                specified, defaults to false. |   99 #                specified, defaults to false. | 
|  100 #   'values': A list of all possible allowed values for a given key. |  100 #   'values': A list of all possible allowed values for a given key. | 
 |  101 #   'shared': Boolean that, if set, ensures that only one of the associated | 
 |  102 #       features has the feature property set. Used primarily for complex | 
 |  103 #       features - for simple features, there is always at most one feature | 
 |  104 #       setting an option. | 
 |  105 #   'feature_reference': Only applicable for strings. An object that, if | 
 |  106 #       provided, verifies that the value is set to a name of another feature | 
 |  107 #       defined in the same features file. It can following options: | 
 |  108 #           'reverse_reference': String that should be equal to another feature | 
 |  109 #                option. If set, it verifies that the feature referenced by | 
 |  110 #                |feature_reference| value references this feature using | 
 |  111 #                |reverse_reference| option. | 
|  101 # If a type definition does not have any restrictions (beyond the type itself), |  112 # If a type definition does not have any restrictions (beyond the type itself), | 
|  102 # an empty definition ({}) is used. |  113 # an empty definition ({}) is used. | 
|  103 FEATURE_GRAMMAR = ( |  114 FEATURE_GRAMMAR = ( | 
|  104   { |  115   { | 
 |  116     'alias': { | 
 |  117       unicode: {}, | 
 |  118       'feature_reference': { | 
 |  119         'reverse_reference': 'source' | 
 |  120       }, | 
 |  121       'shared': True | 
 |  122     }, | 
|  105     'blacklist': { |  123     'blacklist': { | 
|  106       list: {'subtype': unicode} |  124       list: {'subtype': unicode} | 
|  107     }, |  125     }, | 
|  108     'channel': { |  126     'channel': { | 
|  109       unicode: { |  127       unicode: { | 
|  110         'enum_map': { |  128         'enum_map': { | 
|  111           'trunk': 'version_info::Channel::UNKNOWN', |  129           'trunk': 'version_info::Channel::UNKNOWN', | 
|  112           'canary': 'version_info::Channel::CANARY', |  130           'canary': 'version_info::Channel::CANARY', | 
|  113           'dev': 'version_info::Channel::DEV', |  131           'dev': 'version_info::Channel::DEV', | 
|  114           'beta': 'version_info::Channel::BETA', |  132           'beta': 'version_info::Channel::BETA', | 
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  190       } |  208       } | 
|  191     }, |  209     }, | 
|  192     'session_types': { |  210     'session_types': { | 
|  193       list: { |  211       list: { | 
|  194         'enum_map': { |  212         'enum_map': { | 
|  195           'regular': 'FeatureSessionType::REGULAR', |  213           'regular': 'FeatureSessionType::REGULAR', | 
|  196           'kiosk': 'FeatureSessionType::KIOSK', |  214           'kiosk': 'FeatureSessionType::KIOSK', | 
|  197         } |  215         } | 
|  198       } |  216       } | 
|  199     }, |  217     }, | 
 |  218     'source': { | 
 |  219       unicode: {}, | 
 |  220       'feature_reference': { | 
 |  221         'reverse_reference': 'alias' | 
 |  222       }, | 
 |  223       'shared': True | 
 |  224     }, | 
|  200     'whitelist': { |  225     'whitelist': { | 
|  201       list: {'subtype': unicode} |  226       list: {'subtype': unicode} | 
|  202     }, |  227     }, | 
|  203   }) |  228   }) | 
|  204  |  229  | 
|  205 FEATURE_CLASSES = ['APIFeature', 'BehaviorFeature', |  230 FEATURE_CLASSES = ['APIFeature', 'BehaviorFeature', | 
|  206                    'ManifestFeature', 'PermissionFeature'] |  231                    'ManifestFeature', 'PermissionFeature'] | 
|  207  |  232  | 
|  208 def HasProperty(property_name, value): |  233 def HasProperty(property_name, value): | 
|  209   return property_name in value |  234   return property_name in value | 
| (...skipping 11 matching lines...) Expand all  Loading... | 
|  221   ], |  246   ], | 
|  222   'APIFeature': [ |  247   'APIFeature': [ | 
|  223     (partial(HasProperty, 'contexts'), |  248     (partial(HasProperty, 'contexts'), | 
|  224      'APIFeatures must specify at least one context') |  249      'APIFeatures must specify at least one context') | 
|  225   ], |  250   ], | 
|  226   'ManifestFeature': [ |  251   'ManifestFeature': [ | 
|  227     (partial(HasProperty, 'extension_types'), |  252     (partial(HasProperty, 'extension_types'), | 
|  228      'ManifestFeatures must specify at least one extension type'), |  253      'ManifestFeatures must specify at least one extension type'), | 
|  229     (partial(DoesNotHaveProperty, 'contexts'), |  254     (partial(DoesNotHaveProperty, 'contexts'), | 
|  230      'ManifestFeatures do not support contexts.'), |  255      'ManifestFeatures do not support contexts.'), | 
 |  256     (partial(DoesNotHaveProperty, 'alias'), | 
 |  257      'ManifestFeatures do not support alias.'), | 
 |  258     (partial(DoesNotHaveProperty, 'source'), | 
 |  259      'ManifestFeatures do not support source.'), | 
|  231   ], |  260   ], | 
|  232   'BehaviorFeature': [], |  261   'BehaviorFeature': [ | 
 |  262     (partial(DoesNotHaveProperty, 'alias'), | 
 |  263      'BehaviorFeatures do not support alias.'), | 
 |  264     (partial(DoesNotHaveProperty, 'source'), | 
 |  265      'BehaviorFeatures do not support source.'), | 
 |  266    ], | 
|  233   'PermissionFeature': [ |  267   'PermissionFeature': [ | 
|  234     (partial(HasProperty, 'extension_types'), |  268     (partial(HasProperty, 'extension_types'), | 
|  235      'PermissionFeatures must specify at least one extension type'), |  269      'PermissionFeatures must specify at least one extension type'), | 
|  236     (partial(DoesNotHaveProperty, 'contexts'), |  270     (partial(DoesNotHaveProperty, 'contexts'), | 
|  237      'PermissionFeatures do not support contexts.'), |  271      'PermissionFeatures do not support contexts.'), | 
 |  272     (partial(DoesNotHaveProperty, 'alias'), | 
 |  273      'PermissionFeatures do not support alias.'), | 
 |  274     (partial(DoesNotHaveProperty, 'source'), | 
 |  275      'PermissionFeatures do not support source.'), | 
|  238   ], |  276   ], | 
|  239 }) |  277 }) | 
|  240  |  278  | 
|  241 # These keys are used to find the parents of different features, but are not |  279 # These keys are used to find the parents of different features, but are not | 
|  242 # compiled into the features themselves. |  280 # compiled into the features themselves. | 
|  243 IGNORED_KEYS = ['default_parent'] |  281 IGNORED_KEYS = ['default_parent'] | 
|  244  |  282  | 
|  245 # By default, if an error is encountered, assert to stop the compilation. This |  283 # By default, if an error is encountered, assert to stop the compilation. This | 
|  246 # can be disabled for testing. |  284 # can be disabled for testing. | 
|  247 ENABLE_ASSERTIONS = True |  285 ENABLE_ASSERTIONS = True | 
|  248  |  286  | 
|  249 # JSON parsing returns all strings of characters as unicode types. For testing, |  287 # JSON parsing returns all strings of characters as unicode types. For testing, | 
|  250 # we can enable converting all string types to unicode to avoid writing u'' |  288 # we can enable converting all string types to unicode to avoid writing u'' | 
|  251 # everywhere. |  289 # everywhere. | 
|  252 STRINGS_TO_UNICODE = False |  290 STRINGS_TO_UNICODE = False | 
|  253  |  291  | 
|  254 class Feature(object): |  292 class Feature(object): | 
|  255   """A representation of a single simple feature that can handle all parsing, |  293   """A representation of a single simple feature that can handle all parsing, | 
|  256   validation, and code generation. |  294   validation, and code generation. | 
|  257   """ |  295   """ | 
|  258   def __init__(self, name): |  296   def __init__(self, name): | 
|  259     self.name = name |  297     self.name = name | 
|  260     self.has_parent = False |  298     self.has_parent = False | 
|  261     self.errors = [] |  299     self.errors = [] | 
|  262     self.feature_values = {} |  300     self.feature_values = {} | 
 |  301     self.shared_values = {} | 
 |  302     self.feature_references = [] | 
|  263  |  303  | 
|  264   def _GetType(self, value): |  304   def _GetType(self, value): | 
|  265     """Returns the type of the given value. This can be different than type() if |  305     """Returns the type of the given value. This can be different than type() if | 
|  266     STRINGS_TO_UNICODE is enabled. |  306     STRINGS_TO_UNICODE is enabled. | 
|  267     """ |  307     """ | 
|  268     t = type(value) |  308     t = type(value) | 
|  269     if not STRINGS_TO_UNICODE: |  309     if not STRINGS_TO_UNICODE: | 
|  270       return t |  310       return t | 
|  271     if t is str: |  311     if t is str: | 
|  272       return unicode |  312       return unicode | 
|  273     return t |  313     return t | 
|  274  |  314  | 
|  275   def _AddError(self, error): |  315   def _AddError(self, error): | 
|  276     """Adds an error to the feature. If ENABLE_ASSERTIONS is active, this will |  316     """Adds an error to the feature. If ENABLE_ASSERTIONS is active, this will | 
|  277     also assert to stop the compilation process (since errors should never be |  317     also assert to stop the compilation process (since errors should never be | 
|  278     found in production). |  318     found in production). | 
|  279     """ |  319     """ | 
|  280     self.errors.append(error) |  320     self.errors.append(error) | 
|  281     if ENABLE_ASSERTIONS: |  321     if ENABLE_ASSERTIONS: | 
|  282       assert False, error |  322       assert False, error | 
|  283  |  323  | 
|  284   def _AddKeyError(self, key, error): |  324   def _AddKeyError(self, key, error): | 
|  285     """Adds an error relating to a particular key in the feature. |  325     """Adds an error relating to a particular key in the feature. | 
|  286     """ |  326     """ | 
|  287     self._AddError('Error parsing feature "%s" at key "%s": %s' % |  327     self._AddError('Error parsing feature "%s" at key "%s": %s' % | 
|  288                        (self.name, key, error)) |  328                        (self.name, key, error)) | 
|  289  |  329  | 
|  290   def _GetCheckedValue(self, key, expected_type, expected_values, |  330   def _GetCheckedValue(self, key, expected_type, expected_values, | 
|  291                        enum_map, value): |  331                        enum_map, feature_reference, value): | 
|  292     """Returns a string to be used in the generated C++ code for a given key's |  332     """Returns a string to be used in the generated C++ code for a given key's | 
|  293     python value, or None if the value is invalid. For example, if the python |  333     python value, or None if the value is invalid. For example, if the python | 
|  294     value is True, this returns 'true', for a string foo, this returns "foo", |  334     value is True, this returns 'true', for a string foo, this returns "foo", | 
|  295     and for an enum, this looks up the C++ definition in the enum map. |  335     and for an enum, this looks up the C++ definition in the enum map. | 
|  296       key: The key being parsed. |  336       key: The key being parsed. | 
|  297       expected_type: The expected type for this value, or None if any type is |  337       expected_type: The expected type for this value, or None if any type is | 
|  298                      allowed. |  338                      allowed. | 
|  299       expected_values: The list of allowed values for this value, or None if any |  339       expected_values: The list of allowed values for this value, or None if any | 
|  300                        value is allowed. |  340                        value is allowed. | 
|  301       enum_map: The map from python value -> cpp value for all allowed values, |  341       enum_map: The map from python value -> cpp value for all allowed values, | 
|  302                or None if no special mapping should be made. |  342                or None if no special mapping should be made. | 
 |  343       feature_reference: Object set if the value should represent a feature | 
 |  344           reference. If set, the value is expected to be string referencing | 
 |  345           another feature in the feature file. While it will be checked that the | 
 |  346           value is string, validation that the value references an existing | 
 |  347           feature will have to be postponed until all features are parsed - the | 
 |  348           info needed to verify the key value will be cached in | 
 |  349           |self.feature_references|. | 
 |  350           The object may have "reverse_reference" property set - in that case | 
 |  351           the referenced feature should reference this feature by | 
 |  352           "reverse_reference" property. | 
|  303       value: The value to check. |  353       value: The value to check. | 
|  304     """ |  354     """ | 
|  305     valid = True |  355     valid = True | 
|  306     if expected_values and value not in expected_values: |  356     if expected_values and value not in expected_values: | 
|  307       self._AddKeyError(key, 'Illegal value: "%s"' % value) |  357       self._AddKeyError(key, 'Illegal value: "%s"' % value) | 
|  308       valid = False |  358       valid = False | 
|  309  |  359  | 
|  310     t = self._GetType(value) |  360     t = self._GetType(value) | 
|  311     if expected_type and t is not expected_type: |  361     if expected_type and t is not expected_type: | 
|  312       self._AddKeyError(key, 'Illegal value: "%s"' % value) |  362       self._AddKeyError(key, 'Illegal value: "%s"' % value) | 
|  313       valid = False |  363       valid = False | 
|  314  |  364  | 
 |  365     if feature_reference and not t in [str, unicode]: | 
 |  366       self._AddKeyError(key, 'Illegal value for feature reference %s' % value) | 
 |  367       valid = False | 
 |  368  | 
|  315     if not valid: |  369     if not valid: | 
|  316       return None |  370       return None | 
|  317  |  371  | 
|  318     if enum_map: |  372     if enum_map: | 
|  319       return enum_map[value] |  373       return enum_map[value] | 
|  320  |  374  | 
|  321     if t in [str, unicode]: |  375     if t in [str, unicode]: | 
 |  376       if feature_reference: | 
 |  377         self.feature_references.append({ | 
 |  378           'value': str(value), | 
 |  379           'key': key, | 
 |  380           'reverse_reference': feature_reference.get('reverse_reference')}) | 
|  322       return '"%s"' % str(value) |  381       return '"%s"' % str(value) | 
|  323     if t is int: |  382     if t is int: | 
|  324       return str(value) |  383       return str(value) | 
|  325     if t is bool: |  384     if t is bool: | 
|  326       return 'true' if value else 'false' |  385       return 'true' if value else 'false' | 
|  327     assert False, 'Unsupported type: %s' % value |  386     assert False, 'Unsupported type: %s' % value | 
|  328  |  387  | 
|  329   def _ParseKey(self, key, value, grammar): |  388   def _ParseKey(self, key, value, grammar): | 
|  330     """Parses the specific key according to the grammar rule for that key if it |  389     """Parses the specific key according to the grammar rule for that key if it | 
|  331     is present in the json value. |  390     is present in the json value. | 
|  332       key: The key to parse. |  391       key: The key to parse. | 
|  333       value: The full value for this feature. |  392       value: The full value for this feature. | 
|  334       grammar: The rule for the specific key. |  393       grammar: The rule for the specific key. | 
|  335     """ |  394     """ | 
|  336     if key not in value: |  395     if key not in value: | 
|  337       return |  396       return | 
|  338     v = value[key] |  397     v = value[key] | 
|  339  |  398  | 
|  340     is_all = False |  399     is_all = False | 
|  341     if v == 'all' and list in grammar and 'allow_all' in grammar[list]: |  400     if v == 'all' and list in grammar and 'allow_all' in grammar[list]: | 
|  342       v = [] |  401       v = [] | 
|  343       is_all = True |  402       is_all = True | 
|  344  |  403  | 
 |  404     if 'shared' in grammar and key in self.shared_values: | 
 |  405       self._AddKeyError(key, 'Key can be set at most once per feature.') | 
 |  406       return | 
 |  407  | 
|  345     value_type = self._GetType(v) |  408     value_type = self._GetType(v) | 
|  346     if value_type not in grammar: |  409     if value_type not in grammar: | 
|  347       self._AddKeyError(key, 'Illegal value: "%s"' % v) |  410       self._AddKeyError(key, 'Illegal value: "%s"' % v) | 
|  348       return |  411       return | 
|  349  |  412  | 
|  350     expected = grammar[value_type] |  413     expected = grammar[value_type] | 
|  351     expected_values = None |  414     expected_values = None | 
|  352     enum_map = None |  415     enum_map = None | 
|  353     if 'values' in expected: |  416     if 'values' in expected: | 
|  354       expected_values = expected['values'] |  417       expected_values = expected['values'] | 
|  355     elif 'enum_map' in expected: |  418     elif 'enum_map' in expected: | 
|  356       enum_map = expected['enum_map'] |  419       enum_map = expected['enum_map'] | 
|  357       expected_values = enum_map.keys() |  420       expected_values = enum_map.keys() | 
|  358  |  421  | 
|  359     if is_all: |  422     if is_all: | 
|  360       v = copy.deepcopy(expected_values) |  423       v = copy.deepcopy(expected_values) | 
|  361  |  424  | 
|  362     expected_type = None |  425     expected_type = None | 
|  363     if value_type is list and 'subtype' in expected: |  426     if value_type is list and 'subtype' in expected: | 
|  364       expected_type = expected['subtype'] |  427       expected_type = expected['subtype'] | 
|  365  |  428  | 
 |  429     feature_reference = None | 
 |  430     if 'feature_reference' in grammar: | 
 |  431       feature_reference = {} | 
 |  432       feature_reference['reverse_reference'] = grammar['feature_reference'].get( | 
 |  433           'reverse_reference') | 
 |  434  | 
|  366     cpp_value = None |  435     cpp_value = None | 
|  367     # If this value is a list, iterate over each entry and validate. Otherwise, |  436     # If this value is a list, iterate over each entry and validate. Otherwise, | 
|  368     # validate the single value. |  437     # validate the single value. | 
|  369     if value_type is list: |  438     if value_type is list: | 
|  370       cpp_value = [] |  439       cpp_value = [] | 
|  371       for sub_value in v: |  440       for sub_value in v: | 
|  372         cpp_sub_value = self._GetCheckedValue(key, expected_type, |  441         cpp_sub_value = self._GetCheckedValue(key, expected_type, | 
|  373                                               expected_values, enum_map, |  442                                               expected_values, enum_map, | 
 |  443                                               feature_reference, | 
|  374                                               sub_value) |  444                                               sub_value) | 
|  375         if cpp_sub_value: |  445         if cpp_sub_value: | 
|  376           cpp_value.append(cpp_sub_value) |  446           cpp_value.append(cpp_sub_value) | 
|  377       if cpp_value: |  447       if cpp_value: | 
|  378         cpp_value = '{' + ','.join(cpp_value) + '}' |  448         cpp_value = '{' + ','.join(cpp_value) + '}' | 
|  379     else: |  449     else: | 
|  380       cpp_value = self._GetCheckedValue(key, expected_type, expected_values, |  450       cpp_value = self._GetCheckedValue(key, expected_type, expected_values, | 
|  381                                         enum_map, v) |  451                                         enum_map, feature_reference, v) | 
|  382  |  452  | 
|  383     if cpp_value: |  453     if cpp_value: | 
|  384       self.feature_values[key] = cpp_value |  454       if 'shared' in grammar: | 
 |  455         self.shared_values[key] = cpp_value | 
 |  456       else: | 
 |  457         self.feature_values[key] = cpp_value | 
|  385     elif key in self.feature_values: |  458     elif key in self.feature_values: | 
|  386       # If the key is empty and this feature inherited a value from its parent, |  459       # If the key is empty and this feature inherited a value from its parent, | 
|  387       # remove the inherited value. |  460       # remove the inherited value. | 
|  388       del self.feature_values[key] |  461       del self.feature_values[key] | 
|  389  |  462  | 
|  390   def SetParent(self, parent): |  463   def SetParent(self, parent): | 
|  391     """Sets the parent of this feature, and inherits all properties from that |  464     """Sets the parent of this feature, and inherits all properties from that | 
|  392     parent. |  465     parent. | 
|  393     """ |  466     """ | 
|  394     assert not self.feature_values, 'Parents must be set before parsing' |  467     assert not self.feature_values, 'Parents must be set before parsing' | 
|  395     self.feature_values = copy.deepcopy(parent.feature_values) |  468     self.feature_values = copy.deepcopy(parent.feature_values) | 
|  396     self.has_parent = True |  469     self.has_parent = True | 
|  397  |  470  | 
 |  471   def SetSharedValues(self, values): | 
 |  472     self.shared_values = values | 
 |  473  | 
|  398   def Parse(self, parsed_json): |  474   def Parse(self, parsed_json): | 
|  399     """Parses the feature from the given json value.""" |  475     """Parses the feature from the given json value.""" | 
|  400     for key in parsed_json.keys(): |  476     for key in parsed_json.keys(): | 
|  401       if key not in FEATURE_GRAMMAR: |  477       if key not in FEATURE_GRAMMAR: | 
|  402         self._AddKeyError(key, 'Unrecognized key') |  478         self._AddKeyError(key, 'Unrecognized key') | 
|  403     for key, key_grammar in FEATURE_GRAMMAR.iteritems(): |  479     for key, key_grammar in FEATURE_GRAMMAR.iteritems(): | 
|  404       self._ParseKey(key, parsed_json, key_grammar) |  480       self._ParseKey(key, parsed_json, key_grammar) | 
|  405  |  481  | 
|  406   def Validate(self, feature_class): |  482   def Validate(self, feature_class): | 
 |  483     feature_values = self.GetAllFeatureValues() | 
|  407     for validator, error in (VALIDATION[feature_class] + VALIDATION['all']): |  484     for validator, error in (VALIDATION[feature_class] + VALIDATION['all']): | 
|  408       if not validator(self.feature_values): |  485       if not validator(feature_values): | 
|  409         self._AddError(error) |  486         self._AddError(error) | 
|  410  |  487  | 
 |  488   def _GetCodeForFeatureValues(self, feature_values): | 
 |  489     """ Gets the Code object for setting feature values for this object. """ | 
 |  490     c = Code() | 
 |  491     for key in sorted(feature_values.keys()): | 
 |  492       if key in IGNORED_KEYS: | 
 |  493         continue; | 
 |  494       c.Append('feature->set_%s(%s);' % (key, feature_values[key])) | 
 |  495     return c | 
 |  496  | 
|  411   def GetCode(self, feature_class): |  497   def GetCode(self, feature_class): | 
|  412     """Returns the Code object for generating this feature.""" |  498     """Returns the Code object for generating this feature.""" | 
|  413     c = Code() |  499     c = Code() | 
|  414     c.Append('%s* feature = new %s();' % (feature_class, feature_class)) |  500     c.Append('%s* feature = new %s();' % (feature_class, feature_class)) | 
|  415     c.Append('feature->set_name("%s");' % self.name) |  501     c.Append('feature->set_name("%s");' % self.name) | 
|  416     for key in sorted(self.feature_values.keys()): |  502     c.Concat(self._GetCodeForFeatureValues(self.GetAllFeatureValues())) | 
|  417       if key in IGNORED_KEYS: |  | 
|  418         continue; |  | 
|  419       c.Append('feature->set_%s(%s);' % (key, self.feature_values[key])) |  | 
|  420     return c |  503     return c | 
|  421  |  504  | 
 |  505   def AsParent(self): | 
 |  506     """ Returns the feature values that should be inherited by children features | 
 |  507     when this feature is set as parent. | 
 |  508     """ | 
 |  509     return self | 
 |  510  | 
 |  511   def GetAllFeatureValues(self): | 
 |  512     """ Gets all values set for this feature. """ | 
 |  513     values = self.feature_values.copy() | 
 |  514     values.update(self.shared_values) | 
 |  515     return values | 
 |  516  | 
 |  517   def ValidateFeatureReferences(self, features): | 
 |  518     """ Validates feature values that reference another feature - the validation | 
 |  519     cannot be done when individual features are compiled (like it is done for | 
 |  520     other value types), as whole feature set is not know at that point. | 
 |  521     """ | 
 |  522     for ref in self.feature_references: | 
 |  523       # Validate that a feature referenced by this feature exists. | 
 |  524       if not ref['value'] in features: | 
 |  525         self._AddKeyError(ref['key'], '%s is not a feature.' % ref['value']) | 
 |  526         return | 
 |  527  | 
 |  528       # Verify that referenced feature references this feature, if reverse | 
 |  529       # reference is required. | 
 |  530       reverse_ref = ref['reverse_reference'] | 
 |  531       if reverse_ref: | 
 |  532         referenced_feature_values = features[ref['value']].GetAllFeatureValues() | 
 |  533         reverse_ref_value = referenced_feature_values.get(reverse_ref) | 
 |  534         if (reverse_ref_value != ('"%s"' % self.name)): | 
 |  535           self._AddKeyError(ref['key'], | 
 |  536               'Referenced feature ("%s") should have "%s" set to "%s".' % | 
 |  537                   (ref['value'], reverse_ref, self.name)) | 
 |  538  | 
 |  539   def GetErrors(self): | 
 |  540     return self.errors; | 
 |  541  | 
 |  542 class ComplexFeature(Feature): | 
 |  543   """ Complex feature - feature that is comprised of list of features. | 
 |  544   Overall complex feature is available if any of contained | 
 |  545   feature is available. | 
 |  546   """ | 
 |  547   def __init__(self, name, shared_values): | 
 |  548     Feature.__init__(self, name) | 
 |  549     self.shared_values = shared_values | 
 |  550     self.feature_list = [] | 
 |  551  | 
 |  552   def GetCode(self, feature_class): | 
 |  553     c = Code() | 
 |  554     c.Append('std::vector<Feature*> features;') | 
 |  555     for f in self.feature_list: | 
 |  556       c.Sblock('{') | 
 |  557       c.Concat(f.GetCode(feature_class)) | 
 |  558       c.Append('features.push_back(feature);') | 
 |  559       c.Eblock('}') | 
 |  560     c.Append('ComplexFeature* feature(new ComplexFeature(&features));') | 
 |  561     c.Append('feature->set_name("%s");' % self.name) | 
 |  562     c.Concat(self._GetCodeForFeatureValues(self.GetAllFeatureValues())) | 
 |  563     return c | 
 |  564  | 
 |  565   def AsParent(self): | 
 |  566     for p in self.feature_list: | 
 |  567       if 'default_parent' in p.feature_values: | 
 |  568         parent = p | 
 |  569         break | 
 |  570     assert parent, 'No default parent found for %s' % self.name | 
 |  571     return parent | 
 |  572  | 
 |  573   def ValidateFeatureReferences(self, available_features): | 
 |  574     for feature in self.feature_list: | 
 |  575       feature.ValidateFeatureReferences(available_features) | 
 |  576  | 
 |  577   def GetAllFeatureValues(self): | 
 |  578     return self.shared_values.copy() | 
 |  579  | 
 |  580   def GetErrors(self): | 
 |  581     errors = None | 
 |  582     for feature in self.feature_list: | 
 |  583       if not errors: | 
 |  584         errors = [] | 
 |  585       errors.extend(feature.GetErrors()) | 
 |  586     return errors | 
 |  587  | 
|  422 class FeatureCompiler(object): |  588 class FeatureCompiler(object): | 
|  423   """A compiler to load, parse, and generate C++ code for a number of |  589   """A compiler to load, parse, and generate C++ code for a number of | 
|  424   features.json files.""" |  590   features.json files.""" | 
|  425   def __init__(self, chrome_root, source_files, feature_class, |  591   def __init__(self, chrome_root, source_files, feature_class, | 
|  426                provider_class, out_root, out_base_filename): |  592                provider_class, out_root, out_base_filename): | 
|  427     # See __main__'s ArgumentParser for documentation on these properties. |  593     # See __main__'s ArgumentParser for documentation on these properties. | 
|  428     self._chrome_root = chrome_root |  594     self._chrome_root = chrome_root | 
|  429     self._source_files = source_files |  595     self._source_files = source_files | 
|  430     self._feature_class = feature_class |  596     self._feature_class = feature_class | 
|  431     self._provider_class = provider_class |  597     self._provider_class = provider_class | 
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  479       parent_name = feature_name[:sep] |  645       parent_name = feature_name[:sep] | 
|  480  |  646  | 
|  481     if sep == -1: |  647     if sep == -1: | 
|  482       # TODO(devlin): It'd be kind of nice to be able to assert that the |  648       # TODO(devlin): It'd be kind of nice to be able to assert that the | 
|  483       # deduced parent name is in our features, but some dotted features don't |  649       # deduced parent name is in our features, but some dotted features don't | 
|  484       # have parents and also don't have noparent, e.g. system.cpu. We should |  650       # have parents and also don't have noparent, e.g. system.cpu. We should | 
|  485       # probably just noparent them so that we can assert this. |  651       # probably just noparent them so that we can assert this. | 
|  486       #   raise KeyError('Could not find parent "%s" for feature "%s".' % |  652       #   raise KeyError('Could not find parent "%s" for feature "%s".' % | 
|  487       #                      (parent_name, feature_name)) |  653       #                      (parent_name, feature_name)) | 
|  488       return None |  654       return None | 
|  489     parent_value = self._features[parent_name] |  655     return self._features[parent_name].AsParent() | 
|  490     parent = parent_value |  | 
|  491     if type(parent_value) is list: |  | 
|  492       for p in parent_value: |  | 
|  493         if 'default_parent' in p.feature_values: |  | 
|  494           parent = p |  | 
|  495           break |  | 
|  496       assert parent, 'No default parent found for %s' % parent_name |  | 
|  497     return parent |  | 
|  498  |  656  | 
|  499   def _CompileFeature(self, feature_name, feature_value): |  657   def _CompileFeature(self, feature_name, feature_value): | 
|  500     """Parses a single feature.""" |  658     """Parses a single feature.""" | 
|  501     if 'nocompile' in feature_value: |  659     if 'nocompile' in feature_value: | 
|  502       assert feature_value['nocompile'], ( |  660       assert feature_value['nocompile'], ( | 
|  503           'nocompile should only be true; otherwise omit this key.') |  661           'nocompile should only be true; otherwise omit this key.') | 
|  504       return |  662       return | 
|  505  |  663  | 
|  506     def parse_and_validate(name, value, parent): |  664     def parse_and_validate(name, value, parent, shared_values): | 
|  507       try: |  665       try: | 
|  508         feature = Feature(name) |  666         feature = Feature(name) | 
|  509         if parent: |  667         if parent: | 
|  510           feature.SetParent(parent) |  668           feature.SetParent(parent) | 
 |  669         feature.SetSharedValues(shared_values) | 
|  511         feature.Parse(value) |  670         feature.Parse(value) | 
|  512         feature.Validate(self._feature_class) |  671         feature.Validate(self._feature_class) | 
|  513         return feature |  672         return feature | 
|  514       except: |  673       except: | 
|  515         print('Failure to parse feature "%s"' % feature_name) |  674         print('Failure to parse feature "%s"' % feature_name) | 
|  516         raise |  675         raise | 
|  517  |  676  | 
|  518     parent = self._FindParent(feature_name, feature_value) |  677     parent = self._FindParent(feature_name, feature_value) | 
 |  678     shared_values = copy.deepcopy(parent.shared_values) if parent else {} | 
 |  679  | 
|  519     # Handle complex features, which are lists of simple features. |  680     # Handle complex features, which are lists of simple features. | 
|  520     if type(feature_value) is list: |  681     if type(feature_value) is list: | 
|  521       feature_list = [] |  682       feature = ComplexFeature(feature_name, shared_values) | 
 |  683  | 
|  522       # This doesn't handle nested complex features. I think that's probably for |  684       # This doesn't handle nested complex features. I think that's probably for | 
|  523       # the best. |  685       # the best. | 
|  524       for v in feature_value: |  686       for v in feature_value: | 
|  525         feature_list.append(parse_and_validate(feature_name, v, parent)) |  687         feature.feature_list.append( | 
|  526       self._features[feature_name] = feature_list |  688             parse_and_validate(feature_name, v, parent, feature.shared_values)) | 
|  527       return |  689       self._features[feature_name] = feature | 
|  528  |  690     else: | 
|  529     self._features[feature_name] = parse_and_validate( |  691       self._features[feature_name] = parse_and_validate( | 
|  530                                        feature_name, feature_value, parent) |  692           feature_name, feature_value, parent, shared_values) | 
|  531  |  693  | 
|  532   def Compile(self): |  694   def Compile(self): | 
|  533     """Parses all features after loading the input files.""" |  695     """Parses all features after loading the input files.""" | 
|  534     self._Load(); |  696     self._Load(); | 
|  535     # Iterate over in sorted order so that parents come first. |  697     # Iterate over in sorted order so that parents come first. | 
|  536     for k in sorted(self._json.keys()): |  698     for k in sorted(self._json.keys()): | 
|  537       self._CompileFeature(k, self._json[k]) |  699       self._CompileFeature(k, self._json[k]) | 
 |  700     # Validate values that reference feature | 
 |  701     for key in self._features: | 
 |  702       self._features[key].ValidateFeatureReferences(self._features) | 
|  538  |  703  | 
|  539   def Render(self): |  704   def Render(self): | 
|  540     """Returns the Code object for the body of the .cc file, which handles the |  705     """Returns the Code object for the body of the .cc file, which handles the | 
|  541     initialization of all features.""" |  706     initialization of all features.""" | 
|  542     c = Code() |  707     c = Code() | 
|  543     c.Append('%s::%s() {' % (self._provider_class, self._provider_class)) |  708     c.Append('%s::%s() {' % (self._provider_class, self._provider_class)) | 
|  544     c.Sblock() |  709     c.Sblock() | 
|  545     for k in sorted(self._features.keys()): |  710     for k in sorted(self._features.keys()): | 
|  546       c.Sblock('{') |  711       c.Sblock('{') | 
|  547       feature = self._features[k] |  712       feature = self._features[k] | 
|  548       if type(feature) is list: |  713       c.Concat(feature.GetCode(self._feature_class)) | 
|  549         c.Append('std::vector<Feature*> features;') |  | 
|  550         for f in feature: |  | 
|  551           c.Sblock('{') |  | 
|  552           c.Concat(f.GetCode(self._feature_class)) |  | 
|  553           c.Append('features.push_back(feature);') |  | 
|  554           c.Eblock('}') |  | 
|  555         c.Append('ComplexFeature* feature(new ComplexFeature(&features));') |  | 
|  556         c.Append('feature->set_name("%s");' % k) |  | 
|  557       else: |  | 
|  558         c.Concat(feature.GetCode(self._feature_class)) |  | 
|  559       c.Append('AddFeature("%s", feature);' % k) |  714       c.Append('AddFeature("%s", feature);' % k) | 
|  560       c.Eblock('}') |  715       c.Eblock('}') | 
|  561     c.Eblock('}') |  716     c.Eblock('}') | 
|  562     return c |  717     return c | 
|  563  |  718  | 
|  564   def Write(self): |  719   def Write(self): | 
|  565     """Writes the output.""" |  720     """Writes the output.""" | 
|  566     header_file_path = self._out_base_filename + '.h' |  721     header_file_path = self._out_base_filename + '.h' | 
|  567     cc_file_path = self._out_base_filename + '.cc' |  722     cc_file_path = self._out_base_filename + '.cc' | 
|  568     substitutions = ({ |  723     substitutions = ({ | 
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  611   parser.add_argument('source_files', type=str, nargs='+', |  766   parser.add_argument('source_files', type=str, nargs='+', | 
|  612                       help='The source features.json files') |  767                       help='The source features.json files') | 
|  613   args = parser.parse_args() |  768   args = parser.parse_args() | 
|  614   if args.feature_class not in FEATURE_CLASSES: |  769   if args.feature_class not in FEATURE_CLASSES: | 
|  615     raise NameError('Unknown feature class: %s' % args.feature_class) |  770     raise NameError('Unknown feature class: %s' % args.feature_class) | 
|  616   c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class, |  771   c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class, | 
|  617                       args.provider_class, args.out_root, |  772                       args.provider_class, args.out_root, | 
|  618                       args.out_base_filename) |  773                       args.out_base_filename) | 
|  619   c.Compile() |  774   c.Compile() | 
|  620   c.Write() |  775   c.Write() | 
| OLD | NEW |