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 .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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 > ",
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue