feat: moved cli helpers to seperate file
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Hazel 2024-05-27 13:41:24 +02:00
parent 0f2229b0f2
commit 49145a7d93
4 changed files with 177 additions and 154 deletions

View File

@ -1,11 +1,13 @@
import random import random
import re import re
from pathlib import Path from pathlib import Path
from typing import Dict, List, Set, Type from typing import Dict, Generator, List, Set, Type
from .. import console from .. import console
from ..download import Downloader, Page from ..download import Downloader, Page, components
from ..download.results import GoToResults, Option, PageResults, Results from ..download.results import GoToResults
from ..download.results import Option as ResultOption
from ..download.results import PageResults, Results
from ..objects import Album, Artist, DatabaseObject, Song from ..objects import Album, Artist, DatabaseObject, Song
from ..utils import BColors, output from ..utils import BColors, output
from ..utils.config import main_settings, write_config from ..utils.config import main_settings, write_config
@ -17,7 +19,7 @@ from ..utils.string_processing import fit_to_file_system
from ..utils.support_classes.download_result import DownloadResult from ..utils.support_classes.download_result import DownloadResult
from ..utils.support_classes.query import Query from ..utils.support_classes.query import Query
from .options.first_config import initial_config from .options.first_config import initial_config
from .utils import cli_function from .utils import ask_for_bool, cli_function
EXIT_COMMANDS = {"q", "quit", "exit", "abort"} EXIT_COMMANDS = {"q", "quit", "exit", "abort"}
ALPHABET = "abcdefghijklmnopqrstuvwxyz" ALPHABET = "abcdefghijklmnopqrstuvwxyz"
@ -25,50 +27,19 @@ PAGE_NAME_FILL = "-"
MAX_PAGE_LEN = 21 MAX_PAGE_LEN = 21
def get_existing_genre() -> List[str]:
"""
gets the name of all subdirectories of shared.MUSIC_DIR,
but filters out all directories, where the name matches with any patern
from shared.NOT_A_GENRE_REGEX.
"""
existing_genres: List[str] = []
# get all subdirectories of MUSIC_DIR, not the files in the dir.
existing_subdirectories: List[Path] = [f for f in main_settings["music_directory"].iterdir() if f.is_dir()]
for subdirectory in existing_subdirectories:
name: str = subdirectory.name
if not any(re.match(regex_pattern, name) for regex_pattern in main_settings["not_a_genre_regex"]):
existing_genres.append(name)
existing_genres.sort()
return existing_genres
def get_genre(): def get_genre():
existing_genres = get_existing_genre() select_genre = components.GenreSelect()
for i, genre_option in enumerate(existing_genres): select_genre._ask_for_creating_option = lambda key: ask_for_bool(f"Create the genre \"{key}\"")
print(f"{i + 1:0>2}: {genre_option}")
while True: genre: Optional[components.Option] = None
genre = input("Id or new genre: ")
if genre.isdigit(): while genre is None:
genre_id = int(genre) - 1 for genre in select_genre:
if genre_id >= len(existing_genres): print(genre)
print(f"No genre under the id {genre_id + 1}.")
continue
return existing_genres[genre_id] genre = select_genre.choose(input("Id or new genre: "))
new_genre = fit_to_file_system(genre) return genre.value
agree_inputs = {"y", "yes", "ok"}
verification = input(f"create new genre \"{new_genre}\"? (Y/N): ").lower()
if verification in agree_inputs:
return new_genre
def help_message(): def help_message():
@ -111,7 +82,7 @@ class CliDownloader:
page_count = 0 page_count = 0
for option in self.current_results.formatted_generator(): for option in self.current_results.formatted_generator():
if isinstance(option, Option): if isinstance(option, ResultOption):
r = f"{BColors.GREY.value}{option.index:0{self.option_digits}}{BColors.ENDC.value} {option.music_object.option_string}" r = f"{BColors.GREY.value}{option.index:0{self.option_digits}}{BColors.ENDC.value} {option.music_object.option_string}"
print(r) print(r)
else: else:

View File

@ -39,4 +39,8 @@ def print_cute_message():
print(message) print(message)
AGREE_INPUTS = {"y", "yes", "ok"}
def ask_for_bool(msg: str) -> bool:
i = input(msg + " (Y/N):").lower()
return i in AGREE_INPUTS

View File

