falyx 0.1.51__py3-none-any.whl → 0.1.53__py3-none-any.whl
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.
- falyx/action/__init__.py +3 -2
- falyx/action/action.py +1 -1
- falyx/action/action_factory.py +1 -1
- falyx/action/action_group.py +13 -5
- falyx/action/{types.py → action_types.py} +5 -3
- falyx/action/{base.py → base_action.py} +1 -1
- falyx/action/chained_action.py +13 -5
- falyx/action/fallback_action.py +2 -0
- falyx/action/io_action.py +1 -94
- falyx/action/literal_input_action.py +2 -0
- falyx/action/load_file_action.py +28 -0
- falyx/action/menu_action.py +1 -1
- falyx/action/mixins.py +6 -2
- falyx/action/process_action.py +3 -1
- falyx/action/process_pool_action.py +3 -1
- falyx/action/prompt_menu_action.py +1 -1
- falyx/action/save_file_action.py +28 -0
- falyx/action/select_file_action.py +15 -15
- falyx/action/selection_action.py +2 -2
- falyx/action/shell_action.py +105 -0
- falyx/action/user_input_action.py +3 -1
- falyx/command.py +2 -2
- falyx/config.py +1 -1
- falyx/falyx.py +2 -3
- falyx/menu.py +1 -1
- falyx/parser/__init__.py +3 -1
- falyx/parser/argument.py +98 -0
- falyx/parser/argument_action.py +27 -0
- falyx/parser/{argparse.py → command_argument_parser.py} +4 -116
- falyx/parser/parsers.py +15 -7
- falyx/parser/signature.py +1 -0
- falyx/parser/utils.py +2 -1
- falyx/protocols.py +1 -1
- falyx/retry_utils.py +1 -1
- falyx/version.py +1 -1
- {falyx-0.1.51.dist-info → falyx-0.1.53.dist-info}/METADATA +1 -1
- falyx-0.1.53.dist-info/RECORD +66 -0
- falyx-0.1.51.dist-info/RECORD +0 -61
- {falyx-0.1.51.dist-info → falyx-0.1.53.dist-info}/LICENSE +0 -0
- {falyx-0.1.51.dist-info → falyx-0.1.53.dist-info}/WHEEL +0 -0
- {falyx-0.1.51.dist-info → falyx-0.1.53.dist-info}/entry_points.txt +0 -0
falyx/action/__init__.py
CHANGED
@@ -8,11 +8,11 @@ Licensed under the MIT License. See LICENSE file for details.
|
|
8
8
|
from .action import Action
|
9
9
|
from .action_factory import ActionFactoryAction
|
10
10
|
from .action_group import ActionGroup
|
11
|
-
from .
|
11
|
+
from .base_action 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
|
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
|
|
falyx/action/action.py
CHANGED
@@ -6,7 +6,7 @@ from typing import Any, Callable
|
|
6
6
|
|
7
7
|
from rich.tree import Tree
|
8
8
|
|
9
|
-
from falyx.action.
|
9
|
+
from falyx.action.base_action import BaseAction
|
10
10
|
from falyx.context import ExecutionContext
|
11
11
|
from falyx.execution_registry import ExecutionRegistry as er
|
12
12
|
from falyx.hook_manager import HookManager, HookType
|
falyx/action/action_factory.py
CHANGED
@@ -4,7 +4,7 @@ from typing import Any, Callable
|
|
4
4
|
|
5
5
|
from rich.tree import Tree
|
6
6
|
|
7
|
-
from falyx.action.
|
7
|
+
from falyx.action.base_action import BaseAction
|
8
8
|
from falyx.context import ExecutionContext
|
9
9
|
from falyx.execution_registry import ExecutionRegistry as er
|
10
10
|
from falyx.hook_manager import HookType
|
falyx/action/action_group.py
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
2
|
+
"""action_group.py"""
|
1
3
|
import asyncio
|
2
4
|
import random
|
3
|
-
from typing import Any, Callable
|
5
|
+
from typing import Any, Callable, Sequence
|
4
6
|
|
5
7
|
from rich.tree import Tree
|
6
8
|
|
7
9
|
from falyx.action.action import Action
|
8
|
-
from falyx.action.
|
10
|
+
from falyx.action.base_action import BaseAction
|
9
11
|
from falyx.action.mixins import ActionListMixin
|
10
12
|
from falyx.context import ExecutionContext, SharedContext
|
11
13
|
from falyx.execution_registry import ExecutionRegistry as er
|
@@ -52,7 +54,7 @@ class ActionGroup(BaseAction, ActionListMixin):
|
|
52
54
|
def __init__(
|
53
55
|
self,
|
54
56
|
name: str,
|
55
|
-
actions:
|
57
|
+
actions: Sequence[BaseAction | Callable[..., Any]] | None = None,
|
56
58
|
*,
|
57
59
|
hooks: HookManager | None = None,
|
58
60
|
inject_last_result: bool = False,
|
@@ -68,7 +70,7 @@ class ActionGroup(BaseAction, ActionListMixin):
|
|
68
70
|
if actions:
|
69
71
|
self.set_actions(actions)
|
70
72
|
|
71
|
-
def _wrap_if_needed(self, action: BaseAction | Any) -> BaseAction:
|
73
|
+
def _wrap_if_needed(self, action: BaseAction | Callable[..., Any]) -> BaseAction:
|
72
74
|
if isinstance(action, BaseAction):
|
73
75
|
return action
|
74
76
|
elif callable(action):
|
@@ -79,12 +81,18 @@ class ActionGroup(BaseAction, ActionListMixin):
|
|
79
81
|
f"{type(action).__name__}"
|
80
82
|
)
|
81
83
|
|
82
|
-
def add_action(self, action: BaseAction | Any) -> None:
|
84
|
+
def add_action(self, action: BaseAction | Callable[..., Any]) -> None:
|
83
85
|
action = self._wrap_if_needed(action)
|
84
86
|
super().add_action(action)
|
85
87
|
if hasattr(action, "register_teardown") and callable(action.register_teardown):
|
86
88
|
action.register_teardown(self.hooks)
|
87
89
|
|
90
|
+
def set_actions(self, actions: Sequence[BaseAction | Callable[..., Any]]) -> None:
|
91
|
+
"""Replaces the current action list with a new one."""
|
92
|
+
self.actions.clear()
|
93
|
+
for action in actions:
|
94
|
+
self.add_action(action)
|
95
|
+
|
88
96
|
def get_infer_target(self) -> tuple[Callable[..., Any] | None, dict[str, Any] | None]:
|
89
97
|
arg_defs = same_argument_definitions(self.actions)
|
90
98
|
if arg_defs:
|
@@ -1,9 +1,11 @@
|
|
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
|
4
6
|
|
5
7
|
|
6
|
-
class
|
8
|
+
class FileType(Enum):
|
7
9
|
"""Enum for file return types."""
|
8
10
|
|
9
11
|
TEXT = "text"
|
@@ -26,7 +28,7 @@ class FileReturnType(Enum):
|
|
26
28
|
return aliases.get(value, value)
|
27
29
|
|
28
30
|
@classmethod
|
29
|
-
def _missing_(cls, value: object) ->
|
31
|
+
def _missing_(cls, value: object) -> FileType:
|
30
32
|
if isinstance(value, str):
|
31
33
|
normalized = value.lower()
|
32
34
|
alias = cls._get_alias(normalized)
|
@@ -34,7 +36,7 @@ class FileReturnType(Enum):
|
|
34
36
|
if member.value == alias:
|
35
37
|
return member
|
36
38
|
valid = ", ".join(member.value for member in cls)
|
37
|
-
raise ValueError(f"Invalid
|
39
|
+
raise ValueError(f"Invalid FileType: '{value}'. Must be one of: {valid}")
|
38
40
|
|
39
41
|
|
40
42
|
class SelectionReturnType(Enum):
|
falyx/action/chained_action.py
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
2
|
+
"""chained_action.py"""
|
1
3
|
from __future__ import annotations
|
2
4
|
|
3
|
-
from typing import Any, Callable
|
5
|
+
from typing import Any, Callable, Sequence
|
4
6
|
|
5
7
|
from rich.tree import Tree
|
6
8
|
|
7
9
|
from falyx.action.action import Action
|
8
|
-
from falyx.action.
|
10
|
+
from falyx.action.base_action import BaseAction
|
9
11
|
from falyx.action.fallback_action import FallbackAction
|
10
12
|
from falyx.action.literal_input_action import LiteralInputAction
|
11
13
|
from falyx.action.mixins import ActionListMixin
|
@@ -45,7 +47,7 @@ class ChainedAction(BaseAction, ActionListMixin):
|
|
45
47
|
def __init__(
|
46
48
|
self,
|
47
49
|
name: str,
|
48
|
-
actions:
|
50
|
+
actions: Sequence[BaseAction | Callable[..., Any]] | None = None,
|
49
51
|
*,
|
50
52
|
hooks: HookManager | None = None,
|
51
53
|
inject_last_result: bool = False,
|
@@ -65,7 +67,7 @@ class ChainedAction(BaseAction, ActionListMixin):
|
|
65
67
|
if actions:
|
66
68
|
self.set_actions(actions)
|
67
69
|
|
68
|
-
def _wrap_if_needed(self, action: BaseAction | Any) -> BaseAction:
|
70
|
+
def _wrap_if_needed(self, action: BaseAction | Callable[..., Any]) -> BaseAction:
|
69
71
|
if isinstance(action, BaseAction):
|
70
72
|
return action
|
71
73
|
elif callable(action):
|
@@ -73,7 +75,7 @@ class ChainedAction(BaseAction, ActionListMixin):
|
|
73
75
|
else:
|
74
76
|
return LiteralInputAction(action)
|
75
77
|
|
76
|
-
def add_action(self, action: BaseAction | Any) -> None:
|
78
|
+
def add_action(self, action: BaseAction | Callable[..., Any]) -> None:
|
77
79
|
action = self._wrap_if_needed(action)
|
78
80
|
if self.actions and self.auto_inject and not action.inject_last_result:
|
79
81
|
action.inject_last_result = True
|
@@ -81,6 +83,12 @@ class ChainedAction(BaseAction, ActionListMixin):
|
|
81
83
|
if hasattr(action, "register_teardown") and callable(action.register_teardown):
|
82
84
|
action.register_teardown(self.hooks)
|
83
85
|
|
86
|
+
def set_actions(self, actions: Sequence[BaseAction | Callable[..., Any]]) -> None:
|
87
|
+
"""Replaces the current action list with a new one."""
|
88
|
+
self.actions.clear()
|
89
|
+
for action in actions:
|
90
|
+
self.add_action(action)
|
91
|
+
|
84
92
|
def get_infer_target(self) -> tuple[Callable[..., Any] | None, dict[str, Any] | None]:
|
85
93
|
if self.actions:
|
86
94
|
return self.actions[0].get_infer_target()
|
falyx/action/fallback_action.py
CHANGED
falyx/action/io_action.py
CHANGED
@@ -16,19 +16,15 @@ 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
|
|
24
22
|
from rich.tree import Tree
|
25
23
|
|
26
|
-
from falyx.action.
|
24
|
+
from falyx.action.base_action 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
|
-
)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
2
|
+
"""load_file_action.py"""
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
from rich.tree import Tree
|
6
|
+
|
7
|
+
from falyx.action.base_action import BaseAction
|
8
|
+
|
9
|
+
|
10
|
+
class LoadFileAction(BaseAction):
|
11
|
+
""" """
|
12
|
+
|
13
|
+
def __init__(self, name: str, file_path: str):
|
14
|
+
super().__init__(name=name)
|
15
|
+
self.file_path = file_path
|
16
|
+
|
17
|
+
def get_infer_target(self) -> tuple[None, None]:
|
18
|
+
return None, None
|
19
|
+
|
20
|
+
async def _run(self, *args, **kwargs):
|
21
|
+
raise NotImplementedError(
|
22
|
+
"LoadFileAction is not finished yet... Use primatives instead..."
|
23
|
+
)
|
24
|
+
|
25
|
+
async def preview(self, parent: Tree | None = None): ...
|
26
|
+
|
27
|
+
def __str__(self) -> str:
|
28
|
+
return f"LoadFileAction(file_path={self.file_path})"
|
falyx/action/menu_action.py
CHANGED
@@ -7,7 +7,7 @@ from rich.console import Console
|
|
7
7
|
from rich.table import Table
|
8
8
|
from rich.tree import Tree
|
9
9
|
|
10
|
-
from falyx.action.
|
10
|
+
from falyx.action.base_action import BaseAction
|
11
11
|
from falyx.context import ExecutionContext
|
12
12
|
from falyx.execution_registry import ExecutionRegistry as er
|
13
13
|
from falyx.hook_manager import HookType
|
falyx/action/mixins.py
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
2
|
+
"""mixins.py"""
|
3
|
+
from typing import Sequence
|
4
|
+
|
5
|
+
from falyx.action.base_action import BaseAction
|
2
6
|
|
3
7
|
|
4
8
|
class ActionListMixin:
|
@@ -7,7 +11,7 @@ class ActionListMixin:
|
|
7
11
|
def __init__(self) -> None:
|
8
12
|
self.actions: list[BaseAction] = []
|
9
13
|
|
10
|
-
def set_actions(self, actions:
|
14
|
+
def set_actions(self, actions: Sequence[BaseAction]) -> None:
|
11
15
|
"""Replaces the current action list with a new one."""
|
12
16
|
self.actions.clear()
|
13
17
|
for action in actions:
|
falyx/action/process_action.py
CHANGED
@@ -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
|
@@ -7,7 +9,7 @@ from typing import Any, Callable
|
|
7
9
|
|
8
10
|
from rich.tree import Tree
|
9
11
|
|
10
|
-
from falyx.action.
|
12
|
+
from falyx.action.base_action import BaseAction
|
11
13
|
from falyx.context import ExecutionContext
|
12
14
|
from falyx.execution_registry import ExecutionRegistry as er
|
13
15
|
from falyx.hook_manager import HookManager, HookType
|
@@ -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
|
@@ -9,7 +11,7 @@ from typing import Any, Callable
|
|
9
11
|
|
10
12
|
from rich.tree import Tree
|
11
13
|
|
12
|
-
from falyx.action.
|
14
|
+
from falyx.action.base_action import BaseAction
|
13
15
|
from falyx.context import ExecutionContext, SharedContext
|
14
16
|
from falyx.execution_registry import ExecutionRegistry as er
|
15
17
|
from falyx.hook_manager import HookManager, HookType
|
@@ -7,7 +7,7 @@ from prompt_toolkit.formatted_text import FormattedText, merge_formatted_text
|
|
7
7
|
from rich.console import Console
|
8
8
|
from rich.tree import Tree
|
9
9
|
|
10
|
-
from falyx.action.
|
10
|
+
from falyx.action.base_action import BaseAction
|
11
11
|
from falyx.context import ExecutionContext
|
12
12
|
from falyx.execution_registry import ExecutionRegistry as er
|
13
13
|
from falyx.hook_manager import HookType
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
2
|
+
"""save_file_action.py"""
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
from rich.tree import Tree
|
6
|
+
|
7
|
+
from falyx.action.base_action import BaseAction
|
8
|
+
|
9
|
+
|
10
|
+
class SaveFileAction(BaseAction):
|
11
|
+
""" """
|
12
|
+
|
13
|
+
def __init__(self, name: str, file_path: str):
|
14
|
+
super().__init__(name=name)
|
15
|
+
self.file_path = file_path
|
16
|
+
|
17
|
+
def get_infer_target(self) -> tuple[None, None]:
|
18
|
+
return None, None
|
19
|
+
|
20
|
+
async def _run(self, *args, **kwargs):
|
21
|
+
raise NotImplementedError(
|
22
|
+
"SaveFileAction is not finished yet... Use primitives instead..."
|
23
|
+
)
|
24
|
+
|
25
|
+
async def preview(self, parent: Tree | None = None): ...
|
26
|
+
|
27
|
+
def __str__(self) -> str:
|
28
|
+
return f"SaveFileAction(file_path={self.file_path})"
|
@@ -14,8 +14,8 @@ from prompt_toolkit import PromptSession
|
|
14
14
|
from rich.console import Console
|
15
15
|
from rich.tree import Tree
|
16
16
|
|
17
|
-
from falyx.action.
|
18
|
-
from falyx.action.
|
17
|
+
from falyx.action.action_types import FileType
|
18
|
+
from falyx.action.base_action import BaseAction
|
19
19
|
from falyx.context import ExecutionContext
|
20
20
|
from falyx.execution_registry import ExecutionRegistry as er
|
21
21
|
from falyx.hook_manager import HookType
|
@@ -50,7 +50,7 @@ class SelectFileAction(BaseAction):
|
|
50
50
|
prompt_message (str): Message to display when prompting for selection.
|
51
51
|
style (str): Style for the selection options.
|
52
52
|
suffix_filter (str | None): Restrict to certain file types.
|
53
|
-
return_type (
|
53
|
+
return_type (FileType): What to return (path, content, parsed).
|
54
54
|
console (Console | None): Console instance for output.
|
55
55
|
prompt_session (PromptSession | None): Prompt session for user input.
|
56
56
|
"""
|
@@ -65,7 +65,7 @@ class SelectFileAction(BaseAction):
|
|
65
65
|
prompt_message: str = "Choose > ",
|
66
66
|
style: str = OneColors.WHITE,
|
67
67
|
suffix_filter: str | None = None,
|
68
|
-
return_type:
|
68
|
+
return_type: FileType | str = FileType.PATH,
|
69
69
|
number_selections: int | str = 1,
|
70
70
|
separator: str = ",",
|
71
71
|
allow_duplicates: bool = False,
|
@@ -104,35 +104,35 @@ class SelectFileAction(BaseAction):
|
|
104
104
|
else:
|
105
105
|
raise ValueError("number_selections must be a positive integer or one of '*'")
|
106
106
|
|
107
|
-
def _coerce_return_type(self, return_type:
|
108
|
-
if isinstance(return_type,
|
107
|
+
def _coerce_return_type(self, return_type: FileType | str) -> FileType:
|
108
|
+
if isinstance(return_type, FileType):
|
109
109
|
return return_type
|
110
|
-
return
|
110
|
+
return FileType(return_type)
|
111
111
|
|
112
112
|
def get_options(self, files: list[Path]) -> dict[str, SelectionOption]:
|
113
113
|
value: Any
|
114
114
|
options = {}
|
115
115
|
for index, file in enumerate(files):
|
116
116
|
try:
|
117
|
-
if self.return_type ==
|
117
|
+
if self.return_type == FileType.TEXT:
|
118
118
|
value = file.read_text(encoding="UTF-8")
|
119
|
-
elif self.return_type ==
|
119
|
+
elif self.return_type == FileType.PATH:
|
120
120
|
value = file
|
121
|
-
elif self.return_type ==
|
121
|
+
elif self.return_type == FileType.JSON:
|
122
122
|
value = json.loads(file.read_text(encoding="UTF-8"))
|
123
|
-
elif self.return_type ==
|
123
|
+
elif self.return_type == FileType.TOML:
|
124
124
|
value = toml.loads(file.read_text(encoding="UTF-8"))
|
125
|
-
elif self.return_type ==
|
125
|
+
elif self.return_type == FileType.YAML:
|
126
126
|
value = yaml.safe_load(file.read_text(encoding="UTF-8"))
|
127
|
-
elif self.return_type ==
|
127
|
+
elif self.return_type == FileType.CSV:
|
128
128
|
with open(file, newline="", encoding="UTF-8") as csvfile:
|
129
129
|
reader = csv.reader(csvfile)
|
130
130
|
value = list(reader)
|
131
|
-
elif self.return_type ==
|
131
|
+
elif self.return_type == FileType.TSV:
|
132
132
|
with open(file, newline="", encoding="UTF-8") as tsvfile:
|
133
133
|
reader = csv.reader(tsvfile, delimiter="\t")
|
134
134
|
value = list(reader)
|
135
|
-
elif self.return_type ==
|
135
|
+
elif self.return_type == FileType.XML:
|
136
136
|
tree = ET.parse(file, parser=ET.XMLParser(encoding="UTF-8"))
|
137
137
|
root = tree.getroot()
|
138
138
|
value = ET.tostring(root, encoding="unicode")
|
falyx/action/selection_action.py
CHANGED
@@ -6,8 +6,8 @@ from prompt_toolkit import PromptSession
|
|
6
6
|
from rich.console import Console
|
7
7
|
from rich.tree import Tree
|
8
8
|
|
9
|
-
from falyx.action.
|
10
|
-
from falyx.action.
|
9
|
+
from falyx.action.action_types import SelectionReturnType
|
10
|
+
from falyx.action.base_action import BaseAction
|
11
11
|
from falyx.context import ExecutionContext
|
12
12
|
from falyx.execution_registry import ExecutionRegistry as er
|
13
13
|
from falyx.hook_manager import HookType
|
@@ -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,9 +1,11 @@
|
|
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
|
4
6
|
from rich.tree import Tree
|
5
7
|
|
6
|
-
from falyx.action.
|
8
|
+
from falyx.action.base_action import BaseAction
|
7
9
|
from falyx.context import ExecutionContext
|
8
10
|
from falyx.execution_registry import ExecutionRegistry as er
|
9
11
|
from falyx.hook_manager import HookType
|
falyx/command.py
CHANGED
@@ -27,14 +27,14 @@ from rich.console import Console
|
|
27
27
|
from rich.tree import Tree
|
28
28
|
|
29
29
|
from falyx.action.action import Action
|
30
|
-
from falyx.action.
|
30
|
+
from falyx.action.base_action import BaseAction
|
31
31
|
from falyx.context import ExecutionContext
|
32
32
|
from falyx.debug import register_debug_hooks
|
33
33
|
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.
|
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
|
falyx/config.py
CHANGED
@@ -14,7 +14,7 @@ from pydantic import BaseModel, Field, field_validator, model_validator
|
|
14
14
|
from rich.console import Console
|
15
15
|
|
16
16
|
from falyx.action.action import Action
|
17
|
-
from falyx.action.
|
17
|
+
from falyx.action.base_action import BaseAction
|
18
18
|
from falyx.command import Command
|
19
19
|
from falyx.falyx import Falyx
|
20
20
|
from falyx.logger import logger
|
falyx/falyx.py
CHANGED
@@ -43,7 +43,7 @@ from rich.markdown import Markdown
|
|
43
43
|
from rich.table import Table
|
44
44
|
|
45
45
|
from falyx.action.action import Action
|
46
|
-
from falyx.action.
|
46
|
+
from falyx.action.base_action import BaseAction
|
47
47
|
from falyx.bottom_bar import BottomBar
|
48
48
|
from falyx.command import Command
|
49
49
|
from falyx.context import ExecutionContext
|
@@ -346,7 +346,6 @@ class Falyx:
|
|
346
346
|
aliases=["HISTORY"],
|
347
347
|
action=Action(name="View Execution History", action=er.summary),
|
348
348
|
style=OneColors.DARK_YELLOW,
|
349
|
-
simple_help_signature=True,
|
350
349
|
arg_parser=parser,
|
351
350
|
help_text="View the execution history of commands.",
|
352
351
|
)
|
@@ -1152,7 +1151,7 @@ class Falyx:
|
|
1152
1151
|
sys.exit(0)
|
1153
1152
|
|
1154
1153
|
if self.cli_args.command == "version" or self.cli_args.version:
|
1155
|
-
self.console.print(f"[{self.version_style}]{self.program} v{
|
1154
|
+
self.console.print(f"[{self.version_style}]{self.program} v{self.version}[/]")
|
1156
1155
|
sys.exit(0)
|
1157
1156
|
|
1158
1157
|
if self.cli_args.command == "preview":
|
falyx/menu.py
CHANGED
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|
4
4
|
|
5
5
|
from prompt_toolkit.formatted_text import FormattedText
|
6
6
|
|
7
|
-
from falyx.action.
|
7
|
+
from falyx.action.base_action import BaseAction
|
8
8
|
from falyx.signals import BackSignal, QuitSignal
|
9
9
|
from falyx.themes import OneColors
|
10
10
|
from falyx.utils import CaseInsensitiveDict
|
falyx/parser/__init__.py
CHANGED
@@ -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 .
|
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__ = [
|
falyx/parser/argument.py
ADDED
@@ -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_action 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,134 +1,22 @@
|
|
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
|
10
9
|
from rich.markup import escape
|
11
10
|
from rich.text import Text
|
12
11
|
|
13
|
-
from falyx.action.
|
12
|
+
from falyx.action.base_action 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.
|
falyx/parser/parsers.py
CHANGED
@@ -76,14 +76,14 @@ def get_root_parser(
|
|
76
76
|
help="Run in non-interactive mode with all prompts bypassed.",
|
77
77
|
)
|
78
78
|
parser.add_argument(
|
79
|
-
"-v", "--verbose", action="store_true", help="Enable debug logging for
|
79
|
+
"-v", "--verbose", action="store_true", help=f"Enable debug logging for {prog}."
|
80
80
|
)
|
81
81
|
parser.add_argument(
|
82
82
|
"--debug-hooks",
|
83
83
|
action="store_true",
|
84
84
|
help="Enable default lifecycle debug logging",
|
85
85
|
)
|
86
|
-
parser.add_argument("--version", action="store_true", help="Show
|
86
|
+
parser.add_argument("--version", action="store_true", help=f"Show {prog} version")
|
87
87
|
return parser
|
88
88
|
|
89
89
|
|
@@ -98,7 +98,6 @@ def get_subparsers(
|
|
98
98
|
subparsers = parser.add_subparsers(
|
99
99
|
title=title,
|
100
100
|
description=description,
|
101
|
-
metavar="COMMAND",
|
102
101
|
dest="command",
|
103
102
|
)
|
104
103
|
return subparsers
|
@@ -124,6 +123,8 @@ def get_arg_parsers(
|
|
124
123
|
subparsers: _SubParsersAction | None = None,
|
125
124
|
) -> FalyxParsers:
|
126
125
|
"""Returns the argument parser for the CLI."""
|
126
|
+
if epilog is None:
|
127
|
+
epilog = f"Tip: Use '{prog} run ?[COMMAND]' to preview any command from the CLI."
|
127
128
|
if root_parser is None:
|
128
129
|
parser = get_root_parser(
|
129
130
|
prog=prog,
|
@@ -145,7 +146,14 @@ def get_arg_parsers(
|
|
145
146
|
parser = root_parser
|
146
147
|
|
147
148
|
if subparsers is None:
|
148
|
-
|
149
|
+
if prog == "falyx":
|
150
|
+
subparsers = get_subparsers(
|
151
|
+
parser,
|
152
|
+
title="Falyx Commands",
|
153
|
+
description="Available commands for the Falyx CLI.",
|
154
|
+
)
|
155
|
+
else:
|
156
|
+
subparsers = get_subparsers(parser, title="subcommands", description=None)
|
149
157
|
if not isinstance(subparsers, _SubParsersAction):
|
150
158
|
raise TypeError("subparsers must be an instance of _SubParsersAction")
|
151
159
|
|
@@ -154,10 +162,10 @@ def get_arg_parsers(
|
|
154
162
|
if isinstance(commands, dict):
|
155
163
|
for command in commands.values():
|
156
164
|
run_description.append(command.usage)
|
157
|
-
command_description = command.
|
165
|
+
command_description = command.help_text or command.description
|
158
166
|
run_description.append(f"{' '*24}{command_description}")
|
159
167
|
run_epilog = (
|
160
|
-
"Tip: Use '
|
168
|
+
f"Tip: Use '{prog} run ?[COMMAND]' to preview commands by their key or alias."
|
161
169
|
)
|
162
170
|
run_parser = subparsers.add_parser(
|
163
171
|
"run",
|
@@ -259,7 +267,7 @@ def get_arg_parsers(
|
|
259
267
|
"-t", "--tag", help="Filter commands by tag (case-insensitive)", default=None
|
260
268
|
)
|
261
269
|
|
262
|
-
version_parser = subparsers.add_parser("version", help="Show
|
270
|
+
version_parser = subparsers.add_parser("version", help=f"Show {prog} version")
|
263
271
|
|
264
272
|
return FalyxParsers(
|
265
273
|
root=parser,
|
falyx/parser/signature.py
CHANGED
falyx/parser/utils.py
CHANGED
@@ -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
|
@@ -5,7 +6,7 @@ from typing import Any, Literal, Union, get_args, get_origin
|
|
5
6
|
|
6
7
|
from dateutil import parser as date_parser
|
7
8
|
|
8
|
-
from falyx.action.
|
9
|
+
from falyx.action.base_action import BaseAction
|
9
10
|
from falyx.logger import logger
|
10
11
|
from falyx.parser.signature import infer_args_from_func
|
11
12
|
|
falyx/protocols.py
CHANGED
falyx/retry_utils.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
2
2
|
"""retry_utils.py"""
|
3
3
|
from falyx.action.action import Action
|
4
|
-
from falyx.action.
|
4
|
+
from falyx.action.base_action import BaseAction
|
5
5
|
from falyx.hook_manager import HookType
|
6
6
|
from falyx.retry import RetryHandler, RetryPolicy
|
7
7
|
|
falyx/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
1
|
+
__version__ = "0.1.53"
|
@@ -0,0 +1,66 @@
|
|
1
|
+
falyx/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
falyx/__init__.py,sha256=Gh88lQ5pbD7xbGWrBgslE2kSTZKY9TkvKSa53rZ3l8U,305
|
3
|
+
falyx/__main__.py,sha256=xHO4pB45rccixo-ougF84QJeB36ef8mEZXWVK_CJL9M,3420
|
4
|
+
falyx/action/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
falyx/action/__init__.py,sha256=i7rQJDJMPLONgiAgs3KU3Ay0T1rIk7ySF4JJqpzc6QM,1279
|
6
|
+
falyx/action/action.py,sha256=853-7CU9q6HmTEC7ha_snc-DM6P-AWv6vJVqmFSV54I,5760
|
7
|
+
falyx/action/action_factory.py,sha256=FKbqvEfZgxlxCXraWkBtRkUKA8wOVEmEreiiBWVNO6E,4839
|
8
|
+
falyx/action/action_group.py,sha256=daijfvLtE-YSGoS-jD8bqmkhIP_KzH3HrN3Q7pa5tkg,7239
|
9
|
+
falyx/action/action_types.py,sha256=oG8qIFObZdiU1VZQlnWzR8uBDq8rcxzkCli9A31_tMI,1460
|
10
|
+
falyx/action/base_action.py,sha256=-S-ODqetUFpcCYI9V5531-_XKXjgL1aYkOKO0rpfTS8,5873
|
11
|
+
falyx/action/chained_action.py,sha256=nFFzj-GsO6948Zo3hRIfk-4G8pm3hnK511F345QDC0g,8902
|
12
|
+
falyx/action/fallback_action.py,sha256=3FGWfoR1MIgY0ZkDNOpKu8p3JqPWzh5ON3943mfgDGs,1708
|
13
|
+
falyx/action/http_action.py,sha256=DNeSBWh58UTFGlfFyTk2GnhS54hpLAJLC0QNbq2cYic,5799
|
14
|
+
falyx/action/io_action.py,sha256=V888tQgAynqsVvkhICnEeE4wRs2vvdTcdlEpDSEbHqo,6128
|
15
|
+
falyx/action/literal_input_action.py,sha256=ShXXiUYKg01BMZRChlxEWlNcaLXV1B1LW-w5A8mOPvA,1387
|
16
|
+
falyx/action/load_file_action.py,sha256=kRFmYJI0QyBN00NX_89cI77kjYKRqCueKjru_uvXWNE,764
|
17
|
+
falyx/action/menu_action.py,sha256=ODghp2RcQhnSgVKhq3l8tH7pQ5HL5cT-Y0TuoWF8z84,6076
|
18
|
+
falyx/action/mixins.py,sha256=TDR8hPaBA_P_O9l03eW4IxN7Dj6Z08o97jYkXG9qt1A,1237
|
19
|
+
falyx/action/process_action.py,sha256=nUNcJD6Ms34vmj8njWzv1R1P9xJTyJmelnyJksHcp7M,4666
|
20
|
+
falyx/action/process_pool_action.py,sha256=i-RqgN82TndPkgFFvkh7FShIg6O9m7Etlh-4OMIXGTg,6039
|
21
|
+
falyx/action/prompt_menu_action.py,sha256=SYgSDURsyjeQbEm_lbJCEVjVGPSGN59-xtT_VDyJqVY,5293
|
22
|
+
falyx/action/save_file_action.py,sha256=Eveteiy-VejenFCZzoslQ5cIxm11IXVbEcgTI-cFbgg,764
|
23
|
+
falyx/action/select_file_action.py,sha256=5TB5YN6vcFtv4F2aUGP8akuexHdXHXwr1rq06cLHByY,9829
|
24
|
+
falyx/action/selection_action.py,sha256=f3m4sYpGVu6nwydtx76OMvp7HFMZqSEr_nL9lwaPd_A,15749
|
25
|
+
falyx/action/shell_action.py,sha256=0A_kvZLsYmeLHInMM_4Jpe8GCSnXzGBm7H9PnXPvbAs,4055
|
26
|
+
falyx/action/signal_action.py,sha256=5UMqvzy7fBnLANGwYUWoe1VRhrr7e-yOVeLdOnCBiJo,1350
|
27
|
+
falyx/action/user_input_action.py,sha256=DnIYy4Bwl3dPbNSiM4ojuv-Y3WDYgIF2cUE3YPu-xCE,3848
|
28
|
+
falyx/bottom_bar.py,sha256=KPACb9VC0I3dv_pYZLqy7e4uA_KT5dSfwnvuknyV0FI,7388
|
29
|
+
falyx/command.py,sha256=8kUz3QmPGxrMZ2e2xQG2aIUkzlcqZSLBQbiWyuiBBSA,16439
|
30
|
+
falyx/config.py,sha256=JkGLssxRYgSOFqGhLT19q4dqyLdLQj_NAtzZpcYSU2M,9665
|
31
|
+
falyx/context.py,sha256=b9PGkIfhc1BbFUmaqmr4AojzONfKG1c9WP2uixzCJGQ,10806
|
32
|
+
falyx/debug.py,sha256=pguI0XQcZ-7jte5YUPexAufa1oxxalYO1JgmO6GU3rI,1557
|
33
|
+
falyx/exceptions.py,sha256=kK9k1v7LVNjJSwYztRa9Krhr3ZOI-6Htq2ZjlYICPKg,922
|
34
|
+
falyx/execution_registry.py,sha256=7t_96-Q7R7MAJBvWwAt5IAERp0TjbGZPGeeJ1s24ey8,7628
|
35
|
+
falyx/falyx.py,sha256=IGNgkaSgrF0jydQQulJUl0hJZT56tmy2UCDmC9DSWB8,49773
|
36
|
+
falyx/hook_manager.py,sha256=TFuHQnAncS_rk6vuw-VSx8bnAppLuHfrZCrzLwqcO9o,2979
|
37
|
+
falyx/hooks.py,sha256=xMfQROib0BNsaQF4AXJpmCiGePoE1f1xpcdibgnVZWM,2913
|
38
|
+
falyx/init.py,sha256=F9jg7mLPoBWXdJnc_fyWG7zVQSnrAO8ueDiP8AJxDWE,3331
|
39
|
+
falyx/logger.py,sha256=1Mfb_vJFJ1tQwziuyU2p-cSMi2Js8N2byniFEnI6vOQ,132
|
40
|
+
falyx/menu.py,sha256=9kvLZhkC8PoSQvv1NZQsPIFSDy11dXfFgqVAuDmtfsM,3752
|
41
|
+
falyx/options_manager.py,sha256=dFAnQw543tQ6Xupvh1PwBrhiSWlSACHw8K-sHP_lUh4,2842
|
42
|
+
falyx/parser/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
43
|
+
falyx/parser/__init__.py,sha256=NbxAovKIY-duFTs6DAsdM_OzL7s3VIu19KMOmltX9ts,512
|
44
|
+
falyx/parser/argument.py,sha256=l1aNTkMEEgFdBu4wQALBpMlsJ3_8519S3ChAOpxJgxE,3369
|
45
|
+
falyx/parser/argument_action.py,sha256=rNVeth0eMpkZRU_eT1RPVxOGzD4pbdAMx9Kq07T4mG4,709
|
46
|
+
falyx/parser/command_argument_parser.py,sha256=tcyxRhMoBSwpjN-Z56-vglbRjPcA-BI4R8AEQvqoYsk,33899
|
47
|
+
falyx/parser/parsers.py,sha256=X3eEltxBbwRwWG5Q1A1GqSdQCJZAYN5Eub0_U6dlBN4,9159
|
48
|
+
falyx/parser/signature.py,sha256=fSltLEr8ctj1qpbU-OvTMnREjlb8OTG5t-guJFR7j4E,2529
|
49
|
+
falyx/parser/utils.py,sha256=gk-SHGPrVOrV-YCLc0bZlWRzXLw0goGUKA5fWZqT0iw,2963
|
50
|
+
falyx/prompt_utils.py,sha256=qgk0bXs7mwzflqzWyFhEOTpKQ_ZtMIqGhKeg-ocwNnE,1542
|
51
|
+
falyx/protocols.py,sha256=_e3wPDrn-6_20n8Uz80MnesEryDmkrMkPdYXUQO9YvA,500
|
52
|
+
falyx/retry.py,sha256=sGRE9QhdZK98M99G8F15WUsJ_fYLNyLlCgu3UANaSQs,3744
|
53
|
+
falyx/retry_utils.py,sha256=IqvEy_F0dXG8Yl2UoEJVLX-6OXk-dh-D72_SWv4w-p0,730
|
54
|
+
falyx/selection.py,sha256=TPSM_KKGHedJblWI0AzxTZR2haZjRF3k-gQoQeR3L28,15239
|
55
|
+
falyx/signals.py,sha256=Y_neFXpfHs7qY0syw9XcfR9WeAGRcRw1nG_2L1JJqKE,1083
|
56
|
+
falyx/tagged_table.py,sha256=4SV-SdXFrAhy1JNToeBCvyxT-iWVf6cWY7XETTys4n8,1067
|
57
|
+
falyx/themes/__init__.py,sha256=1CZhEUCin9cUk8IGYBUFkVvdHRNNJBEFXccHwpUKZCA,284
|
58
|
+
falyx/themes/colors.py,sha256=4aaeAHJetmeNInI0Zytg4E3YqKfPFelpf04vtjSvsS8,19776
|
59
|
+
falyx/utils.py,sha256=U45xnZFUdoFC4xiji_9S1jHS5V7MvxSDtufP8EgB0SM,6732
|
60
|
+
falyx/validators.py,sha256=Pbdxh5777Y03HxyArAh2ApeVSx23in4w4K38G43Vt98,5197
|
61
|
+
falyx/version.py,sha256=FdQ5_-vfyHXSNAuzQXtxxDH2WjNJ3g581mH_zvly6Xo,23
|
62
|
+
falyx-0.1.53.dist-info/LICENSE,sha256=B0yqgaHuSdhN7T3OBmgQSiDTy8HqT5Oe_dLypRe4Ra4,1073
|
63
|
+
falyx-0.1.53.dist-info/METADATA,sha256=P1gyGshvU_NnFWfQHvq1J8L_VatBhNl8vpv3qXwmkbY,5561
|
64
|
+
falyx-0.1.53.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
65
|
+
falyx-0.1.53.dist-info/entry_points.txt,sha256=j8owOSl2j1Ss8DtGMnKfgehKaolqnIPhVFHaUBLUnMs,45
|
66
|
+
falyx-0.1.53.dist-info/RECORD,,
|
falyx-0.1.51.dist-info/RECORD
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
falyx/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
falyx/__init__.py,sha256=Gh88lQ5pbD7xbGWrBgslE2kSTZKY9TkvKSa53rZ3l8U,305
|
3
|
-
falyx/__main__.py,sha256=xHO4pB45rccixo-ougF84QJeB36ef8mEZXWVK_CJL9M,3420
|
4
|
-
falyx/action/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
falyx/action/__init__.py,sha256=4E3Rb0GgGcmggrPJh0YFiwbVgN_PQjIzL06-Z3qMReo,1247
|
6
|
-
falyx/action/action.py,sha256=j9ANO0xRfWoiNK6z-46T04EKAJ7GhjVrzb8_U8afEAA,5753
|
7
|
-
falyx/action/action_factory.py,sha256=br-P7Oip-4tZkO8qVT_ECwLe6idYjJa_GuBi5QR7vS4,4832
|
8
|
-
falyx/action/action_group.py,sha256=_u64s81kgLAQFlTic3OJJoa8DwYJ2kqBh2U-_mpuPhs,6834
|
9
|
-
falyx/action/base.py,sha256=B7mt66oznmhv2qpSOwOuScgMckVXrxjRMU2buzZkRD8,5866
|
10
|
-
falyx/action/chained_action.py,sha256=aV_plUdDVdc1o-oU57anbWkw33jgRIh4W29QwEA_1Mw,8501
|
11
|
-
falyx/action/fallback_action.py,sha256=0z5l0s_LKnhIwgMdykm8lJqs246DKSpyYs-p7PnsKok,1619
|
12
|
-
falyx/action/http_action.py,sha256=DNeSBWh58UTFGlfFyTk2GnhS54hpLAJLC0QNbq2cYic,5799
|
13
|
-
falyx/action/io_action.py,sha256=9vx3mX9lBUBmwl1Xtzr5-idEwlMKlK1ZQuZkzTmORJc,9881
|
14
|
-
falyx/action/literal_input_action.py,sha256=7H2VX_L5VaytVdV2uis-VTGi782kQtwKTB8T04c7J1k,1293
|
15
|
-
falyx/action/menu_action.py,sha256=SLqwmQ1TOt8kl_cgIWogBYfx8lYPLZa4E-Yy6M2cX_w,6069
|
16
|
-
falyx/action/mixins.py,sha256=eni8_PwzMnuwh0ZqOdzCdAyWlOphoiqL7z27xnFsg5s,1117
|
17
|
-
falyx/action/process_action.py,sha256=HsDqlKy1PkG3HHC6mHa4O6ayY_oKVY2qj5nDRJuSn24,4571
|
18
|
-
falyx/action/process_pool_action.py,sha256=8RQSjJaU0nGY_ObpcO31uI-HfNY7krqMN2wSzTnJ8jw,5939
|
19
|
-
falyx/action/prompt_menu_action.py,sha256=corzjpPNVMYKncfueeRUWwklnlZHN-Fc61psOzbZELg,5286
|
20
|
-
falyx/action/select_file_action.py,sha256=2T4I1CLvHLAAqNUD2rFBIpdi74BP5amU4yTHUOGnd64,9911
|
21
|
-
falyx/action/selection_action.py,sha256=Mav39iTkVIJPDvmDek8R2bSF18f-mII56l5sSzZSPII,15735
|
22
|
-
falyx/action/signal_action.py,sha256=5UMqvzy7fBnLANGwYUWoe1VRhrr7e-yOVeLdOnCBiJo,1350
|
23
|
-
falyx/action/types.py,sha256=NfZz1ufZuvCgp-he2JIItbnjX7LjOUadjtKbjpRlSIY,1399
|
24
|
-
falyx/action/user_input_action.py,sha256=EnwSk-ZW0bqSlEnWpUE9_0jmoFCoTGMQc5PqP49cSyg,3750
|
25
|
-
falyx/bottom_bar.py,sha256=KPACb9VC0I3dv_pYZLqy7e4uA_KT5dSfwnvuknyV0FI,7388
|
26
|
-
falyx/command.py,sha256=7BM4JPK36dsWm2JPmRwarLJiDRk1GgfX5HESbeysuMY,16417
|
27
|
-
falyx/config.py,sha256=Cm1F9SfNSbugPALxaEz7NRqp1wrk-g2jYq35bQzN2uE,9658
|
28
|
-
falyx/context.py,sha256=b9PGkIfhc1BbFUmaqmr4AojzONfKG1c9WP2uixzCJGQ,10806
|
29
|
-
falyx/debug.py,sha256=pguI0XQcZ-7jte5YUPexAufa1oxxalYO1JgmO6GU3rI,1557
|
30
|
-
falyx/exceptions.py,sha256=kK9k1v7LVNjJSwYztRa9Krhr3ZOI-6Htq2ZjlYICPKg,922
|
31
|
-
falyx/execution_registry.py,sha256=7t_96-Q7R7MAJBvWwAt5IAERp0TjbGZPGeeJ1s24ey8,7628
|
32
|
-
falyx/falyx.py,sha256=zOAah7OYHZgMRI60zwXjNgfeEHs8y87SDs3Meslood0,49805
|
33
|
-
falyx/hook_manager.py,sha256=TFuHQnAncS_rk6vuw-VSx8bnAppLuHfrZCrzLwqcO9o,2979
|
34
|
-
falyx/hooks.py,sha256=xMfQROib0BNsaQF4AXJpmCiGePoE1f1xpcdibgnVZWM,2913
|
35
|
-
falyx/init.py,sha256=F9jg7mLPoBWXdJnc_fyWG7zVQSnrAO8ueDiP8AJxDWE,3331
|
36
|
-
falyx/logger.py,sha256=1Mfb_vJFJ1tQwziuyU2p-cSMi2Js8N2byniFEnI6vOQ,132
|
37
|
-
falyx/menu.py,sha256=E580qZsx08bnWcqRVjJuD2Fy8Zh_1zIexp5f0lC7L2c,3745
|
38
|
-
falyx/options_manager.py,sha256=dFAnQw543tQ6Xupvh1PwBrhiSWlSACHw8K-sHP_lUh4,2842
|
39
|
-
falyx/parser/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
-
falyx/parser/__init__.py,sha256=ZfPmbtEUechDvgl99-lWhTXmFnXS_FMXJ_xb8KGEJLo,448
|
41
|
-
falyx/parser/argparse.py,sha256=izJDlhHxyP1y0-NjiuLcNpHYMIO6zS4nY_-v-Xg-O94,37503
|
42
|
-
falyx/parser/parsers.py,sha256=MXWC8OQ3apDaeKfY0O4J8NnkxofWVOCRnKatC00lGm0,8796
|
43
|
-
falyx/parser/signature.py,sha256=cCa-yKUcbbET0Ho45oFZWWHFGCX5a_LaAOWRP7b87po,2465
|
44
|
-
falyx/parser/utils.py,sha256=VX4C58pJdHQihkaLIrYmcwqHJrFjbNjb5blEU3IqSAE,2892
|
45
|
-
falyx/prompt_utils.py,sha256=qgk0bXs7mwzflqzWyFhEOTpKQ_ZtMIqGhKeg-ocwNnE,1542
|
46
|
-
falyx/protocols.py,sha256=-9GbCBUzzsEgw2_KOCYqxxzWJuez0eHmwnZp_ShY0jc,493
|
47
|
-
falyx/retry.py,sha256=sGRE9QhdZK98M99G8F15WUsJ_fYLNyLlCgu3UANaSQs,3744
|
48
|
-
falyx/retry_utils.py,sha256=vwoZmFVCGVqZ13BX_xi3qZZVsmSxkp-jfaf6kJtBV9c,723
|
49
|
-
falyx/selection.py,sha256=TPSM_KKGHedJblWI0AzxTZR2haZjRF3k-gQoQeR3L28,15239
|
50
|
-
falyx/signals.py,sha256=Y_neFXpfHs7qY0syw9XcfR9WeAGRcRw1nG_2L1JJqKE,1083
|
51
|
-
falyx/tagged_table.py,sha256=4SV-SdXFrAhy1JNToeBCvyxT-iWVf6cWY7XETTys4n8,1067
|
52
|
-
falyx/themes/__init__.py,sha256=1CZhEUCin9cUk8IGYBUFkVvdHRNNJBEFXccHwpUKZCA,284
|
53
|
-
falyx/themes/colors.py,sha256=4aaeAHJetmeNInI0Zytg4E3YqKfPFelpf04vtjSvsS8,19776
|
54
|
-
falyx/utils.py,sha256=U45xnZFUdoFC4xiji_9S1jHS5V7MvxSDtufP8EgB0SM,6732
|
55
|
-
falyx/validators.py,sha256=Pbdxh5777Y03HxyArAh2ApeVSx23in4w4K38G43Vt98,5197
|
56
|
-
falyx/version.py,sha256=J5DqQAfJrmJsVwP_F1QEYvB7YXVaXR6y-BTblSWQs-k,23
|
57
|
-
falyx-0.1.51.dist-info/LICENSE,sha256=B0yqgaHuSdhN7T3OBmgQSiDTy8HqT5Oe_dLypRe4Ra4,1073
|
58
|
-
falyx-0.1.51.dist-info/METADATA,sha256=1pOPjynlq0g3vfgJaV2VqedsHH4O-3Egjo1WEengCns,5561
|
59
|
-
falyx-0.1.51.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
60
|
-
falyx-0.1.51.dist-info/entry_points.txt,sha256=j8owOSl2j1Ss8DtGMnKfgehKaolqnIPhVFHaUBLUnMs,45
|
61
|
-
falyx-0.1.51.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|