Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

parser.py 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. from copy import deepcopy
  2. from django import template
  3. from classytags.exceptions import ArgumentRequiredError
  4. from classytags.exceptions import BreakpointExpected
  5. from classytags.exceptions import TooManyArguments
  6. from classytags.exceptions import TrailingBreakpoint
  7. class Parser(object):
  8. """
  9. Argument parsing class. A new instance of this gets created each time a tag
  10. get's parsed.
  11. """
  12. def __init__(self, options):
  13. self.options = options.bootstrap()
  14. def parse(self, parser, tokens):
  15. """
  16. Parse a token stream
  17. """
  18. self.parser = parser
  19. self.bits = tokens.split_contents()
  20. self.tagname = self.bits.pop(0)
  21. self.kwargs = {}
  22. self.blocks = {}
  23. self.forced_next = None
  24. # Get the first chunk of arguments until the next breakpoint
  25. self.arguments = self.options.get_arguments()
  26. self.current_argument = None
  27. # get a copy of the bits (tokens)
  28. self.todo = list(self.bits)
  29. # parse the bits (tokens)
  30. breakpoint = False
  31. for bit in self.bits:
  32. breakpoint = self.handle_bit(bit)
  33. if breakpoint:
  34. raise TrailingBreakpoint(self.tagname, breakpoint)
  35. # finish the bits (tokens)
  36. self.finish()
  37. # parse block tags
  38. self.parse_blocks()
  39. return self.kwargs, self.blocks
  40. def handle_bit(self, bit):
  41. """
  42. Handle the current bit
  43. """
  44. breakpoint = False
  45. if self.forced_next is not None:
  46. if bit != self.forced_next:
  47. raise BreakpointExpected(self.tagname, [self.forced_next], bit)
  48. elif bit in self.options.reversed_combined_breakpoints:
  49. expected = self.options.reversed_combined_breakpoints[bit]
  50. raise BreakpointExpected(self.tagname, [expected], bit)
  51. # Check if the current bit is the next breakpoint
  52. if bit == self.options.next_breakpoint:
  53. self.handle_next_breakpoint(bit)
  54. breakpoint = bit
  55. # Check if the current bit is a future breakpoint
  56. elif bit in self.options.breakpoints:
  57. self.handle_breakpoints(bit)
  58. breakpoint = bit
  59. # Otherwise it's a 'normal' argument
  60. else:
  61. self.handle_argument(bit)
  62. if bit in self.options.combined_breakpoints:
  63. self.forced_next = self.options.combined_breakpoints[bit]
  64. else:
  65. self.forced_next = None
  66. # remove from todos
  67. del self.todo[0]
  68. return breakpoint
  69. def handle_next_breakpoint(self, bit):
  70. """
  71. Handle a bit which is the next breakpoint by checking the current
  72. breakpoint scope is finished or can be finished and then shift to the
  73. next scope.
  74. """
  75. # Check if any unhandled argument in the current breakpoint is required
  76. self.check_required()
  77. # Shift the breakpoint to the next one
  78. self.options.shift_breakpoint()
  79. # Get the next chunk of arguments
  80. self.arguments = self.options.get_arguments()
  81. if self.arguments:
  82. self.current_argument = self.arguments.pop(0)
  83. else:
  84. self.current_argument = None
  85. def handle_breakpoints(self, bit):
  86. """
  87. Handle a bit which is a future breakpoint by trying to finish all
  88. intermediate breakpoint codes as well as the current scope and then
  89. shift.
  90. """
  91. # While we're not at our target breakpoint
  92. while bit != self.options.current_breakpoint:
  93. # Check required arguments
  94. self.check_required()
  95. # Shift to the next breakpoint
  96. self.options.shift_breakpoint()
  97. self.arguments = self.options.get_arguments()
  98. self.current_argument = self.arguments.pop(0)
  99. def handle_argument(self, bit):
  100. """
  101. Handle the current argument.
  102. """
  103. # If we don't have an argument yet
  104. if self.current_argument is None:
  105. try:
  106. # try to get the next one
  107. self.current_argument = self.arguments.pop(0)
  108. except IndexError:
  109. # If we don't have any arguments, left, raise a
  110. # TooManyArguments error
  111. raise TooManyArguments(self.tagname, self.todo)
  112. # parse the current argument and check if this bit was handled by this
  113. # argument
  114. handled = self.current_argument.parse(self.parser, bit, self.tagname,
  115. self.kwargs)
  116. # While this bit is not handled by an argument
  117. while not handled:
  118. try:
  119. # Try to get the next argument
  120. self.current_argument = self.arguments.pop(0)
  121. except IndexError:
  122. # If there is no next argument but there are still breakpoints
  123. # Raise an exception that we expected a breakpoint
  124. if self.options.breakpoints:
  125. raise BreakpointExpected(self.tagname,
  126. self.options.breakpoints, bit)
  127. elif self.options.next_breakpoint:
  128. raise BreakpointExpected(self.tagname,
  129. [self.options.next_breakpoint],
  130. bit)
  131. else:
  132. # Otherwise raise a TooManyArguments excption
  133. raise TooManyArguments(self.tagname, self.todo)
  134. # Try next argument
  135. handled = self.current_argument.parse(self.parser, bit,
  136. self.tagname, self.kwargs)
  137. def finish(self):
  138. """
  139. Finish up parsing by checking all remaining breakpoint scopes
  140. """
  141. # Check if there are any required arguments left in the current
  142. # breakpoint
  143. self.check_required()
  144. # While there are still breakpoints left
  145. while self.options.next_breakpoint:
  146. # Shift to the next breakpoint
  147. self.options.shift_breakpoint()
  148. self.arguments = self.options.get_arguments()
  149. # And check this breakpoints arguments for required arguments.
  150. self.check_required()
  151. def parse_blocks(self):
  152. """
  153. Parse template blocks for block tags.
  154. Example:
  155. {% a %} b {% c %} d {% e %} f {% g %}
  156. => pre_c: b
  157. pre_e: d
  158. pre_g: f
  159. {% a %} b {% f %}
  160. => pre_c: b
  161. pre_e: None
  162. pre_g: None
  163. """
  164. # if no blocks are defined, bail out
  165. if not self.options.blocks:
  166. return
  167. # copy the blocks
  168. blocks = deepcopy(self.options.blocks)
  169. identifiers = {}
  170. for block in blocks:
  171. identifiers[block] = block.collect(self)
  172. while blocks:
  173. current_block = blocks.pop(0)
  174. current_identifiers = identifiers[current_block]
  175. block_identifiers = list(current_identifiers)
  176. for block in blocks:
  177. block_identifiers += identifiers[block]
  178. nodelist = self.parser.parse(block_identifiers)
  179. token = self.parser.next_token()
  180. while token.contents not in current_identifiers:
  181. empty_block = blocks.pop(0)
  182. current_identifiers = identifiers[empty_block]
  183. self.blocks[empty_block.alias] = template.NodeList()
  184. self.blocks[current_block.alias] = nodelist
  185. def check_required(self):
  186. """
  187. Iterate over arguments, checking if they're required, otherwise
  188. populating the kwargs dictionary with their defaults.
  189. """
  190. for argument in self.arguments:
  191. if argument.required:
  192. raise ArgumentRequiredError(argument, self.tagname)
  193. else:
  194. self.kwargs[argument.name] = argument.get_default()