Change color -> style, Add SelectionOption
This commit is contained in:
parent
91c4d5481f
commit
f9cb9ebaef
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 > ",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue