We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
cloudformation describe-stack-events --output table omits ResourceStatusReason
aws-cli/0.13.2 Python/2.7.5 Darwin/12.4.1

This may be an RFE. With a command like `aws cloudformation describe-stack-events --output table ...` the table output does not include the `ResourceStatusReason` data. This is useful data when failures occur. For example here is some sample output with an interesting `ResourceStatusReason`.

```
{
    "StackId": "arn:aws:cloudformation:us-west-2:317324027142:stack/spot-3x-m1large/e1fa9ac0-f985-11e2-aa7f-507bfc8736d2", 
    "EventId": "LaunchConfig1-CREATE_FAILED-1375236694000", 
    "ResourceStatus": "CREATE_FAILED", 
    "ResourceType": "AWS::AutoScaling::LaunchConfiguration", 
    "Timestamp": "2013-07-31T02:11:34Z", 
    "ResourceStatusReason": "AMI cannot be described", 
    "StackName": "spot-3x-m1large", 
    "PhysicalResourceId": "spot-3x-m1large-LaunchConfig1-EFTX7ICLP050", 
    "LogicalResourceId": "LaunchConfig1"
}
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `awscli/formatter.py`
Content:
```
1 # Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 
3 # Licensed under the Apache License, Version 2.0 (the "License"). You
4 # may not use this file except in compliance with the License. A copy of
5 # the License is located at
6 
7 #     http://aws.amazon.com/apache2.0/
8 
9 # or in the "license" file accompanying this file. This file is
10 # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11 # ANY KIND, either express or implied. See the License for the specific
12 # language governing permissions and limitations under the License.
13 import logging
14 import sys
15 import json
16 
17 import six
18 
19 from awscli.table import MultiTable, Styler, ColorizedStyler
20 
21 
22 LOG = logging.getLogger(__name__)
23 
24 
25 class Formatter(object):
26     def __init__(self, args):
27         self._args = args
28 
29     def _remove_request_id(self, response_data):
30         # We only want to display the ResponseMetadata (which includes
31         # the request id) if there is an error in the response.
32         # Since all errors have been unified under the Errors key,
33         # this should be a reasonable way to filter.
34         if 'Errors' not in response_data:
35             if 'ResponseMetadata' in response_data:
36                 if 'RequestId' in response_data['ResponseMetadata']:
37                     request_id = response_data['ResponseMetadata']['RequestId']
38                     LOG.debug('RequestId: %s', request_id)
39                 del response_data['ResponseMetadata']
40 
41 
42 class FullyBufferedFormatter(Formatter):
43     def __call__(self, operation, response, stream=None):
44         if stream is None:
45             # Retrieve stdout on invocation instead of at import time
46             # so that if anything wraps stdout we'll pick up those changes
47             # (specifically colorama on windows wraps stdout).
48             stream = sys.stdout
49         # I think the interfaces between non-paginated
50         # and paginated responses can still be cleaned up.
51         if operation.can_paginate and self._args.paginate:
52             response_data = response.build_full_result()
53         else:
54             response_data = response
55         try:
56             self._remove_request_id(response_data)
57             self._format_response(operation, response_data, stream)
58         finally:
59             # flush is needed to avoid the "close failed in file object
60             # destructor" in python2.x (see http://bugs.python.org/issue11380).
61             stream.flush()
62 
63 
64 class JSONFormatter(FullyBufferedFormatter):
65 
66     def _format_response(self, operation, response, stream):
67         # For operations that have no response body (e.g. s3 put-object)
68         # the response will be an empty string.  We don't want to print
69         # that out to the user but other "falsey" values like an empty
70         # dictionary should be printed.
71         if response != '':
72             json.dump(response, stream, indent=4)
73         stream.write('\n')
74 
75 
76 class TableFormatter(FullyBufferedFormatter):
77     """Pretty print a table from a given response.
78 
79     The table formatter is able to take any generic response
80     and generate a pretty printed table.  It does this without
81     using the output definition from the model.
82 
83     """
84     def __init__(self, args, table=None):
85         super(TableFormatter, self).__init__(args)
86         if args.color == 'auto':
87             self.table = MultiTable(initial_section=False,
88                                     column_separator='|')
89         elif args.color == 'off':
90             styler = Styler()
91             self.table = MultiTable(initial_section=False,
92                                     column_separator='|', styler=styler)
93         elif args.color == 'on':
94             styler = ColorizedStyler()
95             self.table = MultiTable(initial_section=False,
96                                     column_separator='|', styler=styler)
97         else:
98             raise ValueError("Unknown color option: %s" % args.color)
99 
100     def _format_response(self, operation, response, stream):
101         if self._build_table(operation.name, response):
102             try:
103                 self.table.render(stream)
104             except IOError:
105                 # If they're piping stdout to another process which exits before
106                 # we're done writing all of our output, we'll get an error about a
107                 # closed pipe which we can safely ignore.
108                 pass
109 
110     def _build_table(self, title, current, indent_level=0):
111         if not current:
112             return False
113         self.table.new_section(title, indent_level=indent_level)
114         if isinstance(current, list):
115             if isinstance(current[0], dict):
116                 self._build_sub_table_from_list(current, indent_level, title)
117             else:
118                 for item in current:
119                     self.table.add_row([item])
120         if isinstance(current, dict):
121             # Render a single row section with keys as header
122             # and the row as the values, unless the value
123             # is a list.
124             self._build_sub_table_from_dict(current, indent_level)
125         return True
126 
127     def _build_sub_table_from_dict(self, current, indent_level):
128         # Render a single row section with keys as header
129         # and the row as the values, unless the value
130         # is a list.
131         headers, more = self._group_scalar_keys(current)
132         if len(headers) == 1:
133             # Special casing if a dict has a single scalar key/value pair.
134             self.table.add_row([headers[0], current[headers[0]]])
135         elif headers:
136             self.table.add_row_header(headers)
137             self.table.add_row([current[k] for k in headers])
138         for remaining in more:
139             self._build_table(remaining, current[remaining],
140                               indent_level=indent_level + 1)
141 
142     def _build_sub_table_from_list(self, current, indent_level, title):
143         headers, more = self._group_scalar_keys(current[0])
144         self.table.add_row_header(headers)
145         first = True
146         for element in current:
147             if not first and more:
148                 self.table.new_section(title,
149                                        indent_level=indent_level)
150                 self.table.add_row_header(headers)
151             first = False
152             self.table.add_row([element[header] for header in headers])
153             for remaining in more:
154                 # Some of the non scalar attributes may not necessarily
155                 # be in every single element of the list, so we need to
156                 # check this condition before recursing.
157                 if remaining in element:
158                     self._build_table(remaining, element[remaining],
159                                     indent_level=indent_level + 1)
160 
161     def _scalar_type(self, element):
162         return not isinstance(element, (list, dict))
163 
164     def _group_scalar_keys(self, current):
165         # Given a dict, separate the keys into those whose values are
166         # scalar, and those whose values aren't.  Return two lists,
167         # one is the scalar value keys, the second is the remaining keys.
168         more = []
169         headers = []
170         for element in current:
171             if self._scalar_type(current[element]):
172                 headers.append(element)
173             else:
174                 more.append(element)
175         headers.sort()
176         more.sort()
177         return headers, more
178 
179 
180 class TextFormatter(FullyBufferedFormatter):
181 
182     def _output(self, data, stream, label=None):
183         """
184         A very simple, very stupid text formatter that has no
185         knowledge of the output as defined in the JSON model.
186         """
187         if isinstance(data, dict):
188             scalars = []
189             non_scalars = []
190             for key, val in data.items():
191                 if isinstance(val, dict):
192                     non_scalars.append((key, val))
193                 elif isinstance(val, list):
194                     non_scalars.append((key, val))
195                 elif not isinstance(val, six.string_types):
196                     scalars.append(str(val))
197                 else:
198                     scalars.append(val)
199             if label:
200                 scalars.insert(0, label.upper())
201             stream.write('\t'.join(scalars))
202             stream.write('\n')
203             for label, non_scalar in non_scalars:
204                 self._output(non_scalar, stream, label)
205         elif isinstance(data, list):
206             for d in data:
207                 self._output(d, stream)
208 
209     def _format_response(self, operation, response, stream):
210         self._output(response, stream)
211 
212 
213 def get_formatter(format_type, args):
214     if format_type == 'json':
215         return JSONFormatter(args)
216     elif format_type == 'text':
217         return TextFormatter(args)
218     elif format_type == 'table':
219         return TableFormatter(args)
220     return None
221
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/awscli/formatter.py b/awscli/formatter.py
--- a/awscli/formatter.py
+++ b/awscli/formatter.py
@@ -140,7 +140,7 @@
                               indent_level=indent_level + 1)
 
     def _build_sub_table_from_list(self, current, indent_level, title):
