moto/moto/ses/template.py
2023-09-30 07:35:11 +00:00

181 lines
6.0 KiB
Python

from typing import Any, Dict, Optional, Type
from .exceptions import MissingRenderingAttributeException
from moto.utilities.tokenizer import GenericTokenizer
class BlockProcessor:
def __init__(
self, template: str, template_data: Dict[str, Any], tokenizer: GenericTokenizer
):
self.template = template
self.template_data = template_data
self.tokenizer = tokenizer
def parse(self) -> str:
# Added to make MyPy happy
# Not all implementations have this method
# It's up to the caller to know whether to call this method
raise NotImplementedError
class EachBlockProcessor(BlockProcessor):
def __init__(
self, template: str, template_data: Dict[str, Any], tokenizer: GenericTokenizer
):
self.template = template
self.tokenizer = tokenizer
self.tokenizer.skip_characters("#each")
self.tokenizer.skip_white_space()
var_name = self.tokenizer.read_until("}}").strip()
self.tokenizer.skip_characters("}}")
self.template_data = template_data.get(var_name, [])
def parse(self) -> str:
parsed = ""
current_pos = self.tokenizer.token_pos
for template_data in self.template_data:
self.tokenizer.token_pos = current_pos
for char in self.tokenizer:
if char == "{" and self.tokenizer.peek() == "{":
self.tokenizer.skip_characters("{")
self.tokenizer.skip_white_space()
_processor = get_processor(self.tokenizer)(
self.template, template_data, self.tokenizer # type: ignore
)
# If we've reached the end, we should stop processing
# Our parent will continue with whatever comes after {{/each}}
if type(_processor) == EachEndBlockProcessor:
break
# If we've encountered another processor, they can continue
parsed += _processor.parse()
continue
parsed += char
return parsed
class EachEndBlockProcessor(BlockProcessor):
def __init__(
self, template: str, template_data: Dict[str, Any], tokenizer: GenericTokenizer
):
super().__init__(template, template_data, tokenizer)
self.tokenizer.skip_characters("/each")
self.tokenizer.skip_white_space()
self.tokenizer.skip_characters("}}")
class IfBlockProcessor(BlockProcessor):
def __init__(
self, template: str, template_data: Dict[str, Any], tokenizer: GenericTokenizer
):
super().__init__(template, template_data, tokenizer)
self.tokenizer.skip_characters("#if")
self.tokenizer.skip_white_space()
condition = self.tokenizer.read_until("}}").strip()
self.tokenizer.skip_characters("}}")
self.parse_contents = template_data.get(condition)
def parse(self) -> str:
parsed = ""
for char in self.tokenizer:
if char == "{" and self.tokenizer.peek() == "{":
self.tokenizer.skip_characters("{")
self.tokenizer.skip_white_space()
_processor = get_processor(self.tokenizer)(
self.template, self.template_data, self.tokenizer
)
if type(_processor) == IfEndBlockProcessor:
break
elif type(_processor) == ElseBlockProcessor:
self.parse_contents = not self.parse_contents
continue
if self.parse_contents:
parsed += _processor.parse()
continue
if self.parse_contents:
parsed += char
return parsed
class IfEndBlockProcessor(BlockProcessor):
def __init__(
self, template: str, template_data: Dict[str, Any], tokenizer: GenericTokenizer
):
super().__init__(template, template_data, tokenizer)
self.tokenizer.skip_characters("/if")
self.tokenizer.skip_white_space()
self.tokenizer.skip_characters("}}")
class ElseBlockProcessor(BlockProcessor):
def __init__(
self, template: str, template_data: Dict[str, Any], tokenizer: GenericTokenizer
):
super().__init__(template, template_data, tokenizer)
self.tokenizer.skip_characters("else")
self.tokenizer.skip_white_space()
self.tokenizer.skip_characters("}}")
class VarBlockProcessor(BlockProcessor):
def parse(self) -> str:
var_name = self.tokenizer.read_until("}}").strip()
if self.template_data.get(var_name) is None:
raise MissingRenderingAttributeException(var_name)
data: str = self.template_data.get(var_name) # type: ignore
self.tokenizer.skip_white_space()
self.tokenizer.skip_characters("}}")
return data
def get_processor(tokenizer: GenericTokenizer) -> Type[BlockProcessor]:
if tokenizer.peek(5) == "#each":
return EachBlockProcessor
if tokenizer.peek(5) == "/each":
return EachEndBlockProcessor
if tokenizer.peek(3) == "#if":
return IfBlockProcessor
if tokenizer.peek(3) == "/if":
return IfEndBlockProcessor
if tokenizer.peek(4) == "else":
return ElseBlockProcessor
return VarBlockProcessor
def parse_template(
template: str,
template_data: Dict[str, Any],
tokenizer: Optional[GenericTokenizer] = None,
) -> str:
tokenizer = tokenizer or GenericTokenizer(template)
parsed = ""
for char in tokenizer:
if char == "{" and tokenizer.peek() == "{":
# Two braces next to each other indicate a variable/language construct such as for-each
# We have different processors handling different constructs
tokenizer.skip_characters("{")
tokenizer.skip_white_space()
_processor = get_processor(tokenizer)(template, template_data, tokenizer)
parsed += _processor.parse()
continue
parsed += char
return parsed