Repository: aws/aws-cli

Issue:
Make `aws -h` output suggest you also run `aws help`
Every time I use this tool I always run `aws -h` and get a not very helpful usage message. If it suggested I also try `aws help` it'd be much more useful.



Discussion:
I also suggest `aws --help` doing the same thing, and running `aws` on its own. Also update the github README.rst file to indicate you should run `aws help` as part of the getting started guide.


Resolving patch:
diff --git a/awscli/argparser.py b/awscli/argparser.py
--- a/awscli/argparser.py
+++ b/awscli/argparser.py
@@ -16,6 +16,19 @@
 from difflib import get_close_matches
 
 
+HELP_BLURB = (
+    "To see help text, you can run:\n"
+    "\n"
+    "  aws help\n"
+    "  aws <command> help\n"
+    "  aws <command> <subcommand> help\n"
+)
+USAGE = (
+    "aws [options] <command> <subcommand> [<subcommand> ...] [parameters]\n"
+    "%s" % HELP_BLURB
+)
+
+
 class CLIArgParser(argparse.ArgumentParser):
     Formatter = argparse.RawTextHelpFormatter
 
@@ -71,13 +84,13 @@ class MainArgParser(CLIArgParser):
     Formatter = argparse.RawTextHelpFormatter
 
     def __init__(self, command_table, version_string,
-                 description, usage, argument_table):
+                 description, argument_table):
         super(MainArgParser, self).__init__(
             formatter_class=self.Formatter,
             add_help=False,
             conflict_handler='resolve',
             description=description,
-            usage=usage)
+            usage=USAGE)
         self._build(command_table, version_string, argument_table)
 
     def _create_choice_help(self, choices):
@@ -98,14 +111,12 @@ def _build(self, command_table, version_string, argument_table):
 
 class ServiceArgParser(CLIArgParser):
 
-    Usage = ("aws [options] <command> <subcommand> [parameters]")
-
     def __init__(self, operations_table, service_name):
         super(ServiceArgParser, self).__init__(
             formatter_class=argparse.RawTextHelpFormatter,
             add_help=False,
             conflict_handler='resolve',
-            usage=self.Usage)
+            usage=USAGE)
         self._build(operations_table)
         self._service_name = service_name
 
@@ -115,7 +126,6 @@ def _build(self, operations_table):
 
 class ArgTableArgParser(CLIArgParser):
     """CLI arg parser based on an argument table."""
-    Usage = ("aws [options] <command> <subcommand> [parameters]")
 
     def __init__(self, argument_table, command_table=None):
         # command_table is an optional subcommand_table.  If it's passed
@@ -124,7 +134,7 @@ def __init__(self, argument_table, command_table=None):
         super(ArgTableArgParser, self).__init__(
             formatter_class=self.Formatter,
             add_help=False,
-            usage=self.Usage,
+            usage=USAGE,
             conflict_handler='resolve')
         if command_table is None:
             command_table = {}
diff --git a/awscli/clidriver.py b/awscli/clidriver.py
--- a/awscli/clidriver.py
+++ b/awscli/clidriver.py
@@ -27,6 +27,7 @@
 from awscli.argparser import MainArgParser
 from awscli.argparser import ServiceArgParser
 from awscli.argparser import ArgTableArgParser
+from awscli.argparser import USAGE
 from awscli.help import ProviderHelpCommand
 from awscli.help import ServiceHelpCommand
 from awscli.help import OperationHelpCommand
@@ -157,7 +158,6 @@ def _create_parser(self):
         parser = MainArgParser(
             command_table, self.session.user_agent(),
             cli_data.get('description', None),
-            cli_data.get('synopsis', None),
             self._get_argument_table())
         return parser
 
@@ -183,8 +183,9 @@ def main(self, args=None):
             self._emit_session_event()
             return command_table[parsed_args.command](remaining, parsed_args)
         except UnknownArgumentError as e:
+            sys.stderr.write("usage: %s\n" % USAGE)
+            sys.stderr.write(str(e))
             sys.stderr.write("\n")
-            sys.stderr.write(str(e) + '\n')
             return 255
         except NoRegionError as e:
             msg = ('%s You can also configure your region by running '


Test patch:
diff --git a/tests/unit/test_clidriver.py b/tests/unit/test_clidriver.py
--- a/tests/unit/test_clidriver.py
+++ b/tests/unit/test_clidriver.py
@@ -30,6 +30,7 @@
 from awscli.clidriver import ServiceOperation
 from awscli.customizations.commands import BasicCommand
 from awscli import formatter
+from awscli.argparser import HELP_BLURB
 from botocore.hooks import HierarchicalEmitter
 
 
@@ -595,6 +596,26 @@ def override_with_error(**kwargs):
         rc = self.driver.main('ec2 describe-instances'.split())
         self.assertEqual(rc, 255)
 
+    def test_help_blurb_in_error_message(self):
+        with self.assertRaises(SystemExit):
+            self.driver.main([])
+        self.assertIn(HELP_BLURB, self.stderr.getvalue())
+
+    def test_help_blurb_in_service_error_message(self):
+        with self.assertRaises(SystemExit):
+            self.driver.main(['ec2'])
+        self.assertIn(HELP_BLURB, self.stderr.getvalue())
+
+    def test_help_blurb_in_operation_error_message(self):
+        with self.assertRaises(SystemExit):
+            self.driver.main(['ec2', 'run-instances'])
+        self.assertIn(HELP_BLURB, self.stderr.getvalue())
+
+    def test_help_blurb_in_unknown_argument_error_message(self):
+        with self.assertRaises(SystemExit):
+            self.driver.main(['ec2', 'run-instances', '--help'])
+        self.assertIn(HELP_BLURB, self.stderr.getvalue())
+
 
 class TestHowClientIsCreated(BaseAWSCommandParamsTest):
     def setUp(self):
diff --git a/tests/unit/test_help.py b/tests/unit/test_help.py
--- a/tests/unit/test_help.py
+++ b/tests/unit/test_help.py
@@ -20,6 +20,7 @@
 from awscli.help import PosixHelpRenderer, ExecutableNotFoundError
 from awscli.help import WindowsHelpRenderer, ProviderHelpCommand, HelpCommand
 from awscli.help import TopicListerCommand, TopicHelpCommand
+from awscli.argparser import HELP_BLURB
 
 
 class HelpSpyMixin(object):
@@ -160,12 +161,12 @@ def test_regular_call(self):
         self.assertTrue(self.renderer.render.called)
 
     def test_invalid_subcommand(self):
-        # This sole purpose of this patch is to remove errors from being
-        # printed to screen even when the test passes when running the test
-        # suite.
-        with mock.patch('sys.stderr'):
+        with mock.patch('sys.stderr') as f:
             with self.assertRaises(SystemExit):
                 self.cmd(['no-exist-command'], None)
+        # We should see the pointer to "aws help" in the error message.
+        error_message = ''.join(arg[0][0] for arg in f.write.call_args_list)
+        self.assertIn(HELP_BLURB, error_message)
 
 
 class TestProviderHelpCommand(TestHelpCommandBase):