Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

Latest commit

 

History

History
659 lines (470 loc) · 23.3 KB

docs.md

File metadata and controls

659 lines (470 loc) · 23.3 KB

Nonebot Plugin Alconna 文档

本文分为三部分:

Plugin

安装

pip install nonebot-plugin-alconna

nb plugin install nonebot-plugin-alconna

展示

from nonebot.adapters.onebot.v12 import Message, MessageSegment as Ob12MS
from nonebot_plugin_alconna import on_alconna, AlconnaMatches
from nonebot_plugin_alconna.adapters import At
from nonebot_plugin_alconna.adapters.onebot12 import Image
from arclet.alconna import Alconna, Args, Option, Arparma

alc = Alconna("Hello!", Option("--spec", Args["target", At]))
hello = on_alconna(alc, auto_send_output=True)

@hello.handle()
async def _(result: Arparma = AlconnaMatches()):
    if result.find("spec"):
        target: Ob12MS = result.query("spec.target")
        seed = target.data['user_id']
        await hello.finish(Message(Image(await gen_image(seed))))
    else:
        await hello.finish("Hello!")

基础使用

本插件提供了一类新的事件响应器辅助函数 on_alconna, 其使用 Alconna 作为命令解析器。

def on_alconna(
    command: Alconna | str,
    skip_for_unmatch: bool = True,
    auto_send_output: bool = False,
    output_converter: Callable[[OutputType, str], Message | Awaitable[Message]] | None = None,
    aliases: set[str | tuple[str, ...]] | None = None,
    comp_config: CompConfig | None = None,
    ...,
):
  • command: Alconna 命令或字符串,字符串将通过 AlconnaFormat 转换为 Alconna 命令
  • skip_for_unmatch: 是否在命令不匹配时跳过该响应
  • auto_send_output: 是否自动发送输出信息并跳过响应
  • output_converter: 输出信息字符串转换为 Message 方法
  • aliases: 命令别名, 作用类似于 on_command 中的 aliases
  • comp_config: 补全会话配置, 不传入则不启用补全会话

依赖注入

Alconna 的解析结果会放入 Arparma 类中,或用户指定的 Duplication 类。

本插件提供了一系列依赖注入函数,便于在响应函数中获取解析结果:

  • AlconnaResult: CommandResult 类型的依赖注入函数
  • AlconnaMatches: Arparma 类型的依赖注入函数
  • AlconnaDuplication: Duplication 类型的依赖注入函数
  • AlconnaMatch: Match 类型的依赖注入函数
  • AlconnaQuery: Query 类型的依赖注入函数

可以看到,本插件提供了几类额外的模型:

  • CommandResult: 解析结果,包括了源命令 command: Alconna ,解析结果 result: Arparma,以及可能的输出信息 output: str | None 字段
  • Match: 匹配项,表示参数是否存在于 all_matched_args 内,可用 Match.available 判断是否匹配,Match.result 获取匹配的值
  • Query: 查询项,表示参数是否可由 Arparma.query 查询并获得结果,可用 Query.available 判断是否查询成功,Query.result 获取查询结果

同时,基于 Annotated 支持, 添加了两类注解:

  • AlcMatches:同 AlconnaMatches
  • AlcResult:同 AlconnaResult

实例:

...
from nonebot import require
require("nonebot_plugin_alconna")
...

from nonebot_plugin_alconna import (
    on_alconna, 
    Match,
    Query,
    AlconnaMatch, 
    AlconnaQuery,
    AlconnaMatches,
    AlcResult
)
from arclet.alconna import Alconna, Args, Option, Arparma

test = on_alconna(
    Alconna(
        "test",
        Option("foo", Args["bar", int]),
        Option("baz", Args["qux", bool, False])
    ),
    auto_send_output=True
)


@test.handle()
async def handle_test1(result: AlcResult):
    await test.send(f"matched: {result.matched}")
    await test.send(f"maybe output: {result.output}")

@test.handle()
async def handle_test2(result: Arparma = AlconnaMatches()):
    await test.send(f"head result: {result.header_result}")
    await test.send(f"args: {result.all_matched_args}")

@test.handle()
async def handle_test3(bar: Match[int] = AlconnaMatch("bar")):
    if bar.available:    
        await test.send(f"foo={bar.result}")

@test.handle()
async def handle_test4(qux: Query[bool] = AlconnaQuery("baz.qux", False)):
    if qux.available:
        await test.send(f"baz.qux={qux.result}")

条件控制

