From f9cb9ebaefd7163276c1aefdb7730e6eae188dc0 Mon Sep 17 00:00:00 2001 From: Roland Thomas Date: Sun, 4 May 2025 19:35:44 -0400 Subject: [PATCH] Change color -> style, Add SelectionOption --- falyx/__init__.py | 2 ++ falyx/command.py | 4 ++-- falyx/config.py | 2 +- falyx/falyx.py | 34 +++++++++++++++++----------------- falyx/hooks.py | 12 +++++++++--- falyx/menu_action.py | 4 ++-- falyx/selection.py | 32 +++++++++++++++++++++++++------- falyx/selection_action.py | 9 +++++---- falyx/tagged_table.py | 2 +- 9 files changed, 64 insertions(+), 37 deletions(-) diff --git a/falyx/__init__.py b/falyx/__init__.py index 02d0227..10b5003 100644 --- a/falyx/__init__.py +++ b/falyx/__init__.py @@ -12,6 +12,7 @@ from .command import Command from .context import ExecutionContext, SharedContext from .execution_registry import ExecutionRegistry from .falyx import Falyx +from .hook_manager import HookType logger = logging.getLogger("falyx") @@ -27,4 +28,5 @@ __all__ = [ "ExecutionContext", "SharedContext", "ExecutionRegistry", + "HookType", ] diff --git a/falyx/command.py b/falyx/command.py index 999e2cc..4c1ac83 100644 --- a/falyx/command.py +++ b/falyx/command.py @@ -68,7 +68,7 @@ class Command(BaseModel): args (tuple): Static positional arguments. kwargs (dict): Static keyword arguments. help_text (str): Additional help or guidance text. - color (str): Color theme for CLI rendering. + style (str): Rich style for description. confirm (bool): Whether to require confirmation before executing. confirm_message (str): Custom confirmation prompt. preview_before_confirm (bool): Whether to preview before confirming. @@ -101,7 +101,7 @@ class Command(BaseModel): hidden: bool = False aliases: list[str] = Field(default_factory=list) help_text: str = "" - color: str = OneColors.WHITE + style: str = OneColors.WHITE confirm: bool = False confirm_message: str = "Are you sure?" preview_before_confirm: bool = True diff --git a/falyx/config.py b/falyx/config.py index 3bca858..2b028a5 100644 --- a/falyx/config.py +++ b/falyx/config.py @@ -93,7 +93,7 @@ def loader(file_path: Path | str) -> list[dict[str, Any]]: "hidden": entry.get("hidden", False), "aliases": entry.get("aliases", []), "help_text": entry.get("help_text", ""), - "color": entry.get("color", "white"), + "style": entry.get("style", "white"), "confirm": entry.get("confirm", False), "confirm_message": entry.get("confirm_message", "Are you sure?"), "preview_before_confirm": entry.get("preview_before_confirm", True), diff --git a/falyx/falyx.py b/falyx/falyx.py index 19bc341..547e19a 100644 --- a/falyx/falyx.py +++ b/falyx/falyx.py @@ -231,7 +231,7 @@ class Falyx: key="Q", description="Exit", aliases=["EXIT", "QUIT"], - color=OneColors.DARK_RED, + style=OneColors.DARK_RED, ) def _get_history_command(self) -> Command: @@ -241,7 +241,7 @@ class Falyx: description="History", aliases=["HISTORY"], action=er.get_history_action(), - color=OneColors.DARK_YELLOW, + style=OneColors.DARK_YELLOW, ) async def _show_help(self): @@ -256,28 +256,28 @@ class Falyx: if command.requires_input: help_text += " [dim](requires input)[/dim]" table.add_row( - f"[{command.color}]{command.key}[/]", + f"[{command.style}]{command.key}[/]", ", ".join(command.aliases) if command.aliases else "None", help_text, ", ".join(command.tags) if command.tags else "None", ) table.add_row( - f"[{self.exit_command.color}]{self.exit_command.key}[/]", + f"[{self.exit_command.style}]{self.exit_command.key}[/]", ", ".join(self.exit_command.aliases), "Exit this menu or program", ) if self.history_command: table.add_row( - f"[{self.history_command.color}]{self.history_command.key}[/]", + f"[{self.history_command.style}]{self.history_command.key}[/]", ", ".join(self.history_command.aliases), "History of executed actions", ) if self.help_command: table.add_row( - f"[{self.help_command.color}]{self.help_command.key}[/]", + f"[{self.help_command.style}]{self.help_command.key}[/]", ", ".join(self.help_command.aliases), "Show this help menu", ) @@ -291,7 +291,7 @@ class Falyx: aliases=["HELP"], description="Help", action=self._show_help, - color=OneColors.LIGHT_YELLOW, + style=OneColors.LIGHT_YELLOW, ) def _get_completer(self) -> WordCompleter: @@ -526,7 +526,7 @@ class Falyx: description: str = "Exit", aliases: list[str] | None = None, action: Callable[[], Any] = lambda: None, - color: str = OneColors.DARK_RED, + style: str = OneColors.DARK_RED, confirm: bool = False, confirm_message: str = "Are you sure?", ) -> None: @@ -539,19 +539,19 @@ class Falyx: description=description, aliases=aliases if aliases else self.exit_command.aliases, action=action, - color=color, + style=style, confirm=confirm, confirm_message=confirm_message, ) def add_submenu( - self, key: str, description: str, submenu: "Falyx", color: str = OneColors.CYAN + self, key: str, description: str, submenu: "Falyx", style: str = OneColors.CYAN ) -> None: """Adds a submenu to the menu.""" if not isinstance(submenu, Falyx): raise NotAFalyxError("submenu must be an instance of Falyx.") self._validate_command_key(key) - self.add_command(key, description, submenu.menu, color=color) + self.add_command(key, description, submenu.menu, style=style) submenu.update_exit_command(key="B", description="Back", aliases=["BACK"]) def add_commands(self, commands: list[dict]) -> None: @@ -569,7 +569,7 @@ class Falyx: hidden: bool = False, aliases: list[str] | None = None, help_text: str = "", - color: str = OneColors.WHITE, + style: str = OneColors.WHITE, confirm: bool = False, confirm_message: str = "Are you sure?", preview_before_confirm: bool = True, @@ -602,7 +602,7 @@ class Falyx: hidden=hidden, aliases=aliases if aliases else [], help_text=help_text, - color=color, + style=style, confirm=confirm, confirm_message=confirm_message, preview_before_confirm=preview_before_confirm, @@ -644,14 +644,14 @@ class Falyx: bottom_row = [] if self.history_command: bottom_row.append( - f"[{self.history_command.key}] [{self.history_command.color}]{self.history_command.description}" + f"[{self.history_command.key}] [{self.history_command.style}]{self.history_command.description}" ) if self.help_command: bottom_row.append( - f"[{self.help_command.key}] [{self.help_command.color}]{self.help_command.description}" + f"[{self.help_command.key}] [{self.help_command.style}]{self.help_command.description}" ) bottom_row.append( - f"[{self.exit_command.key}] [{self.exit_command.color}]{self.exit_command.description}" + f"[{self.exit_command.key}] [{self.exit_command.style}]{self.exit_command.description}" ) return bottom_row @@ -662,7 +662,7 @@ class Falyx: for chunk in chunks(visible_commands, self.columns): row = [] for key, command in chunk: - row.append(f"[{key}] [{command.color}]{command.description}") + row.append(f"[{key}] [{command.style}]{command.description}") table.add_row(*row) bottom_row = self.get_bottom_row() for row in chunks(bottom_row, self.columns): diff --git a/falyx/hooks.py b/falyx/hooks.py index fbeeb39..0ae3953 100644 --- a/falyx/hooks.py +++ b/falyx/hooks.py @@ -1,7 +1,7 @@ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed """hooks.py""" import time -from typing import Callable +from typing import Any, Callable from falyx.context import ExecutionContext from falyx.exceptions import CircuitBreakerOpen @@ -10,11 +10,17 @@ from falyx.utils import logger class ResultReporter: - def __init__(self, formatter: Callable[[], str] | None = None): + def __init__(self, formatter: Callable[[Any], str] | None = None): """ Optional result formatter. If not provided, uses repr(result). """ - self.formatter = formatter or (lambda r: repr(r)) + self.formatter = formatter or (self.default_formatter) + + def default_formatter(self, result: Any): + """ + Default formatter for results. Converts the result to a string. + """ + return repr(result) @property def __name__(self): diff --git a/falyx/menu_action.py b/falyx/menu_action.py index 183e886..a49f43a 100644 --- a/falyx/menu_action.py +++ b/falyx/menu_action.py @@ -23,7 +23,7 @@ from falyx.utils import CaseInsensitiveDict, chunks, logger class MenuOption: description: str action: BaseAction - color: str = OneColors.WHITE + style: str = OneColors.WHITE def __post_init__(self): if not isinstance(self.description, str): @@ -33,7 +33,7 @@ class MenuOption: def render(self, key: str) -> str: """Render the menu option for display.""" - return f"[{OneColors.WHITE}][{key}][/] [{self.color}]{self.description}[/]" + return f"[{OneColors.WHITE}][{key}][/] [{self.style}]{self.description}[/]" class MenuOptionMap(CaseInsensitiveDict): diff --git a/falyx/selection.py b/falyx/selection.py index a85a2a5..4230ceb 100644 --- a/falyx/selection.py +++ b/falyx/selection.py @@ -1,5 +1,6 @@ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed """selection.py""" +from dataclasses import dataclass from typing import Any, Callable, KeysView, Sequence from prompt_toolkit import PromptSession @@ -13,6 +14,21 @@ from falyx.utils import chunks from falyx.validators import int_range_validator, key_validator +@dataclass +class SelectionOption: + description: str + value: Any + style: str = OneColors.WHITE + + def __post_init__(self): + if not isinstance(self.description, str): + raise TypeError("SelectionOption description must be a string.") + + def render(self, key: str) -> str: + """Render the selection option for display.""" + return f"[{OneColors.WHITE}][{key}][/] [{self.style}]{self.description}[/]" + + def render_table_base( title: str, caption: str = "", @@ -139,7 +155,7 @@ def render_selection_indexed_table( def render_selection_dict_table( title: str, - selections: dict[str, tuple[str, Any]], + selections: dict[str, SelectionOption], columns: int = 2, caption: str = "", box_style: box.Box = box.SIMPLE, @@ -172,8 +188,10 @@ def render_selection_dict_table( for chunk in chunks(selections.items(), columns): row = [] - for key, value in chunk: - row.append(f"[{OneColors.WHITE}][{key.upper()}] {value[0]}") + for key, option in chunk: + row.append( + f"[{OneColors.WHITE}][{key.upper()}] [{option.style}]{option.description}[/]" + ) table.add_row(*row) return table @@ -281,7 +299,7 @@ async def select_value_from_list( async def select_key_from_dict( - selections: dict[str, tuple[str, Any]], + selections: dict[str, SelectionOption], table: Table, console: Console | None = None, session: PromptSession | None = None, @@ -305,7 +323,7 @@ async def select_key_from_dict( async def select_value_from_dict( - selections: dict[str, tuple[str, Any]], + selections: dict[str, SelectionOption], table: Table, console: Console | None = None, session: PromptSession | None = None, @@ -327,12 +345,12 @@ async def select_value_from_dict( prompt_message=prompt_message, ) - return selections[selection_key][1] + return selections[selection_key].value async def get_selection_from_dict_menu( title: str, - selections: dict[str, tuple[str, Any]], + selections: dict[str, SelectionOption], console: Console | None = None, session: PromptSession | None = None, prompt_message: str = "Select an option > ", diff --git a/falyx/selection_action.py b/falyx/selection_action.py index 5cc4b74..72a5bdd 100644 --- a/falyx/selection_action.py +++ b/falyx/selection_action.py @@ -11,6 +11,7 @@ from falyx.context import ExecutionContext from falyx.execution_registry import ExecutionRegistry as er from falyx.hook_manager import HookType from falyx.selection import ( + SelectionOption, prompt_for_index, prompt_for_selection, render_selection_dict_table, @@ -24,7 +25,7 @@ class SelectionAction(BaseAction): def __init__( self, name: str, - selections: list[str] | dict[str, tuple[str, Any]], + selections: list[str] | dict[str, SelectionOption], *, title: str = "Select an option", columns: int = 2, @@ -121,7 +122,7 @@ class SelectionAction(BaseAction): ) else: key = effective_default - result = key if self.return_key else self.selections[key][1] + result = key if self.return_key else self.selections[key].value else: raise TypeError( f"'selections' must be a list[str] or dict[str, tuple[str, Any]], got {type(self.selections).__name__}" @@ -153,8 +154,8 @@ class SelectionAction(BaseAction): sub = tree.add( f"[dim]Type:[/] Dict[str, (str, Any)] ({len(self.selections)} items)" ) - for i, (key, (label, _)) in enumerate(list(self.selections.items())[:10]): - sub.add(f"[dim]{key}[/]: {label}") + for i, (key, option) in enumerate(list(self.selections.items())[:10]): + sub.add(f"[dim]{key}[/]: {option.description}") if len(self.selections) > 10: sub.add(f"[dim]... ({len(self.selections) - 10} more)[/]") else: diff --git a/falyx/tagged_table.py b/falyx/tagged_table.py index a3bd21a..26ba17f 100644 --- a/falyx/tagged_table.py +++ b/falyx/tagged_table.py @@ -22,7 +22,7 @@ def build_tagged_table(flx: Falyx) -> Table: for group_name, commands in grouped.items(): table.add_row(f"[bold underline]{group_name} Commands[/]") for cmd in commands: - table.add_row(f"[{cmd.key}] [{cmd.color}]{cmd.description}") + table.add_row(f"[{cmd.key}] [{cmd.style}]{cmd.description}") table.add_row("") # Add bottom row