119119# The argparse parser for the command
120120CMD_ATTR_ARGPARSER = 'argparser'
121121
122+ # Whether or not tokens are unquoted before sending to argparse
123+ CMD_ATTR_PRESERVE_QUOTES = 'preserve_quotes'
124+
122125
123126def categorize (func : Union [Callable , Iterable [Callable ]], category : str ) -> None :
124127 """Categorize a function.
@@ -225,8 +228,9 @@ def cmd_wrapper(cmd2_app, statement: Union[Statement, str]):
225228 # Set the command's help text as argparser.description (which can be None)
226229 cmd_wrapper .__doc__ = argparser .description
227230
228- # Mark this function as having an argparse ArgumentParser
231+ # Set some custom attributes for this command
229232 setattr (cmd_wrapper , CMD_ATTR_ARGPARSER , argparser )
233+ setattr (cmd_wrapper , CMD_ATTR_PRESERVE_QUOTES , preserve_quotes )
230234
231235 return cmd_wrapper
232236
@@ -283,8 +287,9 @@ def cmd_wrapper(cmd2_app, statement: Union[Statement, str]):
283287 # Set the command's help text as argparser.description (which can be None)
284288 cmd_wrapper .__doc__ = argparser .description
285289
286- # Mark this function as having an argparse ArgumentParser
290+ # Set some custom attributes for this command
287291 setattr (cmd_wrapper , CMD_ATTR_ARGPARSER , argparser )
292+ setattr (cmd_wrapper , CMD_ATTR_PRESERVE_QUOTES , preserve_quotes )
288293
289294 return cmd_wrapper
290295
@@ -1431,7 +1436,8 @@ def _completion_for_command(self, text: str, line: str, begidx: int,
14311436 if func is not None and argparser is not None :
14321437 import functools
14331438 compfunc = functools .partial (self ._autocomplete_default ,
1434- argparser = argparser )
1439+ argparser = argparser ,
1440+ preserve_quotes = getattr (func , CMD_ATTR_PRESERVE_QUOTES ))
14351441 else :
14361442 compfunc = self .completedefault
14371443
@@ -1588,13 +1594,21 @@ def complete(self, text: str, state: int) -> Optional[str]:
15881594 self .pexcept (e )
15891595 return None
15901596
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 ]:
15931599 """Default completion function for argparse commands"""
15941600 from .argparse_completer import AutoCompleter
15951601 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 )
15981612
15991613 def get_all_commands (self ) -> List [str ]:
16001614 """Return a list of all commands"""
@@ -2384,7 +2398,8 @@ def _alias_list(self, args: argparse.Namespace) -> None:
23842398 alias_parser = Cmd2ArgumentParser (description = alias_description , epilog = alias_epilog , prog = 'alias' )
23852399
23862400 # 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
23882403
23892404 # alias -> create
23902405 alias_create_help = "create or overwrite an alias"
@@ -2439,13 +2454,9 @@ def _alias_list(self, args: argparse.Namespace) -> None:
24392454 @with_argparser (alias_parser , preserve_quotes = True )
24402455 def do_alias (self , args : argparse .Namespace ) -> None :
24412456 """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 )
24492460
24502461 # ----- Macro subcommand functions -----
24512462
@@ -2564,7 +2575,8 @@ def _macro_list(self, args: argparse.Namespace) -> None:
25642575 macro_parser = Cmd2ArgumentParser (description = macro_description , epilog = macro_epilog , prog = 'macro' )
25652576
25662577 # 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
25682580
25692581 # macro -> create
25702582 macro_create_help = "create or overwrite a macro"
@@ -2641,13 +2653,9 @@ def _macro_list(self, args: argparse.Namespace) -> None:
26412653 @with_argparser (macro_parser , preserve_quotes = True )
26422654 def do_macro (self , args : argparse .Namespace ) -> None :
26432655 """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 )
26512659
26522660 def complete_help_command (self , text : str , line : str , begidx : int , endidx : int ) -> List [str ]:
26532661 """Completes the command argument of help"""
@@ -2658,49 +2666,34 @@ def complete_help_command(self, text: str, line: str, begidx: int, endidx: int)
26582666 strs_to_match = list (topics | visible_commands )
26592667 return utils .basic_complete (text , line , begidx , endidx , strs_to_match )
26602668
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"""
26732672
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 :
26822676 return []
26832677
2684- command = tokens [cmd_index ]
2685- matches = []
2686-
26872678 # Check if this command uses argparse
26882679 func = self .cmd_func (command )
26892680 argparser = getattr (func , CMD_ATTR_ARGPARSER , None )
2681+ if func is None or argparser is None :
2682+ return []
26902683
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' ]
26952686
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 )
26972690
26982691 help_parser = Cmd2ArgumentParser (description = "List available commands or provide "
26992692 "detailed help for a specific command" )
27002693 help_parser .add_argument ('command' , nargs = argparse .OPTIONAL , help = "command to retrieve help for" ,
27012694 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 )
27042697 help_parser .add_argument ('-v' , '--verbose' , action = 'store_true' ,
27052698 help = "print a list of all commands with descriptions of each" )
27062699
@@ -2724,7 +2717,7 @@ def do_help(self, args: argparse.Namespace) -> None:
27242717 if func is not None and argparser is not None :
27252718 from .argparse_completer import AutoCompleter
27262719 completer = AutoCompleter (argparser , self )
2727- tokens = [args .command ] + args .subcommand
2720+ tokens = [args .command ] + args .subcommands
27282721
27292722 # Set end to blank so the help output matches how it looks when "command -h" is used
27302723 self .poutput (completer .format_help (tokens ), end = '' )
@@ -2959,14 +2952,11 @@ def enable_completion():
29592952 choice = int (response )
29602953 if choice < 1 :
29612954 raise IndexError
2962- result = fulloptions [choice - 1 ][0 ]
2963- break
2955+ return fulloptions [choice - 1 ][0 ]
29642956 except (ValueError , IndexError ):
29652957 self .poutput ("{!r} isn't a valid choice. Pick a number between 1 and {}:" .format (
29662958 response , len (fulloptions )))
29672959
2968- return result
2969-
29702960 def _get_read_only_settings (self ) -> str :
29712961 """Return a summary report of read-only settings which the user cannot modify at runtime.
29722962
@@ -4121,8 +4111,7 @@ def disable_category(self, category: str, message_to_print: str) -> None:
41214111 if getattr (func , CMD_ATTR_HELP_CATEGORY , None ) == category :
41224112 self .disable_command (cmd_name , message_to_print )
41234113
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 :
41264115 """
41274116 Report when a disabled command has been run or had help called on it
41284117 :param args: not used
0 commit comments