changed plugin format. Need unit tests, documentation, update e2e to filter

This commit is contained in:
jbannon 2022-07-13 06:46:57 +00:00
parent 6d56bdcb46
commit bdf69722f0
5 changed files with 65 additions and 64 deletions

View file

@ -108,7 +108,7 @@ class PluginMapping:
"music_tags": MusicTagsPlugin, "music_tags": MusicTagsPlugin,
"nfo_tags": NfoTagsPlugin, "nfo_tags": NfoTagsPlugin,
"output_directory_nfo_tags": OutputDirectoryNfoTagsPlugin, "output_directory_nfo_tags": OutputDirectoryNfoTagsPlugin,
"regex_capture": RegexCapturePlugin, "regex": RegexCapturePlugin,
} }
@classmethod @classmethod

View file

@ -6,10 +6,14 @@ from ytdl_sub.entries.entry import Entry
from ytdl_sub.plugins.plugin import Plugin from ytdl_sub.plugins.plugin import Plugin
from ytdl_sub.plugins.plugin import PluginOptions from ytdl_sub.plugins.plugin import PluginOptions
from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.logger import Logger
from ytdl_sub.validators.regex_validator import RegexListValidator from ytdl_sub.validators.regex_validator import RegexListValidator
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.validators import BoolValidator
logger = Logger.get(name="regex")
def _source_var_name(source_variable: str, capture_group_idx: int) -> str: def _source_var_name(source_variable: str, capture_group_idx: int) -> str:
@ -18,24 +22,24 @@ def _source_var_name(source_variable: str, capture_group_idx: int) -> str:
class SourceVariableRegexCapture(StrictDictValidator): class SourceVariableRegexCapture(StrictDictValidator):
_required_keys = {"capture"} _required_keys = {"match"}
_optional_keys = {"defaults"} _optional_keys = {"defaults"}
def __init__(self, name, value): def __init__(self, name, value):
super().__init__(name, value) super().__init__(name, value)
self._capture = self._validate_key(key="capture", validator=RegexListValidator) self._match = self._validate_key(key="match", validator=RegexListValidator)
self._defaults = self._validate_key_if_present( self._defaults = self._validate_key_if_present(
key="defaults", validator=ListFormatterValidator key="defaults", validator=ListFormatterValidator
) )
# If defaults are to be used, ensure there are the same number of defaults as there are # If defaults are to be used, ensure there are the same number of defaults as there are
# capture groups # capture groups
if self._defaults is not None and self._capture.num_capture_groups != len( if self._defaults is not None and self._match.num_capture_groups != len(
self._defaults.list self._defaults.list
): ):
raise self._validation_exception( raise self._validation_exception(
f"number of defaults must match number of capture groups, " f"number of defaults must match number of capture groups, "
f"{len(self._defaults.list)} != {self._capture.num_capture_groups}" f"{len(self._defaults.list)} != {self._match.num_capture_groups}"
) )
@property @property
@ -45,7 +49,7 @@ class SourceVariableRegexCapture(StrictDictValidator):
------- -------
List of regex captures List of regex captures
""" """
return self._capture return self._match
@property @property
def has_defaults(self) -> bool: def has_defaults(self) -> bool:
@ -66,14 +70,30 @@ class SourceVariableRegexCapture(StrictDictValidator):
return self._defaults.list if self.has_defaults else None return self._defaults.list if self.has_defaults else None
class RegexCaptureOptions(PluginOptions): class FromSourceVariablesRegexCapture(StrictDictValidator):
_optional_keys = {"_"} _optional_keys = Entry.source_variables()
_allow_extra_keys = True _allow_extra_keys = True
def __init__(self, name, value): def __init__(self, name, value):
super().__init__(name, value) super().__init__(name, value)
self._source_variable_capture_dict: Dict[str, SourceVariableRegexCapture] = {} self.source_variable_capture_dict: Dict[str, SourceVariableRegexCapture] = {
key: self._validate_key(key=key, validator=SourceVariableRegexCapture)
for key in self._keys
}
class RegexCaptureOptions(PluginOptions):
_required_keys = {"from"}
_optional_keys = {"skip_if_match_fails"}
def __init__(self, name, value):
super().__init__(name, value)
self._from = self._validate_key(key="from", validator=FromSourceVariablesRegexCapture)
self.skip_if_match_fails: bool = self._validate_key_if_present(
key="skip_if_match_fails", validator=BoolValidator, default=False
).value
def validate_with_source_variables(self, source_variables: List[str]) -> None: def validate_with_source_variables(self, source_variables: List[str]) -> None:
""" """
@ -84,16 +104,12 @@ class RegexCaptureOptions(PluginOptions):
source_variables source_variables
Variables to check against the provided capture groups Variables to check against the provided capture groups
""" """
for key in self._keys: for key in self.source_variable_capture_dict.keys():
if key not in source_variables: if key not in source_variables:
raise self._validation_exception( raise self._validation_exception(
f"cannot regex capture '{key}' because it is not a source variable" f"cannot regex capture '{key}' because it is not a source variable"
) )
self._source_variable_capture_dict[key] = self._validate_key(
key=key, validator=SourceVariableRegexCapture
)
@property @property
def source_variable_capture_dict(self) -> Dict[str, SourceVariableRegexCapture]: def source_variable_capture_dict(self) -> Dict[str, SourceVariableRegexCapture]:
""" """
@ -101,7 +117,7 @@ class RegexCaptureOptions(PluginOptions):
------- -------
Dict of { source variable: capture options } Dict of { source variable: capture options }
""" """
return self._source_variable_capture_dict return self._from.source_variable_capture_dict
def added_source_variables(self) -> List[str]: def added_source_variables(self) -> List[str]:
""" """
@ -148,8 +164,16 @@ class RegexCapturePlugin(Plugin[RegexCaptureOptions]):
# If no capture # If no capture
if maybe_capture is None: if maybe_capture is None:
# and no defaults, then error # and no defaults
if not regex_options.has_defaults: if not regex_options.has_defaults:
# Skip the entry if toggled
if self.plugin_options.skip_if_match_fails:
logger.info(
"Entry with title '%s' failed to match regex, skipping.", entry.title
)
return None
# Otherwise, error
raise ValidationException( raise ValidationException(
f"Failed to capture {source_var} from an entry with the value:\n" f"Failed to capture {source_var} from an entry with the value:\n"
f"{entry_variable_dict[source_var]}" f"{entry_variable_dict[source_var]}"

View file

@ -28,33 +28,20 @@ class RegexValidator(StringValidator):
""" """
return self._compiled_regex.groups return self._compiled_regex.groups
def is_match(self, input_str: str) -> bool:
"""
Parameters
----------
input_str
String to match against the regex
Returns
-------
True if input_str matches. False otherwise.
"""
return self._compiled_regex.search(input_str) is not None
def capture(self, input_str: str) -> Optional[List[str]]: def capture(self, input_str: str) -> Optional[List[str]]:
""" """
Parameters Parameters
---------- ----------
input_str input_str
String to try to regex capture from String to regex match
Returns Returns
------- -------
List of captures (will always be >= 1). None if there are no captures. List of captures. If the regex has no capture groups, then the list will be emtpy.
None is returned if the input_str failed to match
""" """
if match := self._compiled_regex.search(input_str): if match := self._compiled_regex.search(input_str):
if len(to_return := list(match.groups())) > 0: return list(match.groups())
return to_return
return None return None
@ -81,19 +68,6 @@ class RegexListValidator(ListValidator[RegexValidator]):
""" """
return self._num_capture_groups return self._num_capture_groups
def matches_any(self, input_str: str) -> bool:
"""
Parameters
----------
input_str
String to match against any regexes in the list
Returns
-------
True if at least one matches. False if none match
"""
return any(reg.is_match(input_str) for reg in self._list)
def capture_any(self, input_str: str) -> Optional[List[str]]: def capture_any(self, input_str: str) -> Optional[List[str]]:
""" """
Parameters Parameters
@ -103,10 +77,9 @@ class RegexListValidator(ListValidator[RegexValidator]):
Returns Returns
------- -------
List of captures (will always be >= 1) on the first regex that matches. None if List of captures on the first regex that matches. None if no regexes match.
no regexes match.
""" """
for reg in self._list: for reg in self._list:
if maybe_capture := reg.capture(input_str): if (maybe_capture := reg.capture(input_str)) is not None:
return maybe_capture return maybe_capture
return None return None

View file

@ -17,22 +17,25 @@ def regex_capture_subscription_dict(output_directory):
"format": "best[height<=480]", "format": "best[height<=480]",
"postprocessor_args": {"ffmpeg": ["-bitexact"]}, # Must add this for reproducibility "postprocessor_args": {"ffmpeg": ["-bitexact"]}, # Must add this for reproducibility
}, },
"regex_capture": { "regex": {
"skip_if_match_fails": False,
"from": {
"title": { "title": {
"capture": [ "match": [
"should not cap (.+) - (.+)", "should not cap (.+) - (.+)",
".*\\[(.+) - (.+)]", ".*\\[(.+) - (.+)]",
], ],
}, },
"description": {"capture": [".*http:\\/\\/(.+).com.*"]}, "description": {"match": [".*http:\\/\\/(.+).com.*"]},
"upload_date_standardized": { "upload_date_standardized": {
"capture": ["([0-9]+)-([0-9]+)-27"], "match": ["([0-9]+)-([0-9]+)-27"],
"defaults": [ "defaults": [
"First", "First",
"Second", "Second",
], ],
}, },
"artist": {"capture": ["Never (.*) capture"], "defaults": ["Always default"]}, "artist": {"match": ["Never (.*) capture"], "defaults": ["Always default"]},
},
}, },
"nfo_tags": { "nfo_tags": {
"tags": { "tags": {

View file

@ -0,0 +1 @@