falyx 0.1.56__py3-none-any.whl → 0.1.58__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/__init__.py +1 -0
- falyx/action/__init__.py +4 -0
- falyx/action/action_group.py +6 -0
- falyx/action/base_action.py +2 -1
- falyx/action/chained_action.py +7 -1
- falyx/action/confirm_action.py +217 -0
- falyx/action/load_file_action.py +7 -9
- falyx/action/menu_action.py +0 -7
- falyx/action/prompt_menu_action.py +0 -6
- falyx/action/save_file_action.py +214 -11
- falyx/action/select_file_action.py +0 -8
- falyx/action/selection_action.py +0 -8
- falyx/action/user_input_action.py +0 -7
- falyx/bottom_bar.py +2 -2
- falyx/command.py +1 -3
- falyx/config.py +1 -3
- falyx/console.py +5 -0
- falyx/context.py +3 -1
- falyx/execution_registry.py +1 -0
- falyx/falyx.py +5 -2
- falyx/init.py +1 -3
- falyx/parser/argument.py +29 -13
- falyx/parser/command_argument_parser.py +40 -20
- falyx/parser/utils.py +3 -2
- falyx/selection.py +1 -16
- falyx/validators.py +24 -0
- falyx/version.py +1 -1
- {falyx-0.1.56.dist-info → falyx-0.1.58.dist-info}/METADATA +1 -1
- {falyx-0.1.56.dist-info → falyx-0.1.58.dist-info}/RECORD +32 -31
- {falyx-0.1.56.dist-info → falyx-0.1.58.dist-info}/LICENSE +0 -0
- {falyx-0.1.56.dist-info → falyx-0.1.58.dist-info}/WHEEL +0 -0
- {falyx-0.1.56.dist-info → falyx-0.1.58.dist-info}/entry_points.txt +0 -0
@@ -11,7 +11,6 @@ from typing import Any
|
|
11
11
|
import toml
|
12
12
|
import yaml
|
13
13
|
from prompt_toolkit import PromptSession
|
14
|
-
from rich.console import Console
|
15
14
|
from rich.tree import Tree
|
16
15
|
|
17
16
|
from falyx.action.action_types import FileType
|
@@ -51,7 +50,6 @@ class SelectFileAction(BaseAction):
|
|
51
50
|
style (str): Style for the selection options.
|
52
51
|
suffix_filter (str | None): Restrict to certain file types.
|
53
52
|
return_type (FileType): What to return (path, content, parsed).
|
54
|
-
console (Console | None): Console instance for output.
|
55
53
|
prompt_session (PromptSession | None): Prompt session for user input.
|
56
54
|
"""
|
57
55
|
|
@@ -69,7 +67,6 @@ class SelectFileAction(BaseAction):
|
|
69
67
|
number_selections: int | str = 1,
|
70
68
|
separator: str = ",",
|
71
69
|
allow_duplicates: bool = False,
|
72
|
-
console: Console | None = None,
|
73
70
|
prompt_session: PromptSession | None = None,
|
74
71
|
):
|
75
72
|
super().__init__(name)
|
@@ -82,10 +79,6 @@ class SelectFileAction(BaseAction):
|
|
82
79
|
self.number_selections = number_selections
|
83
80
|
self.separator = separator
|
84
81
|
self.allow_duplicates = allow_duplicates
|
85
|
-
if isinstance(console, Console):
|
86
|
-
self.console = console
|
87
|
-
elif console:
|
88
|
-
raise ValueError("`console` must be an instance of `rich.console.Console`")
|
89
82
|
self.prompt_session = prompt_session or PromptSession()
|
90
83
|
self.return_type = self._coerce_return_type(return_type)
|
91
84
|
|
@@ -195,7 +188,6 @@ class SelectFileAction(BaseAction):
|
|
195
188
|
keys = await prompt_for_selection(
|
196
189
|
(options | cancel_option).keys(),
|
197
190
|
table,
|
198
|
-
console=self.console,
|
199
191
|
prompt_session=self.prompt_session,
|
200
192
|
prompt_message=self.prompt_message,
|
201
193
|
number_selections=self.number_selections,
|
falyx/action/selection_action.py
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
from typing import Any
|
4
4
|
|
5
5
|
from prompt_toolkit import PromptSession
|
6
|
-
from rich.console import Console
|
7
6
|
from rich.tree import Tree
|
8
7
|
|
9
8
|
from falyx.action.action_types import SelectionReturnType
|
@@ -54,7 +53,6 @@ class SelectionAction(BaseAction):
|
|
54
53
|
inject_last_result: bool = False,
|
55
54
|
inject_into: str = "last_result",
|
56
55
|
return_type: SelectionReturnType | str = "value",
|
57
|
-
console: Console | None = None,
|
58
56
|
prompt_session: PromptSession | None = None,
|
59
57
|
never_prompt: bool = False,
|
60
58
|
show_table: bool = True,
|
@@ -70,10 +68,6 @@ class SelectionAction(BaseAction):
|
|
70
68
|
self.return_type: SelectionReturnType = self._coerce_return_type(return_type)
|
71
69
|
self.title = title
|
72
70
|
self.columns = columns
|
73
|
-
if isinstance(console, Console):
|
74
|
-
self.console = console
|
75
|
-
elif console:
|
76
|
-
raise ValueError("`console` must be an instance of `rich.console.Console`")
|
77
71
|
self.prompt_session = prompt_session or PromptSession()
|
78
72
|
self.default_selection = default_selection
|
79
73
|
self.number_selections = number_selections
|
@@ -262,7 +256,6 @@ class SelectionAction(BaseAction):
|
|
262
256
|
len(self.selections),
|
263
257
|
table,
|
264
258
|
default_selection=effective_default,
|
265
|
-
console=self.console,
|
266
259
|
prompt_session=self.prompt_session,
|
267
260
|
prompt_message=self.prompt_message,
|
268
261
|
show_table=self.show_table,
|
@@ -306,7 +299,6 @@ class SelectionAction(BaseAction):
|
|
306
299
|
(self.selections | cancel_option).keys(),
|
307
300
|
table,
|
308
301
|
default_selection=effective_default,
|
309
|
-
console=self.console,
|
310
302
|
prompt_session=self.prompt_session,
|
311
303
|
prompt_message=self.prompt_message,
|
312
304
|
show_table=self.show_table,
|
@@ -2,7 +2,6 @@
|
|
2
2
|
"""user_input_action.py"""
|
3
3
|
from prompt_toolkit import PromptSession
|
4
4
|
from prompt_toolkit.validation import Validator
|
5
|
-
from rich.console import Console
|
6
5
|
from rich.tree import Tree
|
7
6
|
|
8
7
|
from falyx.action.base_action import BaseAction
|
@@ -20,7 +19,6 @@ class UserInputAction(BaseAction):
|
|
20
19
|
name (str): Action name.
|
21
20
|
prompt_text (str): Prompt text (can include '{last_result}' for interpolation).
|
22
21
|
validator (Validator, optional): Prompt Toolkit validator.
|
23
|
-
console (Console, optional): Rich console for rendering.
|
24
22
|
prompt_session (PromptSession, optional): Reusable prompt session.
|
25
23
|
inject_last_result (bool): Whether to inject last_result into prompt.
|
26
24
|
inject_into (str): Key to use for injection (default: 'last_result').
|
@@ -33,7 +31,6 @@ class UserInputAction(BaseAction):
|
|
33
31
|
prompt_text: str = "Input > ",
|
34
32
|
default_text: str = "",
|
35
33
|
validator: Validator | None = None,
|
36
|
-
console: Console | None = None,
|
37
34
|
prompt_session: PromptSession | None = None,
|
38
35
|
inject_last_result: bool = False,
|
39
36
|
):
|
@@ -43,10 +40,6 @@ class UserInputAction(BaseAction):
|
|
43
40
|
)
|
44
41
|
self.prompt_text = prompt_text
|
45
42
|
self.validator = validator
|
46
|
-
if isinstance(console, Console):
|
47
|
-
self.console = console
|
48
|
-
elif console:
|
49
|
-
raise ValueError("`console` must be an instance of `rich.console.Console`")
|
50
43
|
self.prompt_session = prompt_session or PromptSession()
|
51
44
|
self.default_text = default_text
|
52
45
|
|
falyx/bottom_bar.py
CHANGED
@@ -5,8 +5,8 @@ from typing import Any, Callable
|
|
5
5
|
|
6
6
|
from prompt_toolkit.formatted_text import HTML, merge_formatted_text
|
7
7
|
from prompt_toolkit.key_binding import KeyBindings
|
8
|
-
from rich.console import Console
|
9
8
|
|
9
|
+
from falyx.console import console
|
10
10
|
from falyx.options_manager import OptionsManager
|
11
11
|
from falyx.themes import OneColors
|
12
12
|
from falyx.utils import CaseInsensitiveDict, chunks
|
@@ -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 =
|
33
|
+
self.console: Console = console
|
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
@@ -23,11 +23,11 @@ from typing import Any, Awaitable, Callable
|
|
23
23
|
|
24
24
|
from prompt_toolkit.formatted_text import FormattedText
|
25
25
|
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, field_validator
|
26
|
-
from rich.console import Console
|
27
26
|
from rich.tree import Tree
|
28
27
|
|
29
28
|
from falyx.action.action import Action
|
30
29
|
from falyx.action.base_action import BaseAction
|
30
|
+
from falyx.console import console
|
31
31
|
from falyx.context import ExecutionContext
|
32
32
|
from falyx.debug import register_debug_hooks
|
33
33
|
from falyx.execution_registry import ExecutionRegistry as er
|
@@ -44,8 +44,6 @@ from falyx.signals import CancelSignal
|
|
44
44
|
from falyx.themes import OneColors
|
45
45
|
from falyx.utils import ensure_async
|
46
46
|
|
47
|
-
console = Console(color_system="truecolor")
|
48
|
-
|
49
47
|
|
50
48
|
class Command(BaseModel):
|
51
49
|
"""
|
falyx/config.py
CHANGED
@@ -11,18 +11,16 @@ from typing import Any, Callable
|
|
11
11
|
import toml
|
12
12
|
import yaml
|
13
13
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
14
|
-
from rich.console import Console
|
15
14
|
|
16
15
|
from falyx.action.action import Action
|
17
16
|
from falyx.action.base_action import BaseAction
|
18
17
|
from falyx.command import Command
|
18
|
+
from falyx.console import console
|
19
19
|
from falyx.falyx import Falyx
|
20
20
|
from falyx.logger import logger
|
21
21
|
from falyx.retry import RetryPolicy
|
22
22
|
from falyx.themes import OneColors
|
23
23
|
|
24
|
-
console = Console(color_system="truecolor")
|
25
|
-
|
26
24
|
|
27
25
|
def wrap_if_needed(obj: Any, name=None) -> BaseAction | Command:
|
28
26
|
if isinstance(obj, (BaseAction, Command)):
|
falyx/console.py
ADDED
falyx/context.py
CHANGED
@@ -24,6 +24,8 @@ from typing import Any
|
|
24
24
|
from pydantic import BaseModel, ConfigDict, Field
|
25
25
|
from rich.console import Console
|
26
26
|
|
27
|
+
from falyx.console import console
|
28
|
+
|
27
29
|
|
28
30
|
class ExecutionContext(BaseModel):
|
29
31
|
"""
|
@@ -83,7 +85,7 @@ class ExecutionContext(BaseModel):
|
|
83
85
|
index: int | None = None
|
84
86
|
|
85
87
|
extra: dict[str, Any] = Field(default_factory=dict)
|
86
|
-
console: Console =
|
88
|
+
console: Console = console
|
87
89
|
|
88
90
|
shared_context: SharedContext | None = None
|
89
91
|
|
falyx/execution_registry.py
CHANGED
falyx/falyx.py
CHANGED
@@ -46,6 +46,7 @@ from falyx.action.base_action import BaseAction
|
|
46
46
|
from falyx.bottom_bar import BottomBar
|
47
47
|
from falyx.command import Command
|
48
48
|
from falyx.completer import FalyxCompleter
|
49
|
+
from falyx.console import console
|
49
50
|
from falyx.context import ExecutionContext
|
50
51
|
from falyx.debug import log_after, log_before, log_error, log_success
|
51
52
|
from falyx.exceptions import (
|
@@ -63,7 +64,7 @@ from falyx.parser import CommandArgumentParser, FalyxParsers, get_arg_parsers
|
|
63
64
|
from falyx.protocols import ArgParserProtocol
|
64
65
|
from falyx.retry import RetryPolicy
|
65
66
|
from falyx.signals import BackSignal, CancelSignal, HelpSignal, QuitSignal
|
66
|
-
from falyx.themes import OneColors
|
67
|
+
from falyx.themes import OneColors
|
67
68
|
from falyx.utils import CaseInsensitiveDict, _noop, chunks
|
68
69
|
from falyx.version import __version__
|
69
70
|
|
@@ -201,7 +202,7 @@ class Falyx:
|
|
201
202
|
self.help_command: Command | None = (
|
202
203
|
self._get_help_command() if include_help_command else None
|
203
204
|
)
|
204
|
-
self.console: Console =
|
205
|
+
self.console: Console = console
|
205
206
|
self.welcome_message: str | Markdown | dict[str, Any] = welcome_message
|
206
207
|
self.exit_message: str | Markdown | dict[str, Any] = exit_message
|
207
208
|
self.hooks: HookManager = HookManager()
|
@@ -513,6 +514,8 @@ class Falyx:
|
|
513
514
|
bottom_toolbar=self._get_bottom_bar_render(),
|
514
515
|
key_bindings=self.key_bindings,
|
515
516
|
validate_while_typing=True,
|
517
|
+
interrupt_exception=QuitSignal,
|
518
|
+
eof_exception=QuitSignal,
|
516
519
|
)
|
517
520
|
return self._prompt_session
|
518
521
|
|
falyx/init.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
"""init.py"""
|
3
3
|
from pathlib import Path
|
4
4
|
|
5
|
-
from
|
5
|
+
from falyx.console import console
|
6
6
|
|
7
7
|
TEMPLATE_TASKS = """\
|
8
8
|
# This file is used by falyx.yaml to define CLI actions.
|
@@ -98,8 +98,6 @@ commands:
|
|
98
98
|
aliases: [clean, cleanup]
|
99
99
|
"""
|
100
100
|
|
101
|
-
console = Console(color_system="truecolor")
|
102
|
-
|
103
101
|
|
104
102
|
def init_project(name: str) -> None:
|
105
103
|
target = Path(name).resolve()
|
falyx/parser/argument.py
CHANGED
@@ -9,21 +9,37 @@ from falyx.parser.argument_action import ArgumentAction
|
|
9
9
|
|
10
10
|
@dataclass
|
11
11
|
class Argument:
|
12
|
-
"""
|
12
|
+
"""
|
13
|
+
Represents a command-line argument.
|
14
|
+
|
15
|
+
Attributes:
|
16
|
+
flags (tuple[str, ...]): Short and long flags for the argument.
|
17
|
+
dest (str): The destination name for the argument.
|
18
|
+
action (ArgumentAction): The action to be taken when the argument is encountered.
|
19
|
+
type (Any): The type of the argument (e.g., str, int, float) or a callable that converts the argument value.
|
20
|
+
default (Any): The default value if the argument is not provided.
|
21
|
+
choices (list[str] | None): A list of valid choices for the argument.
|
22
|
+
required (bool): True if the argument is required, False otherwise.
|
23
|
+
help (str): Help text for the argument.
|
24
|
+
nargs (int | str | None): Number of arguments expected. Can be an int, '?', '*', '+', or None.
|
25
|
+
positional (bool): True if the argument is positional (no leading - or -- in flags), False otherwise.
|
26
|
+
resolver (BaseAction | None):
|
27
|
+
An action object that resolves the argument, if applicable.
|
28
|
+
lazy_resolver (bool): True if the resolver should be called lazily, False otherwise
|
29
|
+
"""
|
13
30
|
|
14
31
|
flags: tuple[str, ...]
|
15
|
-
dest: str
|
16
|
-
action: ArgumentAction =
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
resolver: BaseAction | None = None # Action object for the argument
|
32
|
+
dest: str
|
33
|
+
action: ArgumentAction = ArgumentAction.STORE
|
34
|
+
type: Any = str
|
35
|
+
default: Any = None
|
36
|
+
choices: list[str] | None = None
|
37
|
+
required: bool = False
|
38
|
+
help: str = ""
|
39
|
+
nargs: int | str | None = None
|
40
|
+
positional: bool = False
|
41
|
+
resolver: BaseAction | None = None
|
42
|
+
lazy_resolver: bool = False
|
27
43
|
|
28
44
|
def get_positional_text(self) -> str:
|
29
45
|
"""Get the positional text for the argument."""
|
@@ -9,6 +9,7 @@ from rich.console import Console
|
|
9
9
|
from rich.markup import escape
|
10
10
|
|
11
11
|
from falyx.action.base_action import BaseAction
|
12
|
+
from falyx.console import console
|
12
13
|
from falyx.exceptions import CommandArgumentError
|
13
14
|
from falyx.parser.argument import Argument
|
14
15
|
from falyx.parser.argument_action import ArgumentAction
|
@@ -46,7 +47,7 @@ class CommandArgumentParser:
|
|
46
47
|
aliases: list[str] | None = None,
|
47
48
|
) -> None:
|
48
49
|
"""Initialize the CommandArgumentParser."""
|
49
|
-
self.console =
|
50
|
+
self.console: Console = console
|
50
51
|
self.command_key: str = command_key
|
51
52
|
self.command_description: str = command_description
|
52
53
|
self.command_style: str = command_style
|
@@ -300,6 +301,7 @@ class CommandArgumentParser:
|
|
300
301
|
help: str = "",
|
301
302
|
dest: str | None = None,
|
302
303
|
resolver: BaseAction | None = None,
|
304
|
+
lazy_resolver: bool = False,
|
303
305
|
) -> None:
|
304
306
|
"""Add an argument to the parser.
|
305
307
|
For `ArgumentAction.ACTION`, `nargs` and `type` determine how many and what kind
|
@@ -348,6 +350,10 @@ class CommandArgumentParser:
|
|
348
350
|
f"Default value '{default}' not in allowed choices: {choices}"
|
349
351
|
)
|
350
352
|
required = self._determine_required(required, positional, nargs)
|
353
|
+
if not isinstance(lazy_resolver, bool):
|
354
|
+
raise CommandArgumentError(
|
355
|
+
f"lazy_resolver must be a boolean, got {type(lazy_resolver)}"
|
356
|
+
)
|
351
357
|
argument = Argument(
|
352
358
|
flags=flags,
|
353
359
|
dest=dest,
|
@@ -360,6 +366,7 @@ class CommandArgumentParser:
|
|
360
366
|
nargs=nargs,
|
361
367
|
positional=positional,
|
362
368
|
resolver=resolver,
|
369
|
+
lazy_resolver=lazy_resolver,
|
363
370
|
)
|
364
371
|
for flag in flags:
|
365
372
|
if flag in self._flag_map:
|
@@ -445,6 +452,7 @@ class CommandArgumentParser:
|
|
445
452
|
result: dict[str, Any],
|
446
453
|
positional_args: list[Argument],
|
447
454
|
consumed_positional_indicies: set[int],
|
455
|
+
from_validate: bool = False,
|
448
456
|
) -> int:
|
449
457
|
remaining_positional_args = [
|
450
458
|
(j, spec)
|
@@ -508,12 +516,13 @@ class CommandArgumentParser:
|
|
508
516
|
assert isinstance(
|
509
517
|
spec.resolver, BaseAction
|
510
518
|
), "resolver should be an instance of BaseAction"
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
519
|
+
if not spec.lazy_resolver or not from_validate:
|
520
|
+
try:
|
521
|
+
result[spec.dest] = await spec.resolver(*typed)
|
522
|
+
except Exception as error:
|
523
|
+
raise CommandArgumentError(
|
524
|
+
f"[{spec.dest}] Action failed: {error}"
|
525
|
+
) from error
|
517
526
|
elif not typed and spec.default:
|
518
527
|
result[spec.dest] = spec.default
|
519
528
|
elif spec.action == ArgumentAction.APPEND:
|
@@ -657,21 +666,25 @@ class CommandArgumentParser:
|
|
657
666
|
if not typed_values and spec.nargs not in ("*", "?"):
|
658
667
|
choices = []
|
659
668
|
if spec.default:
|
660
|
-
choices.append(f"default={spec.default
|
669
|
+
choices.append(f"default={spec.default}")
|
661
670
|
if spec.choices:
|
662
|
-
choices.append(f"choices={spec.choices
|
671
|
+
choices.append(f"choices={spec.choices}")
|
663
672
|
if choices:
|
664
673
|
choices_text = ", ".join(choices)
|
665
674
|
raise CommandArgumentError(
|
666
675
|
f"Argument '{spec.dest}' requires a value. {choices_text}"
|
667
676
|
)
|
668
|
-
|
677
|
+
elif spec.nargs is None:
|
678
|
+
try:
|
679
|
+
raise CommandArgumentError(
|
680
|
+
f"Enter a {spec.type.__name__} value for '{spec.dest}'"
|
681
|
+
)
|
682
|
+
except AttributeError:
|
683
|
+
raise CommandArgumentError(f"Enter a value for '{spec.dest}'")
|
684
|
+
else:
|
669
685
|
raise CommandArgumentError(
|
670
|
-
f"
|
686
|
+
f"Argument '{spec.dest}' requires a value. Expected {spec.nargs} values."
|
671
687
|
)
|
672
|
-
raise CommandArgumentError(
|
673
|
-
f"Argument '{spec.dest}' requires a value. Expected {spec.nargs} values."
|
674
|
-
)
|
675
688
|
if spec.nargs in (None, 1, "?") and spec.action != ArgumentAction.APPEND:
|
676
689
|
result[spec.dest] = (
|
677
690
|
typed_values[0] if len(typed_values) == 1 else typed_values
|
@@ -705,6 +718,7 @@ class CommandArgumentParser:
|
|
705
718
|
result,
|
706
719
|
positional_args,
|
707
720
|
consumed_positional_indices,
|
721
|
+
from_validate=from_validate,
|
708
722
|
)
|
709
723
|
i += args_consumed
|
710
724
|
return i
|
@@ -746,13 +760,19 @@ class CommandArgumentParser:
|
|
746
760
|
continue
|
747
761
|
if spec.required and not result.get(spec.dest):
|
748
762
|
help_text = f" help: {spec.help}" if spec.help else ""
|
763
|
+
if (
|
764
|
+
spec.action == ArgumentAction.ACTION
|
765
|
+
and spec.lazy_resolver
|
766
|
+
and from_validate
|
767
|
+
):
|
768
|
+
continue # Lazy resolvers are not validated here
|
749
769
|
raise CommandArgumentError(
|
750
|
-
f"Missing required argument {spec.dest}: {spec.get_choice_text()}{help_text}"
|
770
|
+
f"Missing required argument '{spec.dest}': {spec.get_choice_text()}{help_text}"
|
751
771
|
)
|
752
772
|
|
753
773
|
if spec.choices and result.get(spec.dest) not in spec.choices:
|
754
774
|
raise CommandArgumentError(
|
755
|
-
f"Invalid value for {spec.dest}: must be one of {spec.choices}"
|
775
|
+
f"Invalid value for '{spec.dest}': must be one of {{{', '.join(spec.choices)}}}"
|
756
776
|
)
|
757
777
|
|
758
778
|
if spec.action == ArgumentAction.ACTION:
|
@@ -761,23 +781,23 @@ class CommandArgumentParser:
|
|
761
781
|
if isinstance(spec.nargs, int) and spec.nargs > 1:
|
762
782
|
assert isinstance(
|
763
783
|
result.get(spec.dest), list
|
764
|
-
), f"Invalid value for {spec.dest}: expected a list"
|
784
|
+
), f"Invalid value for '{spec.dest}': expected a list"
|
765
785
|
if not result[spec.dest] and not spec.required:
|
766
786
|
continue
|
767
787
|
if spec.action == ArgumentAction.APPEND:
|
768
788
|
for group in result[spec.dest]:
|
769
789
|
if len(group) % spec.nargs != 0:
|
770
790
|
raise CommandArgumentError(
|
771
|
-
f"Invalid number of values for {spec.dest}: expected a multiple of {spec.nargs}"
|
791
|
+
f"Invalid number of values for '{spec.dest}': expected a multiple of {spec.nargs}"
|
772
792
|
)
|
773
793
|
elif spec.action == ArgumentAction.EXTEND:
|
774
794
|
if len(result[spec.dest]) % spec.nargs != 0:
|
775
795
|
raise CommandArgumentError(
|
776
|
-
f"Invalid number of values for {spec.dest}: expected a multiple of {spec.nargs}"
|
796
|
+
f"Invalid number of values for '{spec.dest}': expected a multiple of {spec.nargs}"
|
777
797
|
)
|
778
798
|
elif len(result[spec.dest]) != spec.nargs:
|
779
799
|
raise CommandArgumentError(
|
780
|
-
f"Invalid number of values for {spec.dest}: expected {spec.nargs}, got {len(result[spec.dest])}"
|
800
|
+
f"Invalid number of values for '{spec.dest}': expected {spec.nargs}, got {len(result[spec.dest])}"
|
781
801
|
)
|
782
802
|
|
783
803
|
result.pop("help", None)
|
falyx/parser/utils.py
CHANGED
@@ -37,7 +37,8 @@ def coerce_enum(value: Any, enum_type: EnumMeta) -> Any:
|
|
37
37
|
coerced_value = base_type(value)
|
38
38
|
return enum_type(coerced_value)
|
39
39
|
except (ValueError, TypeError):
|
40
|
-
|
40
|
+
values = [str(enum.value) for enum in enum_type]
|
41
|
+
raise ValueError(f"'{value}' should be one of {{{', '.join(values)}}}") from None
|
41
42
|
|
42
43
|
|
43
44
|
def coerce_value(value: str, target_type: type) -> Any:
|
@@ -57,7 +58,7 @@ def coerce_value(value: str, target_type: type) -> Any:
|
|
57
58
|
return coerce_value(value, arg)
|
58
59
|
except Exception:
|
59
60
|
continue
|
60
|
-
raise ValueError(f"Value '{value}' could not be coerced to any of {args
|
61
|
+
raise ValueError(f"Value '{value}' could not be coerced to any of {args}")
|
61
62
|
|
62
63
|
if isinstance(target_type, EnumMeta):
|
63
64
|
return coerce_enum(value, target_type)
|
falyx/selection.py
CHANGED
@@ -5,10 +5,10 @@ from typing import Any, Callable, KeysView, Sequence
|
|
5
5
|
|
6
6
|
from prompt_toolkit import PromptSession
|
7
7
|
from rich import box
|
8
|
-
from rich.console import Console
|
9
8
|
from rich.markup import escape
|
10
9
|
from rich.table import Table
|
11
10
|
|
11
|
+
from falyx.console import console
|
12
12
|
from falyx.themes import OneColors
|
13
13
|
from falyx.utils import CaseInsensitiveDict, chunks
|
14
14
|
from falyx.validators import MultiIndexValidator, MultiKeyValidator
|
@@ -267,7 +267,6 @@ async def prompt_for_index(
|
|
267
267
|
*,
|
268
268
|
min_index: int = 0,
|
269
269
|
default_selection: str = "",
|
270
|
-
console: Console | None = None,
|
271
270
|
prompt_session: PromptSession | None = None,
|
272
271
|
prompt_message: str = "Select an option > ",
|
273
272
|
show_table: bool = True,
|
@@ -277,7 +276,6 @@ async def prompt_for_index(
|
|
277
276
|
cancel_key: str = "",
|
278
277
|
) -> int | list[int]:
|
279
278
|
prompt_session = prompt_session or PromptSession()
|
280
|
-
console = console or Console(color_system="truecolor")
|
281
279
|
|
282
280
|
if show_table:
|
283
281
|
console.print(table, justify="center")
|
@@ -307,7 +305,6 @@ async def prompt_for_selection(
|
|
307
305
|
table: Table,
|
308
306
|
*,
|
309
307
|
default_selection: str = "",
|
310
|
-
console: Console | None = None,
|
311
308
|
prompt_session: PromptSession | None = None,
|
312
309
|
prompt_message: str = "Select an option > ",
|
313
310
|
show_table: bool = True,
|
@@ -318,7 +315,6 @@ async def prompt_for_selection(
|
|
318
315
|
) -> str | list[str]:
|
319
316
|
"""Prompt the user to select a key from a set of options. Return the selected key."""
|
320
317
|
prompt_session = prompt_session or PromptSession()
|
321
|
-
console = console or Console(color_system="truecolor")
|
322
318
|
|
323
319
|
if show_table:
|
324
320
|
console.print(table, justify="center")
|
@@ -342,7 +338,6 @@ async def select_value_from_list(
|
|
342
338
|
title: str,
|
343
339
|
selections: Sequence[str],
|
344
340
|
*,
|
345
|
-
console: Console | None = None,
|
346
341
|
prompt_session: PromptSession | None = None,
|
347
342
|
prompt_message: str = "Select an option > ",
|
348
343
|
default_selection: str = "",
|
@@ -381,13 +376,11 @@ async def select_value_from_list(
|
|
381
376
|
highlight=highlight,
|
382
377
|
)
|
383
378
|
prompt_session = prompt_session or PromptSession()
|
384
|
-
console = console or Console(color_system="truecolor")
|
385
379
|
|
386
380
|
selection_index = await prompt_for_index(
|
387
381
|
len(selections) - 1,
|
388
382
|
table,
|
389
383
|
default_selection=default_selection,
|
390
|
-
console=console,
|
391
384
|
prompt_session=prompt_session,
|
392
385
|
prompt_message=prompt_message,
|
393
386
|
number_selections=number_selections,
|
@@ -405,7 +398,6 @@ async def select_key_from_dict(
|
|
405
398
|
selections: dict[str, SelectionOption],
|
406
399
|
table: Table,
|
407
400
|
*,
|
408
|
-
console: Console | None = None,
|
409
401
|
prompt_session: PromptSession | None = None,
|
410
402
|
prompt_message: str = "Select an option > ",
|
411
403
|
default_selection: str = "",
|
@@ -416,7 +408,6 @@ async def select_key_from_dict(
|
|
416
408
|
) -> str | list[str]:
|
417
409
|
"""Prompt for a key from a dict, returns the key."""
|
418
410
|
prompt_session = prompt_session or PromptSession()
|
419
|
-
console = console or Console(color_system="truecolor")
|
420
411
|
|
421
412
|
console.print(table, justify="center")
|
422
413
|
|
@@ -424,7 +415,6 @@ async def select_key_from_dict(
|
|
424
415
|
selections.keys(),
|
425
416
|
table,
|
426
417
|
default_selection=default_selection,
|
427
|
-
console=console,
|
428
418
|
prompt_session=prompt_session,
|
429
419
|
prompt_message=prompt_message,
|
430
420
|
number_selections=number_selections,
|
@@ -438,7 +428,6 @@ async def select_value_from_dict(
|
|
438
428
|
selections: dict[str, SelectionOption],
|
439
429
|
table: Table,
|
440
430
|
*,
|
441
|
-
console: Console | None = None,
|
442
431
|
prompt_session: PromptSession | None = None,
|
443
432
|
prompt_message: str = "Select an option > ",
|
444
433
|
default_selection: str = "",
|
@@ -449,7 +438,6 @@ async def select_value_from_dict(
|
|
449
438
|
) -> Any | list[Any]:
|
450
439
|
"""Prompt for a key from a dict, but return the value."""
|
451
440
|
prompt_session = prompt_session or PromptSession()
|
452
|
-
console = console or Console(color_system="truecolor")
|
453
441
|
|
454
442
|
console.print(table, justify="center")
|
455
443
|
|
@@ -457,7 +445,6 @@ async def select_value_from_dict(
|
|
457
445
|
selections.keys(),
|
458
446
|
table,
|
459
447
|
default_selection=default_selection,
|
460
|
-
console=console,
|
461
448
|
prompt_session=prompt_session,
|
462
449
|
prompt_message=prompt_message,
|
463
450
|
number_selections=number_selections,
|
@@ -475,7 +462,6 @@ async def get_selection_from_dict_menu(
|
|
475
462
|
title: str,
|
476
463
|
selections: dict[str, SelectionOption],
|
477
464
|
*,
|
478
|
-
console: Console | None = None,
|
479
465
|
prompt_session: PromptSession | None = None,
|
480
466
|
prompt_message: str = "Select an option > ",
|
481
467
|
default_selection: str = "",
|
@@ -493,7 +479,6 @@ async def get_selection_from_dict_menu(
|
|
493
479
|
return await select_value_from_dict(
|
494
480
|
selections=selections,
|
495
481
|
table=table,
|
496
|
-
console=console,
|
497
482
|
prompt_session=prompt_session,
|
498
483
|
prompt_message=prompt_message,
|
499
484
|
default_selection=default_selection,
|
falyx/validators.py
CHANGED
@@ -47,6 +47,30 @@ def yes_no_validator() -> Validator:
|
|
47
47
|
return Validator.from_callable(validate, error_message="Enter 'Y' or 'n'.")
|
48
48
|
|
49
49
|
|
50
|
+
def words_validator(keys: Sequence[str] | KeysView[str]) -> Validator:
|
51
|
+
"""Validator for specific word inputs."""
|
52
|
+
|
53
|
+
def validate(text: str) -> bool:
|
54
|
+
if text.upper() not in [key.upper() for key in keys]:
|
55
|
+
return False
|
56
|
+
return True
|
57
|
+
|
58
|
+
return Validator.from_callable(
|
59
|
+
validate, error_message=f"Invalid input. Choices: {{{', '.join(keys)}}}."
|
60
|
+
)
|
61
|
+
|
62
|
+
|
63
|
+
def word_validator(word: str) -> Validator:
|
64
|
+
"""Validator for specific word inputs."""
|
65
|
+
|
66
|
+
def validate(text: str) -> bool:
|
67
|
+
if text.upper().strip() == "N":
|
68
|
+
return True
|
69
|
+
return text.upper().strip() == word.upper()
|
70
|
+
|
71
|
+
return Validator.from_callable(validate, error_message=f"Enter '{word}' or 'N'.")
|
72
|
+
|
73
|
+
|
50
74
|
class MultiIndexValidator(Validator):
|
51
75
|
def __init__(
|
52
76
|
self,
|
falyx/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
1
|
+
__version__ = "0.1.58"
|