falyx 0.1.51__tar.gz → 0.1.52__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {falyx-0.1.51 → falyx-0.1.52}/PKG-INFO +1 -1
  2. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/__init__.py +2 -1
  3. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/action_group.py +2 -0
  4. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/chained_action.py +2 -0
  5. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/fallback_action.py +2 -0
  6. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/io_action.py +0 -93
  7. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/literal_input_action.py +2 -0
  8. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/mixins.py +2 -0
  9. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/process_action.py +2 -0
  10. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/process_pool_action.py +2 -0
  11. falyx-0.1.52/falyx/action/shell_action.py +105 -0
  12. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/types.py +2 -0
  13. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/user_input_action.py +2 -0
  14. {falyx-0.1.51 → falyx-0.1.52}/falyx/command.py +1 -1
  15. {falyx-0.1.51 → falyx-0.1.52}/falyx/parser/__init__.py +3 -1
  16. falyx-0.1.52/falyx/parser/argument.py +98 -0
  17. falyx-0.1.52/falyx/parser/argument_action.py +27 -0
  18. falyx-0.1.51/falyx/parser/argparse.py → falyx-0.1.52/falyx/parser/command_argument_parser.py +3 -115
  19. {falyx-0.1.51 → falyx-0.1.52}/falyx/parser/signature.py +1 -0
  20. {falyx-0.1.51 → falyx-0.1.52}/falyx/parser/utils.py +1 -0
  21. falyx-0.1.52/falyx/version.py +1 -0
  22. {falyx-0.1.51 → falyx-0.1.52}/pyproject.toml +1 -1
  23. falyx-0.1.51/falyx/version.py +0 -1
  24. {falyx-0.1.51 → falyx-0.1.52}/LICENSE +0 -0
  25. {falyx-0.1.51 → falyx-0.1.52}/README.md +0 -0
  26. {falyx-0.1.51 → falyx-0.1.52}/falyx/.pytyped +0 -0
  27. {falyx-0.1.51 → falyx-0.1.52}/falyx/__init__.py +0 -0
  28. {falyx-0.1.51 → falyx-0.1.52}/falyx/__main__.py +0 -0
  29. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/.pytyped +0 -0
  30. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/action.py +0 -0
  31. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/action_factory.py +0 -0
  32. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/base.py +0 -0
  33. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/http_action.py +0 -0
  34. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/menu_action.py +0 -0
  35. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/prompt_menu_action.py +0 -0
  36. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/select_file_action.py +0 -0
  37. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/selection_action.py +0 -0
  38. {falyx-0.1.51 → falyx-0.1.52}/falyx/action/signal_action.py +0 -0
  39. {falyx-0.1.51 → falyx-0.1.52}/falyx/bottom_bar.py +0 -0
  40. {falyx-0.1.51 → falyx-0.1.52}/falyx/config.py +0 -0
  41. {falyx-0.1.51 → falyx-0.1.52}/falyx/context.py +0 -0
  42. {falyx-0.1.51 → falyx-0.1.52}/falyx/debug.py +0 -0
  43. {falyx-0.1.51 → falyx-0.1.52}/falyx/exceptions.py +0 -0
  44. {falyx-0.1.51 → falyx-0.1.52}/falyx/execution_registry.py +0 -0
  45. {falyx-0.1.51 → falyx-0.1.52}/falyx/falyx.py +0 -0
  46. {falyx-0.1.51 → falyx-0.1.52}/falyx/hook_manager.py +0 -0
  47. {falyx-0.1.51 → falyx-0.1.52}/falyx/hooks.py +0 -0
  48. {falyx-0.1.51 → falyx-0.1.52}/falyx/init.py +0 -0
  49. {falyx-0.1.51 → falyx-0.1.52}/falyx/logger.py +0 -0
  50. {falyx-0.1.51 → falyx-0.1.52}/falyx/menu.py +0 -0
  51. {falyx-0.1.51 → falyx-0.1.52}/falyx/options_manager.py +0 -0
  52. {falyx-0.1.51 → falyx-0.1.52}/falyx/parser/.pytyped +0 -0
  53. {falyx-0.1.51 → falyx-0.1.52}/falyx/parser/parsers.py +0 -0
  54. {falyx-0.1.51 → falyx-0.1.52}/falyx/prompt_utils.py +0 -0
  55. {falyx-0.1.51 → falyx-0.1.52}/falyx/protocols.py +0 -0
  56. {falyx-0.1.51 → falyx-0.1.52}/falyx/retry.py +0 -0
  57. {falyx-0.1.51 → falyx-0.1.52}/falyx/retry_utils.py +0 -0
  58. {falyx-0.1.51 → falyx-0.1.52}/falyx/selection.py +0 -0
  59. {falyx-0.1.51 → falyx-0.1.52}/falyx/signals.py +0 -0
  60. {falyx-0.1.51 → falyx-0.1.52}/falyx/tagged_table.py +0 -0
  61. {falyx-0.1.51 → falyx-0.1.52}/falyx/themes/__init__.py +0 -0
  62. {falyx-0.1.51 → falyx-0.1.52}/falyx/themes/colors.py +0 -0
  63. {falyx-0.1.51 → falyx-0.1.52}/falyx/utils.py +0 -0
  64. {falyx-0.1.51 → falyx-0.1.52}/falyx/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: falyx
