Skip to content

Commit cbb94bf

Browse files
committed
Addressed PR comments. Simplified some of the implementation per PR comments.
1 parent 102fc67 commit cbb94bf

File tree

3 files changed

+102
-131
lines changed

3 files changed

+102
-131
lines changed

cmd2/argparse_completer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ def consume_positional_argument() -> None:
415415
return completion_results
416416

417417
def complete_command_help(self, tokens: List[str], text: str, line: str, begidx: int, endidx: int) -> List[str]:
418-
"""Supports the completion of sub-commands for commands thhrough the cmd2 help command."""
418+
"""Supports the completion of sub-commands for commands through the cmd2 help command."""
419419
for idx, token in enumerate(tokens):
420420
if idx >= self._token_start_index:
421421
if self._positional_completers:

cmd2/cmd2.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,7 @@ def cmd_wrapper(instance, cmdline):
267267

268268
cmd_wrapper.__doc__ = argparser.format_help()
269269

270-
# Mark this function as having an argparse ArgumentParser (used by do_help)
271-
cmd_wrapper.__dict__['has_parser'] = True
270+
# Mark this function as having an argparse ArgumentParser
272271
setattr(cmd_wrapper, 'argparser', argparser)
273272

274273
return cmd_wrapper
@@ -305,8 +304,7 @@ def cmd_wrapper(instance, cmdline):
305304

306305
cmd_wrapper.__doc__ = argparser.format_help()
307306

308-
# Mark this function as having an argparse ArgumentParser (used by do_help)
309-
cmd_wrapper.__dict__['has_parser'] = True
307+
# Mark this function as having an argparse ArgumentParser
310308
setattr(cmd_wrapper, 'argparser', argparser)
311309

312310
return cmd_wrapper
@@ -1724,14 +1722,12 @@ def complete(self, text, state):
17241722
compfunc = getattr(self, 'complete_' + command)
17251723
except AttributeError:
17261724
# There's no completer function, next see if the command uses argparser
1727-
cmd_func = getattr(self, 'do_' + command)
1728-
if hasattr(cmd_func, 'has_parser') and hasattr(cmd_func, 'argparser') and \
1729-
getattr(cmd_func, 'has_parser'):
1730-
# Command uses argparser, switch to the default argparse completer
1725+
try:
1726+
cmd_func = getattr(self, 'do_' + command)
17311727
argparser = getattr(cmd_func, 'argparser')
1732-
compfunc = functools.partial(self._autocomplete_default,
1733-
argparser=argparser)
1734-
else:
1728+
# Command uses argparser, switch to the default argparse completer
1729+
compfunc = functools.partial(self._autocomplete_default, argparser=argparser)
1730+
except AttributeError:
17351731
compfunc = self.completedefault
17361732

17371733
# A valid command was not entered
@@ -1904,13 +1900,14 @@ def complete_help(self, text, line, begidx, endidx):
19041900
matches = self.basic_complete(text, line, begidx, endidx, strs_to_match)
19051901

19061902
# check if the command uses argparser
1907-
elif index >= subcmd_index and hasattr(self, 'do_' + tokens[cmd_index]) and\
1908-
hasattr(getattr(self, 'do_' + tokens[cmd_index]), 'has_parser'):
1909-
command = tokens[cmd_index]
1910-
cmd_func = getattr(self, 'do_' + command)
1911-
parser = getattr(cmd_func, 'argparser')
1912-
completer = AutoCompleter(parser)
1913-
matches = completer.complete_command_help(tokens[1:], text, line, begidx, endidx)
1903+
elif index >= subcmd_index:
1904+
try:
1905+
cmd_func = getattr(self, 'do_' + tokens[cmd_index])
1906+
parser = getattr(cmd_func, 'argparser')
1907+
completer = AutoCompleter(parser)
1908+
matches = completer.complete_command_help(tokens[1:], text, line, begidx, endidx)
1909+
except AttributeError:
1910+
pass
19141911

19151912
return matches
19161913

@@ -2561,7 +2558,7 @@ def do_help(self, arglist):
25612558
if funcname:
25622559
# Check to see if this function was decorated with an argparse ArgumentParser
25632560
func = getattr(self, funcname)
2564-
if func.__dict__.get('has_parser', False):
2561+
if hasattr(func, 'argparser'):
25652562
# Function has an argparser, so get help based on all the arguments in case there are sub-commands
25662563
new_arglist = arglist[1:]
25672564
new_arglist.append('-h')

examples/tab_autocompletion.py