本插件可以通过 handle(parameterless) 来控制一个具体的响应函数是否在不满足条件时跳过响应。

...
from nonebot import require
require("nonebot_plugin_alconna")
...

from arclet.alconna import Alconna, Subcommand, Option, Args
from nonebot_plugin_alconna import assign, on_alconna, AlconnaResult, CommandResult, Check

pip = Alconna(
    "pip",
    Subcommand("install", Args["pak", str], Option("--upgrade"), Option("--force-reinstall")),
    Subcommand("list", Option("--out-dated"))
)

pip_cmd = on_alconna(pip)

# 仅在命令为 `pip install` 并且 pak 为 `pip` 时响应
@pip_cmd.handle([Check(assign("install.pak", "pip"))])
async def update(arp: CommandResult = AlconnaResult()):
    ...

# 仅在命令为 `pip list` 时响应
@pip_cmd.handle([Check(assign("list"))])
async def list_(arp: CommandResult = AlconnaResult()):
    ...

# 仅在命令为 `pip install` 时响应
@pip_cmd.handle([Check(assign("install"))])
async def install(arp: CommandResult = AlconnaResult()):
    ...

便捷装饰器

本插件提供了一个 funcommand 装饰器, 其用于将一个接受任意参数, 返回 strMessageMessageSegment 的函数转换为命令响应器。

from nonebot_plugin_alconna import funcommand

@funcommand()
async def echo(msg: str):
    return msg

MessageSegment 标注

本插件提供了一系列便捷的 MessageSegment 标注,可用于匹配消息中除 text 外的其他 MessageSegment,也可用于快速创建 MessageSegment

所有标注位于 nonebot_plugin_alconna.adapters 中。

通用标注

通用标注会将符合条件的 MessageSegment 转为插件提供的内部类型

class Segment:
    origin: MessageSegment

class At(Segment):
    target: str

class Emoji(Segment):
    id: str
    name: Optional[str]
    
class Media(Segment):  # Image, Audio, Voice, Video
    url: Optional[str]
    id: Optional[str]

class File(Segment):
    id: str
    name: Optional[str]
  • Text: str 的别名
  • At: 匹配 At/Mention 类型的 MessageSegment,例如 Onebot 11 中的 AtOnebot 12 中的 Mention
  • Image: 匹配 Image 类型的 MessageSegment
  • Audio: 匹配 Audio 类型的 MessageSegment
  • Voice: 匹配 Voice 类型的 MessageSegment
  • File: 匹配 File 类型的 MessageSegment
  • Video: 匹配 Video 类型的 MessageSegment

此类标注无法用于创建 MessageSegment

适配器标注

本插件为以下设配器提供了seg标注,可用于匹配各适配器的 MessageSegment,也可用于创建 MessageSegment

协议名称 路径
OneBot 协议 adapters.onebot11, adapters.onebot12
Telegram adapters.telegram
飞书 adapters.feishu
GitHub adapters.github
QQ 频道 adapters.qqguild
钉钉 adapters.ding
Console adapters.console
开黑啦 adapters.kook
Mirai adapters.mirai
Ntchat adapters.ntchat
MineCraft adapters.minecraft
BiliBili Live adapters.bilibili
Walle-Q adapters.onebot12

示例

特定适配器:

from nonebot_plugin_alconna.adapters.onebot12 import Mention
from nonebot.adapters.onebot.v12 import Message
from arclet.alconna import Alconna, Args

msg = Message(["Hello!", Mention("123")])
print(msg)  # Hello![mention:user_id=123]

alc = Alconna("Hello!", Args["target", Mention])
assert alc.parse(msg).query("target").data['user_id'] == '123'

通用标注:

from nonebot.adapters.onebot.v12 import Message as Ob12M, MessageSegment as Ob12MS
from nonebot.adapters.onebot.v11 import Message as Ob11M, MessageSegment as Ob11MS
from nonebot_plugin_alconna.adapters import At
from arclet.alconna import Alconna, Args

msg1 = Ob12M(["Hello!", Ob12MS.mention("123")])
print(msg1)  # Hello![mention:user_id=123]
msg2 = Ob11M(["Hello!", Ob11MS.at(123)])
print(msg2)  # Hello![CQ:at,qq=123]

alc = Alconna("Hello!", Args["target", At])
assert alc.parse(msg1).query("target").data['user_id'] == '123'
assert alc.parse(msg2).query("target").data['qq'] == 123

Alconna

Alconna 隶属于 ArcletProject,是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。

示例的 Alconna 使用:

