Repository: python-cmd2/cmd2

Issue:
history and list commands are largely redundant
The **history** and **list** commands largely have the same basic functionality of displaying commands from the history. 

There is a partial overlap in the functionality where they both support some of the same features.  But each also has at least one feature that the other doesn't have.

It would probably be more convenient for end users to combine all of the functionality into one common **history** command and remove the **list** command.

Discussion:

Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 98aa2cf0..2324522f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,6 +1,17 @@
 News
 ====
 
+0.7.3
+-----
+
+*Release date: TBD*
+
+* Bug fixes
+    * Fixed a bug in display a span of history items when only an end index is supplied
+* Enhancements
+    * Added the ability to exclude commands from the help menu (**eof** included by default)
+    * Redundant list command removed and features merged into history command
+
 0.7.2
 -----
 
diff --git a/cmd2.py b/cmd2.py
index 4befe019..3755ef56 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -1121,9 +1121,12 @@ class Cmd(cmd.Cmd):
         Commands may be terminated with: {}
         Command-line arguments allowed: {}
         Output redirection and pipes allowed: {}
-        Settable parameters: {}\n""".format(not self.case_insensitive, str(self.terminators),
-                                            self.allow_cli_args,
-                                            self.allow_redirection, ' '.join(self.settable)))
+        Parsing of @options commands:
+            Use POSIX-style argument parser (vs Windows): {}
+            Strip Quotes when using Windows-style argument parser: {}
+            Use a list of arguments instead of a single argument string: {}
+        \n""".format(not self.case_insensitive, str(self.terminators), self.allow_cli_args, self.allow_redirection,
+                     POSIX_SHLEX, STRIP_QUOTES_FOR_NON_POSIX, USE_ARG_LIST))
 
     def do_help(self, arg):
         """List available commands with "help" or detailed help with "help cmd"."""
@@ -1592,7 +1595,8 @@ class Cmd(cmd.Cmd):
 
         | no arg:         list all
         | arg is integer: list one history item, by index
-        | arg is string:  string search
+        | a..b, a:b, a:, ..b -> list history items by a span of indices (inclusive)
+        | arg is string:  list all commands matching string search
         | arg is /enclosed in forward-slashes/: regular expression search
         """
         # If arguments are being passed as a list instead of as a string
@@ -1602,10 +1606,20 @@ class Cmd(cmd.Cmd):
             else:
                 arg = ''
 
+        # If an argument was supplied, then retrieve partial contents of the history
         if arg:
-            history = self.history.get(arg)
+            # If a character indicating a slice is present, retrieve a slice of the history
+            if '..' in arg or ':' in arg:
+                # Get a slice of history
+                history = self.history.span(arg)
+            else:
+                # Get item(s) from history by index or string search
+                history = self.history.get(arg)
         else:
+            # If no arg given, then retrieve the entire history
             history = self.history
+
+        # Display the history items retrieved
         for hi in history:
             if opts.script:
                 self.poutput(hi)
@@ -1628,38 +1642,6 @@ class Cmd(cmd.Cmd):
         except IndexError:
             return None
 
-    def do_list(self, arg):
-        """list [arg]: lists command(s) from history in a flexible/searchable way.
-
-        :param arg: str - behavior varies as follows:
-
-        * no arg -> list most recent command
-        * arg is integer -> list one history item, by index
-        * a..b, a:b, a:, ..b -> list spans from a (or start) to b (or end)
-        * arg is string -> list all commands matching string search
-        * arg is /enclosed in forward-slashes/ -> regular expression search
-        """
-        try:
-            history = self.history.span(arg or '-1')
-        except IndexError:
-            history = self.history.search(arg)
-        for hi in history:
-            self.poutput(hi.pr())
-
-    def help_list(self):
-        """Print help for do_list()."""
-        help_str = """Lists command(s) from history in a flexible/searchable way.
-
-    Usage:  list [arg]
-
-    Where arg is:
-    no arg                               -> list most recent command
-    arg is integer                       -> list one history item, by index
-    a..b, a:b, a:, ..b                   -> list spans from a (or start) to b (or end)
-    arg is string                        -> list all commands matching string search
-    arg is /enclosed in forward-slashes/ -> regular expression search"""
-        self.stdout.write("{}\n".format(help_str))
-
     def do_edit(self, arg):
         """Edit a file or command in a text editor.
 
