diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index 87fb45a1..db98ab06 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -28,7 +28,7 @@ class Overrides(DictFormatterValidator, Scriptable): my_example_preset: overrides: output_directory: "/path/to/media" - custom_file_name: "{upload_year}.{upload_month_padded}.{upload_day_padded}.{title_sanitized}" + custom_file_name: "{upload_date_standardized}.{title_sanitized}" # Then use the override variables in the output options output_options: @@ -55,19 +55,36 @@ class Overrides(DictFormatterValidator, Scriptable): self.unresolvable.add(VARIABLES.entry_metadata.variable_name) - def initial_variables(self, unresolved_variables: Dict[str, str]) -> Dict[str, str]: + def initial_variables( + self, unresolved_variables: Optional[Dict[str, str]] = None + ) -> Dict[str, str]: + """ + Returns + ------- + Variables and format strings for all Override variables + additional variables (Optional) + """ initial_variables: Dict[str, str] = {} mergedeep.merge( initial_variables, self.dict_with_format_strings, - unresolved_variables, + unresolved_variables if unresolved_variables else {}, {SUBSCRIPTION_NAME: self.subscription_name}, ) return ScriptUtils.add_sanitized_variables(initial_variables) - def initialize_script(self, unresolved_variables: Dict[str, str]) -> "Overrides": - self.script.add(self.initial_variables(unresolved_variables=unresolved_variables)) - self.unresolvable.update(set(unresolved_variables.keys())) + def initialize_script(self, unresolved_variables: Set[str]) -> "Overrides": + """ + Initialize the override script with override variables + any unresolved variables + """ + self.script.add( + self.initial_variables( + unresolved_variables={ + var_name: f"{{%throw('Plugin variable {var_name} has not been created yet')}}" + for var_name in unresolved_variables + } + ) + ) + self.unresolvable.update(unresolved_variables) self.update_script() return self diff --git a/src/ytdl_sub/config/plugin/plugin_mapping.py b/src/ytdl_sub/config/plugin/plugin_mapping.py index 9643d6af..2918c746 100644 --- a/src/ytdl_sub/config/plugin/plugin_mapping.py +++ b/src/ytdl_sub/config/plugin/plugin_mapping.py @@ -108,6 +108,11 @@ class PluginMapping: def order_options_by( cls, zipped: List[Tuple[Type[Plugin], OptionsValidator]], operation: PluginOperation ) -> List[OptionsValidator]: + """ + Returns + ------- + Ordered plugin options with respect to the PluginOperation. + """ ordered_types: List[Type[Plugin]] = cls._order_by( plugin_types=[val[0] for val in zipped], operation=operation ) @@ -131,6 +136,12 @@ class PluginMapping: def order_plugins_by( cls, plugins: List[Plugin], operation: PluginOperation, before_split: Optional[bool] = None ) -> List[Plugin]: + """ + Returns + ------- + Ordered plugins with respect to the PluginOperation. Optionally only return plugins + before/after a split plugin. + """ ordered_types: List[Type[Plugin]] = cls._order_by( plugin_types=[type(plugin) for plugin in plugins], operation=operation ) diff --git a/src/ytdl_sub/config/validators/variable_validation.py b/src/ytdl_sub/config/validators/variable_validation.py index 415d8464..e5588c64 100644 --- a/src/ytdl_sub/config/validators/variable_validation.py +++ b/src/ytdl_sub/config/validators/variable_validation.py @@ -50,7 +50,7 @@ def _get_added_and_modified_variables( def _override_variables(overrides: Overrides) -> Set[str]: - return set(list(overrides.initial_variables(unresolved_variables={}).keys())) + return set(list(overrides.initial_variables().keys())) def _entry_variables() -> Set[str]: @@ -73,6 +73,9 @@ class VariableValidation: self.unresolved_variables: Set[str] = set() def initialize_overrides(self, overrides: Overrides) -> "VariableValidation": + """ + Do some gymnastics to initialize the Overrides script. + """ entry_variables = _entry_variables() override_variables = _override_variables(overrides) @@ -93,12 +96,7 @@ class VariableValidation: # 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={ - var_name: f"{{%throw('Plugin variable {var_name} has not been created yet')}}" - for var_name in self.unresolved_variables - } - ) + overrides = overrides.initialize_script(unresolved_variables=self.unresolved_variables) # copy the script and mock entry variables self.script = copy.deepcopy(overrides.script).add(_add_dummy_variables(entry_variables)) diff --git a/src/ytdl_sub/entries/entry.py b/src/ytdl_sub/entries/entry.py index 413fa4bd..0eaf55ca 100644 --- a/src/ytdl_sub/entries/entry.py +++ b/src/ytdl_sub/entries/entry.py @@ -41,7 +41,6 @@ class Entry(BaseEntry, Scriptable): self.update_script() def initialize_script(self, other: Optional[Scriptable] = None) -> "Entry": - # TODO: CLEAN THIS SHIT UP # Overrides contains added variables that are unresolvable, add them here if other: self.script = copy.deepcopy(other.script) diff --git a/src/ytdl_sub/entries/entry_parent.py b/src/ytdl_sub/entries/entry_parent.py index 19cfefec..68758791 100644 --- a/src/ytdl_sub/entries/entry_parent.py +++ b/src/ytdl_sub/entries/entry_parent.py @@ -16,6 +16,8 @@ from ytdl_sub.entries.script.variable_scripts import ENTRY_REQUIRED_VARIABLES v: VariableDefinitions = VARIABLES +# pylint: disable=protected-access + def _sort_entries(entries: List[TBaseEntry]) -> List[TBaseEntry]: """Try sorting by playlist_id first, then fall back to uid""" return sorted( diff --git a/src/ytdl_sub/entries/script/custom_functions.py b/src/ytdl_sub/entries/script/custom_functions.py index 260c4e06..a7440b9d 100644 --- a/src/ytdl_sub/entries/script/custom_functions.py +++ b/src/ytdl_sub/entries/script/custom_functions.py @@ -23,24 +23,46 @@ _days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] class CustomFunctions: @staticmethod def legacy_bracket_safety(value: ReturnableArgument) -> ReturnableArgument: + """ + ytdl-sub used to replace brackets ('{', '}') with unicode brackets ('{', '}') to not + interfere with its legacy variable scripting system. This function replicates that + behavior. + """ if isinstance(value, String): value = String(value.value.replace("{", "{").replace("}", "}")) return value @staticmethod def to_native_filepath(filepath: String) -> String: + """ + Convert any unix-based path separators ('/') with the OS's native + separator. + """ return String(filepath.value.replace(posixpath.sep, os.sep)) @staticmethod def truncate_filepath_if_too_long(filepath: String) -> String: + """ + If a file-path is too long for the OS, this function will truncate it while preserving + the extension. + """ return String(FilePathTruncater.maybe_truncate_file_path(filepath.value)) @staticmethod def sanitize(value: AnyArgument) -> String: + """ + Sanitize a string using yt-dlp's ``sanitize_filename`` method to ensure it's safe to use + for file/directory names on any OS. + """ return String(sanitize_filename(str(value))) @staticmethod def sanitize_plex_episode(string: String) -> String: + """ + Sanitize a string using ``sanitize`` and replace numerics with their respective fixed-width + numbers. This is used to have Plex avoid scraping numbers like ``4x4`` as the + season and/or episode. + """ sanitized_string = CustomFunctions.sanitize(string).value out = "" for char in sanitized_string: @@ -71,6 +93,27 @@ class CustomFunctions: @staticmethod def to_date_metadata(yyyymmdd: String) -> Map: + """ + Takes a date in the form of YYYYMMDD and returns a Map containing: + + - date (String, YYYYMMDD) + - date_standardized (String, YYYY-MM-DD) + - year (Integer) + - month (Integer) + - day (Integer) + - year_truncated (String, YY from YY[YY]) + - month_padded (String) + - day_padded (String) + - year_truncated_reversed (Integer, 100 - year_truncated) + - month_reversed (Integer, 13 - month) + - month_reversed_padded (String) + - day_reversed (Integer, total_days_in_month + 1 - day) + - day_reversed_padded (String) + - day_of_year (Integer) + - day_of_year_padded (String, padded 3) + - day_of_year_reversed (Integer, total_days_in_year + 1 - day_of_year) + - day_of_year_reversed_padded (String, padded 3) + """ date_str = yyyymmdd.value if not (date_str.isnumeric() and len(date_str) == 8): raise RuntimeException( @@ -123,6 +166,9 @@ class CustomFunctions: @staticmethod def register(): + """ + Register Custom functions once and only once + """ if not Functions.is_built_in("sanitize"): Functions.register_function(CustomFunctions.legacy_bracket_safety) Functions.register_function(CustomFunctions.truncate_filepath_if_too_long)