map working
This commit is contained in:
parent
20ad7b4f7e
commit
ea0d836baf
6 changed files with 113 additions and 18 deletions
|
|
@ -1,9 +1,12 @@
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from ytdl_sub.script.types.array import Array
|
from ytdl_sub.script.types.array import Array
|
||||||
from ytdl_sub.script.types.resolvable import Map
|
from ytdl_sub.script.types.map import Map
|
||||||
|
from ytdl_sub.script.types.resolvable import Hashable
|
||||||
from ytdl_sub.script.types.resolvable import Resolvable
|
from ytdl_sub.script.types.resolvable import Resolvable
|
||||||
|
from ytdl_sub.script.types.resolvable import String
|
||||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,3 +24,11 @@ class MapFunctions:
|
||||||
output[key_value.value[0]] = key_value.value[1]
|
output[key_value.value[0]] = key_value.value[1]
|
||||||
|
|
||||||
return Map(output)
|
return Map(output)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get(mapping: Map, key: Hashable, default: Optional[Resolvable] = None) -> Resolvable:
|
||||||
|
if key not in mapping.value:
|
||||||
|
if default is not None:
|
||||||
|
return default
|
||||||
|
raise StringFormattingException("key not found")
|
||||||
|
return mapping.value[key]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
@ -6,6 +7,7 @@ from ytdl_sub.script.types.array import Array
|
||||||
from ytdl_sub.script.types.array import UnresolvedArray
|
from ytdl_sub.script.types.array import UnresolvedArray
|
||||||
from ytdl_sub.script.types.function import ArgumentType
|
from ytdl_sub.script.types.function import ArgumentType
|
||||||
from ytdl_sub.script.types.function import Function
|
from ytdl_sub.script.types.function import Function
|
||||||
|
from ytdl_sub.script.types.map import UnresolvedMap
|
||||||
from ytdl_sub.script.types.resolvable import Boolean
|
from ytdl_sub.script.types.resolvable import Boolean
|
||||||
from ytdl_sub.script.types.resolvable import Float
|
from ytdl_sub.script.types.resolvable import Float
|
||||||
from ytdl_sub.script.types.resolvable import Integer
|
from ytdl_sub.script.types.resolvable import Integer
|
||||||
|
|
@ -120,6 +122,9 @@ class _Parser:
|
||||||
if self._read(increment_pos=False) == "[":
|
if self._read(increment_pos=False) == "[":
|
||||||
self._pos += 1
|
self._pos += 1
|
||||||
return self._parse_array()
|
return self._parse_array()
|
||||||
|
if self._read(increment_pos=False) == "{":
|
||||||
|
self._pos += 1
|
||||||
|
assert self._parse_map()
|
||||||
if self._read(increment_pos=False).isascii() and self._read(increment_pos=False).islower():
|
if self._read(increment_pos=False).isascii() and self._read(increment_pos=False).islower():
|
||||||
return self._parse_variable()
|
return self._parse_variable()
|
||||||
raise StringFormattingException(
|
raise StringFormattingException(
|
||||||
|
|
@ -127,7 +132,7 @@ class _Parser:
|
||||||
"string, boolean, or variable without brackets"
|
"string, boolean, or variable without brackets"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_args(self, breaking_char: str = ")") -> List[ArgumentType]:
|
def _parse_args(self, breaking_chars: str = ")") -> List[ArgumentType]:
|
||||||
"""
|
"""
|
||||||
Begin parsing function args after the first ``(``, i.e. ``function_name(``
|
Begin parsing function args after the first ``(``, i.e. ``function_name(``
|
||||||
"""
|
"""
|
||||||
|
|
@ -136,7 +141,7 @@ class _Parser:
|
||||||
|
|
||||||
arguments: List[ArgumentType] = []
|
arguments: List[ArgumentType] = []
|
||||||
while ch := self._read(increment_pos=False):
|
while ch := self._read(increment_pos=False):
|
||||||
if ch == breaking_char:
|
if ch in breaking_chars:
|
||||||
break
|
break
|
||||||
|
|
||||||
if ch.isspace():
|
if ch.isspace():
|
||||||
|
|
@ -182,10 +187,40 @@ class _Parser:
|
||||||
self._pos += 1
|
self._pos += 1
|
||||||
return UnresolvedArray(value=function_args)
|
return UnresolvedArray(value=function_args)
|
||||||
else:
|
else:
|
||||||
function_args = self._parse_args(breaking_char="]")
|
function_args = self._parse_args(breaking_chars="]")
|
||||||
|
|
||||||
raise StringFormattingException("Invalid function")
|
raise StringFormattingException("Invalid function")
|
||||||
|
|
||||||
|
def _parse_map(self) -> UnresolvedMap:
|
||||||
|
"""
|
||||||
|
Begin parsing a map after reading the first ``{``
|
||||||
|
"""
|
||||||
|
output: Dict[ArgumentType, ArgumentType] = {}
|
||||||
|
key: Optional[ArgumentType] = None
|
||||||
|
|
||||||
|
while ch := self._read(increment_pos=False):
|
||||||
|
if ch == "}":
|
||||||
|
if key is not None:
|
||||||
|
raise StringFormattingException("Key with no value")
|
||||||
|
|
||||||
|
self._pos += 1
|
||||||
|
return UnresolvedMap(value=output)
|
||||||
|
elif key is None:
|
||||||
|
key_args = self._parse_args(breaking_chars=":")
|
||||||
|
if len(key_args) != 1:
|
||||||
|
raise StringFormattingException("Lazy parsing but got mlutiple args")
|
||||||
|
key = key_args[0]
|
||||||
|
elif key is not None and ch == ":":
|
||||||
|
self._pos += 1
|
||||||
|
value_args = self._parse_args(breaking_chars=",}")
|
||||||
|
if len(value_args) != 1:
|
||||||
|
raise StringFormattingException("Lazy parsing, no value")
|
||||||
|
|
||||||
|
output[key] = value_args[0]
|
||||||
|
key = None
|
||||||
|
else:
|
||||||
|
raise StringFormattingException("Invalid map")
|
||||||
|
|
||||||
def _parse(self) -> SyntaxTree:
|
def _parse(self) -> SyntaxTree:
|
||||||
bracket_counter = 0
|
bracket_counter = 0
|
||||||
literal_str = ""
|
literal_str = ""
|
||||||
|
|
@ -216,6 +251,9 @@ class _Parser:
|
||||||
elif ch1 == "[":
|
elif ch1 == "[":
|
||||||
self._pos += 1
|
self._pos += 1
|
||||||
self._ast.append(self._parse_array())
|
self._ast.append(self._parse_array())
|
||||||
|
elif ch1 == "{":
|
||||||
|
self._pos += 1
|
||||||
|
self._ast.append(self._parse_map())
|
||||||
else:
|
else:
|
||||||
self._ast.append(self._parse_variable())
|
self._ast.append(self._parse_variable())
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
51
src/ytdl_sub/script/types/map.py
Normal file
51
src/ytdl_sub/script/types/map.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from ytdl_sub.script.types.resolvable import ArgumentType
|
||||||
|
from ytdl_sub.script.types.resolvable import Hashable
|
||||||
|
from ytdl_sub.script.types.resolvable import Resolvable
|
||||||
|
from ytdl_sub.script.types.variable import Variable
|
||||||
|
from ytdl_sub.script.types.variable_dependency import VariableDependency
|
||||||
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Map:
|
||||||
|
value: Dict[Hashable, Resolvable]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class UnresolvedMap(Map, VariableDependency, ArgumentType):
|
||||||
|
value: Dict[ArgumentType, ArgumentType]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def variables(self) -> Set[Variable]:
|
||||||
|
output: Set[Variable] = set()
|
||||||
|
for key, value in self.value.items():
|
||||||
|
if isinstance(key, Variable):
|
||||||
|
output.add(key)
|
||||||
|
if isinstance(value, Variable):
|
||||||
|
output.add(key)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
|
||||||
|
output: Dict[Hashable, Resolvable] = {}
|
||||||
|
for key, value in self.value.items():
|
||||||
|
resolved_key = self._resolve_argument_type(
|
||||||
|
resolved_variables=resolved_variables, arg=key
|
||||||
|
)
|
||||||
|
if not isinstance(resolved_key, Hashable):
|
||||||
|
raise StringFormattingException("key is not hashable")
|
||||||
|
|
||||||
|
output[resolved_key] = self._resolve_argument_type(
|
||||||
|
resolved_variables=resolved_variables, arg=value
|
||||||
|
)
|
||||||
|
|
||||||
|
return ResolvedMap(output)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ResolvedMap(Map, Resolvable):
|
||||||
|
pass
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Dict
|
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
|
|
@ -33,13 +32,17 @@ class Resolvable(Resolvable_0, Resolvable_1, Resolvable_2, ABC):
|
||||||
return str(self.value)
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
class Hashable(Resolvable, ABC):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ResolvableT(Resolvable, ABC, Generic[T]):
|
class ResolvableT(Hashable, ABC, Generic[T]):
|
||||||
value: T
|
value: T
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Numeric(ResolvableT[NumericT], ABC, Generic[NumericT]):
|
class Numeric(ResolvableT[NumericT], Hashable, ABC, Generic[NumericT]):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -54,18 +57,10 @@ class Float(Numeric[float], ArgumentType):
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Boolean(ResolvableT[bool], ArgumentType):
|
class Boolean(ResolvableT[bool], Hashable, ArgumentType):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class String(ResolvableT[str], ArgumentType):
|
class String(ResolvableT[str], Hashable, ArgumentType):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Map(Resolvable, ArgumentType):
|
|
||||||
value: Dict[Resolvable, Resolvable]
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"[{', '.join([val.value for val in self.value])}]"
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ class TestParser:
|
||||||
parsed_empty = parse("hello {%map()}")
|
parsed_empty = parse("hello {%map()}")
|
||||||
parsed_with_var = parse("hello {%map([variable_name, 'elem2'])}")
|
parsed_with_var = parse("hello {%map([variable_name, 'elem2'])}")
|
||||||
parsed_extend = parse("hi {%map([variable_name, 'elem2'], ['elem3', variable_name])}")
|
parsed_extend = parse("hi {%map([variable_name, 'elem2'], ['elem3', variable_name])}")
|
||||||
|
parse_raw_map = parse("hello {{'key': 'value'}}")
|
||||||
parsed_extend.resolve({})
|
parsed_extend.resolve({})
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,5 @@ class TestSyntaxTree:
|
||||||
"b": SyntaxTree(ast=[Function(name="capitalize", args=[Variable("b_")])]),
|
"b": SyntaxTree(ast=[Function(name="capitalize", args=[Variable("b_")])]),
|
||||||
"b_": SyntaxTree(ast=[Variable("b")]),
|
"b_": SyntaxTree(ast=[Variable("b")]),
|
||||||
}
|
}
|
||||||
_ = SyntaxTree.resolve_overrides(parsed_overrides=overrides)
|
|
||||||
with pytest.raises(StringFormattingException):
|
with pytest.raises(StringFormattingException):
|
||||||
_ = SyntaxTree.resolve_overrides(parsed_overrides=overrides)
|
_ = SyntaxTree.resolve_overrides(parsed_overrides=overrides)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue