mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 18:18:25 +00:00
commands: emit types from partial parser, implement choice completion
This commit is contained in:
parent
8c0ba71fd8
commit
1c097813c1
@ -15,6 +15,14 @@ from mitmproxy import exceptions
|
|||||||
from mitmproxy import flow
|
from mitmproxy import flow
|
||||||
|
|
||||||
|
|
||||||
|
def lexer(s):
|
||||||
|
# mypy mis-identifies shlex.shlex as abstract
|
||||||
|
lex = shlex.shlex(s, punctuation_chars=True) # type: ignore
|
||||||
|
lex.whitespace_split = True
|
||||||
|
lex.commenters = ''
|
||||||
|
return lex
|
||||||
|
|
||||||
|
|
||||||
Cuts = typing.Sequence[
|
Cuts = typing.Sequence[
|
||||||
typing.Sequence[typing.Union[str, bytes]]
|
typing.Sequence[typing.Union[str, bytes]]
|
||||||
]
|
]
|
||||||
@ -123,9 +131,10 @@ class Command:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class ParseResult(typing.NamedTuple):
|
ParseResult = typing.NamedTuple(
|
||||||
value: str
|
"ParseResult",
|
||||||
type: typing.Type
|
[("value", str), ("type", typing.Type)],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CommandManager:
|
class CommandManager:
|
||||||
@ -147,27 +156,37 @@ class CommandManager:
|
|||||||
"""
|
"""
|
||||||
Parse a possibly partial command. Return a sequence of (part, type) tuples.
|
Parse a possibly partial command. Return a sequence of (part, type) tuples.
|
||||||
"""
|
"""
|
||||||
parts: typing.List[ParseResult] = []
|
|
||||||
buf = io.StringIO(cmdstr)
|
buf = io.StringIO(cmdstr)
|
||||||
# mypy mis-identifies shlex.shlex as abstract
|
parts: typing.List[str] = []
|
||||||
lex = shlex.shlex(buf) # type: ignore
|
lex = lexer(buf)
|
||||||
while 1:
|
while 1:
|
||||||
remainder = cmdstr[buf.tell():]
|
remainder = cmdstr[buf.tell():]
|
||||||
try:
|
try:
|
||||||
t = lex.get_token()
|
t = lex.get_token()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
parts.append(ParseResult(value = remainder, type = str))
|
parts.append(remainder)
|
||||||
break
|
break
|
||||||
if not t:
|
if not t:
|
||||||
break
|
break
|
||||||
typ: type = str
|
parts.append(t)
|
||||||
# First value is a special case: it has to be a command
|
|
||||||
if not parts:
|
if not parts:
|
||||||
typ = Cmd
|
parts = [""]
|
||||||
parts.append(ParseResult(value = t, type = typ))
|
elif cmdstr.endswith(" "):
|
||||||
if not parts:
|
parts.append("")
|
||||||
return [ParseResult(value = "", type = Cmd)]
|
|
||||||
return parts
|
parse: typing.List[ParseResult] = []
|
||||||
|
params: typing.List[type] = []
|
||||||
|
for i in range(len(parts)):
|
||||||
|
if i == 0:
|
||||||
|
params[:] = [Cmd]
|
||||||
|
if parts[i] in self.commands:
|
||||||
|
params.extend(self.commands[parts[i]].paramtypes)
|
||||||
|
if params:
|
||||||
|
typ = params.pop(0)
|
||||||
|
else:
|
||||||
|
typ = str
|
||||||
|
parse.append(ParseResult(value=parts[i], type=typ))
|
||||||
|
return parse
|
||||||
|
|
||||||
def call_args(self, path, args):
|
def call_args(self, path, args):
|
||||||
"""
|
"""
|
||||||
@ -181,7 +200,7 @@ class CommandManager:
|
|||||||
"""
|
"""
|
||||||
Call a command using a string. May raise CommandError.
|
Call a command using a string. May raise CommandError.
|
||||||
"""
|
"""
|
||||||
parts = shlex.split(cmdstr)
|
parts = list(lexer(cmdstr))
|
||||||
if not len(parts) >= 1:
|
if not len(parts) >= 1:
|
||||||
raise exceptions.CommandError("Invalid command: %s" % cmdstr)
|
raise exceptions.CommandError("Invalid command: %s" % cmdstr)
|
||||||
return self.call_args(parts[0], parts[1:])
|
return self.call_args(parts[0], parts[1:])
|
||||||
@ -208,8 +227,6 @@ def parsearg(manager: CommandManager, spec: str, argtype: type) -> typing.Any:
|
|||||||
"Invalid choice: see %s for options" % cmd
|
"Invalid choice: see %s for options" % cmd
|
||||||
)
|
)
|
||||||
return spec
|
return spec
|
||||||
if argtype in (Path, Cmd):
|
|
||||||
return spec
|
|
||||||
elif issubclass(argtype, str):
|
elif issubclass(argtype, str):
|
||||||
return spec
|
return spec
|
||||||
elif argtype == bool:
|
elif argtype == bool:
|
||||||
|
@ -72,7 +72,8 @@ class CommandBuffer():
|
|||||||
def cycle_completion(self) -> None:
|
def cycle_completion(self) -> None:
|
||||||
if not self.completion:
|
if not self.completion:
|
||||||
parts = self.master.commands.parse_partial(self.buf[:self.cursor])
|
parts = self.master.commands.parse_partial(self.buf[:self.cursor])
|
||||||
if parts[-1].type == mitmproxy.command.Cmd:
|
last = parts[-1]
|
||||||
|
if last.type == mitmproxy.command.Cmd:
|
||||||
self.completion = CompletionState(
|
self.completion = CompletionState(
|
||||||
completer = ListCompleter(
|
completer = ListCompleter(
|
||||||
parts[-1].value,
|
parts[-1].value,
|
||||||
@ -80,6 +81,14 @@ class CommandBuffer():
|
|||||||
),
|
),
|
||||||
parse = parts,
|
parse = parts,
|
||||||
)
|
)
|
||||||
|
elif isinstance(last.type, mitmproxy.command.Choice):
|
||||||
|
self.completion = CompletionState(
|
||||||
|
completer = ListCompleter(
|
||||||
|
parts[-1].value,
|
||||||
|
self.master.commands.call(last.type.options_command),
|
||||||
|
),
|
||||||
|
parse = parts,
|
||||||
|
)
|
||||||
if self.completion:
|
if self.completion:
|
||||||
nxt = self.completion.completer.cycle()
|
nxt = self.completion.completer.cycle()
|
||||||
buf = " ".join([i.value for i in self.completion.parse[:-1]]) + " " + nxt
|
buf = " ".join([i.value for i in self.completion.parse[:-1]]) + " " + nxt
|
||||||
|
@ -11,19 +11,24 @@ from mitmproxy.utils import typecheck
|
|||||||
|
|
||||||
|
|
||||||
class TAddon:
|
class TAddon:
|
||||||
|
@command.command("cmd1")
|
||||||
def cmd1(self, foo: str) -> str:
|
def cmd1(self, foo: str) -> str:
|
||||||
"""cmd1 help"""
|
"""cmd1 help"""
|
||||||
return "ret " + foo
|
return "ret " + foo
|
||||||
|
|
||||||
|
@command.command("cmd2")
|
||||||
def cmd2(self, foo: str) -> str:
|
def cmd2(self, foo: str) -> str:
|
||||||
return 99
|
return 99
|
||||||
|
|
||||||
|
@command.command("cmd3")
|
||||||
def cmd3(self, foo: int) -> int:
|
def cmd3(self, foo: int) -> int:
|
||||||
return foo
|
return foo
|
||||||
|
|
||||||
|
@command.command("empty")
|
||||||
def empty(self) -> None:
|
def empty(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@command.command("varargs")
|
||||||
def varargs(self, one: str, *var: str) -> typing.Sequence[str]:
|
def varargs(self, one: str, *var: str) -> typing.Sequence[str]:
|
||||||
return list(var)
|
return list(var)
|
||||||
|
|
||||||
@ -34,6 +39,7 @@ class TAddon:
|
|||||||
def choose(self, arg: str) -> typing.Sequence[str]:
|
def choose(self, arg: str) -> typing.Sequence[str]:
|
||||||
return ["one", "two", "three"]
|
return ["one", "two", "three"]
|
||||||
|
|
||||||
|
@command.command("path")
|
||||||
def path(self, arg: command.Path) -> None:
|
def path(self, arg: command.Path) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -82,11 +88,25 @@ class TestCommand:
|
|||||||
],
|
],
|
||||||
["a", [command.ParseResult(value = "a", type = command.Cmd)]],
|
["a", [command.ParseResult(value = "a", type = command.Cmd)]],
|
||||||
["", [command.ParseResult(value = "", type = command.Cmd)]],
|
["", [command.ParseResult(value = "", type = command.Cmd)]],
|
||||||
|
[
|
||||||
|
"cmd3 1",
|
||||||
|
[
|
||||||
|
command.ParseResult(value = "cmd3", type = command.Cmd),
|
||||||
|
command.ParseResult(value = "1", type = int),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"cmd3 ",
|
||||||
|
[
|
||||||
|
command.ParseResult(value = "cmd3", type = command.Cmd),
|
||||||
|
command.ParseResult(value = "", type = int),
|
||||||
|
]
|
||||||
|
],
|
||||||
]
|
]
|
||||||
with taddons.context() as tctx:
|
with taddons.context() as tctx:
|
||||||
cm = command.CommandManager(tctx.master)
|
tctx.master.addons.add(TAddon())
|
||||||
for s, expected in tests:
|
for s, expected in tests:
|
||||||
assert cm.parse_partial(s) == expected
|
assert tctx.master.commands.parse_partial(s) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_simple():
|
def test_simple():
|
||||||
|
Loading…
Reference in New Issue
Block a user