Fix TLDR causing Command not to run, Add placeholder prompt menu to Falyx
This commit is contained in:
@ -77,7 +77,12 @@ def default_config(parser: CommandArgumentParser) -> None:
|
||||
)
|
||||
|
||||
|
||||
flx = Falyx("Argument Examples", program="argument_examples.py")
|
||||
flx = Falyx(
|
||||
"Argument Examples",
|
||||
program="argument_examples.py",
|
||||
hide_menu_table=True,
|
||||
show_placeholder_menu=True,
|
||||
)
|
||||
|
||||
flx.add_command(
|
||||
key="T",
|
||||
@ -88,7 +93,7 @@ flx.add_command(
|
||||
name="test_args",
|
||||
action=test_args,
|
||||
),
|
||||
style="bold blue",
|
||||
style="bold #B3EBF2",
|
||||
argument_config=default_config,
|
||||
)
|
||||
|
||||
|
@ -19,6 +19,8 @@ flx = Falyx(
|
||||
description="This example demonstrates how to select files using Falyx.",
|
||||
version="1.0.0",
|
||||
program="file_select.py",
|
||||
hide_menu_table=True,
|
||||
show_placeholder_menu=True,
|
||||
)
|
||||
|
||||
flx.add_command(
|
||||
|
@ -33,7 +33,7 @@ from prompt_toolkit import PromptSession
|
||||
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.patch_stdout import patch_stdout
|
||||
from prompt_toolkit.validation import ValidationError, Validator
|
||||
from prompt_toolkit.validation import ValidationError
|
||||
from rich import box
|
||||
from rich.console import Console
|
||||
from rich.markdown import Markdown
|
||||
@ -66,41 +66,10 @@ from falyx.retry import RetryPolicy
|
||||
from falyx.signals import BackSignal, CancelSignal, HelpSignal, QuitSignal
|
||||
from falyx.themes import OneColors
|
||||
from falyx.utils import CaseInsensitiveDict, _noop, chunks, ensure_async
|
||||
from falyx.validators import CommandValidator
|
||||
from falyx.version import __version__
|
||||
|
||||
|
||||
class CommandValidator(Validator):
|
||||
"""Validator to check if the input is a valid command."""
|
||||
|
||||
def __init__(self, falyx: Falyx, error_message: str) -> None:
|
||||
super().__init__()
|
||||
self.falyx = falyx
|
||||
self.error_message = error_message
|
||||
|
||||
def validate(self, document) -> None:
|
||||
if not document.text:
|
||||
raise ValidationError(
|
||||
message=self.error_message,
|
||||
cursor_position=len(document.text),
|
||||
)
|
||||
|
||||
async def validate_async(self, document) -> None:
|
||||
text = document.text
|
||||
if not text:
|
||||
raise ValidationError(
|
||||
message=self.error_message,
|
||||
cursor_position=len(text),
|
||||
)
|
||||
is_preview, choice, _, __ = await self.falyx.get_command(text, from_validate=True)
|
||||
if is_preview:
|
||||
return None
|
||||
if not choice:
|
||||
raise ValidationError(
|
||||
message=self.error_message,
|
||||
cursor_position=len(text),
|
||||
)
|
||||
|
||||
|
||||
class Falyx:
|
||||
"""
|
||||
Main menu controller for Falyx CLI applications.
|
||||
@ -176,6 +145,7 @@ class Falyx:
|
||||
render_menu: Callable[[Falyx], None] | None = None,
|
||||
custom_table: Callable[[Falyx], Table] | Table | None = None,
|
||||
hide_menu_table: bool = False,
|
||||
show_placeholder_menu: bool = False,
|
||||
) -> None:
|
||||
"""Initializes the Falyx object."""
|
||||
self.title: str | Markdown = title
|
||||
@ -208,6 +178,7 @@ class Falyx:
|
||||
self.render_menu: Callable[[Falyx], None] | None = render_menu
|
||||
self.custom_table: Callable[[Falyx], Table] | Table | None = custom_table
|
||||
self._hide_menu_table: bool = hide_menu_table
|
||||
self.show_placeholder_menu: bool = show_placeholder_menu
|
||||
self.validate_options(cli_args, options)
|
||||
self._prompt_session: PromptSession | None = None
|
||||
self.options.set("mode", FalyxMode.MENU)
|
||||
@ -492,6 +463,7 @@ class Falyx:
|
||||
def prompt_session(self) -> PromptSession:
|
||||
"""Returns the prompt session for the menu."""
|
||||
if self._prompt_session is None:
|
||||
placeholder = self.build_placeholder_menu()
|
||||
self._prompt_session = PromptSession(
|
||||
message=self.prompt,
|
||||
multiline=False,
|
||||
@ -502,6 +474,7 @@ class Falyx:
|
||||
validate_while_typing=True,
|
||||
interrupt_exception=QuitSignal,
|
||||
eof_exception=QuitSignal,
|
||||
placeholder=placeholder if self.show_placeholder_menu else None,
|
||||
)
|
||||
return self._prompt_session
|
||||
|
||||
@ -724,16 +697,16 @@ class Falyx:
|
||||
if self.help_command:
|
||||
bottom_row.append(
|
||||
f"[{self.help_command.key}] [{self.help_command.style}]"
|
||||
f"{self.help_command.description}"
|
||||
f"{self.help_command.description}[/]"
|
||||
)
|
||||
if self.history_command:
|
||||
bottom_row.append(
|
||||
f"[{self.history_command.key}] [{self.history_command.style}]"
|
||||
f"{self.history_command.description}"
|
||||
f"{self.history_command.description}[/]"
|
||||
)
|
||||
bottom_row.append(
|
||||
f"[{self.exit_command.key}] [{self.exit_command.style}]"
|
||||
f"{self.exit_command.description}"
|
||||
f"{self.exit_command.description}[/]"
|
||||
)
|
||||
return bottom_row
|
||||
|
||||
@ -754,6 +727,22 @@ class Falyx:
|
||||
table.add_row(*row)
|
||||
return table
|
||||
|
||||
def build_placeholder_menu(self) -> StyleAndTextTuples:
|
||||
"""
|
||||
Builds a menu placeholder for prompt_menu mode.
|
||||
"""
|
||||
visible_commands = [item for item in self.commands.items() if not item[1].hidden]
|
||||
if not visible_commands:
|
||||
return [("", "")]
|
||||
|
||||
placeholder: list[str] = []
|
||||
for key, command in visible_commands:
|
||||
placeholder.append(f"[{key}] [{command.style}]{command.description}[/]")
|
||||
for command_str in self.get_bottom_row():
|
||||
placeholder.append(command_str)
|
||||
|
||||
return rich_text_to_prompt_text(" ".join(placeholder))
|
||||
|
||||
@property
|
||||
def table(self) -> Table:
|
||||
"""Creates or returns a custom table to display the menu commands."""
|
||||
|
@ -101,6 +101,8 @@ class CommandArgumentParser:
|
||||
- Render Help using Rich library.
|
||||
"""
|
||||
|
||||
RESERVED_DESTS = frozenset(("help", "tldr"))
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
command_key: str = "",
|
||||
@ -138,13 +140,13 @@ class CommandArgumentParser:
|
||||
|
||||
def _add_help(self):
|
||||
"""Add help argument to the parser."""
|
||||
self.add_argument(
|
||||
"-h",
|
||||
"--help",
|
||||
help = Argument(
|
||||
flags=("--help", "-h"),
|
||||
action=ArgumentAction.HELP,
|
||||
help="Show this help message.",
|
||||
dest="help",
|
||||
)
|
||||
self._register_argument(help)
|
||||
|
||||
def add_tldr_examples(self, examples: list[tuple[str, str]]) -> None:
|
||||
"""
|
||||
@ -163,12 +165,13 @@ class CommandArgumentParser:
|
||||
self._tldr_examples.append(TLDRExample(usage=usage, description=description))
|
||||
|
||||
if "tldr" not in self._dest_set:
|
||||
self.add_argument(
|
||||
"--tldr",
|
||||
tldr = Argument(
|
||||
("--tldr",),
|
||||
action=ArgumentAction.TLDR,
|
||||
help="Show quick usage examples and exit.",
|
||||
dest="tldr",
|
||||
)
|
||||
self._register_argument(tldr)
|
||||
|
||||
def _is_positional(self, flags: tuple[str, ...]) -> bool:
|
||||
"""Check if the flags are positional."""
|
||||
@ -529,6 +532,10 @@ class CommandArgumentParser:
|
||||
"Merging multiple arguments into the same dest (e.g. positional + flagged) "
|
||||
"is not supported. Define a unique 'dest' for each argument."
|
||||
)
|
||||
if dest in self.RESERVED_DESTS:
|
||||
raise CommandArgumentError(
|
||||
f"Destination '{dest}' is reserved and cannot be used."
|
||||
)
|
||||
action = self._validate_action(action, positional)
|
||||
resolver = self._validate_resolver(action, resolver)
|
||||
|
||||
@ -1050,6 +1057,7 @@ class CommandArgumentParser:
|
||||
)
|
||||
|
||||
result.pop("help", None)
|
||||
result.pop("tldr", None)
|
||||
return result
|
||||
|
||||
async def parse_args_split(
|
||||
@ -1067,7 +1075,7 @@ class CommandArgumentParser:
|
||||
args_list = []
|
||||
kwargs_dict = {}
|
||||
for arg in self._arguments:
|
||||
if arg.dest == "help":
|
||||
if arg.dest in ("help", "tldr"):
|
||||
continue
|
||||
if arg.positional:
|
||||
args_list.append(parsed[arg.dest])
|
||||
|
@ -7,6 +7,7 @@ user input during prompts—especially for selection actions, confirmations, and
|
||||
argument parsing.
|
||||
|
||||
Included Validators:
|
||||
- CommandValidator: Validates if the input matches a known command.
|
||||
- int_range_validator: Enforces numeric input within a range.
|
||||
- key_validator: Ensures the entered value matches a valid selection key.
|
||||
- yes_no_validator: Restricts input to 'Y' or 'N'.
|
||||
@ -17,10 +18,45 @@ Included Validators:
|
||||
These validators integrate directly into `PromptSession.prompt_async()` to
|
||||
enforce correctness and provide helpful error messages.
|
||||
"""
|
||||
from typing import KeysView, Sequence
|
||||
from typing import TYPE_CHECKING, KeysView, Sequence
|
||||
|
||||
from prompt_toolkit.validation import ValidationError, Validator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from falyx.falyx import Falyx
|
||||
|
||||
|
||||
class CommandValidator(Validator):
|
||||
"""Validator to check if the input is a valid command."""
|
||||
|
||||
def __init__(self, falyx: "Falyx", error_message: str) -> None:
|
||||
super().__init__()
|
||||
self.falyx = falyx
|
||||
self.error_message = error_message
|
||||
|
||||
def validate(self, document) -> None:
|
||||
if not document.text:
|
||||
raise ValidationError(
|
||||
message=self.error_message,
|
||||
cursor_position=len(document.text),
|
||||
)
|
||||
|
||||
async def validate_async(self, document) -> None:
|
||||
text = document.text
|
||||
if not text:
|
||||
raise ValidationError(
|
||||
message=self.error_message,
|
||||
cursor_position=len(text),
|
||||
)
|
||||
is_preview, choice, _, __ = await self.falyx.get_command(text, from_validate=True)
|
||||
if is_preview:
|
||||
return None
|
||||
if not choice:
|
||||
raise ValidationError(
|
||||
message=self.error_message,
|
||||
cursor_position=len(text),
|
||||
)
|
||||
|
||||
|
||||
def int_range_validator(minimum: int, maximum: int) -> Validator:
|
||||
"""Validator for integer ranges."""
|
||||
|
@ -1 +1 @@
|
||||
__version__ = "0.1.70"
|
||||
__version__ = "0.1.71"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "falyx"
|
||||
version = "0.1.70"
|
||||
version = "0.1.71"
|
||||
description = "Reliable and introspectable async CLI action framework."
|
||||
authors = ["Roland Thomas Jr <roland@rtj.dev>"]
|
||||
license = "MIT"
|
||||
|
Reference in New Issue
Block a user