diff options
Diffstat (limited to 'mitmproxy/tools/console/grideditor/base.py')
-rw-r--r-- | mitmproxy/tools/console/grideditor/base.py | 227 |
1 files changed, 107 insertions, 120 deletions
diff --git a/mitmproxy/tools/console/grideditor/base.py b/mitmproxy/tools/console/grideditor/base.py index 35ae655f..cdda3def 100644 --- a/mitmproxy/tools/console/grideditor/base.py +++ b/mitmproxy/tools/console/grideditor/base.py @@ -1,26 +1,29 @@ import abc import copy -from typing import Any -from typing import Callable -from typing import Container -from typing import Iterable -from typing import Optional -from typing import Sequence -from typing import Tuple -from typing import Set # noqa - +import os +import typing import urwid -from mitmproxy.tools.console import common + +from mitmproxy.utils import strutils +from mitmproxy import exceptions from mitmproxy.tools.console import signals +from mitmproxy.tools.console import layoutwidget import mitmproxy.tools.console.master # noqa -FOOTER = [ - ('heading_key', "enter"), ":edit ", - ('heading_key', "q"), ":back ", -] -FOOTER_EDITING = [ - ('heading_key', "esc"), ":stop editing ", -] + +def read_file(filename: str, escaped: bool) -> typing.AnyStr: + filename = os.path.expanduser(filename) + try: + with open(filename, "r" if escaped else "rb") as f: + d = f.read() + except IOError as v: + raise exceptions.CommandError(v) + if escaped: + try: + d = strutils.escaped_str_to_bytes(d) + except ValueError: + raise exceptions.CommandError("Invalid Python-style string encoding.") + return d class Cell(urwid.WidgetWrap): @@ -50,27 +53,28 @@ class Column(metaclass=abc.ABCMeta): pass @abc.abstractmethod - def blank(self) -> Any: + def blank(self) -> typing.Any: pass - def keypress(self, key: str, editor: "GridEditor") -> Optional[str]: + def keypress(self, key: str, editor: "GridEditor") -> typing.Optional[str]: return key class GridRow(urwid.WidgetWrap): + def __init__( self, - focused: Optional[int], + focused: typing.Optional[int], editing: bool, editor: "GridEditor", - values: Tuple[Iterable[bytes], Container[int]] + values: typing.Tuple[typing.Iterable[bytes], typing.Container[int]] ) -> None: self.focused = focused self.editor = editor - self.edit_col = None # type: Optional[Cell] + self.edit_col = None # type: typing.Optional[Cell] errors = values[1] - self.fields = [] # type: Sequence[Any] + self.fields = [] # type: typing.Sequence[typing.Any] for i, v in enumerate(values[0]): if focused == i and editing: self.edit_col = self.editor.columns[i].Edit(v) @@ -116,14 +120,14 @@ class GridWalker(urwid.ListWalker): def __init__( self, - lst: Iterable[list], + lst: typing.Iterable[list], editor: "GridEditor" ) -> None: - self.lst = [(i, set()) for i in lst] # type: Sequence[Tuple[Any, Set]] + self.lst = [(i, set()) for i in lst] # type: typing.Sequence[typing.Tuple[typing.Any, typing.Set]] self.editor = editor self.focus = 0 self.focus_col = 0 - self.edit_row = None # type: Optional[GridRow] + self.edit_row = None # type: typing.Optional[GridRow] def _modified(self): self.editor.show_empty_msg() @@ -184,12 +188,10 @@ class GridWalker(urwid.ListWalker): self.edit_row = GridRow( self.focus_col, True, self.editor, self.lst[self.focus] ) - signals.footer_help.send(self, helptext=FOOTER_EDITING) self._modified() def stop_edit(self): if self.edit_row: - signals.footer_help.send(self, helptext=FOOTER) try: val = self.edit_row.edit_col.get_data() except ValueError: @@ -249,18 +251,19 @@ class GridListBox(urwid.ListBox): FIRST_WIDTH_MAX = 40 -FIRST_WIDTH_MIN = 20 class BaseGridEditor(urwid.WidgetWrap): + title = "" + keyctx = "grideditor" def __init__( self, master: "mitmproxy.tools.console.master.ConsoleMaster", title, columns, - value: Any, - callback: Callable[..., None], + value: typing.Any, + callback: typing.Callable[..., None], *cb_args, **cb_kwargs ) -> None: @@ -280,36 +283,29 @@ class BaseGridEditor(urwid.WidgetWrap): first_width = max(len(r), first_width) self.first_width = min(first_width, FIRST_WIDTH_MAX) - title = None - if self.title: - title = urwid.Text(self.title) - title = urwid.Padding(title, align="left", width=("relative", 100)) - title = urwid.AttrWrap(title, "heading") - - headings = [] - for i, col in enumerate(self.columns): - c = urwid.Text(col.heading) - if i == 0 and len(self.columns) > 1: - headings.append(("fixed", first_width + 2, c)) - else: - headings.append(c) - h = urwid.Columns( - headings, - dividechars=2 - ) - h = urwid.AttrWrap(h, "heading") + h = None + if any(col.heading for col in self.columns): + headings = [] + for i, col in enumerate(self.columns): + c = urwid.Text(col.heading) + if i == 0 and len(self.columns) > 1: + headings.append(("fixed", first_width + 2, c)) + else: + headings.append(c) + h = urwid.Columns( + headings, + dividechars=2 + ) + h = urwid.AttrWrap(h, "heading") self.walker = GridWalker(self.value, self) self.lb = GridListBox(self.walker) - w = urwid.Frame( - self.lb, - header=urwid.Pile([title, h]) if title else None - ) + w = urwid.Frame(self.lb, header=h) + super().__init__(w) - signals.footer_help.send(self, helptext="") self.show_empty_msg() - def view_popping(self): + def layout_popping(self): res = [] for i in self.walker.lst: if not i[1] and any([x for x in i[0]]): @@ -323,9 +319,9 @@ class BaseGridEditor(urwid.WidgetWrap): self._w.set_footer( urwid.Text( [ - ("highlight", "No values. Press "), - ("key", "a"), - ("highlight", " to add some."), + ("highlight", "No values - you should add some. Press "), + ("key", "?"), + ("highlight", " for help."), ] ) ) @@ -335,7 +331,7 @@ class BaseGridEditor(urwid.WidgetWrap): def keypress(self, size, key): if self.walker.edit_row: - if key in ["esc"]: + if key == "esc": self.walker.stop_edit() elif key == "tab": pf, pfc = self.walker.focus, self.walker.focus_col @@ -349,37 +345,31 @@ class BaseGridEditor(urwid.WidgetWrap): column = self.columns[self.walker.focus_col] if key == "m_start": self.walker.set_focus(0) + elif key == "m_next": + self.walker.tab_next() elif key == "m_end": self.walker.set_focus(len(self.walker.lst) - 1) elif key == "left": self.walker.left() elif key == "right": self.walker.right() - elif key == "tab": - self.walker.tab_next() - elif key == "a": - self.walker.add() - elif key == "A": - self.walker.insert() - elif key == "d": - self.walker.delete_focus() elif column.keypress(key, self) and not self.handle_key(key): return self._w.keypress(size, key) - def data_out(self, data: Sequence[list]) -> Any: + def data_out(self, data: typing.Sequence[list]) -> typing.Any: """ Called on raw list data, before data is returned through the callback. """ return data - def data_in(self, data: Any) -> Iterable[list]: + def data_in(self, data: typing.Any) -> typing.Iterable[list]: """ Called to prepare provided data. """ return data - def is_error(self, col: int, val: Any) -> Optional[str]: + def is_error(self, col: int, val: typing.Any) -> typing.Optional[str]: """ Return None, or a string error message. """ @@ -388,60 +378,54 @@ class BaseGridEditor(urwid.WidgetWrap): def handle_key(self, key): return False - def make_help(self): - text = [ - urwid.Text([("text", "Editor control:\n")]) - ] - keys = [ - ("A", "insert row before cursor"), - ("a", "add row after cursor"), - ("d", "delete row"), - ("e", "spawn external editor on current field"), - ("q", "save changes and exit editor"), - ("r", "read value from file"), - ("R", "read unescaped value from file"), - ("esc", "save changes and exit editor"), - ("tab", "next field"), - ("enter", "edit field"), - ] - text.extend( - common.format_keyvals(keys, key="key", val="text", indent=4) - ) - text.append( - urwid.Text( - [ - "\n", - ("text", "Values are escaped Python-style strings.\n"), - ] - ) - ) - return text + def cmd_add(self): + self.walker.add() + + def cmd_insert(self): + self.walker.insert() + + def cmd_delete(self): + self.walker.delete_focus() + + def cmd_read_file(self, path): + self.walker.set_current_value(read_file(path, False)) + + def cmd_read_file_escaped(self, path): + self.walker.set_current_value(read_file(path, True)) + def cmd_spawn_editor(self): + o = self.walker.get_current_value() + if o is not None: + n = self.master.spawn_editor(o) + n = strutils.clean_hanging_newline(n) + self.walker.set_current_value(n) -class GridEditor(urwid.WidgetWrap): + +class GridEditor(BaseGridEditor): title = None # type: str - columns = None # type: Sequence[Column] + columns = None # type: typing.Sequence[Column] + keyctx = "grideditor" def __init__( self, master: "mitmproxy.tools.console.master.ConsoleMaster", - value: Any, - callback: Callable[..., None], + value: typing.Any, + callback: typing.Callable[..., None], *cb_args, **cb_kwargs ) -> None: super().__init__( master, - value, self.title, self.columns, + value, callback, *cb_args, **cb_kwargs ) -class FocusEditor(urwid.WidgetWrap): +class FocusEditor(urwid.WidgetWrap, layoutwidget.LayoutWidget): """ A specialised GridEditor that edits the current focused flow. """ @@ -451,27 +435,11 @@ class FocusEditor(urwid.WidgetWrap): self.master = master self.focus_changed() - def focus_changed(self): - if self.master.view.focus.flow: - self._w = BaseGridEditor( - self.master.view.focus.flow, - self.title, - self.columns, - self.get_data(self.master.view.focus.flow), - self.set_data_update, - self.master.view.focus.flow, - ) - else: - self._w = urwid.Pile([]) - def call(self, v, name, *args, **kwargs): f = getattr(v, name, None) if f: f(*args, **kwargs) - def view_popping(self): - self.call(self._w, "view_popping") - def get_data(self, flow): """ Retrieve the data to edit from the current flow. @@ -487,3 +455,22 @@ class FocusEditor(urwid.WidgetWrap): def set_data_update(self, vals, flow): self.set_data(vals, flow) signals.flow_change.send(self, flow = flow) + + def key_responder(self): + return self._w + + def layout_popping(self): + self.call(self._w, "layout_popping") + + def focus_changed(self): + if self.master.view.focus.flow: + self._w = BaseGridEditor( + self.master, + self.title, + self.columns, + self.get_data(self.master.view.focus.flow), + self.set_data_update, + self.master.view.focus.flow, + ) + else: + self._w = urwid.Pile([]) |