Compare commits
	
		
			2 Commits
		
	
	
		
			53ba6a896a
			...
			2d1177e820
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2d1177e820 | |||
| 3c7ef3eb1c | 
| @@ -12,7 +12,7 @@ from .base import BaseAction | |||||||
| from .chained_action import ChainedAction | from .chained_action import ChainedAction | ||||||
| from .fallback_action import FallbackAction | from .fallback_action import FallbackAction | ||||||
| from .http_action import HTTPAction | from .http_action import HTTPAction | ||||||
| from .io_action import BaseIOAction, ShellAction | from .io_action import BaseIOAction | ||||||
| from .literal_input_action import LiteralInputAction | from .literal_input_action import LiteralInputAction | ||||||
| from .menu_action import MenuAction | from .menu_action import MenuAction | ||||||
| from .process_action import ProcessAction | from .process_action import ProcessAction | ||||||
| @@ -20,6 +20,7 @@ from .process_pool_action import ProcessPoolAction | |||||||
| from .prompt_menu_action import PromptMenuAction | from .prompt_menu_action import PromptMenuAction | ||||||
| from .select_file_action import SelectFileAction | from .select_file_action import SelectFileAction | ||||||
| from .selection_action import SelectionAction | from .selection_action import SelectionAction | ||||||
|  | from .shell_action import ShellAction | ||||||
| from .signal_action import SignalAction | from .signal_action import SignalAction | ||||||
| from .user_input_action import UserInputAction | from .user_input_action import UserInputAction | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """action_group.py""" | ||||||
| import asyncio | import asyncio | ||||||
| import random | import random | ||||||
| from typing import Any, Callable | from typing import Any, Callable | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """chained_action.py""" | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| from typing import Any, Callable | from typing import Any, Callable | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """fallback_action.py""" | ||||||
| from functools import cached_property | from functools import cached_property | ||||||
| from typing import Any | from typing import Any | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,8 +16,6 @@ Common usage includes shell-like filters, input transformers, or any tool that | |||||||
| needs to consume input from another process or pipeline. | needs to consume input from another process or pipeline. | ||||||
| """ | """ | ||||||
| import asyncio | import asyncio | ||||||
| import shlex |  | ||||||
| import subprocess |  | ||||||
| import sys | import sys | ||||||
| from typing import Any, Callable | from typing import Any, Callable | ||||||
|  |  | ||||||
| @@ -25,10 +23,8 @@ from rich.tree import Tree | |||||||
|  |  | ||||||
| from falyx.action.base import BaseAction | from falyx.action.base import BaseAction | ||||||
| from falyx.context import ExecutionContext | from falyx.context import ExecutionContext | ||||||
| from falyx.exceptions import FalyxError |  | ||||||
| from falyx.execution_registry import ExecutionRegistry as er | from falyx.execution_registry import ExecutionRegistry as er | ||||||
| from falyx.hook_manager import HookManager, HookType | from falyx.hook_manager import HookManager, HookType | ||||||
| from falyx.logger import logger |  | ||||||
| from falyx.themes import OneColors | from falyx.themes import OneColors | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -171,92 +167,3 @@ class BaseIOAction(BaseAction): | |||||||
|             parent.add("".join(label)) |             parent.add("".join(label)) | ||||||
|         else: |         else: | ||||||
|             self.console.print(Tree("".join(label))) |             self.console.print(Tree("".join(label))) | ||||||
|  |  | ||||||
|  |  | ||||||
| class ShellAction(BaseIOAction): |  | ||||||
|     """ |  | ||||||
|     ShellAction wraps a shell command template for CLI pipelines. |  | ||||||
|  |  | ||||||
|     This Action takes parsed input (from stdin, literal, or last_result), |  | ||||||
|     substitutes it into the provided shell command template, and executes |  | ||||||
|     the command asynchronously using subprocess. |  | ||||||
|  |  | ||||||
|     Designed for quick integration with shell tools like `grep`, `ping`, `jq`, etc. |  | ||||||
|  |  | ||||||
|     ⚠️ Security Warning: |  | ||||||
|     By default, ShellAction uses `shell=True`, which can be dangerous with |  | ||||||
|     unsanitized input. To mitigate this, set `safe_mode=True` to use `shell=False` |  | ||||||
|     with `shlex.split()`. |  | ||||||
|  |  | ||||||
|     Features: |  | ||||||
|     - Automatically handles input parsing (str/bytes) |  | ||||||
|     - `safe_mode=True` disables shell interpretation and runs with `shell=False` |  | ||||||
|     - Captures stdout and stderr from shell execution |  | ||||||
|     - Raises on non-zero exit codes with stderr as the error |  | ||||||
|     - Result is returned as trimmed stdout string |  | ||||||
|  |  | ||||||
|     Args: |  | ||||||
|         name (str): Name of the action. |  | ||||||
|         command_template (str): Shell command to execute. Must include `{}` to include |  | ||||||
|                                 input. If no placeholder is present, the input is not |  | ||||||
|                                 included. |  | ||||||
|         safe_mode (bool): If True, runs with `shell=False` using shlex parsing |  | ||||||
|                           (default: False). |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__( |  | ||||||
|         self, name: str, command_template: str, safe_mode: bool = False, **kwargs |  | ||||||
|     ): |  | ||||||
|         super().__init__(name=name, **kwargs) |  | ||||||
|         self.command_template = command_template |  | ||||||
|         self.safe_mode = safe_mode |  | ||||||
|  |  | ||||||
|     def from_input(self, raw: str | bytes) -> str: |  | ||||||
|         if not isinstance(raw, (str, bytes)): |  | ||||||
|             raise TypeError( |  | ||||||
|                 f"{self.name} expected str or bytes input, got {type(raw).__name__}" |  | ||||||
|             ) |  | ||||||
|         return raw.strip() if isinstance(raw, str) else raw.decode("utf-8").strip() |  | ||||||
|  |  | ||||||
|     def get_infer_target(self) -> tuple[Callable[..., Any] | None, dict[str, Any] | None]: |  | ||||||
|         if sys.stdin.isatty(): |  | ||||||
|             return self._run, {"parsed_input": {"help": self.command_template}} |  | ||||||
|         return None, None |  | ||||||
|  |  | ||||||
|     async def _run(self, parsed_input: str) -> str: |  | ||||||
|         # Replace placeholder in template, or use raw input as full command |  | ||||||
|         command = self.command_template.format(parsed_input) |  | ||||||
|         if self.safe_mode: |  | ||||||
|             try: |  | ||||||
|                 args = shlex.split(command) |  | ||||||
|             except ValueError as error: |  | ||||||
|                 raise FalyxError(f"Invalid command template: {error}") |  | ||||||
|             result = subprocess.run(args, capture_output=True, text=True, check=True) |  | ||||||
|         else: |  | ||||||
|             result = subprocess.run( |  | ||||||
|                 command, shell=True, text=True, capture_output=True, check=True |  | ||||||
|             ) |  | ||||||
|         if result.returncode != 0: |  | ||||||
|             raise RuntimeError(result.stderr.strip()) |  | ||||||
|         return result.stdout.strip() |  | ||||||
|  |  | ||||||
|     def to_output(self, result: str) -> str: |  | ||||||
|         return result |  | ||||||
|  |  | ||||||
|     async def preview(self, parent: Tree | None = None): |  | ||||||
|         label = [f"[{OneColors.GREEN_b}]⚙ ShellAction[/] '{self.name}'"] |  | ||||||
|         label.append(f"\n[dim]Template:[/] {self.command_template}") |  | ||||||
|         label.append( |  | ||||||
|             f"\n[dim]Safe mode:[/] {'Enabled' if self.safe_mode else 'Disabled'}" |  | ||||||
|         ) |  | ||||||
|         if self.inject_last_result: |  | ||||||
|             label.append(f" [dim](injects '{self.inject_into}')[/dim]") |  | ||||||
|         tree = parent.add("".join(label)) if parent else Tree("".join(label)) |  | ||||||
|         if not parent: |  | ||||||
|             self.console.print(tree) |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return ( |  | ||||||
|             f"ShellAction(name={self.name!r}, command_template={self.command_template!r}," |  | ||||||
|             f" safe_mode={self.safe_mode})" |  | ||||||
|         ) |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """literal_input_action.py""" | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| from functools import cached_property | from functools import cached_property | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """mixins.py""" | ||||||
| from falyx.action.base import BaseAction | from falyx.action.base import BaseAction | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """process_action.py""" | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| import asyncio | import asyncio | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """process_pool_action.py""" | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| import asyncio | import asyncio | ||||||
|   | |||||||
							
								
								
									
										105
									
								
								falyx/action/shell_action.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								falyx/action/shell_action.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """shell_action.py | ||||||
