Repository: python-cmd2/cmd2

Issue:
Don't print complete help text on argparse error
**ACArgumentParser** overrides **argparse.ArgumentParser's** `error()` function. One of its behaviors is to print all of the help text when there is a parsing error. Help text can be pretty long and has the tendency to scroll the actual error message off the screen.

I suggest we use the normal behavior of only printing the error and usage statement when a parsing error occurs.

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71feca67..4967891a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@
     * `StdSim.buffer.write()` now flushes when the wrapped stream uses line buffering and the bytes being written
       contain a newline or carriage return. This helps when `pyscript` is echoing the output of a shell command
       since the output will print at the same frequency as when the command is run in a terminal.
+    * **ACArgumentParser** no longer prints complete help text when a parsing error occurs since long help messages
+     scroll the actual error message off the screen.
 * **Python 3.4 EOL notice**
     * Python 3.4 reached its [end of life](https://www.python.org/dev/peps/pep-0429/) on March 18, 2019
     * This is the last release of `cmd2` which will support Python 3.4
@@ -27,7 +29,7 @@
     `argparse.Namespace` object they pass to the `do_*` methods. It is stored in an attribute called `__statement__`.
     This can be useful if a command function needs to know the command line for things like logging.
     * Added a `-t` option to the `load` command for automatically generating a transcript based on a script file
-    * When in a *pyscript*, the stdout and stderr streams of shell commands and processes being piped to are now
+    * When in a **pyscript**, the stdout and stderr streams of shell commands and processes being piped to are now
     captured and included in the ``CommandResult`` structure.
 * Potentially breaking changes
     * The following commands now write to stderr instead of stdout when printing an error. This will make catching
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index 7b466342..ac65185b 100644
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -908,7 +908,7 @@ class ACHelpFormatter(argparse.RawTextHelpFormatter):
                 # join lines into usage
                 usage = '\n'.join(lines)
 
-        # prefix with 'usage:'
+        # prefix with 'Usage:'
         return '%s%s\n\n' % (prefix, usage)
 
     def _format_action_invocation(self, action) -> str:
@@ -970,9 +970,6 @@ class ACHelpFormatter(argparse.RawTextHelpFormatter):
             result = super()._format_args(action, default_metavar)
         return result
 
-    def format_help(self):
-        return super().format_help() + '\n'
-
 
 # noinspection PyCompatibility
 class ACArgumentParser(argparse.ArgumentParser):
@@ -1005,7 +1002,7 @@ class ACArgumentParser(argparse.ArgumentParser):
 
     def error(self, message: str) -> None:
         """Custom error override. Allows application to control the error being displayed by argparse"""
-        if len(self._custom_error_message) > 0:
+        if self._custom_error_message:
             message = self._custom_error_message
             self._custom_error_message = ''
 
@@ -1019,9 +1016,9 @@ class ACArgumentParser(argparse.ArgumentParser):
                 formatted_message += '\n       ' + line
             linum += 1
 
-        sys.stderr.write(Fore.LIGHTRED_EX + '{}\n\n'.format(formatted_message) + Fore.RESET)
-        # sys.stderr.write('{}\n\n'.format(formatted_message))
-        self.print_help()
+        self.print_usage(sys.stderr)
+        sys.stderr.write(Fore.LIGHTRED_EX + '{}\n'.format(formatted_message) + Fore.RESET)
+
         sys.exit(1)
 
     def format_help(self) -> str:


Test patch:
diff --git a/tests/test_autocompletion.py b/tests/test_autocompletion.py
index 8aeac6b1..a5dafd2d 100644
--- a/tests/test_autocompletion.py
+++ b/tests/test_autocompletion.py
@@ -152,7 +152,7 @@ def test_autocomp_flags_narg_max(cmd2_app):
 
 def test_autcomp_narg_beyond_max(cmd2_app):
     out, err = run_cmd(cmd2_app, 'suggest -t movie -d 3 4 5')
-    assert 'Error: unrecognized arguments: 5' in err[0]
+    assert 'Error: unrecognized arguments: 5' in err[1]
 
 
 def test_autocomp_subcmd_nested(cmd2_app):
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 300e3ed9..37e8b60e 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -82,8 +82,8 @@ def test_base_argparse_help(base_app):
 
 def test_base_invalid_option(base_app):
     out, err = run_cmd(base_app, 'set -z')
-    assert 'Error: unrecognized arguments: -z' in err[0]
-    assert out[0] == 'Usage: set [-h] [-a] [-l] [param] [value]'
+    assert err[0] == 'Usage: set [-h] [-a] [-l] [param] [value]'
+    assert 'Error: unrecognized arguments: -z' in err[1]
 
 def test_base_shortcuts(base_app):
     out, err = run_cmd(base_app, 'shortcuts')
@@ -285,7 +285,7 @@ def test_pyscript_with_exception(base_app, request):
 
 def test_pyscript_requires_an_argument(base_app):
     out, err = run_cmd(base_app, "pyscript")
-    assert "the following arguments are required: script_path" in err[0]
+    assert "the following arguments are required: script_path" in err[1]
 
 
 def test_base_error(base_app):
@@ -314,7 +314,7 @@ def test_load_with_empty_args(base_app):
     out, err = run_cmd(base_app, 'load')
 
     # The load command requires a file path argument, so we should get an error message
-    assert "the following arguments are required" in err[0]
+    assert "the following arguments are required" in err[1]
     assert base_app.cmdqueue == []
 
 
@@ -448,7 +448,7 @@ def test_base_relative_load(base_app, request):
 
 def test_relative_load_requires_an_argument(base_app):
     out, err = run_cmd(base_app, '_relative_load')
-    assert 'Error: the following arguments' in err[0]
+    assert 'Error: the following arguments' in err[1]
     assert base_app.cmdqueue == []
 
 
@@ -1590,7 +1590,7 @@ def test_alias_create(base_app):
 
     # Use the alias
     out, err = run_cmd(base_app, 'fake')
-    assert "the following arguments are required: script_path" in err[0]
+    assert "the following arguments are required: script_path" in err[1]
 
     # See a list of aliases
     out, err = run_cmd(base_app, 'alias list')
@@ -1681,7 +1681,7 @@ def test_macro_create(base_app):
 
     # Use the macro
     out, err = run_cmd(base_app, 'fake')
-    assert "the following arguments are required: script_path" in err[0]
+    assert "the following arguments are required: script_path" in err[1]
 
     # See a list of macros
     out, err = run_cmd(base_app, 'macro list')