diff options
Diffstat (limited to 'cmd2.py')
-rwxr-xr-x | cmd2.py | 152 |
1 files changed, 135 insertions, 17 deletions
@@ -164,6 +164,27 @@ def set_use_arg_list(val): USE_ARG_LIST = val +# noinspection PyUnusedLocal +def basic_complete(text, line, begidx, endidx, match_against): + """ + Performs tab completion against a list + :param text: str - the string prefix we are attempting to match (all returned matches must begin with it) + :param line: str - the current input line with leading whitespace removed + :param begidx: int - the beginning index of the prefix text + :param endidx: int - the ending index of the prefix text + :param match_against: iterable - the list being matched against + :return: List[str] - a list of possible tab completions + """ + completions = [cur_str for cur_str in match_against if cur_str.startswith(text)] + + # If there is only 1 match and it's at the end of the line, then add a space + if len(completions) == 1 and endidx == len(line): + completions[0] += ' ' + + completions.sort() + return completions + + def flag_based_complete(text, line, begidx, endidx, flag_dict, all_else=None): """ Tab completes based on a particular flag preceding the token being completed @@ -500,7 +521,7 @@ def with_argparser_and_unknown_args(argparser): argparser.prog = func.__name__[3:] # If the description has not been set, then use the method docstring if one exists - if not argparser.description and func.__doc__: + if argparser.description is None and func.__doc__: argparser.description = func.__doc__ cmd_wrapper.__doc__ = argparser.format_help() @@ -539,7 +560,7 @@ def with_argparser(argparser): argparser.prog = func.__name__[3:] # If the description has not been set, then use the method docstring if one exists - if not argparser.description and func.__doc__: + if argparser.description is None and func.__doc__: argparser.description = func.__doc__ cmd_wrapper.__doc__ = argparser.format_help() @@ -1024,6 +1045,7 @@ class Cmd(cmd.Cmd): prefixParser = pyparsing.Empty() redirector = '>' # for sending output to file shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} + aliases = dict() terminators = [';'] # make sure your terminators are not in legalChars! # Attributes which are NOT dynamically settable at runtime @@ -1113,7 +1135,8 @@ class Cmd(cmd.Cmd): legalChars=self.legalChars, commentGrammars=self.commentGrammars, commentInProgress=self.commentInProgress, blankLinesAllowed=self.blankLinesAllowed, prefixParser=self.prefixParser, - preparse=self.preparse, postparse=self.postparse, shortcuts=self.shortcuts) + preparse=self.preparse, postparse=self.postparse, aliases=self.aliases, + shortcuts=self.shortcuts) self._transcript_files = transcript_files # Used to enable the ability for a Python script to quit the application @@ -1391,20 +1414,11 @@ class Cmd(cmd.Cmd): self.completion_matches = compfunc(text, line, begidx, endidx) else: - # Complete the command against command names and shortcuts. By design, shortcuts that start with - # symbols not in self.identchars won't be tab completed since they are handled in the above if - # statement. This includes shortcuts like: ?, !, @, @@ - strs_to_match = [] - - # If a command has been started, then match against shortcuts. This keeps shortcuts out of the - # full list of commands that show up when tab completion is done on an empty line. - if len(line) > 0: - for (shortcut, expansion) in self.shortcuts: - strs_to_match.append(shortcut) + # Complete the command against aliases and command names + strs_to_match = list(self.aliases.keys()) - # Get command names - do_text = 'do_' + text - strs_to_match.extend([cur_name[3:] for cur_name in self.get_names() if cur_name.startswith(do_text)]) + # Add command names + strs_to_match.extend(self.get_command_names()) # Perform matching completions = [cur_str for cur_str in strs_to_match if cur_str.startswith(text)] @@ -1420,6 +1434,10 @@ class Cmd(cmd.Cmd): except IndexError: return None + def get_command_names(self): + """ Returns a list of commands """ + return [cur_name[3:] for cur_name in self.get_names() if cur_name.startswith('do_')] + def complete_help(self, text, line, begidx, endidx): """ Override of parent class method to handle tab completing subcommands @@ -1538,6 +1556,12 @@ class Cmd(cmd.Cmd): # Deal with empty line or all whitespace line return None, None, line + # Handle aliases + for cur_alias in self.aliases: + if line == cur_alias or line.startswith(cur_alias + ' '): + line = line.replace(cur_alias, self.aliases[cur_alias], 1) + break + # Expand command shortcuts to the full command name for (shortcut, expansion) in self.shortcuts: if line.startswith(shortcut): @@ -1923,6 +1947,92 @@ class Cmd(cmd.Cmd): return stop @with_argument_list + def do_alias(self, arglist): + """Define or display aliases + +Usage: Usage: alias [<name> <value>] + Where: + name - name of the alias being added or edited + value - what the alias will be resolved to + this can contain spaces and does not need to be quoted + + Without arguments, `alias' prints a list of all aliases in a resuable form + + Example: alias ls !ls -lF +""" + # If no args were given, then print a list of current aliases + if len(arglist) == 0: + for cur_alias in self.aliases: + self.poutput("alias {} {}".format(cur_alias, self.aliases[cur_alias])) + + # The user is creating an alias + elif len(arglist) >= 2: + name = arglist[0] + value = ' '.join(arglist[1:]) + + # Make sure the alias does not match an existing command + cmd_func = self._func_named(name) + if cmd_func is not None: + self.perror("Alias names cannot match an existing command: {!r}".format(name), traceback_war=False) + return + + # Check for a valid name + for cur_char in name: + if cur_char not in self.identchars: + self.perror("Alias names can only contain the following characters: {}".format(self.identchars), + traceback_war=False) + return + + # Set the alias + self.aliases[name] = value + self.poutput("Alias created") + + else: + self.do_help('alias') + + def complete_alias(self, text, line, begidx, endidx): + """ Tab completion for alias """ + index_dict = \ + { + 1: self.aliases, + 2: self.get_command_names() + } + return index_based_complete(text, line, begidx, endidx, index_dict, path_complete) + + @with_argument_list + def do_unalias(self, arglist): + """Unsets aliases + +Usage: Usage: unalias [-a] name [name ...] + Where: + name - name of the alias being unset + + Options: + -a remove all alias definitions +""" + if len(arglist) == 0: + self.do_help('unalias') + + if '-a' in arglist: + self.aliases.clear() + self.poutput("All aliases cleared") + + else: + # Get rid of duplicates + arglist = list(set(arglist)) + + for cur_arg in arglist: + if cur_arg in self.aliases: + del self.aliases[cur_arg] + self.poutput("Alias {!r} cleared".format(cur_arg)) + else: + self.perror("Alias {!r} does not exist".format(cur_arg), traceback_war=False) + + def complete_unalias(self, text, line, begidx, endidx): + """ Tab completion for unalias """ + return basic_complete(text, line, begidx, endidx, self.aliases) + + @with_argument_list def do_help(self, arglist): """List available commands with "help" or detailed help with "help cmd".""" if arglist: @@ -2747,12 +2857,13 @@ class ParserManager: Class which encapsulates all of the pyparsing parser functionality for cmd2 in a single location. """ def __init__(self, redirector, terminators, multilineCommands, legalChars, commentGrammars, commentInProgress, - blankLinesAllowed, prefixParser, preparse, postparse, shortcuts): + blankLinesAllowed, prefixParser, preparse, postparse, aliases, shortcuts): """Creates and uses parsers for user input according to app's parameters.""" self.commentGrammars = commentGrammars self.preparse = preparse self.postparse = postparse + self.aliases = aliases self.shortcuts = shortcuts self.main_parser = self._build_main_parser(redirector=redirector, terminators=terminators, @@ -2854,6 +2965,13 @@ class ParserManager: s = self.preparse(raw) s = self.input_source_parser.transformString(s.lstrip()) s = self.commentGrammars.transformString(s) + + # Handle aliases + for cur_alias in self.aliases: + if s == cur_alias or s.startswith(cur_alias + ' '): + s = s.replace(cur_alias, self.aliases[cur_alias], 1) + break + for (shortcut, expansion) in self.shortcuts: if s.startswith(shortcut): s = s.replace(shortcut, expansion + ' ', 1) |