tesssstssss

This commit is contained in:
Jesse Bannon 2023-11-09 18:47:56 -08:00
parent 2fc286662a
commit a01c658fed
11 changed files with 186 additions and 97 deletions

View file

@ -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")

View file

@ -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

View file

@ -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):

View file

@ -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):

View file

@ -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}"),

View file

@ -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"),

View file

@ -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)

View file

View 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]")}

View 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({})}