falyx 0.1.55__py3-none-any.whl → 0.1.57__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 +199 -11
- falyx/action/select_file_action.py +5 -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 +8 -4
- falyx/completer.py +47 -0
- falyx/config.py +1 -3
- falyx/console.py +5 -0
- falyx/context.py +3 -1
- falyx/execution_registry.py +1 -0
- falyx/falyx.py +8 -16
- falyx/falyx_completer.py +105 -30
- falyx/init.py +1 -3
- falyx/parser/argument.py +1 -0
- falyx/parser/command_argument_parser.py +107 -33
- falyx/parser/utils.py +3 -2
- falyx/selection.py +1 -16
- falyx/validators.py +24 -0
- falyx/version.py +1 -1
- {falyx-0.1.55.dist-info → falyx-0.1.57.dist-info}/METADATA +1 -1
- {falyx-0.1.55.dist-info → falyx-0.1.57.dist-info}/RECORD +34 -31
- {falyx-0.1.55.dist-info → falyx-0.1.57.dist-info}/LICENSE +0 -0
- {falyx-0.1.55.dist-info → falyx-0.1.57.dist-info}/WHEEL +0 -0
- {falyx-0.1.55.dist-info → falyx-0.1.57.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
|
|
@@ -165,6 +158,11 @@ class SelectFileAction(BaseAction):
|
|
165
158
|
try:
|
166
159
|
await self.hooks.trigger(HookType.BEFORE, context)
|
167
160
|
|
161
|
+
if not self.directory.exists():
|
162
|
+
raise FileNotFoundError(f"Directory {self.directory} does not exist.")
|
163
|
+
elif not self.directory.is_dir():
|
164
|
+
raise NotADirectoryError(f"{self.directory} is not a directory.")
|
165
|
+
|
168
166
|
files = [
|
169
167
|
file
|
170
168
|
for file in self.directory.iterdir()
|
@@ -190,7 +188,6 @@ class SelectFileAction(BaseAction):
|
|
190
188
|
keys = await prompt_for_selection(
|
191
189
|
(options | cancel_option).keys(),
|
192
190
|
table,
|
193
|
-
console=self.console,
|
194
191
|
prompt_session=self.prompt_session,
|
195
192
|
prompt_message=self.prompt_message,
|
196
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
|
"""
|
@@ -91,6 +89,12 @@ class Command(BaseModel):
|
|
91
89
|
logging_hooks (bool): Whether to attach logging hooks automatically.
|
92
90
|
options_manager (OptionsManager): Manages global command-line options.
|
93
91
|
arg_parser (CommandArgumentParser): Parses command arguments.
|
92
|
+
arguments (list[dict[str, Any]]): Argument definitions for the command.
|
93
|
+
argument_config (Callable[[CommandArgumentParser], None] | None): Function to configure arguments
|
94
|
+
for the command parser.
|
95
|
+
arg_metadata (dict[str, str | dict[str, Any]]): Metadata for arguments,
|
96
|
+
such as help text or choices.
|
97
|
+
simple_help_signature (bool): Whether to use a simplified help signature.
|
94
98
|
custom_parser (ArgParserProtocol | None): Custom argument parser.
|
95
99
|
custom_help (Callable[[], str | None] | None): Custom help message generator.
|
96
100
|
auto_args (bool): Automatically infer arguments from the action.
|
@@ -227,7 +231,7 @@ class Command(BaseModel):
|
|
227
231
|
if self.logging_hooks and isinstance(self.action, BaseAction):
|
228
232
|
register_debug_hooks(self.action.hooks)
|
229
233
|
|
230
|
-
if self.arg_parser is None:
|
234
|
+
if self.arg_parser is None and not self.custom_parser:
|
231
235
|
self.arg_parser = CommandArgumentParser(
|
232
236
|
command_key=self.key,
|
233
237
|
command_description=self.description,
|
falyx/completer.py
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import shlex
|
4
|
+
from typing import TYPE_CHECKING, Iterable
|
5
|
+
|
6
|
+
from prompt_toolkit.completion import Completer, Completion
|
7
|
+
from prompt_toolkit.document import Document
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from falyx import Falyx
|
11
|
+
|
12
|
+
|
13
|
+
class FalyxCompleter(Completer):
|
14
|
+
"""Completer for Falyx commands."""
|
15
|
+
|
16
|
+
def __init__(self, falyx: "Falyx"):
|
17
|
+
self.falyx = falyx
|
18
|
+
|
19
|
+
def get_completions(self, document: Document, complete_event) -> Iterable[Completion]:
|
20
|
+
text = document.text_before_cursor
|
21
|
+
try:
|
22
|
+
tokens = shlex.split(text)
|
23
|
+
cursor_at_end_of_token = document.text_before_cursor.endswith((" ", "\t"))
|
24
|
+
except ValueError:
|
25
|
+
return
|
26
|
+
|
27
|
+
if not tokens or (len(tokens) == 1 and not cursor_at_end_of_token):
|
28
|
+
# Suggest command keys and aliases
|
29
|
+
yield from self._suggest_commands(tokens[0] if tokens else "")
|
30
|
+
return
|
31
|
+
|
32
|
+
def _suggest_commands(self, prefix: str) -> Iterable[Completion]:
|
33
|
+
prefix = prefix.upper()
|
34
|
+
keys = [self.falyx.exit_command.key]
|
35
|
+
keys.extend(self.falyx.exit_command.aliases)
|
36
|
+
if self.falyx.history_command:
|
37
|
+
keys.append(self.falyx.history_command.key)
|
38
|
+
keys.extend(self.falyx.history_command.aliases)
|
39
|
+
if self.falyx.help_command:
|
40
|
+
keys.append(self.falyx.help_command.key)
|
41
|
+
keys.extend(self.falyx.help_command.aliases)
|
42
|
+
for cmd in self.falyx.commands.values():
|
43
|
+
keys.append(cmd.key)
|
44
|
+
keys.extend(cmd.aliases)
|
45
|
+
for key in keys:
|
46
|
+
if key.upper().startswith(prefix):
|
47
|
+
yield Completion(key, start_position=-len(prefix))
|
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
@@ -32,7 +32,6 @@ from functools import cached_property
|
|
32
32
|
from typing import Any, Callable
|
33
33
|
|
34
34
|
from prompt_toolkit import PromptSession
|
35
|
-
from prompt_toolkit.completion import WordCompleter
|
36
35
|
from prompt_toolkit.formatted_text import AnyFormattedText
|
37
36
|
from prompt_toolkit.key_binding import KeyBindings
|
38
37
|
from prompt_toolkit.patch_stdout import patch_stdout
|
@@ -46,6 +45,8 @@ from falyx.action.action import Action
|
|
46
45
|
from falyx.action.base_action import BaseAction
|
47
46
|
from falyx.bottom_bar import BottomBar
|
48
47
|
from falyx.command import Command
|
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()
|
@@ -413,20 +414,9 @@ class Falyx:
|
|
413
414
|
arg_parser=parser,
|
414
415
|
)
|
415
416
|
|
416
|
-
def _get_completer(self) ->
|
417
|
+
def _get_completer(self) -> FalyxCompleter:
|
417
418
|
"""Completer to provide auto-completion for the menu commands."""
|
418
|
-
|
419
|
-
keys.extend(self.exit_command.aliases)
|
420
|
-
if self.history_command:
|
421
|
-
keys.append(self.history_command.key)
|
422
|
-
keys.extend(self.history_command.aliases)
|
423
|
-
if self.help_command:
|
424
|
-
keys.append(self.help_command.key)
|
425
|
-
keys.extend(self.help_command.aliases)
|
426
|
-
for cmd in self.commands.values():
|
427
|
-
keys.append(cmd.key)
|
428
|
-
keys.extend(cmd.aliases)
|
429
|
-
return WordCompleter(keys, ignore_case=True)
|
419
|
+
return FalyxCompleter(self)
|
430
420
|
|
431
421
|
def _get_validator_error_message(self) -> str:
|
432
422
|
"""Validator to check if the input is a valid command or toggle key."""
|
@@ -524,6 +514,8 @@ class Falyx:
|
|
524
514
|
bottom_toolbar=self._get_bottom_bar_render(),
|
525
515
|
key_bindings=self.key_bindings,
|
526
516
|
validate_while_typing=True,
|
517
|
+
interrupt_exception=QuitSignal,
|
518
|
+
eof_exception=QuitSignal,
|
527
519
|
)
|
528
520
|
return self._prompt_session
|
529
521
|
|
falyx/falyx_completer.py
CHANGED
@@ -1,53 +1,128 @@
|
|
1
|
-
import
|
2
|
-
from typing import Iterable
|
3
|
-
|
1
|
+
from collections import Counter
|
4
2
|
from prompt_toolkit.completion import Completer, Completion
|
3
|
+
from prompt_toolkit.document import Document
|
4
|
+
from typing import Iterable, Set, Optional
|
5
|
+
import shlex
|
5
6
|
|
7
|
+
from falyx.command import Command
|
6
8
|
from falyx.parser.command_argument_parser import CommandArgumentParser
|
7
|
-
|
9
|
+
from falyx.parser.argument import Argument
|
10
|
+
from falyx.parser.argument_action import ArgumentAction
|
8
11
|
|
9
12
|
class FalyxCompleter(Completer):
|
10
|
-
|
13
|
+
"""Completer for Falyx commands and their arguments."""
|
14
|
+
def __init__(self, falyx: "Falyx"):
|
11
15
|
self.falyx = falyx
|
16
|
+
self._used_args: Set[str] = set()
|
17
|
+
self._used_args_counter: Counter = Counter()
|
12
18
|
|
13
|
-
def get_completions(self, document, complete_event) -> Iterable[Completion]:
|
14
|
-
text = document.text_before_cursor
|
15
|
-
if not text:
|
16
|
-
yield from self._complete_command("")
|
17
|
-
return
|
18
|
-
|
19
|
+
def get_completions(self, document: Document, complete_event) -> Iterable[Completion]:
|
20
|
+
text = document.text_before_cursor
|
19
21
|
try:
|
20
22
|
tokens = shlex.split(text)
|
23
|
+
cursor_at_end_of_token = document.text_before_cursor.endswith((' ', '\t'))
|
21
24
|
except ValueError:
|
22
|
-
return
|
25
|
+
return
|
23
26
|
|
24
|
-
if not tokens:
|
25
|
-
|
27
|
+
if not tokens or (len(tokens) == 1 and not cursor_at_end_of_token):
|
28
|
+
# Suggest command keys and aliases
|
29
|
+
yield from self._suggest_commands(tokens[0] if tokens else "")
|
26
30
|
return
|
27
31
|
|
28
|
-
|
29
|
-
|
30
|
-
|
32
|
+
command = self._match_command(tokens[0])
|
33
|
+
if not command:
|
34
|
+
return
|
31
35
|
|
32
|
-
if command is None:
|
33
|
-
yield from self._complete_command(command_token)
|
36
|
+
if command.arg_parser is None:
|
34
37
|
return
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
for flag in arg.flags:
|
40
|
-
if flag not in used_flags:
|
41
|
-
yield Completion(flag, start_position=0)
|
39
|
+
self._set_used_args(tokens, command)
|
40
|
+
|
41
|
+
next_arg = self._next_expected_argument(tokens, command.arg_parser)
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
if next_arg:
|
44
|
+
# Positional arguments or required flagged arguments
|
45
|
+
yield from self._suggest_argument(next_arg, document)
|
46
|
+
else:
|
47
|
+
# Optional arguments
|
48
|
+
for arg in command.arg_parser._keyword.values():
|
49
|
+
if not self._arg_already_used(arg.dest):
|
50
|
+
yield from self._suggest_argument(arg, document)
|
46
51
|
|
47
|
-
def
|
52
|
+
def _set_used_args(self, tokens: list[str], command: Command) -> None:
|
53
|
+
"""Extracts used argument flags from the provided tokens."""
|
54
|
+
if not command.arg_parser:
|
55
|
+
return
|
56
|
+
self._used_args.clear()
|
57
|
+
self._used_args_counter.clear()
|
58
|
+
for token in tokens[1:]:
|
59
|
+
if token.startswith('-'):
|
60
|
+
if keyword_argument := command.arg_parser._keyword.get(token):
|
61
|
+
self._used_args_counter[keyword_argument.dest] += 1
|
62
|
+
if isinstance(keyword_argument.nargs, int) and self._used_args_counter[keyword_argument.dest] > keyword_argument.nargs:
|
63
|
+
continue
|
64
|
+
elif isinstance(keyword_argument.nargs, str) and keyword_argument.nargs in ("?"):
|
65
|
+
self._used_args.add(keyword_argument.dest)
|
66
|
+
else:
|
67
|
+
self._used_args.add(keyword_argument.dest)
|
68
|
+
else:
|
69
|
+
# Handle positional arguments
|
70
|
+
if command.arg_parser._positional:
|
71
|
+
for arg in command.arg_parser._positional.values():
|
72
|
+
if arg.dest not in self._used_args:
|
73
|
+
self._used_args.add(arg.dest)
|
74
|
+
break
|
75
|
+
print(f"Used args: {self._used_args}, Counter: {self._used_args_counter}")
|
76
|
+
|
77
|
+
def _suggest_commands(self, prefix: str) -> Iterable[Completion]:
|
78
|
+
prefix = prefix.upper()
|
48
79
|
seen = set()
|
49
80
|
for cmd in self.falyx.commands.values():
|
50
81
|
for key in [cmd.key] + cmd.aliases:
|
51
|
-
if key
|
82
|
+
if key.upper().startswith(prefix) and key not in seen:
|
52
83
|
yield Completion(key, start_position=-len(prefix))
|
53
84
|
seen.add(key)
|
85
|
+
|
86
|
+
def _match_command(self, token: str) -> Optional[Command]:
|
87
|
+
token = token.lstrip("?").upper()
|
88
|
+
return self.falyx._name_map.get(token)
|
89
|
+
|
90
|
+
def _next_expected_argument(
|
91
|
+
self, tokens: list[str], parser: CommandArgumentParser
|
92
|
+
) -> Optional[Argument]:
|
93
|
+
"""Determine the next expected argument based on the current tokens."""
|
94
|
+
# Positional arguments first
|
95
|
+
for arg in parser._positional.values():
|
96
|
+
if arg.dest not in self._used_args:
|
97
|
+
return arg
|
98
|
+
|
99
|
+
# Then required keyword arguments
|
100
|
+
for arg in parser._keyword_list:
|
101
|
+
if arg.required and not self._arg_already_used(arg.dest):
|
102
|
+
return arg
|
103
|
+
|
104
|
+
return None
|
105
|
+
|
106
|
+
def _arg_already_used(self, dest: str) -> bool:
|
107
|
+
print(f"Checking if argument '{dest}' is already used: {dest in self._used_args} - Used args: {self._used_args}")
|
108
|
+
return dest in self._used_args
|
109
|
+
|
110
|
+
def _suggest_argument(self, arg: Argument, document: Document) -> Iterable[Completion]:
|
111
|
+
if not arg.positional:
|
112
|
+
for flag in arg.flags:
|
113
|
+
yield Completion(flag, start_position=0)
|
114
|
+
|
115
|
+
if arg.choices:
|
116
|
+
for choice in arg.choices:
|
117
|
+
yield Completion(
|
118
|
+
choice,
|
119
|
+
start_position=0,
|
120
|
+
display=f"{arg.dest}={choice}"
|
121
|
+
)
|
122
|
+
|
123
|
+
if arg.default is not None and arg.action == ArgumentAction.STORE:
|
124
|
+
yield Completion(
|
125
|
+
str(arg.default),
|
126
|
+
start_position=0,
|
127
|
+
display=f"{arg.dest} (default: {arg.default})"
|
128
|
+
)
|
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
@@ -24,6 +24,7 @@ class Argument:
|
|
24
24
|
nargs: int | str | None = None # int, '?', '*', '+', None
|
25
25
|
positional: bool = False # True if no leading - or -- in flags
|
26
26
|
resolver: BaseAction | None = None # Action object for the argument
|
27
|
+
lazy_resolver: bool = False # True if resolver should be called lazily
|
27
28
|
|
28
29
|
def get_positional_text(self) -> str:
|
29
30
|
"""Get the positional text for the argument."""
|