This commit is contained in:
Jesse Bannon 2024-04-02 00:53:39 -07:00
parent c622cf68b5
commit 5482e9931c
6 changed files with 145 additions and 4 deletions

View file

@ -17,11 +17,11 @@ from ytdl_sub.utils.exceptions import StringFormattingException
from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.script import ScriptUtils
from ytdl_sub.utils.scriptable import Scriptable
from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.string_formatter_validators import UnstructuredDictFormatterValidator
class Overrides(DictFormatterValidator, Scriptable):
class Overrides(UnstructuredDictFormatterValidator, Scriptable):
"""
Allows you to define variables that can be used in any EntryFormatter or OverridesFormatter.
@ -51,11 +51,11 @@ class Overrides(DictFormatterValidator, Scriptable):
@classmethod
def partial_validate(cls, name: str, value: Any) -> None:
dict_formatter = DictFormatterValidator(name=name, value=value)
dict_formatter = UnstructuredDictFormatterValidator(name=name, value=value)
_ = [parse(format_string) for format_string in dict_formatter.dict_with_format_strings]
def __init__(self, name, value):
DictFormatterValidator.__init__(self, name, value)
UnstructuredDictFormatterValidator.__init__(self, name, value)
Scriptable.__init__(self, initialize_base_script=True)
for key in self._keys:

View file

@ -3,7 +3,21 @@ import re
from typing import Any
from typing import Dict
from ytdl_sub.script.parser import parse
from ytdl_sub.script.script import _is_function
from ytdl_sub.script.types.array import Array
from ytdl_sub.script.types.array import UnresolvedArray
from ytdl_sub.script.types.function import BuiltInFunction
from ytdl_sub.script.types.function import Function
from ytdl_sub.script.types.map import UnresolvedMap
from ytdl_sub.script.types.resolvable import AnyArgument
from ytdl_sub.script.types.resolvable import Argument
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
class ScriptUtils:
@ -43,6 +57,64 @@ class ScriptUtils:
return out
@classmethod
def _to_script_argument(cls, value: Any) -> Argument:
# Handle simple types as above
if value is None or (isinstance(value, str) and value == ""):
return String("")
if isinstance(value, str):
ast = parse(text=value).ast
if len(ast) == 1:
return ast[0]
return BuiltInFunction(name="concat", args=ast)
if isinstance(value, int):
return Integer(value)
if isinstance(value, float):
return Float(value)
if isinstance(value, bool):
return Boolean(value)
if isinstance(value, list):
return UnresolvedArray([cls._to_script_argument(val) for val in value])
if isinstance(value, dict):
return UnresolvedMap(
{
cls._to_script_argument(key): cls._to_script_argument(val)
for key, val in value.items()
}
)
assert False, "should never reach here"
@classmethod
def _to_script_code(cls, arg: Argument, top_level: bool = False) -> str:
if not top_level and isinstance(arg, (Integer, Boolean, Float)):
return str(arg.native)
if isinstance(arg, String):
if arg.native == "":
return "" if top_level else "''"
return arg.native if top_level else f"'''{arg.native}'''"
elif isinstance(arg, Integer):
out = f"%int({arg.native})"
elif isinstance(arg, Boolean):
out = f"%bool({arg.native})"
elif isinstance(arg, Float):
out = f"%float({arg.native})"
elif isinstance(arg, UnresolvedArray):
out = f"[ {', '.join(cls._to_script_code(val) for val in arg.value)} ]"
elif isinstance(arg, UnresolvedMap):
out = f"{{ {', '.join(f'{cls._to_script_code(key)}: {cls._to_script_code(val)}' for key, val in arg.value.items())} }}"
elif isinstance(arg, Variable):
out = arg.name
elif isinstance(arg, Function):
out = f"%{arg.name}( {', '.join(cls._to_script_code(val) for val in arg.args)} )"
else:
assert False, "ack"
return f"{{ {out} }}" if top_level else out
@classmethod
def to_native_script(cls, value: Any) -> str:
return cls._to_script_code(cls._to_script_argument(value), top_level=True)
@classmethod
def bool_formatter_output(cls, output: str) -> bool:
"""

View file

