Skip to content

Commit

Permalink
🚧 enhance shortcut
Browse files Browse the repository at this point in the history
  • Loading branch information
RF-Tar-Railt committed Feb 10, 2024
1 parent 6151fd6 commit d1d3630
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 69 deletions.
18 changes: 11 additions & 7 deletions src/arclet/alconna/_internal/_analyser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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): 可能的正则匹配结果
Expand All @@ -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()
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion src/arclet/alconna/_internal/_argv.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/arclet/alconna/_internal/_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
21 changes: 14 additions & 7 deletions src/arclet/alconna/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -203,15 +203,22 @@ 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:
"""操作快捷命令
Args:
key (str): 快捷命令名
args (ShortcutArgs[TDC]): 快捷命令参数, 不传入时则尝试使用最近一次使用的命令
args (ShortcutArgs): 快捷命令参数, 不传入时则尝试使用最近一次使用的命令
Returns:
str: 操作结果
Expand All @@ -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,
Expand All @@ -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`
Expand Down Expand Up @@ -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`
Expand Down
60 changes: 23 additions & 37 deletions src/arclet/alconna/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
"""删除快捷命令"""
Expand Down
5 changes: 2 additions & 3 deletions src/arclet/alconna/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
"""快捷指令的附带参数"""
Expand Down Expand Up @@ -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
Expand Down
21 changes: 9 additions & 12 deletions tests/core_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down

0 comments on commit d1d3630

Please sign in to comment.