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.
Files changed (41) hide show
  1. {falyx-0.1.20 → falyx-0.1.21}/PKG-INFO +1 -1
  2. {falyx-0.1.20 → falyx-0.1.21}/falyx/action.py +33 -8
  3. {falyx-0.1.20 → falyx-0.1.21}/falyx/bottom_bar.py +1 -1
  4. {falyx-0.1.20 → falyx-0.1.21}/falyx/command.py +1 -1
  5. {falyx-0.1.20 → falyx-0.1.21}/falyx/execution_registry.py +1 -1
  6. {falyx-0.1.20 → falyx-0.1.21}/falyx/falyx.py +4 -3
  7. {falyx-0.1.20 → falyx-0.1.21}/falyx/io_action.py +2 -52
  8. {falyx-0.1.20 → falyx-0.1.21}/falyx/menu_action.py +3 -1
  9. falyx-0.1.21/falyx/select_file_action.py +193 -0
  10. falyx-0.1.21/falyx/version.py +1 -0
  11. {falyx-0.1.20 → falyx-0.1.21}/pyproject.toml +1 -1
  12. falyx-0.1.20/falyx/select_files_action.py +0 -68
  13. falyx-0.1.20/falyx/version.py +0 -1
  14. {falyx-0.1.20 → falyx-0.1.21}/LICENSE +0 -0
  15. {falyx-0.1.20 → falyx-0.1.21}/README.md +0 -0
  16. {falyx-0.1.20 → falyx-0.1.21}/falyx/.pytyped +0 -0
  17. {falyx-0.1.20 → falyx-0.1.21}/falyx/__init__.py +0 -0
  18. {falyx-0.1.20 → falyx-0.1.21}/falyx/__main__.py +0 -0
  19. {falyx-0.1.20 → falyx-0.1.21}/falyx/action_factory.py +0 -0
  20. {falyx-0.1.20 → falyx-0.1.21}/falyx/config.py +0 -0
  21. {falyx-0.1.20 → falyx-0.1.21}/falyx/context.py +0 -0
  22. {falyx-0.1.20 → falyx-0.1.21}/falyx/debug.py +0 -0
  23. {falyx-0.1.20 → falyx-0.1.21}/falyx/exceptions.py +0 -0
  24. {falyx-0.1.20 → falyx-0.1.21}/falyx/hook_manager.py +0 -0
  25. {falyx-0.1.20 → falyx-0.1.21}/falyx/hooks.py +0 -0
  26. {falyx-0.1.20 → falyx-0.1.21}/falyx/http_action.py +0 -0
  27. {falyx-0.1.20 → falyx-0.1.21}/falyx/init.py +0 -0
  28. {falyx-0.1.20 → falyx-0.1.21}/falyx/options_manager.py +0 -0
  29. {falyx-0.1.20 → falyx-0.1.21}/falyx/parsers.py +0 -0
  30. {falyx-0.1.20 → falyx-0.1.21}/falyx/prompt_utils.py +0 -0
  31. {falyx-0.1.20 → falyx-0.1.21}/falyx/protocols.py +0 -0
  32. {falyx-0.1.20 → falyx-0.1.21}/falyx/retry.py +0 -0
  33. {falyx-0.1.20 → falyx-0.1.21}/falyx/retry_utils.py +0 -0
  34. {falyx-0.1.20 → falyx-0.1.21}/falyx/selection.py +0 -0
  35. {falyx-0.1.20 → falyx-0.1.21}/falyx/selection_action.py +0 -0
  36. {falyx-0.1.20 → falyx-0.1.21}/falyx/signal_action.py +0 -0
  37. {falyx-0.1.20 → falyx-0.1.21}/falyx/signals.py +0 -0
  38. {falyx-0.1.20 → falyx-0.1.21}/falyx/tagged_table.py +0 -0
  39. {falyx-0.1.20 → falyx-0.1.21}/falyx/themes/colors.py +0 -0
  40. {falyx-0.1.20 → falyx-0.1.21}/falyx/utils.py +0 -0
  41. {falyx-0.1.20 → falyx-0.1.21}/falyx/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: falyx
3
- Version: 0.1.20
3
+ Version: 0.1.21
4
4
  Summary: Reliable and introspectable async CLI action framework.
5
5
  License: MIT
6
6
  Author: Roland Thomas Jr
@@ -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__(name, hooks, inject_last_result, inject_into)
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__(name, hooks, inject_last_result, inject_into)
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__(name, hooks, inject_last_result, inject_into)
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
- func: Callable[..., Any],
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__(name, hooks, inject_last_result, inject_into)
742
- self.func = func
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.func, *combined_args, **combined_kwargs)
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}, func={getattr(self.func, '__name__', repr(self.func))}, "
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="truecolor")
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="truecolor", theme=get_nord_theme())
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, options: dict[str, MenuOption] | None = None, allow_reserved: bool = False
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,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "falyx"
3
- version = "0.1.20"
3
+ version = "0.1.21"
4
4
  description = "Reliable and introspectable async CLI action framework."
5
5
  authors = ["Roland Thomas Jr <roland@rtj.dev>"]
6
6
  license = "MIT"
@@ -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)
@@ -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