falyx 0.1.57__tar.gz → 0.1.59__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 (70) hide show
  1. {falyx-0.1.57 → falyx-0.1.59}/PKG-INFO +1 -1
  2. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/confirm_action.py +13 -8
  3. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/save_file_action.py +19 -4
  4. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/signal_action.py +1 -1
  5. {falyx-0.1.57 → falyx-0.1.59}/falyx/bottom_bar.py +1 -0
  6. {falyx-0.1.57 → falyx-0.1.59}/falyx/falyx.py +1 -3
  7. {falyx-0.1.57 → falyx-0.1.59}/falyx/parser/argument.py +29 -14
  8. {falyx-0.1.57 → falyx-0.1.59}/falyx/parser/parsers.py +105 -2
  9. {falyx-0.1.57 → falyx-0.1.59}/falyx/validators.py +9 -6
  10. falyx-0.1.59/falyx/version.py +1 -0
  11. {falyx-0.1.57 → falyx-0.1.59}/pyproject.toml +1 -1
  12. falyx-0.1.57/falyx/version.py +0 -1
  13. {falyx-0.1.57 → falyx-0.1.59}/LICENSE +0 -0
  14. {falyx-0.1.57 → falyx-0.1.59}/README.md +0 -0
  15. {falyx-0.1.57 → falyx-0.1.59}/falyx/.pytyped +0 -0
  16. {falyx-0.1.57 → falyx-0.1.59}/falyx/__init__.py +0 -0
  17. {falyx-0.1.57 → falyx-0.1.59}/falyx/__main__.py +0 -0
  18. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/.pytyped +0 -0
  19. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/__init__.py +0 -0
  20. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/action.py +0 -0
  21. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/action_factory.py +0 -0
  22. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/action_group.py +0 -0
  23. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/action_mixins.py +0 -0
  24. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/action_types.py +0 -0
  25. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/base_action.py +0 -0
  26. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/chained_action.py +0 -0
  27. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/fallback_action.py +0 -0
  28. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/http_action.py +0 -0
  29. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/io_action.py +0 -0
  30. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/literal_input_action.py +0 -0
  31. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/load_file_action.py +0 -0
  32. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/menu_action.py +0 -0
  33. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/process_action.py +0 -0
  34. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/process_pool_action.py +0 -0
  35. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/prompt_menu_action.py +0 -0
  36. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/select_file_action.py +0 -0
  37. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/selection_action.py +0 -0
  38. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/shell_action.py +0 -0
  39. {falyx-0.1.57 → falyx-0.1.59}/falyx/action/user_input_action.py +0 -0
  40. {falyx-0.1.57 → falyx-0.1.59}/falyx/command.py +0 -0
  41. {falyx-0.1.57 → falyx-0.1.59}/falyx/completer.py +0 -0
  42. {falyx-0.1.57 → falyx-0.1.59}/falyx/config.py +0 -0
  43. {falyx-0.1.57 → falyx-0.1.59}/falyx/console.py +0 -0
  44. {falyx-0.1.57 → falyx-0.1.59}/falyx/context.py +0 -0
  45. {falyx-0.1.57 → falyx-0.1.59}/falyx/debug.py +0 -0
  46. {falyx-0.1.57 → falyx-0.1.59}/falyx/exceptions.py +0 -0
  47. {falyx-0.1.57 → falyx-0.1.59}/falyx/execution_registry.py +0 -0
  48. {falyx-0.1.57 → falyx-0.1.59}/falyx/falyx_completer.py +0 -0
  49. {falyx-0.1.57 → falyx-0.1.59}/falyx/hook_manager.py +0 -0
  50. {falyx-0.1.57 → falyx-0.1.59}/falyx/hooks.py +0 -0
  51. {falyx-0.1.57 → falyx-0.1.59}/falyx/init.py +0 -0
  52. {falyx-0.1.57 → falyx-0.1.59}/falyx/logger.py +0 -0
  53. {falyx-0.1.57 → falyx-0.1.59}/falyx/menu.py +0 -0
  54. {falyx-0.1.57 → falyx-0.1.59}/falyx/options_manager.py +0 -0
  55. {falyx-0.1.57 → falyx-0.1.59}/falyx/parser/.pytyped +0 -0
  56. {falyx-0.1.57 → falyx-0.1.59}/falyx/parser/__init__.py +0 -0
  57. {falyx-0.1.57 → falyx-0.1.59}/falyx/parser/argument_action.py +0 -0
  58. {falyx-0.1.57 → falyx-0.1.59}/falyx/parser/command_argument_parser.py +0 -0
  59. {falyx-0.1.57 → falyx-0.1.59}/falyx/parser/signature.py +0 -0
  60. {falyx-0.1.57 → falyx-0.1.59}/falyx/parser/utils.py +0 -0
  61. {falyx-0.1.57 → falyx-0.1.59}/falyx/prompt_utils.py +0 -0
  62. {falyx-0.1.57 → falyx-0.1.59}/falyx/protocols.py +0 -0
  63. {falyx-0.1.57 → falyx-0.1.59}/falyx/retry.py +0 -0
  64. {falyx-0.1.57 → falyx-0.1.59}/falyx/retry_utils.py +0 -0
  65. {falyx-0.1.57 → falyx-0.1.59}/falyx/selection.py +0 -0
  66. {falyx-0.1.57 → falyx-0.1.59}/falyx/signals.py +0 -0
  67. {falyx-0.1.57 → falyx-0.1.59}/falyx/tagged_table.py +0 -0
  68. {falyx-0.1.57 → falyx-0.1.59}/falyx/themes/__init__.py +0 -0
  69. {falyx-0.1.57 → falyx-0.1.59}/falyx/themes/colors.py +0 -0
  70. {falyx-0.1.57 → falyx-0.1.59}/falyx/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: falyx