Lines changed: 85 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,6 @@ def do_suggest(self, args) -> None:
120120
if not args.type:
121121
self.do_help('suggest')
122122

123-
def complete_suggest(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
124-
""" Adds tab completion to media"""
125-
completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser, 1)
126-
127-
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
128-
results = completer.complete_command(tokens, text, line, begidx, endidx)
129-
130-
return results
131-
132123
# If you prefer the original argparse help output but would like narg ranges, it's possible
133124
# to enable narg ranges without the help changes using this method
134125

@@ -148,15 +139,6 @@ def do_hybrid_suggest(self, args):
148139
if not args.type:
149140
self.do_help('orig_suggest')
150141

151-
def complete_hybrid_suggest(self, text, line, begidx, endidx):
152-
""" Adds tab completion to media"""
153-
completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser_hybrid)
154-
155-
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
156-
results = completer.complete_command(tokens, text, line, begidx, endidx)
157-
158-
return results
159-
160142
# This variant demonstrates the AutoCompleter working with the orginial argparse.
161143
# Base argparse is unable to specify narg ranges. Autocompleter will keep expecting additional arguments
162144
# for the -d/--duration flag until you specify a new flaw or end the list it with '--'
@@ -175,20 +157,12 @@ def do_orig_suggest(self, args) -> None:
175157
if not args.type:
176158
self.do_help('orig_suggest')
177159

178-
def complete_orig_suggest(self, text, line, begidx, endidx) -> List[str]:
179-
""" Adds tab completion to media"""
180-
completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser_orig)
181-
182-
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
183-
results = completer.complete_command(tokens, text, line, begidx, endidx)
184-
185-
return results
186-
187160
###################################################################################
188161
# The media command demonstrates a completer with multiple layers of subcommands
189-
# - This example uses a flat completion lookup dictionary
162+
# - This example demonstrates how to tag a completion attribute on each action, enabling argument
163+
# completion without implementing a complete_COMMAND function
190164

191-
def _do_media_movies(self, args) -> None:
165+
def _do_vid_media_movies(self, args) -> None:
192166
if not args.command:
193167
self.do_help('media movies')
194168
elif args.command == 'list':
@@ -199,7 +173,7 @@ def _do_media_movies(self, args) -> None:
199173
', '.join(movie['director']),
200174
'\n '.join(movie['actor'])))
201175

202-
def _do_media_shows(self, args) -> None:
176+
def _do_vid_media_shows(self, args) -> None:
203177
if not args.command:
204178
self.do_help('media shows')
205179

@@ -215,72 +189,67 @@ def _do_media_shows(self, args) -> None:
215189
'\n '.join(ep_list)))
216190
print()
217191

218-
media_parser = argparse_completer.ACArgumentParser(prog='media')
192+
video_parser = argparse_completer.ACArgumentParser(prog='media')
219193

220-
media_types_subparsers = media_parser.add_subparsers(title='Media Types', dest='type')
194+
video_types_subparsers = video_parser.add_subparsers(title='Media Types', dest='type')
221195

222-
movies_parser = media_types_subparsers.add_parser('movies')
223-
movies_parser.set_defaults(func=_do_media_movies)
196+
vid_movies_parser = video_types_subparsers.add_parser('movies')
197+
vid_movies_parser.set_defaults(func=_do_vid_media_movies)
224198

225-
movies_commands_subparsers = movies_parser.add_subparsers(title='Commands', dest='command')
199+
vid_movies_commands_subparsers = vid_movies_parser.add_subparsers(title='Commands', dest='command')
226200

227-
movies_list_parser = movies_commands_subparsers.add_parser('list')
201+
vid_movies_list_parser = vid_movies_commands_subparsers.add_parser('list')
228202

229-
movies_list_parser.add_argument('-t', '--title', help='Title Filter')
230-
movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+',
231-
choices=ratings_types)
232-
movies_list_parser.add_argument('-d', '--director', help='Director Filter')
233-
movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append')
203+
vid_movies_list_parser.add_argument('-t', '--title', help='Title Filter')
204+
vid_movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+',
205+
choices=ratings_types)
206+
# save a reference to the action object
207+
director_action = vid_movies_list_parser.add_argument('-d', '--director', help='Director Filter')
208+
actor_action = vid_movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append')
234209

