falyx 0.1.52__py3-none-any.whl → 0.1.54__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/action/__init__.py +5 -3
- falyx/action/action.py +8 -8
- falyx/action/action_factory.py +3 -3
- falyx/action/action_group.py +17 -6
- falyx/action/{mixins.py → action_mixins.py} +5 -3
- falyx/action/{types.py → action_types.py} +3 -3
- falyx/action/{base.py → base_action.py} +1 -2
- falyx/action/chained_action.py +15 -6
- falyx/action/io_action.py +1 -1
- falyx/action/load_file_action.py +196 -0
- falyx/action/menu_action.py +1 -1
- falyx/action/process_action.py +1 -1
- falyx/action/process_pool_action.py +7 -4
- falyx/action/prompt_menu_action.py +1 -1
- falyx/action/save_file_action.py +44 -0
- falyx/action/select_file_action.py +18 -15
- falyx/action/selection_action.py +2 -2
- falyx/action/user_input_action.py +1 -1
- falyx/command.py +3 -3
- falyx/config.py +1 -1
- falyx/exceptions.py +8 -0
- falyx/falyx.py +2 -3
- falyx/logger.py +1 -1
- falyx/menu.py +1 -1
- falyx/parser/argument.py +3 -1
- falyx/parser/command_argument_parser.py +171 -153
- falyx/parser/parsers.py +15 -7
- falyx/parser/utils.py +1 -2
- falyx/protocols.py +5 -3
- falyx/retry_utils.py +1 -1
- falyx/version.py +1 -1
- {falyx-0.1.52.dist-info → falyx-0.1.54.dist-info}/METADATA +1 -1
- falyx-0.1.54.dist-info/RECORD +66 -0
- falyx-0.1.52.dist-info/RECORD +0 -64
- {falyx-0.1.52.dist-info → falyx-0.1.54.dist-info}/LICENSE +0 -0
- {falyx-0.1.52.dist-info → falyx-0.1.54.dist-info}/WHEEL +0 -0
- {falyx-0.1.52.dist-info → falyx-0.1.54.dist-info}/entry_points.txt +0 -0
@@ -14,8 +14,8 @@ from prompt_toolkit import PromptSession
|
|
14
14
|
from rich.console import Console
|
15
15
|
from rich.tree import Tree
|
16
16
|
|
17
|
-
from falyx.action.
|
18
|
-
from falyx.action.
|
17
|
+
from falyx.action.action_types import FileType
|
18
|
+
from falyx.action.base_action import BaseAction
|
19
19
|
from falyx.context import ExecutionContext
|
20
20
|
from falyx.execution_registry import ExecutionRegistry as er
|
21
21
|
from falyx.hook_manager import HookType
|
@@ -50,7 +50,7 @@ class SelectFileAction(BaseAction):
|
|
50
50
|
prompt_message (str): Message to display when prompting for selection.
|
51
51
|
style (str): Style for the selection options.
|
52
52
|
suffix_filter (str | None): Restrict to certain file types.
|
53
|
-
return_type (
|
53
|
+
return_type (FileType): What to return (path, content, parsed).
|
54
54
|
console (Console | None): Console instance for output.
|
55
55
|
prompt_session (PromptSession | None): Prompt session for user input.
|
56
56
|
"""
|
@@ -65,7 +65,7 @@ class SelectFileAction(BaseAction):
|
|
65
65
|
prompt_message: str = "Choose > ",
|
66
66
|
style: str = OneColors.WHITE,
|
67
67
|
suffix_filter: str | None = None,
|
68
|
-
return_type:
|
68
|
+
return_type: FileType | str = FileType.PATH,
|
69
69
|
number_selections: int | str = 1,
|
70
70
|
separator: str = ",",
|
71
71
|
allow_duplicates: bool = False,
|
@@ -104,35 +104,38 @@ class SelectFileAction(BaseAction):
|
|
104
104
|
else:
|
105
105
|
raise ValueError("number_selections must be a positive integer or one of '*'")
|
106
106
|
|
107
|
-
def _coerce_return_type(self, return_type:
|
108
|
-
if isinstance(return_type,
|
107
|
+
def _coerce_return_type(self, return_type: FileType | str) -> FileType:
|
108
|
+
if isinstance(return_type, FileType):
|
109
109
|
return return_type
|
110
|
-
|
110
|
+
elif isinstance(return_type, str):
|
111
|
+
return FileType(return_type)
|
112
|
+
else:
|
113
|
+
raise TypeError("return_type must be a FileType enum or string")
|
111
114
|
|
112
115
|
def get_options(self, files: list[Path]) -> dict[str, SelectionOption]:
|
113
116
|
value: Any
|
114
117
|
options = {}
|
115
118
|
for index, file in enumerate(files):
|
116
119
|
try:
|
117
|
-
if self.return_type ==
|
120
|
+
if self.return_type == FileType.TEXT:
|
118
121
|
value = file.read_text(encoding="UTF-8")
|
119
|
-
elif self.return_type ==
|
122
|
+
elif self.return_type == FileType.PATH:
|
120
123
|
value = file
|
121
|
-
elif self.return_type ==
|
124
|
+
elif self.return_type == FileType.JSON:
|
122
125
|
value = json.loads(file.read_text(encoding="UTF-8"))
|
123
|
-
elif self.return_type ==
|
126
|
+
elif self.return_type == FileType.TOML:
|
124
127
|
value = toml.loads(file.read_text(encoding="UTF-8"))
|
125
|
-
elif self.return_type ==
|
128
|
+
elif self.return_type == FileType.YAML:
|
126
129
|
value = yaml.safe_load(file.read_text(encoding="UTF-8"))
|
127
|
-
elif self.return_type ==
|
130
|
+
elif self.return_type == FileType.CSV:
|
128
131
|
with open(file, newline="", encoding="UTF-8") as csvfile:
|
129
132
|
reader = csv.reader(csvfile)
|
130
133
|
value = list(reader)
|
131
|
-
elif self.return_type ==
|
134
|
+
elif self.return_type == FileType.TSV:
|
132
135
|
with open(file, newline="", encoding="UTF-8") as tsvfile:
|
133
136
|
reader = csv.reader(tsvfile, delimiter="\t")
|
134
137
|
value = list(reader)
|
135
|
-
elif self.return_type ==
|
138
|
+
elif self.return_type == FileType.XML:
|
136
139
|
tree = ET.parse(file, parser=ET.XMLParser(encoding="UTF-8"))
|
137
140
|
root = tree.getroot()
|
138
141
|
value = ET.tostring(root, encoding="unicode")
|
falyx/action/selection_action.py
CHANGED
@@ -6,8 +6,8 @@ from prompt_toolkit import PromptSession
|
|
6
6
|
from rich.console import Console
|
7
7
|
from rich.tree import Tree
|
8
8
|
|
9
|
-
from falyx.action.
|
10
|
-
from falyx.action.
|
9
|
+
from falyx.action.action_types import SelectionReturnType
|
10
|
+
from falyx.action.base_action import BaseAction
|
11
11
|
from falyx.context import ExecutionContext
|
12
12
|
from falyx.execution_registry import ExecutionRegistry as er
|
13
13
|
from falyx.hook_manager import HookType
|
@@ -5,7 +5,7 @@ from prompt_toolkit.validation import Validator
|
|
5
5
|
from rich.console import Console
|
6
6
|
from rich.tree import Tree
|
7
7
|
|
8
|
-
from falyx.action.
|
8
|
+
from falyx.action.base_action import BaseAction
|
9
9
|
from falyx.context import ExecutionContext
|
10
10
|
from falyx.execution_registry import ExecutionRegistry as er
|
11
11
|
from falyx.hook_manager import HookType
|
falyx/command.py
CHANGED
@@ -19,7 +19,7 @@ in building robust interactive menus.
|
|
19
19
|
from __future__ import annotations
|
20
20
|
|
21
21
|
import shlex
|
22
|
-
from typing import Any, Callable
|
22
|
+
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
|
@@ -27,7 +27,7 @@ from rich.console import Console
|
|
27
27
|
from rich.tree import Tree
|
28
28
|
|
29
29
|
from falyx.action.action import Action
|
30
|
-
from falyx.action.
|
30
|
+
from falyx.action.base_action import BaseAction
|
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
|
@@ -105,7 +105,7 @@ class Command(BaseModel):
|
|
105
105
|
|
106
106
|
key: str
|
107
107
|
description: str
|
108
|
-
action: BaseAction | Callable[..., Any]
|
108
|
+
action: BaseAction | Callable[..., Any] | Callable[..., Awaitable[Any]]
|
109
109
|
args: tuple = ()
|
110
110
|
kwargs: dict[str, Any] = Field(default_factory=dict)
|
111
111
|
hidden: bool = False
|
falyx/config.py
CHANGED
@@ -14,7 +14,7 @@ from pydantic import BaseModel, Field, field_validator, model_validator
|
|
14
14
|
from rich.console import Console
|
15
15
|
|
16
16
|
from falyx.action.action import Action
|
17
|
-
from falyx.action.
|
17
|
+
from falyx.action.base_action import BaseAction
|
18
18
|
from falyx.command import Command
|
19
19
|
from falyx.falyx import Falyx
|
20
20
|
from falyx.logger import logger
|
falyx/exceptions.py
CHANGED
@@ -30,5 +30,13 @@ class EmptyChainError(FalyxError):
|
|
30
30
|
"""Exception raised when the chain is empty."""
|
31
31
|
|
32
32
|
|
33
|
+
class EmptyGroupError(FalyxError):
|
34
|
+
"""Exception raised when the chain is empty."""
|
35
|
+
|
36
|
+
|
37
|
+
class EmptyPoolError(FalyxError):
|
38
|
+
"""Exception raised when the chain is empty."""
|
39
|
+
|
40
|
+
|
33
41
|
class CommandArgumentError(FalyxError):
|
34
42
|
"""Exception raised when there is an error in the command argument parser."""
|
falyx/falyx.py
CHANGED
@@ -43,7 +43,7 @@ from rich.markdown import Markdown
|
|
43
43
|
from rich.table import Table
|
44
44
|
|
45
45
|
from falyx.action.action import Action
|
46
|
-
from falyx.action.
|
46
|
+
from falyx.action.base_action import BaseAction
|
47
47
|
from falyx.bottom_bar import BottomBar
|
48
48
|
from falyx.command import Command
|
49
49
|
from falyx.context import ExecutionContext
|
@@ -346,7 +346,6 @@ class Falyx:
|
|
346
346
|
aliases=["HISTORY"],
|
347
347
|
action=Action(name="View Execution History", action=er.summary),
|
348
348
|
style=OneColors.DARK_YELLOW,
|
349
|
-
simple_help_signature=True,
|
350
349
|
arg_parser=parser,
|
351
350
|
help_text="View the execution history of commands.",
|
352
351
|
)
|
@@ -1152,7 +1151,7 @@ class Falyx:
|
|
1152
1151
|
sys.exit(0)
|
1153
1152
|
|
1154
1153
|
if self.cli_args.command == "version" or self.cli_args.version:
|
1155
|
-
self.console.print(f"[{self.version_style}]{self.program} v{
|
1154
|
+
self.console.print(f"[{self.version_style}]{self.program} v{self.version}[/]")
|
1156
1155
|
sys.exit(0)
|
1157
1156
|
|
1158
1157
|
if self.cli_args.command == "preview":
|
falyx/logger.py
CHANGED
falyx/menu.py
CHANGED
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|
4
4
|
|
5
5
|
from prompt_toolkit.formatted_text import FormattedText
|
6
6
|
|
7
|
-
from falyx.action.
|
7
|
+
from falyx.action.base_action import BaseAction
|
8
8
|
from falyx.signals import BackSignal, QuitSignal
|
9
9
|
from falyx.themes import OneColors
|
10
10
|
from falyx.utils import CaseInsensitiveDict
|
falyx/parser/argument.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from typing import Any
|
5
5
|
|
6
|
-
from falyx.action.
|
6
|
+
from falyx.action.base_action import BaseAction
|
7
7
|
from falyx.parser.argument_action import ArgumentAction
|
8
8
|
|
9
9
|
|
@@ -46,6 +46,7 @@ class Argument:
|
|
46
46
|
ArgumentAction.STORE,
|
47
47
|
ArgumentAction.APPEND,
|
48
48
|
ArgumentAction.EXTEND,
|
49
|
+
ArgumentAction.ACTION,
|
49
50
|
)
|
50
51
|
and not self.positional
|
51
52
|
):
|
@@ -54,6 +55,7 @@ class Argument:
|
|
54
55
|
ArgumentAction.STORE,
|
55
56
|
ArgumentAction.APPEND,
|
56
57
|
ArgumentAction.EXTEND,
|
58
|
+
ArgumentAction.ACTION,
|
57
59
|
) or isinstance(self.nargs, str):
|
58
60
|
choice_text = self.dest
|
59
61
|
|
@@ -9,7 +9,7 @@ from rich.console import Console
|
|
9
9
|
from rich.markup import escape
|
10
10
|
from rich.text import Text
|
11
11
|
|
12
|
-
from falyx.action.
|
12
|
+
from falyx.action.base_action import BaseAction
|
13
13
|
from falyx.exceptions import CommandArgumentError
|
14
14
|
from falyx.parser.argument import Argument
|
15
15
|
from falyx.parser.argument_action import ArgumentAction
|
@@ -177,20 +177,19 @@ class CommandArgumentParser:
|
|
177
177
|
else:
|
178
178
|
choices = []
|
179
179
|
for choice in choices:
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
) from error
|
180
|
+
try:
|
181
|
+
coerce_value(choice, expected_type)
|
182
|
+
except Exception as error:
|
183
|
+
raise CommandArgumentError(
|
184
|
+
f"Invalid choice {choice!r}: not coercible to {expected_type.__name__} error: {error}"
|
185
|
+
) from error
|
187
186
|
return choices
|
188
187
|
|
189
188
|
def _validate_default_type(
|
190
189
|
self, default: Any, expected_type: type, dest: str
|
191
190
|
) -> None:
|
192
191
|
"""Validate the default value type."""
|
193
|
-
if default is not None
|
192
|
+
if default is not None:
|
194
193
|
try:
|
195
194
|
coerce_value(default, expected_type)
|
196
195
|
except Exception as error:
|
@@ -203,13 +202,12 @@ class CommandArgumentParser:
|
|
203
202
|
) -> None:
|
204
203
|
if isinstance(default, list):
|
205
204
|
for item in default:
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
) from error
|
205
|
+
try:
|
206
|
+
coerce_value(item, expected_type)
|
207
|
+
except Exception as error:
|
208
|
+
raise CommandArgumentError(
|
209
|
+
f"Default list value {default!r} for '{dest}' cannot be coerced to {expected_type.__name__} error: {error}"
|
210
|
+
) from error
|
213
211
|
|
214
212
|
def _validate_resolver(
|
215
213
|
self, action: ArgumentAction, resolver: BaseAction | None
|
@@ -422,22 +420,22 @@ class CommandArgumentParser:
|
|
422
420
|
raise CommandArgumentError(
|
423
421
|
f"Expected at least one value for '{spec.dest}'"
|
424
422
|
)
|
425
|
-
while i < len(args) and
|
423
|
+
while i < len(args) and args[i] not in self._keyword:
|
426
424
|
values.append(args[i])
|
427
425
|
i += 1
|
428
426
|
assert values, "Expected at least one value for '+' nargs: shouldn't happen"
|
429
427
|
return values, i
|
430
428
|
elif spec.nargs == "*":
|
431
|
-
while i < len(args) and
|
429
|
+
while i < len(args) and args[i] not in self._keyword:
|
432
430
|
values.append(args[i])
|
433
431
|
i += 1
|
434
432
|
return values, i
|
435
433
|
elif spec.nargs == "?":
|
436
|
-
if i < len(args) and
|
434
|
+
if i < len(args) and args[i] not in self._keyword:
|
437
435
|
return [args[i]], i + 1
|
438
436
|
return [], i
|
439
437
|
elif spec.nargs is None:
|
440
|
-
if i < len(args) and
|
438
|
+
if i < len(args) and args[i] not in self._keyword:
|
441
439
|
return [args[i]], i + 1
|
442
440
|
return [], i
|
443
441
|
assert False, "Invalid nargs value: shouldn't happen"
|
@@ -524,23 +522,142 @@ class CommandArgumentParser:
|
|
524
522
|
|
525
523
|
return i
|
526
524
|
|
527
|
-
def _expand_posix_bundling(self,
|
525
|
+
def _expand_posix_bundling(self, token: str) -> list[str] | str:
|
528
526
|
"""Expand POSIX-style bundled arguments into separate arguments."""
|
529
527
|
expanded = []
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
expanded.append(token)
|
528
|
+
if token.startswith("-") and not token.startswith("--") and len(token) > 2:
|
529
|
+
# POSIX bundle
|
530
|
+
# e.g. -abc -> -a -b -c
|
531
|
+
for char in token[1:]:
|
532
|
+
flag = f"-{char}"
|
533
|
+
arg = self._flag_map.get(flag)
|
534
|
+
if not arg:
|
535
|
+
raise CommandArgumentError(f"Unrecognized option: {flag}")
|
536
|
+
expanded.append(flag)
|
537
|
+
else:
|
538
|
+
return token
|
542
539
|
return expanded
|
543
540
|
|
541
|
+
async def _handle_token(
|
542
|
+
self,
|
543
|
+
token: str,
|
544
|
+
args: list[str],
|
545
|
+
i: int,
|
546
|
+
result: dict[str, Any],
|
547
|
+
positional_args: list[Argument],
|
548
|
+
consumed_positional_indices: set[int],
|
549
|
+
consumed_indices: set[int],
|
550
|
+
from_validate: bool = False,
|
551
|
+
) -> int:
|
552
|
+
if token in self._keyword:
|
553
|
+
spec = self._keyword[token]
|
554
|
+
action = spec.action
|
555
|
+
|
556
|
+
if action == ArgumentAction.HELP:
|
557
|
+
if not from_validate:
|
558
|
+
self.render_help()
|
559
|
+
raise HelpSignal()
|
560
|
+
elif action == ArgumentAction.ACTION:
|
561
|
+
assert isinstance(
|
562
|
+
spec.resolver, BaseAction
|
563
|
+
), "resolver should be an instance of BaseAction"
|
564
|
+
values, new_i = self._consume_nargs(args, i + 1, spec)
|
565
|
+
try:
|
566
|
+
typed_values = [coerce_value(value, spec.type) for value in values]
|
567
|
+
except ValueError as error:
|
568
|
+
raise CommandArgumentError(
|
569
|
+
f"Invalid value for '{spec.dest}': {error}"
|
570
|
+
) from error
|
571
|
+
try:
|
572
|
+
result[spec.dest] = await spec.resolver(*typed_values)
|
573
|
+
except Exception as error:
|
574
|
+
raise CommandArgumentError(
|
575
|
+
f"[{spec.dest}] Action failed: {error}"
|
576
|
+
) from error
|
577
|
+
consumed_indices.update(range(i, new_i))
|
578
|
+
i = new_i
|
579
|
+
elif action == ArgumentAction.STORE_TRUE:
|
580
|
+
result[spec.dest] = True
|
581
|
+
consumed_indices.add(i)
|
582
|
+
i += 1
|
583
|
+
elif action == ArgumentAction.STORE_FALSE:
|
584
|
+
result[spec.dest] = False
|
585
|
+
consumed_indices.add(i)
|
586
|
+
i += 1
|
587
|
+
elif action == ArgumentAction.COUNT:
|
588
|
+
result[spec.dest] = result.get(spec.dest, 0) + 1
|
589
|
+
consumed_indices.add(i)
|
590
|
+
i += 1
|
591
|
+
elif action == ArgumentAction.APPEND:
|
592
|
+
assert result.get(spec.dest) is not None, "dest should not be None"
|
593
|
+
values, new_i = self._consume_nargs(args, i + 1, spec)
|
594
|
+
try:
|
595
|
+
typed_values = [coerce_value(value, spec.type) for value in values]
|
596
|
+
except ValueError as error:
|
597
|
+
raise CommandArgumentError(
|
598
|
+
f"Invalid value for '{spec.dest}': {error}"
|
599
|
+
) from error
|
600
|
+
if spec.nargs is None:
|
601
|
+
result[spec.dest].append(spec.type(values[0]))
|
602
|
+
else:
|
603
|
+
result[spec.dest].append(typed_values)
|
604
|
+
consumed_indices.update(range(i, new_i))
|
605
|
+
i = new_i
|
606
|
+
elif action == ArgumentAction.EXTEND:
|
607
|
+
assert result.get(spec.dest) is not None, "dest should not be None"
|
608
|
+
values, new_i = self._consume_nargs(args, i + 1, spec)
|
609
|
+
try:
|
610
|
+
typed_values = [coerce_value(value, spec.type) for value in values]
|
611
|
+
except ValueError as error:
|
612
|
+
raise CommandArgumentError(
|
613
|
+
f"Invalid value for '{spec.dest}': {error}"
|
614
|
+
) from error
|
615
|
+
result[spec.dest].extend(typed_values)
|
616
|
+
consumed_indices.update(range(i, new_i))
|
617
|
+
i = new_i
|
618
|
+
else:
|
619
|
+
values, new_i = self._consume_nargs(args, i + 1, spec)
|
620
|
+
try:
|
621
|
+
typed_values = [coerce_value(value, spec.type) for value in values]
|
622
|
+
except ValueError as error:
|
623
|
+
raise CommandArgumentError(
|
624
|
+
f"Invalid value for '{spec.dest}': {error}"
|
625
|
+
) from error
|
626
|
+
if not typed_values and spec.nargs not in ("*", "?"):
|
627
|
+
raise CommandArgumentError(
|
628
|
+
f"Expected at least one value for '{spec.dest}'"
|
629
|
+
)
|
630
|
+
if spec.nargs in (None, 1, "?") and spec.action != ArgumentAction.APPEND:
|
631
|
+
result[spec.dest] = (
|
632
|
+
typed_values[0] if len(typed_values) == 1 else typed_values
|
633
|
+
)
|
634
|
+
else:
|
635
|
+
result[spec.dest] = typed_values
|
636
|
+
consumed_indices.update(range(i, new_i))
|
637
|
+
i = new_i
|
638
|
+
elif token.startswith("-"):
|
639
|
+
# Handle unrecognized option
|
640
|
+
raise CommandArgumentError(f"Unrecognized flag: {token}")
|
641
|
+
else:
|
642
|
+
# Get the next flagged argument index if it exists
|
643
|
+
next_flagged_index = -1
|
644
|
+
for index, arg in enumerate(args[i:], start=i):
|
645
|
+
if arg in self._keyword:
|
646
|
+
next_flagged_index = index
|
647
|
+
break
|
648
|
+
print(f"next_flagged_index: {next_flagged_index}")
|
649
|
+
print(f"{self._keyword_list=}")
|
650
|
+
if next_flagged_index == -1:
|
651
|
+
next_flagged_index = len(args)
|
652
|
+
args_consumed = await self._consume_all_positional_args(
|
653
|
+
args[i:next_flagged_index],
|
654
|
+
result,
|
655
|
+
positional_args,
|
656
|
+
consumed_positional_indices,
|
657
|
+
)
|
658
|
+
i += args_consumed
|
659
|
+
return i
|
660
|
+
|
544
661
|
async def parse_args(
|
545
662
|
self, args: list[str] | None = None, from_validate: bool = False
|
546
663
|
) -> dict[str, Any]:
|
@@ -548,132 +665,29 @@ class CommandArgumentParser:
|
|
548
665
|
if args is None:
|
549
666
|
args = []
|
550
667
|
|
551
|
-
args = self._expand_posix_bundling(args)
|
552
|
-
|
553
668
|
result = {arg.dest: deepcopy(arg.default) for arg in self._arguments}
|
554
|
-
positional_args = [
|
669
|
+
positional_args: list[Argument] = [
|
670
|
+
arg for arg in self._arguments if arg.positional
|
671
|
+
]
|
555
672
|
consumed_positional_indices: set[int] = set()
|
556
673
|
consumed_indices: set[int] = set()
|
557
674
|
|
558
675
|
i = 0
|
559
676
|
while i < len(args):
|
560
|
-
token = args[i]
|
561
|
-
if token
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
try:
|
575
|
-
typed_values = [
|
576
|
-
coerce_value(value, spec.type) for value in values
|
577
|
-
]
|
578
|
-
except ValueError as error:
|
579
|
-
raise CommandArgumentError(
|
580
|
-
f"Invalid value for '{spec.dest}': {error}"
|
581
|
-
) from error
|
582
|
-
try:
|
583
|
-
result[spec.dest] = await spec.resolver(*typed_values)
|
584
|
-
except Exception as error:
|
585
|
-
raise CommandArgumentError(
|
586
|
-
f"[{spec.dest}] Action failed: {error}"
|
587
|
-
) from error
|
588
|
-
consumed_indices.update(range(i, new_i))
|
589
|
-
i = new_i
|
590
|
-
elif action == ArgumentAction.STORE_TRUE:
|
591
|
-
result[spec.dest] = True
|
592
|
-
consumed_indices.add(i)
|
593
|
-
i += 1
|
594
|
-
elif action == ArgumentAction.STORE_FALSE:
|
595
|
-
result[spec.dest] = False
|
596
|
-
consumed_indices.add(i)
|
597
|
-
i += 1
|
598
|
-
elif action == ArgumentAction.COUNT:
|
599
|
-
result[spec.dest] = result.get(spec.dest, 0) + 1
|
600
|
-
consumed_indices.add(i)
|
601
|
-
i += 1
|
602
|
-
elif action == ArgumentAction.APPEND:
|
603
|
-
assert result.get(spec.dest) is not None, "dest should not be None"
|
604
|
-
values, new_i = self._consume_nargs(args, i + 1, spec)
|
605
|
-
try:
|
606
|
-
typed_values = [
|
607
|
-
coerce_value(value, spec.type) for value in values
|
608
|
-
]
|
609
|
-
except ValueError as error:
|
610
|
-
raise CommandArgumentError(
|
611
|
-
f"Invalid value for '{spec.dest}': {error}"
|
612
|
-
) from error
|
613
|
-
if spec.nargs is None:
|
614
|
-
result[spec.dest].append(spec.type(values[0]))
|
615
|
-
else:
|
616
|
-
result[spec.dest].append(typed_values)
|
617
|
-
consumed_indices.update(range(i, new_i))
|
618
|
-
i = new_i
|
619
|
-
elif action == ArgumentAction.EXTEND:
|
620
|
-
assert result.get(spec.dest) is not None, "dest should not be None"
|
621
|
-
values, new_i = self._consume_nargs(args, i + 1, spec)
|
622
|
-
try:
|
623
|
-
typed_values = [
|
624
|
-
coerce_value(value, spec.type) for value in values
|
625
|
-
]
|
626
|
-
except ValueError as error:
|
627
|
-
raise CommandArgumentError(
|
628
|
-
f"Invalid value for '{spec.dest}': {error}"
|
629
|
-
) from error
|
630
|
-
result[spec.dest].extend(typed_values)
|
631
|
-
consumed_indices.update(range(i, new_i))
|
632
|
-
i = new_i
|
633
|
-
else:
|
634
|
-
values, new_i = self._consume_nargs(args, i + 1, spec)
|
635
|
-
try:
|
636
|
-
typed_values = [
|
637
|
-
coerce_value(value, spec.type) for value in values
|
638
|
-
]
|
639
|
-
except ValueError as error:
|
640
|
-
raise CommandArgumentError(
|
641
|
-
f"Invalid value for '{spec.dest}': {error}"
|
642
|
-
) from error
|
643
|
-
if not typed_values and spec.nargs not in ("*", "?"):
|
644
|
-
raise CommandArgumentError(
|
645
|
-
f"Expected at least one value for '{spec.dest}'"
|
646
|
-
)
|
647
|
-
if (
|
648
|
-
spec.nargs in (None, 1, "?")
|
649
|
-
and spec.action != ArgumentAction.APPEND
|
650
|
-
):
|
651
|
-
result[spec.dest] = (
|
652
|
-
typed_values[0] if len(typed_values) == 1 else typed_values
|
653
|
-
)
|
654
|
-
else:
|
655
|
-
result[spec.dest] = typed_values
|
656
|
-
consumed_indices.update(range(i, new_i))
|
657
|
-
i = new_i
|
658
|
-
elif token.startswith("-"):
|
659
|
-
# Handle unrecognized option
|
660
|
-
raise CommandArgumentError(f"Unrecognized flag: {token}")
|
661
|
-
else:
|
662
|
-
# Get the next flagged argument index if it exists
|
663
|
-
next_flagged_index = -1
|
664
|
-
for index, arg in enumerate(args[i:], start=i):
|
665
|
-
if arg.startswith("-"):
|
666
|
-
next_flagged_index = index
|
667
|
-
break
|
668
|
-
if next_flagged_index == -1:
|
669
|
-
next_flagged_index = len(args)
|
670
|
-
args_consumed = await self._consume_all_positional_args(
|
671
|
-
args[i:next_flagged_index],
|
672
|
-
result,
|
673
|
-
positional_args,
|
674
|
-
consumed_positional_indices,
|
675
|
-
)
|
676
|
-
i += args_consumed
|
677
|
+
token = self._expand_posix_bundling(args[i])
|
678
|
+
if isinstance(token, list):
|
679
|
+
args[i : i + 1] = token
|
680
|
+
token = args[i]
|
681
|
+
i = await self._handle_token(
|
682
|
+
token,
|
683
|
+
args,
|
684
|
+
i,
|
685
|
+
result,
|
686
|
+
positional_args,
|
687
|
+
consumed_positional_indices,
|
688
|
+
consumed_indices,
|
689
|
+
from_validate=from_validate,
|
690
|
+
)
|
677
691
|
|
678
692
|
# Required validation
|
679
693
|
for spec in self._arguments:
|
@@ -797,6 +811,8 @@ class CommandArgumentParser:
|
|
797
811
|
flags = arg.get_positional_text()
|
798
812
|
arg_line = Text(f" {flags:<30} ")
|
799
813
|
help_text = arg.help or ""
|
814
|
+
if help_text and len(flags) > 30:
|
815
|
+
help_text = f"\n{'':<33}{help_text}"
|
800
816
|
arg_line.append(help_text)
|
801
817
|
self.console.print(arg_line)
|
802
818
|
self.console.print("[bold]options:[/bold]")
|
@@ -805,6 +821,8 @@ class CommandArgumentParser:
|
|
805
821
|
flags_choice = f"{flags} {arg.get_choice_text()}"
|
806
822
|
arg_line = Text(f" {flags_choice:<30} ")
|
807
823
|
help_text = arg.help or ""
|
824
|
+
if help_text and len(flags_choice) > 30:
|
825
|
+
help_text = f"\n{'':<33}{help_text}"
|
808
826
|
arg_line.append(help_text)
|
809
827
|
self.console.print(arg_line)
|
810
828
|
|