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.
@@ -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,
@@ -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 = Console(color_system="truecolor")
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
@@ -0,0 +1,5 @@
1
+ from rich.console import Console
2
+
3
+ from falyx.themes import get_nord_theme
4
+
5
+ console = Console(color_system="truecolor", theme=get_nord_theme())
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 = Field(default_factory=lambda: Console(color_system="truecolor"))
88
+ console: Console = console
87
89
 
88
90
  shared_context: SharedContext | None = None
89
91
 
@@ -36,6 +36,7 @@ from rich import box
36
36
  from rich.console import Console
37
37
  from rich.table import Table
38
38
 
39
+ from falyx.console import console
39
40
  from falyx.context import ExecutionContext
40
41
  from falyx.logger import logger
41
42
  from falyx.themes import OneColors
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, get_nord_theme
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 = Console(color_system="truecolor", theme=get_nord_theme())
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 rich.console import Console
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
- """Represents a command-line argument."""
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 # Destination name for the argument
16
- action: ArgumentAction = (
17
- ArgumentAction.STORE
18
- ) # Action to be taken when the argument is encountered
19
- type: Any = str # Type of the argument (e.g., str, int, float) or callable
20
- default: Any = None # Default value if the argument is not provided
21
- choices: list[str] | None = None # List of valid choices for the argument
22
- required: bool = False # True if the argument is required
23
- help: str = "" # Help text for the argument
24
- nargs: int | str | None = None # int, '?', '*', '+', None
25
- positional: bool = False # True if no leading - or -- in flags
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 = Console(color_system="truecolor")
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
- try:
512
- result[spec.dest] = await spec.resolver(*typed)
513
- except Exception as error:
514
- raise CommandArgumentError(
515
- f"[{spec.dest}] Action failed: {error}"
516
- ) from error
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!r}")
669
+ choices.append(f"default={spec.default}")
661
670
  if spec.choices:
662
- choices.append(f"choices={spec.choices!r}")
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
- if spec.nargs is None:
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"Enter a {spec.type.__name__} value for '{spec.dest}'"
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
- raise ValueError(f"Value '{value}' could not be coerced to enum type {enum_type}")
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!r}")
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.56"
1
+ __version__ = "0.1.58"