falyx 0.1.25__tar.gz → 0.1.26__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 (46) hide show
  1. {falyx-0.1.25 → falyx-0.1.26}/PKG-INFO +1 -1
  2. {falyx-0.1.25 → falyx-0.1.26}/falyx/config.py +73 -11
  3. {falyx-0.1.25 → falyx-0.1.26}/falyx/falyx.py +5 -2
  4. falyx-0.1.26/falyx/version.py +1 -0
  5. {falyx-0.1.25 → falyx-0.1.26}/pyproject.toml +1 -1
  6. falyx-0.1.25/falyx/version.py +0 -1
  7. {falyx-0.1.25 → falyx-0.1.26}/LICENSE +0 -0
  8. {falyx-0.1.25 → falyx-0.1.26}/README.md +0 -0
  9. {falyx-0.1.25 → falyx-0.1.26}/falyx/.pytyped +0 -0
  10. {falyx-0.1.25 → falyx-0.1.26}/falyx/__init__.py +0 -0
  11. {falyx-0.1.25 → falyx-0.1.26}/falyx/__main__.py +0 -0
  12. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/__init__.py +0 -0
  13. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/action.py +0 -0
  14. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/action_factory.py +0 -0
  15. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/http_action.py +0 -0
  16. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/io_action.py +0 -0
  17. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/menu_action.py +0 -0
  18. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/select_file_action.py +0 -0
  19. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/selection_action.py +0 -0
  20. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/signal_action.py +0 -0
  21. {falyx-0.1.25 → falyx-0.1.26}/falyx/action/types.py +0 -0
  22. {falyx-0.1.25 → falyx-0.1.26}/falyx/bottom_bar.py +0 -0
  23. {falyx-0.1.25 → falyx-0.1.26}/falyx/command.py +0 -0
  24. {falyx-0.1.25 → falyx-0.1.26}/falyx/config_schema.py +0 -0
  25. {falyx-0.1.25 → falyx-0.1.26}/falyx/context.py +0 -0
  26. {falyx-0.1.25 → falyx-0.1.26}/falyx/debug.py +0 -0
  27. {falyx-0.1.25 → falyx-0.1.26}/falyx/exceptions.py +0 -0
  28. {falyx-0.1.25 → falyx-0.1.26}/falyx/execution_registry.py +0 -0
  29. {falyx-0.1.25 → falyx-0.1.26}/falyx/hook_manager.py +0 -0
  30. {falyx-0.1.25 → falyx-0.1.26}/falyx/hooks.py +0 -0
  31. {falyx-0.1.25 → falyx-0.1.26}/falyx/init.py +0 -0
  32. {falyx-0.1.25 → falyx-0.1.26}/falyx/logger.py +0 -0
  33. {falyx-0.1.25 → falyx-0.1.26}/falyx/menu.py +0 -0
  34. {falyx-0.1.25 → falyx-0.1.26}/falyx/options_manager.py +0 -0
  35. {falyx-0.1.25 → falyx-0.1.26}/falyx/parsers.py +0 -0
  36. {falyx-0.1.25 → falyx-0.1.26}/falyx/prompt_utils.py +0 -0
  37. {falyx-0.1.25 → falyx-0.1.26}/falyx/protocols.py +0 -0
  38. {falyx-0.1.25 → falyx-0.1.26}/falyx/retry.py +0 -0
  39. {falyx-0.1.25 → falyx-0.1.26}/falyx/retry_utils.py +0 -0
  40. {falyx-0.1.25 → falyx-0.1.26}/falyx/selection.py +0 -0
  41. {falyx-0.1.25 → falyx-0.1.26}/falyx/signals.py +0 -0
  42. {falyx-0.1.25 → falyx-0.1.26}/falyx/tagged_table.py +0 -0
  43. {falyx-0.1.25 → falyx-0.1.26}/falyx/themes/__init__.py +0 -0
  44. {falyx-0.1.25 → falyx-0.1.26}/falyx/themes/colors.py +0 -0
  45. {falyx-0.1.25 → falyx-0.1.26}/falyx/utils.py +0 -0
  46. {falyx-0.1.25 → falyx-0.1.26}/falyx/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: falyx
