Source code for shillelagh.adapters.api.gsheets.parsing.base
"""
Token for parsing date and time.
"""
import re
from collections.abc import Iterator
from datetime import date, datetime, time, timedelta
from typing import Any, Generic, TypeVar
Valid = TypeVar("Valid", datetime, date, time, timedelta, str, int, float)
[docs]
class Token(Generic[Valid]):
"""
A token.
"""
regex: str
def __init__(self, token: str):
self.token = token
[docs]
@classmethod
def match(
cls,
pattern: str,
history: list["Token"], # pylint: disable=unused-argument
) -> bool:
"""
Check if token handles the beginning of the pattern.
"""
return bool(re.match(cls.regex, pattern))
[docs]
@classmethod
def consume(
cls,
pattern: str,
history: list["Token"], # pylint: disable=unused-argument
) -> tuple["Token", str]:
"""
Consume the pattern, returning the token and the remaining pattern.
"""
match = re.match(cls.regex, pattern)
if not match:
# pylint: disable=broad-exception-raised
raise Exception("Token could not find match")
token = match.group()
return cls(token), pattern[len(token) :]
[docs]
def parse(self, value: str, tokens: list["Token"]) -> tuple[dict[str, Any], str]:
"""
Parse the value given a pattern.
Returns the consumed parameter as an argument, and the rest of the value.
"""
raise NotImplementedError("Subclasses MUST implement ``parse``")
def __repr__(self) -> str:
return f"{self.__class__.__name__}('{self.token}')"
def __eq__(self, other) -> bool:
if other.__class__ != self.__class__:
return NotImplemented
return bool(self.token == other.token)
[docs]
class LITERAL(Token):
"""
A literal.
"""
regex = r'(\\.)|(".*?")|(.)'
[docs]
def parse(self, value: str, tokens: list[Token]) -> tuple[dict[str, Any], str]:
if self.token.startswith("\\"):
size = 1
if not value[:size] == self.token[1:]:
raise InvalidValue(value)
elif self.token.startswith('"'):
size = len(self.token) - 2
if not value[:size] == self.token[1:-1]:
raise InvalidValue(value)
else:
size = len(self.token)
if not value[:size] == self.token:
raise InvalidValue(value)
return {}, value[size:]
[docs]
def tokenize(pattern: str, classes: list[type[Token]]) -> Iterator[Token]:
"""
Tokenize a pattern.
"""
tokens: list[Token] = []
while pattern:
for class_ in classes: # pragma: no cover
if class_.match(pattern, tokens):
token, pattern = class_.consume(pattern, tokens)
tokens.append(token)
break
else:
raise InvalidPattern(f'Could not consume "{pattern}"')
# combine unescaped literals
while tokens:
token = tokens.pop(0)
if is_unescaped_literal(token):
acc = [token.token]
while tokens and is_unescaped_literal(tokens[0]):
next_ = tokens.pop(0)
acc.append(next_.token)
yield LITERAL("".join(acc))
else:
yield token
[docs]
def is_unescaped_literal(token: Token) -> bool:
"""
Return true if the token is an unescaped literal.
"""
return isinstance(token, LITERAL) and not (
token.token.startswith('"') or token.token.startswith("\\")
)
[docs]
class InvalidPattern(Exception):
"""
Raised when the pattern can't be consumed.
"""
[docs]
class InvalidValue(Exception):
"""
Raised when the value can't be consumed.
"""