Skip to content

Commit

Permalink
🧑‍💻 version 1.8.4
Browse files Browse the repository at this point in the history
command_manager.update
  • Loading branch information
RF-Tar-Railt committed Mar 6, 2024
1 parent b7fff91 commit ad81ec5
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 96 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# 更新日志

## Alconna 1.8.4

### 新增

- `command_manager.update` 上下文方法,用于修改 Alconna 对象后更新与其绑定的其他组件

```python
from arclet.alconna import Args, Alconna, Option, command_manager

alc = Alconna("test")

with command_manager.update(alc):
alc.prefixes = ["!"]
alc.add(Option("foo", Args["bar", int]))
```


## Alconna 1.8.3

### 修复
Expand Down
21 changes: 14 additions & 7 deletions devtool.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@
from arclet.alconna.args import Args
from arclet.alconna.argv import Argv
from arclet.alconna.base import Option, Subcommand
from arclet.alconna.config import config
from arclet.alconna.typing import DataCollection
from arclet.alconna.config import Namespace
from arclet.alconna.typing import DataCollection, CommandMeta


class AnalyseError(Exception):
"""分析时发生错误"""


dev_space = Namespace("devtool", enable_message_cache=False)


class _DummyAnalyser(Analyser):
filter_out = []

class _DummyALC:
options = []
meta = namedtuple("Meta", ["keep_crlf", "fuzzy_match", "raise_exception"])(False, False, True)
namespace_config = config.default_namespace
namespace_config = dev_space

def __new__(cls, *args, **kwargs):
cls.command = cls._DummyALC() # type: ignore
Expand All @@ -42,7 +45,8 @@ def analyse_args(
context_style: Literal["bracket", "parentheses"] | None = None,
**kwargs
):
argv = Argv(config.default_namespace, message_cache=False, context_style=context_style, filter_crlf=True)
meta = CommandMeta(keep_crlf=False, fuzzy_match=False, raise_exception=raise_exception, context_style=context_style)
argv = Argv(meta, dev_space)
try:
argv.enter(kwargs)
argv.build(["test"] + command)
Expand All @@ -64,7 +68,8 @@ def analyse_header(
context_style: Literal["bracket", "parentheses"] | None = None,
**kwargs
):
argv = Argv(config.default_namespace, message_cache=False, filter_crlf=True, context_style=context_style, separators=(sep,))
meta = CommandMeta(keep_crlf=False, fuzzy_match=False, raise_exception=raise_exception, context_style=context_style)
argv = Argv(meta, dev_space, separators=(sep,))
command_header = Header.generate(command_name, headers, compact=compact)
try:
argv.enter(kwargs)
Expand All @@ -83,7 +88,8 @@ def analyse_option(
context_style: Literal["bracket", "parentheses"] | None = None,
**kwargs
):
argv = Argv(config.default_namespace, message_cache=False, filter_crlf=True, context_style=context_style)
meta = CommandMeta(keep_crlf=False, fuzzy_match=False, raise_exception=raise_exception, context_style=context_style)
argv = Argv(meta, dev_space)
_analyser = _DummyAnalyser.__new__(_DummyAnalyser)
_analyser.reset()
_analyser.command.separators = (" ",)
Expand All @@ -109,7 +115,8 @@ def analyse_subcommand(
context_style: Literal["bracket", "parentheses"] | None = None,
**kwargs
):
argv = Argv(config.default_namespace, message_cache=False, filter_crlf=True, context_style=context_style)
meta = CommandMeta(keep_crlf=False, fuzzy_match=False, raise_exception=raise_exception, context_style=context_style)
argv = Argv(meta, dev_space)
_analyser = _DummyAnalyser.__new__(_DummyAnalyser)
_analyser.reset()
_analyser.command.separators = (" ",)
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.8.3"
__version__ = "1.8.4"

# backward compatibility
AnyOne = ANY
15 changes: 10 additions & 5 deletions src/arclet/alconna/_internal/_analyser.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ def _clr(self):

def __post_init__(self):
self.reset()
self.__calc_args__()

def __calc_args__(self):
self.self_args = self.command.args
if self.command.nargs > 0 and self.command.nargs > self.self_args.optional_count:
self.need_main_args = True # 如果need_marg那么match的元素里一定得有main_argument
Expand Down Expand Up @@ -258,12 +261,14 @@ def __init__(self, alconna: Alconna[TDC], compiler: TCompile | None = None):
compiler (TCompile | None, optional): 编译器方法
"""
super().__init__(alconna)
self.fuzzy_match = alconna.meta.fuzzy_match
self._compiler = compiler or default_compiler
self.used_tokens = set()
self.command_header = Header.generate(alconna.command, alconna.prefixes, alconna.meta.compact)
self.extra_allow = not alconna.meta.strict or not alconna.namespace_config.strict
compiler = compiler or default_compiler
compiler(self, command_manager.resolve(self.command).param_ids)

def compile(self, param_ids: set[str]):
self.extra_allow = not self.command.meta.strict or not self.command.namespace_config.strict
self.command_header = Header.generate(self.command.command, self.command.prefixes, self.command.meta.compact)
self._compiler(self, param_ids)
return self

def _clr(self):
self.used_tokens.clear()
Expand Down
64 changes: 40 additions & 24 deletions src/arclet/alconna/_internal/_argv.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from dataclasses import dataclass, field, fields
from dataclasses import dataclass, field, fields, InitVar
from typing import Any, Callable, ClassVar, Generic, Iterable, Literal
from typing_extensions import Self

Expand All @@ -11,38 +11,42 @@
from ..config import Namespace, config
from ..constraint import ARGV_OVERRIDES
from ..exceptions import NullMessage
from ..typing import TDC
from ..typing import TDC, CommandMeta


@dataclass(repr=True)
class Argv(Generic[TDC]):
"""命令行参数"""

meta: InitVar[CommandMeta]
namespace: Namespace = field(default=config.default_namespace)
fuzzy_match: bool = field(default=False)
"""当前命令是否模糊匹配"""
fuzzy_threshold: float = field(default=0.6)
"""模糊匹配阈值"""
preprocessors: dict[type, Callable[..., Any]] = field(default_factory=dict)
"""命令元素的预处理器"""
to_text: Callable[[Any], str | None] = field(default=lambda x: x if isinstance(x, str) else None)
"""将命令元素转换为文本, 或者返回None以跳过该元素"""
"""命名空间"""
separators: tuple[str, ...] = field(default=(" ",))
"""命令分隔符"""
context_style: Literal["bracket", "parentheses"] | None = field(default=None)
"命令上下文插值的风格,None 为关闭,bracket 为 {...},parentheses 为 $(...)"

preprocessors: dict[type, Callable[..., Any]] = field(default_factory=dict)
"""命令元素的预处理器"""
filter_out: list[type] = field(default_factory=list)
"""需要过滤掉的命令元素"""
checker: Callable[[Any], bool] | None = field(default=None)
"""检查传入命令"""
param_ids: set[str] = field(default_factory=set)
"""节点名集合"""

fuzzy_match: bool = field(init=False)
"""当前命令是否模糊匹配"""
fuzzy_threshold: float = field(init=False)
"""模糊匹配阈值"""
to_text: Callable[[Any], str | None] = field(default=lambda x: x if isinstance(x, str) else None)
"""将命令元素转换为文本, 或者返回None以跳过该元素"""
converter: Callable[[str | list], TDC] = field(default=lambda x: x)
"""将字符串或列表转为目标命令类型"""
filter_crlf: bool = field(default=True)
filter_crlf: bool = field(init=False)
"""是否过滤掉换行符"""
message_cache: bool = field(default=True)
message_cache: bool = field(init=False)
"""是否缓存消息"""
param_ids: set[str] = field(default_factory=set)
"""节点名集合"""
context_style: Literal["bracket", "parentheses"] | None = field(init=False)
"命令上下文插值的风格,None 为关闭,bracket 为 {...},parentheses 为 $(...)"

current_node: Arg | Subcommand | Option | None = field(init=False)
"""当前节点"""
Expand All @@ -59,26 +63,38 @@ class Argv(Generic[TDC]):
origin: TDC = field(init=False)
"""原始命令"""
context: dict[str, Any] = field(init=False, default_factory=dict)
special: dict[str, str] = field(init=False, default_factory=dict)
completion_names: set[str] = field(init=False, default_factory=set)
_sep: tuple[str, ...] | None = field(init=False)

_cache: ClassVar[dict[type, dict[str, Any]]] = {}

def __post_init__(self):
def __post_init__(self, meta: CommandMeta):
self.reset()
self.special: dict[str, str] = {}
self.special.update(
[(i, "help") for i in self.namespace.builtin_option_name["help"]]
+ [(i, "completion") for i in self.namespace.builtin_option_name["completion"]]
+ [(i, "shortcut") for i in self.namespace.builtin_option_name["shortcut"]]
)
self.completion_names = self.namespace.builtin_option_name["completion"]
self.compile(meta)
if __cache := self.__class__._cache.get(self.__class__, {}):
self.preprocessors.update(__cache.get("preprocessors") or {})
self.filter_out.extend(__cache.get("filter_out") or [])
self.to_text = __cache.get("to_text") or self.to_text
self.checker = __cache.get("checker") or self.checker
self.converter = __cache.get("converter") or self.converter

def compile(self, meta: CommandMeta):
self.fuzzy_match = meta.fuzzy_match
self.fuzzy_threshold = meta.fuzzy_threshold
self.to_text = self.namespace.to_text
self.converter = self.namespace.converter
self.message_cache = self.namespace.enable_message_cache
self.filter_crlf = not meta.keep_crlf
self.context_style = meta.context_style
self.special = {}
self.special.update(
[(i, "help") for i in self.namespace.builtin_option_name["help"]]
+ [(i, "completion") for i in self.namespace.builtin_option_name["completion"]]
+ [(i, "shortcut") for i in self.namespace.builtin_option_name["shortcut"]]
)
self.completion_names = self.namespace.builtin_option_name["completion"]

def reset(self):
"""重置命令行参数"""
self.current_index = 0
Expand Down
4 changes: 1 addition & 3 deletions src/arclet/alconna/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,6 @@ class Option(CommandNode):
"""命令选项默认值"""
aliases: frozenset[str]
"""命令选项别名"""
priority: int
"""命令选项优先级"""
compact: bool
"是否允许名称与后随参数之间无分隔符"

Expand All @@ -179,7 +177,7 @@ def __init__(
help_text: str | None = None,
requires: str | list[str] | tuple[str, ...] | set[str] | None = None,
compact: bool = False,
priority: int = 0,
priority: int = 0,
):
"""初始化命令选项
Expand Down
61 changes: 27 additions & 34 deletions src/arclet/alconna/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,9 @@ class Alconna(Subcommand, Generic[TDC]):
behaviors: list[ArparmaBehavior]
"""命令行为器"""

