changed plugin format. Need unit tests, documentation, update e2e to filter
This commit is contained in:
parent
6d56bdcb46
commit
bdf69722f0
5 changed files with 65 additions and 64 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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]}"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
Loading…
Reference in a new issue