falyx 0.1.21__py3-none-any.whl → 0.1.23__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 +3 -6
- falyx/action_factory.py +1 -0
- falyx/command.py +6 -0
- falyx/config.py +150 -55
- falyx/config_schema.py +76 -0
- falyx/execution_registry.py +3 -2
- falyx/falyx.py +74 -32
- falyx/init.py +82 -22
- falyx/io_action.py +25 -11
- falyx/parsers.py +13 -1
- falyx/prompt_utils.py +1 -0
- falyx/protocols.py +1 -0
- falyx/select_file_action.py +39 -5
- falyx/selection.py +72 -62
- falyx/selection_action.py +6 -5
- falyx/signal_action.py +1 -0
- falyx/version.py +1 -1
- {falyx-0.1.21.dist-info → falyx-0.1.23.dist-info}/METADATA +1 -1
- falyx-0.1.23.dist-info/RECORD +41 -0
- falyx-0.1.21.dist-info/RECORD +0 -40
- {falyx-0.1.21.dist-info → falyx-0.1.23.dist-info}/LICENSE +0 -0
- {falyx-0.1.21.dist-info → falyx-0.1.23.dist-info}/WHEEL +0 -0
- {falyx-0.1.21.dist-info → falyx-0.1.23.dist-info}/entry_points.txt +0 -0
falyx/init.py
CHANGED
@@ -4,27 +4,85 @@ from pathlib import Path
|
|
4
4
|
from rich.console import Console
|
5
5
|
|
6
6
|
TEMPLATE_TASKS = """\
|
7
|
-
|
8
|
-
|
9
|
-
return "Build complete!"
|
7
|
+
# This file is used by falyx.yaml to define CLI actions.
|
8
|
+
# You can run: falyx run [key] or falyx list to see available commands.
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
import asyncio
|
11
|
+
import json
|
12
|
+
|
13
|
+
from falyx.action import Action, ChainedAction
|
14
|
+
from falyx.io_action import ShellAction
|
15
|
+
from falyx.selection_action import SelectionAction
|
16
|
+
|
17
|
+
|
18
|
+
post_ids = ["1", "2", "3", "4", "5"]
|
19
|
+
|
20
|
+
pick_post = SelectionAction(
|
21
|
+
name="Pick Post ID",
|
22
|
+
selections=post_ids,
|
23
|
+
title="Choose a Post ID",
|
24
|
+
prompt_message="Select a post > ",
|
25
|
+
)
|
26
|
+
|
27
|
+
fetch_post = ShellAction(
|
28
|
+
name="Fetch Post via curl",
|
29
|
+
command_template="curl https://jsonplaceholder.typicode.com/posts/{}",
|
30
|
+
)
|
31
|
+
|
32
|
+
async def get_post_title(last_result):
|
33
|
+
return json.loads(last_result).get("title", "No title found")
|
34
|
+
|
35
|
+
post_flow = ChainedAction(
|
36
|
+
name="Fetch and Parse Post",
|
37
|
+
actions=[pick_post, fetch_post, get_post_title],
|
38
|
+
auto_inject=True,
|
39
|
+
)
|
40
|
+
|
41
|
+
async def hello():
|
42
|
+
print("👋 Hello from Falyx!")
|
43
|
+
return "Hello Complete!"
|
44
|
+
|
45
|
+
async def some_work():
|
46
|
+
await asyncio.sleep(2)
|
47
|
+
print("Work Finished!")
|
48
|
+
return "Work Complete!"
|
49
|
+
|
50
|
+
work_action = Action(
|
51
|
+
name="Work Action",
|
52
|
+
action=some_work,
|
53
|
+
)
|
14
54
|
"""
|
15
55
|
|
16
56
|
TEMPLATE_CONFIG = """\
|
17
|
-
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
57
|
+
# falyx.yaml — Config-driven CLI definition
|
58
|
+
# Define your commands here and point to Python callables in tasks.py
|
59
|
+
title: Sample CLI Project
|
60
|
+
prompt:
|
61
|
+
- ["#61AFEF bold", "FALYX > "]
|
62
|
+
columns: 3
|
63
|
+
welcome_message: "🚀 Welcome to your new CLI project!"
|
64
|
+
exit_message: "👋 See you next time!"
|
65
|
+
commands:
|
66
|
+
- key: S
|
67
|
+
description: Say Hello
|
68
|
+
action: tasks.hello
|
69
|
+
aliases: [hi, hello]
|
70
|
+
tags: [example]
|
71
|
+
|
72
|
+
- key: P
|
73
|
+
description: Get Post Title
|
74
|
+
action: tasks.post_flow
|
75
|
+
aliases: [submit]
|
76
|
+
preview_before_confirm: true
|
77
|
+
confirm: true
|
78
|
+
tags: [demo, network]
|
79
|
+
|
80
|
+
- key: G
|
81
|
+
description: Do Some Work
|
82
|
+
action: tasks.work_action
|
83
|
+
aliases: [work]
|
84
|
+
spinner: true
|
85
|
+
spinner_message: "Working..."
|
28
86
|
"""
|
29
87
|
|
30
88
|
GLOBAL_TEMPLATE_TASKS = """\
|
@@ -33,10 +91,12 @@ async def cleanup():
|
|
33
91
|
"""
|
34
92
|
|
35
93
|
GLOBAL_CONFIG = """\
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
94
|
+
title: Global Falyx Config
|
95
|
+
commands:
|
96
|
+
- key: C
|
97
|
+
description: Cleanup temp files
|
98
|
+
action: tasks.cleanup
|
99
|
+
aliases: [clean, cleanup]
|
40
100
|
"""
|
41
101
|
|
42
102
|
console = Console(color_system="auto")
|
@@ -56,7 +116,7 @@ def init_project(name: str = ".") -> None:
|
|
56
116
|
tasks_path.write_text(TEMPLATE_TASKS)
|
57
117
|
config_path.write_text(TEMPLATE_CONFIG)
|
58
118
|
|
59
|
-
print(f"✅ Initialized Falyx project in {target}")
|
119
|
+
console.print(f"✅ Initialized Falyx project in {target}")
|
60
120
|
|
61
121
|
|
62
122
|
def init_global() -> None:
|
falyx/io_action.py
CHANGED
@@ -16,6 +16,7 @@ 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
|
19
20
|
import subprocess
|
20
21
|
import sys
|
21
22
|
from typing import Any
|
@@ -59,6 +60,7 @@ class BaseIOAction(BaseAction):
|
|
59
60
|
def __init__(
|
60
61
|
self,
|
61
62
|
name: str,
|
63
|
+
*,
|
62
64
|
hooks: HookManager | None = None,
|
63
65
|
mode: str = "buffered",
|
64
66
|
logging_hooks: bool = True,
|
@@ -182,13 +184,13 @@ class ShellAction(BaseIOAction):
|
|
182
184
|
|
183
185
|
Designed for quick integration with shell tools like `grep`, `ping`, `jq`, etc.
|
184
186
|
|
185
|
-
⚠️ Warning:
|
186
|
-
|
187
|
-
|
188
|
-
Avoid passing raw user input directly unless the template or use case is secure.
|
187
|
+
⚠️ Security Warning:
|
188
|
+
By default, ShellAction uses `shell=True`, which can be dangerous with unsanitized input.
|
189
|
+
To mitigate this, set `safe_mode=True` to use `shell=False` with `shlex.split()`.
|
189
190
|
|
190
191
|
Features:
|
191
192
|
- Automatically handles input parsing (str/bytes)
|
193
|
+
- `safe_mode=True` disables shell interpretation and runs with `shell=False`
|
192
194
|
- Captures stdout and stderr from shell execution
|
193
195
|
- Raises on non-zero exit codes with stderr as the error
|
194
196
|
- Result is returned as trimmed stdout string
|
@@ -198,11 +200,15 @@ class ShellAction(BaseIOAction):
|
|
198
200
|
name (str): Name of the action.
|
199
201
|
command_template (str): Shell command to execute. Must include `{}` to include input.
|
200
202
|
If no placeholder is present, the input is not included.
|
203
|
+
safe_mode (bool): If True, runs with `shell=False` using shlex parsing (default: False).
|
201
204
|
"""
|
202
205
|
|
203
|
-
def __init__(
|
206
|
+
def __init__(
|
207
|
+
self, name: str, command_template: str, safe_mode: bool = False, **kwargs
|
208
|
+
):
|
204
209
|
super().__init__(name=name, **kwargs)
|
205
210
|
self.command_template = command_template
|
211
|
+
self.safe_mode = safe_mode
|
206
212
|
|
207
213
|
def from_input(self, raw: str | bytes) -> str:
|
208
214
|
if not isinstance(raw, (str, bytes)):
|
@@ -214,7 +220,11 @@ class ShellAction(BaseIOAction):
|
|
214
220
|
async def _run(self, parsed_input: str) -> str:
|
215
221
|
# Replace placeholder in template, or use raw input as full command
|
216
222
|
command = self.command_template.format(parsed_input)
|
217
|
-
|
223
|
+
if self.safe_mode:
|
224
|
+
args = shlex.split(command)
|
225
|
+
result = subprocess.run(args, capture_output=True, text=True)
|
226
|
+
else:
|
227
|
+
result = subprocess.run(command, shell=True, text=True, capture_output=True)
|
218
228
|
if result.returncode != 0:
|
219
229
|
raise RuntimeError(result.stderr.strip())
|
220
230
|
return result.stdout.strip()
|
@@ -224,14 +234,18 @@ class ShellAction(BaseIOAction):
|
|
224
234
|
|
225
235
|
async def preview(self, parent: Tree | None = None):
|
226
236
|
label = [f"[{OneColors.GREEN_b}]⚙ ShellAction[/] '{self.name}'"]
|
237
|
+
label.append(f"\n[dim]Template:[/] {self.command_template}")
|
238
|
+
label.append(
|
239
|
+
f"\n[dim]Safe mode:[/] {'Enabled' if self.safe_mode else 'Disabled'}"
|
240
|
+
)
|
227
241
|
if self.inject_last_result:
|
228
242
|
label.append(f" [dim](injects '{self.inject_into}')[/dim]")
|
229
|
-
if parent
|
230
|
-
|
231
|
-
|
232
|
-
self.console.print(Tree("".join(label)))
|
243
|
+
tree = parent.add("".join(label)) if parent else Tree("".join(label))
|
244
|
+
if not parent:
|
245
|
+
self.console.print(tree)
|
233
246
|
|
234
247
|
def __str__(self):
|
235
248
|
return (
|
236
|
-
f"ShellAction(name={self.name!r}, command_template={self.command_template!r}
|
249
|
+
f"ShellAction(name={self.name!r}, command_template={self.command_template!r}, "
|
250
|
+
f"safe_mode={self.safe_mode})"
|
237
251
|
)
|
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
|
)
|
falyx/prompt_utils.py
CHANGED
falyx/protocols.py
CHANGED
falyx/select_file_action.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
1
2
|
from __future__ import annotations
|
2
3
|
|
3
4
|
import csv
|
@@ -33,8 +34,30 @@ class FileReturnType(Enum):
|
|
33
34
|
TOML = "toml"
|
34
35
|
YAML = "yaml"
|
35
36
|
CSV = "csv"
|
37
|
+
TSV = "tsv"
|
36
38
|
XML = "xml"
|
37
39
|
|
40
|
+
@classmethod
|
41
|
+
def _get_alias(cls, value: str) -> str:
|
42
|
+
aliases = {
|
43
|
+
"yml": "yaml",
|
44
|
+
"txt": "text",
|
45
|
+
"file": "path",
|
46
|
+
"filepath": "path",
|
47
|
+
}
|
48
|
+
return aliases.get(value, value)
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def _missing_(cls, value: object) -> FileReturnType:
|
52
|
+
if isinstance(value, str):
|
53
|
+
normalized = value.lower()
|
54
|
+
alias = cls._get_alias(normalized)
|
55
|
+
for member in cls:
|
56
|
+
if member.value == alias:
|
57
|
+
return member
|
58
|
+
valid = ", ".join(member.value for member in cls)
|
59
|
+
raise ValueError(f"Invalid FileReturnType: '{value}'. Must be one of: {valid}")
|
60
|
+
|
38
61
|
|
39
62
|
class SelectFileAction(BaseAction):
|
40
63
|
"""
|
@@ -42,7 +65,7 @@ class SelectFileAction(BaseAction):
|
|
42
65
|
- file content (as text, JSON, CSV, etc.)
|
43
66
|
- or the file path itself.
|
44
67
|
|
45
|
-
Supported formats: text, json, yaml, toml, csv, xml.
|
68
|
+
Supported formats: text, json, yaml, toml, csv, tsv, xml.
|
46
69
|
|
47
70
|
Useful for:
|
48
71
|
- dynamically loading config files
|
@@ -72,7 +95,7 @@ class SelectFileAction(BaseAction):
|
|
72
95
|
prompt_message: str = "Choose > ",
|
73
96
|
style: str = OneColors.WHITE,
|
74
97
|
suffix_filter: str | None = None,
|
75
|
-
return_type: FileReturnType = FileReturnType.PATH,
|
98
|
+
return_type: FileReturnType | str = FileReturnType.PATH,
|
76
99
|
console: Console | None = None,
|
77
100
|
prompt_session: PromptSession | None = None,
|
78
101
|
):
|
@@ -83,9 +106,14 @@ class SelectFileAction(BaseAction):
|
|
83
106
|
self.prompt_message = prompt_message
|
84
107
|
self.suffix_filter = suffix_filter
|
85
108
|
self.style = style
|
86
|
-
self.return_type = return_type
|
87
109
|
self.console = console or Console(color_system="auto")
|
88
110
|
self.prompt_session = prompt_session or PromptSession()
|
111
|
+
self.return_type = self._coerce_return_type(return_type)
|
112
|
+
|
113
|
+
def _coerce_return_type(self, return_type: FileReturnType | str) -> FileReturnType:
|
114
|
+
if isinstance(return_type, FileReturnType):
|
115
|
+
return return_type
|
116
|
+
return FileReturnType(return_type)
|
89
117
|
|
90
118
|
def get_options(self, files: list[Path]) -> dict[str, SelectionOption]:
|
91
119
|
value: Any
|
@@ -106,6 +134,10 @@ class SelectFileAction(BaseAction):
|
|
106
134
|
with open(file, newline="", encoding="UTF-8") as csvfile:
|
107
135
|
reader = csv.reader(csvfile)
|
108
136
|
value = list(reader)
|
137
|
+
elif self.return_type == FileReturnType.TSV:
|
138
|
+
with open(file, newline="", encoding="UTF-8") as tsvfile:
|
139
|
+
reader = csv.reader(tsvfile, delimiter="\t")
|
140
|
+
value = list(reader)
|
109
141
|
elif self.return_type == FileReturnType.XML:
|
110
142
|
tree = ET.parse(file, parser=ET.XMLParser(encoding="UTF-8"))
|
111
143
|
root = tree.getroot()
|
@@ -137,7 +169,9 @@ class SelectFileAction(BaseAction):
|
|
137
169
|
|
138
170
|
options = self.get_options(files)
|
139
171
|
|
140
|
-
table = render_selection_dict_table(
|
172
|
+
table = render_selection_dict_table(
|
173
|
+
title=self.title, selections=options, columns=self.columns
|
174
|
+
)
|
141
175
|
|
142
176
|
key = await prompt_for_selection(
|
143
177
|
options.keys(),
|
@@ -181,7 +215,7 @@ class SelectFileAction(BaseAction):
|
|
181
215
|
if len(files) > 10:
|
182
216
|
file_list.add(f"[dim]... ({len(files) - 10} more)[/]")
|
183
217
|
except Exception as error:
|
184
|
-
tree.add(f"[
|
218
|
+
tree.add(f"[{OneColors.DARK_RED_b}]⚠️ Error scanning directory: {error}[/]")
|
185
219
|
|
186
220
|
if not parent:
|
187
221
|
self.console.print(tree)
|
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,
|
@@ -211,7 +216,7 @@ async def prompt_for_index(
|
|
211
216
|
console = console or Console(color_system="auto")
|
212
217
|
|
213
218
|
if show_table:
|
214
|
-
console.print(table)
|
219
|
+
console.print(table, justify="center")
|
215
220
|
|
216
221
|
selection = await prompt_session.prompt_async(
|
217
222
|
message=prompt_message,
|
@@ -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 > ",
|
@@ -310,7 +318,7 @@ async def select_key_from_dict(
|
|
310
318
|
prompt_session = prompt_session or PromptSession()
|
311
319
|
console = console or Console(color_system="auto")
|
312
320
|
|
313
|
-
console.print(table)
|
321
|
+
console.print(table, justify="center")
|
314
322
|
|
315
323
|
return await prompt_for_selection(
|
316
324
|
selections.keys(),
|
@@ -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 > ",
|
@@ -334,7 +343,7 @@ async def select_value_from_dict(
|
|
334
343
|
prompt_session = prompt_session or PromptSession()
|
335
344
|
console = console or Console(color_system="auto")
|
336
345
|
|
337
|
-
console.print(table)
|
346
|
+
console.print(table, justify="center")
|
338
347
|
|
339
348
|
selection_key = await prompt_for_selection(
|
340
349
|
selections.keys(),
|
@@ -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
|
@@ -29,7 +28,7 @@ class SelectionAction(BaseAction):
|
|
29
28
|
selections: list[str] | set[str] | tuple[str, ...] | dict[str, SelectionOption],
|
30
29
|
*,
|
31
30
|
title: str = "Select an option",
|
32
|
-
columns: int =
|
31
|
+
columns: int = 5,
|
33
32
|
prompt_message: str = "Select > ",
|
34
33
|
default_selection: str = "",
|
35
34
|
inject_last_result: bool = False,
|
@@ -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(
|
@@ -185,7 +186,7 @@ class SelectionAction(BaseAction):
|
|
185
186
|
if len(self.selections) > 10:
|
186
187
|
sub.add(f"[dim]... ({len(self.selections) - 10} more)[/]")
|
187
188
|
else:
|
188
|
-
tree.add("[
|
189
|
+
tree.add(f"[{OneColors.DARK_RED_b}]Invalid selections type[/]")
|
189
190
|
return
|
190
191
|
|
191
192
|
tree.add(f"[dim]Default:[/] '{self.default_selection or self.last_result}'")
|
falyx/signal_action.py
CHANGED
falyx/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
1
|
+
__version__ = "0.1.23"
|