Compare commits

...

4 Commits

Author SHA1 Message Date
d4fe99ffc7 feat: dynamic indices
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-27 16:59:51 +02:00
413d422e2f draft: improved searching one page only 2024-05-27 16:38:03 +02:00
999299c32a draft: improved searching one page only 2024-05-27 16:37:55 +02:00
a0e42fc6ee draft: outline of better select 2024-05-27 15:50:04 +02:00
4 changed files with 155 additions and 38 deletions

View File

@ -1,7 +1,7 @@
import random import random
import re import re
from pathlib import Path from pathlib import Path
from typing import Dict, Generator, List, Set, Type from typing import Dict, Generator, List, Set, Type, Union
from .. import console from .. import console
from ..download import Downloader, Page, components from ..download import Downloader, Page, components
@ -91,7 +91,9 @@ class CliDownloader:
self.page_dict = dict() self.page_dict = dict()
print() print()
print(self.current_results.pprint())
"""
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, ResultOption): if isinstance(option, ResultOption):
@ -106,10 +108,13 @@ class CliDownloader:
self.page_dict[option.__name__] = option self.page_dict[option.__name__] = option
page_count += 1 page_count += 1
"""
print() print()
def set_current_options(self, current_options: Results): def set_current_options(self, current_options: Union[Generator[DatabaseObject, None, None], components.Select]):
current_options = current_options if isinstance(current_options, components.Select) else components.DataObjectSelect(current_options)
if main_settings["result_history"]: if main_settings["result_history"]:
self._result_history.append(current_options) self._result_history.append(current_options)
@ -216,12 +221,14 @@ class CliDownloader:
self.set_current_options(self.downloader.search(parsed_query)) self.set_current_options(self.downloader.search(parsed_query))
self.print_current_options() self.print_current_options()
def goto(self, data_object: DatabaseObject): def goto(self, data_object: Union[DatabaseObject, components.Select]):
page: Type[Page] page: Type[Page]
self.downloader.fetch_details(data_object, stop_at_level=1) if isinstance(data_object, components.Select):
self.set_current_options(data_object)
self.set_current_options(GoToResults(data_object.options, max_items_per_page=self.max_displayed_options)) else:
self.downloader.fetch_details(data_object, stop_at_level=1)
self.set_current_options(data_object.options)
self.print_current_options() self.print_current_options()
@ -288,24 +295,15 @@ class CliDownloader:
indices = [] indices = []
for possible_index in q.split(","): for possible_index in q.split(","):
possible_index = possible_index.strip()
if possible_index == "": if possible_index == "":
continue continue
if possible_index not in self.current_results:
raise MKInvalidInputException(message=f"The index \"{possible_index}\" is not in the current options.")
i = 0 yield self.current_results[possible_index]
try:
i = int(possible_index)
except ValueError:
raise MKInvalidInputException(message=f"The index \"{possible_index}\" is not a number.")
if i < 0 or i >= len(self.current_results): selected_objects = list(get_selected_objects(query))
raise MKInvalidInputException(message=f"The index \"{i}\" is not within the bounds of 0-{len(self.current_results) - 1}.")
indices.append(i)
return [self.current_results[i] for i in indices]
selected_objects = get_selected_objects(query)
if do_merge: if do_merge:
old_selected_objects = selected_objects old_selected_objects = selected_objects
@ -332,7 +330,7 @@ class CliDownloader:
if len(selected_objects) != 1: if len(selected_objects) != 1:
raise MKInvalidInputException(message="You can only go to one object at a time without merging.") raise MKInvalidInputException(message="You can only go to one object at a time without merging.")
self.goto(selected_objects[0]) self.goto(selected_objects[0].value)
return False return False
except MKInvalidInputException as e: except MKInvalidInputException as e:
output("\n" + e.message + "\n", color=BColors.FAIL) output("\n" + e.message + "\n", color=BColors.FAIL)

View File

