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

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

Issue 2494653005: Support API aliases (Closed)
Patch Set: . Created 4 years 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
227 def DoesNotHaveAllProperties(property_names, value):
228 return not all([HasProperty(name, value) for name in property_names])
229
214 def DoesNotHaveProperty(property_name, value): 230 def DoesNotHaveProperty(property_name, value):
215 return property_name not in value 231 return property_name not in value
216 232
233 def IsFeatureCrossReference(property_name, reverse_property_name, feature,
234 all_features):
235 """ Verifies that |property_name| on |feature| references a feature that
236 references |feature| back using |reverse_property_name| property.
237 |property_name| and |reverse_property_name| are expected to have string
238 values.
239 """
240 value = feature.GetValue(property_name)
241 if not value:
242 return True
243 # String property values will be wrapped in "", strip those.
244 value_regex = re.compile('^"(.+)"$')
245 parsed_value = value_regex.match(value)
246 assert parsed_value, (
247 'IsFeatureCrossReference should only be used on unicode properties')
248
249 referenced_feature = all_features.get(parsed_value.group(1))
250 if not referenced_feature:
251 return False
252 reverse_reference_value = referenced_feature.GetValue(reverse_property_name)
253 if not reverse_reference_value:
254 return False
255 # Don't validate reverse reference value for child features - chances are that
256 # the value was inherited from a feature parent, in which case it won't match
257 # current feature name.
258 if feature.has_parent:
259 return True
260 return reverse_reference_value == ('"%s"' % feature.name)
261
217 VALIDATION = ({ 262 VALIDATION = ({
218 'all': [ 263 'all': [
219 (partial(HasAtLeastOneProperty, ['channel', 'dependencies']), 264 (partial(HasAtLeastOneProperty, ['channel', 'dependencies']),
220 'Features must specify either a channel or dependencies'), 265 'Features must specify either a channel or dependencies'),
221 ], 266 ],
222 'APIFeature': [ 267 'APIFeature': [
223 (partial(HasProperty, 'contexts'), 268 (partial(HasProperty, 'contexts'),
224 'APIFeatures must specify at least one context') 269 'APIFeatures must specify at least one context'),
270 (partial(DoesNotHaveAllProperties, ['alias', 'source']),
271 'Features cannot specify both alias and source.')
225 ], 272 ],
226 'ManifestFeature': [ 273 'ManifestFeature': [
227 (partial(HasProperty, 'extension_types'), 274 (partial(HasProperty, 'extension_types'),
228 'ManifestFeatures must specify at least one extension type'), 275 'ManifestFeatures must specify at least one extension type'),
229 (partial(DoesNotHaveProperty, 'contexts'), 276 (partial(DoesNotHaveProperty, 'contexts'),
230 'ManifestFeatures do not support contexts.'), 277 'ManifestFeatures do not support contexts.'),
278 (partial(DoesNotHaveProperty, 'alias'),
279 'ManifestFeatures do not support alias.'),
280 (partial(DoesNotHaveProperty, 'source'),
281 'ManifestFeatures do not support source.'),
231 ], 282 ],
232 'BehaviorFeature': [], 283 'BehaviorFeature': [
284 (partial(DoesNotHaveProperty, 'alias'),
285 'BehaviorFeatures do not support alias.'),
286 (partial(DoesNotHaveProperty, 'source'),
287 'BehaviorFeatures do not support source.'),
288 ],
233 'PermissionFeature': [ 289 'PermissionFeature': [
234 (partial(HasProperty, 'extension_types'), 290 (partial(HasProperty, 'extension_types'),
235 'PermissionFeatures must specify at least one extension type'), 291 'PermissionFeatures must specify at least one extension type'),
236 (partial(DoesNotHaveProperty, 'contexts'), 292 (partial(DoesNotHaveProperty, 'contexts'),
237 'PermissionFeatures do not support contexts.'), 293 'PermissionFeatures do not support contexts.'),
294 (partial(DoesNotHaveProperty, 'alias'),
295 'PermissionFeatures do not support alias.'),
296 (partial(DoesNotHaveProperty, 'source'),
297 'PermissionFeatures do not support source.'),
238 ], 298 ],
239 }) 299 })
240 300
301 FINAL_VALIDATION = ({
302 'all': [],
303 'APIFeature': [
304 (partial(IsFeatureCrossReference, 'alias', 'source'),
305 'A feature alias property should reference a feature whose source '
306 'property references it back.'),
307 (partial(IsFeatureCrossReference, 'source', 'alias'),
308 'A feature source property should reference a feature whose alias '
309 'property references it back.')
310
311 ],
312 'ManifestFeature': [],
313 'BehaviorFeature': [],
314 'PermissionFeature': []
315 })
316
241 # These keys are used to find the parents of different features, but are not 317 # These keys are used to find the parents of different features, but are not
242 # compiled into the features themselves. 318 # compiled into the features themselves.
243 IGNORED_KEYS = ['default_parent'] 319 IGNORED_KEYS = ['default_parent']
244 320
245 # By default, if an error is encountered, assert to stop the compilation. This 321 # By default, if an error is encountered, assert to stop the compilation. This
246 # can be disabled for testing. 322 # can be disabled for testing.
247 ENABLE_ASSERTIONS = True 323 ENABLE_ASSERTIONS = True
248 324
249 # JSON parsing returns all strings of characters as unicode types. For testing, 325 # 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'' 326 # we can enable converting all string types to unicode to avoid writing u''
251 # everywhere. 327 # everywhere.
252 STRINGS_TO_UNICODE = False 328 STRINGS_TO_UNICODE = False
253 329
330 def GetCodeForFeatureValues(feature_values):
331 """ Gets the Code object for setting feature values for this object. """
332 c = Code()
333 for key in sorted(feature_values.keys()):
334 if key in IGNORED_KEYS:
335 continue;
336 c.Append('feature->set_%s(%s);' % (key, feature_values[key]))
337 return c
338
254 class Feature(object): 339 class Feature(object):
255 """A representation of a single simple feature that can handle all parsing, 340 """A representation of a single simple feature that can handle all parsing,
256 validation, and code generation. 341 validation, and code generation.
257 """ 342 """
258 def __init__(self, name): 343 def __init__(self, name):
259 self.name = name 344 self.name = name
260 self.has_parent = False 345 self.has_parent = False
261 self.errors = [] 346 self.errors = []
262 self.feature_values = {} 347 self.feature_values = {}
348 self.shared_values = {}
263 349
264 def _GetType(self, value): 350 def _GetType(self, value):
265 """Returns the type of the given value. This can be different than type() if 351 """Returns the type of the given value. This can be different than type() if
266 STRINGS_TO_UNICODE is enabled. 352 STRINGS_TO_UNICODE is enabled.
267 """ 353 """
268 t = type(value) 354 t = type(value)
269 if not STRINGS_TO_UNICODE: 355 if not STRINGS_TO_UNICODE:
270 return t 356 return t
271 if t is str: 357 if t is str:
272 return unicode 358 return unicode
273 return t 359 return t
274 360
275 def _AddError(self, error): 361 def AddError(self, error):
276 """Adds an error to the feature. If ENABLE_ASSERTIONS is active, this will 362 """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 363 also assert to stop the compilation process (since errors should never be
278 found in production). 364 found in production).
279 """ 365 """
280 self.errors.append(error) 366 self.errors.append(error)
281 if ENABLE_ASSERTIONS: 367 if ENABLE_ASSERTIONS:
282 assert False, error 368 assert False, error
283 369
284 def _AddKeyError(self, key, error): 370 def _AddKeyError(self, key, error):
285 """Adds an error relating to a particular key in the feature. 371 """Adds an error relating to a particular key in the feature.
286 """ 372 """
287 self._AddError('Error parsing feature "%s" at key "%s": %s' % 373 self.AddError('Error parsing feature "%s" at key "%s": %s' %
288 (self.name, key, error)) 374 (self.name, key, error))
289 375
290 def _GetCheckedValue(self, key, expected_type, expected_values, 376 def _GetCheckedValue(self, key, expected_type, expected_values,
291 enum_map, value): 377 enum_map, value):
292 """Returns a string to be used in the generated C++ code for a given key's 378 """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 379 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", 380 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. 381 and for an enum, this looks up the C++ definition in the enum map.
296 key: The key being parsed. 382 key: The key being parsed.
297 expected_type: The expected type for this value, or None if any type is 383 expected_type: The expected type for this value, or None if any type is
298 allowed. 384 allowed.
(...skipping 20 matching lines...) Expand all
319 return enum_map[value] 405 return enum_map[value]
320 406
321 if t in [str, unicode]: 407 if t in [str, unicode]:
322 return '"%s"' % str(value) 408 return '"%s"' % str(value)
323 if t is int: 409 if t is int:
324 return str(value) 410 return str(value)
325 if t is bool: 411 if t is bool:
326 return 'true' if value else 'false' 412 return 'true' if value else 'false'
327 assert False, 'Unsupported type: %s' % value 413 assert False, 'Unsupported type: %s' % value
328 414
329 def _ParseKey(self, key, value, grammar): 415 def _ParseKey(self, key, value, shared_values, grammar):
330 """Parses the specific key according to the grammar rule for that key if it 416 """Parses the specific key according to the grammar rule for that key if it
331 is present in the json value. 417 is present in the json value.
332 key: The key to parse. 418 key: The key to parse.
333 value: The full value for this feature. 419 value: The full value for this feature.
420 shared_values: Set of shared vfalues associated with this feature.
334 grammar: The rule for the specific key. 421 grammar: The rule for the specific key.
335 """ 422 """
336 if key not in value: 423 if key not in value:
337 return 424 return
338 v = value[key] 425 v = value[key]
339 426
340 is_all = False 427 is_all = False
341 if v == 'all' and list in grammar and 'allow_all' in grammar[list]: 428 if v == 'all' and list in grammar and 'allow_all' in grammar[list]:
342 v = [] 429 v = []
343 is_all = True 430 is_all = True
344 431
432 if 'shared' in grammar and key in shared_values:
433 self._AddKeyError(key, 'Key can be set at most once per feature.')
434 return
435
345 value_type = self._GetType(v) 436 value_type = self._GetType(v)
346 if value_type not in grammar: 437 if value_type not in grammar:
347 self._AddKeyError(key, 'Illegal value: "%s"' % v) 438 self._AddKeyError(key, 'Illegal value: "%s"' % v)
348 return 439 return
349 440
350 expected = grammar[value_type] 441 expected = grammar[value_type]
351 expected_values = None 442 expected_values = None
352 enum_map = None 443 enum_map = None
353 if 'values' in expected: 444 if 'values' in expected:
354 expected_values = expected['values'] 445 expected_values = expected['values']
(...skipping 19 matching lines...) Expand all
374 sub_value) 465 sub_value)
375 if cpp_sub_value: 466 if cpp_sub_value:
376 cpp_value.append(cpp_sub_value) 467 cpp_value.append(cpp_sub_value)
377 if cpp_value: 468 if cpp_value:
378 cpp_value = '{' + ','.join(cpp_value) + '}' 469 cpp_value = '{' + ','.join(cpp_value) + '}'
379 else: 470 else:
380 cpp_value = self._GetCheckedValue(key, expected_type, expected_values, 471 cpp_value = self._GetCheckedValue(key, expected_type, expected_values,
381 enum_map, v) 472 enum_map, v)
382 473
383 if cpp_value: 474 if cpp_value:
384 self.feature_values[key] = cpp_value 475 if 'shared' in grammar:
476 shared_values[key] = cpp_value
477 else:
478 self.feature_values[key] = cpp_value
385 elif key in self.feature_values: 479 elif key in self.feature_values:
386 # If the key is empty and this feature inherited a value from its parent, 480 # If the key is empty and this feature inherited a value from its parent,
387 # remove the inherited value. 481 # remove the inherited value.
388 del self.feature_values[key] 482 del self.feature_values[key]
389 483
390 def SetParent(self, parent): 484 def SetParent(self, parent):
391 """Sets the parent of this feature, and inherits all properties from that 485 """Sets the parent of this feature, and inherits all properties from that
392 parent. 486 parent.
393 """ 487 """
394 assert not self.feature_values, 'Parents must be set before parsing' 488 assert not self.feature_values, 'Parents must be set before parsing'
395 self.feature_values = copy.deepcopy(parent.feature_values) 489 self.feature_values = copy.deepcopy(parent.feature_values)
396 self.has_parent = True 490 self.has_parent = True
397 491
398 def Parse(self, parsed_json): 492 def SetSharedValues(self, values):
493 self.shared_values = values
494
495 def Parse(self, parsed_json, shared_values):
399 """Parses the feature from the given json value.""" 496 """Parses the feature from the given json value."""
400 for key in parsed_json.keys(): 497 for key in parsed_json.keys():
401 if key not in FEATURE_GRAMMAR: 498 if key not in FEATURE_GRAMMAR:
402 self._AddKeyError(key, 'Unrecognized key') 499 self._AddKeyError(key, 'Unrecognized key')
403 for key, key_grammar in FEATURE_GRAMMAR.iteritems(): 500 for key, key_grammar in FEATURE_GRAMMAR.iteritems():
404 self._ParseKey(key, parsed_json, key_grammar) 501 self._ParseKey(key, parsed_json, shared_values, key_grammar)
405 502
406 def Validate(self, feature_class): 503 def Validate(self, feature_class, shared_values):
504 feature_values = self.feature_values.copy()
505 feature_values.update(shared_values)
407 for validator, error in (VALIDATION[feature_class] + VALIDATION['all']): 506 for validator, error in (VALIDATION[feature_class] + VALIDATION['all']):
408 if not validator(self.feature_values): 507 if not validator(feature_values):
409 self._AddError(error) 508 self.AddError(error)
410 509
411 def GetCode(self, feature_class): 510 def GetCode(self, feature_class):
412 """Returns the Code object for generating this feature.""" 511 """Returns the Code object for generating this feature."""
413 c = Code() 512 c = Code()
414 c.Append('%s* feature = new %s();' % (feature_class, feature_class)) 513 c.Append('%s* feature = new %s();' % (feature_class, feature_class))
415 c.Append('feature->set_name("%s");' % self.name) 514 c.Append('feature->set_name("%s");' % self.name)
416 for key in sorted(self.feature_values.keys()): 515 c.Concat(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 516 return c
421 517
518 def AsParent(self):
519 """ Returns the feature values that should be inherited by children features
520 when this feature is set as parent.
521 """
522 return self
523
524 def GetValue(self, key):
525 """ Gets feature value for the specified key """
526 value = self.feature_values.get(key)
527 return value if value else self.shared_values.get(key)
528
529 def GetAllFeatureValues(self):
530 """ Gets all values set for this feature. """
531 values = self.feature_values.copy()
532 values.update(self.shared_values)
533 return values
534
535 def GetErrors(self):
536 return self.errors;
537
538 class ComplexFeature(Feature):
539 """ Complex feature - feature that is comprised of list of features.
540 Overall complex feature is available if any of contained
541 feature is available.
542 """
543 def __init__(self, name):
544 Feature.__init__(self, name)
545 self.feature_list = []
546
547 def GetCode(self, feature_class):
548 c = Code()
549 c.Append('std::vector<Feature*> features;')
550 for f in self.feature_list:
551 # Sanity check that components of complex features have no shared values
552 # set.
553 assert not f.shared_values
554 c.Sblock('{')
555 c.Concat(f.GetCode(feature_class))
556 c.Append('features.push_back(feature);')
557 c.Eblock('}')
558 c.Append('ComplexFeature* feature(new ComplexFeature(&features));')
559 c.Append('feature->set_name("%s");' % self.name)
560 c.Concat(GetCodeForFeatureValues(self.shared_values))
561 return c
562
563 def AsParent(self):
564 parent = None
565 for p in self.feature_list:
566 if 'default_parent' in p.feature_values:
567 parent = p
568 break
569 assert parent, 'No default parent found for %s' % self.name
570 return parent
571
572 def GetErrors(self):
573 errors = copy.copy(self.errors)
574 for feature in self.feature_list:
575 errors.extend(feature.GetErrors())
576 return errors
577
422 class FeatureCompiler(object): 578 class FeatureCompiler(object):
423 """A compiler to load, parse, and generate C++ code for a number of 579 """A compiler to load, parse, and generate C++ code for a number of
424 features.json files.""" 580 features.json files."""
425 def __init__(self, chrome_root, source_files, feature_class, 581 def __init__(self, chrome_root, source_files, feature_class,
426 provider_class, out_root, out_base_filename): 582 provider_class, out_root, out_base_filename):
427 # See __main__'s ArgumentParser for documentation on these properties. 583 # See __main__'s ArgumentParser for documentation on these properties.
428 self._chrome_root = chrome_root 584 self._chrome_root = chrome_root
429 self._source_files = source_files 585 self._source_files = source_files
430 self._feature_class = feature_class 586 self._feature_class = feature_class
431 self._provider_class = provider_class 587 self._provider_class = provider_class
432 self._out_root = out_root 588 self._out_root = out_root
433 self._out_base_filename = out_base_filename 589 self._out_base_filename = out_base_filename
434 590
435 # The json value for the feature files. 591 # The json value for the feature files.
436 self._json = {} 592 self._json = {}
437 # The parsed features. 593 # The parsed features.
438 self._features = {} 594 self._features = {}
439 595
440 def _Load(self): 596 def Load(self):
441 """Loads and parses the source from each input file and puts the result in 597 """Loads and parses the source from each input file and puts the result in
442 self._json.""" 598 self._json."""
443 for f in self._source_files: 599 for f in self._source_files:
444 abs_source_file = os.path.join(self._chrome_root, f) 600 abs_source_file = os.path.join(self._chrome_root, f)
445 try: 601 try:
446 with open(abs_source_file, 'r') as f: 602 with open(abs_source_file, 'r') as f:
447 f_json = json_parse.Parse(f.read()) 603 f_json = json_parse.Parse(f.read())
448 except: 604 except:
449 print('FAILED: Exception encountered while loading "%s"' % 605 print('FAILED: Exception encountered while loading "%s"' %
450 abs_source_file) 606 abs_source_file)
(...skipping 28 matching lines...) Expand all
479 parent_name = feature_name[:sep] 635 parent_name = feature_name[:sep]
480 636
481 if sep == -1: 637 if sep == -1:
482 # TODO(devlin): It'd be kind of nice to be able to assert that the 638 # 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 639 # 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 640 # 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. 641 # probably just noparent them so that we can assert this.
486 # raise KeyError('Could not find parent "%s" for feature "%s".' % 642 # raise KeyError('Could not find parent "%s" for feature "%s".' %
487 # (parent_name, feature_name)) 643 # (parent_name, feature_name))
488 return None 644 return None
489 parent_value = self._features[parent_name] 645 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 646
499 def _CompileFeature(self, feature_name, feature_value): 647 def _CompileFeature(self, feature_name, feature_value):
500 """Parses a single feature.""" 648 """Parses a single feature."""
501 if 'nocompile' in feature_value: 649 if 'nocompile' in feature_value:
502 assert feature_value['nocompile'], ( 650 assert feature_value['nocompile'], (
503 'nocompile should only be true; otherwise omit this key.') 651 'nocompile should only be true; otherwise omit this key.')
504 return 652 return
505 653
506 def parse_and_validate(name, value, parent): 654 def parse_and_validate(name, value, parent, shared_values):
507 try: 655 try:
508 feature = Feature(name) 656 feature = Feature(name)
509 if parent: 657 if parent:
510 feature.SetParent(parent) 658 feature.SetParent(parent)
511 feature.Parse(value) 659 feature.Parse(value, shared_values)
512 feature.Validate(self._feature_class) 660 feature.Validate(self._feature_class, shared_values)
513 return feature 661 return feature
514 except: 662 except:
515 print('Failure to parse feature "%s"' % feature_name) 663 print('Failure to parse feature "%s"' % feature_name)
516 raise 664 raise
517 665
518 parent = self._FindParent(feature_name, feature_value) 666 parent = self._FindParent(feature_name, feature_value)
667 shared_values = {}
668
519 # Handle complex features, which are lists of simple features. 669 # Handle complex features, which are lists of simple features.
520 if type(feature_value) is list: 670 if type(feature_value) is list:
521 feature_list = [] 671 feature = ComplexFeature(feature_name)
672
522 # This doesn't handle nested complex features. I think that's probably for 673 # This doesn't handle nested complex features. I think that's probably for
523 # the best. 674 # the best.
524 for v in feature_value: 675 for v in feature_value:
525 feature_list.append(parse_and_validate(feature_name, v, parent)) 676 feature.feature_list.append(
526 self._features[feature_name] = feature_list 677 parse_and_validate(feature_name, v, parent, shared_values))
527 return 678 self._features[feature_name] = feature
679 else:
680 self._features[feature_name] = parse_and_validate(
681 feature_name, feature_value, parent, shared_values)
528 682
529 self._features[feature_name] = parse_and_validate( 683 # Apply parent shared values at the end to enable child features to
530 feature_name, feature_value, parent) 684 # override parent shared value - if parent shared values are added to
685 # shared value set before a child feature is parsed, the child feature
686 # overriding shared values set by its parent would cause an error due to
687 # shared values being set twice.
688 final_shared_values = copy.deepcopy(parent.shared_values) if parent else {}
689 final_shared_values.update(shared_values)
690 self._features[feature_name].SetSharedValues(final_shared_values)
691
692 def _FinalValidation(self):
693 validators = FINAL_VALIDATION['all'] + FINAL_VALIDATION[self._feature_class]
694 for name, feature in self._features.items():
695 for validator, error in validators:
696 if not validator(feature, self._features):
697 feature.AddError(error)
531 698
532 def Compile(self): 699 def Compile(self):
533 """Parses all features after loading the input files.""" 700 """Parses all features after loading the input files."""
534 self._Load();
535 # Iterate over in sorted order so that parents come first. 701 # Iterate over in sorted order so that parents come first.
536 for k in sorted(self._json.keys()): 702 for k in sorted(self._json.keys()):
537 self._CompileFeature(k, self._json[k]) 703 self._CompileFeature(k, self._json[k])
704 self._FinalValidation()
538 705
539 def Render(self): 706 def Render(self):
540 """Returns the Code object for the body of the .cc file, which handles the 707 """Returns the Code object for the body of the .cc file, which handles the
541 initialization of all features.""" 708 initialization of all features."""
542 c = Code() 709 c = Code()
543 c.Append('%s::%s() {' % (self._provider_class, self._provider_class)) 710 c.Append('%s::%s() {' % (self._provider_class, self._provider_class))
544 c.Sblock() 711 c.Sblock()
545 for k in sorted(self._features.keys()): 712 for k in sorted(self._features.keys()):
546 c.Sblock('{') 713 c.Sblock('{')
547 feature = self._features[k] 714 feature = self._features[k]
548 if type(feature) is list: 715 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) 716 c.Append('AddFeature("%s", feature);' % k)
560 c.Eblock('}') 717 c.Eblock('}')
561 c.Eblock('}') 718 c.Eblock('}')
562 return c 719 return c
563 720
564 def Write(self): 721 def Write(self):
565 """Writes the output.""" 722 """Writes the output."""
566 header_file_path = self._out_base_filename + '.h' 723 header_file_path = self._out_base_filename + '.h'
567 cc_file_path = self._out_base_filename + '.cc' 724 cc_file_path = self._out_base_filename + '.cc'
568 substitutions = ({ 725 substitutions = ({
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
609 'out_base_filename', type=str, 766 'out_base_filename', type=str,
610 help='The base filename for the C++ files (.h and .cc will be appended)') 767 help='The base filename for the C++ files (.h and .cc will be appended)')
611 parser.add_argument('source_files', type=str, nargs='+', 768 parser.add_argument('source_files', type=str, nargs='+',
612 help='The source features.json files') 769 help='The source features.json files')
613 args = parser.parse_args() 770 args = parser.parse_args()
614 if args.feature_class not in FEATURE_CLASSES: 771 if args.feature_class not in FEATURE_CLASSES:
615 raise NameError('Unknown feature class: %s' % args.feature_class) 772 raise NameError('Unknown feature class: %s' % args.feature_class)
616 c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class, 773 c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class,
617 args.provider_class, args.out_root, 774 args.provider_class, args.out_root,
618 args.out_base_filename) 775 args.out_base_filename)
776 c.Load()
619 c.Compile() 777 c.Compile()
620 c.Write() 778 c.Write()
OLDNEW
« no previous file with comments | « extensions/renderer/script_context.cc ('k') | tools/json_schema_compiler/feature_compiler_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698