From d1d3630cf283a6c1a5da8af70f7652f9778d5469 Mon Sep 17 00:00:00 2001 From: RF-Tar-Railt <3165388245@qq.com> Date: Sat, 10 Feb 2024 17:11:00 +0800 Subject: [PATCH] :construction: enhance shortcut --- src/arclet/alconna/_internal/_analyser.py | 18 ++++--- src/arclet/alconna/_internal/_argv.py | 2 +- src/arclet/alconna/_internal/_handlers.py | 4 +- src/arclet/alconna/core.py | 21 +++++--- src/arclet/alconna/manager.py | 60 +++++++++-------------- src/arclet/alconna/typing.py | 5 +- tests/core_test.py | 21 ++++---- 7 files changed, 62 insertions(+), 69 deletions(-) diff --git a/src/arclet/alconna/_internal/_analyser.py b/src/arclet/alconna/_internal/_analyser.py index 94d56a9d..18608f12 100644 --- a/src/arclet/alconna/_internal/_analyser.py +++ b/src/arclet/alconna/_internal/_analyser.py @@ -261,13 +261,12 @@ def __repr__(self): return f"<{self.__class__.__name__} of {self.command.path}>" def shortcut( - self, argv: Argv[TDC], trigger: str, data: list[Any], short: Arparma | InnerShortcutArgs, reg: Match | None = None + self, argv: Argv[TDC], data: list[Any], short: Arparma | InnerShortcutArgs, reg: Match | None = None ) -> Arparma[TDC]: """处理被触发的快捷命令 Args: argv (Argv[TDC]): 命令行参数 - trigger (str): 触发词 data (list[Any]): 剩余参数 short (Arparma | InnerShortcutArgs): 快捷命令 reg (Match | None): 可能的正则匹配结果 @@ -287,8 +286,8 @@ def shortcut( if self.command.meta.raise_exception: raise exc return self.export(argv, True, exc) - if short.fuzzy and reg and len(trigger) > reg.span()[1]: - argv.addon((trigger[reg.span()[1] :],)) + # if short.fuzzy and reg and len(trigger) > reg.span()[1]: + # argv.addon((trigger[reg.span()[1] :],)) argv.addon(short.args) data = _handle_shortcut_data(argv, data) argv.bak_data = argv.raw_data.copy() @@ -324,17 +323,22 @@ def process(self, argv: Argv[TDC]) -> Arparma[TDC]: if self.command.meta.raise_exception: raise e return self.export(argv, True, e) + text = argv.separators[0].join([_next] + argv.release()) try: - _res = command_manager.find_shortcut(self.command, _next) + short, mat = command_manager.find_shortcut(self.command, text) except ValueError as exc: if self.command.meta.raise_exception: raise e from exc return self.export(argv, True, e) else: - data = argv.release() + if mat and len(text) > mat.span()[1]: + data = text[mat.span()[1]:].lstrip(argv.separators[0]).split(argv.separators[0]) + else: + data = [] + # data = argv.release() self.reset() argv.reset() - return self.shortcut(argv, _next, data, *_res) + return self.shortcut(argv, data, short, mat) except FuzzyMatchSuccess as Fuzzy: output_manager.send(self.command.name, lambda: str(Fuzzy)) diff --git a/src/arclet/alconna/_internal/_argv.py b/src/arclet/alconna/_internal/_argv.py index 115d6948..e47336cf 100644 --- a/src/arclet/alconna/_internal/_argv.py +++ b/src/arclet/alconna/_internal/_argv.py @@ -110,7 +110,7 @@ def build(self, data: TDC) -> Self: if not self.converter: raise TypeError(data) try: - data = self.converter(data) + data = self.converter(data) # type: ignore except Exception as e: raise TypeError(data) from e self.origin = data diff --git a/src/arclet/alconna/_internal/_handlers.py b/src/arclet/alconna/_internal/_handlers.py index fbfca465..558aca99 100644 --- a/src/arclet/alconna/_internal/_handlers.py +++ b/src/arclet/alconna/_internal/_handlers.py @@ -537,9 +537,9 @@ def handle_shortcut(analyser: Analyser, argv: Argv): elif opt_v["command"] == "_": msg = analyser.command.shortcut(opt_v["name"], None) elif opt_v["command"] == "$": - msg = analyser.command.shortcut(opt_v["name"], {}) + msg = analyser.command.shortcut(opt_v["name"], fuzzy=False) else: - msg = analyser.command.shortcut(opt_v["name"], {"command": argv.converter(opt_v["command"])}) + msg = analyser.command.shortcut(opt_v["name"], fuzzy=False, command=opt_v["command"]) output_manager.send(analyser.command.name, lambda: msg) except Exception as e: output_manager.send(analyser.command.name, lambda: str(e)) diff --git a/src/arclet/alconna/core.py b/src/arclet/alconna/core.py index 30aa7916..2cb78df0 100644 --- a/src/arclet/alconna/core.py +++ b/src/arclet/alconna/core.py @@ -19,7 +19,7 @@ from .exceptions import ExecuteFailed, NullMessage from .formatter import TextFormatter from .manager import ShortcutArgs, command_manager -from .typing import TDC, CommandMeta, DataCollection, ShortcutRegWrapper, TPrefixes +from .typing import TDC, CommandMeta, DataCollection, ShortcutRegWrapper, TPrefixes, InnerShortcutArgs T_Duplication = TypeVar("T_Duplication", bound=Duplication) T = TypeVar("T") @@ -203,7 +203,14 @@ def get_help(self) -> str: def get_shortcuts(self) -> list[str]: """返回该命令注册的快捷命令""" - return command_manager.list_shortcut(self) + result = [] + shortcuts = command_manager.get_shortcut(self) + for key, short in shortcuts.items(): + if isinstance(short, InnerShortcutArgs): + result.append(key + (" ...args" if short.fuzzy else "")) + else: + result.append(key) + return result @overload def shortcut(self, key: str, args: ShortcutArgs | None = None) -> str: @@ -211,7 +218,7 @@ def shortcut(self, key: str, args: ShortcutArgs | None = None) -> str: Args: key (str): 快捷命令名 - args (ShortcutArgs[TDC]): 快捷命令参数, 不传入时则尝试使用最近一次使用的命令 + args (ShortcutArgs): 快捷命令参数, 不传入时则尝试使用最近一次使用的命令 Returns: str: 操作结果 @@ -226,7 +233,7 @@ def shortcut( self, key: str, *, - command: TDC | None = None, + command: str | None = None, arguments: list[Any] | None = None, fuzzy: bool = True, prefix: bool = False, @@ -236,7 +243,7 @@ def shortcut( Args: key (str): 快捷命令名 - command (TDC): 快捷命令指向的命令 + command (str): 快捷命令指向的命令 arguments (list[Any] | None, optional): 快捷命令参数, 默认为 `None` fuzzy (bool, optional): 是否允许命令后随参数, 默认为 `True` prefix (bool, optional): 是否调用时保留指令前缀, 默认为 `False` @@ -271,9 +278,9 @@ def shortcut(self, key: str, args: ShortcutArgs | None = None, delete: bool = Fa Args: key (str): 快捷命令名 - args (ShortcutArgs[TDC] | None, optional): 快捷命令参数, 不传入时则尝试使用最近一次使用的命令 + args (ShortcutArgs | None, optional): 快捷命令参数, 不传入时则尝试使用最近一次使用的命令 delete (bool, optional): 是否删除快捷命令, 默认为 `False` - command (TDC, optional): 快捷命令指向的命令 + command (str, optional): 快捷命令指向的命令 arguments (list[Any] | None, optional): 快捷命令参数, 默认为 `None` fuzzy (bool, optional): 是否允许命令后随参数, 默认为 `True` prefix (bool, optional): 是否调用时保留指令前缀, 默认为 `False` diff --git a/src/arclet/alconna/manager.py b/src/arclet/alconna/manager.py index 5e5b0907..d253c5c9 100644 --- a/src/arclet/alconna/manager.py +++ b/src/arclet/alconna/manager.py @@ -8,7 +8,7 @@ import weakref from copy import copy from datetime import datetime -from typing import TYPE_CHECKING, Any, Match, Union, overload +from typing import TYPE_CHECKING, Any, Match, Union from weakref import WeakKeyDictionary, WeakValueDictionary from tarina import LRU, lang @@ -220,62 +220,48 @@ def add_shortcut(self, target: Alconna, key: str, source: Arparma | ShortcutArgs else: raise ValueError(lang.require("manager", "incorrect_shortcut").format(target=f"{key}")) - def list_shortcut(self, target: Alconna) -> list[str]: + def get_shortcut(self, target: Alconna[TDC]) -> dict[str, Union[Arparma[TDC], InnerShortcutArgs]]: """列出快捷命令 Args: target (Alconna): 目标命令 Returns: - list[str]: 快捷命令的名称 + dict[str, Arparma | InnerShortcutArgs]: 快捷命令的参数 """ namespace, name = self._command_part(target.path) - result = [] + if f"{namespace}.{name}" not in self.__analysers: + raise ValueError(lang.require("manager", "undefined_command").format(target=f"{namespace}.{name}")) if not (_shortcut := self.__shortcuts.get(f"{namespace}.{name}")): - return result - for i in _shortcut: - short = self.__shortcuts[i] - if isinstance(short, InnerShortcutArgs): - result.append(i + (" ...args" if short.fuzzy else "")) - else: - result.append(i) - return result - - @overload - def find_shortcut(self, target: Alconna[TDC]) -> list[Union[Arparma[TDC], InnerShortcutArgs]]: - ... + return {} + return _shortcut - @overload - def find_shortcut(self, target: Alconna[TDC], query: str) -> tuple[Arparma[TDC] | InnerShortcutArgs, Match[str] | None]: - ... - - def find_shortcut(self, target: Alconna[TDC], query: str | None = None): + def find_shortcut( + self, target: Alconna[TDC], query: str + ) -> tuple[Arparma[TDC] | InnerShortcutArgs, Match[str] | None]: """查找快捷命令 Args: target (Alconna): 目标命令 - query (str, optional): 快捷命令的名称. Defaults to None. + query (str): 快捷命令的名称. Returns: - list[Union[Arparma, InnerShortcutArgs]] | tuple[Union[Arparma, InnerShortcutArgs], Match[str]]: \ - 快捷命令的参数, 若没有 `query` 则返回目标命令的所有快捷命令, 否则返回匹配的快捷命令 + tuple[Union[Arparma, InnerShortcutArgs], re.Match[str]]: 返回匹配的快捷命令 """ namespace, name = self._command_part(target.path) if not (_shortcut := self.__shortcuts.get(f"{namespace}.{name}")): raise ValueError(lang.require("manager", "undefined_command").format(target=f"{namespace}.{name}")) - if query: - try: - return _shortcut[query], None - except KeyError as e: - for key, args in _shortcut.items(): - if isinstance(args, InnerShortcutArgs) and args.fuzzy and (mat := re.match(f"^{key}", query)): - return args, mat - elif mat := re.fullmatch(key, query): - return _shortcut[key], mat - raise ValueError( - lang.require("manager", "shortcut_parse_error").format(target=f"{namespace}.{name}", query=query) - ) from e - return list(_shortcut.values()) + try: + return _shortcut[query], None + except KeyError as e: + for key, args in _shortcut.items(): + if isinstance(args, InnerShortcutArgs) and args.fuzzy and (mat := re.match(f"^{key}", query)): + return args, mat + elif mat := re.fullmatch(key, query): + return _shortcut[key], mat + raise ValueError( + lang.require("manager", "shortcut_parse_error").format(target=f"{namespace}.{name}", query=query) + ) from e def delete_shortcut(self, target: Alconna, key: str | None = None): """删除快捷命令""" diff --git a/src/arclet/alconna/typing.py b/src/arclet/alconna/typing.py index 046001d5..f5a77d5b 100644 --- a/src/arclet/alconna/typing.py +++ b/src/arclet/alconna/typing.py @@ -18,7 +18,7 @@ def __call__(self, slot: int | str, content: str) -> Any: ... class ShortcutArgs(TypedDict): """快捷指令参数""" - command: NotRequired[DataCollection[Any]] + command: NotRequired[str] """快捷指令的命令""" args: NotRequired[list[Any]] """快捷指令的附带参数""" @@ -158,8 +158,7 @@ def __init__(self, value: BasePattern[T] | type[T], flag: int | Literal["+", "*" alias = str(self.base) self.flag = "+" self.length = 1 - origin = Dict[str, self.base.origin] if isinstance(self.base, KeyWordVar) else Tuple[self.base.origin, ...] - super().__init__(model=MatchMode.KEEP, origin=origin, alias=alias) + super().__init__(model=MatchMode.KEEP, origin=self.base.origin, alias=alias) def __repr__(self): return self.alias diff --git a/tests/core_test.py b/tests/core_test.py index e51add0e..f831633c 100644 --- a/tests/core_test.py +++ b/tests/core_test.py @@ -429,27 +429,24 @@ def test_shortcut(): # 原始命令 alc16 = Alconna("core16", Args["foo", int], Option("bar", Args["baz", str])) assert alc16.parse("core16 123 bar abcd").matched is True + # 指令缩写传入, TEST1 -> core16 321 + alc16.parse("core16 --shortcut TEST1 'core16 321'") + res1 = alc16.parse("TEST1") + assert res1.foo == 321 + # 指令缩写传入的不允许后随参数 + alc16.parse("core16 --shortcut TEST2 core16") + res2 = alc16.parse("TEST2 442") + assert not res2.matched # 构造体缩写传入;{i} 将被可能的正则匹配替换 alc16.shortcut(r"TEST(\d+)(.+)", {"args": ["{0}", "bar {1}"]}) res = alc16.parse("TEST123aa") assert res.matched is True assert res.foo == 123 assert res.baz == "aa" - # 指令缩写传入, TEST2 -> core16 321 - alc16.parse("core16 --shortcut TEST2 'core16 321'") - res1 = alc16.parse("TEST2") - assert res1.foo == 321 - # 缩写命令的构造顺序: 1.新指令 2.传入指令的额外参数 3.构造体参数 - alc16.parse("core16 --shortcut TEST3 core16") - res2 = alc16.parse("TEST3 442") - assert res2.foo == 442 # 指令缩写也支持正则 alc16.parse(r"core16 --shortcut TESTa4(\d+) 'core16 {0}'") res3 = alc16.parse("TESTa4257") assert res3.foo == 257 - alc16.parse("core16 --shortcut TESTac 'core16 2{%0}'") - res4 = alc16.parse("TESTac 456") - assert res4.foo == 2456 alc16.shortcut("tTest", {}) assert alc16.parse("tTest123").matched @@ -467,7 +464,7 @@ def test_shortcut(): assert res8.content == "print('123')" alc16_2 = Alconna([1, 2, "3"], "core16_2", Args["foo", bool]) - alc16_2.shortcut("test", {"command": [1, "core16_2 True"]}) + alc16_2.shortcut("test", {"command": [1, "core16_2 True"]}) # type: ignore assert alc16_2.parse([1, "core16_2 True"]).matched res9 = alc16_2.parse("test") assert res9.foo is True