Change color -> style, Add SelectionOption

This commit is contained in:
Roland Thomas Jr 2025-05-04 19:35:44 -04:00
parent 91c4d5481f
commit f9cb9ebaef
Signed by: roland
GPG Key ID: 7C3C2B085A4C2872
9 changed files with 64 additions and 37 deletions

View File

@ -12,6 +12,7 @@ from .command import Command
from .context import ExecutionContext, SharedContext from .context import ExecutionContext, SharedContext
from .execution_registry import ExecutionRegistry from .execution_registry import ExecutionRegistry
from .falyx import Falyx from .falyx import Falyx
from .hook_manager import HookType
logger = logging.getLogger("falyx") logger = logging.getLogger("falyx")
@ -27,4 +28,5 @@ __all__ = [
"ExecutionContext", "ExecutionContext",
"SharedContext", "SharedContext",
"ExecutionRegistry", "ExecutionRegistry",
"HookType",
] ]

View File

@ -68,7 +68,7 @@ class Command(BaseModel):
args (tuple): Static positional arguments. args (tuple): Static positional arguments.
kwargs (dict): Static keyword arguments. kwargs (dict): Static keyword arguments.
help_text (str): Additional help or guidance text. 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 (bool): Whether to require confirmation before executing.
confirm_message (str): Custom confirmation prompt. confirm_message (str): Custom confirmation prompt.
preview_before_confirm (bool): Whether to preview before confirming. preview_before_confirm (bool): Whether to preview before confirming.
@ -101,7 +101,7 @@ class Command(BaseModel):
hidden: bool = False hidden: bool = False
aliases: list[str] = Field(default_factory=list) aliases: list[str] = Field(default_factory=list)
help_text: str = "" help_text: str = ""
color: str = OneColors.WHITE style: str = OneColors.WHITE
confirm: bool = False confirm: bool = False
confirm_message: str = "Are you sure?" confirm_message: str = "Are you sure?"
preview_before_confirm: bool = True preview_before_confirm: bool = True

View File

@ -93,7 +93,7 @@ def loader(file_path: Path | str) -> list[dict[str, Any]]:
"hidden": entry.get("hidden", False), "hidden": entry.get("hidden", False),
"aliases": entry.get("aliases", []), "aliases": entry.get("aliases", []),
"help_text": entry.get("help_text", ""), "help_text": entry.get("help_text", ""),
"color": entry.get("color", "white"), "style": entry.get("style", "white"),
"confirm": entry.get("confirm", False), "confirm": entry.get("confirm", False),
"confirm_message": entry.get("confirm_message", "Are you sure?"), "confirm_message": entry.get("confirm_message", "Are you sure?"),
"preview_before_confirm": entry.get("preview_before_confirm", True), "preview_before_confirm": entry.get("preview_before_confirm", True),

View File

