Skip to content

Commit 67d4a66

Browse files
committed
Renamed CompletionResultsBase.from_strings() to from_values().
Added CompletionResultsBase.to_strings().
1 parent 80ff944 commit 67d4a66

5 files changed

Lines changed: 36 additions & 30 deletions

File tree

cmd2/argparse_completer.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
import argparse
77
import dataclasses
88
import inspect
9-
from collections import deque
9+
from collections import (
10+
defaultdict,
11+
deque,
12+
)
1013
from collections.abc import Sequence
1114
from typing import (
1215
IO,
@@ -247,15 +250,14 @@ def complete(
247250
used_flags: set[str] = set()
248251

249252
# Keeps track of arguments we've seen and any tokens they consumed
250-
consumed_arg_values: dict[str, list[str]] = {} # dict(arg_name -> list[tokens])
253+
consumed_arg_values: dict[str, list[str]] = defaultdict(list)
251254

252255
# Completed mutually exclusive groups
253256
completed_mutex_groups: dict[argparse._MutuallyExclusiveGroup, argparse.Action] = {}
254257

255258
def consume_argument(arg_state: _ArgumentState, arg_token: str) -> None:
256259
"""Consume token as an argument."""
257260
arg_state.count += 1
258-
consumed_arg_values.setdefault(arg_state.action.dest, [])
259261
consumed_arg_values[arg_state.action.dest].append(arg_token)
260262

261263
#############################################################################################
@@ -315,7 +317,11 @@ def consume_argument(arg_state: _ArgumentState, arg_token: str) -> None:
315317

316318
if action is not None:
317319
self._update_mutex_groups(action, completed_mutex_groups, used_flags, remaining_positionals)
318-
if isinstance(
320+
321+
# Check if the action type allows the same flag to be provided multiple times.
322+
# Reusable actions (append, count, extend) preserve their history so the
323+
# completion logic knows which values have already been 'consumed'.
324+
if not isinstance(
319325
action,
320326
(
321327
argparse._AppendAction,
@@ -324,16 +330,12 @@ def consume_argument(arg_state: _ArgumentState, arg_token: str) -> None:
324330
argparse._ExtendAction,
325331
),
326332
):
327-
# Flags with actions set to append, append_const, count, and extend can be reused.
328-
# Therefore don't erase any tokens already consumed for this flag.
329-
consumed_arg_values.setdefault(action.dest, [])
330-
else:
331-
# This flag is not reusable, so mark that we've seen it
333+
# For standard 'overwrite' actions (e.g., --store), providing the flag
334+
# again resets its state. We mark the flags as 'used' to potentially
335+
# filter them from future completion results and clear any previously
336+
# recorded values for this destination.
332337
used_flags.update(action.option_strings)
333-
334-
# It's possible we already have consumed values for this flag if it was used
335-
# earlier in the command line. Reset them now for this use of it.
336-
consumed_arg_values[action.dest] = []
338+
consumed_arg_values[action.dest].clear()
337339

338340
new_arg_state = _ArgumentState(action)
339341

@@ -554,15 +556,15 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, used_f
554556
match_against.append(flag)
555557

556558
# Build a dictionary linking actions with their matched flag names
557-
matched_actions: dict[argparse.Action, list[str]] = {}
559+
matched_actions: dict[argparse.Action, list[str]] = defaultdict(list)
558560

559561
# Keep flags sorted in the order provided by argparse so our completion
560562
# suggestions display the same as argparse help text.
561563
matched_flags = self._cmd2_app.basic_complete(text, line, begidx, endidx, match_against, sort=False)
562564

563-
for item in matched_flags.items:
564-
action = self._flag_to_action[item.text]
565-
matched_actions.setdefault(action, []).append(item.text)
565+
for flag in matched_flags.to_strings():
566+
action = self._flag_to_action[flag]
567+
matched_actions[action].append(flag)
566568

567569
# For completion suggestions, group matched flags by action
568570
items: list[CompletionItem] = []

cmd2/cmd2.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,7 +1178,7 @@ def build_settables(self) -> None:
11781178
def get_allow_style_choices(_cli_self: Cmd) -> Choices:
11791179
"""Complete allow_style values."""
11801180
styles = [val.name.lower() for val in ru.AllowStyle]
1181-
return Choices.from_strings(styles)
1181+
return Choices.from_values(styles)
11821182

11831183
def allow_style_type(value: str) -> ru.AllowStyle:
11841184
"""Convert a string value into an ru.AllowStyle."""
@@ -1858,7 +1858,7 @@ def delimiter_complete(
18581858
if not basic_completions:
18591859
return Completions()
18601860

1861-
match_strings = [item.text for item in basic_completions]
1861+
match_strings = basic_completions.to_strings()
18621862

18631863
# Calculate what portion of the match we are completing
18641864
common_prefix = os.path.commonprefix(match_strings)
@@ -2400,13 +2400,13 @@ def _perform_completion(
24002400

24012401
# Check if we need to add an opening quote
24022402
if not completion_token_quote:
2403-
current_texts = [item.text for item in completions]
2403+
matches = completions.to_strings()
24042404

2405-
if any(' ' in text for text in current_texts):
2405+
if any(' ' in match for match in matches):
24062406
_add_opening_quote = True
24072407

24082408
# Determine best quote (single vs double) based on text content
2409-
_quote_char = "'" if any('"' in t for t in current_texts) else '"'
2409+
_quote_char = "'" if any('"' in t for t in matches) else '"'
24102410

24112411
# Check if we need to remove text from the beginning of completions
24122412
elif text_to_remove:

cmd2/completion.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
class CompletionItem:
5454
"""A single completion result."""
5555

56-
# The underlying object this completion represents (e.g., a Path, Enum, or int).
56+
# The underlying object this completion represents (e.g., str, int, Path).
5757
# This is used to support argparse choices validation.
5858
value: Any = field(kw_only=False)
5959

@@ -148,15 +148,19 @@ def __post_init__(self) -> None:
148148
object.__setattr__(self, "items", tuple(unique_items))
149149

150150
@classmethod
151-
def from_strings(cls, strings: Sequence[str], *, is_sorted: bool = False) -> Self:
152-
"""Create a completion results instance from a sequence of strings.
151+
def from_values(cls, values: Sequence[str], *, is_sorted: bool = False) -> Self:
152+
"""Create a completion results instance from a sequence of arbitrary objects.
153153
154-
:param strings: the raw strings to be converted into CompletionItems.
155-
:param is_sorted: whether the strings are already in the desired order.
154+
:param values: the raw objects (e.g. strs, ints, Paths) to be converted into CompletionItems.
155+
:param is_sorted: whether the values are already in the desired order.
156156
"""
157-
items = [CompletionItem(s) for s in strings]
157+
items = [CompletionItem(value=v) for v in values]
158158
return cls(items=items, is_sorted=is_sorted)
159159

160+
def to_strings(self) -> tuple[str, ...]:
161+
"""Return a tuple of the completion strings (the 'text' field of each item)."""
162+
return tuple(item.text for item in self.items)
163+
160164
# --- Sequence Protocol Functions ---
161165

162166
def __bool__(self) -> bool:

cmd2/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def __init__(
118118

119119
def get_bool_choices(_cmd2_self: "CommandParent") -> Choices:
120120
"""Tab complete lowercase boolean values."""
121-
return Choices.from_strings(['true', 'false'])
121+
return Choices.from_values(['true', 'false'])
122122

123123
val_type = to_bool
124124
choices_provider = get_bool_choices

tests/test_cmd2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3297,7 +3297,7 @@ def help_has_helper_funcs(self) -> None:
32973297
self.poutput('Help for has_helper_funcs')
32983298

32993299
def complete_has_helper_funcs(self, *args) -> Completions:
3300-
return Completions.from_strings(['result'])
3300+
return Completions.from_values(['result'])
33013301

33023302
@cmd2.with_category(category_name)
33033303
def do_has_no_helper_funcs(self, arg) -> None:

0 commit comments

Comments
 (0)