We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Mitmproxy crashes, when viewing response body in url-encoded mode
##### Steps to reproduce the problem:

1. Run mitmproxy.
2. `n` -> `Enter` -> `Enter` -> `m` -> `url-encoded`

_I am seeing:_
```
Traceback (most recent call last):
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/master.py", line 216, in run
    self.loop.run()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 278, in run
    self._run()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 376, in _run
    self.event_loop.run()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 682, in run
    self._loop()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 719, in _loop
    self._watch_files[fd]()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py", line 393, in <lambda>
    event_loop, callback, self.get_available_raw_input())
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py", line 493, in parse_input
    callback(processed, processed_codes)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 403, in _update
    self.process_input(keys)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 503, in process_input
    k = self._topmost_widget.keypress(self.screen_size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 308, in keypress
    k = super().keypress(size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 44, in keypress
    ret = super().keypress(size, key)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 592, in keypress
    *self.calculate_padding_filler(size, True)), key)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/overlay.py", line 130, in keypress
    self.master.keymap.handle("global", key)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/keymap.py", line 130, in handle
    return self.executor(b.command)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/commandexecutor.py", line 17, in __call__
    ret = self.master.commands.call(cmd)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 221, in call
    return self.call_args(parts[0], parts[1:])
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 212, in call_args
    return self.commands[path].call(args)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 101, in call
    ret = self.func(*pargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 251, in wrapper
    return function(*args, **kwargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/consoleaddons.py", line 174, in nav_select
    self.master.inject_key("m_select")
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/master.py", line 181, in inject_key
    self.loop.process_input([key])
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 503, in process_input
    k = self._topmost_widget.keypress(self.screen_size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 308, in keypress
    k = super().keypress(size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 44, in keypress
    ret = super().keypress(size, key)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 592, in keypress
    *self.calculate_padding_filler(size, True)), key)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/overlay.py", line 121, in keypress
    signals.pop_view_state.send(self)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/blinker/base.py", line 267, in send
    for receiver in self.receivers_for(sender)]
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/blinker/base.py", line 267, in <listcomp>
    for receiver in self.receivers_for(sender)]
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 244, in pop
    self.focus_changed()
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 209, in focus_changed
    i.call("focus_changed")
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 118, in call
    getattr(self.top_window(), name)(*args, **kwargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 233, in focus_changed
    self.body.focus_changed()
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 58, in focus_changed
    self.show()
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/tabs.py", line 73, in show
    body = self.tabs[self.tab_offset][1](),
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 86, in view_request
    return self.conn_text(self.flow.request)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 185, in conn_text
    msg, body = self.content_view(viewmode, conn)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 112, in content_view
    return self._get_content_view(viewmode, limit, flow_modify_cache_invalidation)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 132, in _get_content_view
    for line in lines:
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/contentviews/__init__.py", line 85, in safe_to_print
    for line in lines:
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/contentviews/base.py", line 52, in format_dict
    max_key_len = max(len(k) for k in d.keys())
ValueError: max() arg is an empty sequence
```

##### Any other comments? What have you tried so far?

This issue is reproducible not only for request/response without body, but for `query` (maybe some others) content type as well. I continue digging.

##### System information