@ -453,113 +453,3 @@ class Page:
def download_song_to_target(self, source: Source, target: Target, desc: str = None) -> DownloadResult: def download_song_to_target(self, source: Source, target: Target, desc: str = None) -> DownloadResult:
return DownloadResult() return DownloadResult()
class Option:
"""
This could represent a data object, a string or a page.
"""
def __init__(
self,
value: Any,
text: Optional[str] = None,
keys: List[Any] = None,
hidden: bool = False,
parse_key: Callable[[Any], Any] = lambda x: x,
):
self._parse_key: Callable[[Any], Any] = parse_key
self.value = value
self.text = text or str(value)
self.hidden = hidden
self._raw_keys = set(keys or [])
self._raw_keys.add(self.text)
self.keys = set(self.parse_key(key) for key in self._raw_keys)
def register_key(self, key: Any):
self._raw_keys.add(key)
self.keys.add(self._parse_key(key))
@property
def parse_key(self) -> Callable[[Any], Any]:
return self._parse_key
@parse_key.setter
def parse_key(self, value: Callable[[Any], Any]):
self._parse_key = value
self.keys = set(self._parse_key(key) for key in self._raw_keys)
def __str__(self):
return self.text
class Select:
def __init__(
self,
options: List[Option] = None,
option_factory: Callable[[Any], Option] = None,
raw_options: List[Any] = None,
parse_option_key: Callable[[Any], Any] = lambda x: x,
ask_for_creating_option: Callable[[Option], bool] = lambda x: True,
**kwargs
):
self._parse_option_key: Callable[[Any], Any] = parse_option_key
self._ask_for_creating_option: Callable[[Option], bool] = ask_for_creating_option
self._key_to_option: Dict[Any, Option] = dict()
self._options: List[Option] = []
options = options or []
self.option_factory: Optional[Callable[[Any], Option]] = option_factory
if self.can_create_options:
for raw_option in raw_options or []:
self.append(self.option_factory(raw_option))
elif raw_options is not None:
raise MKComposeException("Cannot create options without a factory.")
self.extend(options)
@property
def can_create_options(self) -> bool:
return self.option_factory is not None
def append(self, option: Option):
option.parse_key = self._parse_option_key
self._options.append(option)
for key in option.keys:
self._key_to_option[key] = option
def extend(self, options: List[Option]):
for option in options:
self.append(option)
def __iter__(self) -> Generator[Option, None, None]:
for option in self._options:
if option.hidden:
continue
yield option
def __contains__(self, key: Any) -> bool:
return key in self._key_to_option
def __getitem__(self, key: Any) -> Option:
return self._key_to_option[key]
def create_option(self, key: Any, **kwargs) -> Option:
if not self.can_create_options:
raise MKComposeException("Cannot create options without a factory.")
option = self.option_factory(key, **kwargs)
self.append(option)
return option
def choose(self, key: Any) -> Optional[Option]:
if key not in self:
if self.can_create_options and self._ask_for_creating_option(key):
return self.create_option(key)
return None
return self[key]

View File

@ -0,0 +1,158 @@
import re
from pathlib import Path
from typing import Any, Callable, Dict, Generator, List, Optional
from ..utils.config import main_settings
from ..utils.exception import MKComposeException
from ..utils.string_processing import unify
class Option:
"""
This could represent a data object, a string or a page.
"""
def __init__(
self,
value: Any,
text: Optional[str] = None,
keys: List[Any] = None,
hidden: bool = False,
parse_key: Callable[[Any], Any] = lambda x: x,
):
self._parse_key: Callable[[Any], Any] = parse_key
self.value = value
self.text = text or str(value)
self.hidden = hidden
self._raw_keys = set(keys or [])
self._raw_keys.add(self.text)
self.keys = set(self.parse_key(key) for key in self._raw_keys)
def register_key(self, key: Any):
self._raw_keys.add(key)
self.keys.add(self._parse_key(key))
@property
def parse_key(self) -> Callable[[Any], Any]:
return self._parse_key
@parse_key.setter
def parse_key(self, value: Callable[[Any], Any]):
self._parse_key = value
self.keys = set(self._parse_key(key) for key in self._raw_keys)
def __str__(self):
return self.text
class Select:
def __init__(
self,
options: Generator[Option, None, None] = None,
option_factory: Callable[[Any], Option] = None,
raw_options: List[Any] = None,
parse_option_key: Callable[[Any], Any] = lambda x: x,
ask_for_creating_option: Callable[[Option], bool] = lambda x: True,
sort: bool = False,
**kwargs
):
self._parse_option_key: Callable[[Any], Any] = parse_option_key
self._ask_for_creating_option: Callable[[Option], bool] = ask_for_creating_option
self._key_to_option: Dict[Any, Option] = dict()
self._options: List[Option] = []
options = options or []
self.option_factory: Optional[Callable[[Any], Option]] = option_factory
if self.can_create_options:
_raw_options = raw_options or []
if sort:
_raw_options = sorted(_raw_options)
for raw_option in _raw_options:
self.append(self.option_factory(raw_option))
elif raw_options is not None:
raise MKComposeException("Cannot create options without a factory.")
self.extend(options)
@property
def can_create_options(self) -> bool:
return self.option_factory is not None
def append(self, option: Option):
option.parse_key = self._parse_option_key
self._options.append(option)
for key in option.keys:
self._key_to_option[key] = option
def extend(self, options: List[Option]):
for option in options:
self.append(option)
def __iter__(self) -> Generator[Option, None, None]:
for option in self._options:
if option.hidden:
continue
yield option
def __contains__(self, key: Any) -> bool:
return key in self._key_to_option
def __getitem__(self, key: Any) -> Option:
return self._key_to_option[key]
def create_option(self, key: Any, **kwargs) -> Option:
if not self.can_create_options:
raise MKComposeException("Cannot create options without a factory.")
option = self.option_factory(key, **kwargs)
self.append(option)
return option
def choose(self, key: Any) -> Optional[Option]:
if key not in self:
if self.can_create_options and self._ask_for_creating_option(key):
return self.create_option(key)
return None
return self[key]
class StringSelect(Select):
def __init__(self, **kwargs):
self._current_index = 0
kwargs["option_factory"] = self.next_option
kwargs["parse_option_key"] = lambda x: unify(str(x))
super().__init__(**kwargs)
def next_option(self, value: Any) -> Optional[Option]:
o = Option(value=value, keys=[self._current_index], text=f"{self._current_index:0>2}: {value}")
self._current_index += 1
return o
class GenreSelect(StringSelect):
@staticmethod
def is_valid_genre(genre: Path) -> bool:
"""
gets the name of all subdirectories of shared.MUSIC_DIR,
but filters out all directories, where the name matches with any Patern
from shared.NOT_A_GENRE_REGEX.
"""
if not genre.is_dir():
return False
if any(re.match(regex_pattern, genre.name) for regex_pattern in main_settings["not_a_genre_regex"]):
return False
return True
def __init__(self):
super().__init__(sort=True, raw_options=(genre.name for genre in filter(self.is_valid_genre, main_settings["music_directory"].iterdir())))