@@ -67,12 +67,12 @@ def __init__(self, *args, **kwargs):
6767
6868 # Add subcommands to music
6969 music_subparsers = music_parser .add_subparsers ()
70- music_create_parser = music_subparsers .add_parser ('create' , help = 'Create music' )
70+ music_create_parser = music_subparsers .add_parser ('create' , help = 'create music' )
7171
7272 # Add subcommands to music -> create
7373 music_create_subparsers = music_create_parser .add_subparsers ()
74- music_create_jazz_parser = music_create_subparsers .add_parser ('jazz' , help = 'Create jazz' )
75- music_create_rock_parser = music_create_subparsers .add_parser ('rock' , help = 'Create rocks' )
74+ music_create_jazz_parser = music_create_subparsers .add_parser ('jazz' , help = 'create jazz' )
75+ music_create_rock_parser = music_create_subparsers .add_parser ('rock' , help = 'create rocks' )
7676
7777 @with_argparser (music_parser )
7878 def do_music (self , args : argparse .Namespace ) -> None :
@@ -84,10 +84,10 @@ def do_music(self, args: argparse.Namespace) -> None:
8484
8585 # Uses default flag prefix value (-)
8686 flag_parser = Cmd2ArgumentParser ()
87- flag_parser .add_argument ('-n' , '--normal_flag' , help = 'A normal flag' , action = 'store_true' )
88- flag_parser .add_argument ('-a' , '--append_flag' , help = 'Append flag' , action = 'append' )
89- flag_parser .add_argument ('-o' , '--append_const_flag' , help = 'Append const flag' , action = 'append_const' , const = True )
90- flag_parser .add_argument ('-c' , '--count_flag' , help = 'Count flag' , action = 'count' )
87+ flag_parser .add_argument ('-n' , '--normal_flag' , help = 'a normal flag' , action = 'store_true' )
88+ flag_parser .add_argument ('-a' , '--append_flag' , help = 'append flag' , action = 'append' )
89+ flag_parser .add_argument ('-o' , '--append_const_flag' , help = 'append const flag' , action = 'append_const' , const = True )
90+ flag_parser .add_argument ('-c' , '--count_flag' , help = 'count flag' , action = 'count' )
9191 flag_parser .add_argument ('-s' , '--suppressed_flag' , help = argparse .SUPPRESS , action = 'store_true' )
9292 flag_parser .add_argument ('-r' , '--remainder_flag' , nargs = argparse .REMAINDER , help = 'a remainder flag' )
9393
@@ -97,7 +97,7 @@ def do_flag(self, args: argparse.Namespace) -> None:
9797
9898 # Uses non-default flag prefix value (+)
9999 plus_flag_parser = Cmd2ArgumentParser (prefix_chars = '+' )
100- plus_flag_parser .add_argument ('+n' , '++normal_flag' , help = 'A normal flag' , action = 'store_true' )
100+ plus_flag_parser .add_argument ('+n' , '++normal_flag' , help = 'a normal flag' , action = 'store_true' )
101101
102102 @with_argparser (plus_flag_parser )
103103 def do_plus_flag (self , args : argparse .Namespace ) -> None :
@@ -251,6 +251,22 @@ def do_raise_completion_error(self, args: argparse.Namespace) -> None:
251251 def do_arg_tokens (self , args : argparse .Namespace ) -> None :
252252 pass
253253
254+ ############################################################################################################
255+ # Begin code related to mutually exclusive groups
256+ ############################################################################################################
257+ mutex_parser = Cmd2ArgumentParser ()
258+
259+ mutex_group = mutex_parser .add_mutually_exclusive_group (required = True )
260+ mutex_group .add_argument ('optional_pos' , help = 'the optional positional' , nargs = argparse .OPTIONAL )
261+ mutex_group .add_argument ('-f' , '--flag' , help = 'the flag arg' )
262+ mutex_group .add_argument ('-o' , '--other_flag' , help = 'the other flag arg' )
263+
264+ mutex_parser .add_argument ('last_arg' , help = 'the last arg' )
265+
266+ @with_argparser (mutex_parser )
267+ def do_mutex (self , args : argparse .Namespace ) -> None :
268+ pass
269+
254270
255271@pytest .fixture
256272def ac_app ():
@@ -271,8 +287,16 @@ def test_help(ac_app, command):
271287 assert out1 == out2
272288
273289
290+ def test_bad_subcommand_help (ac_app ):
291+ # These should give the same output because the second one isn't using a
292+ # real subcommand, so help will be called on the music command instead.
293+ out1 , err1 = run_cmd (ac_app , 'help music' )
294+ out2 , err2 = run_cmd (ac_app , 'help music fake' )
295+ assert out1 == out2
296+
297+
274298@pytest .mark .parametrize ('command, text, completions' , [
275- ('' , 'mu ' , ['music ' ]),
299+ ('' , 'mus ' , ['music ' ]),
276300 ('music' , 'cre' , ['create ' ]),
277301 ('music' , 'creab' , []),
278302 ('music create' , '' , ['jazz' , 'rock' ]),
@@ -770,6 +794,45 @@ def test_arg_tokens(ac_app, command_and_args, completions):
770794 assert ac_app .completion_matches == sorted (completions , key = ac_app .default_sort_key )
771795
772796
797+ @pytest .mark .parametrize ('command_and_args, text, output_contains, first_match' , [
798+ # Group isn't done. Hint will show for optional positional and no completions returned
799+ ('mutex' , '' , 'the optional positional' , None ),
800+
801+ # Group isn't done. Flag name will still complete.
802+ ('mutex' , '--fl' , '' , '--flag ' ),
803+
804+ # Group isn't done. Flag hint will show.
805+ ('mutex --flag' , '' , 'the flag arg' , None ),
806+
807+ # Group finished by optional positional. No flag name will complete.
808+ ('mutex pos_val' , '--fl' , '' , None ),
809+
810+ # Group finished by optional positional. Error will display trying to complete the flag's value.
811+ ('mutex pos_val --flag' , '' , 'f/--flag: not allowed with argument optional_pos' , None ),
812+
813+ # Group finished by --flag. Optional positional will be skipped and last_arg will show its hint.
814+ ('mutex --flag flag_val' , '' , 'the last arg' , None ),
815+
816+ # Group finished by --flag. Other flag won't complete.
817+ ('mutex --flag flag_val' , '--oth' , '' , None ),
818+
819+ # Group finished by --flag. Error will display trying to complete other flag's value.
820+ ('mutex --flag flag_val --other' , '' , '-o/--other_flag: not allowed with argument -f/--flag' , None ),
821+
822+ # Group finished by --flag. That same flag can be used again so it's hint will show.
823+ ('mutex --flag flag_val --flag' , '' , 'the flag arg' , None )
824+ ])
825+ def test_complete_mutex_group (ac_app , command_and_args , text , output_contains , first_match , capsys ):
826+ line = '{} {}' .format (command_and_args , text )
827+ endidx = len (line )
828+ begidx = endidx - len (text )
829+
830+ assert first_match == complete_tester (text , line , begidx , endidx , ac_app )
831+
832+ out , err = capsys .readouterr ()
833+ assert output_contains in out
834+
835+
773836def test_single_prefix_char ():
774837 from cmd2 .argparse_completer import _single_prefix_char
775838 parser = Cmd2ArgumentParser (prefix_chars = '-+' )
0 commit comments