falyx 0.1.20__py3-none-any.whl → 0.1.22__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/__main__.py +2 -0
- falyx/action.py +33 -8
- falyx/bottom_bar.py +1 -1
- falyx/command.py +7 -1
- falyx/config.py +28 -3
- falyx/execution_registry.py +1 -1
- falyx/falyx.py +35 -10
- falyx/io_action.py +3 -52
- falyx/menu_action.py +3 -1
- falyx/parsers.py +13 -1
- falyx/select_file_action.py +195 -0
- falyx/selection.py +69 -59
- falyx/selection_action.py +4 -3
- falyx/version.py +1 -1
- {falyx-0.1.20.dist-info → falyx-0.1.22.dist-info}/METADATA +1 -1
- {falyx-0.1.20.dist-info → falyx-0.1.22.dist-info}/RECORD +19 -19
- falyx/select_files_action.py +0 -68
- {falyx-0.1.20.dist-info → falyx-0.1.22.dist-info}/LICENSE +0 -0
- {falyx-0.1.20.dist-info → falyx-0.1.22.dist-info}/WHEEL +0 -0
- {falyx-0.1.20.dist-info → falyx-0.1.22.dist-info}/entry_points.txt +0 -0
falyx/__main__.py
CHANGED
@@ -14,6 +14,7 @@ from typing import Any
|
|
14
14
|
from falyx.config import loader
|
15
15
|
from falyx.falyx import Falyx
|
16
16
|
from falyx.parsers import FalyxParsers, get_arg_parsers
|
17
|
+
from falyx.themes.colors import OneColors
|
17
18
|
|
18
19
|
|
19
20
|
def find_falyx_config() -> Path | None:
|
@@ -71,6 +72,7 @@ def run(args: Namespace) -> Any:
|
|
71
72
|
title="🛠️ Config-Driven CLI",
|
72
73
|
cli_args=args,
|
73
74
|
columns=4,
|
75
|
+
prompt=[(OneColors.BLUE_b, "FALYX > ")],
|
74
76
|
)
|
75
77
|
flx.add_commands(loader(bootstrap_path))
|
76
78
|
return asyncio.run(flx.run())
|
falyx/action.py
CHANGED
@@ -64,6 +64,7 @@ class BaseAction(ABC):
|
|
64
64
|
def __init__(
|
65
65
|
self,
|
66
66
|
name: str,
|
67
|
+
*,
|
67
68
|
hooks: HookManager | None = None,
|
68
69
|
inject_last_result: bool = False,
|
69
70
|
inject_into: str = "last_result",
|
@@ -182,6 +183,7 @@ class Action(BaseAction):
|
|
182
183
|
self,
|
183
184
|
name: str,
|
184
185
|
action: Callable[..., Any],
|
186
|
+
*,
|
185
187
|
rollback: Callable[..., Any] | None = None,
|
186
188
|
args: tuple[Any, ...] = (),
|
187
189
|
kwargs: dict[str, Any] | None = None,
|
@@ -191,7 +193,12 @@ class Action(BaseAction):
|
|
191
193
|
retry: bool = False,
|
192
194
|
retry_policy: RetryPolicy | None = None,
|
193
195
|
) -> None:
|
194
|
-
super().__init__(
|
196
|
+
super().__init__(
|
197
|
+
name,
|
198
|
+
hooks=hooks,
|
199
|
+
inject_last_result=inject_last_result,
|
200
|
+
inject_into=inject_into,
|
201
|
+
)
|
195
202
|
self.action = action
|
196
203
|
self.rollback = rollback
|
197
204
|
self.args = args
|
@@ -422,13 +429,19 @@ class ChainedAction(BaseAction, ActionListMixin):
|
|
422
429
|
self,
|
423
430
|
name: str,
|
424
431
|
actions: list[BaseAction | Any] | None = None,
|
432
|
+
*,
|
425
433
|
hooks: HookManager | None = None,
|
426
434
|
inject_last_result: bool = False,
|
427
435
|
inject_into: str = "last_result",
|
428
436
|
auto_inject: bool = False,
|
429
437
|
return_list: bool = False,
|
430
438
|
) -> None:
|
431
|
-
super().__init__(
|
439
|
+
super().__init__(
|
440
|
+
name,
|
441
|
+
hooks=hooks,
|
442
|
+
inject_last_result=inject_last_result,
|
443
|
+
inject_into=inject_into,
|
444
|
+
)
|
432
445
|
ActionListMixin.__init__(self)
|
433
446
|
self.auto_inject = auto_inject
|
434
447
|
self.return_list = return_list
|
@@ -608,11 +621,17 @@ class ActionGroup(BaseAction, ActionListMixin):
|
|
608
621
|
self,
|
609
622
|
name: str,
|
610
623
|
actions: list[BaseAction] | None = None,
|
624
|
+
*,
|
611
625
|
hooks: HookManager | None = None,
|
612
626
|
inject_last_result: bool = False,
|
613
627
|
inject_into: str = "last_result",
|
614
628
|
):
|
615
|
-
super().__init__(
|
629
|
+
super().__init__(
|
630
|
+
name,
|
631
|
+
hooks=hooks,
|
632
|
+
inject_last_result=inject_last_result,
|
633
|
+
inject_into=inject_into,
|
634
|
+
)
|
616
635
|
ActionListMixin.__init__(self)
|
617
636
|
if actions:
|
618
637
|
self.set_actions(actions)
|
@@ -730,7 +749,8 @@ class ProcessAction(BaseAction):
|
|
730
749
|
def __init__(
|
731
750
|
self,
|
732
751
|
name: str,
|
733
|
-
|
752
|
+
action: Callable[..., Any],
|
753
|
+
*,
|
734
754
|
args: tuple = (),
|
735
755
|
kwargs: dict[str, Any] | None = None,
|
736
756
|
hooks: HookManager | None = None,
|
@@ -738,8 +758,13 @@ class ProcessAction(BaseAction):
|
|
738
758
|
inject_last_result: bool = False,
|
739
759
|
inject_into: str = "last_result",
|
740
760
|
):
|
741
|
-
super().__init__(
|
742
|
-
|
761
|
+
super().__init__(
|
762
|
+
name,
|
763
|
+
hooks=hooks,
|
764
|
+
inject_last_result=inject_last_result,
|
765
|
+
inject_into=inject_into,
|
766
|
+
)
|
767
|
+
self.action = action
|
743
768
|
self.args = args
|
744
769
|
self.kwargs = kwargs or {}
|
745
770
|
self.executor = executor or ProcessPoolExecutor()
|
@@ -767,7 +792,7 @@ class ProcessAction(BaseAction):
|
|
767
792
|
try:
|
768
793
|
await self.hooks.trigger(HookType.BEFORE, context)
|
769
794
|
result = await loop.run_in_executor(
|
770
|
-
self.executor, partial(self.
|
795
|
+
self.executor, partial(self.action, *combined_args, **combined_kwargs)
|
771
796
|
)
|
772
797
|
context.result = result
|
773
798
|
await self.hooks.trigger(HookType.ON_SUCCESS, context)
|
@@ -806,6 +831,6 @@ class ProcessAction(BaseAction):
|
|
806
831
|
|
807
832
|
def __str__(self) -> str:
|
808
833
|
return (
|
809
|
-
f"ProcessAction(name={self.name!r},
|
834
|
+
f"ProcessAction(name={self.name!r}, action={getattr(self.action, '__name__', repr(self.action))}, "
|
810
835
|
f"args={self.args!r}, kwargs={self.kwargs!r})"
|
811
836
|
)
|
falyx/bottom_bar.py
CHANGED
@@ -30,7 +30,7 @@ class BottomBar:
|
|
30
30
|
key_validator: Callable[[str], bool] | None = None,
|
31
31
|
) -> None:
|
32
32
|
self.columns = columns
|
33
|
-
self.console = Console()
|
33
|
+
self.console = Console(color_system="auto")
|
34
34
|
self._named_items: dict[str, Callable[[], HTML]] = {}
|
35
35
|
self._value_getters: dict[str, Callable[[], Any]] = CaseInsensitiveDict()
|
36
36
|
self.toggle_keys: list[str] = []
|
falyx/command.py
CHANGED
@@ -40,7 +40,7 @@ from falyx.retry_utils import enable_retries_recursively
|
|
40
40
|
from falyx.themes.colors import OneColors
|
41
41
|
from falyx.utils import _noop, confirm_async, ensure_async, logger
|
42
42
|
|
43
|
-
console = Console()
|
43
|
+
console = Console(color_system="auto")
|
44
44
|
|
45
45
|
|
46
46
|
class Command(BaseModel):
|
@@ -272,15 +272,21 @@ class Command(BaseModel):
|
|
272
272
|
if hasattr(self.action, "preview") and callable(self.action.preview):
|
273
273
|
tree = Tree(label)
|
274
274
|
await self.action.preview(parent=tree)
|
275
|
+
if self.help_text:
|
276
|
+
tree.add(f"[dim]💡 {self.help_text}[/dim]")
|
275
277
|
console.print(tree)
|
276
278
|
elif callable(self.action) and not isinstance(self.action, BaseAction):
|
277
279
|
console.print(f"{label}")
|
280
|
+
if self.help_text:
|
281
|
+
console.print(f"[dim]💡 {self.help_text}[/dim]")
|
278
282
|
console.print(
|
279
283
|
f"[{OneColors.LIGHT_RED_b}]→ Would call:[/] {self.action.__name__}"
|
280
284
|
f"[dim](args={self.args}, kwargs={self.kwargs})[/dim]"
|
281
285
|
)
|
282
286
|
else:
|
283
287
|
console.print(f"{label}")
|
288
|
+
if self.help_text:
|
289
|
+
console.print(f"[dim]💡 {self.help_text}[/dim]")
|
284
290
|
console.print(
|
285
291
|
f"[{OneColors.DARK_RED}]⚠️ Action is not callable or lacks a preview method.[/]"
|
286
292
|
)
|
falyx/config.py
CHANGED
@@ -3,15 +3,21 @@
|
|
3
3
|
Configuration loader for Falyx CLI commands."""
|
4
4
|
|
5
5
|
import importlib
|
6
|
+
import sys
|
6
7
|
from pathlib import Path
|
7
8
|
from typing import Any
|
8
9
|
|
9
10
|
import toml
|
10
11
|
import yaml
|
12
|
+
from rich.console import Console
|
11
13
|
|
12
14
|
from falyx.action import Action, BaseAction
|
13
15
|
from falyx.command import Command
|
14
16
|
from falyx.retry import RetryPolicy
|
17
|
+
from falyx.themes.colors import OneColors
|
18
|
+
from falyx.utils import logger
|
19
|
+
|
20
|
+
console = Console(color_system="auto")
|
15
21
|
|
16
22
|
|
17
23
|
def wrap_if_needed(obj: Any, name=None) -> BaseAction | Command:
|
@@ -30,9 +36,28 @@ def import_action(dotted_path: str) -> Any:
|
|
30
36
|
"""Dynamically imports a callable from a dotted path like 'my.module.func'."""
|
31
37
|
module_path, _, attr = dotted_path.rpartition(".")
|
32
38
|
if not module_path:
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
console.print(f"[{OneColors.DARK_RED}]❌ Invalid action path:[/] {dotted_path}")
|
40
|
+
sys.exit(1)
|
41
|
+
try:
|
42
|
+
module = importlib.import_module(module_path)
|
43
|
+
except ModuleNotFoundError as error:
|
44
|
+
logger.error("Failed to import module '%s': %s", module_path, error)
|
45
|
+
console.print(
|
46
|
+
f"[{OneColors.DARK_RED}]❌ Could not import '{dotted_path}': {error}[/]\n"
|
47
|
+
f"[{OneColors.COMMENT_GREY}]Ensure the module is installed and discoverable via PYTHONPATH."
|
48
|
+
)
|
49
|
+
sys.exit(1)
|
50
|
+
try:
|
51
|
+
action = getattr(module, attr)
|
52
|
+
except AttributeError as error:
|
53
|
+
logger.error(
|
54
|
+
"Module '%s' does not have attribute '%s': %s", module_path, attr, error
|
55
|
+
)
|
56
|
+
console.print(
|
57
|
+
f"[{OneColors.DARK_RED}]❌ Module '{module_path}' has no attribute '{attr}': {error}[/]"
|
58
|
+
)
|
59
|
+
sys.exit(1)
|
60
|
+
return action
|
36
61
|
|
37
62
|
|
38
63
|
def loader(file_path: Path | str) -> list[dict[str, Any]]:
|
falyx/execution_registry.py
CHANGED
@@ -15,7 +15,7 @@ from falyx.utils import logger
|
|
15
15
|
class ExecutionRegistry:
|
16
16
|
_store_by_name: Dict[str, List[ExecutionContext]] = defaultdict(list)
|
17
17
|
_store_all: List[ExecutionContext] = []
|
18
|
-
_console = Console(color_system="
|
18
|
+
_console = Console(color_system="auto")
|
19
19
|
|
20
20
|
@classmethod
|
21
21
|
def record(cls, context: ExecutionContext):
|
falyx/falyx.py
CHANGED
@@ -110,12 +110,12 @@ class Falyx:
|
|
110
110
|
register_all_hooks(): Register hooks across all commands and submenus.
|
111
111
|
debug_hooks(): Log hook registration for debugging.
|
112
112
|
build_default_table(): Construct the standard Rich table layout.
|
113
|
-
|
114
113
|
"""
|
115
114
|
|
116
115
|
def __init__(
|
117
116
|
self,
|
118
117
|
title: str | Markdown = "Menu",
|
118
|
+
*,
|
119
119
|
prompt: str | AnyFormattedText = "> ",
|
120
120
|
columns: int = 3,
|
121
121
|
bottom_bar: BottomBar | str | Callable[[], Any] | None = None,
|
@@ -143,7 +143,7 @@ class Falyx:
|
|
143
143
|
self.help_command: Command | None = (
|
144
144
|
self._get_help_command() if include_help_command else None
|
145
145
|
)
|
146
|
-
self.console: Console = Console(color_system="
|
146
|
+
self.console: Console = Console(color_system="auto", theme=get_nord_theme())
|
147
147
|
self.welcome_message: str | Markdown | dict[str, Any] = welcome_message
|
148
148
|
self.exit_message: str | Markdown | dict[str, Any] = exit_message
|
149
149
|
self.hooks: HookManager = HookManager()
|
@@ -283,7 +283,7 @@ class Falyx:
|
|
283
283
|
self.console.print(table, justify="center")
|
284
284
|
if self.mode == FalyxMode.MENU:
|
285
285
|
self.console.print(
|
286
|
-
f"📦 Tip:
|
286
|
+
f"📦 Tip: '[{OneColors.LIGHT_YELLOW}]?[KEY][/]' to preview a command before running it.\n",
|
287
287
|
justify="center",
|
288
288
|
)
|
289
289
|
|
@@ -343,7 +343,9 @@ class Falyx:
|
|
343
343
|
error_message = " ".join(message_lines)
|
344
344
|
|
345
345
|
def validator(text):
|
346
|
-
|
346
|
+
is_preview, choice = self.get_command(text, from_validate=True)
|
347
|
+
if is_preview and choice is None:
|
348
|
+
return True
|
347
349
|
return True if choice else False
|
348
350
|
|
349
351
|
return Validator.from_callable(
|
@@ -549,7 +551,7 @@ class Falyx:
|
|
549
551
|
)
|
550
552
|
|
551
553
|
def add_submenu(
|
552
|
-
self, key: str, description: str, submenu: "Falyx", style: str = OneColors.CYAN
|
554
|
+
self, key: str, description: str, submenu: "Falyx", *, style: str = OneColors.CYAN
|
553
555
|
) -> None:
|
554
556
|
"""Adds a submenu to the menu."""
|
555
557
|
if not isinstance(submenu, Falyx):
|
@@ -568,6 +570,7 @@ class Falyx:
|
|
568
570
|
key: str,
|
569
571
|
description: str,
|
570
572
|
action: BaseAction | Callable[[], Any],
|
573
|
+
*,
|
571
574
|
args: tuple = (),
|
572
575
|
kwargs: dict[str, Any] = {},
|
573
576
|
hidden: bool = False,
|
@@ -693,6 +696,13 @@ class Falyx:
|
|
693
696
|
) -> tuple[bool, Command | None]:
|
694
697
|
"""Returns the selected command based on user input. Supports keys, aliases, and abbreviations."""
|
695
698
|
is_preview, choice = self.parse_preview_command(choice)
|
699
|
+
if is_preview and not choice:
|
700
|
+
if not from_validate:
|
701
|
+
self.console.print(
|
702
|
+
f"[{OneColors.DARK_RED}]❌ You must enter a command for preview mode.[/]"
|
703
|
+
)
|
704
|
+
return is_preview, None
|
705
|
+
|
696
706
|
choice = choice.upper()
|
697
707
|
name_map = self._name_map
|
698
708
|
|
@@ -787,12 +797,17 @@ class Falyx:
|
|
787
797
|
async def run_key(self, command_key: str, return_context: bool = False) -> Any:
|
788
798
|
"""Run a command by key without displaying the menu (non-interactive mode)."""
|
789
799
|
self.debug_hooks()
|
790
|
-
|
800
|
+
is_preview, selected_command = self.get_command(command_key)
|
791
801
|
self.last_run_command = selected_command
|
792
802
|
|
793
803
|
if not selected_command:
|
794
804
|
return None
|
795
805
|
|
806
|
+
if is_preview:
|
807
|
+
logger.info(f"Preview command '{selected_command.key}' selected.")
|
808
|
+
await selected_command.preview()
|
809
|
+
return None
|
810
|
+
|
796
811
|
logger.info(
|
797
812
|
"[run_key] 🚀 Executing: %s — %s",
|
798
813
|
selected_command.key,
|
@@ -942,11 +957,14 @@ class Falyx:
|
|
942
957
|
|
943
958
|
if self.cli_args.command == "run":
|
944
959
|
self.mode = FalyxMode.RUN
|
945
|
-
|
960
|
+
is_preview, command = self.get_command(self.cli_args.name)
|
961
|
+
if is_preview:
|
962
|
+
if command is None:
|
963
|
+
sys.exit(1)
|
964
|
+
logger.info(f"Preview command '{command.key}' selected.")
|
965
|
+
await command.preview()
|
966
|
+
sys.exit(0)
|
946
967
|
if not command:
|
947
|
-
self.console.print(
|
948
|
-
f"[{OneColors.DARK_RED}]❌ Command '{self.cli_args.name}' not found.[/]"
|
949
|
-
)
|
950
968
|
sys.exit(1)
|
951
969
|
self._set_retry_policy(command)
|
952
970
|
try:
|
@@ -954,6 +972,9 @@ class Falyx:
|
|
954
972
|
except FalyxError as error:
|
955
973
|
self.console.print(f"[{OneColors.DARK_RED}]❌ Error: {error}[/]")
|
956
974
|
sys.exit(1)
|
975
|
+
|
976
|
+
if self.cli_args.summary:
|
977
|
+
er.summary()
|
957
978
|
sys.exit(0)
|
958
979
|
|
959
980
|
if self.cli_args.command == "run-all":
|
@@ -975,6 +996,10 @@ class Falyx:
|
|
975
996
|
for cmd in matching:
|
976
997
|
self._set_retry_policy(cmd)
|
977
998
|
await self.run_key(cmd.key)
|
999
|
+
|
1000
|
+
if self.cli_args.summary:
|
1001
|
+
er.summary()
|
1002
|
+
|
978
1003
|
sys.exit(0)
|
979
1004
|
|
980
1005
|
await self.menu()
|
falyx/io_action.py
CHANGED
@@ -20,7 +20,6 @@ import subprocess
|
|
20
20
|
import sys
|
21
21
|
from typing import Any
|
22
22
|
|
23
|
-
from rich.console import Console
|
24
23
|
from rich.tree import Tree
|
25
24
|
|
26
25
|
from falyx.action import BaseAction
|
@@ -31,8 +30,6 @@ from falyx.hook_manager import HookManager, HookType
|
|
31
30
|
from falyx.themes.colors import OneColors
|
32
31
|
from falyx.utils import logger
|
33
32
|
|
34
|
-
console = Console()
|
35
|
-
|
36
33
|
|
37
34
|
class BaseIOAction(BaseAction):
|
38
35
|
"""
|
@@ -62,6 +59,7 @@ class BaseIOAction(BaseAction):
|
|
62
59
|
def __init__(
|
63
60
|
self,
|
64
61
|
name: str,
|
62
|
+
*,
|
65
63
|
hooks: HookManager | None = None,
|
66
64
|
mode: str = "buffered",
|
67
65
|
logging_hooks: bool = True,
|
@@ -172,22 +170,7 @@ class BaseIOAction(BaseAction):
|
|
172
170
|
if parent:
|
173
171
|
parent.add("".join(label))
|
174
172
|
else:
|
175
|
-
console.print(Tree("".join(label)))
|
176
|
-
|
177
|
-
|
178
|
-
class UppercaseIO(BaseIOAction):
|
179
|
-
def from_input(self, raw: str | bytes) -> str:
|
180
|
-
if not isinstance(raw, (str, bytes)):
|
181
|
-
raise TypeError(
|
182
|
-
f"{self.name} expected str or bytes input, got {type(raw).__name__}"
|
183
|
-
)
|
184
|
-
return raw.strip() if isinstance(raw, str) else raw.decode("utf-8").strip()
|
185
|
-
|
186
|
-
async def _run(self, parsed_input: str, *args, **kwargs) -> str:
|
187
|
-
return parsed_input.upper()
|
188
|
-
|
189
|
-
def to_output(self, data: str) -> str:
|
190
|
-
return data + "\n"
|
173
|
+
self.console.print(Tree("".join(label)))
|
191
174
|
|
192
175
|
|
193
176
|
class ShellAction(BaseIOAction):
|
@@ -247,41 +230,9 @@ class ShellAction(BaseIOAction):
|
|
247
230
|
if parent:
|
248
231
|
parent.add("".join(label))
|
249
232
|
else:
|
250
|
-
console.print(Tree("".join(label)))
|
233
|
+
self.console.print(Tree("".join(label)))
|
251
234
|
|
252
235
|
def __str__(self):
|
253
236
|
return (
|
254
237
|
f"ShellAction(name={self.name!r}, command_template={self.command_template!r})"
|
255
238
|
)
|
256
|
-
|
257
|
-
|
258
|
-
class GrepAction(BaseIOAction):
|
259
|
-
def __init__(self, name: str, pattern: str, **kwargs):
|
260
|
-
super().__init__(name=name, **kwargs)
|
261
|
-
self.pattern = pattern
|
262
|
-
|
263
|
-
def from_input(self, raw: str | bytes) -> str:
|
264
|
-
if not isinstance(raw, (str, bytes)):
|
265
|
-
raise TypeError(
|
266
|
-
f"{self.name} expected str or bytes input, got {type(raw).__name__}"
|
267
|
-
)
|
268
|
-
return raw.strip() if isinstance(raw, str) else raw.decode("utf-8").strip()
|
269
|
-
|
270
|
-
async def _run(self, parsed_input: str) -> str:
|
271
|
-
command = ["grep", "-n", self.pattern]
|
272
|
-
process = subprocess.Popen(
|
273
|
-
command,
|
274
|
-
stdin=subprocess.PIPE,
|
275
|
-
stdout=subprocess.PIPE,
|
276
|
-
stderr=subprocess.PIPE,
|
277
|
-
text=True,
|
278
|
-
)
|
279
|
-
stdout, stderr = process.communicate(input=parsed_input)
|
280
|
-
if process.returncode == 1:
|
281
|
-
return ""
|
282
|
-
if process.returncode != 0:
|
283
|
-
raise RuntimeError(stderr.strip())
|
284
|
-
return stdout.strip()
|
285
|
-
|
286
|
-
def to_output(self, result: str) -> str:
|
287
|
-
return result
|
falyx/menu_action.py
CHANGED
@@ -45,7 +45,9 @@ class MenuOptionMap(CaseInsensitiveDict):
|
|
45
45
|
RESERVED_KEYS = {"Q", "B"}
|
46
46
|
|
47
47
|
def __init__(
|
48
|
-
self,
|
48
|
+
self,
|
49
|
+
options: dict[str, MenuOption] | None = None,
|
50
|
+
allow_reserved: bool = False,
|
49
51
|
):
|
50
52
|
super().__init__()
|
51
53
|
self.allow_reserved = allow_reserved
|
falyx/parsers.py
CHANGED
@@ -36,7 +36,9 @@ def get_arg_parsers(
|
|
36
36
|
prog: str | None = "falyx",
|
37
37
|
usage: str | None = None,
|
38
38
|
description: str | None = "Falyx CLI - Run structured async command workflows.",
|
39
|
-
epilog:
|
39
|
+
epilog: (
|
40
|
+
str | None
|
41
|
+
) = "Tip: Use 'falyx run ?[COMMAND]' to preview any command from the CLI.",
|
40
42
|
parents: Sequence[ArgumentParser] = [],
|
41
43
|
prefix_chars: str = "-",
|
42
44
|
fromfile_prefix_chars: str | None = None,
|
@@ -79,6 +81,11 @@ def get_arg_parsers(
|
|
79
81
|
|
80
82
|
run_parser = subparsers.add_parser("run", help="Run a specific command")
|
81
83
|
run_parser.add_argument("name", help="Key, alias, or description of the command")
|
84
|
+
run_parser.add_argument(
|
85
|
+
"--summary",
|
86
|
+
action="store_true",
|
87
|
+
help="Print an execution summary after command completes",
|
88
|
+
)
|
82
89
|
run_parser.add_argument(
|
83
90
|
"--retries", type=int, help="Number of retries on failure", default=0
|
84
91
|
)
|
@@ -111,6 +118,11 @@ def get_arg_parsers(
|
|
111
118
|
"run-all", help="Run all commands with a given tag"
|
112
119
|
)
|
113
120
|
run_all_parser.add_argument("-t", "--tag", required=True, help="Tag to match")
|
121
|
+
run_all_parser.add_argument(
|
122
|
+
"--summary",
|
123
|
+
action="store_true",
|
124
|
+
help="Print a summary after all tagged commands run",
|
125
|
+
)
|
114
126
|
run_all_parser.add_argument(
|
115
127
|
"--retries", type=int, help="Number of retries on failure", default=0
|
116
128
|
)
|
@@ -0,0 +1,195 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import csv
|
4
|
+
import json
|
5
|
+
import xml.etree.ElementTree as ET
|
6
|
+
from enum import Enum
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
import toml
|
11
|
+
import yaml
|
12
|
+
from prompt_toolkit import PromptSession
|
13
|
+
from rich.console import Console
|
14
|
+
from rich.tree import Tree
|
15
|
+
|
16
|
+
from falyx.action import BaseAction
|
17
|
+
from falyx.context import ExecutionContext
|
18
|
+
from falyx.execution_registry import ExecutionRegistry as er
|
19
|
+
from falyx.hook_manager import HookType
|
20
|
+
from falyx.selection import (
|
21
|
+
SelectionOption,
|
22
|
+
prompt_for_selection,
|
23
|
+
render_selection_dict_table,
|
24
|
+
)
|
25
|
+
from falyx.themes.colors import OneColors
|
26
|
+
from falyx.utils import logger
|
27
|
+
|
28
|
+
|
29
|
+
class FileReturnType(Enum):
|
30
|
+
TEXT = "text"
|
31
|
+
PATH = "path"
|
32
|
+
JSON = "json"
|
33
|
+
TOML = "toml"
|
34
|
+
YAML = "yaml"
|
35
|
+
CSV = "csv"
|
36
|
+
XML = "xml"
|
37
|
+
|
38
|
+
|
39
|
+
class SelectFileAction(BaseAction):
|
40
|
+
"""
|
41
|
+
SelectFileAction allows users to select a file from a directory and return:
|
42
|
+
- file content (as text, JSON, CSV, etc.)
|
43
|
+
- or the file path itself.
|
44
|
+
|
45
|
+
Supported formats: text, json, yaml, toml, csv, xml.
|
46
|
+
|
47
|
+
Useful for:
|
48
|
+
- dynamically loading config files
|
49
|
+
- interacting with user-selected data
|
50
|
+
- chaining file contents into workflows
|
51
|
+
|
52
|
+
Args:
|
53
|
+
name (str): Name of the action.
|
54
|
+
directory (Path | str): Where to search for files.
|
55
|
+
title (str): Title of the selection menu.
|
56
|
+
columns (int): Number of columns in the selection menu.
|
57
|
+
prompt_message (str): Message to display when prompting for selection.
|
58
|
+
style (str): Style for the selection options.
|
59
|
+
suffix_filter (str | None): Restrict to certain file types.
|
60
|
+
return_type (FileReturnType): What to return (path, content, parsed).
|
61
|
+
console (Console | None): Console instance for output.
|
62
|
+
prompt_session (PromptSession | None): Prompt session for user input.
|
63
|
+
"""
|
64
|
+
|
65
|
+
def __init__(
|
66
|
+
self,
|
67
|
+
name: str,
|
68
|
+
directory: Path | str = ".",
|
69
|
+
*,
|
70
|
+
title: str = "Select a file",
|
71
|
+
columns: int = 3,
|
72
|
+
prompt_message: str = "Choose > ",
|
73
|
+
style: str = OneColors.WHITE,
|
74
|
+
suffix_filter: str | None = None,
|
75
|
+
return_type: FileReturnType = FileReturnType.PATH,
|
76
|
+
console: Console | None = None,
|
77
|
+
prompt_session: PromptSession | None = None,
|
78
|
+
):
|
79
|
+
super().__init__(name)
|
80
|
+
self.directory = Path(directory).resolve()
|
81
|
+
self.title = title
|
82
|
+
self.columns = columns
|
83
|
+
self.prompt_message = prompt_message
|
84
|
+
self.suffix_filter = suffix_filter
|
85
|
+
self.style = style
|
86
|
+
self.return_type = return_type
|
87
|
+
self.console = console or Console(color_system="auto")
|
88
|
+
self.prompt_session = prompt_session or PromptSession()
|
89
|
+
|
90
|
+
def get_options(self, files: list[Path]) -> dict[str, SelectionOption]:
|
91
|
+
value: Any
|
92
|
+
options = {}
|
93
|
+
for index, file in enumerate(files):
|
94
|
+
try:
|
95
|
+
if self.return_type == FileReturnType.TEXT:
|
96
|
+
value = file.read_text(encoding="UTF-8")
|
97
|
+
elif self.return_type == FileReturnType.PATH:
|
98
|
+
value = file
|
99
|
+
elif self.return_type == FileReturnType.JSON:
|
100
|
+
value = json.loads(file.read_text(encoding="UTF-8"))
|
101
|
+
elif self.return_type == FileReturnType.TOML:
|
102
|
+
value = toml.loads(file.read_text(encoding="UTF-8"))
|
103
|
+
elif self.return_type == FileReturnType.YAML:
|
104
|
+
value = yaml.safe_load(file.read_text(encoding="UTF-8"))
|
105
|
+
elif self.return_type == FileReturnType.CSV:
|
106
|
+
with open(file, newline="", encoding="UTF-8") as csvfile:
|
107
|
+
reader = csv.reader(csvfile)
|
108
|
+
value = list(reader)
|
109
|
+
elif self.return_type == FileReturnType.XML:
|
110
|
+
tree = ET.parse(file, parser=ET.XMLParser(encoding="UTF-8"))
|
111
|
+
root = tree.getroot()
|
112
|
+
value = ET.tostring(root, encoding="unicode")
|
113
|
+
else:
|
114
|
+
raise ValueError(f"Unsupported return type: {self.return_type}")
|
115
|
+
|
116
|
+
options[str(index)] = SelectionOption(
|
117
|
+
description=file.name, value=value, style=self.style
|
118
|
+
)
|
119
|
+
except Exception as error:
|
120
|
+
logger.warning("[ERROR] Failed to parse %s: %s", file.name, error)
|
121
|
+
return options
|
122
|
+
|
123
|
+
async def _run(self, *args, **kwargs) -> Any:
|
124
|
+
context = ExecutionContext(name=self.name, args=args, kwargs=kwargs, action=self)
|
125
|
+
context.start_timer()
|
126
|
+
try:
|
127
|
+
await self.hooks.trigger(HookType.BEFORE, context)
|
128
|
+
|
129
|
+
files = [
|
130
|
+
f
|
131
|
+
for f in self.directory.iterdir()
|
132
|
+
if f.is_file()
|
133
|
+
and (self.suffix_filter is None or f.suffix == self.suffix_filter)
|
134
|
+
]
|
135
|
+
if not files:
|
136
|
+
raise FileNotFoundError("No files found in directory.")
|
137
|
+
|
138
|
+
options = self.get_options(files)
|
139
|
+
|
140
|
+
table = render_selection_dict_table(
|
141
|
+
title=self.title, selections=options, columns=self.columns
|
142
|
+
)
|
143
|
+
|
144
|
+
key = await prompt_for_selection(
|
145
|
+
options.keys(),
|
146
|
+
table,
|
147
|
+
console=self.console,
|
148
|
+
prompt_session=self.prompt_session,
|
149
|
+
prompt_message=self.prompt_message,
|
150
|
+
)
|
151
|
+
|
152
|
+
result = options[key].value
|
153
|
+
context.result = result
|
154
|
+
await self.hooks.trigger(HookType.ON_SUCCESS, context)
|
155
|
+
return result
|
156
|
+
except Exception as error:
|
157
|
+
context.exception = error
|
158
|
+
await self.hooks.trigger(HookType.ON_ERROR, context)
|
159
|
+
raise
|
160
|
+
finally:
|
161
|
+
context.stop_timer()
|
162
|
+
await self.hooks.trigger(HookType.AFTER, context)
|
163
|
+
await self.hooks.trigger(HookType.ON_TEARDOWN, context)
|
164
|
+
er.record(context)
|
165
|
+
|
166
|
+
async def preview(self, parent: Tree | None = None):
|
167
|
+
label = f"[{OneColors.GREEN}]📁 SelectFilesAction[/] '{self.name}'"
|
168
|
+
tree = parent.add(label) if parent else Tree(label)
|
169
|
+
|
170
|
+
tree.add(f"[dim]Directory:[/] {str(self.directory)}")
|
171
|
+
tree.add(f"[dim]Suffix filter:[/] {self.suffix_filter or 'None'}")
|
172
|
+
tree.add(f"[dim]Return type:[/] {self.return_type}")
|
173
|
+
tree.add(f"[dim]Prompt:[/] {self.prompt_message}")
|
174
|
+
tree.add(f"[dim]Columns:[/] {self.columns}")
|
175
|
+
try:
|
176
|
+
files = list(self.directory.iterdir())
|
177
|
+
if self.suffix_filter:
|
178
|
+
files = [f for f in files if f.suffix == self.suffix_filter]
|
179
|
+
sample = files[:10]
|
180
|
+
file_list = tree.add("[dim]Files:[/]")
|
181
|
+
for f in sample:
|
182
|
+
file_list.add(f"[dim]{f.name}[/]")
|
183
|
+
if len(files) > 10:
|
184
|
+
file_list.add(f"[dim]... ({len(files) - 10} more)[/]")
|
185
|
+
except Exception as error:
|
186
|
+
tree.add(f"[bold red]⚠️ Error scanning directory: {error}[/]")
|
187
|
+
|
188
|
+
if not parent:
|
189
|
+
self.console.print(tree)
|
190
|
+
|
191
|
+
def __str__(self) -> str:
|
192
|
+
return (
|
193
|
+
f"SelectFilesAction(name={self.name!r}, dir={str(self.directory)!r}, "
|
194
|
+
f"suffix_filter={self.suffix_filter!r}, return_type={self.return_type})"
|
195
|
+
)
|
falyx/selection.py
CHANGED
@@ -31,6 +31,7 @@ class SelectionOption:
|
|
31
31
|
|
32
32
|
def render_table_base(
|
33
33
|
title: str,
|
34
|
+
*,
|
34
35
|
caption: str = "",
|
35
36
|
columns: int = 4,
|
36
37
|
box_style: box.Box = box.SIMPLE,
|
@@ -71,6 +72,7 @@ def render_table_base(
|
|
71
72
|
def render_selection_grid(
|
72
73
|
title: str,
|
73
74
|
selections: Sequence[str],
|
75
|
+
*,
|
74
76
|
columns: int = 4,
|
75
77
|
caption: str = "",
|
76
78
|
box_style: box.Box = box.SIMPLE,
|
@@ -86,19 +88,19 @@ def render_selection_grid(
|
|
86
88
|
) -> Table:
|
87
89
|
"""Create a selection table with the given parameters."""
|
88
90
|
table = render_table_base(
|
89
|
-
title,
|
90
|
-
caption,
|
91
|
-
columns,
|
92
|
-
box_style,
|
93
|
-
show_lines,
|
94
|
-
show_header,
|
95
|
-
show_footer,
|
96
|
-
style,
|
97
|
-
header_style,
|
98
|
-
footer_style,
|
99
|
-
title_style,
|
100
|
-
caption_style,
|
101
|
-
highlight,
|
91
|
+
title=title,
|
92
|
+
caption=caption,
|
93
|
+
columns=columns,
|
94
|
+
box_style=box_style,
|
95
|
+
show_lines=show_lines,
|
96
|
+
show_header=show_header,
|
97
|
+
show_footer=show_footer,
|
98
|
+
style=style,
|
99
|
+
header_style=header_style,
|
100
|
+
footer_style=footer_style,
|
101
|
+
title_style=title_style,
|
102
|
+
caption_style=caption_style,
|
103
|
+
highlight=highlight,
|
102
104
|
)
|
103
105
|
|
104
106
|
for chunk in chunks(selections, columns):
|
@@ -110,6 +112,7 @@ def render_selection_grid(
|
|
110
112
|
def render_selection_indexed_table(
|
111
113
|
title: str,
|
112
114
|
selections: Sequence[str],
|
115
|
+
*,
|
113
116
|
columns: int = 4,
|
114
117
|
caption: str = "",
|
115
118
|
box_style: box.Box = box.SIMPLE,
|
@@ -126,19 +129,19 @@ def render_selection_indexed_table(
|
|
126
129
|
) -> Table:
|
127
130
|
"""Create a selection table with the given parameters."""
|
128
131
|
table = render_table_base(
|
129
|
-
title,
|
130
|
-
caption,
|
131
|
-
columns,
|
132
|
-
box_style,
|
133
|
-
show_lines,
|
134
|
-
show_header,
|
135
|
-
show_footer,
|
136
|
-
style,
|
137
|
-
header_style,
|
138
|
-
footer_style,
|
139
|
-
title_style,
|
140
|
-
caption_style,
|
141
|
-
highlight,
|
132
|
+
title=title,
|
133
|
+
caption=caption,
|
134
|
+
columns=columns,
|
135
|
+
box_style=box_style,
|
136
|
+
show_lines=show_lines,
|
137
|
+
show_header=show_header,
|
138
|
+
show_footer=show_footer,
|
139
|
+
style=style,
|
140
|
+
header_style=header_style,
|
141
|
+
footer_style=footer_style,
|
142
|
+
title_style=title_style,
|
143
|
+
caption_style=caption_style,
|
144
|
+
highlight=highlight,
|
142
145
|
)
|
143
146
|
|
144
147
|
for indexes, chunk in zip(
|
@@ -156,6 +159,7 @@ def render_selection_indexed_table(
|
|
156
159
|
def render_selection_dict_table(
|
157
160
|
title: str,
|
158
161
|
selections: dict[str, SelectionOption],
|
162
|
+
*,
|
159
163
|
columns: int = 2,
|
160
164
|
caption: str = "",
|
161
165
|
box_style: box.Box = box.SIMPLE,
|
@@ -171,19 +175,19 @@ def render_selection_dict_table(
|
|
171
175
|
) -> Table:
|
172
176
|
"""Create a selection table with the given parameters."""
|
173
177
|
table = render_table_base(
|
174
|
-
title,
|
175
|
-
caption,
|
176
|
-
columns,
|
177
|
-
box_style,
|
178
|
-
show_lines,
|
179
|
-
show_header,
|
180
|
-
show_footer,
|
181
|
-
style,
|
182
|
-
header_style,
|
183
|
-
footer_style,
|
184
|
-
title_style,
|
185
|
-
caption_style,
|
186
|
-
highlight,
|
178
|
+
title=title,
|
179
|
+
caption=caption,
|
180
|
+
columns=columns,
|
181
|
+
box_style=box_style,
|
182
|
+
show_lines=show_lines,
|
183
|
+
show_header=show_header,
|
184
|
+
show_footer=show_footer,
|
185
|
+
style=style,
|
186
|
+
header_style=header_style,
|
187
|
+
footer_style=footer_style,
|
188
|
+
title_style=title_style,
|
189
|
+
caption_style=caption_style,
|
190
|
+
highlight=highlight,
|
187
191
|
)
|
188
192
|
|
189
193
|
for chunk in chunks(selections.items(), columns):
|
@@ -200,6 +204,7 @@ def render_selection_dict_table(
|
|
200
204
|
async def prompt_for_index(
|
201
205
|
max_index: int,
|
202
206
|
table: Table,
|
207
|
+
*,
|
203
208
|
min_index: int = 0,
|
204
209
|
default_selection: str = "",
|
205
210
|
console: Console | None = None,
|
@@ -224,6 +229,7 @@ async def prompt_for_index(
|
|
224
229
|
async def prompt_for_selection(
|
225
230
|
keys: Sequence[str] | KeysView[str],
|
226
231
|
table: Table,
|
232
|
+
*,
|
227
233
|
default_selection: str = "",
|
228
234
|
console: Console | None = None,
|
229
235
|
prompt_session: PromptSession | None = None,
|
@@ -249,6 +255,7 @@ async def prompt_for_selection(
|
|
249
255
|
async def select_value_from_list(
|
250
256
|
title: str,
|
251
257
|
selections: Sequence[str],
|
258
|
+
*,
|
252
259
|
console: Console | None = None,
|
253
260
|
prompt_session: PromptSession | None = None,
|
254
261
|
prompt_message: str = "Select an option > ",
|
@@ -268,20 +275,20 @@ async def select_value_from_list(
|
|
268
275
|
):
|
269
276
|
"""Prompt for a selection. Return the selected item."""
|
270
277
|
table = render_selection_indexed_table(
|
271
|
-
title,
|
272
|
-
selections,
|
273
|
-
columns,
|
274
|
-
caption,
|
275
|
-
box_style,
|
276
|
-
show_lines,
|
277
|
-
show_header,
|
278
|
-
show_footer,
|
279
|
-
style,
|
280
|
-
header_style,
|
281
|
-
footer_style,
|
282
|
-
title_style,
|
283
|
-
caption_style,
|
284
|
-
highlight,
|
278
|
+
title=title,
|
279
|
+
selections=selections,
|
280
|
+
columns=columns,
|
281
|
+
caption=caption,
|
282
|
+
box_style=box_style,
|
283
|
+
show_lines=show_lines,
|
284
|
+
show_header=show_header,
|
285
|
+
show_footer=show_footer,
|
286
|
+
style=style,
|
287
|
+
header_style=header_style,
|
288
|
+
footer_style=footer_style,
|
289
|
+
title_style=title_style,
|
290
|
+
caption_style=caption_style,
|
291
|
+
highlight=highlight,
|
285
292
|
)
|
286
293
|
prompt_session = prompt_session or PromptSession()
|
287
294
|
console = console or Console(color_system="auto")
|
@@ -301,6 +308,7 @@ async def select_value_from_list(
|
|
301
308
|
async def select_key_from_dict(
|
302
309
|
selections: dict[str, SelectionOption],
|
303
310
|
table: Table,
|
311
|
+
*,
|
304
312
|
console: Console | None = None,
|
305
313
|
prompt_session: PromptSession | None = None,
|
306
314
|
prompt_message: str = "Select an option > ",
|
@@ -325,6 +333,7 @@ async def select_key_from_dict(
|
|
325
333
|
async def select_value_from_dict(
|
326
334
|
selections: dict[str, SelectionOption],
|
327
335
|
table: Table,
|
336
|
+
*,
|
328
337
|
console: Console | None = None,
|
329
338
|
prompt_session: PromptSession | None = None,
|
330
339
|
prompt_message: str = "Select an option > ",
|
@@ -351,6 +360,7 @@ async def select_value_from_dict(
|
|
351
360
|
async def get_selection_from_dict_menu(
|
352
361
|
title: str,
|
353
362
|
selections: dict[str, SelectionOption],
|
363
|
+
*,
|
354
364
|
console: Console | None = None,
|
355
365
|
prompt_session: PromptSession | None = None,
|
356
366
|
prompt_message: str = "Select an option > ",
|
@@ -363,10 +373,10 @@ async def get_selection_from_dict_menu(
|
|
363
373
|
)
|
364
374
|
|
365
375
|
return await select_value_from_dict(
|
366
|
-
selections,
|
367
|
-
table,
|
368
|
-
console,
|
369
|
-
prompt_session,
|
370
|
-
prompt_message,
|
371
|
-
default_selection,
|
376
|
+
selections=selections,
|
377
|
+
table=table,
|
378
|
+
console=console,
|
379
|
+
prompt_session=prompt_session,
|
380
|
+
prompt_message=prompt_message,
|
381
|
+
default_selection=default_selection,
|
372
382
|
)
|
falyx/selection_action.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
2
2
|
"""selection_action.py"""
|
3
|
-
from pathlib import Path
|
4
3
|
from typing import Any
|
5
4
|
|
6
5
|
from prompt_toolkit import PromptSession
|
@@ -117,7 +116,9 @@ class SelectionAction(BaseAction):
|
|
117
116
|
await self.hooks.trigger(HookType.BEFORE, context)
|
118
117
|
if isinstance(self.selections, list):
|
119
118
|
table = render_selection_indexed_table(
|
120
|
-
self.title,
|
119
|
+
title=self.title,
|
120
|
+
selections=self.selections,
|
121
|
+
columns=self.columns,
|
121
122
|
)
|
122
123
|
if not self.never_prompt:
|
123
124
|
index = await prompt_for_index(
|
@@ -134,7 +135,7 @@ class SelectionAction(BaseAction):
|
|
134
135
|
result = self.selections[int(index)]
|
135
136
|
elif isinstance(self.selections, dict):
|
136
137
|
table = render_selection_dict_table(
|
137
|
-
self.title, self.selections, self.columns
|
138
|
+
title=self.title, selections=self.selections, columns=self.columns
|
138
139
|
)
|
139
140
|
if not self.never_prompt:
|
140
141
|
key = await prompt_for_selection(
|
falyx/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
1
|
+
__version__ = "0.1.22"
|
@@ -1,40 +1,40 @@
|
|
1
1
|
falyx/.pytyped,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
falyx/__init__.py,sha256=dYRamQJlT1Zoy5Uu1uG4NCV05Xk98nN1LAQrSR1CT2A,643
|
3
|
-
falyx/__main__.py,sha256=
|
4
|
-
falyx/action.py,sha256=
|
3
|
+
falyx/__main__.py,sha256=559pd3P1iScvC-V__gfXUbEQTxeV7PXlRcQI9kpMQcg,2286
|
4
|
+
falyx/action.py,sha256=J-SG5zltYbqtdvTwBBUeEj4jp44DOKBR6G5rvmdkkTs,32147
|
5
5
|
falyx/action_factory.py,sha256=SMucCBuigKk3rlKXCEN69Sew4dVaBUxQqxyUUAHMZeo,3629
|
6
|
-
falyx/bottom_bar.py,sha256=
|
7
|
-
falyx/command.py,sha256=
|
8
|
-
falyx/config.py,sha256=
|
6
|
+
falyx/bottom_bar.py,sha256=NTen52Nfz32eWSBmJtEUuJO33u5sGQj-33IeudPVsqQ,7403
|
7
|
+
falyx/command.py,sha256=8J3xeHw3fYiqf05EUbXd--aEIJso5bzxb5JECGuISQk,12199
|
8
|
+
falyx/config.py,sha256=MnZeyti2TpeuOKrvXVaslIxYPHFyE4liREJsmUKlySg,5455
|
9
9
|
falyx/context.py,sha256=Dm7HV-eigU-aTv5ERah6Ow9fIRdrOsB1G6ETPIu42Gw,10070
|
10
10
|
falyx/debug.py,sha256=-jbTti29UC5zP9qQlWs3TbkOQR2f3zKSuNluh-r56wY,1551
|
11
11
|
falyx/exceptions.py,sha256=YVbhPp2BNvZoO_xqeGSRKHVQ2rdLOLf1HCjH4JTj9w8,776
|
12
|
-
falyx/execution_registry.py,sha256=
|
13
|
-
falyx/falyx.py,sha256=
|
12
|
+
falyx/execution_registry.py,sha256=IZvZr2hElzsYcMX3bAXg7sc7V2zi4RURn0OR7de2iCo,2864
|
13
|
+
falyx/falyx.py,sha256=Yj7dYpEiqnswVRWSJyqS45wlRTUKvABIQR8Bh2axA9I,40453
|
14
14
|
falyx/hook_manager.py,sha256=E9Vk4bdoUTeXPQ_BQEvY2Jt-jUAusc40LI8JDy3NLUw,2381
|
15
15
|
falyx/hooks.py,sha256=9zXk62DsJLJrmwTdyeNy5s-rVRvl8feuYRrfMmz6cVQ,2802
|
16
16
|
falyx/http_action.py,sha256=JfopEleXJ0goVHi0VCn983c22GrmJhobnPIP7sTRqzU,5796
|
17
17
|
falyx/init.py,sha256=jP4ZNw7ycDMKw4n1HDifxWSa0NYHaGLq7_LiFt85NpA,1832
|
18
|
-
falyx/io_action.py,sha256=
|
19
|
-
falyx/menu_action.py,sha256=
|
18
|
+
falyx/io_action.py,sha256=mBRX8rqQ11gkcUnJluZ87XT4QBA1oFkof6PaCtuK5o8,8997
|
19
|
+
falyx/menu_action.py,sha256=kagtnn3djDxUm_Cyynp0lj-sZ9D_FZn4IEBYnFYqB74,7986
|
20
20
|
falyx/options_manager.py,sha256=yYpn-moYN-bRYgMLccmi_de4mUzhTT7cv_bR2FFWZ8c,2798
|
21
|
-
falyx/parsers.py,sha256=
|
21
|
+
falyx/parsers.py,sha256=r2FZTN26PqrnEQG4hVPorzzTPQZihsb4ca23fQY4Lgo,5574
|
22
22
|
falyx/prompt_utils.py,sha256=JOg3p8Juv6ZdY1srfy_HlMNYfE-ajggDWLqNsjZq87I,560
|
23
23
|
falyx/protocols.py,sha256=yNtQEugq9poN-SbOJf5LL_j6HBWdglbTNghpyopLpTs,216
|
24
24
|
falyx/retry.py,sha256=GncBUiDDfDHUvLsWsWQw2Nq2XYL0TR0Fne3iXPzvQ48,3551
|
25
25
|
falyx/retry_utils.py,sha256=SN5apcsg71IG2-KylysqdJd-PkPBLoCVwsgrSTF9wrQ,666
|
26
|
-
falyx/
|
27
|
-
falyx/selection.py,sha256=
|
28
|
-
falyx/selection_action.py,sha256=
|
26
|
+
falyx/select_file_action.py,sha256=5Pt9ThIfwqd8ZJLEVDapevV6SF_AQEQFkk-i9Y1to_Q,7315
|
27
|
+
falyx/selection.py,sha256=aeJZPDwb5LR9e9iTjCYx4D5bh1mWyn3uBPA-M1AXdAw,10553
|
28
|
+
falyx/selection_action.py,sha256=MAKZeDwfCEE3hOoL1hpiMVlFUEDYV6X0oNCVGEcT_ZU,8324
|
29
29
|
falyx/signal_action.py,sha256=wfhW9miSUj9MUoc1WOyk4tU9CtYKAXusHxQdBPYLoyQ,829
|
30
30
|
falyx/signals.py,sha256=tlUbz3x6z3rYlUggan_Ntoy4bU5RbOd8UfR4cNcV6kQ,694
|
31
31
|
falyx/tagged_table.py,sha256=sn2kosRRpcpeMB8vKk47c9yjpffSz_9FXH_e6kw15mA,1019
|
32
32
|
falyx/themes/colors.py,sha256=4aaeAHJetmeNInI0Zytg4E3YqKfPFelpf04vtjSvsS8,19776
|
33
33
|
falyx/utils.py,sha256=b1GQ3ooz4Io3zPE7MsoDm7j42AioTG-ZcWH-N2TRpbI,7710
|
34
34
|
falyx/validators.py,sha256=NMxqCk8Fr8HQGVDYpg8B_JRk5SKR41E_G9gj1YfQnxg,1316
|
35
|
-
falyx/version.py,sha256=
|
36
|
-
falyx-0.1.
|
37
|
-
falyx-0.1.
|
38
|
-
falyx-0.1.
|
39
|
-
falyx-0.1.
|
40
|
-
falyx-0.1.
|
35
|
+
falyx/version.py,sha256=zmP2TRnzKPjZJ1eiBcT-cRInsji6FW-OVD3FafQFCc4,23
|
36
|
+
falyx-0.1.22.dist-info/LICENSE,sha256=B0yqgaHuSdhN7T3OBmgQSiDTy8HqT5Oe_dLypRe4Ra4,1073
|
37
|
+
falyx-0.1.22.dist-info/METADATA,sha256=jXADXacZ3y4WPCwGkMSg5aGsseu-G98twHd2YIK5Hzg,5484
|
38
|
+
falyx-0.1.22.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
39
|
+
falyx-0.1.22.dist-info/entry_points.txt,sha256=j8owOSl2j1Ss8DtGMnKfgehKaolqnIPhVFHaUBLUnMs,45
|
40
|
+
falyx-0.1.22.dist-info/RECORD,,
|
falyx/select_files_action.py
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
|
2
|
-
class SelectFilesAction(BaseAction):
|
3
|
-
def __init__(
|
4
|
-
self,
|
5
|
-
name: str,
|
6
|
-
directory: Path | str = ".",
|
7
|
-
title: str = "Select a file",
|
8
|
-
prompt_message: str = "Choose > ",
|
9
|
-
style: str = OneColors.WHITE,
|
10
|
-
suffix_filter: str | None = None,
|
11
|
-
return_path: bool = True,
|
12
|
-
console: Console | None = None,
|
13
|
-
prompt_session: PromptSession | None = None,
|
14
|
-
):
|
15
|
-
super().__init__(name)
|
16
|
-
self.directory = Path(directory).resolve()
|
17
|
-
self.title = title
|
18
|
-
self.prompt_message = prompt_message
|
19
|
-
self.suffix_filter = suffix_filter
|
20
|
-
self.style = style
|
21
|
-
self.return_path = return_path
|
22
|
-
self.console = console or Console()
|
23
|
-
self.prompt_session = prompt_session or PromptSession()
|
24
|
-
|
25
|
-
async def _run(self, *args, **kwargs) -> Any:
|
26
|
-
context = ExecutionContext(name=self.name, args=args, kwargs=kwargs, action=self)
|
27
|
-
context.start_timer()
|
28
|
-
try:
|
29
|
-
await self.hooks.trigger(HookType.BEFORE, context)
|
30
|
-
|
31
|
-
files = [
|
32
|
-
f
|
33
|
-
for f in self.directory.iterdir()
|
34
|
-
if f.is_file()
|
35
|
-
and (self.suffix_filter is None or f.suffix == self.suffix_filter)
|
36
|
-
]
|
37
|
-
if not files:
|
38
|
-
raise FileNotFoundError("No files found in directory.")
|
39
|
-
|
40
|
-
options = {
|
41
|
-
str(i): SelectionOption(
|
42
|
-
f.name, f if self.return_path else f.read_text(), self.style
|
43
|
-
)
|
44
|
-
for i, f in enumerate(files)
|
45
|
-
}
|
46
|
-
table = render_selection_dict_table(self.title, options)
|
47
|
-
|
48
|
-
key = await prompt_for_selection(
|
49
|
-
options.keys(),
|
50
|
-
table,
|
51
|
-
console=self.console,
|
52
|
-
prompt_session=self.prompt_session,
|
53
|
-
prompt_message=self.prompt_message,
|
54
|
-
)
|
55
|
-
|
56
|
-
result = options[key].value
|
57
|
-
context.result = result
|
58
|
-
await self.hooks.trigger(HookType.ON_SUCCESS, context)
|
59
|
-
return result
|
60
|
-
except Exception as error:
|
61
|
-
context.exception = error
|
62
|
-
await self.hooks.trigger(HookType.ON_ERROR, context)
|
63
|
-
raise
|
64
|
-
finally:
|
65
|
-
context.stop_timer()
|
66
|
-
await self.hooks.trigger(HookType.AFTER, context)
|
67
|
-
await self.hooks.trigger(HookType.ON_TEARDOWN, context)
|
68
|
-
er.record(context)
|
File without changes
|
File without changes
|
File without changes
|