3
- Version: 0.1.57
3
+ Version: 0.1.59
4
4
  Summary: Reliable and introspectable async CLI action framework.
5
5
  License: MIT
6
6
  Author: Roland Thomas Jr
@@ -56,13 +56,14 @@ class ConfirmAction(BaseAction):
56
56
  prompt_session (PromptSession | None): The session to use for input.
57
57
  confirm (bool): Whether to prompt the user for confirmation.
58
58
  word (str): The word to type for TYPE_WORD confirmation.
59
- return_last_result (bool): Whether to return the last result of the action.
59
+ return_last_result (bool): Whether to return the last result of the action
60
+ instead of a boolean.
60
61
  """
61
62
 
62
63
  def __init__(
63
64
  self,
64
65
  name: str,
65
- message: str = "Continue",
66
+ message: str = "Confirm?",
66
67
  confirm_type: ConfirmType | str = ConfirmType.YES_NO,
67
68
  prompt_session: PromptSession | None = None,
68
69
  confirm: bool = True,
@@ -114,16 +115,19 @@ class ConfirmAction(BaseAction):
114
115
  session=self.prompt_session,
115
116
  )
116
117
  case ConfirmType.YES_NO_CANCEL:
118
+ error_message = "Enter 'Y', 'y' to confirm, 'N', 'n' to decline, or 'C', 'c' to abort."
117
119
  answer = await self.prompt_session.prompt_async(
118
- f"❓ {self.message} ([Y]es, [N]o, or [C]ancel to abort): ",
119
- validator=words_validator(["Y", "N", "C"]),
120
+ f"❓ {self.message} [Y]es, [N]o, or [C]ancel to abort > ",
121
+ validator=words_validator(
122
+ ["Y", "N", "C"], error_message=error_message
123
+ ),
120
124
  )
121
125
  if answer.upper() == "C":
122
126
  raise CancelSignal(f"Action '{self.name}' was cancelled by the user.")
123
127
  return answer.upper() == "Y"
124
128
  case ConfirmType.TYPE_WORD:
125
129
  answer = await self.prompt_session.prompt_async(
126
- f"❓ {self.message} (type '{self.word}' to confirm or N/n): ",
130
+ f"❓ {self.message} [{self.word}] to confirm or [N/n] > ",
127
131
  validator=word_validator(self.word),
128
132
  )
129
133
  return answer.upper().strip() != "N"
@@ -138,9 +142,10 @@ class ConfirmAction(BaseAction):
138
142
  raise CancelSignal(f"Action '{self.name}' was cancelled by the user.")
139
143
  return answer
140
144
  case ConfirmType.OK_CANCEL:
145
+ error_message = "Enter 'O', 'o' to confirm or 'C', 'c' to abort."
141
146
  answer = await self.prompt_session.prompt_async(
142
- f"❓ {self.message} ([O]k to continue, [C]ancel to abort): ",
143
- validator=words_validator(["O", "C"]),
147
+ f"❓ {self.message} [O]k to confirm, [C]ancel to abort > ",
148
+ validator=words_validator(["O", "C"], error_message=error_message),
144
149
  )
145
150
  if answer.upper() == "C":
146
151
  raise CancelSignal(f"Action '{self.name}' was cancelled by the user.")
@@ -213,5 +218,5 @@ class ConfirmAction(BaseAction):
213
218
  def __str__(self) -> str:
214
219
  return (
215
220
  f"ConfirmAction(name={self.name}, message={self.message}, "
216
- f"confirm_type={self.confirm_type})"
221
+ f"confirm_type={self.confirm_type}, return_last_result={self.return_last_result})"
217
222
  )
@@ -36,9 +36,11 @@ class SaveFileAction(BaseAction):
36
36
  file_path: str,
37
37
  file_type: FileType | str = FileType.TEXT,
38
38
  mode: Literal["w", "a"] = "w",
39
- inject_last_result: bool = True,
40
- inject_into: str = "data",
39
+ data: Any = None,
41
40
  overwrite: bool = True,
41
+ create_dirs: bool = True,
42
+ inject_last_result: bool = False,
43
+ inject_into: str = "data",
42
44
  ):
43
45
  """
