Add aliases for ytdl-sub dl (#68)
* Add aliases for `ytdl-sub dl` * error tests
This commit is contained in:
parent
67104bca43
commit
e111ae53f5
5 changed files with 178 additions and 11 deletions
|
|
@ -1,10 +1,13 @@
|
|||
import hashlib
|
||||
import shlex
|
||||
from argparse import ArgumentError
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
from mergedeep import mergedeep
|
||||
|
||||
from ytdl_sub.config.config_file import ConfigOptions
|
||||
|
||||
|
||||
class DownloadArgsParser:
|
||||
"""
|
||||
|
|
@ -14,11 +17,17 @@ class DownloadArgsParser:
|
|||
:class:`~ytdl_subscribe.validators.config.preset_validator.PresetValidator`
|
||||
"""
|
||||
|
||||
def __init__(self, extra_arguments: List[str]):
|
||||
def __init__(self, extra_arguments: List[str], config_options: ConfigOptions):
|
||||
"""
|
||||
:param extra_arguments: List of extra arguments from argparse
|
||||
Parameters
|
||||
----------
|
||||
extra_arguments
|
||||
List of extra arguments from argparse
|
||||
config_options
|
||||
Configuration portion of config.yaml
|
||||
"""
|
||||
self._unknown_arguments = extra_arguments
|
||||
self._config_options = config_options
|
||||
|
||||
@property
|
||||
def _argument_exception(self) -> ArgumentError:
|
||||
|
|
@ -27,8 +36,7 @@ class DownloadArgsParser:
|
|||
"""
|
||||
return ArgumentError(
|
||||
argument=None,
|
||||
message="dl arguments must be in the form of --subscription.option.name 'value' "
|
||||
"proceeding the url",
|
||||
message="dl arguments must be in the form of --subscription.option.name 'value'",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -39,6 +47,20 @@ class DownloadArgsParser:
|
|||
"""
|
||||
return arg.startswith("--")
|
||||
|
||||
@classmethod
|
||||
def _get_argument_name(cls, arg_name: str) -> str:
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
arg_name
|
||||
Argument name
|
||||
|
||||
Returns
|
||||
-------
|
||||
The argument name with the -- removed
|
||||
"""
|
||||
return arg_name[2:]
|
||||
|
||||
@classmethod
|
||||
def _argument_name_and_value_to_dict(cls, arg_name: str, arg_value: str) -> Dict:
|
||||
"""
|
||||
|
|
@ -49,7 +71,7 @@ class DownloadArgsParser:
|
|||
argument_dict = {}
|
||||
|
||||
# Remove the argument --'s, then split on period
|
||||
arg_name_split = arg_name.replace("--", "", 1).split(".")
|
||||
arg_name_split = cls._get_argument_name(arg_name).split(".")
|
||||
|
||||
next_dict = argument_dict
|
||||
for next_arg_name in arg_name_split[:-1]:
|
||||
|
|
@ -60,18 +82,36 @@ class DownloadArgsParser:
|
|||
|
||||
return argument_dict
|
||||
|
||||
@classmethod
|
||||
def _apply_aliases(cls, unknown_arguments: List[str], aliases: Dict) -> List[str]:
|
||||
"""
|
||||
Applies any aliases from the config to the unknown arguments
|
||||
"""
|
||||
applied_alias_arguments: List[str] = []
|
||||
|
||||
for arg in unknown_arguments:
|
||||
if cls._is_argument_name(arg) and cls._get_argument_name(arg) in aliases:
|
||||
arg = aliases[cls._get_argument_name(arg)]
|
||||
applied_alias_arguments.extend(shlex.split(arg))
|
||||
else:
|
||||
applied_alias_arguments.append(arg)
|
||||
|
||||
return applied_alias_arguments
|
||||
|
||||
def to_subscription_dict(self) -> Dict:
|
||||
"""
|
||||
Converts the extra arguments into a dict equivalent to a subscription yaml.
|
||||
:return: dict containing argument names and values
|
||||
"""
|
||||
subscription_dict = {}
|
||||
if len(self._unknown_arguments) % 2 != 0:
|
||||
arguments = self._apply_aliases(
|
||||
unknown_arguments=self._unknown_arguments, aliases=self._config_options.dl_aliases
|
||||
)
|
||||
if len(arguments) % 2 != 0:
|
||||
raise self._argument_exception
|
||||
|
||||
for idx in range(0, len(self._unknown_arguments), 2):
|
||||
argument_name = self._unknown_arguments[idx]
|
||||
argument_value = self._unknown_arguments[idx + 1]
|
||||
for idx in range(0, len(arguments), 2):
|
||||
argument_name, argument_value = arguments[idx], arguments[idx + 1]
|
||||
|
||||
if not self._is_argument_name(arg=argument_name):
|
||||
raise self._argument_exception
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
from ytdl_sub.utils.yaml import load_yaml
|
||||
|
|
@ -10,7 +11,7 @@ from ytdl_sub.validators.validators import StringValidator
|
|||
|
||||
class ConfigOptions(StrictDictValidator):
|
||||
_required_keys = {"working_directory"}
|
||||
_optional_keys = {"umask"}
|
||||
_optional_keys = {"umask", "dl_aliases"}
|
||||
|
||||
def __init__(self, name: str, value: Any):
|
||||
super().__init__(name, value)
|
||||
|
|
@ -21,6 +22,9 @@ class ConfigOptions(StrictDictValidator):
|
|||
self._umask = self._validate_key_if_present(
|
||||
key="umask", validator=StringValidator, default="022"
|
||||
)
|
||||
self._dl_aliases = self._validate_key_if_present(
|
||||
key="dl_aliases", validator=LiteralDictValidator
|
||||
)
|
||||
|
||||
@property
|
||||
def working_directory(self) -> str:
|
||||
|
|
@ -37,6 +41,34 @@ class ConfigOptions(StrictDictValidator):
|
|||
"""
|
||||
return self._umask.value
|
||||
|
||||
@property
|
||||
def dl_aliases(self) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
Alias definitions to shorten ``ytdl-sub dl`` arguments. For example,
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
configuration:
|
||||
dl_aliases:
|
||||
mv: "--preset yt_music_video"
|
||||
v: "--youtube.video_id"
|
||||
|
||||
Simplifies
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ytdl-sub dl --preset yt_music_video --youtube.video_id a1b2c3
|
||||
|
||||
to
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ytdl-sub dl --mv --v a1b2c3
|
||||
"""
|
||||
if self._dl_aliases:
|
||||
return self._dl_aliases.dict
|
||||
return {}
|
||||
|
||||
|
||||
class ConfigFile(StrictDictValidator):
|
||||
_required_keys = {"configuration", "presets"}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ def _download_subscription_from_cli(config: ConfigFile, extra_args: List[str]) -
|
|||
:param config: Configuration file
|
||||
:param extra_args: Extra arguments from argparse that contain dynamic subscription options
|
||||
"""
|
||||
dl_args_parser = DownloadArgsParser(extra_arguments=extra_args)
|
||||
dl_args_parser = DownloadArgsParser(
|
||||
extra_arguments=extra_args, config_options=config.config_options
|
||||
)
|
||||
subscription_args_dict = dl_args_parser.to_subscription_dict()
|
||||
|
||||
subscription_name = f"cli-dl-{dl_args_parser.get_args_hash()}"
|
||||
|
|
|
|||
0
tests/unit/cli/__init__.py
Normal file
0
tests/unit/cli/__init__.py
Normal file
93
tests/unit/cli/test_download_args_parser.py
Normal file
93
tests/unit/cli/test_download_args_parser.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import shlex
|
||||
from argparse import ArgumentError
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
|
||||
from ytdl_sub.cli.download_args_parser import DownloadArgsParser
|
||||
from ytdl_sub.cli.main_args_parser import parser
|
||||
from ytdl_sub.config.config_file import ConfigOptions
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_options_generator() -> Callable:
|
||||
def _config_options_generator(dl_aliases: Optional[Dict[str, str]] = None):
|
||||
config_options_dict = {
|
||||
"working_directory": ".",
|
||||
}
|
||||
if dl_aliases:
|
||||
config_options_dict["dl_aliases"] = dl_aliases
|
||||
|
||||
return ConfigOptions(name="config_options_unit_test", value=config_options_dict)
|
||||
|
||||
return _config_options_generator
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def argument_error_msg() -> str:
|
||||
return "dl arguments must be in the form of --subscription.option.name 'value'"
|
||||
|
||||
|
||||
def _get_extra_arguments(cmd_string: str) -> List[str]:
|
||||
sys_argv = shlex.split(cmd_string)
|
||||
_, extra_args = parser.parse_known_args(args=sys_argv)
|
||||
|
||||
return extra_args
|
||||
|
||||
|
||||
class TestDownloadArgsParser:
|
||||
@pytest.mark.parametrize(
|
||||
"aliases, cmd, expected_sub_dict",
|
||||
[
|
||||
(
|
||||
{"mv": "--preset yt_music_video", "v": "--youtube.video_id"},
|
||||
"dl --mv --v 123abc",
|
||||
{"preset": "yt_music_video", "youtube": {"video_id": "123abc"}},
|
||||
),
|
||||
(
|
||||
{
|
||||
"ch": "--preset yt_channel",
|
||||
"c": "--youtube.channel_id",
|
||||
"name": "--overrides.tv_name",
|
||||
"concert": "--overrides.genre 'Some Genre Name'",
|
||||
},
|
||||
"dl --ch --c 123abc --name 'Sweet TV Show' --concert",
|
||||
{
|
||||
"preset": "yt_channel",
|
||||
"youtube": {"channel_id": "123abc"},
|
||||
"overrides": {"tv_name": "Sweet TV Show", "genre": "Some Genre Name"},
|
||||
},
|
||||
),
|
||||
(None, "dl --youtube.playlist_id 123abc", {"youtube": {"playlist_id": "123abc"}}),
|
||||
],
|
||||
)
|
||||
def test_successful_args(self, config_options_generator, aliases, cmd, expected_sub_dict):
|
||||
config_options = config_options_generator(dl_aliases=aliases)
|
||||
extra_args = _get_extra_arguments(cmd_string=cmd)
|
||||
|
||||
output_sub_dict = DownloadArgsParser(
|
||||
extra_arguments=extra_args, config_options=config_options
|
||||
).to_subscription_dict()
|
||||
|
||||
assert output_sub_dict == expected_sub_dict
|
||||
|
||||
def test_error_uneven_args(self, config_options_generator, argument_error_msg):
|
||||
config_options = config_options_generator()
|
||||
extra_args = _get_extra_arguments(cmd_string="dl --preset")
|
||||
|
||||
with pytest.raises(ArgumentError, match=argument_error_msg):
|
||||
DownloadArgsParser(
|
||||
extra_arguments=extra_args, config_options=config_options
|
||||
).to_subscription_dict()
|
||||
|
||||
def test_error_adjacent_values(self, config_options_generator, argument_error_msg):
|
||||
config_options = config_options_generator()
|
||||
extra_args = _get_extra_arguments(cmd_string="dl --preset buttered toast --test")
|
||||
|
||||
with pytest.raises(ArgumentError, match=argument_error_msg):
|
||||
DownloadArgsParser(
|
||||
extra_arguments=extra_args, config_options=config_options
|
||||
).to_subscription_dict()
|
||||
Loading…
Reference in a new issue