tesssstssss
This commit is contained in:
parent
2fc286662a
commit
a01c658fed
11 changed files with 186 additions and 97 deletions
|
|
@ -2,8 +2,6 @@ from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from ytdl_sub.script.syntax_tree import SyntaxTree
|
|
||||||
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
|
||||||
|
|
@ -12,6 +10,7 @@ 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
|
||||||
from ytdl_sub.script.types.resolvable import String
|
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
|
from ytdl_sub.script.types.variable import FunctionArgument
|
||||||
from ytdl_sub.script.types.variable import Variable
|
from ytdl_sub.script.types.variable import Variable
|
||||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
|
|
@ -225,6 +224,7 @@ class _Parser:
|
||||||
"""
|
"""
|
||||||
output: Dict[ArgumentType, ArgumentType] = {}
|
output: Dict[ArgumentType, ArgumentType] = {}
|
||||||
key: Optional[ArgumentType] = None
|
key: Optional[ArgumentType] = None
|
||||||
|
in_comma = False
|
||||||
|
|
||||||
while ch := self._read(increment_pos=False):
|
while ch := self._read(increment_pos=False):
|
||||||
if ch == "}":
|
if ch == "}":
|
||||||
|
|
@ -233,7 +233,17 @@ class _Parser:
|
||||||
|
|
||||||
self._pos += 1
|
self._pos += 1
|
||||||
return UnresolvedMap(value=output)
|
return UnresolvedMap(value=output)
|
||||||
|
elif ch == ",":
|
||||||
|
if in_comma:
|
||||||
|
raise StringFormattingException("Comma followed by comma")
|
||||||
|
if key is not None:
|
||||||
|
raise StringFormattingException("key followed by comma")
|
||||||
|
if output is None:
|
||||||
|
raise StringFormattingException("Empty dict with comma")
|
||||||
|
in_comma = True
|
||||||
|
self._pos += 1
|
||||||
elif key is None:
|
elif key is None:
|
||||||
|
in_comma = False
|
||||||
key_args = self._parse_args(breaking_chars=":")
|
key_args = self._parse_args(breaking_chars=":")
|
||||||
if len(key_args) != 1:
|
if len(key_args) != 1:
|
||||||
raise StringFormattingException("Lazy parsing but got mlutiple args")
|
raise StringFormattingException("Lazy parsing but got mlutiple args")
|
||||||
|
|
@ -255,7 +265,7 @@ class _Parser:
|
||||||
while ch := self._read():
|
while ch := self._read():
|
||||||
if ch == "}":
|
if ch == "}":
|
||||||
bracket_counter -= 1
|
bracket_counter -= 1
|
||||||
break
|
continue
|
||||||
if ch == "{":
|
if ch == "{":
|
||||||
bracket_counter += 1
|
bracket_counter += 1
|
||||||
if literal_str:
|
if literal_str:
|
||||||
|
|
@ -284,8 +294,12 @@ class _Parser:
|
||||||
self._ast.append(self._parse_map())
|
self._ast.append(self._parse_map())
|
||||||
else:
|
else:
|
||||||
self._ast.append(self._parse_variable())
|
self._ast.append(self._parse_variable())
|
||||||
else:
|
elif bracket_counter == 0:
|
||||||
|
# Only accumulate literal str if not in brackets
|
||||||
literal_str += ch
|
literal_str += ch
|
||||||
|
else:
|
||||||
|
# Should only be possible to get here if it's a space
|
||||||
|
assert ch.isspace()
|
||||||
|
|
||||||
if bracket_counter != 0:
|
if bracket_counter != 0:
|
||||||
raise StringFormattingException("Bracket count mismatch")
|
raise StringFormattingException("Bracket count mismatch")
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from typing import Dict
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from ytdl_sub.script.parser import parse
|
from ytdl_sub.script.parser import parse
|
||||||
from ytdl_sub.script.syntax_tree import SyntaxTree
|
|
||||||
from ytdl_sub.script.types.resolvable import Resolvable
|
from ytdl_sub.script.types.resolvable import Resolvable
|
||||||
|
from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
||||||
from ytdl_sub.script.types.variable import Variable
|
from ytdl_sub.script.types.variable import Variable
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ from ytdl_sub.script.types.variable_dependency import VariableDependency
|
||||||
class Array:
|
class Array:
|
||||||
value: List[Resolvable]
|
value: List[Resolvable]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"[{', '.join([str(val.value) for val in self.value])}]"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class UnresolvedArray(Array, VariableDependency, ArgumentType):
|
class UnresolvedArray(Array, VariableDependency, ArgumentType):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
@ -16,6 +17,22 @@ from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
class Map:
|
class Map:
|
||||||
value: Dict[Hashable, Resolvable]
|
value: Dict[Hashable, Resolvable]
|
||||||
|
|
||||||
|
def to_native(self) -> Dict:
|
||||||
|
output = {}
|
||||||
|
for key, value in self.value.items():
|
||||||
|
native_key = key.value
|
||||||
|
if isinstance(value, Map):
|
||||||
|
native_value = value.to_native()
|
||||||
|
else:
|
||||||
|
native_value = value.value
|
||||||
|
|
||||||
|
output[native_key] = native_value
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return json.dumps(self.to_native())
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class UnresolvedMap(Map, VariableDependency, ArgumentType):
|
class UnresolvedMap(Map, VariableDependency, ArgumentType):
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
|
from typing import Optional
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ytdl_sub.script.parser import parse
|
from ytdl_sub.script.parser import parse
|
||||||
from ytdl_sub.script.syntax_tree import SyntaxTree
|
|
||||||
from ytdl_sub.script.types.function import BuiltInFunction
|
from ytdl_sub.script.types.function import BuiltInFunction
|
||||||
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
|
||||||
from ytdl_sub.script.types.resolvable import String
|
from ytdl_sub.script.types.resolvable import String
|
||||||
|
from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
||||||
from ytdl_sub.script.types.variable import Variable
|
from ytdl_sub.script.types.variable import Variable
|
||||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
|
|
||||||
|
|
@ -28,28 +29,28 @@ class TestParser:
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_array(self):
|
# def test_array(self):
|
||||||
parsed = parse("hello {['elem1', 'elem2']}")
|
# parsed = parse("hello {['elem1', 'elem2']}")
|
||||||
parsed_empty = parse("hello {[]}")
|
# parsed_empty = parse("hello {[]}")
|
||||||
parsed_with_var = parse("hello {['elem1', variable_name]}")
|
# parsed_with_var = parse("hello {['elem1', variable_name]}")
|
||||||
parsed_extend = parse(
|
# parsed_extend = parse(
|
||||||
"hi {%at(%flatten_array(%extend(['elem1', 'elem2'], ['elem3'], [['elem4'], ['elem5', 'elem6']], ['elem7'])), 1)}"
|
# "hi {%at(%flatten_array(%extend(['elem1', 'elem2'], ['elem3'], [['elem4'], ['elem5', 'elem6']], ['elem7'])), 1)}"
|
||||||
)
|
# )
|
||||||
parsed_extend.resolve({})
|
# parsed_extend.resolve({})
|
||||||
assert False
|
# assert False
|
||||||
|
#
|
||||||
def test_map(self):
|
# def test_map(self):
|
||||||
parsed = parse("hello {%map(['elem1', 'elem2'])}")
|
# parsed = parse("hello {%map(['elem1', 'elem2'])}")
|
||||||
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'}}")
|
# parse_raw_map = parse("hello {{'key': 'value'}}")
|
||||||
parsed_extend.resolve({})
|
# parsed_extend.resolve({})
|
||||||
assert False
|
# assert False
|
||||||
|
#
|
||||||
def test_function_argument(self):
|
# def test_function_argument(self):
|
||||||
parsed = parse("hello {%map([$1, $2])}")
|
# parsed = parse("hello {%map([$1, $2])}")
|
||||||
assert False
|
# assert False
|
||||||
|
|
||||||
def test_conditional(self):
|
def test_conditional(self):
|
||||||
parsed = parse("hello {%if(True, 'hi', 3.4)}")
|
parsed = parse("hello {%if(True, 'hi', 3.4)}")
|
||||||
|
|
@ -141,13 +142,18 @@ class TestParser:
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.parametrize("whitespace", ["", " ", " ", "\n", " \n "])
|
@pytest.mark.parametrize("whitespace", [None, " ", " ", "\n", " \n "])
|
||||||
def test_single_function_multiple_args(self, whitespace: str):
|
def test_single_function_multiple_args(self, whitespace: Optional[str]):
|
||||||
s = whitespace
|
s = whitespace
|
||||||
parsed = parse(
|
if s is None:
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
input_str = (
|
||||||
f"hello{s}{{{s}%concat({s}'string'{s},{s}%string(1){s},{s}%string(2.4){s},"
|
f"hello{s}{{{s}%concat({s}'string'{s},{s}%string(1){s},{s}%string(2.4){s},"
|
||||||
f"{s}%string(TRUE){s},{s}%string(variable_name){s},{s}%capitalize({s}'hi'{s}){s}){s}}}"
|
f"{s}%string(TRUE){s},{s}%string(variable_name){s},{s}%capitalize({s}'hi'{s}){s})}}"
|
||||||
|
f"{s}"
|
||||||
)
|
)
|
||||||
|
parsed = parse(input_str)
|
||||||
assert parsed == SyntaxTree(
|
assert parsed == SyntaxTree(
|
||||||
[
|
[
|
||||||
String(value=f"hello{s}"),
|
String(value=f"hello{s}"),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ytdl_sub.script.script import Script
|
from ytdl_sub.script.script import Script
|
||||||
from ytdl_sub.script.syntax_tree import SyntaxTree
|
|
||||||
from ytdl_sub.script.types.function import Function
|
|
||||||
from ytdl_sub.script.types.resolvable import String
|
from ytdl_sub.script.types.resolvable import String
|
||||||
from ytdl_sub.script.types.variable import Variable
|
|
||||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,17 +14,24 @@ class TestSyntaxTree:
|
||||||
"bb": "b",
|
"bb": "b",
|
||||||
"cc": "{%custom_func(aa, bb)}",
|
"cc": "{%custom_func(aa, bb)}",
|
||||||
}
|
}
|
||||||
).resolve() == {"aa": String("a"), "bb": String("b"), "cc": String("return [aa, bb]")}
|
).resolve() == {"aa": String("a"), "bb": String("b"), "cc": String("return [a, b]")}
|
||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
assert Script({"a": "a", "b": "{b_}", "b_": "b",}).resolve() == {
|
assert Script({"a": "a", "b": "{b_}", "b_": "b"}).resolve() == {
|
||||||
"a": String("a"),
|
"a": String("a"),
|
||||||
"b": String("b"),
|
"b": String("b"),
|
||||||
"b_": String("b"),
|
"b_": String("b"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def test_multiple_variables(self):
|
||||||
|
assert Script({"a": "a", "b": "b", "b_": " {a} {b} "}).resolve() == {
|
||||||
|
"a": String("a"),
|
||||||
|
"b": String("b"),
|
||||||
|
"b_": String(" a b "),
|
||||||
|
}
|
||||||
|
|
||||||
def test_simple_with_function(self):
|
def test_simple_with_function(self):
|
||||||
assert Script({"a": "a", "b": "{%capitalize(b_)}", "b_": "b",}).resolve() == {
|
assert Script({"a": "a", "b": "{%capitalize(b_)}", "b_": "b"}).resolve() == {
|
||||||
"a": String("a"),
|
"a": String("a"),
|
||||||
"b": String("B"),
|
"b": String("B"),
|
||||||
"b_": String("b"),
|
"b_": String("b"),
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from ytdl_sub.script.syntax_tree import SyntaxTree
|
|
||||||
from ytdl_sub.script.types.function import Function
|
|
||||||
from ytdl_sub.script.types.resolvable import String
|
|
||||||
from ytdl_sub.script.types.variable import Variable
|
|
||||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
|
||||||
|
|
||||||
|
|
||||||
class TestSyntaxTree:
|
|
||||||
def test_simple(self):
|
|
||||||
overrides: Dict[str, SyntaxTree] = {
|
|
||||||
"a": SyntaxTree(ast=[String("a")]),
|
|
||||||
"b": SyntaxTree(ast=[Variable("b_")]),
|
|
||||||
"b_": SyntaxTree(ast=[String("b")]),
|
|
||||||
}
|
|
||||||
|
|
||||||
resolved = SyntaxTree.resolve_overrides(parsed_overrides=overrides)
|
|
||||||
assert resolved == {
|
|
||||||
"a": String(value="a"),
|
|
||||||
"b": String(value="b"),
|
|
||||||
"b_": String(value="b"),
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_simple_with_function(self):
|
|
||||||
overrides: Dict[str, SyntaxTree] = {
|
|
||||||
"a": SyntaxTree(ast=[String("a")]),
|
|
||||||
"b": SyntaxTree(ast=[Function(name="capitalize", args=[Variable("b_")])]),
|
|
||||||
"b_": SyntaxTree(ast=[String("b")]),
|
|
||||||
}
|
|
||||||
|
|
||||||
resolved = SyntaxTree.resolve_overrides(parsed_overrides=overrides)
|
|
||||||
assert resolved == {
|
|
||||||
"a": String(value="a"),
|
|
||||||
"b": String(value="B"),
|
|
||||||
"b_": String(value="b"),
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_simple_cycle(self):
|
|
||||||
overrides: Dict[str, SyntaxTree] = {
|
|
||||||
"a": SyntaxTree(ast=[Variable("b")]),
|
|
||||||
"b": SyntaxTree(ast=[Variable("a")]),
|
|
||||||
}
|
|
||||||
|
|
||||||
with pytest.raises(StringFormattingException):
|
|
||||||
_ = SyntaxTree.resolve_overrides(parsed_overrides=overrides)
|
|
||||||
|
|
||||||
def test_simple_cycle_with_function(self):
|
|
||||||
overrides: Dict[str, SyntaxTree] = {
|
|
||||||
"a": SyntaxTree(ast=[String("a")]),
|
|
||||||
"b": SyntaxTree(ast=[Function(name="capitalize", args=[Variable("b_")])]),
|
|
||||||
"b_": SyntaxTree(ast=[Variable("b")]),
|
|
||||||
}
|
|
||||||
with pytest.raises(StringFormattingException):
|
|
||||||
_ = SyntaxTree.resolve_overrides(parsed_overrides=overrides)
|
|
||||||
0
tests/unit/script/types/__init__.py
Normal file
0
tests/unit/script/types/__init__.py
Normal file
48
tests/unit/script/types/test_array.py
Normal file
48
tests/unit/script/types/test_array.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
from ytdl_sub.script.script import Script
|
||||||
|
from ytdl_sub.script.types.array import ResolvedArray
|
||||||
|
from ytdl_sub.script.types.resolvable import Float
|
||||||
|
from ytdl_sub.script.types.resolvable import String
|
||||||
|
|
||||||
|
|
||||||
|
class TestArray:
|
||||||
|
def test_return(self):
|
||||||
|
assert Script({"array": "{['a', 3.14]}"}).resolve() == {
|
||||||
|
"array": ResolvedArray([String("a"), Float(3.14)])
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_return_as_str(self):
|
||||||
|
assert Script({"array": "str: {['a', 3.14]}"}).resolve() == {
|
||||||
|
"array": String("str: [a, 3.14]")
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_nested_array(self):
|
||||||
|
assert Script(
|
||||||
|
{"array": "{['level1', ['level2', ['level3', 'level3'], 'level2'], 'level1']}"}
|
||||||
|
).resolve() == {
|
||||||
|
"array": ResolvedArray(
|
||||||
|
[
|
||||||
|
String("level1"),
|
||||||
|
ResolvedArray(
|
||||||
|
[
|
||||||
|
String("level2"),
|
||||||
|
ResolvedArray([String("level3"), String("level3")]),
|
||||||
|
String("level2"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
String("level1"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_empty_array(self):
|
||||||
|
assert Script({"array": "{[]}"}).resolve() == {"array": ResolvedArray([])}
|
||||||
|
|
||||||
|
def test_custom_function(self):
|
||||||
|
assert Script(
|
||||||
|
{
|
||||||
|
"%custom_func": "return {[$1, $2]}",
|
||||||
|
"aa": "a",
|
||||||
|
"bb": "b",
|
||||||
|
"cc": "{%custom_func(aa, bb)}",
|
||||||
|
}
|
||||||
|
).resolve() == {"aa": String("a"), "bb": String("b"), "cc": String("return [a, b]")}
|
||||||
56
tests/unit/script/types/test_map.py
Normal file
56
tests/unit/script/types/test_map.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
from ytdl_sub.script.script import Script
|
||||||
|
from ytdl_sub.script.types.map import ResolvedMap
|
||||||
|
from ytdl_sub.script.types.resolvable import Float
|
||||||
|
from ytdl_sub.script.types.resolvable import String
|
||||||
|
|
||||||
|
|
||||||
|
class TestMap:
|
||||||
|
def test_return(self):
|
||||||
|
assert Script({"map": "{{'a': 3.14}}"}).resolve() == {
|
||||||
|
"map": ResolvedMap({String("a"): Float(3.14)})
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_return_as_str(self):
|
||||||
|
assert Script({"map": "json: {{'a': 3.14}}"}).resolve() == {
|
||||||
|
"map": String('json: {"a": 3.14}')
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_nested_map(self):
|
||||||
|
map_str = """{
|
||||||
|
{
|
||||||
|
'level1': {
|
||||||
|
'level2': {
|
||||||
|
'level3': {
|
||||||
|
'level4_key': 'level4_value'
|
||||||
|
},
|
||||||
|
'level3_key': 'level3_value'
|
||||||
|
},
|
||||||
|
'level2_key': 'level2_value'
|
||||||
|
},
|
||||||
|
'level1_key': 'level1_value'
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
assert Script({"map": map_str}).resolve() == {
|
||||||
|
"map": ResolvedMap(
|
||||||
|
{
|
||||||
|
String("level1"): ResolvedMap(
|
||||||
|
{
|
||||||
|
String("level2"): ResolvedMap(
|
||||||
|
{
|
||||||
|
String("level3"): ResolvedMap(
|
||||||
|
{String("level4_key"): String("level4_value")}
|
||||||
|
),
|
||||||
|
String("level3_key"): String("level3_value"),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
String("level2_key"): String("level2_value"),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
String("level1_key"): String("level1_value"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_empty_map(self):
|
||||||
|
assert Script({"map": "{{}}"}).resolve() == {"map": ResolvedMap({})}
|
||||||
Loading…
Reference in a new issue