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