source: tagsplugin/tags/0.3.1/tractags/expr.py

Last change on this file was 1817, checked in by Alec Thomas, 17 years ago
  • Quote all tag expression arguments to prevent the Python compiler from getting confused by tags like E-Mail addresses or floating point numbers.
File size: 3.0 KB
Line 
1import compiler
2import operator
3import re
4from trac.core import TracError
5
6class Expression:
7    """ Pass a set of tags through a basic expression filter.
8   
9        Supported operators are: unary - (not); binary +, - and | (and, and
10        not, or). All values in the expression are treated as tags. Any tag
11        not in the same form as a Python variable must be quoted.
12       
13        eg. Match all objects tagged with ticket and workflow, and not tagged
14        with wiki or closed.
15       
16            (ticket+workflow)-(wiki|closed)
17        """
18    __slots__ = ['ast', 'expression']
19    __visitors = {}
20
21    def __init__(self, expression):
22        tokenizer = re.compile(r'''[-|+()\[\]]|[^-|+()\[\]\s,]+''')
23        expr = []
24        for token in tokenizer.findall(expression):
25            if token not in '-|+()[]' and token[0] not in '"\'':
26                expr.append('"%s"' % token)
27            else:
28                expr.append(token)
29        self.expression = ' '.join(expr)
30        self.ast = compiler.parse(self.expression, 'eval')
31
32    def get_tags(self):
33        """ Fetch tags used in this expression. """
34        tags = set()
35        import parser
36        def walk(node):
37            if node[0] == 1:
38                tags.add(node[1])
39            elif node[0] == 3:
40                tags.add(eval(node[1]))
41            else:
42                for i in range(1, len(node)):
43                    if isinstance(node[i], tuple):
44                        walk(node[i])
45        tree = parser.expr(self.expression).totuple()
46        walk(tree)
47        return tags
48
49    def __call__(self, context):
50        return self._visit(self.ast, context)
51
52    def _visit(self, node, context):
53        v = self.__visitors.get(node.__class__)
54        if not v:
55            try:
56                v = getattr(self, '_visit_%s' % node.__class__.__name__.lower())
57            except AttributeError:
58                raise TracError('invalid expression node "%s"' % str(node.__class__.__name__.lower()))
59            self.__visitors[node.__class__] = v
60        return v(node, context)
61
62    def _visit_expression(self, node, context):
63        for child in node.getChildNodes():
64            return self._visit(child, context)
65
66    def _visit_sub(self, node, context):
67        return self._visit(node.left, context) \
68               and not self._visit(node.right, context)
69
70    def _visit_add(self, node, context):
71        return self._visit(node.left, context) \
72               and self._visit(node.right, context)
73
74    def _visit_bitor(self, node, context):
75        return self._visit(node.nodes[0], context) \
76               or self._visit(node.nodes[1], context)
77
78    def _visit_name(self, node, context):
79        return node.name in context
80
81    def _visit_const(self, node, context):
82        return node.value in context
83
84    def _visit_unarysub(self, node, context):
85        return not self._visit(node.expr, context)
86
87    def _visit_tuple(self, node, context):
88        return reduce(operator.and_, [self._visit(n, context) for n in node.nodes])
Note: See TracBrowser for help on using the repository browser.