Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: shell command 词法解析错误未捕获 #3290

Merged
merged 5 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions nonebot/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,12 +557,22 @@ async def __call__(
if cmd not in self.cmds or msg is None:
return False

state[SHELL_ARGV] = list(
chain.from_iterable(
shlex.split(str(seg)) if cast(MessageSegment, seg).is_text() else (seg,)
for seg in msg
try:
state[SHELL_ARGV] = list(
chain.from_iterable(
shlex.split(str(seg))
if cast(MessageSegment, seg).is_text()
else (seg,)
for seg in msg
)
)
)
except Exception as e:
# set SHELL_ARGV to none indicating shlex error
state[SHELL_ARGV] = None
# ensure SHELL_ARGS is set to ParserExit if parser is provided
if self.parser:
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
return True

if self.parser:
t = parser_message.set("")
Expand Down
22 changes: 22 additions & 0 deletions tests/test_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,9 +371,31 @@ async def test_shell_command():
assert state[SHELL_ARGV] == []
assert SHELL_ARGS not in state

test_lexical_error = shell_command(CMD)
dependent = next(iter(test_lexical_error.checkers))
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = Message("-a '1")
event = make_fake_event(_message=message)()
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
assert await dependent(event=event, state=state)
assert state[SHELL_ARGV] is None

parser = ArgumentParser("test")
parser.add_argument("-a", required=True)

test_lexical_error_with_parser = shell_command(CMD, parser=ArgumentParser("test"))
dependent = next(iter(test_lexical_error_with_parser.checkers))
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = Message("-a '1")
event = make_fake_event(_message=message)()
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
assert await dependent(event=event, state=state)
assert state[SHELL_ARGV] is None
assert isinstance(state[SHELL_ARGS], ParserExit)
assert state[SHELL_ARGS].status != 0

test_simple_parser = shell_command(CMD, parser=parser)
dependent = next(iter(test_simple_parser.checkers))
checker = dependent.call
Expand Down
48 changes: 42 additions & 6 deletions website/docs/advanced/dependency.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -843,21 +843,39 @@ async def _(foo: str = CommandWhitespace()): ...

### ShellCommandArgv

获取 shell 命令解析前的参数列表,列表中可能包含文本字符串和富文本消息段(如:图片)。
获取 shell 命令解析前的参数列表,列表中可能包含文本字符串和富文本消息段(如:图片)。当词法解析出错的时候,返回值将为 `None`。通过重载机制即可处理两种不同的情况。

<Tabs groupId="python">
<TabItem value="3.10" label="Python 3.10+" default>

```python {4}
from typing import Annotated
from nonebot.params import ShellCommandArgs
from nonebot import on_shell_command
from nonebot.params import ShellCommandArgv

matcher = on_shell_command("cmd")

# 解析失败
@matcher.handle()
async def _(foo: Annotated[None, ShellCommandArgv()]): ...

# 解析成功
@matcher.handle()
async def _(foo: Annotated[list[str | MessageSegment], ShellCommandArgv()]): ...
```

```python {4}
from nonebot.params import ShellCommandArgs
from nonebot import on_shell_command
from nonebot.params import ShellCommandArgv

matcher = on_shell_command("cmd")

# 解析失败
@matcher.handle()
async def _(foo: None = ShellCommandArgv()): ...

# 解析成功
@matcher.handle()
async def _(foo: list[str | MessageSegment] = ShellCommandArgv()): ...
```

Expand All @@ -866,15 +884,33 @@ async def _(foo: list[str | MessageSegment] = ShellCommandArgv()): ...

```python {4}
from typing import Union, Annotated
from nonebot.params import ShellCommandArgs
from nonebot import on_shell_command
from nonebot.params import ShellCommandArgv

matcher = on_shell_command("cmd")

# 解析失败
@matcher.handle()
async def _(foo: Annotated[None, ShellCommandArgv()]): ...

# 解析成功
@matcher.handle()
async def _(foo: Annotated[list[Union[str, MessageSegment]], ShellCommandArgv()]): ...
```

```python {4}
from typing import Union
from nonebot.params import ShellCommandArgs
from nonebot import on_shell_command
from nonebot.params import ShellCommandArgv

matcher = on_shell_command("cmd")

# 解析失败
@matcher.handle()
async def _(foo: None = ShellCommandArgv()): ...

# 解析成功
@matcher.handle()
async def _(foo: list[Union[str, MessageSegment]] = ShellCommandArgv()): ...
```

Expand All @@ -886,7 +922,7 @@ async def _(foo: list[Union[str, MessageSegment]] = ShellCommandArgv()): ...
获取 shell 命令解析后的参数 Namespace,支持 MessageSegment 富文本(如:图片)。

:::tip 提示
如果参数解析成功,则为 parser 返回的 Namespace;如果参数解析失败,则为 [`ParserExit`](../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。通过重载机制即可处理两种不同的情况。
如果参数解析成功,则为 parser 返回的 Namespace;如果参数解析失败,则为 [`ParserExit`](../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。在前置词法解析失败时,返回值也为 [`ParserExit`](../api/exception.md#ParserExit) 异常。通过重载机制即可处理两种不同的情况。

由于 `ArgumentParser` 在解析到 `--help` 参数时也会抛出异常,这种情况下错误码为 `0` 且错误信息即为帮助信息。
:::
Expand Down