summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md18
-rwxr-xr-xREADME.md4
-rwxr-xr-xcmd2.py112
-rwxr-xr-xexamples/remove_unused.py8
4 files changed, 85 insertions, 57 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 82d978f5..6fa1b049 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,21 @@
+## 0.8.3 (TBD)
+* Bug Fixes
+ * Fixed ``help`` command not calling functions for help topics
+
+* Enhancements
+ * Tab completion has been overhauled and now supports completion of strings with quotes and spaces.
+ * Tab completion will automatically add an opening quote if a string with a space is completed.
+ * Added more control over tab completion behavior including the following flags. The use of these flags is documented in cmd2.py
+ * ``allow_appended_space``
+ * ``allow_closing_quote``
+ * ``display_entire_match``
+ * ``display_match_delimiter``
+
+* Attributes Changes
+ * ``exclude_from_help`` is now called ``hidden_commands`` since these commands hidden from things other than help, including tab completion
+ * ``excludeFromHistory`` is now called ``exclude_from_history``
+
+
## 0.8.2 (March 21, 2018)
* Bug Fixes
diff --git a/README.md b/README.md
index c73db019..f9c2aa1e 100755
--- a/README.md
+++ b/README.md
@@ -82,10 +82,10 @@ Instructions for implementing each feature follow.
- Searchable command history
- All commands will automatically be tracked in the session's history, unless the command is listed in Cmd's excludeFromHistory attribute.
+ All commands will automatically be tracked in the session's history, unless the command is listed in Cmd's exclude_from_history attribute.
The history is accessed through the `history` command.
If you wish to exclude some of your custom commands from the history, append their names
- to the list at `Cmd.ExcludeFromHistory`.
+ to the list at `Cmd.exclude_from_history`.
- Load commands from file, save to file, edit commands in file
diff --git a/cmd2.py b/cmd2.py
index 777570a6..ded7bd73 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -145,8 +145,8 @@ else:
orig_pyreadline_display = readline.rl.mode._display_completions
############################################################################################################
-# The following variables are used by tab-completion functions. They are reset each time complete()
-# is run, and it is up to the completer functions to set them on a case-by-case basis
+# The following variables are used by tab-completion functions. They are reset each time complete() is run
+# using set_completion_defaults() and it is up to completer functions to set them before returning results.
# For convenience, use the setter functions for these variables. The setters appear after the variables.
############################################################################################################
@@ -406,16 +406,12 @@ def basic_complete(text, line, begidx, endidx, match_against):
if len(tokens) == 0:
return []
- # Perform matching
+ # Perform matching and eliminate duplicates
completion_token = tokens[-1]
- matches = [cur_match for cur_match in match_against if cur_match.startswith(completion_token)]
+ matches = [cur_match for cur_match in set(match_against) if cur_match.startswith(completion_token)]
if len(matches) == 0:
return []
- # Eliminate duplicates
- matches_set = set(matches)
- matches = list(matches_set)
-
# We will only keep where the text value starts
starting_index = len(completion_token) - len(text)
completions = [cur_match[starting_index:] for cur_match in matches]
@@ -1399,9 +1395,11 @@ class Cmd(cmd.Cmd):
# Call super class constructor. Need to do it in this way for Python 2 and 3 compatibility
cmd.Cmd.__init__(self, completekey=completekey, stdin=stdin, stdout=stdout)
- # Commands to exclude from the help menu or history command
- self.exclude_from_help = ['do_eof', 'do_eos', 'do__relative_load']
- self.excludeFromHistory = '''history edit eof eos'''.split()
+ # Commands to exclude from the help menu and tab completion
+ self.hidden_commands = ['eof', 'eos', '_relative_load']
+
+ # Commands to exclude from the history command
+ self.exclude_from_history = '''history edit eof eos'''.split()
self._finalize_app_parameters()
@@ -1784,7 +1782,7 @@ class Cmd(cmd.Cmd):
return self.completion_matches[state]
# Check if a valid command was entered
- if command not in self.get_command_names():
+ if command not in self.get_visible_command_names():
# Check if this command should be run as a shell command
if self.default_to_shell and command in self._get_exes_in_path(command):
compfunc = functools.partial(path_complete)
@@ -1895,9 +1893,10 @@ class Cmd(cmd.Cmd):
self.completion_matches = [shortcut_to_restore + match for match in self.completion_matches]
else:
- # Complete the command against aliases and command names
- strs_to_match = list(self.aliases.keys())
- strs_to_match.extend(self.get_command_names())
+ # Complete token against aliases and command names
+ alias_names = set(self.aliases.keys())
+ visible_commands = set(self.get_visible_command_names())
+ strs_to_match = list(alias_names | visible_commands)
self.completion_matches = basic_complete(text, line, begidx, endidx, strs_to_match)
# Eliminate duplicates and sort
@@ -1924,13 +1923,28 @@ 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 get_visible_command_names(self):
+ """
+ Returns a sorted list of commands that have not been hidden
+ Any duplicates have been removed as well
+ """
+ commands = [cur_name[3:] for cur_name in set(self.get_names()) if cur_name.startswith('do_')]
+
+ # Remove the hidden commands
+ for name in self.hidden_commands:
+ if name in commands:
+ commands.remove(name)
+
+ commands.sort()
+ return commands
+
+ def get_help_topics(self):
+ """ Returns a sorted list of help topics with all duplicates removed """
+ return [name[5:] for name in set(self.get_names()) if name.startswith('help_')]
def complete_help(self, text, line, begidx, endidx):
"""
- Override of parent class method to handle tab completing subcommands
+ Override of parent class method to handle tab completing subcommands and not showing hidden commands
"""
# The command is the token at index 1 in the command line
@@ -1949,9 +1963,14 @@ class Cmd(cmd.Cmd):
# Get the index of the token being completed
index = len(tokens) - 1
- # Check if we are completing a command
+ # Check if we are completing a command or help topic
if index == cmd_index:
- completions = cmd.Cmd.complete_help(self, text, line, begidx, endidx)
+
+ # Complete token against topics and visible commands
+ topics = set(self.get_help_topics())
+ visible_commands = set(self.get_visible_command_names())
+ strs_to_match = list(topics | visible_commands)
+ completions = basic_complete(text, line, begidx, endidx, strs_to_match)
# Check if we are completing a subcommand
elif index == subcmd_index:
@@ -2309,7 +2328,7 @@ class Cmd(cmd.Cmd):
return self.default(statement)
# Since we have a valid command store it in the history
- if statement.parsed.command not in self.excludeFromHistory:
+ if statement.parsed.command not in self.exclude_from_history:
self.history.append(statement.parsed.raw)
try:
@@ -2514,7 +2533,7 @@ Usage: Usage: alias [<name> <value>]
index_dict = \
{
1: self.aliases,
- 2: self.get_command_names()
+ 2: self.get_visible_command_names()
}
return index_based_complete(text, line, begidx, endidx, index_dict, path_complete)
@@ -2572,6 +2591,9 @@ Usage: Usage: unalias [-a] name [name ...]
else:
# No special behavior needed, delegate to cmd base class do_help()
cmd.Cmd.do_help(self, funcname[3:])
+ else:
+ # This could be a help topic
+ cmd.Cmd.do_help(self, arglist[0])
else:
# Show a menu of what commands help can be gotten for
self._help_menu()
@@ -2579,39 +2601,27 @@ Usage: Usage: unalias [-a] name [name ...]
def _help_menu(self):
"""Show a list of commands which help can be displayed for.
"""
- # Get a list of all method names
- names = self.get_names()
-
- # Remove any command names which are explicitly excluded from the help menu
- for name in self.exclude_from_help:
- if name in names:
- names.remove(name)
+ # Get a sorted list of help topics
+ help_topics = self.get_help_topics()
cmds_doc = []
cmds_undoc = []
- help_dict = {}
- for name in names:
- if name[:5] == 'help_':
- help_dict[name[5:]] = 1
- names.sort()
- # There can be duplicates if routines overridden
- prevname = ''
- for name in names:
- if name[:3] == 'do_':
- if name == prevname:
- continue
- prevname = name
- command = name[3:]
- if command in help_dict:
- cmds_doc.append(command)
- del help_dict[command]
- elif getattr(self, name).__doc__:
- cmds_doc.append(command)
- else:
- cmds_undoc.append(command)
+
+ # Get a sorted list of visible command names
+ visible_commands = self.get_visible_command_names()
+
+ for command in visible_commands:
+ if command in help_topics:
+ cmds_doc.append(command)
+ help_topics.remove(command)
+ elif getattr(self, self._func_named(command)).__doc__:
+ cmds_doc.append(command)
+ else:
+ cmds_undoc.append(command)
+
self.poutput("%s\n" % str(self.doc_leader))
self.print_topics(self.doc_header, cmds_doc, 15, 80)
- self.print_topics(self.misc_header, list(help_dict.keys()), 15, 80)
+ self.print_topics(self.misc_header, help_topics, 15, 80)
self.print_topics(self.undoc_header, cmds_undoc, 15, 80)
def do_shortcuts(self, _):
diff --git a/examples/remove_unused.py b/examples/remove_unused.py
index 2de0e9b6..cf26fcff 100755
--- a/examples/remove_unused.py
+++ b/examples/remove_unused.py
@@ -2,8 +2,8 @@
# coding=utf-8
"""A simple example demonstrating how to remove unused commands.
-Commands can be removed from the help menu by appending their full command name (including "do_") to the
-"exclude_from_help" list. These commands will still exist and can be executed and help can be retrieved for them by
+Commands can be removed from help menu and tab completion by appending their command name to the hidden_commands list.
+These commands will still exist and can be executed and help can be retrieved for them by
name, they just won't clutter the help menu.
Commands can also be removed entirely by using Python's "del".
@@ -18,8 +18,8 @@ class RemoveUnusedBuiltinCommands(cmd2.Cmd):
def __init__(self):
cmd2.Cmd.__init__(self)
- # To hide commands from displaying in the help menu, add their function name to the exclude_from_help list
- self.exclude_from_help.append('do_py')
+ # To hide commands from displaying in the help menu, add them to the hidden_commands list
+ self.hidden_commands.append('py')
# To remove built-in commands entirely, delete their "do_*" function from the cmd2.Cmd class
del cmd2.Cmd.do_edit