Skip to content

Commit

Permalink
✨ builtin behavior conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
RF-Tar-Railt committed Aug 31, 2024
1 parent f324d71 commit ff86de0
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 25 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ ParamsUnmatched: 参数 3 不正确
# fuzzy.py
from arclet.alconna import Alconna, CommandMeta, Arg

alc = Alconna('!test_fuzzy', Arg("foo", str), meta=CommandMeta(fuzzy_match=True))
alc = Alconna('!test_fuzzy', Arg("foo", str), CommandMeta(fuzzy_match=True))

if __name__ == '__main__':
alc()
Expand Down
1 change: 1 addition & 0 deletions src/arclet/alconna/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .base import Option as Option
from .base import Subcommand as Subcommand
from .builtin import set_default as set_default
from .builtin import conflict as conflict
from .completion import CompSession as CompSession
from .config import Namespace as Namespace
from .config import config as config
Expand Down
8 changes: 4 additions & 4 deletions src/arclet/alconna/arparma.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,14 +225,14 @@ def unpack(self) -> None:
self._unpack_subs(self.subcommands)

@staticmethod
def behave_cancel():
def behave_cancel(*msg: str):
"""取消行为器的后续操作"""
raise BehaveCancelled
raise BehaveCancelled(*msg)

@staticmethod
def behave_fail():
def behave_fail(*msg: str):
"""取消行为器的后续操作并抛出 `OutBoundsBehave`"""
raise OutBoundsBehave
raise OutBoundsBehave(*msg)

def execute(self, behaviors: list[ArparmaBehavior] | None = None) -> Self:
"""执行行为器
Expand Down
64 changes: 59 additions & 5 deletions src/arclet/alconna/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
from dataclasses import dataclass, field
from typing import Any, Callable, cast, overload

from tarina import Empty

from .config import lang
from .arparma import Arparma, ArparmaBehavior
from .core import Alconna
from .duplication import Duplication
from .exceptions import BehaveCancelled
from .model import OptionResult, SubcommandResult
from .stub import ArgsStub, OptionStub, SubcommandStub

__all__ = ["set_default", "generate_duplication"]
__all__ = ["set_default", "generate_duplication", "conflict"]


def generate_duplication(alc: Alconna) -> type[Duplication]:
Expand All @@ -34,6 +38,55 @@ def generate_duplication(alc: Alconna) -> type[Duplication]:
)


@dataclass
class ConflictWith(ArparmaBehavior):
source: str
target: str
source_limiter: Callable[..., bool] | None = None
target_limiter: Callable[..., bool] | None = None

def get_type(self, res):
if isinstance(res, OptionResult):
return lang.require("builtin", "conflict.option")
if isinstance(res, SubcommandResult):
return lang.require("builtin", "conflict.subcommand")
return lang.require("builtin", "conflict.arg")

def operate(self, interface: Arparma):
if (s_r := interface.query(self.source, Empty)) is not Empty and (t_r := interface.query(self.target, Empty)) is not Empty:
source_type = self.get_type(s_r)
target_type = self.get_type(t_r)
if self.source_limiter and not self.source_limiter(s_r):
return
if self.target_limiter and not self.target_limiter(t_r):
return
interface.behave_fail(lang.require("builtin", "conflict.msg").format(
source_type=source_type,
target_type=target_type,
source=self.source,
target=self.target
))


def conflict(
source: str,
target: str,
source_limiter: Callable[..., bool] | None = None,
target_limiter: Callable[..., bool] | None = None,
):
"""
当 `source` 与 `target` 同时存在时设置解析结果为失败
Args:
source (str): 参数路径1
target (str): 参数路径2
source_limiter (Callable[..., bool]): 假设 source 存在时限定特定结果以继续的函数
target_limiter (Callable[..., bool]): 假设 target 存在时限定特定结果以继续的函数
"""

return ConflictWith(source, target, source_limiter, target_limiter)


class _MISSING_TYPE:
pass

Expand All @@ -57,10 +110,11 @@ def default(self):

def operate(self, interface: Arparma):
if not self.path:
raise BehaveCancelled
def_val = self.default
if not interface.query(self.path):
self.update(interface, self.path, def_val)
interface.behave_cancel()
else:
def_val = self.default
if not interface.query(self.path):
self.update(interface, self.path, def_val)


@overload
Expand Down
10 changes: 6 additions & 4 deletions src/arclet/alconna/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def compile(self, compiler: TCompile | None = None, param_ids: set[str] | None =

def __init__(
self,
*args: Option | Subcommand | str | TPrefixes | Any | Args | Arg,
*args: Option | Subcommand | str | TPrefixes | Args | Arg | CommandMeta | ArparmaBehavior | Any,
meta: CommandMeta | None = None,
namespace: str | Namespace | None = None,
separators: str | set[str] | Sequence[str] | None = None,
Expand All @@ -146,12 +146,12 @@ def __init__(
ns_config = namespace
self.prefixes = next((i for i in args if isinstance(i, list)), ns_config.prefixes.copy()) # type: ignore
try:
self.command = next(i for i in args if not isinstance(i, (list, Option, Subcommand, Args, Arg)))
self.command = next(i for i in args if not isinstance(i, (list, Option, Subcommand, Args, Arg, CommandMeta, ArparmaBehavior)))
except StopIteration:
self.command = "" if self.prefixes else handle_argv()
self.namespace = ns_config.name
self.formatter = (formatter_type or ns_config.formatter_type or TextFormatter)()
self.meta = meta or CommandMeta()
self.meta = meta or next((i for i in args if isinstance(i, CommandMeta)), CommandMeta())
if self.meta.example:
self.meta.example = self.meta.example.replace("$", str(self.prefixes[0]) if self.prefixes else "")
self.meta.fuzzy_match = self.meta.fuzzy_match or ns_config.fuzzy_match
Expand All @@ -167,7 +167,9 @@ def __init__(
self.name = name
self.aliases = frozenset((name,))
self.behaviors = []
for behavior in behaviors or []:
_behaviors = [i for i in args if isinstance(i, ArparmaBehavior)]
_behaviors.extend(behaviors or [])
for behavior in _behaviors:
self.behaviors.extend(requirement_handler(behavior))
command_manager.register(self)
self._executors: dict[ArparmaExecutor, Any] = {}
Expand Down
30 changes: 28 additions & 2 deletions src/arclet/alconna/i18n/.lang.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
"title": "Lang Schema",
"description": "Schema for lang file",
"type": "object",
"minProperties": 19,
"maxProperties": 19,
"properties": {
"common": {
"title": "Common",
Expand Down Expand Up @@ -388,6 +386,34 @@
"title": "option_completion",
"description": "value of lang item type 'option_completion'",
"type": "string"
},
"conflict": {
"title": "Conflict",
"description": "Scope 'conflict' of lang item",
"type": "object",
"additionalProperties": false,
"properties": {
"option": {
"title": "option",
"description": "value of lang item type 'option'",
"type": "string"
},
"subcommand": {
"title": "subcommand",
"description": "value of lang item type 'subcommand'",
"type": "string"
},
"arg": {
"title": "arg",
"description": "value of lang item type 'arg'",
"type": "string"
},
"msg": {
"title": "msg",
"description": "value of lang item type 'msg'",
"type": "string"
}
}
}
}
},
Expand Down
11 changes: 10 additions & 1 deletion src/arclet/alconna/i18n/.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,16 @@
"types": [
"option_help",
"option_shortcut",
"option_completion"
"option_completion",
{
"subtype": "conflict",
"types": [
"option",
"subcommand",
"arg",
"msg"
]
}
]
},
{
Expand Down
30 changes: 25 additions & 5 deletions src/arclet/alconna/i18n/.template.schema.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

{
"title": "Template",
"description": "Template for lang items to generate schema for lang files",
Expand All @@ -17,16 +16,37 @@
"scope": {
"type": "string",
"description": "Scope name"
},
},
"types": {
"type": "array",
"description": "All types of lang items",
"uniqueItems": true,
"items": {
"type": "string",
"description": "Value of lang item"
"oneOf": [
{
"type": "string",
"description": "Value of lang item"
},
{
"type": "object",
"properties": {
"subtype": {
"type": "string",
"description": "Subtype name of lang item"
},
"types": {
"type": "array",
"description": "All subtypes of lang items",
"uniqueItems": true,
"items": {
"$ref": "#/properties/scopes/items/properties/types/items"
}
}
}
}
]
}
}
}
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/arclet/alconna/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,13 @@
"builtin": {
"option_help": "show the help information",
"option_shortcut": "set a shortcut command",
"option_completion": "give a next input suggestion of current command"
"option_completion": "give a next input suggestion of current command",
"conflict": {
"option": "Option",
"subcommand": "Subcommand",
"arg": "Arg",
"msg": "{source_type} {source} conflict with {target_type} {target}"
}
},
"format": {
"notice": "Notice",
Expand Down
8 changes: 7 additions & 1 deletion src/arclet/alconna/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,13 @@
"builtin": {
"option_help": "显示帮助信息",
"option_shortcut": "设置快捷命令",
"option_completion": "补全当前命令"
"option_completion": "补全当前命令",
"conflict": {
"option": "选项",
"subcommand": "子命令",
"arg": "参数",
"msg": "{source_type} {source} 与 {target_type} {target} 冲突"
}
},
"format": {
"notice": "注释",
Expand Down
30 changes: 29 additions & 1 deletion tests/components_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from arclet.alconna import Alconna, Args, Arparma, ArparmaBehavior, Option, Subcommand
from arclet.alconna.builtin import generate_duplication, set_default
from arclet.alconna.builtin import generate_duplication, set_default, conflict
from arclet.alconna.duplication import Duplication
from arclet.alconna.model import OptionResult
from arclet.alconna.output import output_manager
Expand All @@ -22,6 +22,34 @@ def operate(cls, interface: "Arparma"):
com.behaviors.append(Test())
assert com.parse("comp 123").matched is False

com1 = Alconna("comp1", Option("--foo", Args["bar", int]), Option("--baz", Args["qux", int]))
com1.behaviors.append(conflict("foo", "baz"))

assert com1.parse("comp1 --foo 1").matched
assert com1.parse("comp1 --baz 2").matched
assert com1.parse("comp1 --foo 1 --baz 2").matched is False

com1.behaviors.clear()
com1.behaviors.append(conflict("foo.bar", "baz.qux", source_limiter=lambda x: x == 2, target_limiter=lambda x: x == 1))

assert com1.parse("comp1 --foo 1").matched
assert com1.parse("comp1 --baz 2").matched
assert com1.parse("comp1 --foo 1 --baz 2").matched
assert com1.parse("comp1 --foo 2 --baz 1").matched is False

com1_1 = Alconna(
"comp1_1",
Option("-1", dest="one"),
Option("-2", dest="two"),
Option("-3", dest="three")
)
com1_1.behaviors.append(conflict("one", "two"))
com1_1.behaviors.append(conflict("two", "three"))

assert com1_1.parse("comp1_1 -1 -2").matched is False
assert com1_1.parse("comp1_1 -2 -3").matched is False
assert com1_1.parse("comp1_1 -1 -3").matched


def test_duplication():
class Demo(Duplication):
Expand Down

0 comments on commit ff86de0

Please sign in to comment.