Mitmproxy: 3.0.0.dev53 (commit 3b5237c) 
Python:    3.5.2
OpenSSL:   OpenSSL 1.1.0g  2 Nov 2017
Platform:  Linux-4.4.0-109-generic-x86_64-with-Ubuntu-16.04-xenial


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mitmproxy/contentviews/base.py`
Content:
```
1 # Default view cutoff *in lines*
2 import typing
3 
4 VIEW_CUTOFF = 512
5 
6 KEY_MAX = 30
7 
8 TTextType = typing.Union[str, bytes]  # FIXME: This should be either bytes or str ultimately.
9 TViewLine = typing.List[typing.Tuple[str, TTextType]]
10 TViewResult = typing.Tuple[str, typing.Iterator[TViewLine]]
11 
12 
13 class View:
14     name = None  # type: str
15     prompt = None  # type: typing.Tuple[str,str]
16     content_types = []  # type: typing.List[str]
17 
18     def __call__(self, data: bytes, **metadata) -> TViewResult:
19         """
20         Transform raw data into human-readable output.
21 
22         Args:
23             data: the data to decode/format.
24             metadata: optional keyword-only arguments for metadata. Implementations must not
25                 rely on a given argument being present.
26 
27         Returns:
28             A (description, content generator) tuple.
29 
30             The content generator yields lists of (style, text) tuples, where each list represents
31             a single line. ``text`` is a unfiltered byte string which may need to be escaped,
32             depending on the used output.
33 
34         Caveats:
35             The content generator must not yield tuples of tuples,
36             because urwid cannot process that. You have to yield a *list* of tuples per line.
37         """
38         raise NotImplementedError()  # pragma: no cover
39 
40 
41 def format_dict(
42         d: typing.Mapping[TTextType, TTextType]
43 ) -> typing.Iterator[TViewLine]:
44     """
45     Helper function that transforms the given dictionary into a list of
46     [
47         ("key",   key  )
48         ("value", value)
49     ]
50     entries, where key is padded to a uniform width.
51     """
52     max_key_len = max(len(k) for k in d.keys())
53     max_key_len = min(max_key_len, KEY_MAX)
54     for key, value in d.items():
55         if isinstance(key, bytes):
56             key += b":"
57         else:
58             key += ":"
59         key = key.ljust(max_key_len + 2)
60         yield [
61             ("header", key),
62             ("text", value)
63         ]
64 
65 
66 def format_text(text: TTextType) -> typing.Iterator[TViewLine]:
67     """
68     Helper function that transforms bytes into the view output format.
69     """
70     for line in text.splitlines():
71         yield [("text", line)]
72
```
--- 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/mitmproxy/contentviews/base.py b/mitmproxy/contentviews/base.py
--- a/mitmproxy/contentviews/base.py
+++ b/mitmproxy/contentviews/base.py
@@ -49,8 +49,9 @@
     ]
     entries, where key is padded to a uniform width.
     """
-    max_key_len = max(len(k) for k in d.keys())
-    max_key_len = min(max_key_len, KEY_MAX)
+
+    max_key_len = max((len(k) for k in d.keys()), default=0)
+    max_key_len = min((max_key_len, KEY_MAX), default=0)
     for key, value in d.items():
         if isinstance(key, bytes):
             key += b":"

{"golden_diff": "diff --git a/mitmproxy/contentviews/base.py b/mitmproxy/contentviews/base.py\n--- a/mitmproxy/contentviews/base.py\n+++ b/mitmproxy/contentviews/base.py\n@@ -49,8 +49,9 @@\n     ]\n     entries, where key is padded to a uniform width.\n     \"\"\"\n-    max_key_len = max(len(k) for k in d.keys())\n-    max_key_len = min(max_key_len, KEY_MAX)\n+\n+    max_key_len = max((len(k) for k in d.keys()), default=0)\n+    max_key_len = min((max_key_len, KEY_MAX), default=0)\n     for key, value in d.items():\n         if isinstance(key, bytes):\n             key += b\":\"\n", "issue": "Mitmproxy crashes, when viewing response body in url-encoded mode\n##### Steps to reproduce the problem:\r\n\r\n1. Run mitmproxy.\r\n2. `n` -> `Enter` -> `Enter` -> `m` -> `url-encoded`\r\n\r\n_I am seeing:_\r\n```\r\nTraceback (most recent call last):\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/master.py\", line 216, in run\r\n    self.loop.run()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 278, in run\r\n    self._run()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 376, in _run\r\n    self.event_loop.run()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 682, in run\r\n    self._loop()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 719, in _loop\r\n    self._watch_files[fd]()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py\", line 393, in <lambda>\r\n    event_loop, callback, self.get_available_raw_input())\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py\", line 493, in parse_input\r\n    callback(processed, processed_codes)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 403, in _update\r\n    self.process_input(keys)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 503, in process_input\r\n    k = self._topmost_widget.keypress(self.screen_size, k)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py\", line 308, in keypress\r\n    k = super().keypress(size, k)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py\", line 1128, in keypress\r\n    return self.body.keypress( (maxcol, remaining), key )\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py\", line 44, in keypress\r\n    ret = super().keypress(size, key)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py\", line 1128, in keypress\r\n    return self.body.keypress( (maxcol, remaining), key )\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py\", line 592, in keypress\r\n    *self.calculate_padding_filler(size, True)), key)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/overlay.py\", line 130, in keypress\r\n    self.master.keymap.handle(\"global\", key)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/keymap.py\", line 130, in handle\r\n    return self.executor(b.command)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/commandexecutor.py\", line 17, in __call__\r\n    ret = self.master.commands.call(cmd)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py\", line 221, in call\r\n    return self.call_args(parts[0], parts[1:])\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py\", line 212, in call_args\r\n    return self.commands[path].call(args)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py\", line 101, in call\r\n    ret = self.func(*pargs)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py\", line 251, in wrapper\r\n    return function(*args, **kwargs)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/consoleaddons.py\", line 174, in nav_select\r\n    self.master.inject_key(\"m_select\")\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/master.py\", line 181, in inject_key\r\n    self.loop.process_input([key])\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 503, in process_input\r\n    k = self._topmost_widget.keypress(self.screen_size, k)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py\", line 308, in keypress\r\n    k = super().keypress(size, k)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py\", line 1128, in keypress\r\n    return self.body.keypress( (maxcol, remaining), key )\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py\", line 44, in keypress\r\n    ret = super().keypress(size, key)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py\", line 1128, in keypress\r\n    return self.body.keypress( (maxcol, remaining), key )\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py\", line 592, in keypress\r\n    *self.calculate_padding_filler(size, True)), key)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/overlay.py\", line 121, in keypress\r\n    signals.pop_view_state.send(self)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/blinker/base.py\", line 267, in send\r\n    for receiver in self.receivers_for(sender)]\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/blinker/base.py\", line 267, in <listcomp>\r\n    for receiver in self.receivers_for(sender)]\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py\", line 244, in pop\r\n    self.focus_changed()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py\", line 209, in focus_changed\r\n    i.call(\"focus_changed\")\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py\", line 118, in call\r\n    getattr(self.top_window(), name)(*args, **kwargs)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py\", line 233, in focus_changed\r\n    self.body.focus_changed()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py\", line 58, in focus_changed\r\n    self.show()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/tabs.py\", line 73, in show\r\n    body = self.tabs[self.tab_offset][1](),\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py\", line 86, in view_request\r\n    return self.conn_text(self.flow.request)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py\", line 185, in conn_text\r\n    msg, body = self.content_view(viewmode, conn)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py\", line 112, in content_view\r\n    return self._get_content_view(viewmode, limit, flow_modify_cache_invalidation)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py\", line 132, in _get_content_view\r\n    for line in lines:\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/contentviews/__init__.py\", line 85, in safe_to_print\r\n    for line in lines:\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/contentviews/base.py\", line 52, in format_dict\r\n    max_key_len = max(len(k) for k in d.keys())\r\nValueError: max() arg is an empty sequence\r\n```\r\n\r\n##### Any other comments? What have you tried so far?\r\n\r\nThis issue is reproducible not only for request/response without body, but for `query` (maybe some others) content type as well. I continue digging.\r\n\r\n##### System information\r\n\r\nMitmproxy: 3.0.0.dev53 (commit 3b5237c) \r\nPython:    3.5.2\r\nOpenSSL:   OpenSSL 1.1.0g  2 Nov 2017\r\nPlatform:  Linux-4.4.0-109-generic-x86_64-with-Ubuntu-16.04-xenial\r\n\n", "before_files": [{"content": "# Default view cutoff *in lines*\nimport typing\n\nVIEW_CUTOFF = 512\n\nKEY_MAX = 30\n\nTTextType = typing.Union[str, bytes]  # FIXME: This should be either bytes or str ultimately.\nTViewLine = typing.List[typing.Tuple[str, TTextType]]\nTViewResult = typing.Tuple[str, typing.Iterator[TViewLine]]\n\n\nclass View:\n    name = None  # type: str\n    prompt = None  # type: typing.Tuple[str,str]\n    content_types = []  # type: typing.List[str]\n\n    def __call__(self, data: bytes, **metadata) -> TViewResult:\n        \"\"\"\n        Transform raw data into human-readable output.\n\n        Args:\n            data: the data to decode/format.\n            metadata: optional keyword-only arguments for metadata. Implementations must not\n                rely on a given argument being present.\n\n        Returns:\n            A (description, content generator) tuple.\n\n            The content generator yields lists of (style, text) tuples, where each list represents\n            a single line. ``text`` is a unfiltered byte string which may need to be escaped,\n            depending on the used output.\n\n        Caveats:\n            The content generator must not yield tuples of tuples,\n            because urwid cannot process that. You have to yield a *list* of tuples per line.\n        \"\"\"\n        raise NotImplementedError()  # pragma: no cover\n\n\ndef format_dict(\n        d: typing.Mapping[TTextType, TTextType]\n) -> typing.Iterator[TViewLine]:\n    \"\"\"\n    Helper function that transforms the given dictionary into a list of\n    [\n        (\"key\",   key  )\n        (\"value\", value)\n    ]\n    entries, where key is padded to a uniform width.\n    \"\"\"\n    max_key_len = max(len(k) for k in d.keys())\n    max_key_len = min(max_key_len, KEY_MAX)\n    for key, value in d.items():\n        if isinstance(key, bytes):\n            key += b\":\"\n        else:\n            key += \":\"\n        key = key.ljust(max_key_len + 2)\n        yield [\n            (\"header\", key),\n            (\"text\", value)\n        ]\n\n\ndef format_text(text: TTextType) -> typing.Iterator[TViewLine]:\n    \"\"\"\n    Helper function that transforms bytes into the view output format.\n    \"\"\"\n    for line in text.splitlines():\n        yield [(\"text\", line)]\n", "path": "mitmproxy/contentviews/base.py"}], "after_files": [{"content": "# Default view cutoff *in lines*\nimport typing\n\nVIEW_CUTOFF = 512\n\nKEY_MAX = 30\n\nTTextType = typing.Union[str, bytes]  # FIXME: This should be either bytes or str ultimately.\nTViewLine = typing.List[typing.Tuple[str, TTextType]]\nTViewResult = typing.Tuple[str, typing.Iterator[TViewLine]]\n\n\nclass View:\n    name = None  # type: str\n    prompt = None  # type: typing.Tuple[str,str]\n    content_types = []  # type: typing.List[str]\n\n    def __call__(self, data: bytes, **metadata) -> TViewResult:\n        \"\"\"\n        Transform raw data into human-readable output.\n\n        Args:\n            data: the data to decode/format.\n            metadata: optional keyword-only arguments for metadata. Implementations must not\n                rely on a given argument being present.\n\n        Returns:\n            A (description, content generator) tuple.\n\n            The content generator yields lists of (style, text) tuples, where each list represents\n            a single line. ``text`` is a unfiltered byte string which may need to be escaped,\n            depending on the used output.\n\n        Caveats:\n            The content generator must not yield tuples of tuples,\n            because urwid cannot process that. You have to yield a *list* of tuples per line.\n        \"\"\"\n        raise NotImplementedError()  # pragma: no cover\n\n\ndef format_dict(\n        d: typing.Mapping[TTextType, TTextType]\n) -> typing.Iterator[TViewLine]:\n    \"\"\"\n    Helper function that transforms the given dictionary into a list of\n    [\n        (\"key\",   key  )\n        (\"value\", value)\n    ]\n    entries, where key is padded to a uniform width.\n    \"\"\"\n\n    max_key_len = max((len(k) for k in d.keys()), default=0)\n    max_key_len = min((max_key_len, KEY_MAX), default=0)\n    for key, value in d.items():\n        if isinstance(key, bytes):\n            key += b\":\"\n        else:\n            key += \":\"\n        key = key.ljust(max_key_len + 2)\n        yield [\n            (\"header\", key),\n            (\"text\", value)\n        ]\n\n\ndef format_text(text: TTextType) -> typing.Iterator[TViewLine]:\n    \"\"\"\n    Helper function that transforms bytes into the view output format.\n    \"\"\"\n    for line in text.splitlines():\n        yield [(\"text\", line)]\n", "path": "mitmproxy/contentviews/base.py"}]}