|  | Execute shell commands with input substitution.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import shlex | ||||||
|  | import subprocess | ||||||
|  | import sys | ||||||
|  | from typing import Any, Callable | ||||||
|  |  | ||||||
|  | from rich.tree import Tree | ||||||
|  |  | ||||||
|  | from falyx.action.io_action import BaseIOAction | ||||||
|  | from falyx.exceptions import FalyxError | ||||||
|  | from falyx.themes import OneColors | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ShellAction(BaseIOAction): | ||||||
|  |     """ | ||||||
|  |     ShellAction wraps a shell command template for CLI pipelines. | ||||||
|  |  | ||||||
|  |     This Action takes parsed input (from stdin, literal, or last_result), | ||||||
|  |     substitutes it into the provided shell command template, and executes | ||||||
|  |     the command asynchronously using subprocess. | ||||||
|  |  | ||||||
|  |     Designed for quick integration with shell tools like `grep`, `ping`, `jq`, etc. | ||||||
|  |  | ||||||
|  |     ⚠️ Security Warning: | ||||||
|  |     By default, ShellAction uses `shell=True`, which can be dangerous with | ||||||
|  |     unsanitized input. To mitigate this, set `safe_mode=True` to use `shell=False` | ||||||
|  |     with `shlex.split()`. | ||||||
|  |  | ||||||
|  |     Features: | ||||||
|  |     - Automatically handles input parsing (str/bytes) | ||||||
|  |     - `safe_mode=True` disables shell interpretation and runs with `shell=False` | ||||||
|  |     - Captures stdout and stderr from shell execution | ||||||
|  |     - Raises on non-zero exit codes with stderr as the error | ||||||
|  |     - Result is returned as trimmed stdout string | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         name (str): Name of the action. | ||||||
|  |         command_template (str): Shell command to execute. Must include `{}` to include | ||||||
|  |                                 input. If no placeholder is present, the input is not | ||||||
|  |                                 included. | ||||||
|  |         safe_mode (bool): If True, runs with `shell=False` using shlex parsing | ||||||
|  |                           (default: False). | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__( | ||||||
|  |         self, name: str, command_template: str, safe_mode: bool = False, **kwargs | ||||||
|  |     ): | ||||||
|  |         super().__init__(name=name, **kwargs) | ||||||
|  |         self.command_template = command_template | ||||||
|  |         self.safe_mode = safe_mode | ||||||
|  |  | ||||||
|  |     def from_input(self, raw: str | bytes) -> str: | ||||||
|  |         if not isinstance(raw, (str, bytes)): | ||||||
|  |             raise TypeError( | ||||||
|  |                 f"{self.name} expected str or bytes input, got {type(raw).__name__}" | ||||||
|  |             ) | ||||||
|  |         return raw.strip() if isinstance(raw, str) else raw.decode("utf-8").strip() | ||||||
|  |  | ||||||
|  |     def get_infer_target(self) -> tuple[Callable[..., Any] | None, dict[str, Any] | None]: | ||||||
|  |         if sys.stdin.isatty(): | ||||||
|  |             return self._run, {"parsed_input": {"help": self.command_template}} | ||||||
|  |         return None, None | ||||||
|  |  | ||||||
|  |     async def _run(self, parsed_input: str) -> str: | ||||||
|  |         # Replace placeholder in template, or use raw input as full command | ||||||
|  |         command = self.command_template.format(parsed_input) | ||||||
|  |         if self.safe_mode: | ||||||
|  |             try: | ||||||
|  |                 args = shlex.split(command) | ||||||
|  |             except ValueError as error: | ||||||
|  |                 raise FalyxError(f"Invalid command template: {error}") | ||||||
|  |             result = subprocess.run(args, capture_output=True, text=True, check=True) | ||||||
|  |         else: | ||||||
|  |             result = subprocess.run( | ||||||
|  |                 command, shell=True, text=True, capture_output=True, check=True | ||||||
|  |             ) | ||||||
|  |         if result.returncode != 0: | ||||||
|  |             raise RuntimeError(result.stderr.strip()) | ||||||
|  |         return result.stdout.strip() | ||||||
|  |  | ||||||
|  |     def to_output(self, result: str) -> str: | ||||||
|  |         return result | ||||||
|  |  | ||||||
|  |     async def preview(self, parent: Tree | None = None): | ||||||
|  |         label = [f"[{OneColors.GREEN_b}]⚙ ShellAction[/] '{self.name}'"] | ||||||
|  |         label.append(f"\n[dim]Template:[/] {self.command_template}") | ||||||
|  |         label.append( | ||||||
|  |             f"\n[dim]Safe mode:[/] {'Enabled' if self.safe_mode else 'Disabled'}" | ||||||
|  |         ) | ||||||
|  |         if self.inject_last_result: | ||||||
|  |             label.append(f" [dim](injects '{self.inject_into}')[/dim]") | ||||||
|  |         tree = parent.add("".join(label)) if parent else Tree("".join(label)) | ||||||
|  |         if not parent: | ||||||
|  |             self.console.print(tree) | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return ( | ||||||
|  |             f"ShellAction(name={self.name!r}, command_template={self.command_template!r}," | ||||||
|  |             f" safe_mode={self.safe_mode})" | ||||||
|  |         ) | ||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """types.py""" | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| from enum import Enum | from enum import Enum | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """user_input_action.py""" | ||||||
| from prompt_toolkit import PromptSession | from prompt_toolkit import PromptSession | ||||||
| from prompt_toolkit.validation import Validator | from prompt_toolkit.validation import Validator | ||||||
| from rich.console import Console | from rich.console import Console | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ from falyx.execution_registry import ExecutionRegistry as er | |||||||
| from falyx.hook_manager import HookManager, HookType | from falyx.hook_manager import HookManager, HookType | ||||||
| from falyx.logger import logger | from falyx.logger import logger | ||||||
| from falyx.options_manager import OptionsManager | from falyx.options_manager import OptionsManager | ||||||
| from falyx.parser.argparse import CommandArgumentParser | from falyx.parser.command_argument_parser import CommandArgumentParser | ||||||
| from falyx.parser.signature import infer_args_from_func | from falyx.parser.signature import infer_args_from_func | ||||||
| from falyx.prompt_utils import confirm_async, should_prompt_user | from falyx.prompt_utils import confirm_async, should_prompt_user | ||||||
| from falyx.protocols import ArgParserProtocol | from falyx.protocols import ArgParserProtocol | ||||||
|   | |||||||
| @@ -5,7 +5,9 @@ Copyright (c) 2025 rtj.dev LLC. | |||||||
| Licensed under the MIT License. See LICENSE file for details. | Licensed under the MIT License. See LICENSE file for details. | ||||||
| """ | """ | ||||||
|  |  | ||||||
| from .argparse import Argument, ArgumentAction, CommandArgumentParser | from .argument import Argument | ||||||
|  | from .argument_action import ArgumentAction | ||||||
|  | from .command_argument_parser import CommandArgumentParser | ||||||
| from .parsers import FalyxParsers, get_arg_parsers, get_root_parser, get_subparsers | from .parsers import FalyxParsers, get_arg_parsers, get_root_parser, get_subparsers | ||||||
|  |  | ||||||
| __all__ = [ | __all__ = [ | ||||||
|   | |||||||
							
								
								
									
										98
									
								
								falyx/parser/argument.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								falyx/parser/argument.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """argument.py""" | ||||||
|  | from dataclasses import dataclass | ||||||
|  | from typing import Any | ||||||
|  |  | ||||||
|  | from falyx.action.base import BaseAction | ||||||
|  | from falyx.parser.argument_action import ArgumentAction | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class Argument: | ||||||
|  |     """Represents a command-line argument.""" | ||||||
|  |  | ||||||
|  |     flags: tuple[str, ...] | ||||||
|  |     dest: str  # Destination name for the argument | ||||||
|  |     action: ArgumentAction = ( | ||||||
|  |         ArgumentAction.STORE | ||||||
|  |     )  # Action to be taken when the argument is encountered | ||||||
|  |     type: Any = str  # Type of the argument (e.g., str, int, float) or callable | ||||||
|  |     default: Any = None  # Default value if the argument is not provided | ||||||
|  |     choices: list[str] | None = None  # List of valid choices for the argument | ||||||
|  |     required: bool = False  # True if the argument is required | ||||||
|  |     help: str = ""  # Help text for the argument | ||||||
|  |     nargs: int | str | None = None  # int, '?', '*', '+', None | ||||||
|  |     positional: bool = False  # True if no leading - or -- in flags | ||||||
|  |     resolver: BaseAction | None = None  # Action object for the argument | ||||||
|  |  | ||||||
|  |     def get_positional_text(self) -> str: | ||||||
|  |         """Get the positional text for the argument.""" | ||||||
|  |         text = "" | ||||||
|  |         if self.positional: | ||||||
|  |             if self.choices: | ||||||
|  |                 text = f"{{{','.join([str(choice) for choice in self.choices])}}}" | ||||||
|  |             else: | ||||||
|  |                 text = self.dest | ||||||
|  |         return text | ||||||
|  |  | ||||||
|  |     def get_choice_text(self) -> str: | ||||||
|  |         """Get the choice text for the argument.""" | ||||||
|  |         choice_text = "" | ||||||
|  |         if self.choices: | ||||||
|  |             choice_text = f"{{{','.join([str(choice) for choice in self.choices])}}}" | ||||||
|  |         elif ( | ||||||
|  |             self.action | ||||||
|  |             in ( | ||||||
|  |                 ArgumentAction.STORE, | ||||||
|  |                 ArgumentAction.APPEND, | ||||||
|  |                 ArgumentAction.EXTEND, | ||||||
|  |             ) | ||||||
|  |             and not self.positional | ||||||
|  |         ): | ||||||
|  |             choice_text = self.dest.upper() | ||||||
|  |         elif self.action in ( | ||||||
|  |             ArgumentAction.STORE, | ||||||
|  |             ArgumentAction.APPEND, | ||||||
|  |             ArgumentAction.EXTEND, | ||||||
|  |         ) or isinstance(self.nargs, str): | ||||||
|  |             choice_text = self.dest | ||||||
|  |  | ||||||
|  |         if self.nargs == "?": | ||||||
|  |             choice_text = f"[{choice_text}]" | ||||||
|  |         elif self.nargs == "*": | ||||||
|  |             choice_text = f"[{choice_text} ...]" | ||||||
|  |         elif self.nargs == "+": | ||||||
|  |             choice_text = f"{choice_text} [{choice_text} ...]" | ||||||
|  |         return choice_text | ||||||
|  |  | ||||||
|  |     def __eq__(self, other: object) -> bool: | ||||||
|  |         if not isinstance(other, Argument): | ||||||
|  |             return False | ||||||
|  |         return ( | ||||||
|  |             self.flags == other.flags | ||||||
|  |             and self.dest == other.dest | ||||||
|  |             and self.action == other.action | ||||||
|  |             and self.type == other.type | ||||||
|  |             and self.choices == other.choices | ||||||
|  |             and self.required == other.required | ||||||
|  |             and self.nargs == other.nargs | ||||||
|  |             and self.positional == other.positional | ||||||
|  |             and self.default == other.default | ||||||
|  |             and self.help == other.help | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def __hash__(self) -> int: | ||||||
|  |         return hash( | ||||||
|  |             ( | ||||||
|  |                 tuple(self.flags), | ||||||
|  |                 self.dest, | ||||||
|  |                 self.action, | ||||||
|  |                 self.type, | ||||||
|  |                 tuple(self.choices or []), | ||||||
|  |                 self.required, | ||||||
|  |                 self.nargs, | ||||||
|  |                 self.positional, | ||||||
|  |                 self.default, | ||||||
|  |                 self.help, | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
							
								
								
									
										27
									
								
								falyx/parser/argument_action.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								falyx/parser/argument_action.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """argument_action.py""" | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | from enum import Enum | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ArgumentAction(Enum): | ||||||
|  |     """Defines the action to be taken when the argument is encountered.""" | ||||||
|  |  | ||||||
|  |     ACTION = "action" | ||||||
|  |     STORE = "store" | ||||||
|  |     STORE_TRUE = "store_true" | ||||||
|  |     STORE_FALSE = "store_false" | ||||||
|  |     APPEND = "append" | ||||||
|  |     EXTEND = "extend" | ||||||
|  |     COUNT = "count" | ||||||
|  |     HELP = "help" | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def choices(cls) -> list[ArgumentAction]: | ||||||
|  |         """Return a list of all argument actions.""" | ||||||
|  |         return list(cls) | ||||||
|  |  | ||||||
|  |     def __str__(self) -> str: | ||||||
|  |         """Return the string representation of the argument action.""" | ||||||
|  |         return self.value | ||||||
| @@ -1,9 +1,8 @@ | |||||||
| # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
|  | """command_argument_parser.py""" | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
| 
 | 
 | ||||||
| from copy import deepcopy | from copy import deepcopy | ||||||
| from dataclasses import dataclass |  | ||||||
| from enum import Enum |  | ||||||
| from typing import Any, Iterable | from typing import Any, Iterable | ||||||
| 
 | 
 | ||||||
| from rich.console import Console | from rich.console import Console | ||||||
| @@ -12,123 +11,12 @@ from rich.text import Text | |||||||
| 
 | 
 | ||||||
| from falyx.action.base import BaseAction | from falyx.action.base import BaseAction | ||||||
| from falyx.exceptions import CommandArgumentError | from falyx.exceptions import CommandArgumentError | ||||||
|  | from falyx.parser.argument import Argument | ||||||
|  | from falyx.parser.argument_action import ArgumentAction | ||||||
| from falyx.parser.utils import coerce_value | from falyx.parser.utils import coerce_value | ||||||
| from falyx.signals import HelpSignal | from falyx.signals import HelpSignal | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ArgumentAction(Enum): |  | ||||||
|     """Defines the action to be taken when the argument is encountered.""" |  | ||||||
| 
 |  | ||||||
|     ACTION = "action" |  | ||||||
|     STORE = "store" |  | ||||||
|     STORE_TRUE = "store_true" |  | ||||||
|     STORE_FALSE = "store_false" |  | ||||||
|     APPEND = "append" |  | ||||||
|     EXTEND = "extend" |  | ||||||
|     COUNT = "count" |  | ||||||
|     HELP = "help" |  | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def choices(cls) -> list[ArgumentAction]: |  | ||||||
|         """Return a list of all argument actions.""" |  | ||||||
|         return list(cls) |  | ||||||
| 
 |  | ||||||
|     def __str__(self) -> str: |  | ||||||
|         """Return the string representation of the argument action.""" |  | ||||||
|         return self.value |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @dataclass |  | ||||||
| class Argument: |  | ||||||
|     """Represents a command-line argument.""" |  | ||||||
| 
 |  | ||||||
|     flags: tuple[str, ...] |  | ||||||
|     dest: str  # Destination name for the argument |  | ||||||
|     action: ArgumentAction = ( |  | ||||||
|         ArgumentAction.STORE |  | ||||||
|     )  # Action to be taken when the argument is encountered |  | ||||||
|     type: Any = str  # Type of the argument (e.g., str, int, float) or callable |  | ||||||
|     default: Any = None  # Default value if the argument is not provided |  | ||||||
|     choices: list[str] | None = None  # List of valid choices for the argument |  | ||||||
|     required: bool = False  # True if the argument is required |  | ||||||
|     help: str = ""  # Help text for the argument |  | ||||||
|     nargs: int | str | None = None  # int, '?', '*', '+', None |  | ||||||
|     positional: bool = False  # True if no leading - or -- in flags |  | ||||||
|     resolver: BaseAction | None = None  # Action object for the argument |  | ||||||
| 
 |  | ||||||
|     def get_positional_text(self) -> str: |  | ||||||
|         """Get the positional text for the argument.""" |  | ||||||
|         text = "" |  | ||||||
|         if self.positional: |  | ||||||
|             if self.choices: |  | ||||||
|                 text = f"{{{','.join([str(choice) for choice in self.choices])}}}" |  | ||||||
|             else: |  | ||||||
|                 text = self.dest |  | ||||||
|         return text |  | ||||||
| 
 |  | ||||||
|     def get_choice_text(self) -> str: |  | ||||||
|         """Get the choice text for the argument.""" |  | ||||||
|         choice_text = "" |  | ||||||
|         if self.choices: |  | ||||||
|             choice_text = f"{{{','.join([str(choice) for choice in self.choices])}}}" |  | ||||||
|         elif ( |  | ||||||
|             self.action |  | ||||||
|             in ( |  | ||||||
|                 ArgumentAction.STORE, |  | ||||||
|                 ArgumentAction.APPEND, |  | ||||||
|                 ArgumentAction.EXTEND, |  | ||||||
|             ) |  | ||||||
|             and not self.positional |  | ||||||
|         ): |  | ||||||
|             choice_text = self.dest.upper() |  | ||||||
|         elif self.action in ( |  | ||||||
|             ArgumentAction.STORE, |  | ||||||
|             ArgumentAction.APPEND, |  | ||||||
|             ArgumentAction.EXTEND, |  | ||||||
|         ) or isinstance(self.nargs, str): |  | ||||||
|             choice_text = self.dest |  | ||||||
| 
 |  | ||||||
|         if self.nargs == "?": |  | ||||||
|             choice_text = f"[{choice_text}]" |  | ||||||
|         elif self.nargs == "*": |  | ||||||
|             choice_text = f"[{choice_text} ...]" |  | ||||||
|         elif self.nargs == "+": |  | ||||||
|             choice_text = f"{choice_text} [{choice_text} ...]" |  | ||||||
|         return choice_text |  | ||||||
| 
 |  | ||||||
|     def __eq__(self, other: object) -> bool: |  | ||||||
|         if not isinstance(other, Argument): |  | ||||||
|             return False |  | ||||||
|         return ( |  | ||||||
|             self.flags == other.flags |  | ||||||
|             and self.dest == other.dest |  | ||||||
|             and self.action == other.action |  | ||||||
|             and self.type == other.type |  | ||||||
|             and self.choices == other.choices |  | ||||||
|             and self.required == other.required |  | ||||||
|             and self.nargs == other.nargs |  | ||||||
|             and self.positional == other.positional |  | ||||||
|             and self.default == other.default |  | ||||||
|             and self.help == other.help |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     def __hash__(self) -> int: |  | ||||||
|         return hash( |  | ||||||
|             ( |  | ||||||
|                 tuple(self.flags), |  | ||||||
|                 self.dest, |  | ||||||
|                 self.action, |  | ||||||
|                 self.type, |  | ||||||
|                 tuple(self.choices or []), |  | ||||||
|                 self.required, |  | ||||||
|                 self.nargs, |  | ||||||
|                 self.positional, |  | ||||||
|                 self.default, |  | ||||||
|                 self.help, |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class CommandArgumentParser: | class CommandArgumentParser: | ||||||
|     """ |     """ | ||||||
|     Custom argument parser for Falyx Commands. |     Custom argument parser for Falyx Commands. | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
| import inspect | import inspect | ||||||
| from typing import Any, Callable | from typing import Any, Callable | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||||
| import types | import types | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from enum import EnumMeta | from enum import EnumMeta | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| __version__ = "0.1.51" | __version__ = "0.1.52" | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| [tool.poetry] | [tool.poetry] | ||||||
| name = "falyx" | name = "falyx" | ||||||
| version = "0.1.51" | version = "0.1.52" | ||||||
| description = "Reliable and introspectable async CLI action framework." | description = "Reliable and introspectable async CLI action framework." | ||||||
| authors = ["Roland Thomas Jr <roland@rtj.dev>"] | authors = ["Roland Thomas Jr <roland@rtj.dev>"] | ||||||
| license = "MIT" | license = "MIT" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user