3
- Version: 0.1.25
3
+ Version: 0.1.26
4
4
  Summary: Reliable and introspectable async CLI action framework.
5
5
  License: MIT
6
6
  Author: Roland Thomas Jr
@@ -72,10 +72,10 @@ class RawCommand(BaseModel):
72
72
  description: str
73
73
  action: str
74
74
 
75
- args: tuple[Any, ...] = ()
76
- kwargs: dict[str, Any] = {}
77
- aliases: list[str] = []
78
- tags: list[str] = []
75
+ args: tuple[Any, ...] = Field(default_factory=tuple)
76
+ kwargs: dict[str, Any] = Field(default_factory=dict)
77
+ aliases: list[str] = Field(default_factory=list)
78
+ tags: list[str] = Field(default_factory=list)
79
79
  style: str = OneColors.WHITE
80
80
 
81
81
  confirm: bool = False
@@ -86,13 +86,13 @@ class RawCommand(BaseModel):
86
86
  spinner_message: str = "Processing..."
87
87
  spinner_type: str = "dots"
88
88
  spinner_style: str = OneColors.CYAN
89
- spinner_kwargs: dict[str, Any] = {}
89
+ spinner_kwargs: dict[str, Any] = Field(default_factory=dict)
90
90
 
91
- before_hooks: list[Callable] = []
92
- success_hooks: list[Callable] = []
93
- error_hooks: list[Callable] = []
94
- after_hooks: list[Callable] = []
95
- teardown_hooks: list[Callable] = []
91
+ before_hooks: list[Callable] = Field(default_factory=list)
92
+ success_hooks: list[Callable] = Field(default_factory=list)
93
+ error_hooks: list[Callable] = Field(default_factory=list)
94
+ after_hooks: list[Callable] = Field(default_factory=list)
95
+ teardown_hooks: list[Callable] = Field(default_factory=list)
96
96
 
97
97
  logging_hooks: bool = False
98
98
  retry: bool = False
@@ -129,6 +129,60 @@ def convert_commands(raw_commands: list[dict[str, Any]]) -> list[Command]:
129
129
  return commands
130
130
 
131
131
 
132
+ def convert_submenus(
133
+ raw_submenus: list[dict[str, Any]], *, parent_path: Path | None = None, depth: int = 0
134
+ ) -> list[dict[str, Any]]:
135
+ submenus: list[dict[str, Any]] = []
136
+ for raw_submenu in raw_submenus:
137
+ if raw_submenu.get("config"):
138
+ config_path = Path(raw_submenu["config"])
139
+ if parent_path:
140
+ config_path = (parent_path.parent / config_path).resolve()
141
+ submenu = loader(config_path, _depth=depth + 1)
142
+ else:
143
+ submenu_module_path = raw_submenu.get("submenu")
144
+ if not isinstance(submenu_module_path, str):
145
+ console.print(
146
+ f"[{OneColors.DARK_RED}]❌ Invalid submenu path:[/] {submenu_module_path}"
147
+ )
148
+ sys.exit(1)
149
+ submenu = import_action(submenu_module_path)
150
+ if not isinstance(submenu, Falyx):
151
+ console.print(f"[{OneColors.DARK_RED}]❌ Invalid submenu:[/] {submenu}")
152
+ sys.exit(1)
153
+
154
+ key = raw_submenu.get("key")
155
+ if not isinstance(key, str):
156
+ console.print(f"[{OneColors.DARK_RED}]❌ Invalid submenu key:[/] {key}")
157
+ sys.exit(1)
158
+
159
+ description = raw_submenu.get("description")
160
+ if not isinstance(description, str):
161
+ console.print(
162
+ f"[{OneColors.DARK_RED}]❌ Invalid submenu description:[/] {description}"
163
+ )
164
+ sys.exit(1)
165
+
166
+ submenus.append(
167
+ Submenu(
168
+ key=key,
169
+ description=description,
170
+ submenu=submenu,
171
+ style=raw_submenu.get("style", OneColors.CYAN),
172
+ ).model_dump()
173
+ )
174
+ return submenus
175
+
176
+
177
+ class Submenu(BaseModel):
178
+ """Submenu model for Falyx CLI configuration."""
179
+
180
+ key: str
181
+ description: str
182
+ submenu: Any
183
+ style: str = OneColors.CYAN
184
+
185
+
132
186
  class FalyxConfig(BaseModel):