from arclet.alconna import Alconna, Args, Option, Subcommand, count

alc = Alconna(
    "pip",
    Subcommand(
        "install",
        Args["package", str],
        Option("-r|--requirement", Args["file", str]),
        Option("-i|--index-url", Args["url", str]),
    ),
    Option("-v|--version", action=count),
)

print(alc.parse("pip install nonebot2 -i https://mirrors.aliyun.com/pypi/simple/").all_matched_args)
# {'package': 'nonebot2', 'url': 'https://mirrors.aliyun.com/pypi/simple/'}

其特点有:

  • 高效
  • 直观的命令组件创建方式,例如选项别名,默认值,解析操作等
  • 强大的类型解析与类型转换功能,具体在 Args 的使用上
  • 自定义的帮助信息格式,帮助信息由内置选项 help 触发,用户可以选择自定义的 TextFormatter 获得不一样的输出格式
  • 多语言支持,目前支持中文与英文
  • 易用的快捷命令创建与使用,可由内置选项 shortcut 触发,或使用 Alconna.shortcut() 方法
  • 可创建命令补全会话, 以实现多轮连续的补全提示
  • 可通过 NamespaceCommandMeta 配置命令的行为与属性,例如通过 Namespace 自定义内置选项的触发字段,或通过 CommandMeta 启用模糊匹配
  • 可嵌套的多级子命令
  • Duplication 能像 argparse.Namespace 一样获取指定的解析结果并获得类型支持
  • 正则匹配支持
  • ...

组件

Alconna 拥有两大组件:OptionSubcommand

Option 可以传入一组 alias,如 Option("--foo|-F|--FOO|-f")Option("--foo", alias=["-F"]

Subcommand 则可以传入自己的 OptionSubcommand

他们拥有如下共同参数:

  • help_text: 传入该组件的帮助信息

  • dest: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)

  • requires: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换

    对于命令 test foo bar baz qux <a:int> 来讲,因为foo bar baz 仅需要判断是否相等, 所以可以这么编写:

    Alconna("test", Option("qux", Args.a[int], requires=["foo", "bar", "baz"]))
  • default: 默认值,在该组件未被解析时使用使用该值替换。

    特别的,使用 OptionResultSubcomanndResult 可以设置包括参数字典在内的默认值:

    from arclet.alconna import Option, OptionResult
    
    opt1 = Option("--foo", default=False)
    opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))

选项操作

Option 可以特别设置传入一类 Action,作为解析操作

Action 分为三类:

  • store: 无 Args 时, 仅存储一个值, 默认为 Ellipsis; 有 Args 时, 后续的解析结果会覆盖之前的值

  • append: 无 Args 时, 将多个值存为列表, 默认为 Ellipsis; 有 Args 时, 每个解析结果会追加到列表中

    当存在默认值并且不为列表时, 会自动将默认值变成列表, 以保证追加的正确性

  • count: 无 Args 时, 计数器加一; 有 Args 时, 表现与 STORE 相同

    当存在默认值并且不为数字时, 会自动将默认值变成 1, 以保证计数器的正确性。

Alconna 提供了预制的几类 action

  • storestore_valuestore_truestore_false
  • appendappend_value
  • count

紧凑命令

Alconna, OptionSubcommand 可以设置 compact=True 使得解析命令时允许名称与后随参数之间没有分隔:

from arclet.alconna import Alconna, Option, CommandMeta, Args

alc = Alconna("test", Args["foo", int], Option("BAR", Args["baz", str], compact=True), meta=CommandMeta(compact=True))

assert alc.parse("test123 BARabc").matched

这使得我们可以实现如下命令:

>>> from arclet.alconna import Alconna, Option, Args, append
>>> alc = Alconna("gcc", Option("--flag|-F", Args["content", str], action=append))
>>> alc.parse("gcc -Fabc -Fdef -Fxyz").query("flag.content")
['abc', 'def', 'xyz']

Optionactioncount 时,其自动支持 compact 特性:

>>> from arclet.alconna import Alconna, Option, Args, count
>>> alc = Alconna("pp", Option("--verbose|-v", action=count, default=0))
>>> alc.parse("pp -vvv").query("verbose.value")
3

配置

arclet.alconna.Namespace 表示某一命名空间下的默认配置:

from arclet.alconna import config, namespace, Namespace
from arclet.alconna.tools import ShellTextFormatter


np = Namespace("foo", prefixes=["/"])  # 创建 Namespace 对象,并进行初始配置