44
46
  SaveFileAction allows saving data to a file.
@@ -47,17 +49,22 @@ class SaveFileAction(BaseAction):
47
49
  name (str): Name of the action.
48
50
  file_path (str | Path): Path to the file where data will be saved.
49
51
  file_type (FileType | str): Format to write to (e.g. TEXT, JSON, YAML).
52
+ mode (Literal["w", "a"]): File mode (default: "w").
53
+ data (Any): Data to be saved (if not using inject_last_result).
54
+ overwrite (bool): Whether to overwrite the file if it exists.
55
+ create_dirs (bool): Whether to create parent directories if they do not exist.
50
56
  inject_last_result (bool): Whether to inject result from previous action.
51
57
  inject_into (str): Kwarg name to inject the last result as.
52
- overwrite (bool): Whether to overwrite the file if it exists.
53
58
  """
54
59
  super().__init__(
55
60
  name=name, inject_last_result=inject_last_result, inject_into=inject_into
56
61
  )
57
62
  self._file_path = self._coerce_file_path(file_path)
58
63
  self._file_type = self._coerce_file_type(file_type)
64
+ self.data = data
59
65
  self.overwrite = overwrite
60
66
  self.mode = mode
67
+ self.create_dirs = create_dirs
61
68
 
62
69
  @property
63
70
  def file_path(self) -> Path | None:
@@ -126,6 +133,14 @@ class SaveFileAction(BaseAction):
126
133
  elif self.file_path.exists() and not self.overwrite:
127
134
  raise FileExistsError(f"File already exists: {self.file_path}")
128
135
 
136
+ if self.file_path.parent and not self.file_path.parent.exists():
137
+ if self.create_dirs:
138
+ self.file_path.parent.mkdir(parents=True, exist_ok=True)
139
+ else:
140
+ raise FileNotFoundError(
141
+ f"Directory does not exist: {self.file_path.parent}"
142
+ )
143
+
129
144
  try:
130
145
  if self.file_type == FileType.TEXT:
131
146
  self.file_path.write_text(data, encoding="UTF-8")
@@ -175,7 +190,7 @@ class SaveFileAction(BaseAction):
175
190
 
176
191
  async def _run(self, *args, **kwargs):
177
192
  combined_kwargs = self._maybe_inject_last_result(kwargs)
178
- data = combined_kwargs.get(self.inject_into)
193
+ data = self.data or combined_kwargs.get(self.inject_into)
179
194
 
180
195
  context = ExecutionContext(
181
196
  name=self.name, args=args, kwargs=combined_kwargs, action=self
@@ -14,7 +14,7 @@ class SignalAction(Action):
14
14
  Useful for exiting a menu, going back, or halting execution gracefully.
15
15
  """
16
16
 
17
- def __init__(self, name: str, signal: Exception):
17
+ def __init__(self, name: str, signal: FlowSignal):
18
18
  self.signal = signal
19
19
  super().__init__(name, action=self.raise_signal)
20
20
 
@@ -5,6 +5,7 @@ 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
8
9
 
9
10
  from falyx.console import console
10
11
  from falyx.options_manager import OptionsManager
@@ -1,7 +1,5 @@
1
1
  # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
- """falyx.py
3
-
4
- Main class for constructing and running Falyx CLI menus.
2
+ """Main class for constructing and running Falyx CLI menus.
5
3
 
