%PDF- %PDF-
| Direktori : /proc/thread-self/root/usr/lib/python3/dist-packages/mitmproxy/tools/console/ |
| Current File : //proc/thread-self/root/usr/lib/python3/dist-packages/mitmproxy/tools/console/keymap.py |
import typing
import os
import ruamel.yaml
from mitmproxy import command
from mitmproxy.tools.console import commandexecutor
from mitmproxy.tools.console import signals
from mitmproxy import ctx
from mitmproxy import exceptions
import mitmproxy.types
class KeyBindingError(Exception):
pass
Contexts = {
"chooser",
"commands",
"commonkey",
"dataviewer",
"eventlog",
"flowlist",
"flowview",
"global",
"grideditor",
"help",
"keybindings",
"options",
}
navkeys = [
"m_start", "m_end", "m_next", "m_select",
"up", "down", "page_up", "page_down",
"left", "right"
]
class Binding:
def __init__(self, key, command, contexts, help):
self.key, self.command, self.contexts = key, command, sorted(contexts)
self.help = help
def keyspec(self):
"""
Translate the key spec from a convenient user specification to one
Urwid understands.
"""
return self.key.replace("space", " ")
def sortkey(self):
return self.key + ",".join(self.contexts)
class Keymap:
def __init__(self, master):
self.executor = commandexecutor.CommandExecutor(master)
self.keys = {}
for c in Contexts:
self.keys[c] = {}
self.bindings = []
def _check_contexts(self, contexts):
if not contexts:
raise ValueError("Must specify at least one context.")
for c in contexts:
if c not in Contexts:
raise ValueError("Unsupported context: %s" % c)
def add(
self,
key: str,
command: str,
contexts: typing.Sequence[str],
help=""
) -> None:
"""
Add a key to the key map.
"""
self._check_contexts(contexts)
for b in self.bindings:
if b.key == key and b.command.strip() == command.strip():
b.contexts = sorted(list(set(b.contexts + contexts)))
if help:
b.help = help
self.bind(b)
break
else:
self.remove(key, contexts)
b = Binding(key=key, command=command, contexts=contexts, help=help)
self.bindings.append(b)
self.bind(b)
signals.keybindings_change.send(self)
def remove(self, key: str, contexts: typing.Sequence[str]) -> None:
"""
Remove a key from the key map.
"""
self._check_contexts(contexts)
for c in contexts:
b = self.get(c, key)
if b:
self.unbind(b)
b.contexts = [x for x in b.contexts if x != c]
if b.contexts:
self.bindings.append(b)
self.bind(b)
signals.keybindings_change.send(self)
def bind(self, binding: Binding) -> None:
for c in binding.contexts:
self.keys[c][binding.keyspec()] = binding
def unbind(self, binding: Binding) -> None:
"""
Unbind also removes the binding from the list.
"""
for c in binding.contexts:
del self.keys[c][binding.keyspec()]
self.bindings = [b for b in self.bindings if b != binding]
def get(self, context: str, key: str) -> typing.Optional[Binding]:
if context in self.keys:
return self.keys[context].get(key, None)
return None
def list(self, context: str) -> typing.Sequence[Binding]:
b = [x for x in self.bindings if context in x.contexts or context == "all"]
single = [x for x in b if len(x.key.split()) == 1]
multi = [x for x in b if len(x.key.split()) != 1]
single.sort(key=lambda x: x.sortkey())
multi.sort(key=lambda x: x.sortkey())
return single + multi
def handle(self, context: str, key: str) -> typing.Optional[str]:
"""
Returns the key if it has not been handled, or None.
"""
b = self.get(context, key) or self.get("global", key)
if b:
return self.executor(b.command)
return key
def handle_only(self, context: str, key: str) -> typing.Optional[str]:
"""
Like handle, but ignores global bindings. Returns the key if it has
not been handled, or None.
"""
b = self.get(context, key)
if b:
return self.executor(b.command)
return key
keyAttrs = {
"key": lambda x: isinstance(x, str),
"cmd": lambda x: isinstance(x, str),
"ctx": lambda x: isinstance(x, list) and [isinstance(v, str) for v in x],
"help": lambda x: isinstance(x, str),
}
requiredKeyAttrs = {"key", "cmd"}
class KeymapConfig:
defaultFile = "keys.yaml"
@command.command("console.keymap.load")
def keymap_load_path(self, path: mitmproxy.types.Path) -> None:
try:
self.load_path(ctx.master.keymap, path) # type: ignore
except (OSError, KeyBindingError) as e:
raise exceptions.CommandError(
"Could not load key bindings - %s" % e
) from e
def running(self):
p = os.path.join(os.path.expanduser(ctx.options.confdir), self.defaultFile)
if os.path.exists(p):
try:
self.load_path(ctx.master.keymap, p)
except KeyBindingError as e:
ctx.log.error(e)
def load_path(self, km, p):
if os.path.exists(p) and os.path.isfile(p):
with open(p, encoding="utf8") as f:
try:
txt = f.read()
except UnicodeDecodeError as e:
raise KeyBindingError(
f"Encoding error - expected UTF8: {p}: {e}"
)
try:
vals = self.parse(txt)
except KeyBindingError as e:
raise KeyBindingError(
f"Error reading {p}: {e}"
) from e
for v in vals:
user_ctxs = v.get("ctx", ["global"])
try:
km._check_contexts(user_ctxs)
km.remove(v["key"], user_ctxs)
km.add(
key = v["key"],
command = v["cmd"],
contexts = user_ctxs,
help = v.get("help", None),
)
except ValueError as e:
raise KeyBindingError(
f"Error reading {p}: {e}"
) from e
def parse(self, text):
try:
data = ruamel.yaml.safe_load(text)
except ruamel.yaml.error.YAMLError as v:
if hasattr(v, "problem_mark"):
snip = v.problem_mark.get_snippet()
raise KeyBindingError(
"Key binding config error at line %s:\n%s\n%s" %
(v.problem_mark.line + 1, snip, v.problem)
)
else:
raise KeyBindingError("Could not parse key bindings.")
if not data:
return []
if not isinstance(data, list):
raise KeyBindingError("Inalid keybinding config - expected a list of keys")
for k in data:
unknown = k.keys() - keyAttrs.keys()
if unknown:
raise KeyBindingError("Unknown key attributes: %s" % unknown)
missing = requiredKeyAttrs - k.keys()
if missing:
raise KeyBindingError("Missing required key attributes: %s" % unknown)
for attr in k.keys():
if not keyAttrs[attr](k[attr]):
raise KeyBindingError("Invalid type for %s" % attr)
return data