File: parser.py

package info (click to toggle)
django-classy-tags 4.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 492 kB
  • sloc: python: 2,109; makefile: 98; sh: 6
file content (204 lines) | stat: -rw-r--r-- 7,916 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
from copy import deepcopy

from django import template

from classytags.exceptions import ArgumentRequiredError, BreakpointExpected, TooManyArguments, TrailingBreakpoint


class Parser:
    """
    Argument parsing class. A new instance of this gets created each time a tag
    get's parsed.
    """
    def __init__(self, options):
        self.options = options.bootstrap()

    def parse(self, parser, tokens):
        """
        Parse a token stream
        """
        self.parser = parser
        self.bits = tokens.split_contents()
        self.tagname = self.bits.pop(0)
        self.kwargs = {}
        self.blocks = {}
        self.forced_next = None
        # Get the first chunk of arguments until the next breakpoint
        self.arguments = self.options.get_arguments()
        self.current_argument = None
        # get a copy of the bits (tokens)
        self.todo = list(self.bits)
        # parse the bits (tokens)
        breakpoint = False
        for bit in self.bits:
            breakpoint = self.handle_bit(bit)
        if breakpoint:
            raise TrailingBreakpoint(self.tagname, breakpoint)
        # finish the bits (tokens)
        self.finish()
        # parse block tags
        self.parse_blocks()
        return self.kwargs, self.blocks

    def handle_bit(self, bit):
        """
        Handle the current bit
        """
        breakpoint = False
        if self.forced_next is not None:
            if bit != self.forced_next:
                raise BreakpointExpected(self.tagname, [self.forced_next], bit)
        elif bit in self.options.reversed_combined_breakpoints:
            expected = self.options.reversed_combined_breakpoints[bit]
            raise BreakpointExpected(self.tagname, [expected], bit)
        # Check if the current bit is the next breakpoint
        if bit == self.options.next_breakpoint:
            self.handle_next_breakpoint(bit)
            breakpoint = bit
        # Check if the current bit is a future breakpoint
        elif bit in self.options.breakpoints:
            self.handle_breakpoints(bit)
            breakpoint = bit
        # Otherwise it's a 'normal' argument
        else:
            self.handle_argument(bit)
        if bit in self.options.combined_breakpoints:
            self.forced_next = self.options.combined_breakpoints[bit]
        else:
            self.forced_next = None
        # remove from todos
        del self.todo[0]
        return breakpoint

    def handle_next_breakpoint(self, bit):
        """
        Handle a bit which is the next breakpoint by checking the current
        breakpoint scope is finished or can be finished and then shift to the
        next scope.
        """
        # Check if any unhandled argument in the current breakpoint is required
        self.check_required()
        # Shift the breakpoint to the next one
        self.options.shift_breakpoint()
        # Get the next chunk of arguments
        self.arguments = self.options.get_arguments()
        if self.arguments:
            self.current_argument = self.arguments.pop(0)
        else:
            self.current_argument = None

    def handle_breakpoints(self, bit):
        """
        Handle a bit which is a future breakpoint by trying to finish all
        intermediate breakpoint codes as well as the current scope and then
        shift.
        """
        # While we're not at our target breakpoint
        while bit != self.options.current_breakpoint:
            # Check required arguments
            self.check_required()
            # Shift to the next breakpoint
            self.options.shift_breakpoint()
            self.arguments = self.options.get_arguments()
        self.current_argument = self.arguments.pop(0)

    def handle_argument(self, bit):
        """
        Handle the current argument.
        """
        # If we don't have an argument yet
        if self.current_argument is None:
            try:
                # try to get the next one
                self.current_argument = self.arguments.pop(0)
            except IndexError:
                # If we don't have any arguments, left, raise a
                # TooManyArguments error
                raise TooManyArguments(self.tagname, self.todo)
        # parse the current argument and check if this bit was handled by this
        # argument
        handled = self.current_argument.parse(self.parser, bit, self.tagname,
                                              self.kwargs)
        # While this bit is not handled by an argument
        while not handled:
            try:
                # Try to get the next argument
                self.current_argument = self.arguments.pop(0)
            except IndexError:
                # If there is no next argument but there are still breakpoints
                # Raise an exception that we expected a breakpoint
                if self.options.breakpoints:
                    raise BreakpointExpected(self.tagname,
                                             self.options.breakpoints, bit)
                elif self.options.next_breakpoint:
                    raise BreakpointExpected(self.tagname,
                                             [self.options.next_breakpoint],
                                             bit)
                else:
                    # Otherwise raise a TooManyArguments excption
                    raise TooManyArguments(self.tagname, self.todo)
            # Try next argument
            handled = self.current_argument.parse(self.parser, bit,
                                                  self.tagname, self.kwargs)

    def finish(self):
        """
        Finish up parsing by checking all remaining breakpoint scopes
        """
        # Check if there are any required arguments left in the current
        # breakpoint
        self.check_required()
        # While there are still breakpoints left
        while self.options.next_breakpoint:
            # Shift to the next breakpoint
            self.options.shift_breakpoint()
            self.arguments = self.options.get_arguments()
            # And check this breakpoints arguments for required arguments.
            self.check_required()

    def parse_blocks(self):
        """
        Parse template blocks for block tags.

        Example:
            {% a %} b {% c %} d {% e %} f {% g %}
             => pre_c: b
                pre_e: d
                pre_g: f
            {% a %} b {% f %}
             => pre_c: b
                pre_e: None
                pre_g: None
        """
        # if no blocks are defined, bail out
        if not self.options.blocks:
            return
        # copy the blocks
        blocks = deepcopy(self.options.blocks)
        identifiers = {}
        for block in blocks:
            identifiers[block] = block.collect(self)
        while blocks:
            current_block = blocks.pop(0)
            current_identifiers = identifiers[current_block]
            block_identifiers = list(current_identifiers)
            for block in blocks:
                block_identifiers += identifiers[block]
            nodelist = self.parser.parse(block_identifiers)
            token = self.parser.next_token()
            while token.contents not in current_identifiers:
                empty_block = blocks.pop(0)
                current_identifiers = identifiers[empty_block]
                self.blocks[empty_block.alias] = template.NodeList()
            self.blocks[current_block.alias] = nodelist

    def check_required(self):
        """
        Iterate over arguments, checking if they're required, otherwise
        populating the kwargs dictionary with their defaults.
        """
        for argument in self.arguments:
            if argument.required:
                raise ArgumentRequiredError(argument, self.tagname)
            else:
                self.kwargs[argument.name] = argument.get_default()