@@ -2170,7 +2152,7 @@ class History(list):
             raise IndexError
         if not results.group('separator'):
             return [self[self._to_index(results.group('start'))]]
-        start = self._to_index(results.group('start'))
+        start = self._to_index(results.group('start')) or 0  # Ensure start is not None
         end = self._to_index(results.group('end'))
         reverse = False
         if end is not None:
diff --git a/docs/freefeatures.rst b/docs/freefeatures.rst
index 3c740c00..5e354549 100644
--- a/docs/freefeatures.rst
+++ b/docs/freefeatures.rst
@@ -220,8 +220,6 @@ also provide `bash-like history list editing`_.
 
 .. automethod:: cmd2.Cmd.do_history
 
-.. automethod:: cmd2.Cmd.do_list
-
 .. automethod:: cmd2.Cmd.do_run
 
 Quitting the application
diff --git a/examples/exampleSession.txt b/examples/exampleSession.txt
index 893d7b59..62c130f0 100644
--- a/examples/exampleSession.txt
+++ b/examples/exampleSession.txt
@@ -3,8 +3,8 @@
 
 Documented commands (type help <topic>):
 ========================================
-_relative_load  edit  help     list  orate  py    run   say  shell      show
-cmdenvironment  eof   history  load  pause  quit  save  set  shortcuts  speak
+_relative_load  edit  history  orate  py    run   say  shell      show
+cmdenvironment  help  load     pause  quit  save  set  shortcuts  speak
 
 (Cmd) help say
 Repeats what you tell me to.


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 3977de5f..41bd007a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -15,8 +15,8 @@ import cmd2
 # Help text for base cmd2.Cmd application
 BASE_HELP = """Documented commands (type help <topic>):
 ========================================
-_relative_load  edit  history  load   py    run   set    shortcuts
-cmdenvironment  help  list     pause  quit  save  shell  show
+_relative_load  edit  history  pause  quit  save  shell      show
+cmdenvironment  help  load     py     run   set   shortcuts
 """
 
 # Help text for the history command
@@ -24,7 +24,8 @@ HELP_HISTORY = """history [arg]: lists past commands issued
 
         | no arg:         list all
         | arg is integer: list one history item, by index
-        | arg is string:  string search
+        | a..b, a:b, a:, ..b -> list history items by a span of indices (inclusive)
+        | arg is string:  list all commands matching string search
         | arg is /enclosed in forward-slashes/: regular expression search
 
 Usage: history [options] (limit on which commands to include)
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index ce47ef47..5783d944 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -142,48 +142,62 @@ shortcuts
 """)
     assert out == expected
 
-
-def test_base_list(base_app):
+def test_history_with_string_argument(base_app):
     run_cmd(base_app, 'help')
     run_cmd(base_app, 'shortcuts')
-    out = run_cmd(base_app, 'list')
+    run_cmd(base_app, 'help history')
+    out = run_cmd(base_app, 'history help')
     expected = normalize("""
--------------------------[2]
-shortcuts
+-------------------------[1]
+help
+-------------------------[3]
+help history
 """)
     assert out == expected
 
 
-def test_list_with_string_argument(base_app):
+def test_history_with_integer_argument(base_app):
     run_cmd(base_app, 'help')
     run_cmd(base_app, 'shortcuts')
-    run_cmd(base_app, 'help list')
-    out = run_cmd(base_app, 'list help')
+    out = run_cmd(base_app, 'history 1')
     expected = normalize("""
 -------------------------[1]
 help
--------------------------[3]
-help list
 """)
     assert out == expected
 
 
