OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 '''Base types for nodes in a GRIT resource tree. | 6 '''Base types for nodes in a GRIT resource tree. |
7 ''' | 7 ''' |
8 | 8 |
9 import collections | 9 import ast |
10 import os | 10 import os |
11 import sys | 11 import sys |
12 import types | 12 import types |
13 from xml.sax import saxutils | 13 from xml.sax import saxutils |
14 | 14 |
15 from grit import clique | 15 from grit import clique |
16 from grit import exception | 16 from grit import exception |
17 from grit import util | 17 from grit import util |
18 | 18 |
19 | 19 |
20 class Node(object): | 20 class Node(object): |
21 '''An item in the tree that has children.''' | 21 '''An item in the tree that has children.''' |
22 | 22 |
23 # Valid content types that can be returned by _ContentType() | 23 # Valid content types that can be returned by _ContentType() |
24 _CONTENT_TYPE_NONE = 0 # No CDATA content but may have children | 24 _CONTENT_TYPE_NONE = 0 # No CDATA content but may have children |
25 _CONTENT_TYPE_CDATA = 1 # Only CDATA, no children. | 25 _CONTENT_TYPE_CDATA = 1 # Only CDATA, no children. |
26 _CONTENT_TYPE_MIXED = 2 # CDATA and children, possibly intermingled | 26 _CONTENT_TYPE_MIXED = 2 # CDATA and children, possibly intermingled |
27 | 27 |
28 # Default nodes to not whitelist skipped | 28 # Default nodes to not whitelist skipped |
29 _whitelist_marked_as_skip = False | 29 _whitelist_marked_as_skip = False |
30 | 30 |
31 # A class-static cache to memoize EvaluateExpression(). | 31 # A class-static cache to speed up EvaluateExpression(). |
32 # It has a 2 level nested dict structure. The outer dict has keys | 32 # Keys are expressions (e.g. 'is_ios and lang == "fr"'). Values are tuples |
33 # of tuples which define the environment in which the expression | 33 # (code, variables_in_expr) where code is the compiled expression and can be |
34 # will be evaluated. The inner dict is map of expr->result. | 34 # directly eval'd, and variables_in_expr is the list of variable and method |
35 eval_expr_cache = collections.defaultdict(dict) | 35 # names used in the expression (e.g. ['is_ios', 'lang']). |
| 36 eval_expr_cache = {} |
36 | 37 |
37 def __init__(self): | 38 def __init__(self): |
38 self.children = [] # A list of child elements | 39 self.children = [] # A list of child elements |
39 self.mixed_content = [] # A list of u'' and/or child elements (this | 40 self.mixed_content = [] # A list of u'' and/or child elements (this |
40 # duplicates 'children' but | 41 # duplicates 'children' but |
41 # is needed to preserve markup-type content). | 42 # is needed to preserve markup-type content). |
42 self.name = u'' # The name of this element | 43 self.name = u'' # The name of this element |
43 self.attrs = {} # The set of attributes (keys to values) | 44 self.attrs = {} # The set of attributes (keys to values) |
44 self.parent = None # Our parent unless we are the root element. | 45 self.parent = None # Our parent unless we are the root element. |
45 self.uberclique = None # Allows overriding uberclique for parts of tree | 46 self.uberclique = None # Allows overriding uberclique for parts of tree |
(...skipping 389 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
435 return [child for child in self if isinstance(child, type)] | 436 return [child for child in self if isinstance(child, type)] |
436 | 437 |
437 def GetTextualIds(self): | 438 def GetTextualIds(self): |
438 '''Returns a list of the textual ids of this node. | 439 '''Returns a list of the textual ids of this node. |
439 ''' | 440 ''' |
440 if 'name' in self.attrs: | 441 if 'name' in self.attrs: |
441 return [self.attrs['name']] | 442 return [self.attrs['name']] |
442 return [] | 443 return [] |
443 | 444 |
444 @classmethod | 445 @classmethod |
445 def GetPlatformAssertion(cls, target_platform): | 446 def EvaluateExpression(cls, expr, defs, target_platform, extra_variables={}): |
446 '''If the platform is a specific well-known platform, this returns | 447 '''Worker for EvaluateCondition (below) and conditions in XTB files.''' |
447 the is_xyz string representing that platform (e.g. is_linux), | 448 if expr in cls.eval_expr_cache: |
448 otherwise the empty string. | 449 code, variables_in_expr = cls.eval_expr_cache[expr] |
449 ''' | 450 else: |
450 platform = '' | 451 # Get a list of all variable and method names used in the expression. |
451 if target_platform == 'darwin': | 452 syntax_tree = ast.parse(expr, mode='eval') |
452 platform = 'is_macosx' | 453 variables_in_expr = [node.id for node in ast.walk(syntax_tree) if |
453 elif target_platform.startswith('linux'): | 454 isinstance(node, ast.Name) and node.id not in ('True', 'False')] |
454 platform = 'is_linux' | 455 code = compile(syntax_tree, filename='<string>', mode='eval') |
455 elif target_platform in ('cygwin', 'win32'): | 456 cls.eval_expr_cache[expr] = code, variables_in_expr |
456 platform = 'is_win' | |
457 elif target_platform in ('android', 'ios'): | |
458 platform = 'is_%s' % target_platform | |
459 return platform | |
460 | 457 |
461 @classmethod | 458 # Set values only for variables that are needed to eval the expression. |
462 def EvaluateExpression(cls, expr, defs, target_platform, extra_variables=None)
: | 459 variable_map = {} |
463 '''Worker for EvaluateCondition (below) and conditions in XTB files.''' | 460 for name in variables_in_expr: |
464 cache_dict = cls.eval_expr_cache[ | 461 if name == 'os': |
465 (tuple(defs.iteritems()), target_platform, extra_variables)] | 462 value = target_platform |
466 if expr in cache_dict: | 463 elif name == 'defs': |
467 return cache_dict[expr] | 464 value = defs |
468 def pp_ifdef(symbol): | |
469 return symbol in defs | |
470 def pp_if(symbol): | |
471 return defs.get(symbol, False) | |
472 variable_map = { | |
473 'defs' : defs, | |
474 'os': target_platform, | |
475 | 465 |
476 # One of these is_xyz assertions gets set to True in the line | 466 elif name == 'is_linux': |
477 # following this initializer block. | 467 value = target_platform.startswith('linux') |
478 'is_linux': False, | 468 elif name == 'is_macosx': |
479 'is_macosx': False, | 469 value = target_platform == 'darwin' |
480 'is_win': False, | 470 elif name == 'is_win': |
481 'is_android': False, | 471 value = target_platform in ('cygwin', 'win32') |
482 'is_ios': False, | 472 elif name == 'is_android': |
| 473 value = target_platform == 'android' |
| 474 elif name == 'is_ios': |
| 475 value = target_platform == 'ios' |
| 476 elif name == 'is_posix': |
| 477 value = (target_platform in ('darwin', 'linux2', 'linux3', 'sunos5') |
| 478 or 'bsd' in sys.platform) |
483 | 479 |
484 # is_posix is not mutually exclusive of the others and gets | 480 elif name == 'pp_ifdef': |
485 # set here, not below. | 481 def pp_ifdef(symbol): |
486 'is_posix': (target_platform in ('darwin', 'linux2', 'linux3', 'sunos5') | 482 return symbol in defs |
487 or 'bsd' in sys.platform), | 483 value = pp_ifdef |
| 484 elif name == 'pp_if': |
| 485 def pp_if(symbol): |
| 486 return defs.get(symbol, False) |
| 487 value = pp_if |
488 | 488 |
489 'pp_ifdef' : pp_ifdef, | 489 elif name in defs: |
490 'pp_if' : pp_if, | 490 value = defs[name] |
491 } | 491 elif name in extra_variables: |
492 variable_map[Node.GetPlatformAssertion(target_platform)] = True | 492 value = extra_variables[name] |
| 493 else: |
| 494 # Undefined variables default to False. |
| 495 value = False |
493 | 496 |
494 if extra_variables: | 497 variable_map[name] = value |
495 variable_map.update(extra_variables) | 498 |
496 eval_result = cache_dict[expr] = eval(expr, {}, variable_map) | 499 eval_result = eval(code, {}, variable_map) |
| 500 assert isinstance(eval_result, bool) |
497 return eval_result | 501 return eval_result |
498 | 502 |
499 def EvaluateCondition(self, expr): | 503 def EvaluateCondition(self, expr): |
500 '''Returns true if and only if the Python expression 'expr' evaluates | 504 '''Returns true if and only if the Python expression 'expr' evaluates |
501 to true. | 505 to true. |
502 | 506 |
503 The expression is given a few local variables: | 507 The expression is given a few local variables: |
504 - 'lang' is the language currently being output | 508 - 'lang' is the language currently being output |
505 (the 'lang' attribute of the <output> element). | 509 (the 'lang' attribute of the <output> element). |
506 - 'context' is the current output context | 510 - 'context' is the current output context |
507 (the 'context' attribute of the <output> element). | 511 (the 'context' attribute of the <output> element). |
508 - 'defs' is a map of C preprocessor-style symbol names to their values. | 512 - 'defs' is a map of C preprocessor-style symbol names to their values. |
509 - 'os' is the current platform (likely 'linux2', 'win32' or 'darwin'). | 513 - 'os' is the current platform (likely 'linux2', 'win32' or 'darwin'). |
510 - 'pp_ifdef(symbol)' is a shorthand for "symbol in defs". | 514 - 'pp_ifdef(symbol)' is a shorthand for "symbol in defs". |
511 - 'pp_if(symbol)' is a shorthand for "symbol in defs and defs[symbol]". | 515 - 'pp_if(symbol)' is a shorthand for "symbol in defs and defs[symbol]". |
512 - 'is_linux', 'is_macosx', 'is_win', 'is_posix' are true if 'os' | 516 - 'is_linux', 'is_macosx', 'is_win', 'is_posix' are true if 'os' |
513 matches the given platform. | 517 matches the given platform. |
514 ''' | 518 ''' |
515 root = self.GetRoot() | 519 root = self.GetRoot() |
516 lang = getattr(root, 'output_language', '') | 520 lang = getattr(root, 'output_language', '') |
517 context = getattr(root, 'output_context', '') | 521 context = getattr(root, 'output_context', '') |
518 defs = getattr(root, 'defines', {}) | 522 defs = getattr(root, 'defines', {}) |
519 target_platform = getattr(root, 'target_platform', '') | 523 target_platform = getattr(root, 'target_platform', '') |
520 extra_variables = ( | 524 extra_variables = { |
521 ('lang', lang), | 525 'lang': lang, |
522 ('context', context), | 526 'context': context, |
523 ) | 527 } |
524 return Node.EvaluateExpression( | 528 return Node.EvaluateExpression( |
525 expr, defs, target_platform, extra_variables) | 529 expr, defs, target_platform, extra_variables) |
526 | 530 |
527 def OnlyTheseTranslations(self, languages): | 531 def OnlyTheseTranslations(self, languages): |
528 '''Turns off loading of translations for languages not in the provided list. | 532 '''Turns off loading of translations for languages not in the provided list. |
529 | 533 |
530 Attrs: | 534 Attrs: |
531 languages: ['fr', 'zh_cn'] | 535 languages: ['fr', 'zh_cn'] |
532 ''' | 536 ''' |
533 for node in self: | 537 for node in self: |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
583 def ExpandVariables(self): | 587 def ExpandVariables(self): |
584 '''Whether we need to expand variables on a given node.''' | 588 '''Whether we need to expand variables on a given node.''' |
585 return False | 589 return False |
586 | 590 |
587 | 591 |
588 class ContentNode(Node): | 592 class ContentNode(Node): |
589 '''Convenience baseclass for nodes that can have content.''' | 593 '''Convenience baseclass for nodes that can have content.''' |
590 def _ContentType(self): | 594 def _ContentType(self): |
591 return self._CONTENT_TYPE_MIXED | 595 return self._CONTENT_TYPE_MIXED |
592 | 596 |
OLD | NEW |