diff --git a/fastanime/cli/__init__.py b/fastanime/cli/__init__.py index 9ad771c..29e88c6 100644 --- a/fastanime/cli/__init__.py +++ b/fastanime/cli/__init__.py @@ -60,7 +60,7 @@ signal.signal(signal.SIGINT, handle_exit) fastanime --icons --default anilist \b # viewing manga - fastanime --manga search -t + fastanime --manga search -t """, ) @click.version_option(__version__, "--version") @@ -142,7 +142,7 @@ signal.signal(signal.SIGINT, handle_exit) @click.option( "--normalize-titles/--no-normalize-titles", type=bool, - help="whether to normalize anime and episode titls given by providers", + help="whether to normalize anime and episode titles given by providers", ) @click.option("-d", "--downloads-dir", type=click.Path(), help="Downloads location") @click.option("--fzf", is_flag=True, help="Use fzf for the ui") diff --git a/fastanime/cli/commands/anilist/login.py b/fastanime/cli/commands/anilist/login.py index d2d68d2..c6c4319 100644 --- a/fastanime/cli/commands/anilist/login.py +++ b/fastanime/cli/commands/anilist/login.py @@ -19,7 +19,7 @@ def login(config: "Config", status, erase): if status: is_logged_in = True if config.user else False message = ( - "You are logged in :smile:" if is_logged_in else "You arent logged in :cry:" + "You are logged in :smile:" if is_logged_in else "You aren't logged in :cry:" ) print(message) print(config.user) diff --git a/fastanime/cli/commands/anilist/random_anime.py b/fastanime/cli/commands/anilist/random_anime.py index 5fdd47f..209b781 100644 --- a/fastanime/cli/commands/anilist/random_anime.py +++ b/fastanime/cli/commands/anilist/random_anime.py @@ -2,7 +2,7 @@ import click @click.command( - help="Get random anime from anilist based on a range of anilist anime ids that are seected at random", + help="Get random anime from anilist based on a range of anilist anime ids that are selected at random", short_help="View random anime", ) @click.option( diff --git a/fastanime/cli/commands/anilist/upcoming.py b/fastanime/cli/commands/anilist/upcoming.py index 8472e48..65ef8df 100644 --- a/fastanime/cli/commands/anilist/upcoming.py +++ b/fastanime/cli/commands/anilist/upcoming.py @@ -2,7 +2,7 @@ import click @click.command( - help="Fetch the 15 most anticipited anime", short_help="View upcoming anime" + help="Fetch the 15 most anticipated anime", short_help="View upcoming anime" ) @click.option( "--dump-json", diff --git a/fastanime/cli/completion_functions.py b/fastanime/cli/completion_functions.py index 358ec96..65affc9 100644 --- a/fastanime/cli/completion_functions.py +++ b/fastanime/cli/completion_functions.py @@ -59,7 +59,7 @@ def get_anime_titles(query: str, variables: dict = {}): else: return [] except Exception as e: - logger.error(f"Something unexpected occured {e}") + logger.error(f"Something unexpected occurred {e}") return [] diff --git a/fastanime/cli/interfaces/anilist_interfaces.py b/fastanime/cli/interfaces/anilist_interfaces.py index d22cbe1..4ac8ad9 100644 --- a/fastanime/cli/interfaces/anilist_interfaces.py +++ b/fastanime/cli/interfaces/anilist_interfaces.py @@ -764,18 +764,22 @@ def provider_anime_episodes_menu( if not current_episode_number or current_episode_number not in available_episodes: choices = [*available_episodes, "Back"] preview = None - if config.preview: - from .utils import get_fzf_episode_preview - - e = fastanime_runtime_state.selected_anime_anilist["episodes"] - if e: - eps = range(0, e + 1) - else: - eps = available_episodes - preview = get_fzf_episode_preview( - fastanime_runtime_state.selected_anime_anilist, eps - ) if config.use_fzf: + if config.preview: + from .utils import get_fzf_episode_preview + + e = fastanime_runtime_state.selected_anime_anilist["episodes"] + if e: + eps = range(0, e + 1) + else: + eps = available_episodes + preview = get_fzf_episode_preview( + fastanime_runtime_state.selected_anime_anilist, eps + ) + + if not preview: + print("Failed to find bash executable which is necessary for preview with fzf.\nIf you are on Windows, please make sure Git is installed and available in PATH.") + current_episode_number = fzf.run( choices, prompt="Select Episode", header=anime_title, preview=preview ) @@ -1498,6 +1502,9 @@ def anilist_results_menu( from .utils import get_fzf_anime_preview preview = get_fzf_anime_preview(search_results, anime_data.keys()) + if not preview: + print("Failed to find bash executable which is necessary for preview with fzf.\nIf you are on Windows, please make sure Git is installed and available in PATH.") + selected_anime_title = fzf.run( choices, prompt="Select Anime", diff --git a/fastanime/cli/interfaces/utils.py b/fastanime/cli/interfaces/utils.py index ca93bb1..e86d414 100644 --- a/fastanime/cli/interfaces/utils.py +++ b/fastanime/cli/interfaces/utils.py @@ -13,7 +13,7 @@ from ...constants import APP_CACHE_DIR, S_PLATFORM from ...libs.anilist.types import AnilistBaseMediaDataSchema from ...Utility import anilist_data_helper from ..utils.scripts import fzf_preview -from ..utils.utils import get_true_fg +from ..utils.utils import get_true_fg, which_bashlike logger = logging.getLogger(__name__) @@ -96,7 +96,7 @@ def write_search_results( titles: list[str], workers: int | None = None, ): - """A helper function used by and run in a background thread by get_fzf_preview function inorder to get the actual preview data to be displayed by fzf + """A helper function used by and run in a background thread by get_fzf_preview function in order to get the actual preview data to be displayed by fzf Args: anilist_results: the anilist results from an anilist action @@ -122,7 +122,7 @@ def write_search_results( # handle the text data template = f""" ll=2 - while [ $ll -le $FZF_PREVIEW_COLUMNS ];do + while [ $ll -le $FZF_PREVIEW_COLUMNS ];do echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}" ((ll++)) done @@ -130,7 +130,7 @@ def write_search_results( echo "{get_true_fg('Title(jp):',*HEADER_COLOR)} {(anime['title']['romaji'] or "").replace('"',SINGLE_QUOTE)}" echo "{get_true_fg('Title(eng):',*HEADER_COLOR)} {(anime['title']['english'] or "").replace('"',SINGLE_QUOTE)}" ll=2 - while [ $ll -le $FZF_PREVIEW_COLUMNS ];do + while [ $ll -le $FZF_PREVIEW_COLUMNS ];do echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}" ((ll++)) done @@ -141,7 +141,7 @@ def write_search_results( echo "{get_true_fg('Next Episode:',*HEADER_COLOR)} {anilist_data_helper.extract_next_airing_episode(anime['nextAiringEpisode']).replace('"',SINGLE_QUOTE)}" echo "{get_true_fg('Genres:',*HEADER_COLOR)} {anilist_data_helper.format_list_data_with_comma(anime['genres']).replace('"',SINGLE_QUOTE)}" ll=2 - while [ $ll -le $FZF_PREVIEW_COLUMNS ];do + while [ $ll -le $FZF_PREVIEW_COLUMNS ];do echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}" ((ll++)) done @@ -150,7 +150,7 @@ def write_search_results( echo "{get_true_fg('Start Date:',*HEADER_COLOR)} {anilist_data_helper.format_anilist_date_object(anime['startDate']).replace('"',SINGLE_QUOTE)}" echo "{get_true_fg('End Date:',*HEADER_COLOR)} {anilist_data_helper.format_anilist_date_object(anime['endDate']).replace('"',SINGLE_QUOTE)}" ll=2 - while [ $ll -le $FZF_PREVIEW_COLUMNS ];do + while [ $ll -le $FZF_PREVIEW_COLUMNS ];do echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}" ((ll++)) done @@ -158,7 +158,7 @@ def write_search_results( echo "{get_true_fg('Media List:',*HEADER_COLOR)} {mediaListName.replace('"',SINGLE_QUOTE)}" echo "{get_true_fg('Progress:',*HEADER_COLOR)} {progress}" ll=2 - while [ $ll -le $FZF_PREVIEW_COLUMNS ];do + while [ $ll -le $FZF_PREVIEW_COLUMNS ];do echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}" ((ll++)) done @@ -279,6 +279,9 @@ def get_fzf_episode_preview( titles (list[str]): sanitized titles of the anime; NOTE: its important that they are sanitized since they are used as the filenames of the images workers ([TODO:parameter]): Number of threads to use to download the images; defaults to as many as possible anilist_results: the anilist results from an anilist action + + Returns: + The fzf preview script to use or None if the bash is not found """ # HEADER_COLOR = 215, 0, 95 @@ -305,7 +308,7 @@ def get_fzf_episode_preview( template = textwrap.dedent( f""" ll=2 - while [ $ll -le $FZF_PREVIEW_COLUMNS ];do + while [ $ll -le $FZF_PREVIEW_COLUMNS ];do echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}" ((ll++)) done @@ -313,13 +316,13 @@ def get_fzf_episode_preview( echo "{get_true_fg('Anime Title(jp):',*HEADER_COLOR)} {(anilist_result['title']['romaji'] or '').replace('"',SINGLE_QUOTE)}" ll=2 - while [ $ll -le $FZF_PREVIEW_COLUMNS ];do + while [ $ll -le $FZF_PREVIEW_COLUMNS ];do echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}" ((ll++)) done echo "{str(episode_title).replace('"',SINGLE_QUOTE)}" ll=2 - while [ $ll -le $FZF_PREVIEW_COLUMNS ];do + while [ $ll -le $FZF_PREVIEW_COLUMNS ];do echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}" ((ll++)) done @@ -345,22 +348,26 @@ def get_fzf_episode_preview( background_worker.start() # the preview script is in bash so making sure fzf doesnt use any other shell lang to process the preview script - os.environ["SHELL"] = shutil.which("bash") or "bash" + bash_path = which_bashlike() + if not bash_path: + return + + os.environ["SHELL"] = bash_path if S_PLATFORM == "win32": preview = """ %s title={} show_image_previews="%s" dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES} - if [ $show_image_previews = "true" ];then - if [ -s "%s\\\\\\${title}.png" ]; then + if [ $show_image_previews = "true" ];then + if [ -s "%s\\\\\\${title}.png" ]; then if command -v "chafa">/dev/null;then chafa -s $dim "%s\\\\\\${title}.png" else echo please install chafa to enjoy image previews fi - echo - else + echo + else echo Loading... fi fi @@ -380,7 +387,7 @@ def get_fzf_episode_preview( title={} %s show_image_previews="%s" - if [ $show_image_previews = "true" ];then + if [ $show_image_previews = "true" ];then if [ -s %s/${title}.png ]; then fzf-preview %s/${title}.png else echo Loading... fi @@ -412,7 +419,7 @@ def get_fzf_anime_preview( anilist_results: the anilist results got from an anilist action Returns: - THe fzf preview script to use + The fzf preview script to use or None if the bash is not found """ # ensure images and info exists @@ -423,7 +430,12 @@ def get_fzf_anime_preview( background_worker.start() # the preview script is in bash so making sure fzf doesnt use any other shell lang to process the preview script - os.environ["SHELL"] = shutil.which("bash") or "bash" + bash_path = which_bashlike() + if not bash_path: + return + + os.environ["SHELL"] = bash_path + if S_PLATFORM == "win32": preview = """ %s @@ -431,14 +443,14 @@ def get_fzf_anime_preview( show_image_previews="%s" dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES} if [ $show_image_previews = "true" ];then - if [ -s "%s\\\\\\${title}.png" ]; then + if [ -s "%s\\\\\\${title}.png" ]; then if command -v "chafa">/dev/null;then chafa -s $dim "%s\\\\\\${title}.png" else echo please install chafa to enjoy image previews fi - echo - else + echo + else echo Loading... fi fi diff --git a/fastanime/cli/utils/print_img.py b/fastanime/cli/utils/print_img.py index e2f5879..f3c78d4 100644 --- a/fastanime/cli/utils/print_img.py +++ b/fastanime/cli/utils/print_img.py @@ -5,7 +5,7 @@ import requests def print_img(url: str): - """helper funtion to print an image given its url + """helper function to print an image given its url Args: url: [TODO:description] @@ -25,7 +25,7 @@ def print_img(url: str): return img_bytes = res.content """ - Change made in call to chafa. Chafa dev dropped abilty + Change made in call to chafa. Chafa dev dropped ability to pull from urls. Keeping old line here just in case. subprocess.run([EXECUTABLE, url, "--size=15x15"], input=img_bytes) diff --git a/fastanime/cli/utils/utils.py b/fastanime/cli/utils/utils.py index 86c34fe..a23c004 100644 --- a/fastanime/cli/utils/utils.py +++ b/fastanime/cli/utils/utils.py @@ -1,8 +1,11 @@ import logging +import shutil from typing import TYPE_CHECKING from InquirerPy import inquirer +from fastanime.constants import S_PLATFORM + logger = logging.getLogger(__name__) if TYPE_CHECKING: from ...libs.anime_provider.types import EpisodeStream @@ -92,7 +95,7 @@ def filter_by_quality(quality: str, stream_links: "list[EpisodeStream]", default def format_bytes_to_human(num_of_bytes: float, suffix: str = "B"): - """Helper function usedd to format bytes to human + """Helper function used to format bytes to human Args: num_of_bytes: the number of bytes to format @@ -155,3 +158,41 @@ def fuzzy_inquirer(choices: list, prompt: str, **kwargs): **kwargs, ).execute() return action + + +def which_win32_gitbash(): + """Helper function that returns absolute path to the git bash executable + (came with Git for Windows) on Windows + + Returns: + the path to the git bash executable or None if not found + """ + from os import path + + gb_path = shutil.which("bash") + + # Windows came with its own bash.exe but it's just an entry point for WSL not Git Bash + if gb_path and not path.dirname(gb_path).lower().endswith("windows\\system32"): + return gb_path + + git_path = shutil.which("git") + + if git_path: + if path.dirname(git_path).endswith("cmd"): + gb_path = path.abspath( + path.join(path.dirname(git_path), "..", "bin", "bash.exe") + ) + else: + gb_path = path.join(path.dirname(git_path), "bash.exe") + + if path.exists(gb_path): + return gb_path + + +def which_bashlike(): + """Helper function that returns absolute path to the bash executable for the current platform + + Returns: + the path to the bash executable or None if not found + """ + return (shutil.which("bash") or "bash") if S_PLATFORM != "win32" else which_win32_gitbash() \ No newline at end of file diff --git a/fastanime/libs/anilist/api.py b/fastanime/libs/anilist/api.py index 1cf0651..0abc350 100644 --- a/fastanime/libs/anilist/api.py +++ b/fastanime/libs/anilist/api.py @@ -64,7 +64,7 @@ class AniListApi: self.session = requests.session() def login_user(self, token: str): - """methosd used to login a new user enabling authenticated requests + """method used to login a new user enabling authenticated requests Args: token: anilist app token @@ -247,7 +247,7 @@ class AniListApi: return (False, None) except Exception as e: - logger.error(f"Something unexpected occured {e}") + logger.error(f"Something unexpected occurred {e}") return (False, None) # type: ignore def get_data( @@ -311,7 +311,7 @@ class AniListApi: }, ) # type: ignore except Exception as e: - logger.error(f"Something unexpected occured {e}") + logger.error(f"Something unexpected occurred {e}") return (False, {"Error": f"{e}"}) # type: ignore def search( @@ -456,7 +456,7 @@ class AniListApi: recommended_anime = self.get_data(recommended_query, variables) return recommended_anime - def get_charcters_of(self, id: int, type="ANIME", *_, **kwargs): + def get_characters_of(self, id: int, type="ANIME", *_, **kwargs): variables = {"id": id} characters = self.get_data(anime_characters_query, variables) return characters diff --git a/fastanime/libs/anilist/queries_graphql.py b/fastanime/libs/anilist/queries_graphql.py index 21e82d3..13eaf10 100644 --- a/fastanime/libs/anilist/queries_graphql.py +++ b/fastanime/libs/anilist/queries_graphql.py @@ -1,5 +1,5 @@ """ -This module contains all the preset queries for the sake of neatness and convinience +This module contains all the preset queries for the sake of neatness and convenience Mostly for internal usage """ @@ -26,7 +26,7 @@ query($id:Int){ } } body - + } } } @@ -88,7 +88,7 @@ query{ large medium } - + } } """ @@ -909,7 +909,7 @@ query ($id: Int,$type:MediaType) { airingAt timeUntilAiring episode - + } } } diff --git a/fastanime/libs/common/mini_anilist.py b/fastanime/libs/common/mini_anilist.py index 2ad069b..dc4f1b4 100644 --- a/fastanime/libs/common/mini_anilist.py +++ b/fastanime/libs/common/mini_anilist.py @@ -246,7 +246,7 @@ def get_mal_id_and_anilist_id(anime_title: str) -> "dict[str,int] | None": ) return {"id_anilist": anime["id"], "id_mal": anime["idMal"]} except Exception as e: - logger.error(f"Something unexpected occured {e}") + logger.error(f"Something unexpected occurred {e}") def get_basic_anime_info_by_title(anime_title: str): @@ -320,4 +320,4 @@ def get_basic_anime_info_by_title(anime_title: str): ], } except Exception as e: - logger.error(f"Something unexpected occured {e}") + logger.error(f"Something unexpected occurred {e}") diff --git a/fastanime/libs/fzf/__init__.py b/fastanime/libs/fzf/__init__.py index 9b2a887..f52ee0f 100644 --- a/fastanime/libs/fzf/__init__.py +++ b/fastanime/libs/fzf/__init__.py @@ -8,10 +8,12 @@ from typing import Callable, List from click import clear from rich import print +from ...cli.utils.tools import exit_app + logger = logging.getLogger(__name__) -FZF_DEFAULT_OPTS = """ +FZF_DEFAULT_OPTS = """ --color=fg:#d0d0d0,fg+:#d0d0d0,bg:#121212,bg+:#262626 --color=hl:#5f87af,hl+:#5fd7ff,info:#afaf87,marker:#87ff00 --color=prompt:#d7005f,spinner:#af5fff,pointer:#af5fff,header:#87afaf @@ -127,7 +129,10 @@ class FZF: encoding="utf-8", ) if not result or result.returncode != 0 or not result.stdout: - print("sth went wrong:confused:") + if result.returncode == 130: # fzf terminated by ctrl-c + exit_app() + + print("sth went wrong :confused:") input("press enter to try again...") clear() return self._run_fzf(commands, _fzf_input)