235-
movies_add_parser = movies_commands_subparsers.add_parser('add')
236-
movies_add_parser.add_argument('title', help='Movie Title')
237-
movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types)
238-
movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), required=True)
239-
movies_add_parser.add_argument('actor', help='Actors', nargs='*')
210+
# tag the action objects with completion providers. This can be a collection or a callable
211+
setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors)
212+
setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors)
240213

241-
movies_delete_parser = movies_commands_subparsers.add_parser('delete')
214+
vid_movies_add_parser = vid_movies_commands_subparsers.add_parser('add')
215+
vid_movies_add_parser.add_argument('title', help='Movie Title')
216+
vid_movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types)
242217

243-
shows_parser = media_types_subparsers.add_parser('shows')
244-
shows_parser.set_defaults(func=_do_media_shows)
218+
# save a reference to the action object
219+
director_action = vid_movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2),
220+
required=True)
221+
actor_action = vid_movies_add_parser.add_argument('actor', help='Actors', nargs='*')
245222

246-
shows_commands_subparsers = shows_parser.add_subparsers(title='Commands', dest='command')
223+
# tag the action objects with completion providers. This can be a collection or a callable
224+
setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors)
225+
setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors)
247226

248-
shows_list_parser = shows_commands_subparsers.add_parser('list')
227+
vid_movies_delete_parser = vid_movies_commands_subparsers.add_parser('delete')
228+
229+
vid_shows_parser = video_types_subparsers.add_parser('shows')
230+
vid_shows_parser.set_defaults(func=_do_vid_media_shows)
231+
232+
vid_shows_commands_subparsers = vid_shows_parser.add_subparsers(title='Commands', dest='command')
233+
234+
vid_shows_list_parser = vid_shows_commands_subparsers.add_parser('list')
249235

250236
@with_category(CAT_AUTOCOMPLETE)
251-
@with_argparser(media_parser)
252-
def do_media(self, args):
253-
"""Media management command demonstrates multiple layers of subcommands being handled by AutoCompleter"""
237+
@with_argparser(video_parser)
238+
def do_video(self, args):
239+
"""Video management command demonstrates multiple layers of subcommands being handled by AutoCompleter"""
254240
func = getattr(args, 'func', None)
255241
if func is not None:
256242
# Call whatever subcommand function was selected
257243
func(self, args)
258244
else:
259245
# No subcommand was provided, so call help
260-
self.do_help('media')
261-
262-
# This completer is implemented using a single dictionary to look up completion lists for all layers of
263-
# subcommands. For each argument, AutoCompleter will search for completion values from the provided
264-
# arg_choices dict. This requires careful naming of argparse arguments so that there are no unintentional
265-
# name collisions.
266-
def complete_media(self, text, line, begidx, endidx):
267-
""" Adds tab completion to media"""
268-
choices = {'actor': query_actors, # function
269-
'director': TabCompleteExample.static_list_directors # static list
270-
}
271-
completer = argparse_completer.AutoCompleter(TabCompleteExample.media_parser, arg_choices=choices)
272-
273-
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
274-
results = completer.complete_command(tokens, text, line, begidx, endidx)
275-
276-
return results
246+
self.do_help('video')
277247

278248
###################################################################################
279249
# The media command demonstrates a completer with multiple layers of subcommands
280-
# - This example demonstrates how to tag a completion attribute on each action, enabling argument
281-
# completion without implementing a complete_COMMAND function
250+
# - This example uses a flat completion lookup dictionary
282251

283-
def _do_vid_media_movies(self, args) -> None:
252+
def _do_media_movies(self, args) -> None:
284253
if not args.command:
285254
self.do_help('media movies')
286255
elif args.command == 'list':
@@ -291,7 +260,7 @@ def _do_vid_media_movies(self, args) -> None:
291260
', '.join(movie['director']),
292261
'\n '.join(movie['actor'])))
293262

294-
def _do_vid_media_shows(self, args) -> None:
263+
def _do_media_shows(self, args) -> None:
295264
if not args.command:
296265
self.do_help('media shows')
297266

@@ -307,60 +276,65 @@ def _do_vid_media_shows(self, args) -> None:
307276
'\n '.join(ep_list)))
308277
print()
309278

310-
video_parser = argparse_completer.ACArgumentParser(prog='media')
311-
312-
video_types_subparsers = video_parser.add_subparsers(title='Media Types', dest='type')
313-
314-
vid_movies_parser = video_types_subparsers.add_parser('movies')
315-
vid_movies_parser.set_defaults(func=_do_vid_media_movies)
316-
317-
vid_movies_commands_subparsers = vid_movies_parser.add_subparsers(title='Commands', dest='command')
279+
media_parser = argparse_completer.ACArgumentParser(prog='media')
318280