3
- Version: 0.1.51
3
+ Version: 0.1.52
4
4
  Summary: Reliable and introspectable async CLI action framework.
5
5
  License: MIT
6
6
  Author: Roland Thomas Jr
@@ -12,7 +12,7 @@ from .base import BaseAction
12
12
  from .chained_action import ChainedAction
13
13
  from .fallback_action import FallbackAction
14
14
  from .http_action import HTTPAction
15
- from .io_action import BaseIOAction, ShellAction
15
+ from .io_action import BaseIOAction
16
16
  from .literal_input_action import LiteralInputAction
17
17
  from .menu_action import MenuAction
18
18
  from .process_action import ProcessAction
@@ -20,6 +20,7 @@ from .process_pool_action import ProcessPoolAction
20
20
  from .prompt_menu_action import PromptMenuAction
21
21
  from .select_file_action import SelectFileAction
22
22
  from .selection_action import SelectionAction
23
+ from .shell_action import ShellAction
23
24
  from .signal_action import SignalAction
24
25
  from .user_input_action import UserInputAction
25
26
 
@@ -1,3 +1,5 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """action_group.py"""
1
3
  import asyncio
2
4
  import random
3
5
  from typing import Any, Callable
@@ -1,3 +1,5 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """chained_action.py"""
1
3
  from __future__ import annotations
2
4
 
3
5
  from typing import Any, Callable
@@ -1,3 +1,5 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """fallback_action.py"""
1
3
  from functools import cached_property
2
4
  from typing import Any
3
5
 
@@ -16,8 +16,6 @@ Common usage includes shell-like filters, input transformers, or any tool that
16
16
  needs to consume input from another process or pipeline.