-def test_list_with_integer_argument(base_app):
+def test_history_with_integer_span(base_app):
     run_cmd(base_app, 'help')
     run_cmd(base_app, 'shortcuts')
-    out = run_cmd(base_app, 'list 1')
+    run_cmd(base_app, 'help history')
+    out = run_cmd(base_app, 'history 1..2')
     expected = normalize("""
 -------------------------[1]
 help
+-------------------------[2]
+shortcuts
 """)
     assert out == expected
 
+def test_history_with_span_start(base_app):
+    run_cmd(base_app, 'help')
+    run_cmd(base_app, 'shortcuts')
+    run_cmd(base_app, 'help history')
+    out = run_cmd(base_app, 'history 2:')
+    expected = normalize("""
+-------------------------[2]
+shortcuts
+-------------------------[3]
+help history
+""")
+    assert out == expected
 
-def test_list_with_integer_span(base_app):
+def test_history_with_span_end(base_app):
     run_cmd(base_app, 'help')
     run_cmd(base_app, 'shortcuts')
-    run_cmd(base_app, 'help list')
-    out = run_cmd(base_app, 'list 1..2')
+    run_cmd(base_app, 'help history')
+    out = run_cmd(base_app, 'history :2')
     expected = normalize("""
 -------------------------[1]
 help
@@ -201,17 +215,13 @@ def test_base_cmdenvironment(base_app):
         Commands may be terminated with: [';']
         Command-line arguments allowed: True
         Output redirection and pipes allowed: True
+        Parsing of @options commands:
+            Use POSIX-style argument parser (vs Windows): False
+            Strip Quotes when using Windows-style argument parser: True
+            Use a list of arguments instead of a single argument string: False
+            
 """)
-    assert out[:4] == expected[:4]
-    assert out[4].strip().startswith('Settable parameters: ')
-
-    # Settable parameters can be listed in any order, so need to validate carefully using unordered sets
-    settable_params = {'continuation_prompt', 'default_file_name', 'prompt', 'abbrev', 'quiet', 'case_insensitive',
-                       'colors', 'echo', 'timing', 'editor', 'feedback_to_output', 'debug', 'autorun_on_edit',
-                       'locals_in_py'}
-    out_params = set(out[4].split("Settable parameters: ")[1].split())
-    assert settable_params == out_params
-
+    assert out == expected
 
 def test_base_load(base_app, request):
     test_dir = os.path.dirname(request.module.__file__)
diff --git a/tests/test_transcript.py b/tests/test_transcript.py
index c31b519d..03fec92a 100644
--- a/tests/test_transcript.py
+++ b/tests/test_transcript.py
@@ -106,9 +106,8 @@ def test_base_with_transcript(_cmdline_app):
 
 Documented commands (type help <topic>):
 ========================================
-_relative_load  help     load   py    save  shell      speak
-cmdenvironment  history  orate  quit  say   shortcuts
-edit            list     pause  run   set   show
+_relative_load  edit  history  orate  py    run   say  shell      show
+cmdenvironment  help  load     pause  quit  save  set  shortcuts  speak
 
 (Cmd) help say
 Repeats what you tell me to.
diff --git a/tests/transcript.txt b/tests/transcript.txt
index 013e2d0f..d0fd86a6 100644
--- a/tests/transcript.txt
+++ b/tests/transcript.txt
@@ -2,9 +2,8 @@
 
 Documented commands (type help <topic>):
 ========================================
-_relative_load  help     load   py    save  shell      speak
-cmdenvironment  history  orate  quit  say   shortcuts
-edit            list     pause  run   set   show
+_relative_load  edit  history  orate  py    run   say  shell      show
+cmdenvironment  help  load     pause  quit  save  set  shortcuts  speak
 
 (Cmd) help say
 Repeats what you tell me to.