-        headers, more = self._group_scalar_keys(current[0])
+        headers, more = self._group_scalar_keys_from_list(current)
         self.table.add_row_header(headers)
         first = True
         for element in current:
@@ -149,7 +149,9 @@
                                        indent_level=indent_level)
                 self.table.add_row_header(headers)
             first = False
-            self.table.add_row([element[header] for header in headers])
+            # Use .get() to account for the fact that sometimes an element
+            # may not have all the keys from the header.
+            self.table.add_row([element.get(header, '') for header in headers])
             for remaining in more:
                 # Some of the non scalar attributes may not necessarily
                 # be in every single element of the list, so we need to
@@ -161,6 +163,20 @@
     def _scalar_type(self, element):
         return not isinstance(element, (list, dict))
 
+    def _group_scalar_keys_from_list(self, list_of_dicts):
+        # We want to make sure we catch all the keys in the list of dicts.
+        # Most of the time each list element has the same keys, but sometimes
+        # a list element will have keys not defined in other elements.
+        headers = set()
+        more = set()
+        for item in list_of_dicts:
+            current_headers, current_more = self._group_scalar_keys(item)
+            headers.update(current_headers)
+            more.update(current_more)
+        headers = list(sorted(headers))
+        more = list(sorted(more))
+        return headers, more
+
     def _group_scalar_keys(self, current):
         # Given a dict, separate the keys into those whose values are
         # scalar, and those whose values aren't.  Return two lists,

