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.
@@ -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.base import BaseAction
18
- from falyx.action.types import FileReturnType
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 (FileReturnType): What to return (path, content, parsed).
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: FileReturnType | str = FileReturnType.PATH,
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: FileReturnType | str) -> FileReturnType:
108
- if isinstance(return_type, FileReturnType):
107
+ def _coerce_return_type(self, return_type: FileType | str) -> FileType:
108
+ if isinstance(return_type, FileType):
109
109
  return return_type
110
- return FileReturnType(return_type)
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 == FileReturnType.TEXT:
120
+ if self.return_type == FileType.TEXT:
118
121
  value = file.read_text(encoding="UTF-8")
119
- elif self.return_type == FileReturnType.PATH:
122
+ elif self.return_type == FileType.PATH:
120
123
  value = file
121
- elif self.return_type == FileReturnType.JSON:
124
+ elif self.return_type == FileType.JSON:
122
125
  value = json.loads(file.read_text(encoding="UTF-8"))
123
- elif self.return_type == FileReturnType.TOML:
126
+ elif self.return_type == FileType.TOML:
124
127
  value = toml.loads(file.read_text(encoding="UTF-8"))
125
- elif self.return_type == FileReturnType.YAML:
128
+ elif self.return_type == FileType.YAML:
126
129
  value = yaml.safe_load(file.read_text(encoding="UTF-8"))
127
- elif self.return_type == FileReturnType.CSV:
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 == FileReturnType.TSV:
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 == FileReturnType.XML:
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")
@@ -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.base import BaseAction
10
- from falyx.action.types import SelectionReturnType
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.base import BaseAction
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.base import BaseAction
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.base import BaseAction
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.base import BaseAction
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{__version__}[/]")
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
@@ -2,4 +2,4 @@
2
2
  """logger.py"""
3
3
  import logging
4
4
 
5
- logger = logging.getLogger("falyx")
5
+ logger: logging.Logger = logging.getLogger("falyx")
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.base import BaseAction
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.base import BaseAction
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.base import BaseAction
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
- if not isinstance(choice, expected_type):
181
- try:
182
- coerce_value(choice, expected_type)
183
- except Exception as error:
184
- raise CommandArgumentError(
185
- f"Invalid choice {choice!r}: not coercible to {expected_type.__name__} error: {error}"
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 and not isinstance(default, expected_type):
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
- if not isinstance(item, expected_type):
207
- try:
208
- coerce_value(item, expected_type)
209
- except Exception as error:
210
- raise CommandArgumentError(
211
- f"Default list value {default!r} for '{dest}' cannot be coerced to {expected_type.__name__} error: {error}"
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 not args[i].startswith("-"):
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 not args[i].startswith("-"):
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 not args[i].startswith("-"):
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 not args[i].startswith("-"):
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, args: list[str]) -> list[str]:
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
- for token in args:
531
- if token.startswith("-") and not token.startswith("--") and len(token) > 2:
532
- # POSIX bundle
533
- # e.g. -abc -> -a -b -c
534
- for char in token[1:]:
535
- flag = f"-{char}"
536
- arg = self._flag_map.get(flag)
537
- if not arg:
538
- raise CommandArgumentError(f"Unrecognized option: {flag}")
539
- expanded.append(flag)
540
- else:
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 = [arg for arg in self._arguments if arg.positional]
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 in self._keyword:
562
- spec = self._keyword[token]
563
- action = spec.action
564
-
565
- if action == ArgumentAction.HELP:
566
- if not from_validate:
567
- self.render_help()
568
- raise HelpSignal()
569
- elif action == ArgumentAction.ACTION:
570
- assert isinstance(
571
- spec.resolver, BaseAction
572
- ), "resolver should be an instance of BaseAction"
573
- values, new_i = self._consume_nargs(args, i + 1, spec)
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