@@ -58,7 +58,7 @@ def __init__(self, *args, **kwargs) -> None:
5858 # Add subcommands to music -> create
5959 music_create_subparsers = music_create_parser .add_subparsers ()
6060 music_create_jazz_parser = music_create_subparsers .add_parser ('jazz' , help = 'create jazz' )
61- music_create_rock_parser = music_create_subparsers .add_parser ('rock' , help = 'create rocks ' )
61+ music_create_rock_parser = music_create_subparsers .add_parser ('rock' , help = 'create rock ' )
6262
6363 @with_argparser (music_parser )
6464 def do_music (self , args : argparse .Namespace ) -> None :
@@ -74,6 +74,7 @@ def do_music(self, args: argparse.Namespace) -> None:
7474 flag_parser .add_argument ('-a' , '--append_flag' , help = 'append flag' , action = 'append' )
7575 flag_parser .add_argument ('-o' , '--append_const_flag' , help = 'append const flag' , action = 'append_const' , const = True )
7676 flag_parser .add_argument ('-c' , '--count_flag' , help = 'count flag' , action = 'count' )
77+ flag_parser .add_argument ('-e' , '--extend_flag' , help = 'extend flag' , action = 'extend' )
7778 flag_parser .add_argument ('-s' , '--suppressed_flag' , help = argparse .SUPPRESS , action = 'store_true' )
7879 flag_parser .add_argument ('-r' , '--remainder_flag' , nargs = argparse .REMAINDER , help = 'a remainder flag' )
7980 flag_parser .add_argument ('-q' , '--required_flag' , required = True , help = 'a required flag' , action = 'store_true' )
@@ -146,14 +147,14 @@ def completion_item_method(self) -> list[CompletionItem]:
146147 "-p" , "--provider" , help = "a flag populated with a choices provider" , choices_provider = choices_provider
147148 )
148149 choices_parser .add_argument (
149- "--desc_header " ,
150- help = 'this arg has a descriptive header' ,
150+ "--table_header " ,
151+ help = 'this arg has a table header' ,
151152 choices_provider = completion_item_method ,
152153 table_header = CUSTOM_TABLE_HEADER ,
153154 )
154155 choices_parser .add_argument (
155156 "--no_header" ,
156- help = 'this arg has no descriptive header' ,
157+ help = 'this arg has no table header' ,
157158 choices_provider = completion_item_method ,
158159 metavar = STR_METAVAR ,
159160 )
@@ -299,7 +300,7 @@ def completer_takes_arg_tokens(
299300 arg_tokens_parser = Cmd2ArgumentParser ()
300301 arg_tokens_parser .add_argument ('parent_arg' , help = 'arg from a parent parser' )
301302
302- # Create a subcommand for to exercise receiving parent_tokens and subcommand name in arg_tokens
303+ # Create a subcommand to exercise receiving parent_tokens and subcommand name in arg_tokens
303304 arg_tokens_subparser = arg_tokens_parser .add_subparsers (dest = 'subcommand' )
304305 arg_tokens_subcmd_parser = arg_tokens_subparser .add_parser ('subcmd' )
305306
@@ -340,6 +341,26 @@ def do_mutex(self, args: argparse.Namespace) -> None:
340341 def do_standalone (self , args : argparse .Namespace ) -> None :
341342 pass
342343
344+ ############################################################################################################
345+ # Begin code related to display_meta data
346+ ############################################################################################################
347+ meta_parser = Cmd2ArgumentParser ()
348+
349+ # Add subcommands to meta
350+ meta_subparsers = meta_parser .add_subparsers ()
351+
352+ # Create subcommands with and without help text
353+ meta_helpful_parser = meta_subparsers .add_parser ('helpful' , help = 'my helpful text' )
354+ meta_helpless_parser = meta_subparsers .add_parser ('helpless' )
355+
356+ # Create flags with and without help text
357+ meta_helpful_parser .add_argument ('--helpful_flag' , help = "a helpful flag" )
358+ meta_helpless_parser .add_argument ('--helpless_flag' )
359+
360+ @with_argparser (meta_parser )
361+ def do_meta (self , args : argparse .Namespace ) -> None :
362+ pass
363+
343364
344365@pytest .fixture
345366def ac_app () -> ArgparseCompleterTester :
@@ -402,142 +423,122 @@ def test_subcommand_completions(ac_app, subcommand, text, expected) -> None:
402423
403424
404425@pytest .mark .parametrize (
405- ('command_and_args' , 'text' , 'expected_matches' , 'expected_displays' ),
426+ # expected_data is a list of tuples with completion text and display values
427+ ('command_and_args' , 'text' , 'expected_data' ),
406428 [
407429 # Complete all flags (suppressed will not show)
408430 (
409431 'flag' ,
410432 '-' ,
411433 [
412- '-a' ,
413- '-c' ,
414- '-h' ,
415- '-n' ,
416- '-o' ,
417- '-q' ,
418- '-r' ,
419- ],
420- [
421- '-q, --required_flag' ,
422- '[-o, --append_const_flag]' ,
423- '[-a, --append_flag]' ,
424- '[-c, --count_flag]' ,
425- '[-h, --help]' ,
426- '[-n, --normal_flag]' ,
427- '[-r, --remainder_flag]' ,
434+ ("-a" , "[-a, --append_flag]" ),
435+ ("-c" , "[-c, --count_flag]" ),
436+ ('-e' , '[-e, --extend_flag]' ),
437+ ("-h" , "[-h, --help]" ),
438+ ("-n" , "[-n, --normal_flag]" ),
439+ ("-o" , "[-o, --append_const_flag]" ),
440+ ("-q" , "-q, --required_flag" ),
441+ ("-r" , "[-r, --remainder_flag]" ),
428442 ],
429443 ),
430444 (
431445 'flag' ,
432446 '--' ,
433447 [
434- '--append_const_flag' ,
435- '--append_flag' ,
436- '--count_flag' ,
437- '--help' ,
438- '--normal_flag' ,
439- '--remainder_flag' ,
440- '--required_flag' ,
441- ],
442- [
443- '--required_flag' ,
444- '[--append_const_flag]' ,
445- '[--append_flag]' ,
446- '[--count_flag]' ,
447- '[--help]' ,
448- '[--normal_flag]' ,
449- '[--remainder_flag]' ,
448+ ('--append_const_flag' , '[--append_const_flag]' ),
449+ ('--append_flag' , '[--append_flag]' ),
450+ ('--count_flag' , '[--count_flag]' ),
451+ ('--extend_flag' , '[--extend_flag]' ),
452+ ('--help' , '[--help]' ),
453+ ('--normal_flag' , '[--normal_flag]' ),
454+ ('--remainder_flag' , '[--remainder_flag]' ),
455+ ('--required_flag' , '--required_flag' ),
450456 ],
451457 ),
452458 # Complete individual flag
453- ('flag' , '-n' , ['-n' ], [ '[-n]' ]),
454- ('flag' , '--n' , ['--normal_flag' ], [ '[--normal_flag]' ]),
459+ ('flag' , '-n' , [( '-n' , '[-n]' ) ]),
460+ ('flag' , '--n' , [( '--normal_flag' , '[--normal_flag]' ) ]),
455461 # No flags should complete until current flag has its args
456- ('flag --append_flag' , '-' , [], [] ),
462+ ('flag --append_flag' , '-' , []),
457463 # Complete REMAINDER flag name
458- ('flag' , '-r' , ['-r' ], [ '[-r]' ]),
459- ('flag' , '--rem' , ['--remainder_flag' ], [ '[--remainder_flag]' ]),
464+ ('flag' , '-r' , [( '-r' , '[-r]' ) ]),
465+ ('flag' , '--rem' , [( '--remainder_flag' , '[--remainder_flag]' ) ]),
460466 # No flags after a REMAINDER should complete
461- ('flag -r value' , '-' , [], [] ),
462- ('flag --remainder_flag value' , '--' , [], [] ),
467+ ('flag -r value' , '-' , []),
468+ ('flag --remainder_flag value' , '--' , []),
463469 # Suppressed flag should not complete
464- ('flag' , '-s' , [], [] ),
465- ('flag' , '--s' , [], [] ),
470+ ('flag' , '-s' , []),
471+ ('flag' , '--s' , []),
466472 # A used flag should not show in completions
467473 (
468474 'flag -n' ,
469475 '--' ,
470- ['--append_const_flag' , '--append_flag' , '--count_flag' , '--help' , '--remainder_flag' , '--required_flag' ],
471476 [
472- '--required_flag' ,
473- '[--append_const_flag]' ,
474- '[--append_flag]' ,
475- '[--count_flag]' ,
476- '[--help]' ,
477- '[--remainder_flag]' ,
477+ ('--append_const_flag' , '[--append_const_flag]' ),
478+ ('--append_flag' , '[--append_flag]' ),
479+ ('--count_flag' , '[--count_flag]' ),
480+ ('--extend_flag' , '[--extend_flag]' ),
481+ ('--help' , '[--help]' ),
482+ ('--remainder_flag' , '[--remainder_flag]' ),
483+ ('--required_flag' , '--required_flag' ),
478484 ],
479485 ),
480- # Flags with actions set to append, append_const, and count will always show even if they've been used
486+ # Flags with actions set to append, append_const, extend, and count will always show even if they've been used
481487 (
482- 'flag --append_const_flag -c --append_flag value' ,
488+ 'flag --append_flag value -- append_const_flag --count_flag --extend_flag value' ,
483489 '--' ,
484490 [
485- '--append_const_flag' ,
486- '--append_flag' ,
487- '--count_flag' ,
488- '--help' ,
489- '--normal_flag' ,
490- '--remainder_flag' ,
491- '--required_flag' ,
492- ],
493- [
494- '--required_flag' ,
495- '[--append_const_flag]' ,
496- '[--append_flag]' ,
497- '[--count_flag]' ,
498- '[--help]' ,
499- '[--normal_flag]' ,
500- '[--remainder_flag]' ,
491+ ('--append_const_flag' , '[--append_const_flag]' ),
492+ ('--append_flag' , '[--append_flag]' ),
493+ ('--count_flag' , '[--count_flag]' ),
494+ ('--extend_flag' , '[--extend_flag]' ),
495+ ('--help' , '[--help]' ),
496+ ('--normal_flag' , '[--normal_flag]' ),
497+ ('--remainder_flag' , '[--remainder_flag]' ),
498+ ('--required_flag' , '--required_flag' ),
501499 ],
502500 ),
503501 # Non-default flag prefix character (+)
504502 (
505503 'plus_flag' ,
506504 '+' ,
507- ['+h' , '+n' , '+q' ],
508- ['+q, ++required_flag' , '[+h, ++help]' , '[+n, ++normal_flag]' ],
505+ [
506+ ('+h' , '[+h, ++help]' ),
507+ ('+n' , '[+n, ++normal_flag]' ),
508+ ('+q' , '+q, ++required_flag' ),
509+ ],
509510 ),
510511 (
511512 'plus_flag' ,
512513 '++' ,
513- ['++help' , '++normal_flag' , '++required_flag' ],
514- ['++required_flag' , '[++help]' , '[++normal_flag]' ],
514+ [
515+ ('++help' , '[++help]' ),
516+ ('++normal_flag' , '[++normal_flag]' ),
517+ ('++required_flag' , '++required_flag' ),
518+ ],
515519 ),
516520 # Flag completion should not occur after '--' since that tells argparse all remaining arguments are non-flags
517- ('flag --' , '--' , [], [] ),
518- ('flag --help --' , '--' , [], [] ),
519- ('plus_flag --' , '++' , [], [] ),
520- ('plus_flag ++help --' , '++' , [], [] ),
521+ ('flag --' , '--' , []),
522+ ('flag --help --' , '--' , []),
523+ ('plus_flag --' , '++' , []),
524+ ('plus_flag ++help --' , '++' , []),
521525 # Test remaining flag names complete after all positionals are complete
522- ('pos_and_flag' , '' , ['a' , 'choice' ], [ 'a ' , 'choice' ]),
523- ('pos_and_flag choice ' , '' , ['-f' , '-h' ], [ '[ -f, --flag]', ' [-h, --help]' ]),
524- ('pos_and_flag choice -f ' , '' , ['-h' ], [ '[-h, --help]' ]),
525- ('pos_and_flag choice -f -h ' , '' , [], [] ),
526+ ('pos_and_flag' , '' , [( 'a' , 'a' ), ( 'choice ' , 'choice' ) ]),
527+ ('pos_and_flag choice ' , '' , [( '-f' , '[ -f, --flag]' ), ( '-h' , ' [-h, --help]') ]),
528+ ('pos_and_flag choice -f ' , '' , [( '-h' , '[-h, --help]' ) ]),
529+ ('pos_and_flag choice -f -h ' , '' , []),
526530 ],
527531)
528- def test_autcomp_flag_completion (ac_app , command_and_args , text , expected_matches , expected_displays ) -> None :
532+ def test_autcomp_flag_completion (ac_app , command_and_args , text , expected_data ) -> None :
529533 line = f'{ command_and_args } { text } '
530534 endidx = len (line )
531535 begidx = endidx - len (text )
532536
533- first_match = complete_tester (text , line , begidx , endidx , ac_app )
534- if completion_matches :
535- assert first_match is not None
536- else :
537- assert first_match is None
537+ expected_completions = Completions (items = [CompletionItem (value = v , display = d ) for v , d in expected_data ])
538+ completions = ac_app .complete (text , line , begidx , endidx )
538539
539- assert ac_app . completion_matches == sorted ( completion_matches , key = ac_app . default_sort_key )
540- assert ac_app . display_matches == sorted ( display_matches , key = ac_app . default_sort_key )
540+ assert completions . to_strings () == expected_completions . to_strings ( )
541+ assert [ item . display for item in completions ] == [ item . display for item in expected_completions ]
541542
542543
543544@pytest .mark .parametrize (
@@ -576,7 +577,7 @@ def test_autocomp_flag_choices_completion(ac_app, flag, text, expected) -> None:
576577 ],
577578)
578579def test_autocomp_positional_choices_completion (ac_app , pos , text , expected ) -> None :
579- # Generate line were preceding positionals are already filled
580+ # Generate line where preceding positionals are already filled
580581 line = 'choices {} {}' .format ('foo ' * (pos - 1 ), text )
581582 endidx = len (line )
582583 begidx = endidx - len (text )
@@ -585,26 +586,6 @@ def test_autocomp_positional_choices_completion(ac_app, pos, text, expected) ->
585586 assert completions .to_strings () == Completions .from_values (expected ).to_strings ()
586587
587588
588- def test_flag_sorting (ac_app ) -> None :
589- # This test exercises the case where a positional arg has non-negative integers for its choices.
590- # ArgparseCompleter will sort these numerically before converting them to strings. As a result,
591- # cmd2.matches_sorted gets set to True. If no completion matches are returned and the entered
592- # text looks like the beginning of a flag (e.g -), then ArgparseCompleter will try to complete
593- # flag names next. Before it does this, cmd2.matches_sorted is reset to make sure the flag names
594- # get sorted correctly.
595- option_strings = [action .option_strings [0 ] for action in ac_app .choices_parser ._actions if action .option_strings ]
596- option_strings .sort (key = ac_app .default_sort_key )
597-
598- text = '-'
599- line = f'choices arg1 arg2 arg3 { text } '
600- endidx = len (line )
601- begidx = endidx - len (text )
602-
603- first_match = complete_tester (text , line , begidx , endidx , ac_app )
604- assert first_match is not None
605- assert ac_app .completion_matches == option_strings
606-
607-
608589@pytest .mark .parametrize (
609590 ('flag' , 'text' , 'expected' ),
610591 [
@@ -848,12 +829,12 @@ def test_unfinished_flag_error(ac_app, command_and_args, text, is_error) -> None
848829def test_completion_table_arg_header (ac_app ) -> None :
849830 # Test when metavar is None
850831 text = ''
851- line = f'choices --desc_header { text } '
832+ line = f'choices --table_header { text } '
852833 endidx = len (line )
853834 begidx = endidx - len (text )
854835
855836 completions = ac_app .complete (text , line , begidx , endidx )
856- assert "DESC_HEADER " in normalize (completions .completion_table )[0 ]
837+ assert "TABLE_HEADER " in normalize (completions .completion_table )[0 ]
857838
858839 # Test when metavar is a string
859840 text = ''
@@ -901,7 +882,7 @@ def test_completion_table_header(ac_app) -> None:
901882
902883 # This argument provided a table header
903884 text = ''
904- line = f'choices --desc_header { text } '
885+ line = f'choices --table_header { text } '
905886 endidx = len (line )
906887 begidx = endidx - len (text )
907888
@@ -1124,6 +1105,31 @@ def test_complete_standalone(ac_app, flag, expected) -> None:
11241105 assert completions .to_strings () == Completions .from_values (expected ).to_strings ()
11251106
11261107
1108+ @pytest .mark .parametrize (
1109+ ('subcommand' , 'flag' , 'display_meta' ),
1110+ [
1111+ ('helpful' , '' , 'my helpful text' ),
1112+ ('helpful' , '--helpful_flag' , "a helpful flag" ),
1113+ ('helpless' , '' , '' ),
1114+ ('helpless' , '--helpless_flag' , '' ),
1115+ ],
1116+ )
1117+ def test_display_meta (ac_app , subcommand , flag , display_meta ) -> None :
1118+ """Test that subcommands and flags can have display_meta data."""
1119+ if flag :
1120+ text = flag
1121+ line = line = f'meta { subcommand } { text } '
1122+ else :
1123+ text = subcommand
1124+ line = line = f'meta { text } '
1125+
1126+ endidx = len (line )
1127+ begidx = endidx - len (text )
1128+
1129+ completions = ac_app .complete (text , line , begidx , endidx )
1130+ assert completions [0 ].display_meta == display_meta
1131+
1132+
11271133# Custom ArgparseCompleter-based class
11281134class CustomCompleter (argparse_completer .ArgparseCompleter ):
11291135 def _complete_flags (self , text : str , line : str , begidx : int , endidx : int , matched_flags : list [str ]) -> list [str ]:
0 commit comments