From d63b4383cabf67db0f43b94fd28bb9c87354892a Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:21:16 +0000 Subject: [PATCH 1/5] :bug: fix shlex error not catched --- nonebot/rule.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/nonebot/rule.py b/nonebot/rule.py index e984049029c8..71218846ea67 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -557,12 +557,21 @@ 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: + # shlex may raise value error when syntax error + state[SHELL_ARGS] = ParserExit(status=2, message=str(e)) + # we need to set SHELL_ARGV to empty list to avoid key error + state[SHELL_ARGV] = [] + return True if self.parser: t = parser_message.set("") From 1cf6bd9f04a94ae3a531827e6dde51e6c795d4cd Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:40:42 +0000 Subject: [PATCH 2/5] :white_check_mark: add tests --- nonebot/rule.py | 9 +++--- tests/test_rule.py | 13 ++++++++ website/docs/advanced/dependency.mdx | 48 ++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/nonebot/rule.py b/nonebot/rule.py index 71218846ea67..c458031af3e0 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -567,10 +567,11 @@ async def __call__( ) ) except Exception as e: - # shlex may raise value error when syntax error - state[SHELL_ARGS] = ParserExit(status=2, message=str(e)) - # we need to set SHELL_ARGV to empty list to avoid key error - state[SHELL_ARGV] = [] + # 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: diff --git a/tests/test_rule.py b/tests/test_rule.py index 5e145a799982..1ef774a61de0 100644 --- a/tests/test_rule.py +++ b/tests/test_rule.py @@ -371,6 +371,19 @@ async def test_shell_command(): assert state[SHELL_ARGV] == [] assert SHELL_ARGS not in state + test_syntax_error = shell_command(CMD) + dependent = next(iter(test_syntax_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 + assert isinstance(state[SHELL_ARGS], ParserExit) + assert state[SHELL_ARGS].status != 0 + assert state[SHELL_ARGS].message.startswith("ValueError") + parser = ArgumentParser("test") parser.add_argument("-a", required=True) diff --git a/website/docs/advanced/dependency.mdx b/website/docs/advanced/dependency.mdx index e7946b39c89c..43d2b466f192 100644 --- a/website/docs/advanced/dependency.mdx +++ b/website/docs/advanced/dependency.mdx @@ -843,21 +843,39 @@ async def _(foo: str = CommandWhitespace()): ... ### ShellCommandArgv -获取 shell 命令解析前的参数列表,列表中可能包含文本字符串和富文本消息段(如:图片)。 +获取 shell 命令解析前的参数列表,列表中可能包含文本字符串和富文本消息段(如:图片)。当词法解析出错的时候,返回值将为 `None`。通过重载机制即可处理两种不同的情况。 ```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()): ... ``` @@ -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()): ... ``` @@ -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` 且错误信息即为帮助信息。 ::: From 396cc5024c7714f5ab12501be54c52577048dec8 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:45:24 +0000 Subject: [PATCH 3/5] :white_check_mark: fix test --- tests/test_rule.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/test_rule.py b/tests/test_rule.py index 1ef774a61de0..b5e562a0f653 100644 --- a/tests/test_rule.py +++ b/tests/test_rule.py @@ -371,8 +371,8 @@ async def test_shell_command(): assert state[SHELL_ARGV] == [] assert SHELL_ARGS not in state - test_syntax_error = shell_command(CMD) - dependent = next(iter(test_syntax_error.checkers)) + test_lexical_error = shell_command(CMD) + dependent = next(iter(test_lexical_error.checkers)) checker = dependent.call assert isinstance(checker, ShellCommandRule) message = Message("-a '1") @@ -382,11 +382,21 @@ async def test_shell_command(): assert state[SHELL_ARGV] is None assert isinstance(state[SHELL_ARGS], ParserExit) assert state[SHELL_ARGS].status != 0 - assert state[SHELL_ARGS].message.startswith("ValueError") 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) + test_simple_parser = shell_command(CMD, parser=parser) dependent = next(iter(test_simple_parser.checkers)) checker = dependent.call From db958df804b46dfe1f2a69a82371a5f83975a007 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:53:04 +0000 Subject: [PATCH 4/5] :white_check_mark: fix test --- tests/test_rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_rule.py b/tests/test_rule.py index b5e562a0f653..f5984f328d46 100644 --- a/tests/test_rule.py +++ b/tests/test_rule.py @@ -380,8 +380,6 @@ async def test_shell_command(): 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 parser = ArgumentParser("test") parser.add_argument("-a", required=True) @@ -396,6 +394,8 @@ async def test_shell_command(): 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 + assert state[SHELL_ARGS].message.startswith("ValueError") test_simple_parser = shell_command(CMD, parser=parser) dependent = next(iter(test_simple_parser.checkers)) From b4e170a85b51912a3d84243308283513c6fd7b92 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:55:44 +0800 Subject: [PATCH 5/5] Update test_rule.py --- tests/test_rule.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_rule.py b/tests/test_rule.py index f5984f328d46..c7bee2bf56bc 100644 --- a/tests/test_rule.py +++ b/tests/test_rule.py @@ -395,7 +395,6 @@ async def test_shell_command(): assert state[SHELL_ARGV] is None assert isinstance(state[SHELL_ARGS], ParserExit) assert state[SHELL_ARGS].status != 0 - assert state[SHELL_ARGS].message.startswith("ValueError") test_simple_parser = shell_command(CMD, parser=parser) dependent = next(iter(test_simple_parser.checkers))