console: add a data view overlay for command output

Fixes #2654
This commit is contained in:
Aldo Cortesi 2017-12-16 11:53:45 +13:00
parent eab27db7d6
commit f14ec2d8f0
7 changed files with 123 additions and 8 deletions

View File

@ -170,7 +170,7 @@ class Command:
if chk:
pargs.extend(remainder)
else:
raise exceptions.CommandError("Invalid value type.")
raise exceptions.CommandError("Invalid value type: %s - expected %s" % (remainder, self.paramtypes[-1]))
with self.manager.master.handlecontext():
ret = self.func(*pargs)

View File

@ -1,4 +1,3 @@
import time
from io import BytesIO
from mitmproxy.utils import data

View File

@ -2,6 +2,8 @@ import typing
from mitmproxy import exceptions
from mitmproxy import flow
from mitmproxy.tools.console import overlay
from mitmproxy.tools.console import signals
@ -21,9 +23,11 @@ class CommandExecutor:
signals.status_message.send(
message="Command returned %s flows" % len(ret)
)
elif len(str(ret)) < 50:
signals.status_message.send(message=str(ret))
else:
signals.status_message.send(
message="Command returned too much data to display."
self.master.overlay(
overlay.DataViewerOverlay(
self.master,
ret,
),
valign="top"
)

View File

@ -0,0 +1,67 @@
import typing
import urwid
from mitmproxy.tools.console import signals
from mitmproxy.tools.console.grideditor import base
from mitmproxy.utils import strutils
strbytes = typing.Union[str, bytes]
class Column(base.Column):
def Display(self, data):
return Display(data)
def Edit(self, data):
return Edit(data)
def blank(self):
return ""
def keypress(self, key, editor):
if key in ["m_select"]:
editor.walker.start_edit()
else:
return key
class Display(base.Cell):
def __init__(self, data: strbytes) -> None:
self.data = data
if isinstance(data, bytes):
escaped = strutils.bytes_to_escaped_str(data)
else:
escaped = data.encode()
w = urwid.Text(escaped, wrap="any")
super().__init__(w)
def get_data(self) -> strbytes:
return self.data
class Edit(base.Cell):
def __init__(self, data: strbytes) -> None:
if isinstance(data, bytes):
escaped = strutils.bytes_to_escaped_str(data)
else:
escaped = data.encode()
self.type = type(data) # type: typing.Type
w = urwid.Edit(edit_text=escaped, wrap="any", multiline=True)
w = urwid.AttrWrap(w, "editfield")
super().__init__(w)
def get_data(self) -> strbytes:
txt = self._w.get_text()[0].strip()
try:
if self.type == bytes:
return strutils.escaped_str_to_bytes(txt)
else:
return txt.decode()
except ValueError:
signals.status_message.send(
self,
message="Invalid Python-style string encoding.",
expire=1000
)
raise

View File

@ -46,7 +46,7 @@ class Edit(base.Cell):
except ValueError:
signals.status_message.send(
self,
message="Invalid Python-style string encoding.",
message="Invalid data.",
expire=1000
)
raise

View File

@ -2,6 +2,7 @@
from mitmproxy import exceptions
from mitmproxy.tools.console import layoutwidget
from mitmproxy.tools.console.grideditor import base
from mitmproxy.tools.console.grideditor import col
from mitmproxy.tools.console.grideditor import col_text
from mitmproxy.tools.console.grideditor import col_bytes
from mitmproxy.tools.console.grideditor import col_subgrid
@ -169,3 +170,20 @@ class OptionsEditor(base.GridEditor, layoutwidget.LayoutWidget):
def is_error(self, col, val):
pass
class DataViewer(base.GridEditor, layoutwidget.LayoutWidget):
title = None # type: str
def __init__(self, master, vals):
if vals:
if not isinstance(vals[0], list):
vals = [[i] for i in vals]
self.columns = [col.Column("")] * len(vals[0])
super().__init__(master, vals, self.callback)
def callback(self, vals):
pass
def is_error(self, col, val):
pass

View File

@ -148,3 +148,30 @@ class OptionsOverlay(urwid.WidgetWrap, layoutwidget.LayoutWidget):
def layout_popping(self):
return self.ge.layout_popping()
class DataViewerOverlay(urwid.WidgetWrap, layoutwidget.LayoutWidget):
keyctx = "grideditor"
def __init__(self, master, vals):
"""
vspace: how much vertical space to keep clear
"""
cols, rows = master.ui.get_cols_rows()
self.ge = grideditor.DataViewer(master, vals)
super().__init__(
urwid.AttrWrap(
urwid.LineBox(
urwid.BoxAdapter(self.ge, rows - 5),
title="Data viewer"
),
"background"
)
)
self.width = math.ceil(cols * 0.8)
def key_responder(self):
return self.ge.key_responder()
def layout_popping(self):
return self.ge.layout_popping()