diff --git a/src/ytdl_sub/script/parser.py b/src/ytdl_sub/script/parser.py index d316a3fd..86ea1e2e 100644 --- a/src/ytdl_sub/script/parser.py +++ b/src/ytdl_sub/script/parser.py @@ -10,6 +10,7 @@ from ytdl_sub.script.types.map import UnresolvedMap from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import Integer +from ytdl_sub.script.types.resolvable import NonHashable from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.types.variable import FunctionArgument @@ -27,6 +28,9 @@ MAP_KEY_MULTIPLE_VALUES = InvalidSyntaxException( "Map key has multiple values when there should only be one" ) MAP_MISSING_KEY = InvalidSyntaxException("Map has a missing key") +MAP_KEY_NOT_HASHABLE = InvalidSyntaxException( + "Map key must be a hashable type (Integer, Float, Boolean, String)" +) class _Parser: @@ -283,6 +287,8 @@ class _Parser: raise MAP_KEY_WITH_NO_VALUE if len(value_args) > 1: raise StringFormattingException("map has key with multiple values") + if isinstance(key, NonHashable): + raise MAP_KEY_NOT_HASHABLE output[key] = value_args[0] key = None diff --git a/src/ytdl_sub/script/types/array.py b/src/ytdl_sub/script/types/array.py index 47dfb48d..3f7e1343 100644 --- a/src/ytdl_sub/script/types/array.py +++ b/src/ytdl_sub/script/types/array.py @@ -4,6 +4,7 @@ from typing import List from typing import Set from ytdl_sub.script.types.resolvable import ArgumentType +from ytdl_sub.script.types.resolvable import NonHashable from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable @@ -11,7 +12,7 @@ from ytdl_sub.script.types.variable_dependency import VariableDependency @dataclass(frozen=True) -class Array: +class Array(NonHashable): value: List[Resolvable] def __str__(self): diff --git a/src/ytdl_sub/script/types/map.py b/src/ytdl_sub/script/types/map.py index 30f6ec26..2920104b 100644 --- a/src/ytdl_sub/script/types/map.py +++ b/src/ytdl_sub/script/types/map.py @@ -6,6 +6,7 @@ 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 NonHashable from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable @@ -14,7 +15,7 @@ from ytdl_sub.utils.exceptions import StringFormattingException @dataclass(frozen=True) -class Map: +class Map(NonHashable): value: Dict[Hashable, Resolvable] def to_native(self) -> Dict: diff --git a/src/ytdl_sub/script/types/resolvable.py b/src/ytdl_sub/script/types/resolvable.py index 71e318dd..a5e548cd 100644 --- a/src/ytdl_sub/script/types/resolvable.py +++ b/src/ytdl_sub/script/types/resolvable.py @@ -36,6 +36,10 @@ class Hashable(Resolvable, ABC): pass +class NonHashable(ABC): + pass + + @dataclass(frozen=True) class ResolvableT(Hashable, ABC, Generic[T]): value: T diff --git a/tests/unit/script/types/test_map.py b/tests/unit/script/types/test_map.py index e75eb703..efedf6b0 100644 --- a/tests/unit/script/types/test_map.py +++ b/tests/unit/script/types/test_map.py @@ -3,6 +3,7 @@ import re import pytest from ytdl_sub.script.parser import MAP_KEY_MULTIPLE_VALUES +from ytdl_sub.script.parser import MAP_KEY_NOT_HASHABLE from ytdl_sub.script.parser import MAP_KEY_WITH_NO_VALUE from ytdl_sub.script.parser import MAP_MISSING_KEY from ytdl_sub.script.parser import UNEXPECTED_ARGUMENT @@ -124,3 +125,16 @@ class TestMap: def test_map_missing_key(self, value: str): with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_MISSING_KEY))): Script({"map": value}).resolve() + + @pytest.mark.parametrize( + "value", + [ + "{{{}:'value'}}", + "{{ {} : 'value' }}", + "{{[]:'value'}}", + "{{ [] : 'value' }}", + ], + ) + def test_map_key_not_hashable(self, value: str): + with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_KEY_NOT_HASHABLE))): + Script({"map": value}).resolve()