1"""
2    sphinx.util.tags
3    ~~~~~~~~~~~~~~~~
4
5    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
6    :license: BSD, see LICENSE for details.
7"""
8
9from typing import Iterator, List
10
11from jinja2 import nodes
12from jinja2.environment import Environment
13from jinja2.nodes import Node
14from jinja2.parser import Parser
15
16env = Environment()
17
18
19class BooleanParser(Parser):
20    """
21    Only allow condition exprs and/or/not operations.
22    """
23
24    def parse_compare(self) -> Node:
25        node = None  # type: Node
26        token = self.stream.current
27        if token.type == 'name':
28            if token.value in ('true', 'false', 'True', 'False'):
29                node = nodes.Const(token.value in ('true', 'True'),
30                                   lineno=token.lineno)
31            elif token.value in ('none', 'None'):
32                node = nodes.Const(None, lineno=token.lineno)
33            else:
34                node = nodes.Name(token.value, 'load', lineno=token.lineno)
35            next(self.stream)
36        elif token.type == 'lparen':
37            next(self.stream)
38            node = self.parse_expression()
39            self.stream.expect('rparen')
40        else:
41            self.fail("unexpected token '%s'" % (token,), token.lineno)
42        return node
43
44
45class Tags:
46    def __init__(self, tags: List[str] = None) -> None:
47        self.tags = dict.fromkeys(tags or [], True)
48
49    def has(self, tag: str) -> bool:
50        return tag in self.tags
51
52    __contains__ = has
53
54    def __iter__(self) -> Iterator[str]:
55        return iter(self.tags)
56
57    def add(self, tag: str) -> None:
58        self.tags[tag] = True
59
60    def remove(self, tag: str) -> None:
61        self.tags.pop(tag, None)
62
63    def eval_condition(self, condition: str) -> bool:
64        # exceptions are handled by the caller
65        parser = BooleanParser(env, condition, state='variable')
66        expr = parser.parse_expression()
67        if not parser.stream.eos:
68            raise ValueError('chunk after expression')
69
70        def eval_node(node: Node) -> bool:
71            if isinstance(node, nodes.CondExpr):
72                if eval_node(node.test):  # type: ignore
73                    return eval_node(node.expr1)  # type: ignore
74                else:
75                    return eval_node(node.expr2)  # type: ignore
76            elif isinstance(node, nodes.And):
77                return eval_node(node.left) and eval_node(node.right)  # type: ignore
78            elif isinstance(node, nodes.Or):
79                return eval_node(node.left) or eval_node(node.right)  # type: ignore
80            elif isinstance(node, nodes.Not):
81                return not eval_node(node.node)  # type: ignore
82            elif isinstance(node, nodes.Name):
83                return self.tags.get(node.name, False)  # type: ignore
84            else:
85                raise ValueError('invalid node, check parsing')
86
87        return eval_node(expr)
88