6
4
  Falyx provides a structured, customizable interactive menu system
7
5
  for running commands, actions, and workflows. It supports:
@@ -9,22 +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
27
- lazy_resolver: bool = False # True if resolver should be called lazily
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
28
43
 
29
44
  def get_positional_text(self) -> str:
30
45
  """Get the positional text for the argument."""
@@ -56,6 +56,39 @@ def get_root_parser(
56
56
  allow_abbrev: bool = True,
57
57
  exit_on_error: bool = True,
58
58
  ) -> ArgumentParser:
59
+ """
60
+ Construct the root-level ArgumentParser for the Falyx CLI.
61
+
62
+ This parser handles global arguments shared across subcommands and can serve
63
+ as the base parser for the Falyx CLI or standalone applications. It includes
64
+ options for verbosity, debug logging, and version output.
65
+
66
+ Args:
67
+ prog (str | None): Name of the program (e.g., 'falyx').
68
+ usage (str | None): Optional custom usage string.
69
+ description (str | None): Description shown in the CLI help.
70
+ epilog (str | None): Message displayed at the end of help output.
71
+ parents (Sequence[ArgumentParser] | None): Optional parent parsers.
72
+ prefix_chars (str): Characters to denote optional arguments (default: "-").
73
+ fromfile_prefix_chars (str | None): Prefix to indicate argument file input.
74
+ argument_default (Any): Global default value for arguments.
75
+ conflict_handler (str): Strategy to resolve conflicting argument names.
76
+ add_help (bool): Whether to include help (`-h/--help`) in this parser.
77
+ allow_abbrev (bool): Allow abbreviated long options.
78
+ exit_on_error (bool): Exit immediately on error or raise an exception.
79
+
80
+ Returns:
81
+ ArgumentParser: The root parser with global options attached.
82
+
83
+ Notes:
84
+ ```
85
+ Includes the following arguments:
86
+ --never-prompt : Run in non-interactive mode.
87
+ -v / --verbose : Enable debug logging.
88
+ --debug-hooks : Enable hook lifecycle debug logs.
89
+ --version : Print the Falyx version.
90
+ ```
91
+ """
59
92
  parser = ArgumentParser(
60
93
  prog=prog,
61
94
  usage=usage,
@@ -92,7 +125,30 @@ def get_subparsers(
92
125
  title: str = "Falyx Commands",
93
126
  description: str | None = "Available commands for the Falyx CLI.",
94
127
  ) -> _SubParsersAction:
95
- """Create and return a subparsers action for the given parser."""
128
+ """
129
+ Create and return a subparsers object for registering Falyx CLI subcommands.
130
+
131
+ This function adds a `subparsers` block to the given root parser, enabling
132
+ structured subcommands such as `run`, `run-all`, `preview`, etc.
133
+
134
+ Args:
135
+ parser (ArgumentParser): The root parser to attach the subparsers to.
136
+ title (str): Title used in help output to group subcommands.
137
+ description (str | None): Optional text describing the group of subcommands.
138
+
139
+ Returns:
140
+ _SubParsersAction: The subparsers object that can be used to add new CLI subcommands.
141
+
142
+ Raises:
143
+ TypeError: If `parser` is not an instance of `ArgumentParser`.
144
+
145
+ Example:
146
+ ```python
147
+ >>> parser = get_root_parser()
148
+ >>> subparsers = get_subparsers(parser, title="Available Commands")
149
+ >>> subparsers.add_parser("run", help="Run a Falyx command")
150
+ ```
151
+ """
96
152
  if not isinstance(parser, ArgumentParser):
97
153
  raise TypeError("parser must be an instance of ArgumentParser")
98
154
  subparsers = parser.add_subparsers(
@@ -122,7 +178,54 @@ def get_arg_parsers(
122
178
  root_parser: ArgumentParser | None = None,
123
179
  subparsers: _SubParsersAction | None = None,
124
180
  ) -> FalyxParsers:
125
- """Returns the argument parser for the CLI."""
181
+ """
182
+ Create and return the full suite of argument parsers used by the Falyx CLI.
183
+
184
+ This function builds the root parser and all subcommand parsers used for structured
185
+ CLI workflows in Falyx. It supports standard subcommands including `run`, `run-all`,
186
+ `preview`, `list`, and `version`, and integrates with registered `Command` objects
187
+ to populate dynamic help and usage documentation.
188
+
189
+ Args:
190
+ prog (str | None): Program name to display in help and usage messages.
191
+ usage (str | None): Optional usage message to override the default.
192
+ description (str | None): Description for the CLI root parser.
193
+ epilog (str | None): Epilog message shown after the help text.
194
+ parents (Sequence[ArgumentParser] | None): Optional parent parsers.
195
+ prefix_chars (str): Characters that prefix optional arguments.
196
+ fromfile_prefix_chars (str | None): Prefix character for reading args from file.
197
+ argument_default (Any): Default value for arguments if not specified.
198
+ conflict_handler (str): Strategy for resolving conflicting arguments.
199
+ add_help (bool): Whether to add the `-h/--help` option to the root parser.
200
+ allow_abbrev (bool): Whether to allow abbreviated long options.
201
+ exit_on_error (bool): Whether the parser exits on error or raises.
202
+ commands (dict[str, Command] | None): Optional dictionary of registered commands
203
+ to populate help and subcommand descriptions dynamically.
204
+ root_parser (ArgumentParser | None): Custom root parser to use instead of building one.
205
+ subparsers (_SubParsersAction | None): Optional existing subparser object to extend.
206
+
207
+ Returns:
208
+ FalyxParsers: A structured container of all parsers, including `run`, `run-all`,
209
+ `preview`, `list`, `version`, and the root parser.
210
+
211
+ Raises:
212
+ TypeError: If `root_parser` is not an instance of ArgumentParser or
213
+ `subparsers` is not an instance of _SubParsersAction.
214
+
215
+ Example:
216
+ ```python
217
+ >>> parsers = get_arg_parsers(commands=my_command_dict)
218
+ >>> args = parsers.root.parse_args()
219
+ ```
220
+
221
+ Notes:
222
+ - This function integrates dynamic command usage and descriptions if the
223
+ `commands` argument is provided.
224
+ - The `run` parser supports additional options for retry logic and confirmation
225
+ prompts.
226
+ - The `run-all` parser executes all commands matching a tag.
227
+ - Use `falyx run ?[COMMAND]` from the CLI to preview a command.
228
+ """
126
229
  if epilog is None:
127
230
  epilog = f"Tip: Use '{prog} run ?[COMMAND]' to preview any command from the CLI."
128
231
  if root_parser is None:
@@ -44,10 +44,12 @@ def yes_no_validator() -> Validator:
44
44
  return False
45
45
  return True
46
46
 
47
- return Validator.from_callable(validate, error_message="Enter 'Y' or 'n'.")
47
+ return Validator.from_callable(validate, error_message="Enter 'Y', 'y' or 'N', 'n'.")
48
48
 
49
49
 
50
- def words_validator(keys: Sequence[str] | KeysView[str]) -> Validator:
50
+ def words_validator(
51
+ keys: Sequence[str] | KeysView[str], error_message: str | None = None
52
+ ) -> Validator:
51
53
  """Validator for specific word inputs."""
52
54
 
53
55
  def validate(text: str) -> bool:
@@ -55,9 +57,10 @@ def words_validator(keys: Sequence[str] | KeysView[str]) -> Validator:
55
57
  return False
56
58
  return True
57
59
 
58
- return Validator.from_callable(
59
- validate, error_message=f"Invalid input. Choices: {{{', '.join(keys)}}}."
60
- )
60
+ if error_message is None:
61
+ error_message = f"Invalid input. Choices: {{{', '.join(keys)}}}."
62
+
63
+ return Validator.from_callable(validate, error_message=error_message)
61
64
 
62
65
 
63
66
  def word_validator(word: str) -> Validator:
@@ -68,7 +71,7 @@ def word_validator(word: str) -> Validator:
68
71
  return True
69
72
  return text.upper().strip() == word.upper()
70
73
 
71
- return Validator.from_callable(validate, error_message=f"Enter '{word}' or 'N'.")
74
+ return Validator.from_callable(validate, error_message=f"Enter '{word}' or 'N', 'n'.")
72
75
 
73
76
 
74
77
  class MultiIndexValidator(Validator):
@@ -0,0 +1 @@
1
+ __version__ = "0.1.59"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "falyx"
3
- version = "0.1.57"
3
+ version = "0.1.59"
4
4
  description = "Reliable and introspectable async CLI action framework."
5
5
  authors = ["Roland Thomas Jr <roland@rtj.dev>"]
6
6
  license = "MIT"
@@ -1 +0,0 @@
1
- __version__ = "0.1.57"
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
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