OverridesResolver
This commit is contained in:
parent
fe095b151c
commit
ebf7989681
4 changed files with 147 additions and 11 deletions
65
src/ytdl_sub/script/overrides_resolver.py
Normal file
65
src/ytdl_sub/script/overrides_resolver.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from ytdl_sub.script.functions import Resolvable
|
||||||
|
from ytdl_sub.script.parser import parse
|
||||||
|
from ytdl_sub.script.syntax_tree import SyntaxTree
|
||||||
|
from ytdl_sub.script.syntax_tree import Variable
|
||||||
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
|
|
||||||
|
|
||||||
|
class OverridesResolver:
|
||||||
|
def __init__(self, overrides: Dict[str, str]):
|
||||||
|
self.overrides: Dict[Variable, SyntaxTree] = {
|
||||||
|
Variable(name=name): parse(value) for name, value in overrides.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def _ensure_no_cycles(self) -> None:
|
||||||
|
variable_dependencies: Dict[Variable, Set[Variable]] = {
|
||||||
|
variable: ast.variables for variable, ast in self.overrides.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def _traverse(
|
||||||
|
to_variable: Variable, visited_variables: Optional[List[Variable]] = None
|
||||||
|
) -> None:
|
||||||
|
if visited_variables is None:
|
||||||
|
visited_variables = []
|
||||||
|
|
||||||
|
if to_variable in visited_variables:
|
||||||
|
raise StringFormattingException("Detected cycle in variables")
|
||||||
|
visited_variables.append(to_variable)
|
||||||
|
|
||||||
|
for dep in variable_dependencies[to_variable]:
|
||||||
|
_traverse(to_variable=dep, visited_variables=visited_variables)
|
||||||
|
|
||||||
|
for variable in variable_dependencies.keys():
|
||||||
|
_traverse(variable)
|
||||||
|
|
||||||
|
def resolve_overrides(self) -> Dict[str, str]:
|
||||||
|
self._ensure_no_cycles()
|
||||||
|
|
||||||
|
unresolved_variables: List[Variable] = list(self.overrides.keys())
|
||||||
|
resolved_variables: Dict[Variable, Resolvable] = {}
|
||||||
|
|
||||||
|
while unresolved_variables:
|
||||||
|
unresolved_count: int = len(unresolved_variables)
|
||||||
|
|
||||||
|
for variable in unresolved_variables:
|
||||||
|
if not self.overrides[variable].has_variable_dependency(
|
||||||
|
resolved_variables=resolved_variables
|
||||||
|
):
|
||||||
|
resolved_variables[variable] = self.overrides[variable].resolve(
|
||||||
|
resolved_variables=resolved_variables
|
||||||
|
)
|
||||||
|
unresolved_variables.remove(variable)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
len(unresolved_variables) != unresolved_count
|
||||||
|
), "did not resolve any variables, cycle detected"
|
||||||
|
|
||||||
|
return {
|
||||||
|
variable.name: resolvable.resolve()
|
||||||
|
for variable, resolvable in resolved_variables.items()
|
||||||
|
}
|
||||||
|
|
@ -5,10 +5,10 @@ from ytdl_sub.script.functions import Boolean
|
||||||
from ytdl_sub.script.functions import Float
|
from ytdl_sub.script.functions import Float
|
||||||
from ytdl_sub.script.functions import Integer
|
from ytdl_sub.script.functions import Integer
|
||||||
from ytdl_sub.script.functions import String
|
from ytdl_sub.script.functions import String
|
||||||
from ytdl_sub.script.types import ArgumentType
|
from ytdl_sub.script.syntax_tree import ArgumentType
|
||||||
from ytdl_sub.script.types import Function
|
from ytdl_sub.script.syntax_tree import Function
|
||||||
from ytdl_sub.script.types import SyntaxTree
|
from ytdl_sub.script.syntax_tree import SyntaxTree
|
||||||
from ytdl_sub.script.types import Variable
|
from ytdl_sub.script.syntax_tree import Variable
|
||||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
from ytdl_sub.validators.string_formatter_validators import is_valid_source_variable_name
|
from ytdl_sub.validators.string_formatter_validators import is_valid_source_variable_name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
|
from abc import ABC
|
||||||
|
from abc import abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
from typing import final
|
||||||
|
|
||||||
from ytdl_sub.script.functions import Boolean
|
from ytdl_sub.script.functions import Boolean
|
||||||
from ytdl_sub.script.functions import Float
|
from ytdl_sub.script.functions import Float
|
||||||
from ytdl_sub.script.functions import Functions
|
from ytdl_sub.script.functions import Functions
|
||||||
from ytdl_sub.script.functions import Integer
|
from ytdl_sub.script.functions import Integer
|
||||||
|
from ytdl_sub.script.functions import Resolvable
|
||||||
from ytdl_sub.script.functions import String
|
from ytdl_sub.script.functions import String
|
||||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
|
|
||||||
|
|
@ -22,11 +26,33 @@ ArgumentType = Union[Integer, Float, String, Boolean, Variable, "Function"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Function:
|
class VariableDependency(ABC):
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def variables(self) -> Set[Variable]:
|
||||||
|
raise NotImplemented()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> str:
|
||||||
|
raise NotImplemented()
|
||||||
|
|
||||||
|
@final
|
||||||
|
def has_variable_dependency(self, resolved_variables: Dict[Variable, Resolvable]) -> bool:
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
True if variable dependency. False otherwise.
|
||||||
|
"""
|
||||||
|
return self.variables.issubset(set(resolved_variables.keys()))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Function(VariableDependency):
|
||||||
name: str
|
name: str
|
||||||
args: List[ArgumentType]
|
args: List[ArgumentType]
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
# TODO: Figure out resolution via introspecting args and outputs of function
|
||||||
try:
|
try:
|
||||||
getattr(Functions, self.name)
|
getattr(Functions, self.name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
|
@ -48,9 +74,12 @@ class Function:
|
||||||
|
|
||||||
return variables
|
return variables
|
||||||
|
|
||||||
|
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
|
||||||
|
raise NotImplemented()
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class SyntaxTree:
|
class SyntaxTree(VariableDependency):
|
||||||
ast: List[String | Variable | Function]
|
ast: List[String | Variable | Function]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -69,6 +98,20 @@ class SyntaxTree:
|
||||||
|
|
||||||
return variables
|
return variables
|
||||||
|
|
||||||
|
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
|
||||||
|
output: str = ""
|
||||||
|
for token in self.ast:
|
||||||
|
if isinstance(token, String):
|
||||||
|
output += token.resolve()
|
||||||
|
elif isinstance(token, Variable):
|
||||||
|
output += resolved_variables[token].resolve()
|
||||||
|
elif isinstance(token, Function):
|
||||||
|
output += token.resolve(resolved_variables=resolved_variables)
|
||||||
|
else:
|
||||||
|
assert False, "should never reach"
|
||||||
|
|
||||||
|
return String(output)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def detect_cycles(cls, parsed_overrides: Dict[str, "SyntaxTree"]) -> None:
|
def detect_cycles(cls, parsed_overrides: Dict[str, "SyntaxTree"]) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -98,5 +141,33 @@ class SyntaxTree:
|
||||||
_traverse(variable)
|
_traverse(variable)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve(cls, parsed_overrides: Dict[str, "SyntaxTree"]) -> Dict[str, str]:
|
def resolve_overrides(cls, parsed_overrides: Dict[str, "SyntaxTree"]) -> Dict[str, str]:
|
||||||
raise NotImplemented()
|
cls.detect_cycles(parsed_overrides=parsed_overrides)
|
||||||
|
|
||||||
|
overrides: Dict[Variable, "SyntaxTree"] = {
|
||||||
|
Variable(name): ast for name, ast in parsed_overrides.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
unresolved_variables: List[Variable] = list(overrides.keys())
|
||||||
|
resolved_variables: Dict[Variable, Resolvable] = {}
|
||||||
|
|
||||||
|
while unresolved_variables:
|
||||||
|
unresolved_count: int = len(unresolved_variables)
|
||||||
|
|
||||||
|
for variable in unresolved_variables:
|
||||||
|
if not overrides[variable].has_variable_dependency(
|
||||||
|
resolved_variables=resolved_variables
|
||||||
|
):
|
||||||
|
resolved_variables[variable] = overrides[variable].resolve(
|
||||||
|
resolved_variables=resolved_variables
|
||||||
|
)
|
||||||
|
unresolved_variables.remove(variable)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
len(unresolved_variables) != unresolved_count
|
||||||
|
), "did not resolve any variables, cycle detected"
|
||||||
|
|
||||||
|
return {
|
||||||
|
variable.name: resolvable.resolve()
|
||||||
|
for variable, resolvable in resolved_variables.items()
|
||||||
|
}
|
||||||
|
|
@ -5,9 +5,9 @@ from ytdl_sub.script.functions import Float
|
||||||
from ytdl_sub.script.functions import Integer
|
from ytdl_sub.script.functions import Integer
|
||||||
from ytdl_sub.script.functions import String
|
from ytdl_sub.script.functions import String
|
||||||
from ytdl_sub.script.parser import parse
|
from ytdl_sub.script.parser import parse
|
||||||
from ytdl_sub.script.types import Function
|
from ytdl_sub.script.syntax_tree import Function
|
||||||
from ytdl_sub.script.types import SyntaxTree
|
from ytdl_sub.script.syntax_tree import SyntaxTree
|
||||||
from ytdl_sub.script.types import Variable
|
from ytdl_sub.script.syntax_tree import Variable
|
||||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue