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()
|