with namespace("bar") as np1:
    np1.prefixes = ["!"]    # 以上下文管理器方式配置命名空间,此时配置会自动注入上下文内创建的命令
    np1.formatter_type = ShellTextFormatter  # 设置此命名空间下的命令的 formatter 默认为 ShellTextFormatter
    np1.builtin_option_name["help"] = {"帮助", "-h"}  # 设置此命名空间下的命令的帮助选项名称

config.namespaces["foo"] = np  # 将命名空间挂载到 config 上

同时也提供了默认命名空间配置与修改方法:

from arclet.alconna import config, namespace, Namespace


config.default_namespace.prefixes = [...]  # 直接修改默认配置

np = Namespace("xxx", prefixes=[...])
config.default_namespace = np  # 更换默认的命名空间

with namespace(config.default_namespace.name) as np:
    np.prefixes = [...]

半自动补全

半自动补全为用户提供了推荐后续输入的功能。

补全默认通过 --comp-cp 触发:(命名空间配置可修改名称)

from arclet.alconna import Alconna, Args, Option

alc = Alconna("test", Args["abc", int]) + Option("foo") + Option("bar")
alc.parse("test --comp")

'''
output

以下是建议的输入:
* <abc: int>
* --help
* -h
* -sct
* --shortcut
* foo
* bar
'''

快捷指令

快捷指令顾名思义,可以为基础指令创建便捷的触发方式

一般情况下你可以通过 Alconna.shortcut 进行快捷指令操作 (创建,删除);

>>> from arclet.alconna import Alconna, Args
>>> alc = Alconna("setu", Args["count", int])
>>> alc.shortcut("涩图(\d+)张", {"args": ["{0}"]})
'Alconna::setu 的快截指令: "涩图(\\d+)张" 添加成功'
>>> alc.parse("涩图3张").query("count")
3

shortcut 的第一个参数为快捷指令名称,第二个参数为 ShortcutArgs,作为快捷指令的配置

class ShortcutArgs(TypedDict, Generic[TDC]):
    """快捷指令参数"""

    command: NotRequired[TDC]
    """快捷指令的命令"""
    args: NotRequired[list[Any]]
    """快捷指令的附带参数"""
    fuzzy: NotRequired[bool]
    """是否允许命令后随参数"""

fuzzy 为 False 时,传入 "涩图1张 abc" 之类的快捷指令将视为解析失败

快捷指令允许三类特殊的 placeholder:

  • {%X}: 只用于 command, 如 setu {%0},表示此处填入快截指令后随的第 X 个参数。

    例如,若快捷指令为 涩图, 配置为 {"command": "setu {%0}"}, 则指令 涩图 1 相当于 setu 1

  • {*}: 只用于 command, 表示此处填入所有后随参数,并且可以通过 {*X} 的方式指定组合参数之间的分隔符。

  • {X}: 用于 commandargs, 表示此处填入可能的正则匹配的组:

    • command 中存在匹配组 (xxx),则 {X} 表示第 X 个匹配组的内容
    • command 中存储匹配组 (?P<xxx>...), 则 {X} 表示名字为 X 的匹配结果

除此之外, 通过内置选项 --shortcut 可以动态操作快捷指令。

使用模糊匹配

模糊匹配通过在 Alconna 中设置其 CommandMeta 开启。

模糊匹配会应用在任意需要进行名称判断的地方,如命令名称选项名称参数名称(如指定需要传入参数名称)。

from arclet.alconna import Alconna, CommandMeta

alc = Alconna("test_fuzzy", meta=CommandMeta(fuzzy_match=True))
alc.parse("test_fuzy")
# output: test_fuzy is not matched. Do you mean "test_fuzzy"?

Args

Args 在 Alconna 中有非常重要的地位,甚至称得上是核心,比 Alconna 重要十倍甚至九倍。

其通常以 Args[key1, var1, default1][key2, var2][Arg(key3, var3), Arg(key4, var4, default4)][...] 的方式构造一个 Args。

其中,key 一定是字符串,而 var 一般为参数的类型,default 为具体的值或者 arclet.alconna.args.Field

key

key 的作用是用以标记解析出来的参数并存放于 Arparma 中,以方便用户调用。

其有三种为 Args 注解的标识符,为 ?/!。标识符与 key 之间建议以 ; 分隔:

  • ! 标识符表示该处传入的参数应不是规定的类型,或不在指定的值中。
  • ? 标识符表示该参数为可选参数,会在无参数匹配时跳过。
  • / 标识符表示该参数的类型注解需要隐藏。

