We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
--output text results in "None" while --output json gives correct result on cloudformation changesets
Hi,

the following gives "CREATE_COMPLETE" which is correct

`aws cloudformation describe-change-set --change-set-name website-production-alb-a-1-0-7 --stack-name website-production-alb-a`

while this gives "None" which is incorrect

`aws cloudformation describe-change-set --change-set-name website-production-alb-a-1-0-7 --stack-name website-production-alb-a --output text`

`aws --version`
`aws-cli/1.16.110 Python/3.7.2 Darwin/18.2.0 botocore/1.12.100`

colleague has aws-cli/1.16.1 which also gives correct result on text.


--- 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 
15 from botocore.compat import json
16 
17 from botocore.utils import set_value_from_jmespath
18 from botocore.paginate import PageIterator
19 
20 from awscli.table import MultiTable, Styler, ColorizedStyler
21 from awscli import text
22 from awscli import compat
23 from awscli.utils import json_encoder
24 
25 
26 LOG = logging.getLogger(__name__)
27 
28 
29 def is_response_paginated(response):
30     return isinstance(response, PageIterator)
31 
32 
33 class Formatter(object):
34     def __init__(self, args):
35         self._args = args
36 
37     def _remove_request_id(self, response_data):
38         # We only want to display the ResponseMetadata (which includes
39         # the request id) if there is an error in the response.
40         # Since all errors have been unified under the Errors key,
41         # this should be a reasonable way to filter.
42         if 'Errors' not in response_data:
43             if 'ResponseMetadata' in response_data:
44                 if 'RequestId' in response_data['ResponseMetadata']:
45                     request_id = response_data['ResponseMetadata']['RequestId']
46                     LOG.debug('RequestId: %s', request_id)
47                 del response_data['ResponseMetadata']
48 
49     def _get_default_stream(self):
50         return compat.get_stdout_text_writer()
51 
52     def _flush_stream(self, stream):
53         try:
54             stream.flush()
55         except IOError:
56             pass
57 
58 
59 class FullyBufferedFormatter(Formatter):
60     def __call__(self, command_name, response, stream=None):
61         if stream is None:
62             # Retrieve stdout on invocation instead of at import time
63             # so that if anything wraps stdout we'll pick up those changes
64             # (specifically colorama on windows wraps stdout).
65             stream = self._get_default_stream()
66         # I think the interfaces between non-paginated
67         # and paginated responses can still be cleaned up.
68         if is_response_paginated(response):
69             response_data = response.build_full_result()
70         else:
71             response_data = response
72         self._remove_request_id(response_data)
73         if self._args.query is not None:
74             response_data = self._args.query.search(response_data)
75         try:
76             self._format_response(command_name, response_data, stream)
77         except IOError as e:
78             # If the reading end of our stdout stream has closed the file
79             # we can just exit.
80             pass
81         finally:
82             # flush is needed to avoid the "close failed in file object
83             # destructor" in python2.x (see http://bugs.python.org/issue11380).
84             self._flush_stream(stream)
85 
86 
87 class JSONFormatter(FullyBufferedFormatter):
88 
89     def _format_response(self, command_name, response, stream):
90         # For operations that have no response body (e.g. s3 put-object)
91         # the response will be an empty string.  We don't want to print
92         # that out to the user but other "falsey" values like an empty
93         # dictionary should be printed.
94         if response != {}:
95             json.dump(response, stream, indent=4, default=json_encoder,
96                     ensure_ascii=False)
97             stream.write('\n')
98 
99 
100 class TableFormatter(FullyBufferedFormatter):
101     """Pretty print a table from a given response.
102 
103     The table formatter is able to take any generic response
104     and generate a pretty printed table.  It does this without
105     using the output definition from the model.
106 
107     """
108     def __init__(self, args, table=None):
109         super(TableFormatter, self).__init__(args)
110         if args.color == 'auto':
111             self.table = MultiTable(initial_section=False,
112                                     column_separator='|')
113         elif args.color == 'off':
114             styler = Styler()
115             self.table = MultiTable(initial_section=False,
116                                     column_separator='|', styler=styler)
117         elif args.color == 'on':
118             styler = ColorizedStyler()
119             self.table = MultiTable(initial_section=False,
120                                     column_separator='|', styler=styler)
121         else:
122             raise ValueError("Unknown color option: %s" % args.color)
123 
124     def _format_response(self, command_name, response, stream):
125         if self._build_table(command_name, response):
126             try:
127                 self.table.render(stream)
128             except IOError:
129                 # If they're piping stdout to another process which exits before
130                 # we're done writing all of our output, we'll get an error about a
131                 # closed pipe which we can safely ignore.
132                 pass
133 
134     def _build_table(self, title, current, indent_level=0):
135         if not current:
136             return False
137         if title is not None:
138             self.table.new_section(title, indent_level=indent_level)
139         if isinstance(current, list):
140             if isinstance(current[0], dict):
141                 self._build_sub_table_from_list(current, indent_level, title)
142             else:
143                 for item in current:
144                     if self._scalar_type(item):
145                         self.table.add_row([item])
146                     elif all(self._scalar_type(el) for el in item):
147                         self.table.add_row(item)
148                     else:
149                         self._build_table(title=None, current=item)
150         if isinstance(current, dict):
151             # Render a single row section with keys as header
152             # and the row as the values, unless the value
153             # is a list.
154             self._build_sub_table_from_dict(current, indent_level)
155         return True
156 
157     def _build_sub_table_from_dict(self, current, indent_level):
158         # Render a single row section with keys as header
159         # and the row as the values, unless the value
160         # is a list.
161         headers, more = self._group_scalar_keys(current)
162         if len(headers) == 1:
163             # Special casing if a dict has a single scalar key/value pair.
164             self.table.add_row([headers[0], current[headers[0]]])
165         elif headers:
166             self.table.add_row_header(headers)
167             self.table.add_row([current[k] for k in headers])
168         for remaining in more:
169             self._build_table(remaining, current[remaining],
170                               indent_level=indent_level + 1)
171 
172     def _build_sub_table_from_list(self, current, indent_level, title):
173         headers, more = self._group_scalar_keys_from_list(current)
174         self.table.add_row_header(headers)
175         first = True
176         for element in current:
177             if not first and more:
178                 self.table.new_section(title,
179                                        indent_level=indent_level)
180                 self.table.add_row_header(headers)
181             first = False
182             # Use .get() to account for the fact that sometimes an element
183             # may not have all the keys from the header.
184             self.table.add_row([element.get(header, '') for header in headers])
185             for remaining in more:
186                 # Some of the non scalar attributes may not necessarily
187                 # be in every single element of the list, so we need to
188                 # check this condition before recursing.
189                 if remaining in element:
190                     self._build_table(remaining, element[remaining],
191                                     indent_level=indent_level + 1)
192 
193     def _scalar_type(self, element):
194         return not isinstance(element, (list, dict))
195 
196     def _group_scalar_keys_from_list(self, list_of_dicts):
197         # We want to make sure we catch all the keys in the list of dicts.
198         # Most of the time each list element has the same keys, but sometimes
199         # a list element will have keys not defined in other elements.
200         headers = set()
201         more = set()
202         for item in list_of_dicts:
203             current_headers, current_more = self._group_scalar_keys(item)
204             headers.update(current_headers)
205             more.update(current_more)
206         headers = list(sorted(headers))
207         more = list(sorted(more))
208         return headers, more
209 
210     def _group_scalar_keys(self, current):
211         # Given a dict, separate the keys into those whose values are
212         # scalar, and those whose values aren't.  Return two lists,
213         # one is the scalar value keys, the second is the remaining keys.
214         more = []
215         headers = []
216         for element in current:
217             if self._scalar_type(current[element]):
218                 headers.append(element)
219             else:
220                 more.append(element)
221         headers.sort()
222         more.sort()
223         return headers, more
224 
225 
226 class TextFormatter(Formatter):
227 
228     def __call__(self, command_name, response, stream=None):
229         if stream is None:
230             stream = self._get_default_stream()
231         try:
232             if is_response_paginated(response):
233                 result_keys = response.result_keys
234                 for page in response:
235                     current = {}
236                     for result_key in result_keys:
237                         data = result_key.search(page)
238                         set_value_from_jmespath(
239                             current,
240                             result_key.expression,
241                             data
242                         )
243                     self._format_response(current, stream)
244                 if response.resume_token:
245                     # Tell the user about the next token so they can continue
246                     # if they want.
247                     self._format_response(
248                         {'NextToken': {'NextToken': response.resume_token}},
249                         stream)
250             else:
251                 self._remove_request_id(response)
252                 self._format_response(response, stream)
253         finally:
254             # flush is needed to avoid the "close failed in file object
255             # destructor" in python2.x (see http://bugs.python.org/issue11380).
256             self._flush_stream(stream)
257 
258     def _format_response(self, response, stream):
259         if self._args.query is not None:
260             expression = self._args.query
261             response = expression.search(response)
262         text.format_text(response, stream)
263 
264 
265 def get_formatter(format_type, args):
266     if format_type == 'json':
267         return JSONFormatter(args)
268     elif format_type == 'text':
269         return TextFormatter(args)
270     elif format_type == 'table':
271         return TableFormatter(args)
272     raise ValueError("Unknown output type: %s" % format_type)
273
```
--- 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
@@ -231,8 +231,12 @@
         try:
             if is_response_paginated(response):
                 result_keys = response.result_keys
-                for page in response:
-                    current = {}
+                for i, page in enumerate(response):
+                    if i > 0:
+                        current = {}
+                    else:
+                        current = response.non_aggregate_part
+
                     for result_key in result_keys:
                         data = result_key.search(page)
                         set_value_from_jmespath(

{"golden_diff": "diff --git a/awscli/formatter.py b/awscli/formatter.py\n--- a/awscli/formatter.py\n+++ b/awscli/formatter.py\n@@ -231,8 +231,12 @@\n         try:\n             if is_response_paginated(response):\n                 result_keys = response.result_keys\n-                for page in response:\n-                    current = {}\n+                for i, page in enumerate(response):\n+                    if i > 0:\n+                        current = {}\n+                    else:\n+                        current = response.non_aggregate_part\n+\n                     for result_key in result_keys:\n                         data = result_key.search(page)\n                         set_value_from_jmespath(\n", "issue": "--output text results in \"None\" while --output json gives correct result on cloudformation changesets\nHi,\r\n\r\nthe following gives \"CREATE_COMPLETE\" which is correct\r\n\r\n`aws cloudformation describe-change-set --change-set-name website-production-alb-a-1-0-7 --stack-name website-production-alb-a`\r\n\r\nwhile this gives \"None\" which is incorrect\r\n\r\n`aws cloudformation describe-change-set --change-set-name website-production-alb-a-1-0-7 --stack-name website-production-alb-a --output text`\r\n\r\n`aws --version`\r\n`aws-cli/1.16.110 Python/3.7.2 Darwin/18.2.0 botocore/1.12.100`\r\n\r\ncolleague has aws-cli/1.16.1 which also gives correct result on text.\r\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\n\nfrom botocore.compat import json\n\nfrom botocore.utils import set_value_from_jmespath\nfrom botocore.paginate import PageIterator\n\nfrom awscli.table import MultiTable, Styler, ColorizedStyler\nfrom awscli import text\nfrom awscli import compat\nfrom awscli.utils import json_encoder\n\n\nLOG = logging.getLogger(__name__)\n\n\ndef is_response_paginated(response):\n    return isinstance(response, PageIterator)\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    def _get_default_stream(self):\n        return compat.get_stdout_text_writer()\n\n    def _flush_stream(self, stream):\n        try:\n            stream.flush()\n        except IOError:\n            pass\n\n\nclass FullyBufferedFormatter(Formatter):\n    def __call__(self, command_name, 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 = self._get_default_stream()\n        # I think the interfaces between non-paginated\n        # and paginated responses can still be cleaned up.\n        if is_response_paginated(response):\n            response_data = response.build_full_result()\n        else:\n            response_data = response\n        self._remove_request_id(response_data)\n        if self._args.query is not None:\n            response_data = self._args.query.search(response_data)\n        try:\n            self._format_response(command_name, response_data, stream)\n        except IOError as e:\n            # If the reading end of our stdout stream has closed the file\n            # we can just exit.\n            pass\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            self._flush_stream(stream)\n\n\nclass JSONFormatter(FullyBufferedFormatter):\n\n    def _format_response(self, command_name, 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, default=json_encoder,\n                    ensure_ascii=False)\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, command_name, response, stream):\n        if self._build_table(command_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        if title is not None:\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                    if self._scalar_type(item):\n                        self.table.add_row([item])\n                    elif all(self._scalar_type(el) for el in item):\n                        self.table.add_row(item)\n                    else:\n                        self._build_table(title=None, current=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(Formatter):\n\n    def __call__(self, command_name, response, stream=None):\n        if stream is None:\n            stream = self._get_default_stream()\n        try:\n            if is_response_paginated(response):\n                result_keys = response.result_keys\n                for page in response:\n                    current = {}\n                    for result_key in result_keys:\n                        data = result_key.search(page)\n                        set_value_from_jmespath(\n                            current,\n                            result_key.expression,\n                            data\n                        )\n                    self._format_response(current, stream)\n                if response.resume_token:\n                    # Tell the user about the next token so they can continue\n                    # if they want.\n                    self._format_response(\n                        {'NextToken': {'NextToken': response.resume_token}},\n                        stream)\n            else:\n                self._remove_request_id(response)\n                self._format_response(response, 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            self._flush_stream(stream)\n\n    def _format_response(self, response, stream):\n        if self._args.query is not None:\n            expression = self._args.query\n            response = expression.search(response)\n        text.format_text(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    raise ValueError(\"Unknown output type: %s\" % format_type)\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\n\nfrom botocore.compat import json\n\nfrom botocore.utils import set_value_from_jmespath\nfrom botocore.paginate import PageIterator\n\nfrom awscli.table import MultiTable, Styler, ColorizedStyler\nfrom awscli import text\nfrom awscli import compat\nfrom awscli.utils import json_encoder\n\n\nLOG = logging.getLogger(__name__)\n\n\ndef is_response_paginated(response):\n    return isinstance(response, PageIterator)\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    def _get_default_stream(self):\n        return compat.get_stdout_text_writer()\n\n    def _flush_stream(self, stream):\n        try:\n            stream.flush()\n        except IOError:\n            pass\n\n\nclass FullyBufferedFormatter(Formatter):\n    def __call__(self, command_name, 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 = self._get_default_stream()\n        # I think the interfaces between non-paginated\n        # and paginated responses can still be cleaned up.\n        if is_response_paginated(response):\n            response_data = response.build_full_result()\n        else:\n            response_data = response\n        self._remove_request_id(response_data)\n        if self._args.query is not None:\n            response_data = self._args.query.search(response_data)\n        try:\n            self._format_response(command_name, response_data, stream)\n        except IOError as e:\n            # If the reading end of our stdout stream has closed the file\n            # we can just exit.\n            pass\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            self._flush_stream(stream)\n\n\nclass JSONFormatter(FullyBufferedFormatter):\n\n    def _format_response(self, command_name, 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, default=json_encoder,\n                    ensure_ascii=False)\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, command_name, response, stream):\n        if self._build_table(command_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        if title is not None:\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                    if self._scalar_type(item):\n                        self.table.add_row([item])\n                    elif all(self._scalar_type(el) for el in item):\n                        self.table.add_row(item)\n                    else:\n                        self._build_table(title=None, current=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(Formatter):\n\n    def __call__(self, command_name, response, stream=None):\n        if stream is None:\n            stream = self._get_default_stream()\n        try:\n            if is_response_paginated(response):\n                result_keys = response.result_keys\n                for i, page in enumerate(response):\n                    if i > 0:\n                        current = {}\n                    else:\n                        current = response.non_aggregate_part\n\n                    for result_key in result_keys:\n                        data = result_key.search(page)\n                        set_value_from_jmespath(\n                            current,\n                            result_key.expression,\n                            data\n                        )\n                    self._format_response(current, stream)\n                if response.resume_token:\n                    # Tell the user about the next token so they can continue\n                    # if they want.\n                    self._format_response(\n                        {'NextToken': {'NextToken': response.resume_token}},\n                        stream)\n            else:\n                self._remove_request_id(response)\n                self._format_response(response, 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            self._flush_stream(stream)\n\n    def _format_response(self, response, stream):\n        if self._args.query is not None:\n            expression = self._args.query\n            response = expression.search(response)\n        text.format_text(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    raise ValueError(\"Unknown output type: %s\" % format_type)\n", "path": "awscli/formatter.py"}]}