@ -11,6 +11,7 @@ from ytdl_sub.script.utils.exceptions import RuntimeException
from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
from ytdl_sub.script.utils.exceptions import UserException
from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException
from ytdl_sub.utils.script import ScriptUtils
from ytdl_sub.validators.validators import DictValidator
from ytdl_sub.validators.validators import ListValidator
from ytdl_sub.validators.validators import LiteralDictValidator
@ -148,6 +149,18 @@ class OverridesDictFormatterValidator(DictFormatterValidator):
_key_validator = OverridesStringFormatterValidator
class UnstructuredDictFormatterValidator(DictFormatterValidator):
def __init__(self, name, value):
# Convert the unstructured-ness into a script
if isinstance(value, dict):
value = {key: ScriptUtils.to_native_script(val) for key, val in value.items()}
super().__init__(name, value)
class UnstructuredOverridesDictFormatterValidator(UnstructuredDictFormatterValidator):
_key_validator = OverridesStringFormatterValidator
def to_variable_dependency_format_string(script: Script, parsed_format_string: SyntaxTree) -> str:
"""
Create a dummy format string that contains all variable deps as a string.

View file

@ -34,6 +34,8 @@ def single_video_preset_dict(output_directory):
"overrides": {
"music_video_artist": "JMC",
"music_video_directory": output_directory,
"test_override_map": {"{music_video_artist}": "{music_video_directory}"},
"test_override_map_get": "{ %map_get(test_override_map, music_video_artist) }",
},
}

View file

@ -3,6 +3,7 @@ import copy
import pytest
from unit.script.conftest import single_variable_output
from ytdl_sub.script.parser import parse
from ytdl_sub.utils.script import ScriptUtils
@ -51,3 +52,10 @@ class TestScriptUtils:
)
def test_bool_formatter_output(self, input_str: str, expected_output: bool):
assert ScriptUtils.bool_formatter_output(input_str) == expected_output
def test_to_syntax_tree(self):
out = ScriptUtils.to_native_script(
{"{var_a}": "{var_b}", "static_a": "string with {var_c} in it"}
)
out2 = parse(out)
assert False

View file

@ -7,6 +7,10 @@ from ytdl_sub.validators.string_formatter_validators import DictFormatterValidat
from ytdl_sub.validators.string_formatter_validators import OverridesDictFormatterValidator
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.string_formatter_validators import UnstructuredDictFormatterValidator
from ytdl_sub.validators.string_formatter_validators import (
UnstructuredOverridesDictFormatterValidator,
)
@pytest.mark.parametrize(
@ -75,3 +79,45 @@ class TestDictFormatterValidator(object):
"key1": key1_format_string,
"key2": key2_format_string,
}
class TestUnstructuredDictFormatterValidator(object):
@pytest.mark.parametrize(
"dict_validator_class, expected_formatter_class",
[
(UnstructuredDictFormatterValidator, StringFormatterValidator),
(UnstructuredOverridesDictFormatterValidator, OverridesStringFormatterValidator),
],
)
def test_validates_values(self, dict_validator_class, expected_formatter_class):
key1_format_string = "string with {variable}"
key2_format_string = "no variables"
key3_int = 3
key4_float = 4.132
key5_bool = True
key6_map = {"{variable}_key": "value", "static_key": "{variable}_value"}
key7_list = ["list_1", "list_{variable_2}"]
validator = dict_validator_class(
name="validator",
value={
"key1": key1_format_string,
"key2": key2_format_string,
"key3": key3_int,
"key4": key4_float,
"key5": key5_bool,
"key6": key6_map,
"key7": key7_list,
},
)
assert len(validator.dict) == 7
assert all(isinstance(val, expected_formatter_class) for val in validator.dict.values())
assert validator.dict_with_format_strings == {
"key1": "string with {variable}",
"key2": "no variables",
"key3": "{%int(3)}",
"key4": "{%float(4.132)}",
"key5": "{%int(True)}",
"key6": '{%from_json(\'\'\'{"static_key": "{variable}_value", "{variable}_key": "value"}\'\'\')}',
"key7": "{%from_json('''[\"list_1\", \"list_{variable_2}\"]''')}",
}