{"golden_diff": "diff --git a/awscli/formatter.py b/awscli/formatter.py\n--- a/awscli/formatter.py\n+++ b/awscli/formatter.py\n@@ -140,7 +140,7 @@\n                               indent_level=indent_level + 1)\n \n     def _build_sub_table_from_list(self, current, indent_level, title):\n-        headers, more = self._group_scalar_keys(current[0])\n+        headers, more = self._group_scalar_keys_from_list(current)\n         self.table.add_row_header(headers)\n         first = True\n         for element in current:\n@@ -149,7 +149,9 @@\n                                        indent_level=indent_level)\n                 self.table.add_row_header(headers)\n             first = False\n-            self.table.add_row([element[header] for header in headers])\n+            # Use .get() to account for the fact that sometimes an element\n+            # may not have all the keys from the header.\n+            self.table.add_row([element.get(header, '') for header in headers])\n             for remaining in more:\n                 # Some of the non scalar attributes may not necessarily\n                 # be in every single element of the list, so we need to\n@@ -161,6 +163,20 @@\n     def _scalar_type(self, element):\n         return not isinstance(element, (list, dict))\n \n+    def _group_scalar_keys_from_list(self, list_of_dicts):\n+        # We want to make sure we catch all the keys in the list of dicts.\n+        # Most of the time each list element has the same keys, but sometimes\n+        # a list element will have keys not defined in other elements.\n+        headers = set()\n+        more = set()\n+        for item in list_of_dicts:\n+            current_headers, current_more = self._group_scalar_keys(item)\n+            headers.update(current_headers)\n+            more.update(current_more)\n+        headers = list(sorted(headers))\n+        more = list(sorted(more))\n+        return headers, more\n+\n     def _group_scalar_keys(self, current):\n         # Given a dict, separate the keys into those whose values are\n         # scalar, and those whose values aren't.  Return two lists,\n", "issue": "cloudformation describe-stack-events --output table omits ResourceStatusReason\naws-cli/0.13.2 Python/2.7.5 Darwin/12.4.1\n\nThis may be an RFE. With a command like `aws cloudformation describe-stack-events --output table ...` the table output does not include the `ResourceStatusReason` data. This is useful data when failures occur. For example here is some sample output with an interesting `ResourceStatusReason`.\n\n```\n{\n    \"StackId\": \"arn:aws:cloudformation:us-west-2:317324027142:stack/spot-3x-m1large/e1fa9ac0-f985-11e2-aa7f-507bfc8736d2\", \n    \"EventId\": \"LaunchConfig1-CREATE_FAILED-1375236694000\", \n    \"ResourceStatus\": \"CREATE_FAILED\", \n    \"ResourceType\": \"AWS::AutoScaling::LaunchConfiguration\", \n    \"Timestamp\": \"2013-07-31T02:11:34Z\", \n    \"ResourceStatusReason\": \"AMI cannot be described\", \n    \"StackName\": \"spot-3x-m1large\", \n    \"PhysicalResourceId\": \"spot-3x-m1large-LaunchConfig1-EFTX7ICLP050\", \n    \"LogicalResourceId\": \"LaunchConfig1\"\n}\n```\n\n", "before_files": [{"content": "# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n# Licensed under the Apache License, Version 2.0 (the \"License\"). You\n# may not use this file except in compliance with the License. A copy of\n# the License is located at\n\n#     http://aws.amazon.com/apache2.0/\n\n# or in the \"license\" file accompanying this file. This file is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n# ANY KIND, either express or implied. See the License for the specific\n# language governing permissions and limitations under the License.\nimport logging\nimport sys\nimport json\n\nimport six\n\nfrom awscli.table import MultiTable, Styler, ColorizedStyler\n\n\nLOG = logging.getLogger(__name__)\n\n\nclass Formatter(object):\n    def __init__(self, args):\n        self._args = args\n\n    def _remove_request_id(self, response_data):\n        # We only want to display the ResponseMetadata (which includes\n        # the request id) if there is an error in the response.\n        # Since all errors have been unified under the Errors key,\n        # this should be a reasonable way to filter.\n        if 'Errors' not in response_data:\n            if 'ResponseMetadata' in response_data:\n                if 'RequestId' in response_data['ResponseMetadata']:\n                    request_id = response_data['ResponseMetadata']['RequestId']\n                    LOG.debug('RequestId: %s', request_id)\n                del response_data['ResponseMetadata']\n\n\nclass FullyBufferedFormatter(Formatter):\n    def __call__(self, operation, response, stream=None):\n        if stream is None:\n            # Retrieve stdout on invocation instead of at import time\n            # so that if anything wraps stdout we'll pick up those changes\n            # (specifically colorama on windows wraps stdout).\n            stream = sys.stdout\n        # I think the interfaces between non-paginated\n        # and paginated responses can still be cleaned up.\n        if operation.can_paginate and self._args.paginate:\n            response_data = response.build_full_result()\n        else:\n            response_data = response\n        try:\n            self._remove_request_id(response_data)\n            self._format_response(operation, response_data, stream)\n        finally:\n            # flush is needed to avoid the \"close failed in file object\n            # destructor\" in python2.x (see http://bugs.python.org/issue11380).\n            stream.flush()\n\n\nclass JSONFormatter(FullyBufferedFormatter):\n\n    def _format_response(self, operation, response, stream):\n        # For operations that have no response body (e.g. s3 put-object)\n        # the response will be an empty string.  We don't want to print\n        # that out to the user but other \"falsey\" values like an empty\n        # dictionary should be printed.\n        if response != '':\n            json.dump(response, stream, indent=4)\n        stream.write('\\n')\n\n\nclass TableFormatter(FullyBufferedFormatter):\n    \"\"\"Pretty print a table from a given response.\n\n    The table formatter is able to take any generic response\n    and generate a pretty printed table.  It does this without\n    using the output definition from the model.\n\n    \"\"\"\n    def __init__(self, args, table=None):\n        super(TableFormatter, self).__init__(args)\n        if args.color == 'auto':\n            self.table = MultiTable(initial_section=False,\n                                    column_separator='|')\n        elif args.color == 'off':\n            styler = Styler()\n            self.table = MultiTable(initial_section=False,\n                                    column_separator='|', styler=styler)\n        elif args.color == 'on':\n            styler = ColorizedStyler()\n            self.table = MultiTable(initial_section=False,\n                                    column_separator='|', styler=styler)\n        else:\n            raise ValueError(\"Unknown color option: %s\" % args.color)\n\n    def _format_response(self, operation, response, stream):\n        if self._build_table(operation.name, response):\n            try:\n                self.table.render(stream)\n            except IOError:\n                # If they're piping stdout to another process which exits before\n                # we're done writing all of our output, we'll get an error about a\n                # closed pipe which we can safely ignore.\n                pass\n\n    def _build_table(self, title, current, indent_level=0):\n        if not current:\n            return False\n        self.table.new_section(title, indent_level=indent_level)\n        if isinstance(current, list):\n            if isinstance(current[0], dict):\n                self._build_sub_table_from_list(current, indent_level, title)\n            else:\n                for item in current:\n                    self.table.add_row([item])\n        if isinstance(current, dict):\n            # Render a single row section with keys as header\n            # and the row as the values, unless the value\n            # is a list.\n            self._build_sub_table_from_dict(current, indent_level)\n        return True\n\n    def _build_sub_table_from_dict(self, current, indent_level):\n        # Render a single row section with keys as header\n        # and the row as the values, unless the value\n        # is a list.\n        headers, more = self._group_scalar_keys(current)\n        if len(headers) == 1:\n            # Special casing if a dict has a single scalar key/value pair.\n            self.table.add_row([headers[0], current[headers[0]]])\n        elif headers:\n            self.table.add_row_header(headers)\n            self.table.add_row([current[k] for k in headers])\n        for remaining in more:\n            self._build_table(remaining, current[remaining],\n                              indent_level=indent_level + 1)\n\n    def _build_sub_table_from_list(self, current, indent_level, title):\n        headers, more = self._group_scalar_keys(current[0])\n        self.table.add_row_header(headers)\n        first = True\n        for element in current:\n            if not first and more:\n                self.table.new_section(title,\n                                       indent_level=indent_level)\n                self.table.add_row_header(headers)\n            first = False\n            self.table.add_row([element[header] for header in headers])\n            for remaining in more:\n                # Some of the non scalar attributes may not necessarily\n                # be in every single element of the list, so we need to\n                # check this condition before recursing.\n                if remaining in element:\n                    self._build_table(remaining, element[remaining],\n                                    indent_level=indent_level + 1)\n\n    def _scalar_type(self, element):\n        return not isinstance(element, (list, dict))\n\n    def _group_scalar_keys(self, current):\n        # Given a dict, separate the keys into those whose values are\n        # scalar, and those whose values aren't.  Return two lists,\n        # one is the scalar value keys, the second is the remaining keys.\n        more = []\n        headers = []\n        for element in current:\n            if self._scalar_type(current[element]):\n                headers.append(element)\n            else:\n                more.append(element)\n        headers.sort()\n        more.sort()\n        return headers, more\n\n\nclass TextFormatter(FullyBufferedFormatter):\n\n    def _output(self, data, stream, label=None):\n        \"\"\"\n        A very simple, very stupid text formatter that has no\n        knowledge of the output as defined in the JSON model.\n        \"\"\"\n        if isinstance(data, dict):\n            scalars = []\n            non_scalars = []\n            for key, val in data.items():\n                if isinstance(val, dict):\n                    non_scalars.append((key, val))\n                elif isinstance(val, list):\n                    non_scalars.append((key, val))\n                elif not isinstance(val, six.string_types):\n                    scalars.append(str(val))\n                else:\n                    scalars.append(val)\n            if label:\n                scalars.insert(0, label.upper())\n            stream.write('\\t'.join(scalars))\n            stream.write('\\n')\n            for label, non_scalar in non_scalars:\n                self._output(non_scalar, stream, label)\n        elif isinstance(data, list):\n            for d in data:\n                self._output(d, stream)\n\n    def _format_response(self, operation, response, stream):\n        self._output(response, stream)\n\n\ndef get_formatter(format_type, args):\n    if format_type == 'json':\n        return JSONFormatter(args)\n    elif format_type == 'text':\n        return TextFormatter(args)\n    elif format_type == 'table':\n        return TableFormatter(args)\n    return None\n", "path": "awscli/formatter.py"}], "after_files": [{"content": "# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n# Licensed under the Apache License, Version 2.0 (the \"License\"). You\n# may not use this file except in compliance with the License. A copy of\n# the License is located at\n\n#     http://aws.amazon.com/apache2.0/\n\n# or in the \"license\" file accompanying this file. This file is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n# ANY KIND, either express or implied. See the License for the specific\n# language governing permissions and limitations under the License.\nimport logging\nimport sys\nimport json\n\nimport six\n\nfrom awscli.table import MultiTable, Styler, ColorizedStyler\n\n\nLOG = logging.getLogger(__name__)\n\n\nclass Formatter(object):\n    def __init__(self, args):\n        self._args = args\n\n    def _remove_request_id(self, response_data):\n        # We only want to display the ResponseMetadata (which includes\n        # the request id) if there is an error in the response.\n        # Since all errors have been unified under the Errors key,\n        # this should be a reasonable way to filter.\n        if 'Errors' not in response_data:\n            if 'ResponseMetadata' in response_data:\n                if 'RequestId' in response_data['ResponseMetadata']:\n                    request_id = response_data['ResponseMetadata']['RequestId']\n                    LOG.debug('RequestId: %s', request_id)\n                del response_data['ResponseMetadata']\n\n\nclass FullyBufferedFormatter(Formatter):\n    def __call__(self, operation, response, stream=None):\n        if stream is None:\n            # Retrieve stdout on invocation instead of at import time\n            # so that if anything wraps stdout we'll pick up those changes\n            # (specifically colorama on windows wraps stdout).\n            stream = sys.stdout\n        # I think the interfaces between non-paginated\n        # and paginated responses can still be cleaned up.\n        if operation.can_paginate and self._args.paginate:\n            response_data = response.build_full_result()\n        else:\n            response_data = response\n        try:\n            self._remove_request_id(response_data)\n            self._format_response(operation, response_data, stream)\n        finally:\n            # flush is needed to avoid the \"close failed in file object\n            # destructor\" in python2.x (see http://bugs.python.org/issue11380).\n            stream.flush()\n\n\nclass JSONFormatter(FullyBufferedFormatter):\n\n    def _format_response(self, operation, response, stream):\n        # For operations that have no response body (e.g. s3 put-object)\n        # the response will be an empty string.  We don't want to print\n        # that out to the user but other \"falsey\" values like an empty\n        # dictionary should be printed.\n        if response != '':\n            json.dump(response, stream, indent=4)\n        stream.write('\\n')\n\n\nclass TableFormatter(FullyBufferedFormatter):\n    \"\"\"Pretty print a table from a given response.\n\n    The table formatter is able to take any generic response\n    and generate a pretty printed table.  It does this without\n    using the output definition from the model.\n\n    \"\"\"\n    def __init__(self, args, table=None):\n        super(TableFormatter, self).__init__(args)\n        if args.color == 'auto':\n            self.table = MultiTable(initial_section=False,\n                                    column_separator='|')\n        elif args.color == 'off':\n            styler = Styler()\n            self.table = MultiTable(initial_section=False,\n                                    column_separator='|', styler=styler)\n        elif args.color == 'on':\n            styler = ColorizedStyler()\n            self.table = MultiTable(initial_section=False,\n                                    column_separator='|', styler=styler)\n        else:\n            raise ValueError(\"Unknown color option: %s\" % args.color)\n\n    def _format_response(self, operation, response, stream):\n        if self._build_table(operation.name, response):\n            try:\n                self.table.render(stream)\n            except IOError:\n                # If they're piping stdout to another process which exits before\n                # we're done writing all of our output, we'll get an error about a\n                # closed pipe which we can safely ignore.\n                pass\n\n    def _build_table(self, title, current, indent_level=0):\n        if not current:\n            return False\n        self.table.new_section(title, indent_level=indent_level)\n        if isinstance(current, list):\n            if isinstance(current[0], dict):\n                self._build_sub_table_from_list(current, indent_level, title)\n            else:\n                for item in current:\n                    self.table.add_row([item])\n        if isinstance(current, dict):\n            # Render a single row section with keys as header\n            # and the row as the values, unless the value\n            # is a list.\n            self._build_sub_table_from_dict(current, indent_level)\n        return True\n\n    def _build_sub_table_from_dict(self, current, indent_level):\n        # Render a single row section with keys as header\n        # and the row as the values, unless the value\n        # is a list.\n        headers, more = self._group_scalar_keys(current)\n        if len(headers) == 1:\n            # Special casing if a dict has a single scalar key/value pair.\n            self.table.add_row([headers[0], current[headers[0]]])\n        elif headers:\n            self.table.add_row_header(headers)\n            self.table.add_row([current[k] for k in headers])\n        for remaining in more:\n            self._build_table(remaining, current[remaining],\n                              indent_level=indent_level + 1)\n\n    def _build_sub_table_from_list(self, current, indent_level, title):\n        headers, more = self._group_scalar_keys_from_list(current)\n        self.table.add_row_header(headers)\n        first = True\n        for element in current:\n            if not first and more:\n                self.table.new_section(title,\n                                       indent_level=indent_level)\n                self.table.add_row_header(headers)\n            first = False\n            # Use .get() to account for the fact that sometimes an element\n            # may not have all the keys from the header.\n            self.table.add_row([element.get(header, '') for header in headers])\n            for remaining in more:\n                # Some of the non scalar attributes may not necessarily\n                # be in every single element of the list, so we need to\n                # check this condition before recursing.\n                if remaining in element:\n                    self._build_table(remaining, element[remaining],\n                                    indent_level=indent_level + 1)\n\n    def _scalar_type(self, element):\n        return not isinstance(element, (list, dict))\n\n    def _group_scalar_keys_from_list(self, list_of_dicts):\n        # We want to make sure we catch all the keys in the list of dicts.\n        # Most of the time each list element has the same keys, but sometimes\n        # a list element will have keys not defined in other elements.\n        headers = set()\n        more = set()\n        for item in list_of_dicts:\n            current_headers, current_more = self._group_scalar_keys(item)\n            headers.update(current_headers)\n            more.update(current_more)\n        headers = list(sorted(headers))\n        more = list(sorted(more))\n        return headers, more\n\n    def _group_scalar_keys(self, current):\n        # Given a dict, separate the keys into those whose values are\n        # scalar, and those whose values aren't.  Return two lists,\n        # one is the scalar value keys, the second is the remaining keys.\n        more = []\n        headers = []\n        for element in current:\n            if self._scalar_type(current[element]):\n                headers.append(element)\n            else:\n                more.append(element)\n        headers.sort()\n        more.sort()\n        return headers, more\n\n\nclass TextFormatter(FullyBufferedFormatter):\n\n    def _output(self, data, stream, label=None):\n        \"\"\"\n        A very simple, very stupid text formatter that has no\n        knowledge of the output as defined in the JSON model.\n        \"\"\"\n        if isinstance(data, dict):\n            scalars = []\n            non_scalars = []\n            for key, val in data.items():\n                if isinstance(val, dict):\n                    non_scalars.append((key, val))\n                elif isinstance(val, list):\n                    non_scalars.append((key, val))\n                elif not isinstance(val, six.string_types):\n                    scalars.append(str(val))\n                else:\n                    scalars.append(val)\n            if label:\n                scalars.insert(0, label.upper())\n            stream.write('\\t'.join(scalars))\n            stream.write('\\n')\n            for label, non_scalar in non_scalars:\n                self._output(non_scalar, stream, label)\n        elif isinstance(data, list):\n            for d in data:\n                self._output(d, stream)\n\n    def _format_response(self, operation, response, stream):\n        self._output(response, stream)\n\n\ndef get_formatter(format_type, args):\n    if format_type == 'json':\n        return JSONFormatter(args)\n    elif format_type == 'text':\n        return TextFormatter(args)\n    elif format_type == 'table':\n        return TableFormatter(args)\n    return None\n", "path": "awscli/formatter.py"}]}