119
119
# The argparse parser for the command
120
120
CMD_ATTR_ARGPARSER = 'argparser'
121
121
122
+ # Whether or not tokens are unquoted before sending to argparse
123
+ CMD_ATTR_PRESERVE_QUOTES = 'preserve_quotes'
124
+
122
125
123
126
def categorize (func : Union [Callable , Iterable [Callable ]], category : str ) -> None :
124
127
"""Categorize a function.
@@ -225,8 +228,9 @@ def cmd_wrapper(cmd2_app, statement: Union[Statement, str]):
225
228
# Set the command's help text as argparser.description (which can be None)
226
229
cmd_wrapper .__doc__ = argparser .description
227
230
228
- # Mark this function as having an argparse ArgumentParser
231
+ # Set some custom attributes for this command
229
232
setattr (cmd_wrapper , CMD_ATTR_ARGPARSER , argparser )
233
+ setattr (cmd_wrapper , CMD_ATTR_PRESERVE_QUOTES , preserve_quotes )
230
234
231
235
return cmd_wrapper
232
236
@@ -283,8 +287,9 @@ def cmd_wrapper(cmd2_app, statement: Union[Statement, str]):
283
287
# Set the command's help text as argparser.description (which can be None)
284
288
cmd_wrapper .__doc__ = argparser .description
285
289
286
- # Mark this function as having an argparse ArgumentParser
290
+ # Set some custom attributes for this command
287
291
setattr (cmd_wrapper , CMD_ATTR_ARGPARSER , argparser )
292
+ setattr (cmd_wrapper , CMD_ATTR_PRESERVE_QUOTES , preserve_quotes )
288
293
289
294
return cmd_wrapper
290
295
@@ -1431,7 +1436,8 @@ def _completion_for_command(self, text: str, line: str, begidx: int,
1431
1436
if func is not None and argparser is not None :
1432
1437
import functools
1433
1438
compfunc = functools .partial (self ._autocomplete_default ,
1434
- argparser = argparser )
1439
+ argparser = argparser ,
1440
+ preserve_quotes = getattr (func , CMD_ATTR_PRESERVE_QUOTES ))
1435
1441
else :
1436
1442
compfunc = self .completedefault
1437
1443
@@ -1588,13 +1594,21 @@ def complete(self, text: str, state: int) -> Optional[str]:
1588
1594
self .pexcept (e )
1589
1595
return None
1590
1596
1591
- def _autocomplete_default (self , text : str , line : str , begidx : int , endidx : int ,
1592
- argparser : argparse .ArgumentParser ) -> List [str ]:
1597
+ def _autocomplete_default (self , text : str , line : str , begidx : int , endidx : int , * ,
1598
+ argparser : argparse .ArgumentParser , preserve_quotes : bool ) -> List [str ]:
1593
1599
"""Default completion function for argparse commands"""
1594
1600
from .argparse_completer import AutoCompleter
1595
1601
completer = AutoCompleter (argparser , self )
1596
- tokens , _ = self .tokens_for_completion (line , begidx , endidx )
1597
- return completer .complete_command (tokens , text , line , begidx , endidx )
1602
+ tokens , raw_tokens = self .tokens_for_completion (line , begidx , endidx )
1603
+
1604
+ # To have tab-completion parsing match command line parsing behavior,
1605
+ # use preserve_quotes to determine if we parse the quoted or unquoted tokens.
1606
+ tokens_to_parse = raw_tokens if preserve_quotes else tokens
1607
+ return completer .complete_command (tokens_to_parse , text , line , begidx , endidx )
1608
+
1609
+ def get_names (self ):
1610
+ """Return an alphabetized list of names comprising the attributes of the cmd2 class instance."""
1611
+ return dir (self )
1598
1612
1599
1613
def get_all_commands (self ) -> List [str ]:
1600
1614
"""Return a list of all commands"""
@@ -2384,7 +2398,8 @@ def _alias_list(self, args: argparse.Namespace) -> None:
2384
2398
alias_parser = Cmd2ArgumentParser (description = alias_description , epilog = alias_epilog , prog = 'alias' )
2385
2399
2386
2400
# Add subcommands to alias
2387
- alias_subparsers = alias_parser .add_subparsers ()
2401
+ alias_subparsers = alias_parser .add_subparsers (dest = 'subcommand' )
2402
+ alias_subparsers .required = True
2388
2403
2389
2404
# alias -> create
2390
2405
alias_create_help = "create or overwrite an alias"
@@ -2439,13 +2454,9 @@ def _alias_list(self, args: argparse.Namespace) -> None:
2439
2454
@with_argparser (alias_parser , preserve_quotes = True )
2440
2455
def do_alias (self , args : argparse .Namespace ) -> None :
2441
2456
"""Manage aliases"""
2442
- func = getattr (args , 'func' , None )
2443
- if func is not None :
2444
- # Call whatever subcommand function was selected
2445
- func (self , args )
2446
- else :
2447
- # noinspection PyTypeChecker
2448
- self .do_help ('alias' )
2457
+ # Call whatever subcommand function was selected
2458
+ func = getattr (args , 'func' )
2459
+ func (self , args )
2449
2460
2450
2461
# ----- Macro subcommand functions -----
2451
2462
@@ -2564,7 +2575,8 @@ def _macro_list(self, args: argparse.Namespace) -> None:
2564
2575
macro_parser = Cmd2ArgumentParser (description = macro_description , epilog = macro_epilog , prog = 'macro' )
2565
2576
2566
2577
# Add subcommands to macro
2567
- macro_subparsers = macro_parser .add_subparsers ()
2578
+ macro_subparsers = macro_parser .add_subparsers (dest = 'subcommand' )
2579
+ macro_subparsers .required = True
2568
2580
2569
2581
# macro -> create
2570
2582
macro_create_help = "create or overwrite a macro"
@@ -2641,13 +2653,9 @@ def _macro_list(self, args: argparse.Namespace) -> None:
2641
2653
@with_argparser (macro_parser , preserve_quotes = True )
2642
2654
def do_macro (self , args : argparse .Namespace ) -> None :
2643
2655
"""Manage macros"""
2644
- func = getattr (args , 'func' , None )
2645
- if func is not None :
2646
- # Call whatever subcommand function was selected
2647
- func (self , args )
2648
- else :
2649
- # noinspection PyTypeChecker
2650
- self .do_help ('macro' )
2656
+ # Call whatever subcommand function was selected
2657
+ func = getattr (args , 'func' )
2658
+ func (self , args )
2651
2659
2652
2660
def complete_help_command (self , text : str , line : str , begidx : int , endidx : int ) -> List [str ]:
2653
2661
"""Completes the command argument of help"""
@@ -2658,49 +2666,34 @@ def complete_help_command(self, text: str, line: str, begidx: int, endidx: int)
2658
2666
strs_to_match = list (topics | visible_commands )
2659
2667
return utils .basic_complete (text , line , begidx , endidx , strs_to_match )
2660
2668
2661
- def complete_help_subcommand (self , text : str , line : str , begidx : int , endidx : int ) -> List [str ]:
2662
- """Completes the subcommand argument of help"""
2663
-
2664
- # Get all tokens through the one being completed
2665
- tokens , _ = self .tokens_for_completion (line , begidx , endidx )
2666
-
2667
- if not tokens :
2668
- return []
2669
-
2670
- # Must have at least 3 args for 'help command subcommand'
2671
- if len (tokens ) < 3 :
2672
- return []
2669
+ def complete_help_subcommands (self , text : str , line : str , begidx : int , endidx : int ,
2670
+ arg_tokens : Dict [str , List [str ]]) -> List [str ]:
2671
+ """Completes the subcommands argument of help"""
2673
2672
2674
- # Find where the command is by skipping past any flags
2675
- cmd_index = 1
2676
- for cur_token in tokens [cmd_index :]:
2677
- if not cur_token .startswith ('-' ):
2678
- break
2679
- cmd_index += 1
2680
-
2681
- if cmd_index >= len (tokens ):
2673
+ # Make sure we have a command whose subcommands we will complete
2674
+ command = arg_tokens ['command' ][0 ]
2675
+ if not command :
2682
2676
return []
2683
2677
2684
- command = tokens [cmd_index ]
2685
- matches = []
2686
-
2687
2678
# Check if this command uses argparse
2688
2679
func = self .cmd_func (command )
2689
2680
argparser = getattr (func , CMD_ATTR_ARGPARSER , None )
2681
+ if func is None or argparser is None :
2682
+ return []
2690
2683
2691
- if func is not None and argparser is not None :
2692
- from .argparse_completer import AutoCompleter
2693
- completer = AutoCompleter (argparser , self )
2694
- matches = completer .complete_command_help (tokens [cmd_index :], text , line , begidx , endidx )
2684
+ # Combine the command and its subcommand tokens for the AutoCompleter
2685
+ tokens = [command ] + arg_tokens ['subcommands' ]
2695
2686
2696
- return matches
2687
+ from .argparse_completer import AutoCompleter
2688
+ completer = AutoCompleter (argparser , self )
2689
+ return completer .complete_subcommand_help (tokens , text , line , begidx , endidx )
2697
2690
2698
2691
help_parser = Cmd2ArgumentParser (description = "List available commands or provide "
2699
2692
"detailed help for a specific command" )
2700
2693
help_parser .add_argument ('command' , nargs = argparse .OPTIONAL , help = "command to retrieve help for" ,
2701
2694
completer_method = complete_help_command )
2702
- help_parser .add_argument ('subcommand ' , nargs = argparse .REMAINDER , help = "subcommand to retrieve help for" ,
2703
- completer_method = complete_help_subcommand )
2695
+ help_parser .add_argument ('subcommands ' , nargs = argparse .REMAINDER , help = "subcommand(s) to retrieve help for" ,
2696
+ completer_method = complete_help_subcommands )
2704
2697
help_parser .add_argument ('-v' , '--verbose' , action = 'store_true' ,
2705
2698
help = "print a list of all commands with descriptions of each" )
2706
2699
@@ -2724,7 +2717,7 @@ def do_help(self, args: argparse.Namespace) -> None:
2724
2717
if func is not None and argparser is not None :
2725
2718
from .argparse_completer import AutoCompleter
2726
2719
completer = AutoCompleter (argparser , self )
2727
- tokens = [args .command ] + args .subcommand
2720
+ tokens = [args .command ] + args .subcommands
2728
2721
2729
2722
# Set end to blank so the help output matches how it looks when "command -h" is used
2730
2723
self .poutput (completer .format_help (tokens ), end = '' )
@@ -2959,14 +2952,11 @@ def enable_completion():
2959
2952
choice = int (response )
2960
2953
if choice < 1 :
2961
2954
raise IndexError
2962
- result = fulloptions [choice - 1 ][0 ]
2963
- break
2955
+ return fulloptions [choice - 1 ][0 ]
2964
2956
except (ValueError , IndexError ):
2965
2957
self .poutput ("{!r} isn't a valid choice. Pick a number between 1 and {}:" .format (
2966
2958
response , len (fulloptions )))
2967
2959
2968
- return result
2969
-
2970
2960
def _get_read_only_settings (self ) -> str :
2971
2961
"""Return a summary report of read-only settings which the user cannot modify at runtime.
2972
2962
@@ -4121,8 +4111,7 @@ def disable_category(self, category: str, message_to_print: str) -> None:
4121
4111
if getattr (func , CMD_ATTR_HELP_CATEGORY , None ) == category :
4122
4112
self .disable_command (cmd_name , message_to_print )
4123
4113
4124
- # noinspection PyUnusedLocal
4125
- def _report_disabled_command_usage (self , * args , message_to_print : str , ** kwargs ) -> None :
4114
+ def _report_disabled_command_usage (self , * _args , message_to_print : str , ** _kwargs ) -> None :
4126
4115
"""
4127
4116
Report when a disabled command has been run or had help called on it
4128
4117
:param args: not used
0 commit comments