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 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.function import ArgumentType
|
||||
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 Integer
|
||||
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 Variable
|
||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||
|
|
@ -225,6 +224,7 @@ class _Parser:
|
|||
"""
|
||||
output: Dict[ArgumentType, ArgumentType] = {}
|
||||
key: Optional[ArgumentType] = None
|
||||
in_comma = False
|
||||
|
||||
while ch := self._read(increment_pos=False):
|
||||
if ch == "}":
|
||||
|
|
@ -233,7 +233,17 @@ class _Parser:
|
|||
|
||||
self._pos += 1
|
||||
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:
|
||||
in_comma = False
|
||||
key_args = self._parse_args(breaking_chars=":")
|
||||
if len(key_args) != 1:
|
||||
raise StringFormattingException("Lazy parsing but got mlutiple args")
|
||||
|
|
@ -255,7 +265,7 @@ class _Parser:
|
|||
while ch := self._read():
|
||||
if ch == "}":
|
||||
bracket_counter -= 1
|
||||
break
|
||||
continue
|
||||
if ch == "{":
|
||||
bracket_counter += 1
|
||||
if literal_str:
|
||||
|
|
@ -284,8 +294,12 @@ class _Parser:
|
|||
self._ast.append(self._parse_map())
|
||||
else:
|
||||
self._ast.append(self._parse_variable())
|
||||
else:
|
||||
elif bracket_counter == 0:
|
||||
# Only accumulate literal str if not in brackets
|
||||
literal_str += ch
|
||||
else:
|
||||
# Should only be possible to get here if it's a space
|
||||
assert ch.isspace()
|
||||
|
||||
if bracket_counter != 0:
|
||||
raise StringFormattingException("Bracket count mismatch")
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from typing import Dict
|
|||
from typing import Optional
|
||||
|
||||
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.syntax_tree import SyntaxTree
|
||||
from ytdl_sub.script.types.variable import Variable
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ from ytdl_sub.script.types.variable_dependency import VariableDependency
|
|||
class Array:
|
||||
value: List[Resolvable]
|
||||
|
||||
def __str__(self):
|
||||
return f"[{', '.join([str(val.value) for val in self.value])}]"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UnresolvedArray(Array, VariableDependency, ArgumentType):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
|
@ -16,6 +17,22 @@ from ytdl_sub.utils.exceptions import StringFormattingException
|
|||
class Map:
|
||||
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)
|
||||
class UnresolvedMap(Map, VariableDependency, ArgumentType):
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
|
||||
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.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 String
|
||||
from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
||||
from ytdl_sub.script.types.variable import Variable
|
||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||
|
||||
|
|
@ -28,28 +29,28 @@ class TestParser:
|
|||
]
|
||||
)
|
||||
|
||||
def test_array(self):
|
||||
parsed = parse("hello {['elem1', 'elem2']}")
|
||||
parsed_empty = parse("hello {[]}")
|
||||
parsed_with_var = parse("hello {['elem1', variable_name]}")
|
||||
parsed_extend = parse(
|
||||
"hi {%at(%flatten_array(%extend(['elem1', 'elem2'], ['elem3'], [['elem4'], ['elem5', 'elem6']], ['elem7'])), 1)}"
|
||||
)
|
||||
parsed_extend.resolve({})
|
||||
assert False
|
||||
|
||||
def test_map(self):
|
||||
parsed = parse("hello {%map(['elem1', 'elem2'])}")
|
||||
parsed_empty = parse("hello {%map()}")
|
||||
parsed_with_var = parse("hello {%map([variable_name, 'elem2'])}")
|
||||
parsed_extend = parse("hi {%map([variable_name, 'elem2'], ['elem3', variable_name])}")
|
||||
parse_raw_map = parse("hello {{'key': 'value'}}")
|
||||
parsed_extend.resolve({})
|
||||
assert False
|
||||
|
||||
def test_function_argument(self):
|
||||
parsed = parse("hello {%map([$1, $2])}")
|
||||
assert False
|
||||
# def test_array(self):
|
||||
# parsed = parse("hello {['elem1', 'elem2']}")
|
||||
# parsed_empty = parse("hello {[]}")
|
||||
# parsed_with_var = parse("hello {['elem1', variable_name]}")
|
||||
# parsed_extend = parse(
|
||||
# "hi {%at(%flatten_array(%extend(['elem1', 'elem2'], ['elem3'], [['elem4'], ['elem5', 'elem6']], ['elem7'])), 1)}"
|
||||
# )
|
||||
# parsed_extend.resolve({})
|
||||
# assert False
|
||||
#
|
||||
# def test_map(self):
|
||||
# parsed = parse("hello {%map(['elem1', 'elem2'])}")
|
||||
# parsed_empty = parse("hello {%map()}")
|
||||
# parsed_with_var = parse("hello {%map([variable_name, 'elem2'])}")
|
||||
# parsed_extend = parse("hi {%map([variable_name, 'elem2'], ['elem3', variable_name])}")
|
||||
# parse_raw_map = parse("hello {{'key': 'value'}}")
|
||||
# parsed_extend.resolve({})
|
||||
# assert False
|
||||
#
|
||||
# def test_function_argument(self):
|
||||
# parsed = parse("hello {%map([$1, $2])}")
|
||||
# assert False
|
||||
|
||||
def test_conditional(self):
|
||||
parsed = parse("hello {%if(True, 'hi', 3.4)}")
|
||||
|
|
@ -141,13 +142,18 @@ class TestParser:
|
|||
]
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("whitespace", ["", " ", " ", "\n", " \n "])
|
||||
def test_single_function_multiple_args(self, whitespace: str):
|
||||
@pytest.mark.parametrize("whitespace", [None, " ", " ", "\n", " \n "])
|
||||
def test_single_function_multiple_args(self, whitespace: Optional[str]):
|
||||
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"{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(
|
||||
[
|
||||
String(value=f"hello{s}"),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
from typing import Dict
|
||||
|
||||
import pytest
|
||||
|
||||
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.variable import Variable
|
||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||
|
||||
|
||||
|
|
@ -19,17 +14,24 @@ class TestSyntaxTree:
|
|||
"bb": "b",
|
||||
"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):
|
||||
assert Script({"a": "a", "b": "{b_}", "b_": "b",}).resolve() == {
|
||||
assert Script({"a": "a", "b": "{b_}", "b_": "b"}).resolve() == {
|
||||
"a": String("a"),
|
||||
"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):
|
||||
assert Script({"a": "a", "b": "{%capitalize(b_)}", "b_": "b",}).resolve() == {
|
||||
assert Script({"a": "a", "b": "{%capitalize(b_)}", "b_": "b"}).resolve() == {
|
||||
"a": String("a"),
|
||||
"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