1import re
2
3from jinja2.exceptions import TemplateSyntaxError
4from jinja2.ext import Extension
5from jinja2.lexer import count_newlines
6from jinja2.lexer import Token
7
8
9_outside_re = re.compile(r"\\?(gettext|_)\(")
10_inside_re = re.compile(r"\\?[()]")
11
12
13class InlineGettext(Extension):
14    """This extension implements support for inline gettext blocks::
15
16        <h1>_(Welcome)</h1>
17        <p>_(This is a paragraph)</p>
18
19    Requires the i18n extension to be loaded and configured.
20    """
21
22    def filter_stream(self, stream):
23        paren_stack = 0
24
25        for token in stream:
26            if token.type != "data":
27                yield token
28                continue
29
30            pos = 0
31            lineno = token.lineno
32
33            while 1:
34                if not paren_stack:
35                    match = _outside_re.search(token.value, pos)
36                else:
37                    match = _inside_re.search(token.value, pos)
38                if match is None:
39                    break
40                new_pos = match.start()
41                if new_pos > pos:
42                    preval = token.value[pos:new_pos]
43                    yield Token(lineno, "data", preval)
44                    lineno += count_newlines(preval)
45                gtok = match.group()
46                if gtok[0] == "\\":
47                    yield Token(lineno, "data", gtok[1:])
48                elif not paren_stack:
49                    yield Token(lineno, "block_begin", None)
50                    yield Token(lineno, "name", "trans")
51                    yield Token(lineno, "block_end", None)
52                    paren_stack = 1
53                else:
54                    if gtok == "(" or paren_stack > 1:
55                        yield Token(lineno, "data", gtok)
56                    paren_stack += -1 if gtok == ")" else 1
57                    if not paren_stack:
58                        yield Token(lineno, "block_begin", None)
59                        yield Token(lineno, "name", "endtrans")
60                        yield Token(lineno, "block_end", None)
61                pos = match.end()
62
63            if pos < len(token.value):
64                yield Token(lineno, "data", token.value[pos:])
65
66        if paren_stack:
67            raise TemplateSyntaxError(
68                "unclosed gettext expression",
69                token.lineno,
70                stream.name,
71                stream.filename,
72            )
73