Skip to content

Commit

Permalink
✨ version 1.7.37
Browse files Browse the repository at this point in the history
shortcut reg wrapper
  • Loading branch information
RF-Tar-Railt committed Nov 19, 2023
1 parent d127e7c commit 8b58b9b
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 50 deletions.
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# 更新日志

## Alconna 1.7.37

### 改进

- `Alconna.shortcut` 可以用关键字参数传入 `command``args`
- 允许提供参数来额外处理 `shortcut` 的正则匹配结果

## Alconna 1.7.36

### 改进

修改换行处理
- 修改换行处理

## Alconna 1.7.35

### 改进

为 completion 下提示列出的选中符号和未选中符号添加 i18n 支持
- 为 completion 下提示列出的选中符号和未选中符号添加 i18n 支持

## Alconna 1.7.34

Expand Down
2 changes: 1 addition & 1 deletion src/arclet/alconna/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from .typing import UnpackVar as UnpackVar
from .typing import Up as Up

__version__ = "1.7.36"
__version__ = "1.7.37"

# backward compatibility
Arpamar = Arparma
Expand Down
18 changes: 9 additions & 9 deletions src/arclet/alconna/_internal/_analyser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
from ..completion import comp_ctx
from ..config import config
from ..exceptions import ArgumentMissing, FuzzyMatchSuccess, InvalidParam, ParamsUnmatched, PauseTriggered, SpecialOptionTriggered
from ..manager import ShortcutArgs, command_manager
from ..manager import command_manager
from ..model import HeadResult, OptionResult, Sentence, SubcommandResult
from ..output import output_manager
from ..typing import TDC
from ..typing import TDC, InnerShortcutArgs
from ._handlers import (
_handle_shortcut_data,
_handle_shortcut_reg,
Expand Down Expand Up @@ -258,15 +258,15 @@ 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 | ShortcutArgs, reg: Match | None = None
self, argv: Argv[TDC], trigger: str, data: list[Any], short: Arparma | InnerShortcutArgs, reg: Match | None = None
) -> Arparma[TDC]:
"""处理被触发的快捷命令
Args:
argv (Argv[TDC]): 命令行参数
trigger (str): 触发词
data (list[Any]): 剩余参数
short (Arparma | ShortcutArgs): 快捷命令
short (Arparma | InnerShortcutArgs): 快捷命令
reg (Match | None): 可能的正则匹配结果
Returns:
Expand All @@ -278,20 +278,20 @@ def shortcut(
if isinstance(short, Arparma):
return short

argv.build(short.get("command", argv.converter(self.command.command or self.command.name)))
if not short.get("fuzzy") and data:
argv.build(short["command"]) # type: ignore
if not short["fuzzy"] and data:
exc = ParamsUnmatched(lang.require("analyser", "param_unmatched").format(target=data[0]))
if self.command.meta.raise_exception:
raise exc
return self.export(argv, True, exc)
if short.get("fuzzy") and reg and len(trigger) > reg.span()[1]:
if short["fuzzy"] and reg and len(trigger) > reg.span()[1]:
argv.addon((trigger[reg.span()[1] :],))
argv.addon(short.get("args", []))
argv.addon(short["args"])
data = _handle_shortcut_data(argv, data)
argv.bak_data = argv.raw_data.copy()
argv.addon(data)
if reg:
_handle_shortcut_reg(argv, reg.groups(), reg.groupdict())
argv.raw_data = _handle_shortcut_reg(argv, reg.groups(), reg.groupdict(), short["wrapper"])
argv.bak_data = argv.raw_data.copy()
if argv.message_cache:
argv.token = argv.generate_token(argv.raw_data)
Expand Down
58 changes: 50 additions & 8 deletions src/arclet/alconna/_internal/_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from ..exceptions import ArgumentMissing, FuzzyMatchSuccess, InvalidParam, PauseTriggered, SpecialOptionTriggered
from ..model import HeadResult, OptionResult, Sentence
from ..output import output_manager
from ..typing import KWBool
from ..typing import KWBool, ShortcutRegWrapper
from ._header import Double, Header
from ._util import escape, levenshtein, unescape

Expand Down Expand Up @@ -597,16 +597,58 @@ def _handle_shortcut_data(argv: Argv, data: list):
return [unit for i, unit in enumerate(data) if i not in record]


def _handle_shortcut_reg(argv: Argv, groups: tuple[str, ...], gdict: dict[str, str]):
for j, unit in enumerate(argv.raw_data):
INDEX_REG_SLOT = re.compile(r"\{(\d+)\}")
KEY_REG_SLOT = re.compile(r"\{(\w+)\}")


def _handle_shortcut_reg(argv: Argv, groups: tuple[str, ...], gdict: dict[str, str], wrapper: ShortcutRegWrapper):
data = []
for unit in argv.raw_data:
if not isinstance(unit, str):
data.append(unit)
continue
unit = escape(unit)
for i, c in enumerate(groups):
unit = unit.replace(f"{{{i}}}", c)
for k, v in gdict.items():
unit = unit.replace(f"{{{k}}}", v)
argv.raw_data[j] = unescape(unit)
if mat := INDEX_REG_SLOT.fullmatch(unit):
index = int(mat[1])
if index >= len(groups):
continue
slot = groups[index]
if slot is None:
continue
data.append(wrapper(index, slot) or slot)
continue
if mat := KEY_REG_SLOT.fullmatch(unit):
key = mat[1]
if key not in gdict:
continue
slot = gdict[key]
if slot is None:
continue
data.append(wrapper(key, slot) or slot)
continue
if mat := INDEX_REG_SLOT.findall(unit):
for index in map(int, mat):
if index >= len(groups):
unit = unit.replace(f"{{{index}}}", "")
continue
slot = groups[index]
if slot is None:
unit = unit.replace(f"{{{index}}}", "")
continue
unit = unit.replace(f"{{{index}}}", str(wrapper(index, slot) or slot))
if mat := KEY_REG_SLOT.findall(unit):
for key in mat:
if key not in gdict:
unit = unit.replace(f"{{{key}}}", "")
continue
slot = gdict[key]
if slot is None:
unit = unit.replace(f"{{{key}}}", "")
continue
unit = unit.replace(f"{{{key}}}", str(wrapper(key, slot) or slot))
if unit:
data.append(unescape(unit))
return data


def _prompt_unit(analyser: Analyser, argv: Argv, trig: Arg):
Expand Down
76 changes: 73 additions & 3 deletions src/arclet/alconna/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from dataclasses import dataclass, field
from functools import partial
from pathlib import Path
from typing import Any, Callable, Generic, Sequence, TypeVar, overload
from typing import Any, Callable, Generic, Literal, Sequence, TypeVar, cast, overload
from typing_extensions import Self

from tarina import init_spec, lang
Expand All @@ -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, TPrefixes
from .typing import TDC, CommandMeta, DataCollection, ShortcutRegWrapper, TPrefixes

T_Duplication = TypeVar("T_Duplication", bound=Duplication)
T = TypeVar("T")
Expand Down Expand Up @@ -205,13 +205,79 @@ def get_shortcuts(self) -> list[str]:
"""返回该命令注册的快捷命令"""
return command_manager.list_shortcut(self)

def shortcut(self, key: str, args: ShortcutArgs | None = None, delete: bool = False):
@overload
def shortcut(self, key: str, args: ShortcutArgs | None = None) -> str:
"""操作快捷命令
Args:
key (str): 快捷命令名
args (ShortcutArgs[TDC]): 快捷命令参数, 不传入时则尝试使用最近一次使用的命令
Returns:
str: 操作结果
Raises:
ValueError: 快捷命令操作失败时抛出
"""
...

@overload
def shortcut(
self,
key: str,
*,
command: TDC | None = None,
arguments: list[Any] | None = None,
fuzzy: bool = True,
prefix: bool = False,
wrapper: ShortcutRegWrapper | None = None,
) -> str:
"""操作快捷命令
Args:
key (str): 快捷命令名
command (TDC): 快捷命令指向的命令
arguments (list[Any] | None, optional): 快捷命令参数, 默认为 `None`
fuzzy (bool, optional): 是否允许命令后随参数, 默认为 `True`
prefix (bool, optional): 是否调用时保留指令前缀, 默认为 `False`
wrapper (ShortcutRegWrapper, optional): 快捷指令的正则匹配结果的额外处理函数, 默认为 `None`
Returns:
str: 操作结果
Raises:
ValueError: 快捷命令操作失败时抛出
"""
...

@overload
def shortcut(self, key: str, *, delete: Literal[True]) -> str:
"""操作快捷命令
Args:
key (str): 快捷命令名
delete (bool): 是否删除快捷命令
Returns:
str: 操作结果
Raises:
ValueError: 快捷命令操作失败时抛出
"""
...

def shortcut(self, key: str, args: ShortcutArgs | None = None, delete: bool = False, **kwargs):
"""操作快捷命令
Args:
key (str): 快捷命令名
args (ShortcutArgs[TDC] | None, optional): 快捷命令参数, 不传入时则尝试使用最近一次使用的命令
delete (bool, optional): 是否删除快捷命令, 默认为 `False`
command (TDC, optional): 快捷命令指向的命令
arguments (list[Any] | None, optional): 快捷命令参数, 默认为 `None`
fuzzy (bool, optional): 是否允许命令后随参数, 默认为 `True`
prefix (bool, optional): 是否调用时保留指令前缀, 默认为 `False`
wrapper (ShortcutRegWrapper, optional): 快捷指令的正则匹配结果的额外处理函数, 默认为 `None`
Returns:
str: 操作结果
Expand All @@ -223,6 +289,10 @@ def shortcut(self, key: str, args: ShortcutArgs | None = None, delete: bool = Fa
if delete:
command_manager.delete_shortcut(self, key)
return lang.require("shortcut", "delete_success").format(shortcut=key, target=self.path)
if kwargs and not args:
kwargs["args"] = kwargs.pop("arguments", None)
kwargs = {k: v for k, v in kwargs.items() if v is not None}
args = cast(ShortcutArgs, kwargs)
if args is not None:
return command_manager.add_shortcut(self, key, args)
elif cmd := command_manager.recent_message:
Expand Down
36 changes: 12 additions & 24 deletions src/arclet/alconna/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import weakref
from copy import copy
from datetime import datetime
from typing import TYPE_CHECKING, Any, Match, TypedDict, Union, overload
from typing_extensions import NotRequired
from typing import TYPE_CHECKING, Any, Match, Union, cast, overload
from weakref import WeakKeyDictionary, WeakValueDictionary

from tarina import LRU, lang
Expand All @@ -18,26 +17,13 @@
from .arparma import Arparma
from .config import Namespace, config
from .exceptions import ExceedMaxCount
from .typing import TDC, CommandMeta, DataCollection
from .typing import TDC, CommandMeta, DataCollection, ShortcutArgs, InnerShortcutArgs

if TYPE_CHECKING:
from ._internal._analyser import Analyser
from .core import Alconna


class ShortcutArgs(TypedDict):
"""快捷指令参数"""

command: NotRequired[DataCollection[Any]]
"""快捷指令的命令"""
args: NotRequired[list[Any]]
"""快捷指令的附带参数"""
fuzzy: NotRequired[bool]
"""是否允许命令后随参数"""
prefix: NotRequired[bool]
"""是否调用时保留指令前缀"""


class CommandManager:
"""
`Alconna` 命令管理器
Expand All @@ -54,7 +40,7 @@ class CommandManager:
__argv: WeakKeyDictionary[Alconna, Argv]
__abandons: list[Alconna]
__record: LRU[int, Arparma]
__shortcuts: dict[str, Union[Arparma, ShortcutArgs]]
__shortcuts: dict[str, Union[Arparma, InnerShortcutArgs]]

def __init__(self):
self.cache_path = f"{__file__.replace('manager.py', '')}manager_cache.db"
Expand Down Expand Up @@ -205,21 +191,23 @@ def add_shortcut(self, target: Alconna, key: str, source: Arparma | ShortcutArgs
if isinstance(source, dict):
source.setdefault("fuzzy", True)
source.setdefault("prefix", False)
source.setdefault("wrapper", lambda slot, content: content)
source.setdefault("args", [])
if source.get("prefix") and target.prefixes:
out = []
for prefix in target.prefixes:
if not isinstance(prefix, str):
continue
_src = source.copy()
_src["command"] = argv.converter(prefix + source.get("command", str(target.command)))
_src["command"] = argv.converter(prefix + source.get("command", str(target.command))) # type: ignore
prefix = re.escape(prefix)
self.__shortcuts[f"{namespace}.{name}::{prefix}{key}"] = _src
self.__shortcuts[f"{namespace}.{name}::{prefix}{key}"] = cast(InnerShortcutArgs, _src)
out.append(
lang.require("shortcut", "add_success").format(shortcut=f"{prefix}{key}", target=target.path)
)
return "\n".join(out)
source["command"] = argv.converter(source.get("command", target.command or target.name))
self.__shortcuts[f"{namespace}.{name}::{key}"] = source
self.__shortcuts[f"{namespace}.{name}::{key}"] = cast(InnerShortcutArgs, source)
return lang.require("shortcut", "add_success").format(shortcut=key, target=target.path)
elif source.matched:
self.__shortcuts[f"{namespace}.{name}::{key}"] = source
Expand All @@ -243,17 +231,17 @@ def list_shortcut(self, target: Alconna) -> list[str]:
continue
short = self.__shortcuts[i]
if isinstance(short, dict):
result.append(i.split("::")[1] + (" ..." if short.get("fuzzy") else ""))
result.append(i.split("::")[1] + (" ..." if short["fuzzy"] else ""))
else:
result.append(i.split("::")[1])
return result

@overload
def find_shortcut(self, target: Alconna[TDC]) -> list[Union[Arparma[TDC], ShortcutArgs]]:
def find_shortcut(self, target: Alconna[TDC]) -> list[Union[Arparma[TDC], InnerShortcutArgs]]:
...

@overload
def find_shortcut(self, target: Alconna[TDC], query: str) -> tuple[Arparma[TDC] | ShortcutArgs, Match[str] | None]:
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):
Expand All @@ -264,7 +252,7 @@ def find_shortcut(self, target: Alconna[TDC], query: str | None = None):
query (str, optional): 快捷命令的名称. Defaults to None.
Returns:
list[Union[Arparma, ShortcutArgs]] | tuple[Union[Arparma, ShortcutArgs], Match[str]]: \
list[Union[Arparma, InnerShortcutArgs]] | tuple[Union[Arparma, InnerShortcutArgs], Match[str]]: \
快捷命令的参数, 若没有 `query` 则返回目标命令的所有快捷命令, 否则返回匹配的快捷命令
"""
namespace, name = self._command_part(target.path)
Expand Down
Loading

0 comments on commit 8b58b9b

Please sign in to comment.