17
17
  """
18
18
  import asyncio
19
- import shlex
20
- import subprocess
21
19
  import sys
22
20
  from typing import Any, Callable
23
21
 
@@ -25,10 +23,8 @@ from rich.tree import Tree
25
23
 
26
24
  from falyx.action.base import BaseAction
27
25
  from falyx.context import ExecutionContext
28
- from falyx.exceptions import FalyxError
29
26
  from falyx.execution_registry import ExecutionRegistry as er
30
27
  from falyx.hook_manager import HookManager, HookType
31
- from falyx.logger import logger
32
28
  from falyx.themes import OneColors
33
29
 
34
30
 
@@ -171,92 +167,3 @@ class BaseIOAction(BaseAction):
171
167
  parent.add("".join(label))
172
168
  else:
173
169
  self.console.print(Tree("".join(label)))
174
-
175
-
176
- class ShellAction(BaseIOAction):
177
- """
178
- ShellAction wraps a shell command template for CLI pipelines.
179
-
180
- This Action takes parsed input (from stdin, literal, or last_result),
181
- substitutes it into the provided shell command template, and executes
182
- the command asynchronously using subprocess.
183
-
184
- Designed for quick integration with shell tools like `grep`, `ping`, `jq`, etc.
185
-
186
- ⚠️ Security Warning:
187
- By default, ShellAction uses `shell=True`, which can be dangerous with
188
- unsanitized input. To mitigate this, set `safe_mode=True` to use `shell=False`
189
- with `shlex.split()`.
190
-
191
- Features:
192
- - Automatically handles input parsing (str/bytes)
193
- - `safe_mode=True` disables shell interpretation and runs with `shell=False`
194
- - Captures stdout and stderr from shell execution
195
- - Raises on non-zero exit codes with stderr as the error
196
- - Result is returned as trimmed stdout string
197
-
198
- Args:
199
- name (str): Name of the action.
200
- command_template (str): Shell command to execute. Must include `{}` to include
201
- input. If no placeholder is present, the input is not
202
- included.
203
- safe_mode (bool): If True, runs with `shell=False` using shlex parsing
204
- (default: False).
205
- """
206
-
207
- def __init__(
208
- self, name: str, command_template: str, safe_mode: bool = False, **kwargs
209
- ):
210
- super().__init__(name=name, **kwargs)
211
- self.command_template = command_template
212
- self.safe_mode = safe_mode
213
-
214
- def from_input(self, raw: str | bytes) -> str:
215
- if not isinstance(raw, (str, bytes)):
216
- raise TypeError(
217
- f"{self.name} expected str or bytes input, got {type(raw).__name__}"
218
- )
219
- return raw.strip() if isinstance(raw, str) else raw.decode("utf-8").strip()
220
-
221
- def get_infer_target(self) -> tuple[Callable[..., Any] | None, dict[str, Any] | None]:
222
- if sys.stdin.isatty():
223
- return self._run, {"parsed_input": {"help": self.command_template}}
224
- return None, None
225
-
226
- async def _run(self, parsed_input: str) -> str:
227
- # Replace placeholder in template, or use raw input as full command
228
- command = self.command_template.format(parsed_input)
229
- if self.safe_mode:
230
- try:
231
- args = shlex.split(command)
232
- except ValueError as error:
233
- raise FalyxError(f"Invalid command template: {error}")
234
- result = subprocess.run(args, capture_output=True, text=True, check=True)
235
- else:
236
- result = subprocess.run(
237
- command, shell=True, text=True, capture_output=True, check=True
238
- )
239
- if result.returncode != 0:
240
- raise RuntimeError(result.stderr.strip())
241
- return result.stdout.strip()
242
-
243
- def to_output(self, result: str) -> str:
244
- return result
245
-
246
- async def preview(self, parent: Tree | None = None):
247
- label = [f"[{OneColors.GREEN_b}]⚙ ShellAction[/] '{self.name}'"]
248
- label.append(f"\n[dim]Template:[/] {self.command_template}")
249
- label.append(
250
- f"\n[dim]Safe mode:[/] {'Enabled' if self.safe_mode else 'Disabled'}"
251
- )
252
- if self.inject_last_result:
253
- label.append(f" [dim](injects '{self.inject_into}')[/dim]")
254
- tree = parent.add("".join(label)) if parent else Tree("".join(label))
255
- if not parent:
256
- self.console.print(tree)
257
-
258
- def __str__(self):
259
- return (
260
- f"ShellAction(name={self.name!r}, command_template={self.command_template!r},"
261
- f" safe_mode={self.safe_mode})"
262
- )
@@ -1,3 +1,5 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """literal_input_action.py"""
1
3
  from __future__ import annotations
2
4
 
3
5
  from functools import cached_property
@@ -1,3 +1,5 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """mixins.py"""
1
3
  from falyx.action.base import BaseAction
2
4
 
3
5
 
@@ -1,3 +1,5 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """process_action.py"""
1
3
  from __future__ import annotations
2
4
 
3
5
  import asyncio
@@ -1,3 +1,5 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """process_pool_action.py"""
1
3
  from __future__ import annotations
2
4
 
3
5
  import asyncio
@@ -0,0 +1,105 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """shell_action.py
3
+ Execute shell commands with input substitution."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import shlex
8
+ import subprocess
9
+ import sys
10
+ from typing import Any, Callable
11
+
12
+ from rich.tree import Tree
13
+
14
+ from falyx.action.io_action import BaseIOAction
15
+ from falyx.exceptions import FalyxError
16
+ from falyx.themes import OneColors
17
+
18
+
19
+ class ShellAction(BaseIOAction):
20
+ """
21
+ ShellAction wraps a shell command template for CLI pipelines.
22
+
23
+ This Action takes parsed input (from stdin, literal, or last_result),
24
+ substitutes it into the provided shell command template, and executes
25
+ the command asynchronously using subprocess.
26
+
27
+ Designed for quick integration with shell tools like `grep`, `ping`, `jq`, etc.
28
+
29
+ ⚠️ Security Warning:
30
+ By default, ShellAction uses `shell=True`, which can be dangerous with
31
+ unsanitized input. To mitigate this, set `safe_mode=True` to use `shell=False`
32
+ with `shlex.split()`.
33
+
34
+ Features:
35
+ - Automatically handles input parsing (str/bytes)
36
+ - `safe_mode=True` disables shell interpretation and runs with `shell=False`
37
+ - Captures stdout and stderr from shell execution
38
+ - Raises on non-zero exit codes with stderr as the error
39
+ - Result is returned as trimmed stdout string
40
+
41
+ Args:
42
+ name (str): Name of the action.
43
+ command_template (str): Shell command to execute. Must include `{}` to include
44
+ input. If no placeholder is present, the input is not
45
+ included.
46
+ safe_mode (bool): If True, runs with `shell=False` using shlex parsing
47
+ (default: False).
48
+ """
49
+
50
+ def __init__(
51
+ self, name: str, command_template: str, safe_mode: bool = False, **kwargs
52
+ ):
53
+ super().__init__(name=name, **kwargs)
54
+ self.command_template = command_template
55
+ self.safe_mode = safe_mode
56
+
57
+ def from_input(self, raw: str | bytes) -> str:
58
+ if not isinstance(raw, (str, bytes)):
59
+ raise TypeError(
60
+ f"{self.name} expected str or bytes input, got {type(raw).__name__}"
61
+ )
62
+ return raw.strip() if isinstance(raw, str) else raw.decode("utf-8").strip()
63
+
64
+ def get_infer_target(self) -> tuple[Callable[..., Any] | None, dict[str, Any] | None]:
65
+ if sys.stdin.isatty():
66
+ return self._run, {"parsed_input": {"help": self.command_template}}
67
+ return None, None
68
+
69
+ async def _run(self, parsed_input: str) -> str:
70
+ # Replace placeholder in template, or use raw input as full command
71
+ command = self.command_template.format(parsed_input)
72
+ if self.safe_mode:
73
+ try:
74
+ args = shlex.split(command)
75
+ except ValueError as error:
76
+ raise FalyxError(f"Invalid command template: {error}")
77
+ result = subprocess.run(args, capture_output=True, text=True, check=True)
78
+ else:
79
+ result = subprocess.run(
80
+ command, shell=True, text=True, capture_output=True, check=True
81
+ )
82
+ if result.returncode != 0:
83
+ raise RuntimeError(result.stderr.strip())
84
+ return result.stdout.strip()
85
+
86
+ def to_output(self, result: str) -> str:
87
+ return result
88
+
89
+ async def preview(self, parent: Tree | None = None):
90
+ label = [f"[{OneColors.GREEN_b}]⚙ ShellAction[/] '{self.name}'"]
91
+ label.append(f"\n[dim]Template:[/] {self.command_template}")
92
+ label.append(
93
+ f"\n[dim]Safe mode:[/] {'Enabled' if self.safe_mode else 'Disabled'}"
94
+ )
95
+ if self.inject_last_result:
96
+ label.append(f" [dim](injects '{self.inject_into}')[/dim]")
97
+ tree = parent.add("".join(label)) if parent else Tree("".join(label))
98
+ if not parent:
99
+ self.console.print(tree)
100
+
101
+ def __str__(self):
102
+ return (
103
+ f"ShellAction(name={self.name!r}, command_template={self.command_template!r},"
104
+ f" safe_mode={self.safe_mode})"
105
+ )
@@ -1,3 +1,5 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """types.py"""
1
3
  from __future__ import annotations
