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