另外,对于参数的注释也可以标记在 key 中,其与 key 或者标识符 以 # 分割:

foo#这是注释;?foo?#这是注释

var

var 负责命令参数的类型检查与类型转化

var 可以是以下几类:

  • 存在于 nepattern.pattern_map 中的类型/字符串,用以替换为预制好的 BasePattern
  • 字符串
    • 若字符串以 "re:" 打头,表示将其转为正则解析表达式,并且返回类型为匹配字符串
    • 若字符串以 "rep:" 打头,表示将其转为特殊的 RegexPattern,并且返回类型为 re.Match
    • 其他字符串将作为直接的比较对象
  • 列表,其中可存放 BasePattern、类型或者任意参数值,如字符串或者数字
  • UnionOptionalLiteral 等会尝试转换为 List[Type]
  • Dict[type1,type2]List[type]Set[type]
  • 一般的类型,其会尝试比较传入参数的类型是否与其相关
  • AnyOneAllParam,作为泛匹配的标识符
  • AnyString, 会将传入的任意参数转为字符串
  • 预制好的字典, 表示传入值依据该字典的键决定匹配结果
  • Annotated[type, Callable[..., bool], ...],表示为某一类型添加校验器
  • Callable[[P], T],表示会将传入的参数 P 经过该调用对象并将返回值 T 作为匹配结果
  • ...

内置的类型检查包括 intstrfloatbool'url''ip''email'listdicttuplesetAnybyteshexdatetime 等。

Arg 只传入了 key,则 var 自动选择 key 的值作为比较对象

另外,Alconna 提供了两类特殊的类型用以实现限制功能:

  • MultiVar:将该参数标记为需要获取可变数量或指定数量的数据,通过填入 flag: int | Literal['+', '*'] 实现
  • KeyWordVar:将该参数标记为需要同时写入参数名才认定为合法参数,默认形式为 key=arg,可指定分隔符

MultiVarKeyWordVar 一起使用时, 该参数表示为需要接收多个 key=arg 形式的数据, 类似 **kwargs

Arparma

Alconna.parse 会返回由 Arparma 承载的解析结果。

Arpamar 会有如下参数:

  • 调试类

    • matched: 是否匹配成功
    • error_data: 解析失败时剩余的数据
    • error_info: 解析失败时的异常内容
    • origin: 原始命令,可以类型标注
  • 分析类

    • header_match: 命令头部的解析结果,包括原始头部、解析后头部、解析结果与可能的正则匹配组
    • main_args: 命令的主参数的解析结果
    • options: 命令所有选项的解析结果
    • subcommands: 命令所有子命令的解析结果
    • other_args: 除主参数外的其他解析结果
    • all_matched_args: 所有 Args 的解析结果

Arparma 同时提供了便捷的查询方法 query(),会根据传入的 path 查找参数并返回

path 支持如下:

  • main_args, options, ...: 返回对应的属性
  • args: 返回 all_matched_args
  • main_args.xxx, options.xxx, ...: 返回字典中 xxx键对应的值
  • args.xxx: 返回 all_matched_args 中 xxx键对应的值
  • options.foo, foo: 返回选项 foo 的解析结果 (OptionResult)
  • options.foo.value, foo.value: 返回选项 foo 的解析值
  • options.foo.args, foo.args: 返回选项 foo 的解析参数字典
  • options.foo.args.bar, foo.bar: 返回选项 foo 的参数字典中 bar 键对应的值 ...

同样, Arparma["foo.bar"] 的表现与 query() 一致

Duplication

Duplication 用来提供更好的自动补全,类似于 ArgParseNamespace,经测试表现良好(好耶)。

普通情况下使用,需要利用到 ArgsStubOptionStubSubcommandStub 三个部分,

以pip为例,其对应的 Duplication 应如下构造:

from arclet.alconna import OptionResult, Duplication, SubcommandStub

class MyDup(Duplication):
    verbose: OptionResult
    install: SubcommandStub  # 选项与子命令对应的stub的变量名必须与其名字相同

并在解析时传入 Duplication:

result = alc.parse("pip -v install ...", duplication=MyDup)
>>> type(result)
<class MyDup>

Duplication 也可以如 Namespace 一样直接标明参数名称和类型:

from typing import Optional
from arclet.alconna import Duplication


class MyDup(Duplication):
    package: str
    file: Optional[str] = None
    url: Optional[str] = None

References

插件仓库: 📦这里

官方文档: 👉指路

QQ 交流群: 🔗链接

友链: 📚文档