2
4
 
3
5
  from enum import Enum
@@ -1,3 +1,5 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """user_input_action.py"""
1
3
  from prompt_toolkit import PromptSession
2
4
  from prompt_toolkit.validation import Validator
3
5
  from rich.console import Console
@@ -34,7 +34,7 @@ from falyx.execution_registry import ExecutionRegistry as er
34
34
  from falyx.hook_manager import HookManager, HookType
35
35
  from falyx.logger import logger
36
36
  from falyx.options_manager import OptionsManager
37
- from falyx.parser.argparse import CommandArgumentParser
37
+ from falyx.parser.command_argument_parser import CommandArgumentParser
38
38
  from falyx.parser.signature import infer_args_from_func
39
39
  from falyx.prompt_utils import confirm_async, should_prompt_user
40
40
  from falyx.protocols import ArgParserProtocol
@@ -5,7 +5,9 @@ Copyright (c) 2025 rtj.dev LLC.
5
5
  Licensed under the MIT License. See LICENSE file for details.
6
6
  """
7
7
 
8
- from .argparse import Argument, ArgumentAction, CommandArgumentParser
8
+ from .argument import Argument
9
+ from .argument_action import ArgumentAction
10
+ from .command_argument_parser import CommandArgumentParser
9
11
  from .parsers import FalyxParsers, get_arg_parsers, get_root_parser, get_subparsers
10
12
 
11
13
  __all__ = [
@@ -0,0 +1,98 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """argument.py"""
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from falyx.action.base import BaseAction
7
+ from falyx.parser.argument_action import ArgumentAction
8
+
9
+
10
+ @dataclass
11
+ class Argument:
12
+ """Represents a command-line argument."""
13
+
14
+ flags: tuple[str, ...]
15
+ dest: str # Destination name for the argument
16
+ action: ArgumentAction = (
17
+ ArgumentAction.STORE
18
+ ) # Action to be taken when the argument is encountered
19
+ type: Any = str # Type of the argument (e.g., str, int, float) or callable
20
+ default: Any = None # Default value if the argument is not provided
21
+ choices: list[str] | None = None # List of valid choices for the argument
22
+ required: bool = False # True if the argument is required
23
+ help: str = "" # Help text for the argument
24
+ nargs: int | str | None = None # int, '?', '*', '+', None
25
+ positional: bool = False # True if no leading - or -- in flags
26
+ resolver: BaseAction | None = None # Action object for the argument
27
+
28
+ def get_positional_text(self) -> str:
29
+ """Get the positional text for the argument."""
30
+ text = ""
31
+ if self.positional:
32
+ if self.choices:
33
+ text = f"{{{','.join([str(choice) for choice in self.choices])}}}"
34
+ else:
35
+ text = self.dest
36
+ return text
37
+
38
+ def get_choice_text(self) -> str:
39
+ """Get the choice text for the argument."""
40
+ choice_text = ""
41
+ if self.choices:
42
+ choice_text = f"{{{','.join([str(choice) for choice in self.choices])}}}"
43
+ elif (
44
+ self.action
45
+ in (
46
+ ArgumentAction.STORE,
47
+ ArgumentAction.APPEND,
48
+ ArgumentAction.EXTEND,
49
+ )
50
+ and not self.positional
51
+ ):
52
+ choice_text = self.dest.upper()
53
+ elif self.action in (
54
+ ArgumentAction.STORE,
55
+ ArgumentAction.APPEND,
56
+ ArgumentAction.EXTEND,
57
+ ) or isinstance(self.nargs, str):
58
+ choice_text = self.dest
59
+
60
+ if self.nargs == "?":
61
+ choice_text = f"[{choice_text}]"
62
+ elif self.nargs == "*":
63
+ choice_text = f"[{choice_text} ...]"
64
+ elif self.nargs == "+":
65
+ choice_text = f"{choice_text} [{choice_text} ...]"
66
+ return choice_text
67
+
68
+ def __eq__(self, other: object) -> bool:
69
+ if not isinstance(other, Argument):
70
+ return False
71
+ return (
72
+ self.flags == other.flags
73
+ and self.dest == other.dest
74
+ and self.action == other.action
75
+ and self.type == other.type
76
+ and self.choices == other.choices
77
+ and self.required == other.required
78
+ and self.nargs == other.nargs
79
+ and self.positional == other.positional
80
+ and self.default == other.default
81
+ and self.help == other.help
82
+ )
83
+
84
+ def __hash__(self) -> int:
85
+ return hash(
86
+ (
87
+ tuple(self.flags),
88
+ self.dest,
89
+ self.action,
90
+ self.type,
91
+ tuple(self.choices or []),
92
+ self.required,
93
+ self.nargs,
94
+ self.positional,
95
+ self.default,
96
+ self.help,
97
+ )
98
+ )
@@ -0,0 +1,27 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """argument_action.py"""
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class ArgumentAction(Enum):
9
+ """Defines the action to be taken when the argument is encountered."""
10
+
11
+ ACTION = "action"
12
+ STORE = "store"
13
+ STORE_TRUE = "store_true"
14
+ STORE_FALSE = "store_false"
15
+ APPEND = "append"
16
+ EXTEND = "extend"
17
+ COUNT = "count"
18
+ HELP = "help"
19
+
20
+ @classmethod
21
+ def choices(cls) -> list[ArgumentAction]:
22
+ """Return a list of all argument actions."""
23
+ return list(cls)
24
+
25
+ def __str__(self) -> str:
26
+ """Return the string representation of the argument action."""
27
+ return self.value
@@ -1,9 +1,8 @@
1
1
  # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
+ """command_argument_parser.py"""
2
3
  from __future__ import annotations
3
4
 
4
5
  from copy import deepcopy
5
- from dataclasses import dataclass
6
- from enum import Enum
7
6
  from typing import Any, Iterable
8
7
 
9
8
  from rich.console import Console
@@ -12,123 +11,12 @@ from rich.text import Text
12
11
 
13
12
  from falyx.action.base import BaseAction
14
13
  from falyx.exceptions import CommandArgumentError
14
+ from falyx.parser.argument import Argument
15
+ from falyx.parser.argument_action import ArgumentAction
15
16
  from falyx.parser.utils import coerce_value
16
17
  from falyx.signals import HelpSignal
17
18
 
18
19
 
19
- class ArgumentAction(Enum):
20
- """Defines the action to be taken when the argument is encountered."""
21
-
22
- ACTION = "action"
23
- STORE = "store"
24
- STORE_TRUE = "store_true"
25
- STORE_FALSE = "store_false"
26
- APPEND = "append"
27
- EXTEND = "extend"
28
- COUNT = "count"
29
- HELP = "help"
30
-
31
- @classmethod
32
- def choices(cls) -> list[ArgumentAction]:
33
- """Return a list of all argument actions."""
34
- return list(cls)
35
-
36
- def __str__(self) -> str:
37
- """Return the string representation of the argument action."""
38
- return self.value
39
-
40
-
41
- @dataclass
42
- class Argument:
43
- """Represents a command-line argument."""
44
-
45
- flags: tuple[str, ...]
46
- dest: str # Destination name for the argument
47
- action: ArgumentAction = (
48
- ArgumentAction.STORE
49
- ) # Action to be taken when the argument is encountered
50
- type: Any = str # Type of the argument (e.g., str, int, float) or callable
51
- default: Any = None # Default value if the argument is not provided
52
- choices: list[str] | None = None # List of valid choices for the argument
53
- required: bool = False # True if the argument is required
54
- help: str = "" # Help text for the argument
55
- nargs: int | str | None = None # int, '?', '*', '+', None
56
- positional: bool = False # True if no leading - or -- in flags
57
- resolver: BaseAction | None = None # Action object for the argument
58
-
59
- def get_positional_text(self) -> str:
60
- """Get the positional text for the argument."""
61
- text = ""
62
- if self.positional:
63
- if self.choices:
64
- text = f"{{{','.join([str(choice) for choice in self.choices])}}}"
65
- else:
66
- text = self.dest
67
- return text
68
-
69
- def get_choice_text(self) -> str:
70
- """Get the choice text for the argument."""
71
- choice_text = ""
72
- if self.choices:
73
- choice_text = f"{{{','.join([str(choice) for choice in self.choices])}}}"
74
- elif (
75
- self.action
76
- in (
77
- ArgumentAction.STORE,
78
- ArgumentAction.APPEND,
79
- ArgumentAction.EXTEND,
80
- )
81
- and not self.positional
82
- ):
83
- choice_text = self.dest.upper()
84
- elif self.action in (
85
- ArgumentAction.STORE,
86
- ArgumentAction.APPEND,
87
- ArgumentAction.EXTEND,
88
- ) or isinstance(self.nargs, str):
89
- choice_text = self.dest
90
-
91
- if self.nargs == "?":
92
- choice_text = f"[{choice_text}]"
93
- elif self.nargs == "*":
94
- choice_text = f"[{choice_text} ...]"
95
- elif self.nargs == "+":
96
- choice_text = f"{choice_text} [{choice_text} ...]"
97
- return choice_text
98
-
99
- def __eq__(self, other: object) -> bool:
100
- if not isinstance(other, Argument):
101
- return False
102
- return (
103
- self.flags == other.flags
104
- and self.dest == other.dest
105
- and self.action == other.action
106
- and self.type == other.type
107
- and self.choices == other.choices
108
- and self.required == other.required
109
- and self.nargs == other.nargs
110
- and self.positional == other.positional
111
- and self.default == other.default
112
- and self.help == other.help
113
- )
114
-
115
- def __hash__(self) -> int:
116
- return hash(
117
- (
118
- tuple(self.flags),
119
- self.dest,
120
- self.action,
121
- self.type,
122
- tuple(self.choices or []),
123
- self.required,
124
- self.nargs,
125
- self.positional,
126
- self.default,
127
- self.help,
128
- )
129
- )
130
-
131
-
132
20
  class CommandArgumentParser:
133
21
  """
134
22
  Custom argument parser for Falyx Commands.
@@ -1,3 +1,4 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
1
2
  import inspect
2
3
  from typing import Any, Callable
3
4
 
@@ -1,3 +1,4 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
1
2
  import types
2
3
  from datetime import datetime
3
4
  from enum import EnumMeta
@@ -0,0 +1 @@
1
+ __version__ = "0.1.52"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "falyx"
3
- version = "0.1.51"
3
+ version = "0.1.52"
4
4
  description = "Reliable and introspectable async CLI action framework."
5
5
  authors = ["Roland Thomas Jr <roland@rtj.dev>"]
6
6
  license = "MIT"
@@ -1 +0,0 @@
1
- __version__ = "0.1.51"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes