Skip to content

Commit

Permalink
✨ separate shortcut_wrap, make it independent
Browse files Browse the repository at this point in the history
  • Loading branch information
RF-Tar-Railt committed Oct 21, 2024
1 parent 5471d44 commit 3ff04a8
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 120 deletions.
3 changes: 0 additions & 3 deletions src/arclet/alconna/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
from .args import ArgFlag as ArgFlag
from .args import Args as Args
from .args import Field as Field
from .argv import Argv as Argv
from .argv import argv_config as argv_config
from .argv import set_default_argv_type as set_default_argv_type
from .arparma import Arparma as Arparma
from .arparma import ArparmaBehavior as ArparmaBehavior
from .base import Option as Option
Expand Down
37 changes: 0 additions & 37 deletions src/arclet/alconna/argv.py

This file was deleted.

9 changes: 5 additions & 4 deletions src/arclet/alconna/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@

from .ingedia._analyser import Analyser, TCompile
from .ingedia._handlers import handle_head_fuzzy, analyse_header
from .ingedia._shortcut import shortcut as _shortcut
from .ingedia._argv import Argv, __argv_type__
from .args import Arg, Args
from .argv import Argv, __argv_type__
from .arparma import Arparma, ArparmaBehavior, requirement_handler
from .base import Completion, Help, Option, OptionResult, Shortcut, Subcommand, Header, SPECIAL_OPTIONS, Config, Metadata
from .config import Namespace, global_config
Expand All @@ -29,6 +28,7 @@
InvalidHeader,
PauseTriggered,
)
from .shortcut import wrap_shortcut
from .completion import prompt, comp_ctx
from .formatter import TextFormatter
from .manager import ShortcutArgs, command_manager
Expand Down Expand Up @@ -442,11 +442,12 @@ def _parse(self, message: TDC, ctx: dict[str, Any] | None = None) -> Arparma[TDC
if trigger.__class__ is str and trigger:
argv.context[SHORTCUT_TRIGGER] = trigger
try:
rest, short, mat = command_manager.find_shortcut(self, [trigger] + argv.release())
rest, short, mat = command_manager.find_shortcut(self, [trigger] + argv.release(no_split=True))
argv.context[SHORTCUT_ARGS] = short
argv.context[SHORTCUT_REST] = rest
argv.context[SHORTCUT_REGEX_MATCH] = mat
_shortcut(argv, rest, short, mat)
argv.reset()
argv.addon(wrap_shortcut(rest, short, mat, argv.context), merge_str=False)
analyser.header_result = analyse_header(self._header, argv)
analyser.header_result.origin = trigger
if not (exc := analyser.process(argv)):
Expand Down
35 changes: 33 additions & 2 deletions src/arclet/alconna/ingedia/_argv.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from dataclasses import InitVar, dataclass, field, fields
from typing import Any, Callable, ClassVar, Generic, Iterable, Literal, TYPE_CHECKING
from typing_extensions import Self

from contextvars import ContextVar
from tarina import lang, split, split_once

from ..base import Option, Config
Expand Down Expand Up @@ -262,7 +262,7 @@ def release(self, separate: str | None = None, recover: bool = False, no_split:
if _data.__class__ is str and not _data:
continue
if _data.__class__ is str and not no_split:
_result.extend(split(_data, separate or " ", self.filter_crlf))
_result.extend(split(_data, separate or self.separators, self.filter_crlf))
else:
_result.append(_data)
return _result
Expand All @@ -289,3 +289,34 @@ def exit(self) -> dict[str, Any]:
_ = self.context
self.context = {}
return _


__argv_type__: ContextVar[type[Argv]] = ContextVar("argv_type", default=Argv)


def set_default_argv_type(argv_type: type[Argv]):
"""设置默认的命令行参数类型"""
__argv_type__.set(argv_type)


def argv_config(
target: type[Argv] | None = None,
preprocessors: dict[type, Callable[..., Any]] | None = None,
to_text: Callable[[Any], str | None] | None = None,
filter_out: list[type] | None = None,
checker: Callable[[Any], bool] | None = None,
converter: Callable[[str | list], TDC] | None = None,
):
"""配置命令行参数
Args:
target (type[Argv] | None, optional): 目标命令类型.
preprocessors (dict[type, Callable[..., Any]] | None, optional): 命令元素的预处理器.
to_text (Callable[[Any], str | None] | None, optional): 将命令元素转换为文本, 或者返回None以跳过该元素.
filter_out (list[type] | None, optional): 需要过滤掉的命令元素.
checker (Callable[[Any], bool] | None, optional): 检查传入命令.
converter (Callable[[str | list], TDC] | None, optional): 将字符串或列表转为目标命令类型.
"""
Argv._cache.setdefault(target or __argv_type__.get(), {}).update(
{k: v for k, v in locals().items() if v is not None}
)
18 changes: 0 additions & 18 deletions src/arclet/alconna/ingedia/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,3 @@ def levenshtein(source: str, target: str) -> float:
matrix[i][j] = min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, sub_distance)

return 1 - float(matrix[l_s][l_t]) / max(l_s, l_t)


ESCAPE = {"\\": "\x00", "[": "\x01", "]": "\x02", "{": "\x03", "}": "\x04", "|": "\x05"}
R_ESCAPE = {v: k for k, v in ESCAPE.items()}


def escape(string: str) -> str:
"""转义字符串"""
for k, v in ESCAPE.items():
string = string.replace("\\" + k, v)
return string


def unescape(string: str) -> str:
"""逆转义字符串, 自动去除空白符"""
for k, v in R_ESCAPE.items():
string = string.replace(k, v)
return string.strip()
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,27 @@

from tarina import lang

from ..exceptions import ArgumentMissing, ParamsUnmatched
from ..typing import InnerShortcutArgs, _ShortcutRegWrapper
from ._argv import Argv
from ._util import escape, unescape
from .exceptions import ArgumentMissing, ParamsUnmatched
from .typing import InnerShortcutArgs, _ShortcutRegWrapper


ESCAPE = {"\\": "\x01", "[": "\x01", "]": "\x02", "{": "\x03", "}": "\x04", "|": "\x05"}
R_ESCAPE = {v: k for k, v in ESCAPE.items()}


def escape(string: str) -> str:
"""转义字符串"""
for k, v in ESCAPE.items():
string = string.replace("\\" + k, v)
return string


def unescape(string: str) -> str:
"""逆转义字符串, 自动去除空白符"""
for k, v in R_ESCAPE.items():
string = string.replace(k, v)
return string.strip()


INDEX_SLOT = re.compile(r"\{%(\d+)\}")
WILDCARD_SLOT = re.compile(r"\{\*(.*)\}", re.DOTALL)
Expand All @@ -24,65 +41,65 @@ def _gen_extend(data: list, sep: str):
return extend


def _handle_multi_slot(argv: Argv, unit: str, data: list, index: int, current: int, offset: int):
def _handle_multi_slot(result: list, unit: str, data: list, index: int, current: int, offset: int):
slot = data[index]
if not isinstance(slot, str):
left, right = unit.split(f"{{%{index}}}", 1)
if left.strip():
argv.raw_data[current] = left.strip()
argv.raw_data.insert(current + 1, slot)
result[current] = left.strip()
result.insert(current + 1, slot)
if right.strip():
argv.raw_data[current + 2] = right.strip()
result[current + 2] = right.strip()
offset += 1
else:
argv.raw_data[current + offset] = unescape(unit.replace(f"{{%{index}}}", slot))
result[current + offset] = unescape(unit.replace(f"{{%{index}}}", slot))
return offset


def _handle_shortcut_data(argv: Argv, data: list):
def _handle_shortcut_data(result: list, data: list):
data_len = len(data)
record = set()
offset = 0
for i, unit in enumerate(argv.raw_data.copy()):
for i, unit in enumerate(result.copy()):
if not isinstance(unit, str):
continue
unit = escape(unit)
if mat := INDEX_SLOT.fullmatch(unit):
index = int(mat[1])
if index >= data_len:
continue
argv.raw_data[i + offset] = data[index]
result[i + offset] = data[index]
record.add(index)
elif res := INDEX_SLOT.findall(unit):
for index in map(int, res):
if index >= data_len:
continue
offset = _handle_multi_slot(argv, unit, data, index, i, offset)
offset = _handle_multi_slot(result, unit, data, index, i, offset)
record.add(index)
elif mat := WILDCARD_SLOT.search(unit):
extend = _gen_extend(data, mat[1] or " ")
if unit == f"{{*{mat[1]}}}":
argv.raw_data.extend(extend)
result.extend(extend)
else:
argv.raw_data[i + offset] = unescape(unit.replace(f"{{*{mat[1]}}}", "".join(map(str, extend))))
result[i + offset] = unescape(unit.replace(f"{{*{mat[1]}}}", "".join(map(str, extend))))
data.clear()
break

def recover_quote(_unit):
if isinstance(_unit, str) and any(_unit.count(sep) for sep in argv.separators) and not (_unit[0] in ('"', "'") and _unit[0] == _unit[-1]):
return f'"{_unit}"'
return _unit
# def recover_quote(_unit):
# if isinstance(_unit, str) and any(_unit.count(sep) for sep in argv.separators) and not (_unit[0] in ('"', "'") and _unit[0] == _unit[-1]):
# return f'"{_unit}"'
# return _unit

return [recover_quote(unit) for i, unit in enumerate(data) if i not in record]
return [unit for i, unit in enumerate(data) if i not in record]


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):
def _handle_shortcut_reg(result: list, groups: tuple[str, ...], gdict: dict[str, str], wrapper: _ShortcutRegWrapper, ctx: dict[str, Any]):
data = []
for unit in argv.raw_data:
for unit in result:
if not isinstance(unit, str):
data.append(unit)
continue
Expand All @@ -92,66 +109,62 @@ def _handle_shortcut_reg(argv: Argv, groups: tuple[str, ...], gdict: dict[str, s
if index >= len(groups):
continue
slot = groups[index]
data.append(wrapper(index, slot, argv.context))
data.append(wrapper(index, slot, ctx))
continue
if mat := KEY_REG_SLOT.fullmatch(unit):
key = mat[1]
if key not in gdict:
continue
slot = gdict[key]
data.append(wrapper(key, slot, argv.context))
data.append(wrapper(key, slot, ctx))
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]
unit = unit.replace(f"{{{index}}}", str(wrapper(index, slot, argv.context) or ""))
unit = unit.replace(f"{{{index}}}", str(wrapper(index, slot, ctx) or ""))
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]
unit = unit.replace(f"{{{key}}}", str(wrapper(key, slot, argv.context) or ""))
unit = unit.replace(f"{{{key}}}", str(wrapper(key, slot, ctx) or ""))
if unit:
data.append(unescape(unit))
return data