319-
vid_movies_list_parser = vid_movies_commands_subparsers.add_parser('list')
281+
media_types_subparsers = media_parser.add_subparsers(title='Media Types', dest='type')
320282

321-
vid_movies_list_parser.add_argument('-t', '--title', help='Title Filter')
322-
vid_movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+',
323-
choices=ratings_types)
324-
# save a reference to the action object
325-
director_action = vid_movies_list_parser.add_argument('-d', '--director', help='Director Filter')
326-
actor_action = vid_movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append')
283+
movies_parser = media_types_subparsers.add_parser('movies')
284+
movies_parser.set_defaults(func=_do_media_movies)
327285

328-
# tag the action objects with completion providers. This can be a collection or a callable
329-
setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors)
330-
setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors)
286+
movies_commands_subparsers = movies_parser.add_subparsers(title='Commands', dest='command')
331287

332-
vid_movies_add_parser = vid_movies_commands_subparsers.add_parser('add')
333-
vid_movies_add_parser.add_argument('title', help='Movie Title')
334-
vid_movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types)
288+
movies_list_parser = movies_commands_subparsers.add_parser('list')
335289

336-
# save a reference to the action object
337-
director_action = vid_movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), required=True)
338-
actor_action = vid_movies_add_parser.add_argument('actor', help='Actors', nargs='*')
290+
movies_list_parser.add_argument('-t', '--title', help='Title Filter')
291+
movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+',
292+
choices=ratings_types)
293+
movies_list_parser.add_argument('-d', '--director', help='Director Filter')
294+
movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append')
339295

340-
# tag the action objects with completion providers. This can be a collection or a callable
341-
setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors)
342-
setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors)
296+
movies_add_parser = movies_commands_subparsers.add_parser('add')
297+
movies_add_parser.add_argument('title', help='Movie Title')
298+
movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types)
299+
movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), required=True)
300+
movies_add_parser.add_argument('actor', help='Actors', nargs='*')
343301

344-
vid_movies_delete_parser = vid_movies_commands_subparsers.add_parser('delete')
302+
movies_delete_parser = movies_commands_subparsers.add_parser('delete')
345303

346-
vid_shows_parser = video_types_subparsers.add_parser('shows')
347-
vid_shows_parser.set_defaults(func=_do_vid_media_shows)
304+
shows_parser = media_types_subparsers.add_parser('shows')
305+
shows_parser.set_defaults(func=_do_media_shows)
348306

349-
vid_shows_commands_subparsers = vid_shows_parser.add_subparsers(title='Commands', dest='command')
307+
shows_commands_subparsers = shows_parser.add_subparsers(title='Commands', dest='command')
350308

351-
vid_shows_list_parser = vid_shows_commands_subparsers.add_parser('list')
309+
shows_list_parser = shows_commands_subparsers.add_parser('list')
352310

353311
@with_category(CAT_AUTOCOMPLETE)
354-
@with_argparser(video_parser)
355-
def do_video(self, args):
356-
"""Video management command demonstrates multiple layers of subcommands being handled by AutoCompleter"""
312+
@with_argparser(media_parser)
313+
def do_media(self, args):
314+
"""Media management command demonstrates multiple layers of subcommands being handled by AutoCompleter"""
357315
func = getattr(args, 'func', None)
358316
if func is not None:
359317
# Call whatever subcommand function was selected
360318
func(self, args)
361319
else:
362320
# No subcommand was provided, so call help
363-
self.do_help('video')
321+
self.do_help('media')
322+
323+
# This completer is implemented using a single dictionary to look up completion lists for all layers of
324+
# subcommands. For each argument, AutoCompleter will search for completion values from the provided
325+
# arg_choices dict. This requires careful naming of argparse arguments so that there are no unintentional
326+
# name collisions.
327+
def complete_media(self, text, line, begidx, endidx):
328+
""" Adds tab completion to media"""
329+
choices = {'actor': query_actors, # function
330+
'director': TabCompleteExample.static_list_directors # static list
331+
}
332+
completer = argparse_completer.AutoCompleter(TabCompleteExample.media_parser, arg_choices=choices)
333+
334+
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
335+
results = completer.complete_command(tokens, text, line, begidx, endidx)
336+
337+
return results
364338

365339
###################################################################################
366340
# The library command demonstrates a completer with multiple layers of subcommands

0 commit comments

Comments
 (0)