@ -231,7 +231,7 @@ class Falyx:
key="Q", key="Q",
description="Exit", description="Exit",
aliases=["EXIT", "QUIT"], aliases=["EXIT", "QUIT"],
color=OneColors.DARK_RED, style=OneColors.DARK_RED,
) )
def _get_history_command(self) -> Command: def _get_history_command(self) -> Command:
@ -241,7 +241,7 @@ class Falyx:
description="History", description="History",
aliases=["HISTORY"], aliases=["HISTORY"],
action=er.get_history_action(), action=er.get_history_action(),
color=OneColors.DARK_YELLOW, style=OneColors.DARK_YELLOW,
) )
async def _show_help(self): async def _show_help(self):
@ -256,28 +256,28 @@ class Falyx:
if command.requires_input: if command.requires_input:
help_text += " [dim](requires input)[/dim]" help_text += " [dim](requires input)[/dim]"
table.add_row( table.add_row(
f"[{command.color}]{command.key}[/]", f"[{command.style}]{command.key}[/]",
", ".join(command.aliases) if command.aliases else "None", ", ".join(command.aliases) if command.aliases else "None",
help_text, help_text,
", ".join(command.tags) if command.tags else "None", ", ".join(command.tags) if command.tags else "None",
) )
table.add_row( 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), ", ".join(self.exit_command.aliases),
"Exit this menu or program", "Exit this menu or program",
) )
if self.history_command: if self.history_command:
table.add_row( 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), ", ".join(self.history_command.aliases),
"History of executed actions", "History of executed actions",
) )
if self.help_command: if self.help_command:
table.add_row( 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), ", ".join(self.help_command.aliases),
"Show this help menu", "Show this help menu",
) )
@ -291,7 +291,7 @@ class Falyx:
aliases=["HELP"], aliases=["HELP"],
description="Help", description="Help",
action=self._show_help, action=self._show_help,
color=OneColors.LIGHT_YELLOW, style=OneColors.LIGHT_YELLOW,
) )
def _get_completer(self) -> WordCompleter: def _get_completer(self) -> WordCompleter:
@ -526,7 +526,7 @@ class Falyx:
description: str = "Exit", description: str = "Exit",
aliases: list[str] | None = None, aliases: list[str] | None = None,
action: Callable[[], Any] = lambda: None, action: Callable[[], Any] = lambda: None,
color: str = OneColors.DARK_RED, style: str = OneColors.DARK_RED,
confirm: bool = False, confirm: bool = False,
confirm_message: str = "Are you sure?", confirm_message: str = "Are you sure?",
) -> None: ) -> None:
@ -539,19 +539,19 @@ class Falyx:
description=description, description=description,
aliases=aliases if aliases else self.exit_command.aliases, aliases=aliases if aliases else self.exit_command.aliases,
action=action, action=action,
color=color, style=style,
confirm=confirm, confirm=confirm,
confirm_message=confirm_message, confirm_message=confirm_message,
) )
def add_submenu( 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: ) -> None:
"""Adds a submenu to the menu.""" """Adds a submenu to the menu."""
if not isinstance(submenu, Falyx): if not isinstance(submenu, Falyx):
raise NotAFalyxError("submenu must be an instance of Falyx.") raise NotAFalyxError("submenu must be an instance of Falyx.")
self._validate_command_key(key) 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"]) submenu.update_exit_command(key="B", description="Back", aliases=["BACK"])
def add_commands(self, commands: list[dict]) -> None: def add_commands(self, commands: list[dict]) -> None:
@ -569,7 +569,7 @@ class Falyx:
hidden: bool = False, hidden: bool = False,
aliases: list[str] | None = None, aliases: list[str] | None = None,
help_text: str = "", help_text: str = "",
color: str = OneColors.WHITE, style: str = OneColors.WHITE,
confirm: bool = False, confirm: bool = False,
confirm_message: str = "Are you sure?", confirm_message: str = "Are you sure?",
preview_before_confirm: bool = True, preview_before_confirm: bool = True,
@ -602,7 +602,7 @@ class Falyx:
hidden=hidden, hidden=hidden,
aliases=aliases if aliases else [], aliases=aliases if aliases else [],
help_text=help_text, help_text=help_text,
color=color, style=style,
confirm=confirm, confirm=confirm,
confirm_message=confirm_message, confirm_message=confirm_message,
preview_before_confirm=preview_before_confirm, preview_before_confirm=preview_before_confirm,
@ -644,14 +644,14 @@ class Falyx:
bottom_row = [] bottom_row = []
if self.history_command: if self.history_command:
bottom_row.append( 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: if self.help_command:
bottom_row.append( 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( 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 return bottom_row
@ -662,7 +662,7 @@ class Falyx:
for chunk in chunks(visible_commands, self.columns): for chunk in chunks(visible_commands, self.columns):
row = [] row = []
for key, command in chunk: 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) table.add_row(*row)
bottom_row = self.get_bottom_row() bottom_row = self.get_bottom_row()
for row in chunks(bottom_row, self.columns): for row in chunks(bottom_row, self.columns):

View File

@ -1,7 +1,7 @@
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
"""hooks.py""" """hooks.py"""
import time import time
from typing import Callable from typing import Any, Callable
from falyx.context import ExecutionContext from falyx.context import ExecutionContext
from falyx.exceptions import CircuitBreakerOpen from falyx.exceptions import CircuitBreakerOpen
@ -10,11 +10,17 @@ from falyx.utils import logger
class ResultReporter: 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). 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 @property
def __name__(self): def __name__(self):

View File

@ -23,7 +23,7 @@ from falyx.utils import CaseInsensitiveDict, chunks, logger
class MenuOption: class MenuOption:
description: str description: str
action: BaseAction action: BaseAction
color: str = OneColors.WHITE style: str = OneColors.WHITE
def __post_init__(self): def __post_init__(self):
if not isinstance(self.description, str): if not isinstance(self.description, str):
@ -33,7 +33,7 @@ class MenuOption:
def render(self, key: str) -> str: def render(self, key: str) -> str:
"""Render the menu option for display.""" """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): class MenuOptionMap(CaseInsensitiveDict):

View File

@ -1,5 +1,6 @@
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
"""selection.py""" """selection.py"""
from dataclasses import dataclass
from typing import Any, Callable, KeysView, Sequence from typing import Any, Callable, KeysView, Sequence
from prompt_toolkit import PromptSession from prompt_toolkit import PromptSession
@ -13,6 +14,21 @@ from falyx.utils import chunks
from falyx.validators import int_range_validator, key_validator 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( def render_table_base(
title: str, title: str,
caption: str = "", caption: str = "",
@ -139,7 +155,7 @@ def render_selection_indexed_table(
def render_selection_dict_table( def render_selection_dict_table(
title: str, title: str,
selections: dict[str, tuple[str, Any]], selections: dict[str, SelectionOption],
columns: int = 2, columns: int = 2,
caption: str = "", caption: str = "",
box_style: box.Box = box.SIMPLE, box_style: box.Box = box.SIMPLE,
@ -172,8 +188,10 @@ def render_selection_dict_table(
for chunk in chunks(selections.items(), columns): for chunk in chunks(selections.items(), columns):
row = [] row = []
for key, value in chunk: for key, option in chunk:
row.append(f"[{OneColors.WHITE}][{key.upper()}] {value[0]}") row.append(
f"[{OneColors.WHITE}][{key.upper()}] [{option.style}]{option.description}[/]"
)
table.add_row(*row) table.add_row(*row)
return table return table
@ -281,7 +299,7 @@ async def select_value_from_list(
async def select_key_from_dict( async def select_key_from_dict(
selections: dict[str, tuple[str, Any]], selections: dict[str, SelectionOption],
table: Table, table: Table,
console: Console | None = None, console: Console | None = None,
session: PromptSession | None = None, session: PromptSession | None = None,
@ -305,7 +323,7 @@ async def select_key_from_dict(
async def select_value_from_dict( async def select_value_from_dict(
selections: dict[str, tuple[str, Any]], selections: dict[str, SelectionOption],
table: Table, table: Table,
console: Console | None = None, console: Console | None = None,
session: PromptSession | None = None, session: PromptSession | None = None,
@ -327,12 +345,12 @@ async def select_value_from_dict(
prompt_message=prompt_message, prompt_message=prompt_message,
) )
return selections[selection_key][1] return selections[selection_key].value
async def get_selection_from_dict_menu( async def get_selection_from_dict_menu(
title: str, title: str,
selections: dict[str, tuple[str, Any]], selections: dict[str, SelectionOption],
console: Console | None = None, console: Console | None = None,
session: PromptSession | None = None, session: PromptSession | None = None,
prompt_message: str = "Select an option > ", prompt_message: str = "Select an option > ",

View File

@ -11,6 +11,7 @@ from falyx.context import ExecutionContext
from falyx.execution_registry import ExecutionRegistry as er from falyx.execution_registry import ExecutionRegistry as er
from falyx.hook_manager import HookType from falyx.hook_manager import HookType
from falyx.selection import ( from falyx.selection import (
SelectionOption,
prompt_for_index, prompt_for_index,
prompt_for_selection, prompt_for_selection,
render_selection_dict_table, render_selection_dict_table,
@ -24,7 +25,7 @@ class SelectionAction(BaseAction):
def __init__( def __init__(
self, self,
name: str, name: str,
selections: list[str] | dict[str, tuple[str, Any]], selections: list[str] | dict[str, SelectionOption],
*, *,
title: str = "Select an option", title: str = "Select an option",
columns: int = 2, columns: int = 2,
@ -121,7 +122,7 @@ class SelectionAction(BaseAction):
) )
else: else:
key = effective_default 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: else:
raise TypeError( raise TypeError(
f"'selections' must be a list[str] or dict[str, tuple[str, Any]], got {type(self.selections).__name__}" 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( sub = tree.add(
f"[dim]Type:[/] Dict[str, (str, Any)] ({len(self.selections)} items)" f"[dim]Type:[/] Dict[str, (str, Any)] ({len(self.selections)} items)"
) )
for i, (key, (label, _)) in enumerate(list(self.selections.items())[:10]): for i, (key, option) in enumerate(list(self.selections.items())[:10]):
sub.add(f"[dim]{key}[/]: {label}") sub.add(f"[dim]{key}[/]: {option.description}")
if len(self.selections) > 10: if len(self.selections) > 10:
sub.add(f"[dim]... ({len(self.selections) - 10} more)[/]") sub.add(f"[dim]... ({len(self.selections) - 10} more)[/]")
else: else:

View File

@ -22,7 +22,7 @@ def build_tagged_table(flx: Falyx) -> Table:
for group_name, commands in grouped.items(): for group_name, commands in grouped.items():
table.add_row(f"[bold underline]{group_name} Commands[/]") table.add_row(f"[bold underline]{group_name} Commands[/]")
for cmd in 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("") table.add_row("")
# Add bottom row # Add bottom row