210 lines
8.2 KiB
Python
210 lines
8.2 KiB
Python
import copy
|
|
from typing import Dict
|
|
from typing import Iterable
|
|
from typing import List
|
|
from typing import Optional
|
|
from typing import Set
|
|
from typing import Tuple
|
|
|
|
from ytdl_sub.config.overrides import Overrides
|
|
from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
|
|
from ytdl_sub.config.plugin.plugin_operation import PluginOperation
|
|
from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
|
|
from ytdl_sub.config.preset_options import OutputOptions
|
|
from ytdl_sub.config.validators.options import OptionsValidator
|
|
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
|
|
from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES
|
|
from ytdl_sub.script.script import Script
|
|
from ytdl_sub.script.script import _is_function
|
|
from ytdl_sub.utils.scriptable import BASE_SCRIPT
|
|
from ytdl_sub.validators.string_formatter_validators import to_variable_dependency_format_string
|
|
from ytdl_sub.validators.string_formatter_validators import validate_formatters
|
|
|
|
# Entry variables to mock during validation
|
|
_DUMMY_ENTRY_VARIABLES: Dict[str, str] = {
|
|
name: to_variable_dependency_format_string(
|
|
# pylint: disable=protected-access
|
|
script=BASE_SCRIPT,
|
|
parsed_format_string=BASE_SCRIPT._variables[name],
|
|
# pylint: enable=protected-access
|
|
)
|
|
for name in BASE_SCRIPT.variable_names
|
|
}
|
|
|
|
|
|
def _add_dummy_variables(variables: Iterable[str]) -> Dict[str, str]:
|
|
dummy_variables: Dict[str, str] = {}
|
|
for var in variables:
|
|
dummy_variables[var] = ""
|
|
dummy_variables[f"{var}_sanitized"] = ""
|
|
|
|
return dummy_variables
|
|
|
|
|
|
def _add_dummy_overrides(overrides: Overrides) -> Dict[str, str]:
|
|
# Have the dummy override variable contain all variable deps that it uses in the string
|
|
dummy_overrides: Dict[str, str] = {}
|
|
for override_name in _override_variables(overrides):
|
|
if _is_function(override_name):
|
|
continue
|
|
|
|
# pylint: disable=protected-access
|
|
dummy_overrides[override_name] = to_variable_dependency_format_string(
|
|
script=overrides.script, parsed_format_string=overrides.script._variables[override_name]
|
|
)
|
|
# pylint: enable=protected-access
|
|
return dummy_overrides
|
|
|
|
|
|
def _get_added_and_modified_variables(
|
|
plugins: PresetPlugins, downloader_options: MultiUrlValidator
|
|
) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]:
|
|
"""
|
|
Iterates and returns the plugin options, added variables, modified variables
|
|
"""
|
|
options: List[OptionsValidator] = plugins.plugin_options
|
|
options.append(downloader_options)
|
|
|
|
for plugin_options in options:
|
|
added_variables: Set[str] = set()
|
|
modified_variables: Set[str] = set()
|
|
|
|
for plugin_added_variables in plugin_options.added_variables(
|
|
unresolved_variables=set(),
|
|
).values():
|
|
added_variables |= set(plugin_added_variables)
|
|
|
|
for plugin_modified_variables in plugin_options.modified_variables().values():
|
|
modified_variables = plugin_modified_variables
|
|
|
|
yield plugin_options, added_variables, modified_variables
|
|
|
|
|
|
def _override_variables(overrides: Overrides) -> Set[str]:
|
|
return set(list(overrides.initial_variables().keys()))
|
|
|
|
|
|
class VariableValidation:
|
|
def __init__(
|
|
self,
|
|
downloader_options: MultiUrlValidator,
|
|
output_options: OutputOptions,
|
|
plugins: PresetPlugins,
|
|
):
|
|
self.downloader_options = downloader_options
|
|
self.output_options = output_options
|
|
self.plugins = plugins
|
|
|
|
self.script: Optional[Script] = None
|
|
self.resolved_variables: Set[str] = set()
|
|
self.unresolved_variables: Set[str] = set()
|
|
|
|
def initialize_preset_overrides(self, overrides: Overrides) -> "VariableValidation":
|
|
"""
|
|
Do some gymnastics to initialize the Overrides script.
|
|
"""
|
|
override_variables = set(list(overrides.initial_variables().keys()))
|
|
|
|
# Set resolved variables as all entry + override variables
|
|
# at this point to generate every possible added/modified variable
|
|
self.resolved_variables = set(_DUMMY_ENTRY_VARIABLES.keys()) | override_variables
|
|
plugin_variables: Set[str] = set()
|
|
|
|
for (
|
|
plugin_options,
|
|
added_variables,
|
|
modified_variables,
|
|
) in _get_added_and_modified_variables(
|
|
plugins=self.plugins,
|
|
downloader_options=self.downloader_options,
|
|
):
|
|
|
|
for added_variable in added_variables:
|
|
if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable):
|
|
# pylint: disable=protected-access
|
|
raise plugin_options._validation_exception(
|
|
f"Cannot use the variable name {added_variable} because it exists as a"
|
|
" built-in ytdl-sub variable name."
|
|
)
|
|
# pylint: enable=protected-access
|
|
|
|
# Set unresolved as variables that are added but do not exist as
|
|
# entry/override variables since they are created at run-time
|
|
self.unresolved_variables |= added_variables | modified_variables
|
|
plugin_variables |= added_variables | modified_variables
|
|
|
|
# Then update resolved variables to reflect that
|
|
self.resolved_variables -= self.unresolved_variables
|
|
|
|
# Initialize overrides with unresolved variables + modified variables to throw an error.
|
|
# For modified variables, this is to prevent a resolve(update=True) to setting any
|
|
# dependencies until it has been explicitly added
|
|
overrides = overrides.initialize_script(unresolved_variables=self.unresolved_variables)
|
|
|
|
# copy the script and mock entry variables
|
|
self.script = copy.deepcopy(overrides.script)
|
|
self.script.add(
|
|
variables=_add_dummy_overrides(overrides=overrides)
|
|
| _add_dummy_variables(variables=plugin_variables)
|
|
| _DUMMY_ENTRY_VARIABLES
|
|
)
|
|
|
|
return self
|
|
|
|
def _update_script(self) -> None:
|
|
_ = self.script.resolve(unresolvable=self.unresolved_variables, update=True)
|
|
|
|
def _add_subscription_override_variables(self) -> None:
|
|
"""
|
|
Add dummy subscription variables for script validation
|
|
"""
|
|
self.resolved_variables |= REQUIRED_OVERRIDE_VARIABLE_NAMES
|
|
|
|
def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None:
|
|
"""
|
|
Add dummy variables for script validation
|
|
"""
|
|
added_variables = options.added_variables(
|
|
unresolved_variables=self.unresolved_variables,
|
|
).get(plugin_op, set())
|
|
modified_variables = options.modified_variables().get(plugin_op, set())
|
|
|
|
resolved_variables = added_variables | modified_variables
|
|
|
|
self.resolved_variables |= resolved_variables
|
|
self.unresolved_variables -= resolved_variables
|
|
|
|
def ensure_proper_usage(self) -> None:
|
|
"""
|
|
Validate variables resolve as plugins are executed, and return
|
|
a mock script which contains actualized added variables from the plugins
|
|
"""
|
|
|
|
self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options)
|
|
self._add_subscription_override_variables()
|
|
|
|
# Metadata variables to be added
|
|
for plugin_options in PluginMapping.order_options_by(
|
|
self.plugins.zipped(), PluginOperation.MODIFY_ENTRY_METADATA
|
|
):
|
|
self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=plugin_options)
|
|
|
|
for plugin_options in PluginMapping.order_options_by(
|
|
self.plugins.zipped(), PluginOperation.MODIFY_ENTRY
|
|
):
|
|
self._add_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options)
|
|
|
|
# Validate that any formatter in the plugin options can resolve
|
|
validate_formatters(
|
|
script=self.script,
|
|
unresolved_variables=self.unresolved_variables,
|
|
validator=plugin_options,
|
|
)
|
|
|
|
validate_formatters(
|
|
script=self.script,
|
|
unresolved_variables=self.unresolved_variables,
|
|
validator=self.output_options,
|
|
)
|
|
|
|
assert not self.unresolved_variables
|