def shortcut(
argv: Argv, data: list[Any], short: InnerShortcutArgs, reg: re.Match | None = None
) -> None:
def wrap_shortcut(
data: list[Any], short: InnerShortcutArgs, reg: re.Match | None = None, ctx: dict[str, Any] | None = None
) -> list[Any]:
"""处理被触发的快捷命令
Args:
argv (Argv): 命令行参数
data (list[Any]): 剩余参数
short (InnerShortcutArgs): 快捷命令
reg (Match | None): 可能的正则匹配结果
ctx (dict[str, Any] | None): 上下文
Returns:
None
list[Any]: 处理后的参数
Raises:
ParamsUnmatched: 若不允许快捷命令后随其他参数,则抛出此异常
"""

argv.build(short.command) # type: ignore
result = [short.command]
if not short.fuzzy and data:
raise ParamsUnmatched(lang.require("analyser", "param_unmatched").format(target=data[0]))
argv.addon(short.args, merge_str=False)
data = _handle_shortcut_data(argv, data)
if not data and argv.raw_data and any(
isinstance(i, str) and bool(re.search(r"\{%(\d+)|\*(.*?)\}", i)) for i in argv.raw_data
result.extend(short.args)
data = _handle_shortcut_data(result, data)
if not data and result and any(
isinstance(i, str) and bool(re.search(r"\{%(\d+)|\*(.*?)\}", i)) for i in result
):
raise ArgumentMissing(lang.require("analyser", "param_missing"))
argv.addon(data, merge_str=False)
result.extend(data)
if reg:
data = _handle_shortcut_reg(argv, reg.groups(), reg.groupdict(), short.wrapper)
argv.raw_data.clear()
argv.ndata = 0
argv.current_index = 0
argv.addon(data)
return
data = _handle_shortcut_reg(result, reg.groups(), reg.groupdict(), short.wrapper, ctx or {})
result.clear()
result.extend(data)
return result
4 changes: 2 additions & 2 deletions src/arclet/alconna/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class ShortcutArgs(TypedDict):


class InnerShortcutArgs:
command: DataCollection[Any]
command: str
args: list[Any]
fuzzy: bool
prefix: bool
Expand All @@ -103,7 +103,7 @@ class InnerShortcutArgs:

def __init__(
self,
command: DataCollection[Any],
command: str,
args: list[Any] | None = None,
fuzzy: bool = True,
prefix: bool = False,
Expand Down
6 changes: 3 additions & 3 deletions src/arclet/alconna/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
from arclet.alconna.args import ArgFlag as ArgFlag # noqa: F401
from arclet.alconna.args import Args as Args # noqa: F401
from arclet.alconna.args import Field as Field # noqa: F401
from arclet.alconna.argv import Argv as Argv # noqa: F401
from arclet.alconna.argv import argv_config as argv_config # noqa: F401
from arclet.alconna.argv import set_default_argv_type as set_default_argv_type # noqa: F401
from arclet.alconna.ingedia._argv import Argv as Argv # noqa: F401
from arclet.alconna.ingedia._argv import argv_config as argv_config # noqa: F401
from arclet.alconna.ingedia._argv import set_default_argv_type as set_default_argv_type # noqa: F401
from arclet.alconna.arparma import Arparma as Arparma # noqa: F401
from arclet.alconna.arparma import ArparmaBehavior as ArparmaBehavior # noqa: F401
from arclet.alconna.base import Option as Option # noqa: F401
Expand Down
2 changes: 1 addition & 1 deletion tests/analyser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from nepattern import BasePattern, MatchMode

from arclet.alconna import Alconna, Args, Option
from arclet.alconna.argv import argv_config
from arclet.alconna.ingedia._argv import argv_config


@dataclass
Expand Down
Loading

0 comments on commit 3ff04a8

Please sign in to comment.