@ -117,21 +117,14 @@ class Downloader:
def get_pages(self, *page_types: List[Type[Page]]) -> Generator[Page, None, None]: def get_pages(self, *page_types: List[Type[Page]]) -> Generator[Page, None, None]:
if len(page_types) == 0: if len(page_types) == 0:
page_types = _registered_pages.keys() page_types = self._registered_pages.keys()
for page_type in page_types: for page_type in page_types:
yield from self._registered_pages[page_type] yield from self._registered_pages[page_type]
def search(self, query: Query) -> SearchResults: def search(self, query: Query) -> Generator[DataObject, None, None]:
result = SearchResults()
for page in self.get_pages(): for page in self.get_pages():
result.add( yield from page.search(query=query)
page=type(page),
search_result=page.search(query=query)
)
return result
def fetch_details(self, data_object: DataObject, stop_at_level: int = 1, **kwargs) -> DataObject: def fetch_details(self, data_object: DataObject, stop_at_level: int = 1, **kwargs) -> DataObject:
source: Source source: Source

View File

@ -1,12 +1,16 @@
from __future__ import annotations from __future__ import annotations
import re import re
from collections import defaultdict
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Dict, Generator, List, Optional from typing import Any, Callable, Dict, Generator, List, Optional
from ..objects import OuterProxy as DataObject
from ..utils import BColors from ..utils import BColors
from ..utils.config import main_settings from ..utils.config import main_settings
from ..utils.enums import SourceType
from ..utils.exception import MKComposeException from ..utils.exception import MKComposeException
from ..utils.shared import ALPHABET
from ..utils.string_processing import unify from ..utils.string_processing import unify
@ -32,18 +36,44 @@ class Option:
keys: List[Any] = None, keys: List[Any] = None,
hidden: bool = False, hidden: bool = False,
parse_key: Callable[[Any], Any] = lambda x: x, parse_key: Callable[[Any], Any] = lambda x: x,
index: int = None,
): ):
self._parse_key: Callable[[Any], Any] = parse_key self._parse_key: Callable[[Any], Any] = parse_key
self._index = index
self.value = value self.value = value
self.text = text or str(value) self._text = text or str(value)
self.hidden = hidden self.hidden = hidden
self._raw_keys = set(keys or []) self._raw_keys = set(keys or [])
self._raw_keys.add(self.text) self._raw_keys.add(self.text)
self._raw_keys.add(self.value) try:
self._raw_keys.add(self.value)
except TypeError:
pass
self._raw_keys.add(str(self.value)) self._raw_keys.add(str(self.value))
self._raw_keys.add(self._index)
self.keys = set(self.parse_key(key) for key in self._raw_keys) self.keys = set(self.parse_key(key) for key in self._raw_keys)
@property
def text(self) -> str:
return self._text.replace("{index}", str(self.index))
@text.setter
def text(self, value: str):
self._text = value
@property
def index(self) -> int:
return self._index
@index.setter
def index(self, value: int):
p = self._parse_key(self._index)
if p in self.keys:
self.keys.remove(p)
self._index = value
self.keys.add(p)
def register_key(self, key: Any): def register_key(self, key: Any):
self._raw_keys.add(key) self._raw_keys.add(key)
@ -103,6 +133,12 @@ class Select:
for key in option.keys: for key in option.keys:
self._key_to_option[key] = option self._key_to_option[key] = option
def _remap(self):
self._key_to_option = dict()
for option in self._options:
for key in option.keys:
self._key_to_option[key] = option
def extend(self, options: List[Option]): def extend(self, options: List[Option]):
for option in options: for option in options:
self.append(option) self.append(option)
@ -118,7 +154,12 @@ class Select:
return self._parse_option_key(key) in self._key_to_option return self._parse_option_key(key) in self._key_to_option
def __getitem__(self, key: Any) -> Option: def __getitem__(self, key: Any) -> Option:
return self._key_to_option[self._parse_option_key(key)] r = self._key_to_option[self._parse_option_key(key)]
if callable(r):
r = r()
if callable(r.value):
r.value = r.value()
return r
def create_option(self, key: Any, **kwargs) -> Option: def create_option(self, key: Any, **kwargs) -> Option:
if not self.can_create_options: if not self.can_create_options:
@ -178,3 +219,85 @@ class GenreSelect(StringSelect):
def __init__(self): def __init__(self):
super().__init__(sort=True, raw_options=(genre.name for genre in filter(self.is_valid_genre, main_settings["music_directory"].iterdir()))) super().__init__(sort=True, raw_options=(genre.name for genre in filter(self.is_valid_genre, main_settings["music_directory"].iterdir())))
class SourceTypeToOption(dict):
def __init__(self, callback):
super().__init__()
self.callback = callback
def __missing__(self, key):
self[key] = self.callback(key)
return self[key]
class DataObjectSelect(Select):
def __init__(self, data_objects: Generator[DataObject]):
self._source_type_to_data_objects: Dict[SourceType, List[Option]] = defaultdict(list)
self._source_type_to_option: Dict[SourceType, Option] = SourceTypeToOption(self.option_from_source_type)
self._data_object_index: int = 0
self._source_type_index: int = 0
super().__init__(
parse_option_key=lambda x: unify(str(x)),
)
self.extend(data_objects)
def option_from_data_object(self, data_object: DataObject) -> Option:
index = self._data_object_index
self._data_object_index += 1
return Option(
value=data_object,
keys=[index, data_object.option_string, data_object.title_string],
text=f"{BColors.BOLD.value}{{index}}{BColors.ENDC.value}: {data_object.option_string}",
index=index,
)
def option_from_source_type(self, source_type: SourceType) -> Option:
index = ALPHABET[self._source_type_index % len(ALPHABET)]
self._source_type_index += 1
o = Option(
value=lambda: DataObjectSelect(self._source_type_to_data_objects[source_type]),
keys=[index, source_type],
text=f"{BColors.HEADER.value}({index}) --------------------------------{source_type.name:{'-'}<{21}}--------------------{BColors.ENDC.value}",
)
super().append(o)
return o
def append(self, option: Union[Option, DataObject]):
if isinstance(option, DataObject):
data_object = option
option = self.option_from_data_object(data_object)
else:
data_object = option.value
for source_type in data_object.source_collection.source_types(only_with_page=True):
self._source_type_to_data_objects[source_type].append(option)
super().append(option)
def __iter__(self):
source_types = list(sorted(self._source_type_to_data_objects.keys(), key=lambda x: x.name))
single_source = len(source_types) > 1
j = 0
for st in source_types:
if single_source:
yield self._source_type_to_option[st]
limit = min(15, len(self._source_type_to_data_objects[st])) if single_source else len(self._source_type_to_data_objects[st])
for i in range(limit):
o = self._source_type_to_data_objects[st][i]
o.index = j
yield o
j += 1
self._remap()

View File

@ -1,11 +1,11 @@
import random
from dotenv import load_dotenv
from pathlib import Path
import os import os
import random
from pathlib import Path
from dotenv import load_dotenv
from .path_manager import LOCATIONS
from .config import main_settings from .config import main_settings
from .path_manager import LOCATIONS
if not load_dotenv(Path(__file__).parent.parent.parent / ".env"): if not load_dotenv(Path(__file__).parent.parent.parent / ".env"):
load_dotenv(Path(__file__).parent.parent.parent / ".env.example") load_dotenv(Path(__file__).parent.parent.parent / ".env.example")
@ -51,3 +51,6 @@ have fun :3""".strip()
URL_PATTERN = r"https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+" URL_PATTERN = r"https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+"
INT_PATTERN = r"^\d*$" INT_PATTERN = r"^\d*$"
FLOAT_PATTERN = r"^[\d|\,|\.]*$" FLOAT_PATTERN = r"^[\d|\,|\.]*$"
ALPHABET = "abcdefghijklmnopqrstuvwxyz"