@property
def compile(self) -> Callable[[TCompile | None], Analyser[TDC]]:
def compile(self, compiler: TCompile | None = None, param_ids: set[str] | None = None) -> Analyser[TDC]:
"""编译 `Alconna` 为对应的解析器"""
return partial(Analyser, self)
return Analyser(self, compiler).compile(param_ids)

def __init__(
self,
Expand Down Expand Up @@ -183,19 +182,17 @@ def reset_namespace(self, namespace: Namespace | str, header: bool = True) -> Se
namespace (Namespace | str): 命名空间
header (bool, optional): 是否保留命令头, 默认为 `True`
"""
command_manager.delete(self)
if isinstance(namespace, str):
namespace = config.namespaces.setdefault(namespace, Namespace(namespace))
self.namespace = namespace.name
self.path = f"{self.namespace}::{self.name}"
if header:
self.prefixes = namespace.prefixes.copy()
self.options = self.options[:-3]
add_builtin_options(self.options, namespace)
self.meta.fuzzy_match = namespace.fuzzy_match or self.meta.fuzzy_match
self.meta.raise_exception = namespace.raise_exception or self.meta.raise_exception
self._hash = self._calc_hash()
command_manager.register(self)
with command_manager.update(self):
if isinstance(namespace, str):
namespace = config.namespaces.setdefault(namespace, Namespace(namespace))
self.namespace = namespace.name
self.path = f"{self.namespace}::{self.name}"
if header:
self.prefixes = namespace.prefixes.copy()
self.options = self.options[:-3]
add_builtin_options(self.options, namespace)
self.meta.fuzzy_match = namespace.fuzzy_match or self.meta.fuzzy_match
self.meta.raise_exception = namespace.raise_exception or self.meta.raise_exception
return self

def get_help(self) -> str:
Expand Down Expand Up @@ -312,10 +309,8 @@ def add(self, opt: Option | Subcommand) -> Self:
Returns:
Self: 命令本身
"""
command_manager.delete(self)
self.options.insert(-3, opt)
self._hash = self._calc_hash()
command_manager.register(self)
with command_manager.update(self):
self.options.insert(-3, opt)
return self

@init_spec(Option, is_method=True)
Expand Down Expand Up @@ -383,20 +378,18 @@ def __truediv__(self, other) -> Self:
__rtruediv__ = __truediv__

def __add__(self, other) -> Self:
command_manager.delete(self)
if isinstance(other, Alconna):
self.options.extend(other.options)
elif isinstance(other, CommandMeta):
self.meta = other
elif isinstance(other, Option):
self.options.append(other)
elif isinstance(other, Args):
self.args += other
self.nargs = len(self.args)
elif isinstance(other, str):
self.options.append(Option(other))
self._hash = self._calc_hash()
command_manager.register(self)
with command_manager.update(self):
if isinstance(other, Alconna):
self.options.extend(other.options)
elif isinstance(other, CommandMeta):
self.meta = other
elif isinstance(other, Option):
self.options.append(other)
elif isinstance(other, Args):
self.args += other
self.nargs = len(self.args)
elif isinstance(other, str):
self.options.append(Option(other))
return self

def __or__(self, other: Alconna) -> Self:
Expand Down
Loading

0 comments on commit ad81ec5

Please sign in to comment.