falyx 0.1.20__tar.gz → 0.1.21__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {falyx-0.1.20 → falyx-0.1.21}/PKG-INFO +1 -1
- {falyx-0.1.20 → falyx-0.1.21}/falyx/action.py +33 -8
- {falyx-0.1.20 → falyx-0.1.21}/falyx/bottom_bar.py +1 -1
- {falyx-0.1.20 → falyx-0.1.21}/falyx/command.py +1 -1
- {falyx-0.1.20 → falyx-0.1.21}/falyx/execution_registry.py +1 -1
- {falyx-0.1.20 → falyx-0.1.21}/falyx/falyx.py +4 -3
- {falyx-0.1.20 → falyx-0.1.21}/falyx/io_action.py +2 -52
- {falyx-0.1.20 → falyx-0.1.21}/falyx/menu_action.py +3 -1
- falyx-0.1.21/falyx/select_file_action.py +193 -0
- falyx-0.1.21/falyx/version.py +1 -0
- {falyx-0.1.20 → falyx-0.1.21}/pyproject.toml +1 -1
- falyx-0.1.20/falyx/select_files_action.py +0 -68
- falyx-0.1.20/falyx/version.py +0 -1
- {falyx-0.1.20 → falyx-0.1.21}/LICENSE +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/README.md +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/.pytyped +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/__init__.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/__main__.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/action_factory.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/config.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/context.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/debug.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/exceptions.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/hook_manager.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/hooks.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/http_action.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/init.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/options_manager.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/parsers.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/prompt_utils.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/protocols.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/retry.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/retry_utils.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/selection.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/selection_action.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/signal_action.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/signals.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/tagged_table.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/themes/colors.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/utils.py +0 -0
- {falyx-0.1.20 → falyx-0.1.21}/falyx/validators.py +0 -0
@@ -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
|
)
|
@@ -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] = []
|
@@ -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):
|
@@ -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):
|
@@ -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()
|
@@ -549,7 +549,7 @@ class Falyx:
|
|
549
549
|
)
|
550
550
|
|
551
551
|
def add_submenu(
|
552
|
-
self, key: str, description: str, submenu: "Falyx", style: str = OneColors.CYAN
|
552
|
+
self, key: str, description: str, submenu: "Falyx", *, style: str = OneColors.CYAN
|
553
553
|
) -> None:
|
554
554
|
"""Adds a submenu to the menu."""
|
555
555
|
if not isinstance(submenu, Falyx):
|
@@ -568,6 +568,7 @@ class Falyx:
|
|
568
568
|
key: str,
|
569
569
|
description: str,
|
570
570
|
action: BaseAction | Callable[[], Any],
|
571
|
+
*,
|
571
572
|
args: tuple = (),
|
572
573
|
kwargs: dict[str, Any] = {},
|
573
574
|
hidden: bool = False,
|
@@ -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
|
"""
|
@@ -172,22 +169,7 @@ class BaseIOAction(BaseAction):
|
|
172
169
|
if parent:
|
173
170
|
parent.add("".join(label))
|
174
171
|
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"
|
172
|
+
self.console.print(Tree("".join(label)))
|
191
173
|
|
192
174
|
|
193
175
|
class ShellAction(BaseIOAction):
|
@@ -247,41 +229,9 @@ class ShellAction(BaseIOAction):
|
|
247
229
|
if parent:
|
248
230
|
parent.add("".join(label))
|
249
231
|
else:
|
250
|
-
console.print(Tree("".join(label)))
|
232
|
+
self.console.print(Tree("".join(label)))
|
251
233
|
|
252
234
|
def __str__(self):
|
253
235
|
return (
|
254
236
|
f"ShellAction(name={self.name!r}, command_template={self.command_template!r})"
|
255
237
|
)
|
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
|
@@ -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
|
@@ -0,0 +1,193 @@
|
|
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(self.title, options, self.columns)
|
141
|
+
|
142
|
+
key = await prompt_for_selection(
|
143
|
+
options.keys(),
|
144
|
+
table,
|
145
|
+
console=self.console,
|
146
|
+
prompt_session=self.prompt_session,
|
147
|
+
prompt_message=self.prompt_message,
|
148
|
+
)
|
149
|
+
|
150
|
+
result = options[key].value
|
151
|
+
context.result = result
|
152
|
+
await self.hooks.trigger(HookType.ON_SUCCESS, context)
|
153
|
+
return result
|
154
|
+
except Exception as error:
|
155
|
+
context.exception = error
|
156
|
+
await self.hooks.trigger(HookType.ON_ERROR, context)
|
157
|
+
raise
|
158
|
+
finally:
|
159
|
+
context.stop_timer()
|
160
|
+
await self.hooks.trigger(HookType.AFTER, context)
|
161
|
+
await self.hooks.trigger(HookType.ON_TEARDOWN, context)
|
162
|
+
er.record(context)
|
163
|
+
|
164
|
+
async def preview(self, parent: Tree | None = None):
|
165
|
+
label = f"[{OneColors.GREEN}]📁 SelectFilesAction[/] '{self.name}'"
|
166
|
+
tree = parent.add(label) if parent else Tree(label)
|
167
|
+
|
168
|
+
tree.add(f"[dim]Directory:[/] {str(self.directory)}")
|
169
|
+
tree.add(f"[dim]Suffix filter:[/] {self.suffix_filter or 'None'}")
|
170
|
+
tree.add(f"[dim]Return type:[/] {self.return_type}")
|
171
|
+
tree.add(f"[dim]Prompt:[/] {self.prompt_message}")
|
172
|
+
tree.add(f"[dim]Columns:[/] {self.columns}")
|
173
|
+
try:
|
174
|
+
files = list(self.directory.iterdir())
|
175
|
+
if self.suffix_filter:
|
176
|
+
files = [f for f in files if f.suffix == self.suffix_filter]
|
177
|
+
sample = files[:10]
|
178
|
+
file_list = tree.add("[dim]Files:[/]")
|
179
|
+
for f in sample:
|
180
|
+
file_list.add(f"[dim]{f.name}[/]")
|
181
|
+
if len(files) > 10:
|
182
|
+
file_list.add(f"[dim]... ({len(files) - 10} more)[/]")
|
183
|
+
except Exception as error:
|
184
|
+
tree.add(f"[bold red]⚠️ Error scanning directory: {error}[/]")
|
185
|
+
|
186
|
+
if not parent:
|
187
|
+
self.console.print(tree)
|
188
|
+
|
189
|
+
def __str__(self) -> str:
|
190
|
+
return (
|
191
|
+
f"SelectFilesAction(name={self.name!r}, dir={str(self.directory)!r}, "
|
192
|
+
f"suffix_filter={self.suffix_filter!r}, return_type={self.return_type})"
|
193
|
+
)
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.1.21"
|
@@ -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)
|
falyx-0.1.20/falyx/version.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = "0.1.20"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|