From 48eac48738d1c7af4c81d38540bd046bb6410339 Mon Sep 17 00:00:00 2001 From: Benexl Date: Sun, 13 Jul 2025 14:52:40 +0300 Subject: [PATCH] feat: single source of app level constants --- fastanime/__init__.py | 9 ----- fastanime/cli/cli.py | 49 ++++++++---------------- fastanime/cli/commands/config.py | 9 ++++- fastanime/cli/config/generate.py | 16 +++++++- fastanime/cli/utils/exceptions.py | 12 +++++- fastanime/cli/utils/logging.py | 8 +--- fastanime/core/constants.py | 10 ++++- fastanime/libs/selectors/fzf/selector.py | 30 ++++++++++----- 8 files changed, 80 insertions(+), 63 deletions(-) diff --git a/fastanime/__init__.py b/fastanime/__init__.py index 419c855..e6ebf75 100644 --- a/fastanime/__init__.py +++ b/fastanime/__init__.py @@ -1,4 +1,3 @@ -import importlib.metadata import sys if sys.version_info < (3, 10): @@ -7,14 +6,6 @@ if sys.version_info < (3, 10): ) -__version__ = importlib.metadata.version("FastAnime") - -APP_NAME = "FastAnime" -AUTHOR = "Benexl" -GIT_REPO = "github.com" -REPO = f"{GIT_REPO}/{AUTHOR}/{APP_NAME}" - - def FastAnime(): from .cli import run_cli diff --git a/fastanime/cli/cli.py b/fastanime/cli/cli.py index 6fcd53b..f8b6113 100644 --- a/fastanime/cli/cli.py +++ b/fastanime/cli/cli.py @@ -3,9 +3,8 @@ from typing import TYPE_CHECKING import click from click.core import ParameterSource -from .. import __version__ from ..core.config import AppConfig -from ..core.constants import PROJECT_NAME, USER_CONFIG_PATH +from ..core.constants import PROJECT_NAME, USER_CONFIG_PATH, __version__ from .config import ConfigLoader from .options import options_from_model from .utils.exceptions import setup_exceptions_handler @@ -24,6 +23,7 @@ if TYPE_CHECKING: dev: bool | None log: bool | None rich_traceback: bool | None + rich_traceback_theme: str commands = { @@ -39,38 +39,22 @@ commands = { context_settings=dict(auto_envvar_prefix=PROJECT_NAME), ) @click.version_option(__version__, "--version") +@click.option("--no-config", is_flag=True, help="Don't load the user config file.") @click.option( - "--no-config", - is_flag=True, - help="Don't load the user config file.", - envvar=f"{PROJECT_NAME}_NO_CONFIG", -) -@click.option( - "--trace", - is_flag=True, - help="Controls Whether to display tracebacks or not", - envvar=f"{PROJECT_NAME}_TRACE", -) -@click.option( - "--dev", - is_flag=True, - help="Controls Whether the app is in dev mode", - envvar=f"{PROJECT_NAME}_DEV", -) -@click.option( - "--log", is_flag=True, help="Controls Whether to log", envvar=f"{PROJECT_NAME}_LOG" -) -@click.option( - "--log-to-file", - is_flag=True, - help="Controls Whether to log to a file", - envvar=f"{PROJECT_NAME}_LOG_TO_FILE", + "--trace", is_flag=True, help="Controls Whether to display tracebacks or not" ) +@click.option("--dev", is_flag=True, help="Controls Whether the app is in dev mode") +@click.option("--log", is_flag=True, help="Controls Whether to log") +@click.option("--log-to-file", is_flag=True, help="Controls Whether to log to a file") @click.option( "--rich-traceback", is_flag=True, help="Controls Whether to display a rich traceback", - envvar=f"{PROJECT_NAME}_LOG_TO_FILE", +) +@click.option( + "--rich-traceback-theme", + default="github-dark", + help="Controls Whether to display a rich traceback", ) @options_from_model(AppConfig) @click.pass_context @@ -78,12 +62,13 @@ def cli(ctx: click.Context, **options: "Unpack[Options]"): """ The main entry point for the FastAnime CLI. """ - setup_logging( - options["log"], - options["log_to_file"], + setup_logging(options["log"], options["log_to_file"]) + setup_exceptions_handler( + options["trace"], + options["dev"], options["rich_traceback"], + options["rich_traceback_theme"], ) - setup_exceptions_handler(options["trace"], options["dev"]) loader = ConfigLoader(config_path=USER_CONFIG_PATH) config = AppConfig.model_validate({}) if options["no_config"] else loader.load() diff --git a/fastanime/cli/commands/config.py b/fastanime/cli/commands/config.py index 5aee4fb..e991983 100644 --- a/fastanime/cli/commands/config.py +++ b/fastanime/cli/commands/config.py @@ -113,8 +113,13 @@ def _generate_desktop_entry(): from rich import print from rich.prompt import Confirm - from ... import __version__ - from ...core.constants import ICON_PATH, PLATFORM, PROJECT_NAME, USER_APPLICATIONS + from ...core.constants import ( + ICON_PATH, + PLATFORM, + PROJECT_NAME, + USER_APPLICATIONS, + __version__, + ) EXECUTABLE = shutil.which("fastanime") if EXECUTABLE: diff --git a/fastanime/cli/config/generate.py b/fastanime/cli/config/generate.py index 6ec4eb7..5489184 100644 --- a/fastanime/cli/config/generate.py +++ b/fastanime/cli/config/generate.py @@ -2,7 +2,7 @@ import textwrap from pathlib import Path from ...core.config import AppConfig -from ...core.constants import APP_ASCII_ART +from ...core.constants import APP_ASCII_ART, DISCORD_INVITE, PROJECT_NAME, REPO_HOME # The header for the config file. config_asci = "\n".join([f"# {line}" for line in APP_ASCII_ART.split()]) @@ -17,6 +17,19 @@ CONFIG_HEADER = f""" # For path-based options, you can use '~' for your home directory. """.lstrip() +CONFIG_FOOTER = f""" +# ============================================================================== +# +# HOPE YOU ENJOY {PROJECT_NAME} AND BE SURE TO STAR THE PROJECT ON GITHUB +# {REPO_HOME} +# +# Also join the discord server +# where the anime tech community lives :) +# {DISCORD_INVITE} +# +# ============================================================================== +""".lstrip() + def generate_config_ini_from_app_model(app_model: AppConfig) -> str: """Generate a configuration file content from a Pydantic model.""" @@ -61,4 +74,5 @@ def generate_config_ini_from_app_model(app_model: AppConfig) -> str: config_ini_content.append(f"{field_name} = {value_str}") + config_ini_content.extend(["\n", CONFIG_FOOTER]) return "\n".join(config_ini_content) diff --git a/fastanime/cli/utils/exceptions.py b/fastanime/cli/utils/exceptions.py index 6b6c247..b3cafa4 100644 --- a/fastanime/cli/utils/exceptions.py +++ b/fastanime/cli/utils/exceptions.py @@ -1,16 +1,24 @@ import sys +from rich.traceback import install as rich_install + def custom_exception_hook(exc_type, exc_value, exc_traceback): print(f"{exc_type.__name__}: {exc_value}") default_exception_hook = sys.excepthook -# sys.tracebacklimit = 0 -def setup_exceptions_handler(trace: bool | None, dev: bool | None): +def setup_exceptions_handler( + trace: bool | None, + dev: bool | None, + rich_traceback: bool | None, + rich_traceback_theme: str, +): if trace or dev: sys.excepthook = default_exception_hook + if rich_traceback: + rich_install(show_locals=True, theme=rich_traceback_theme) else: sys.excepthook = custom_exception_hook diff --git a/fastanime/cli/utils/logging.py b/fastanime/cli/utils/logging.py index 8499139..8a43597 100644 --- a/fastanime/cli/utils/logging.py +++ b/fastanime/cli/utils/logging.py @@ -1,16 +1,10 @@ import logging -from rich.traceback import install as rich_install - from ...core.constants import LOG_FILE_PATH -def setup_logging( - log: bool | None, log_file: bool | None, rich_traceback: bool | None -) -> None: +def setup_logging(log: bool | None, log_file: bool | None) -> None: """Configures the application's logging based on CLI flags.""" - if rich_traceback: - rich_install(show_locals=True) if log: from rich.logging import RichHandler diff --git a/fastanime/core/constants.py b/fastanime/core/constants.py index a06b306..8f48f3f 100644 --- a/fastanime/core/constants.py +++ b/fastanime/core/constants.py @@ -1,12 +1,20 @@ import os import sys -from importlib import resources +from importlib import metadata, resources from pathlib import Path PLATFORM = sys.platform APP_NAME = os.environ.get("FASTANIME_APP_NAME", "fastanime") PROJECT_NAME = "FASTANIME" +__version__ = metadata.version(PROJECT_NAME) + +AUTHOR = "Benexl" +GIT_REPO = "github.com" +GIT_PROTOCOL = "https://" +REPO_HOME = f"https://{GIT_REPO}/{AUTHOR}/FastAnime" +DISCORD_INVITE = "https://discord.gg/C4rhMA4mmK" + try: APP_DIR = Path(str(resources.files(PROJECT_NAME.lower()))) diff --git a/fastanime/libs/selectors/fzf/selector.py b/fastanime/libs/selectors/fzf/selector.py index 937b618..409dd6b 100644 --- a/fastanime/libs/selectors/fzf/selector.py +++ b/fastanime/libs/selectors/fzf/selector.py @@ -4,6 +4,7 @@ import shutil import subprocess from ....core.config import FzfConfig +from ....core.exceptions import FastAnimeError from ..base import BaseSelector logger = logging.getLogger(__name__) @@ -14,7 +15,7 @@ class FzfSelector(BaseSelector): self.config = config self.executable = shutil.which("fzf") if not self.executable: - raise FileNotFoundError("fzf executable not found in PATH.") + raise FastAnimeError("Please install fzf to use the fzf selector") os.environ["FZF_DEFAULT_OPTS"] = self.config.opts @@ -29,15 +30,19 @@ class FzfSelector(BaseSelector): def choose(self, prompt, choices, *, preview=None, header=None): fzf_input = "\n".join(choices) - # Build command from base options and specific arguments - commands = [] - commands.extend(["--prompt", f"{prompt.title()}: "]) - commands.extend(["--header", self.header, "--header-first"]) + commands = [ + self.executable, + "--prompt", + f"{prompt.title()}: ", + "--header", + self.header, + "--header-first", + ] if preview: commands.extend(["--preview", preview]) result = subprocess.run( - [self.executable, *commands], + commands, input=fzf_input, stdout=subprocess.PIPE, text=True, @@ -54,11 +59,18 @@ class FzfSelector(BaseSelector): def ask(self, prompt, *, default=None): # Use FZF's --print-query to capture user input - commands = [] - commands.extend(["--prompt", f"{prompt}: ", "--print-query"]) + commands = [ + self.executable, + "--prompt", + f"{prompt.title()}: ", + "--header", + self.header, + "--header-first", + "--print-query", + ] result = subprocess.run( - [self.executable, *commands], + commands, input="", stdout=subprocess.PIPE, text=True,