133
187
  """Falyx CLI configuration model."""
134
188
 
@@ -140,6 +194,7 @@ class FalyxConfig(BaseModel):
140
194
  welcome_message: str = ""
141
195
  exit_message: str = ""
142
196
  commands: list[Command] | list[dict] = []
197
+ submenus: list[dict[str, Any]] = []
143
198
 
144
199
  @model_validator(mode="after")
145
200
  def validate_prompt_format(self) -> FalyxConfig:
@@ -160,10 +215,12 @@ class FalyxConfig(BaseModel):
160
215
  exit_message=self.exit_message,
161
216
  )
162
217
  flx.add_commands(self.commands)
218
+ for submenu in self.submenus:
219
+ flx.add_submenu(**submenu)
163
220
  return flx
164
221
 
165
222
 
166
- def loader(file_path: Path | str) -> Falyx:
223
+ def loader(file_path: Path | str, _depth: int = 0) -> Falyx:
167
224
  """
168
225
  Load Falyx CLI configuration from a YAML or TOML file.
169
226
 
@@ -183,6 +240,9 @@ def loader(file_path: Path | str) -> Falyx:
183
240
  Raises:
184
241
  ValueError: If the file format is unsupported or file cannot be parsed.
185
242
  """
243
+ if _depth > 5:
244
+ raise ValueError("Maximum submenu depth exceeded (5 levels deep)")
245
+
186
246
  if isinstance(file_path, (str, Path)):
187
247
  path = Path(file_path)
188
248
  else:
@@ -212,6 +272,7 @@ def loader(file_path: Path | str) -> Falyx:
212
272
  )
213
273
 
214
274
  commands = convert_commands(raw_config["commands"])
275
+ submenus = convert_submenus(raw_config.get("submenus", []))
215
276
  return FalyxConfig(
216
277
  title=raw_config.get("title", f"[{OneColors.BLUE_b}]Falyx CLI"),
217
278
  prompt=raw_config.get("prompt", [(OneColors.BLUE_b, "FALYX > ")]),
@@ -219,4 +280,5 @@ def loader(file_path: Path | str) -> Falyx:
219
280
  welcome_message=raw_config.get("welcome_message", ""),
220
281
  exit_message=raw_config.get("exit_message", ""),
221
282
  commands=commands,
283
+ submenus=submenus,
222
284
  ).to_falyx()
@@ -19,6 +19,8 @@ for running commands, actions, and workflows. It supports:
19
19
  Falyx enables building flexible, robust, and user-friendly
20
20
  terminal applications with minimal boilerplate.
21
21
  """
22
+ from __future__ import annotations
23
+
22
24
  import asyncio
23
25
  import logging
24
26
  import sys
@@ -528,14 +530,15 @@ class Falyx:
528
530
  )
529
531
 
530
532
  def add_submenu(
531
- self, key: str, description: str, submenu: "Falyx", *, style: str = OneColors.CYAN
533
+ self, key: str, description: str, submenu: Falyx, *, style: str = OneColors.CYAN
532
534
  ) -> None:
533
535
  """Adds a submenu to the menu."""
534
536
  if not isinstance(submenu, Falyx):
535
537
  raise NotAFalyxError("submenu must be an instance of Falyx.")
536
538
  self._validate_command_key(key)
537
539
  self.add_command(key, description, submenu.menu, style=style)
538
- submenu.update_exit_command(key="B", description="Back", aliases=["BACK"])
540
+ if submenu.exit_command.key == "Q":
541
+ submenu.update_exit_command(key="B", description="Back", aliases=["BACK"])
539
542
 
540
543
  def add_commands(self, commands: list[Command] | list[dict]) -> None:
541
544
  """Adds a list of Command instances or config dicts."""
@@ -0,0 +1 @@
1
+ __version__ = "0.1.26"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "falyx"
3
- version = "0.1.25"
3
+ version = "0.1.26"
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.25"
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