Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(32)

Side by Side Diff: tools/json_schema_compiler/feature_compiler.py

Issue 2494653005: Support API aliases (Closed)
Patch Set: . Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 import re
10 11
11 from code import Code 12 from code import Code
12 import json_parse 13 import json_parse
13 14
14 # The template for the header file of the generated FeatureProvider. 15 # The template for the header file of the generated FeatureProvider.
15 HEADER_FILE_TEMPLATE = """ 16 HEADER_FILE_TEMPLATE = """
16 // Copyright %(year)s The Chromium Authors. All rights reserved. 17 // Copyright %(year)s The Chromium Authors. All rights reserved.
17 // Use of this source code is governed by a BSD-style license that can be 18 // Use of this source code is governed by a BSD-style license that can be
18 // found in the LICENSE file. 19 // found in the LICENSE file.
19 20
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
91 # version_info::Channel::STABLE to channel. The keys in this map 92 # version_info::Channel::STABLE to channel. The keys in this map
92 # also serve as a list all of possible values. 93 # also serve as a list all of possible values.
93 # 'allow_all': Only applicable for lists. If present, this will check for 94 # '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 95 # a value of "all" for a list value, and will replace it with
95 # the collection of all possible values. For instance, if a 96 # the collection of all possible values. For instance, if a
96 # feature specifies 'contexts': 'all', the generated C++ will 97 # feature specifies 'contexts': 'all', the generated C++ will
97 # assign the list of Feature::BLESSED_EXTENSION_CONTEXT, 98 # assign the list of Feature::BLESSED_EXTENSION_CONTEXT,
98 # Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not 99 # Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not
99 # specified, defaults to false. 100 # specified, defaults to false.
100 # 'values': A list of all possible allowed values for a given key. 101 # 'values': A list of all possible allowed values for a given key.
102 # 'shared': Boolean that, if set, ensures that only one of the associated
103 # features has the feature property set. Used primarily for complex
104 # features - for simple features, there is always at most one feature
105 # setting an option.
101 # If a type definition does not have any restrictions (beyond the type itself), 106 # If a type definition does not have any restrictions (beyond the type itself),
102 # an empty definition ({}) is used. 107 # an empty definition ({}) is used.
103 FEATURE_GRAMMAR = ( 108 FEATURE_GRAMMAR = (
104 { 109 {
110 'alias': {
111 unicode: {},
112 'shared': True
113 },
105 'blacklist': { 114 'blacklist': {
106 list: {'subtype': unicode} 115 list: {'subtype': unicode}
107 }, 116 },
108 'channel': { 117 'channel': {
109 unicode: { 118 unicode: {
110 'enum_map': { 119 'enum_map': {
111 'trunk': 'version_info::Channel::UNKNOWN', 120 'trunk': 'version_info::Channel::UNKNOWN',
112 'canary': 'version_info::Channel::CANARY', 121 'canary': 'version_info::Channel::CANARY',
113 'dev': 'version_info::Channel::DEV', 122 'dev': 'version_info::Channel::DEV',
114 'beta': 'version_info::Channel::BETA', 123 'beta': 'version_info::Channel::BETA',
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
190 } 199 }
191 }, 200 },
192 'session_types': { 201 'session_types': {
193 list: { 202 list: {
194 'enum_map': { 203 'enum_map': {
195 'regular': 'FeatureSessionType::REGULAR', 204 'regular': 'FeatureSessionType::REGULAR',
196 'kiosk': 'FeatureSessionType::KIOSK', 205 'kiosk': 'FeatureSessionType::KIOSK',
197 } 206 }
198 } 207 }
199 }, 208 },
209 'source': {
210 unicode: {},
211 'shared': True
212 },
200 'whitelist': { 213 'whitelist': {
201 list: {'subtype': unicode} 214 list: {'subtype': unicode}
202 }, 215 },
203 }) 216 })
204 217
205 FEATURE_CLASSES = ['APIFeature', 'BehaviorFeature', 218 FEATURE_CLASSES = ['APIFeature', 'BehaviorFeature',
206 'ManifestFeature', 'PermissionFeature'] 219 'ManifestFeature', 'PermissionFeature']
207 220
208 def HasProperty(property_name, value): 221 def HasProperty(property_name, value):
209 return property_name in value 222 return property_name in value
210 223
211 def HasAtLeastOneProperty(property_names, value): 224 def HasAtLeastOneProperty(property_names, value):
212 return any([HasProperty(name, value) for name in property_names]) 225 return any([HasProperty(name, value) for name in property_names])
213 226
214 def DoesNotHaveProperty(property_name, value): 227 def DoesNotHaveProperty(property_name, value):
215 return property_name not in value 228 return property_name not in value
216 229
230 def IsFeatureCrossReference(property_name, reverse_property_name, feature,
231 all_features):
232 feature_values = feature.GetAllFeatureValues()
Devlin 2016/11/23 15:56:13 This would be a lot more efficient as a GetValue()
tbarzic 2016/11/23 20:16:41 Done.
233 value = feature_values.get(property_name)
234 if not value:
235 return True
236 # String property values will be wrapped in "", strip those.
237 value_regex = re.compile('^"(.+)"$')
238 parsed_value = value_regex.match(value)
239 if not parsed_value:
240 return False
241
242 referenced_feature = all_features.get(parsed_value.group(1))
243 if not referenced_feature:
244 return False
245 referenced_feature_values = referenced_feature.GetAllFeatureValues()
246 reverse_reference_value = referenced_feature_values.get(reverse_property_name)
247 if not reverse_reference_value:
248 return False
249 # Don't validate reverse reference value for child features - chances are that
250 # the value was inherited from a feature parent, in which case it won't match
251 # current feature name.
252 if feature.has_parent:
253 return True
254 return reverse_reference_value == ('"%s"' % feature.name)
255
217 VALIDATION = ({ 256 VALIDATION = ({
218 'all': [ 257 'all': [
219 (partial(HasAtLeastOneProperty, ['channel', 'dependencies']), 258 (partial(HasAtLeastOneProperty, ['channel', 'dependencies']),
220 'Features must specify either a channel or dependencies'), 259 'Features must specify either a channel or dependencies'),
221 ], 260 ],
222 'APIFeature': [ 261 'APIFeature': [
223 (partial(HasProperty, 'contexts'), 262 (partial(HasProperty, 'contexts'),
224 'APIFeatures must specify at least one context') 263 'APIFeatures must specify at least one context')
225 ], 264 ],
226 'ManifestFeature': [ 265 'ManifestFeature': [
227 (partial(HasProperty, 'extension_types'), 266 (partial(HasProperty, 'extension_types'),
228 'ManifestFeatures must specify at least one extension type'), 267 'ManifestFeatures must specify at least one extension type'),
229 (partial(DoesNotHaveProperty, 'contexts'), 268 (partial(DoesNotHaveProperty, 'contexts'),
230 'ManifestFeatures do not support contexts.'), 269 'ManifestFeatures do not support contexts.'),
270 (partial(DoesNotHaveProperty, 'alias'),
271 'ManifestFeatures do not support alias.'),
272 (partial(DoesNotHaveProperty, 'source'),
273 'ManifestFeatures do not support source.'),
231 ], 274 ],
232 'BehaviorFeature': [], 275 'BehaviorFeature': [
276 (partial(DoesNotHaveProperty, 'alias'),
277 'BehaviorFeatures do not support alias.'),
278 (partial(DoesNotHaveProperty, 'source'),
279 'BehaviorFeatures do not support source.'),
280 ],
233 'PermissionFeature': [ 281 'PermissionFeature': [
234 (partial(HasProperty, 'extension_types'), 282 (partial(HasProperty, 'extension_types'),
235 'PermissionFeatures must specify at least one extension type'), 283 'PermissionFeatures must specify at least one extension type'),
236 (partial(DoesNotHaveProperty, 'contexts'), 284 (partial(DoesNotHaveProperty, 'contexts'),
237 'PermissionFeatures do not support contexts.'), 285 'PermissionFeatures do not support contexts.'),
286 (partial(DoesNotHaveProperty, 'alias'),
287 'PermissionFeatures do not support alias.'),
288 (partial(DoesNotHaveProperty, 'source'),
289 'PermissionFeatures do not support source.'),
238 ], 290 ],
239 }) 291 })
240 292
293 FINAL_VALIDATION = ({
294 'all': [],
295 'APIFeature': [
296 (partial(IsFeatureCrossReference, 'alias', 'source'),
297 'A feature alias property should reference a feature whose source '
298 'property references it back.'),
299 (partial(IsFeatureCrossReference, 'source', 'alias'),
300 'A feature source property should reference a feature whose alias '
301 'property references it back.')
302
303 ],
304 'ManifestFeature': [],
305 'BehaviorFeature': [],
306 'PermissionFeature': []
307 })
308
241 # These keys are used to find the parents of different features, but are not 309 # These keys are used to find the parents of different features, but are not
242 # compiled into the features themselves. 310 # compiled into the features themselves.
243 IGNORED_KEYS = ['default_parent'] 311 IGNORED_KEYS = ['default_parent']
244 312
245 # By default, if an error is encountered, assert to stop the compilation. This 313 # By default, if an error is encountered, assert to stop the compilation. This
246 # can be disabled for testing. 314 # can be disabled for testing.
247 ENABLE_ASSERTIONS = True 315 ENABLE_ASSERTIONS = True
248 316
249 # JSON parsing returns all strings of characters as unicode types. For testing, 317 # 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'' 318 # we can enable converting all string types to unicode to avoid writing u''
251 # everywhere. 319 # everywhere.
252 STRINGS_TO_UNICODE = False 320 STRINGS_TO_UNICODE = False
253 321
254 class Feature(object): 322 class Feature(object):
255 """A representation of a single simple feature that can handle all parsing, 323 """A representation of a single simple feature that can handle all parsing,
256 validation, and code generation. 324 validation, and code generation.
257 """ 325 """
258 def __init__(self, name): 326 def __init__(self, name):
259 self.name = name 327 self.name = name
260 self.has_parent = False 328 self.has_parent = False
261 self.errors = [] 329 self.errors = []
262 self.feature_values = {} 330 self.feature_values = {}
331 self.shared_values = {}
263 332
264 def _GetType(self, value): 333 def _GetType(self, value):
265 """Returns the type of the given value. This can be different than type() if 334 """Returns the type of the given value. This can be different than type() if
266 STRINGS_TO_UNICODE is enabled. 335 STRINGS_TO_UNICODE is enabled.
267 """ 336 """
268 t = type(value) 337 t = type(value)
269 if not STRINGS_TO_UNICODE: 338 if not STRINGS_TO_UNICODE:
270 return t 339 return t
271 if t is str: 340 if t is str:
272 return unicode 341 return unicode
273 return t 342 return t
274 343
275 def _AddError(self, error): 344 def AddError(self, error):
276 """Adds an error to the feature. If ENABLE_ASSERTIONS is active, this will 345 """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 346 also assert to stop the compilation process (since errors should never be
278 found in production). 347 found in production).
279 """ 348 """
280 self.errors.append(error) 349 self.errors.append(error)
281 if ENABLE_ASSERTIONS: 350 if ENABLE_ASSERTIONS:
282 assert False, error 351 assert False, error
283 352
284 def _AddKeyError(self, key, error): 353 def _AddKeyError(self, key, error):
285 """Adds an error relating to a particular key in the feature. 354 """Adds an error relating to a particular key in the feature.
286 """ 355 """
287 self._AddError('Error parsing feature "%s" at key "%s": %s' % 356 self.AddError('Error parsing feature "%s" at key "%s": %s' %
288 (self.name, key, error)) 357 (self.name, key, error))
289 358
290 def _GetCheckedValue(self, key, expected_type, expected_values, 359 def _GetCheckedValue(self, key, expected_type, expected_values,
291 enum_map, value): 360 enum_map, value):
292 """Returns a string to be used in the generated C++ code for a given key's 361 """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 362 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", 363 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. 364 and for an enum, this looks up the C++ definition in the enum map.
296 key: The key being parsed. 365 key: The key being parsed.
297 expected_type: The expected type for this value, or None if any type is 366 expected_type: The expected type for this value, or None if any type is
298 allowed. 367 allowed.
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
335 """ 404 """
336 if key not in value: 405 if key not in value:
337 return 406 return
338 v = value[key] 407 v = value[key]
339 408
340 is_all = False 409 is_all = False
341 if v == 'all' and list in grammar and 'allow_all' in grammar[list]: 410 if v == 'all' and list in grammar and 'allow_all' in grammar[list]:
342 v = [] 411 v = []
343 is_all = True 412 is_all = True
344 413
414 if 'shared' in grammar and key in self.shared_values:
415 self._AddKeyError(key, 'Key can be set at most once per feature.')
416 return
417
345 value_type = self._GetType(v) 418 value_type = self._GetType(v)
346 if value_type not in grammar: 419 if value_type not in grammar:
347 self._AddKeyError(key, 'Illegal value: "%s"' % v) 420 self._AddKeyError(key, 'Illegal value: "%s"' % v)
348 return 421 return
349 422
350 expected = grammar[value_type] 423 expected = grammar[value_type]
351 expected_values = None 424 expected_values = None
352 enum_map = None 425 enum_map = None
353 if 'values' in expected: 426 if 'values' in expected:
354 expected_values = expected['values'] 427 expected_values = expected['values']
(...skipping 19 matching lines...) Expand all
374 sub_value) 447 sub_value)
375 if cpp_sub_value: 448 if cpp_sub_value:
376 cpp_value.append(cpp_sub_value) 449 cpp_value.append(cpp_sub_value)
377 if cpp_value: 450 if cpp_value:
378 cpp_value = '{' + ','.join(cpp_value) + '}' 451 cpp_value = '{' + ','.join(cpp_value) + '}'
379 else: 452 else:
380 cpp_value = self._GetCheckedValue(key, expected_type, expected_values, 453 cpp_value = self._GetCheckedValue(key, expected_type, expected_values,
381 enum_map, v) 454 enum_map, v)
382 455
383 if cpp_value: 456 if cpp_value:
384 self.feature_values[key] = cpp_value 457 if 'shared' in grammar:
458 self.shared_values[key] = cpp_value
459 else:
460 self.feature_values[key] = cpp_value
385 elif key in self.feature_values: 461 elif key in self.feature_values:
386 # If the key is empty and this feature inherited a value from its parent, 462 # If the key is empty and this feature inherited a value from its parent,
387 # remove the inherited value. 463 # remove the inherited value.
388 del self.feature_values[key] 464 del self.feature_values[key]
389 465
390 def SetParent(self, parent): 466 def SetParent(self, parent):
391 """Sets the parent of this feature, and inherits all properties from that 467 """Sets the parent of this feature, and inherits all properties from that
392 parent. 468 parent.
393 """ 469 """
394 assert not self.feature_values, 'Parents must be set before parsing' 470 assert not self.feature_values, 'Parents must be set before parsing'
395 self.feature_values = copy.deepcopy(parent.feature_values) 471 self.feature_values = copy.deepcopy(parent.feature_values)
396 self.has_parent = True 472 self.has_parent = True
397 473
474 def SetSharedValues(self, values):
475 self.shared_values = values
476
398 def Parse(self, parsed_json): 477 def Parse(self, parsed_json):
399 """Parses the feature from the given json value.""" 478 """Parses the feature from the given json value."""
400 for key in parsed_json.keys(): 479 for key in parsed_json.keys():
401 if key not in FEATURE_GRAMMAR: 480 if key not in FEATURE_GRAMMAR:
402 self._AddKeyError(key, 'Unrecognized key') 481 self._AddKeyError(key, 'Unrecognized key')
403 for key, key_grammar in FEATURE_GRAMMAR.iteritems(): 482 for key, key_grammar in FEATURE_GRAMMAR.iteritems():
404 self._ParseKey(key, parsed_json, key_grammar) 483 self._ParseKey(key, parsed_json, key_grammar)
405 484
406 def Validate(self, feature_class): 485 def Validate(self, feature_class):
486 feature_values = self.GetAllFeatureValues()
407 for validator, error in (VALIDATION[feature_class] + VALIDATION['all']): 487 for validator, error in (VALIDATION[feature_class] + VALIDATION['all']):
408 if not validator(self.feature_values): 488 if not validator(feature_values):
409 self._AddError(error) 489 self.AddError(error)
490
491 def _GetCodeForFeatureValues(self, feature_values):
Devlin 2016/11/23 15:56:13 This should be either a static function or a funct
tbarzic 2016/11/23 20:16:41 Done.
492 """ Gets the Code object for setting feature values for this object. """
493 c = Code()
494 for key in sorted(feature_values.keys()):
495 if key in IGNORED_KEYS:
496 continue;
497 c.Append('feature->set_%s(%s);' % (key, feature_values[key]))
498 return c
410 499
411 def GetCode(self, feature_class): 500 def GetCode(self, feature_class):
412 """Returns the Code object for generating this feature.""" 501 """Returns the Code object for generating this feature."""
413 c = Code() 502 c = Code()
414 c.Append('%s* feature = new %s();' % (feature_class, feature_class)) 503 c.Append('%s* feature = new %s();' % (feature_class, feature_class))
415 c.Append('feature->set_name("%s");' % self.name) 504 c.Append('feature->set_name("%s");' % self.name)
416 for key in sorted(self.feature_values.keys()): 505 c.Concat(self._GetCodeForFeatureValues(self.GetAllFeatureValues()))
Devlin 2016/11/23 15:56:13 As this is written now, won't this result in each
417 if key in IGNORED_KEYS:
418 continue;
419 c.Append('feature->set_%s(%s);' % (key, self.feature_values[key]))
420 return c 506 return c
421 507
508 def AsParent(self):
509 """ Returns the feature values that should be inherited by children features
510 when this feature is set as parent.
511 """
512 return self
513
514 def GetAllFeatureValues(self):
515 """ Gets all values set for this feature. """
516 values = self.feature_values.copy()
517 values.update(self.shared_values)
518 return values
519
520 def GetErrors(self):
521 return self.errors;
522
523 class ComplexFeature(Feature):
524 """ Complex feature - feature that is comprised of list of features.
525 Overall complex feature is available if any of contained
526 feature is available.
527 """
528 def __init__(self, name):
529 Feature.__init__(self, name)
530 self.shared_values = {}
531 self.feature_list = []
532
533 def GetCode(self, feature_class):
534 c = Code()
535 c.Append('std::vector<Feature*> features;')
536 for f in self.feature_list:
537 c.Sblock('{')
538 c.Concat(f.GetCode(feature_class))
539 c.Append('features.push_back(feature);')
540 c.Eblock('}')
541 c.Append('ComplexFeature* feature(new ComplexFeature(&features));')
542 c.Append('feature->set_name("%s");' % self.name)
543 c.Concat(self._GetCodeForFeatureValues(self.GetAllFeatureValues()))
544 return c
545
546 def AsParent(self):
547 parent = None
548 for p in self.feature_list:
549 if 'default_parent' in p.feature_values:
550 parent = p
551 break
552 assert parent, 'No default parent found for %s' % self.name
553 return parent
554
555 def GetAllFeatureValues(self):
556 return self.shared_values.copy()
557
558 def GetErrors(self):
559 errors = copy.copy(self.errors)
560 for feature in self.feature_list:
561 if not errors:
Devlin 2016/11/23 15:56:13 When is this the case?
tbarzic 2016/11/23 20:16:41 when error is []. I initially wrote this assuming
562 errors = []
563 errors.extend(feature.GetErrors())
564 return errors
565
422 class FeatureCompiler(object): 566 class FeatureCompiler(object):
423 """A compiler to load, parse, and generate C++ code for a number of 567 """A compiler to load, parse, and generate C++ code for a number of
424 features.json files.""" 568 features.json files."""
425 def __init__(self, chrome_root, source_files, feature_class, 569 def __init__(self, chrome_root, source_files, feature_class,
426 provider_class, out_root, out_base_filename): 570 provider_class, out_root, out_base_filename):
427 # See __main__'s ArgumentParser for documentation on these properties. 571 # See __main__'s ArgumentParser for documentation on these properties.
428 self._chrome_root = chrome_root 572 self._chrome_root = chrome_root
429 self._source_files = source_files 573 self._source_files = source_files
430 self._feature_class = feature_class 574 self._feature_class = feature_class
431 self._provider_class = provider_class 575 self._provider_class = provider_class
432 self._out_root = out_root 576 self._out_root = out_root
433 self._out_base_filename = out_base_filename 577 self._out_base_filename = out_base_filename
434 578
435 # The json value for the feature files. 579 # The json value for the feature files.
436 self._json = {} 580 self._json = {}
437 # The parsed features. 581 # The parsed features.
438 self._features = {} 582 self._features = {}
439 583
440 def _Load(self): 584 def Load(self):
441 """Loads and parses the source from each input file and puts the result in 585 """Loads and parses the source from each input file and puts the result in
442 self._json.""" 586 self._json."""
443 for f in self._source_files: 587 for f in self._source_files:
444 abs_source_file = os.path.join(self._chrome_root, f) 588 abs_source_file = os.path.join(self._chrome_root, f)
445 try: 589 try:
446 with open(abs_source_file, 'r') as f: 590 with open(abs_source_file, 'r') as f:
447 f_json = json_parse.Parse(f.read()) 591 f_json = json_parse.Parse(f.read())
448 except: 592 except:
449 print('FAILED: Exception encountered while loading "%s"' % 593 print('FAILED: Exception encountered while loading "%s"' %
450 abs_source_file) 594 abs_source_file)
(...skipping 28 matching lines...) Expand all
479 parent_name = feature_name[:sep] 623 parent_name = feature_name[:sep]
480 624
481 if sep == -1: 625 if sep == -1:
482 # TODO(devlin): It'd be kind of nice to be able to assert that the 626 # 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 627 # 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 628 # 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. 629 # probably just noparent them so that we can assert this.
486 # raise KeyError('Could not find parent "%s" for feature "%s".' % 630 # raise KeyError('Could not find parent "%s" for feature "%s".' %
487 # (parent_name, feature_name)) 631 # (parent_name, feature_name))
488 return None 632 return None
489 parent_value = self._features[parent_name] 633 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 634
499 def _CompileFeature(self, feature_name, feature_value): 635 def _CompileFeature(self, feature_name, feature_value):
500 """Parses a single feature.""" 636 """Parses a single feature."""
501 if 'nocompile' in feature_value: 637 if 'nocompile' in feature_value:
502 assert feature_value['nocompile'], ( 638 assert feature_value['nocompile'], (
503 'nocompile should only be true; otherwise omit this key.') 639 'nocompile should only be true; otherwise omit this key.')
504 return 640 return
505 641
506 def parse_and_validate(name, value, parent): 642 def parse_and_validate(name, value, parent, shared_values):
507 try: 643 try:
508 feature = Feature(name) 644 feature = Feature(name)
509 if parent: 645 if parent:
510 feature.SetParent(parent) 646 feature.SetParent(parent)
647 feature.SetSharedValues(shared_values)
Devlin 2016/11/23 15:56:13 We shouldn't have to call this each time. Instead
tbarzic 2016/11/23 20:16:41 Sure.
511 feature.Parse(value) 648 feature.Parse(value)
512 feature.Validate(self._feature_class) 649 feature.Validate(self._feature_class)
513 return feature 650 return feature
514 except: 651 except:
515 print('Failure to parse feature "%s"' % feature_name) 652 print('Failure to parse feature "%s"' % feature_name)
516 raise 653 raise
517 654
518 parent = self._FindParent(feature_name, feature_value) 655 parent = self._FindParent(feature_name, feature_value)
656 shared_values = copy.deepcopy(parent.shared_values) if parent else {}
657
519 # Handle complex features, which are lists of simple features. 658 # Handle complex features, which are lists of simple features.
520 if type(feature_value) is list: 659 if type(feature_value) is list:
521 feature_list = [] 660 feature = ComplexFeature(feature_name)
661 feature.SetSharedValues(shared_values)
662
522 # This doesn't handle nested complex features. I think that's probably for 663 # This doesn't handle nested complex features. I think that's probably for
523 # the best. 664 # the best.
524 for v in feature_value: 665 for v in feature_value:
525 feature_list.append(parse_and_validate(feature_name, v, parent)) 666 feature.feature_list.append(
526 self._features[feature_name] = feature_list 667 parse_and_validate(feature_name, v, parent, feature.shared_values))
527 return 668 self._features[feature_name] = feature
669 else:
670 self._features[feature_name] = parse_and_validate(
671 feature_name, feature_value, parent, shared_values)
528 672
529 self._features[feature_name] = parse_and_validate( 673 def _FinalValidation(self):
530 feature_name, feature_value, parent) 674 validators = FINAL_VALIDATION['all'] + FINAL_VALIDATION[self._feature_class]
675 for name, feature in self._features.items():
676 for validator, error in validators:
677 if not validator(feature, self._features):
678 feature.AddError(error)
531 679
532 def Compile(self): 680 def Compile(self):
533 """Parses all features after loading the input files.""" 681 """Parses all features after loading the input files."""
534 self._Load();
535 # Iterate over in sorted order so that parents come first. 682 # Iterate over in sorted order so that parents come first.
536 for k in sorted(self._json.keys()): 683 for k in sorted(self._json.keys()):
537 self._CompileFeature(k, self._json[k]) 684 self._CompileFeature(k, self._json[k])
685 self._FinalValidation()
538 686
539 def Render(self): 687 def Render(self):
540 """Returns the Code object for the body of the .cc file, which handles the 688 """Returns the Code object for the body of the .cc file, which handles the
541 initialization of all features.""" 689 initialization of all features."""
542 c = Code() 690 c = Code()
543 c.Append('%s::%s() {' % (self._provider_class, self._provider_class)) 691 c.Append('%s::%s() {' % (self._provider_class, self._provider_class))
544 c.Sblock() 692 c.Sblock()
545 for k in sorted(self._features.keys()): 693 for k in sorted(self._features.keys()):
546 c.Sblock('{') 694 c.Sblock('{')
547 feature = self._features[k] 695 feature = self._features[k]
548 if type(feature) is list: 696 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) 697 c.Append('AddFeature("%s", feature);' % k)
560 c.Eblock('}') 698 c.Eblock('}')
561 c.Eblock('}') 699 c.Eblock('}')
562 return c 700 return c
563 701
564 def Write(self): 702 def Write(self):
565 """Writes the output.""" 703 """Writes the output."""
566 header_file_path = self._out_base_filename + '.h' 704 header_file_path = self._out_base_filename + '.h'
567 cc_file_path = self._out_base_filename + '.cc' 705 cc_file_path = self._out_base_filename + '.cc'
568 substitutions = ({ 706 substitutions = ({
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
609 'out_base_filename', type=str, 747 'out_base_filename', type=str,
610 help='The base filename for the C++ files (.h and .cc will be appended)') 748 help='The base filename for the C++ files (.h and .cc will be appended)')
611 parser.add_argument('source_files', type=str, nargs='+', 749 parser.add_argument('source_files', type=str, nargs='+',
612 help='The source features.json files') 750 help='The source features.json files')
613 args = parser.parse_args() 751 args = parser.parse_args()
614 if args.feature_class not in FEATURE_CLASSES: 752 if args.feature_class not in FEATURE_CLASSES:
615 raise NameError('Unknown feature class: %s' % args.feature_class) 753 raise NameError('Unknown feature class: %s' % args.feature_class)
616 c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class, 754 c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class,
617 args.provider_class, args.out_root, 755 args.provider_class, args.out_root,
618 args.out_base_filename) 756 args.out_base_filename)
757 c.Load()
619 c.Compile() 758 c.Compile()
620 c.Write() 759 c.Write()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698