|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- from copy import deepcopy
-
- from django import template
-
- from classytags.exceptions import ArgumentRequiredError
- from classytags.exceptions import BreakpointExpected
- from classytags.exceptions import TooManyArguments
- from classytags.exceptions import TrailingBreakpoint
-
-
- class Parser(object):
- """
- 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()
|