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 .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",
]

View File

@ -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

View File

@ -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),

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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 > ",

View File

@ -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:

View File

@ -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