Alconna
WARNING
该教程是对整个 Alconna 特性的大致归纳,以帮助读者熟悉 Alconna 的使用与开发。
有关更多详细内容,请参阅 文档(/guide/alconna)
DANGER
该部分内容对应的 v2 版本的 Alconna 仍然处于开发中,可能会有较大变动。
Alconna
是 Arclet Project
下负责命令参数解析的模块,是 Arclet
的核心模块之一。
我们通过一个例子来讲解 Alconna 的核心 —— Args
, Subcommand
, Option
:
from arclet.alconna import Alconna, Args, Subcommand, Option
alc = Alconna(
"pip",
Subcommand(
"install",
Args.package(str),
Option("-r|--requirement", Args.file(str)),
Option("-i|--index-url", Args.url(str)),
)
)
res = alc.parse("pip install arclet-entari -i URL")
print(res)
# matched=True, header_match=(origin='pip' result='pip' matched=True), value_result={('install',): Ellipsis, ('install', 'index-url'): Ellipsis}, other_args={'package': 'arclet-entari', 'url': 'URL'}
print(res.all_matched_args)
# {'package': 'arclet-entari', 'url': 'URL'}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
解释
这段代码通过 Alconna
创建了一个接受主命令名为 pip
, 子命令为 install
且子命令接受一个 Args 参数package和二个 Option 参数requirement
和index-url
的命令参数解析器, 通过parse方法返回解析结果 Arparma 的实例。
其特点有:
- 高效. 在 i5-10210U 处理器上, 性能大约为
+71000~289000 msg/s
- 直观的命令组件创建方式
- 强大的类型解析与类型转换功能
- 自定义的帮助信息格式与命令解析控制
- i18n
- 命令输入缓存, 以保证重复命令的快速响应
- 易用的快捷命令创建与使用
- 可以绑定回调函数, 以便于在命令解析完成后执行
- 可创建命令补全会话, 以实现多轮连续的补全提示
- 模糊匹配、输出捕获等一众特性
安装
git clone https://github.com/ArcletProject/Alconna@dev
参数声明(Args)
Args 是用于声明命令参数的组件, 在 Alconna 中有非常重要的地位,甚至称得上是核心,比 Alconna 重要十倍甚至九倍。
Args 可以通过以下三种方式构造:
from arclet.alconna import Alconna, Args
alc = Alconna("test", Args.foo(str).bar(int))
# Args.key(var).key1(var1, default=default1, ...)
2
3
4
from arclet.alconna import Alconna, ArgsBase, arg_field
class TestArgs(ArgsBase):
foo: str
bar: int
# key: var
# key1: var1 = default1
# key2: var2 = arg_field(default=default2)
alc = Alconna("test", TestArgs)
2
3
4
5
6
7
8
9
10
from arclet.alconna import Alconna, Arg
alc = Alconna("test", Arg("foo", str), Arg("bar", int))
# Arg(key, var), Arg(key, var, default=default, ...)
2
3
4
其中,key 一定是字符串,而 var 一般为参数的类型,default 为可能的默认值。
其与函数签名类似,但是动态构建中允许含有默认值的参数在前。
参数名(key)
key
的作用是用以标记解析出来的参数并存放于 Arparma 中,以方便用户调用。
INFO
Args
中的 key
在实际命令中并不需要传入(keyword 参数除外):
from arclet.alconna import Alconna, Args
alc = Alconna("test", Args.foo(str))
alc.parse("test --foo abc") # 错误
alc.parse("test abc") # 正确
2
3
4
5
若需要 test --foo abc
,你应该使用 Option
:
from arclet.alconna import Alconna, Args, Option
alc = Alconna("test", Option("--foo", Args.foo(str)))
2
3
参数值(var)
var 负责命令参数的类型检查与类型转化
Args
的var
表面上看需要传入一个 type
,但实际上它需要的是一个 nepattern.Pattern
的实例:
from arclet.alconna import Args
from nepattern import Pattern
# 表示 foo 参数需要匹配一个 @number 样式的字符串
args = Args.foo(Pattern.regex_match("@\d+"))
2
3
4
5
pip
示例中可以传入 str
是因为 str
已经注册在了 nepattern.global_patterns
中,因此会替换为 nepattern.global_patterns[str]
nepattern.global_patterns
默认支持的类型有:
str
: 匹配任意字符串int
: 匹配整数float
: 匹配浮点数bool
: 匹配True
与False
以及他们小写形式hex
: 匹配0x
开头的十六进制字符串url
: 匹配网址email
: 匹配xxxx@xxx
的字符串ipv4
: 匹配xxx.xxx.xxx.xxx
的字符串list
: 匹配类似["foo","bar","baz"]
的字符串dict
: 匹配类似{"foo":"bar","baz":"qux"}
的字符串datetime
: 传入一个datetime
支持的格式字符串,或时间戳Any
: 匹配任意类型AnyString
: 匹配任意类型,转为str
Number
: 匹配int
与float
,转为int
同时可以使用 typing 中的类型:
Literal[X]
: 匹配其中的任意一个值Union[X, Y]
: 匹配其中的任意一个类型Optional[xxx]
: 会自动将默认值设为None
,并在解析失败时使用默认值list[X]
: 匹配一个列表,其中的元素为X
类型dict[X, Y]
: 匹配一个字典,其中的 key 为X
类型,value 为Y
类型- ...
TIP
几类特殊的传入标记:
"foo"
: 匹配字符串 "foo" (若没有某个BasePattern
与之关联)RawStr("foo")
: 匹配字符串 "foo" (即使有BasePattern
与之关联也不会被替换)"foo|bar|baz"
: 匹配 "foo" 或 "bar" 或 "baz"[foo, bar, Baz, ...]
: 匹配其中的任意一个值或类型Callable[[X], Y]
: 匹配一个参数为X
类型的值,并返回通过该函数调用得到的Y
类型的值"re:xxx"
: 匹配一个正则表达式xxx
,会返回 Match[0]"rep:xxx"
: 匹配一个正则表达式xxx
,会返回re.Match
对象{foo: bar, baz: qux}
: 匹配字典中的任意一个键, 并返回对应的值 (特殊的键 ... 会匹配任意的值)- ...
特别的,你可以不传入 var
,此时会使用 key
作为 var
, 匹配 key
字符串。
参数域(Field)
除了 key
与 var
外,剩余的参数都会被视为 Field 对象的属性。其用于携带对于该参数的更多信息。
Field 具有如下属性:
- default:
T
默认值 - default_factory:
() -> T
默认值工厂 - alias:
str | None
给默认值的别名 - completion:
(() -> str | list[str]) | None
补全提示 - unmatch_tips:
(Any) -> str | None
传入参数未匹配时的提示信息 - missing_tips:
() -> str | None
缺失传入参数时的提示信息 - notice:
str
注释/帮助信息 - seps:
str
参数单元使用的分隔符 - multiple:
bool | int | "+" | "*" | "str"
参数单元是否可接受多个值,类似于函数中的*args
- optional:
bool
参数单元是否可选 - hidden:
bool
参数单元是否隐藏类型(在帮助信息中只显示名称) - wildcard:
bool
参数单元是否为泛匹配 (即该参数后的所有参数都会被匹配到该参数中,并结束解析)
默认情况下 (即不声明) default
的值为特殊值 Empty
。这也意味着你可以将默认值设置为 None
表示默认值为空值。
INFO
multiple
的值可以为 bool
,int
,"+"
,"*"
,"str"
:
bool
: 表示是否接受多个值int
: 表示接受的值的最大数量"+"
: 表示接受多个值,但至少接受一个"*"
: 表示接受多个值,但可以接受零个"str"
: 表示接受多个值,但会被合并为一个字符串
选项与子命令(Option & Subcommand)
Option 与 Subcommand 是 Alconna 中的两个重要组件。
Option
和 Subcommand
可以传入一组 alias
,
如 Option("--foo|-F|--FOO|-f")
,Subcommand("foo", alias=["F"])
传入别名后,选项与子命令会选择其中长度最长的作为其名称。若传入为 "--foo|-f",则命令名称为 "--foo"
特别提醒!!!
Option 的名字或别名没有要求必须在前面写上 -
Option 与 Subcommand 的唯一区别在于 Subcommand 可以传入自己的 Option 与 Subcommand
他们拥有如下共同参数:
help_text
: 传入该组件的帮助信息dest
: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)default
: 默认值,在该组件未被解析时使用使用该值替换。特别的,使用
OptionResult
或SubcomanndResult
可以设置包括参数字典在内的默认值:pythonfrom arclet.alconna import Option, OptionResult opt1 = Option("--foo", default=False) opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
1
2
3
4
选项操作(Action)
Option
可以特别设置传入一类 Action
,作为解析操作
Action
分为三类:
store
: 无 Args 时, 仅存储一个值, 默认为 Ellipsis; 有 Args 时, 后续的解析结果会覆盖之前的值append
: 无 Args 时, 将多个值存为列表, 默认为 Ellipsis; 有 Args 时, 每个解析结果会追加到列表中当存在默认值并且不为列表时, 会自动将默认值变成列表, 以保证追加的正确性
count
: 无 Args 时, 计数器加一; 有 Args 时, 表现与 STORE 相同当存在默认值并且不为数字时, 会自动将默认值变成 1, 以保证计数器的正确性。
Alconna
提供了预制的几类 action
:
store
,store_value
,store_true
,store_false
append
,append_value
count
解析结果(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[T]()
,会根据传入的 path
查找参数并返回
path
支持如下:
options
, ...: 返回对应的属性args
: 返回 all_matched_argsoptions.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()
一致
元数据(Metadata)
Alconna
的元数据相当于拓展的帮助信息,包括:
description
: 命令的描述usage
: 命令的用法example
: 命令的使用样例author
: 命令的作者version
: 命令的版本extra
: 命令的额外信息
命令配置(Config)
命令配置相当于 v1 版本的 CommandMeta
,包括:
disable_builtin_options
: 禁用的内置选项builtin_option_name
: 内置选项的名称enable_message_cache
: 是否启用消息缓存fuzzy_match
: 命令是否开启模糊匹配fuzzy_threshold
: 模糊匹配阈值raise_exception
: 命令是否抛出异常hide
: 命令是否对 manager 隐藏hide_shortcut
: 命令的快捷指令是否在 help 信息中隐藏keep_crlf
: 命令解析时是否保留换行字符compact
: 命令是否允许第一个参数紧随头部strict
: 命令是否严格匹配,若为 False 则未知参数将作为名为 $extra 的参数context_style
: 命令上下文插值的风格,None 为关闭,bracket 为{...}
,parentheses 为$(...)
extra
: 命令的自定义额外配置
紧凑命令
Alconna
, Option
可以设置 compact=True
使得解析命令时允许名称与后随参数之间没有分隔:
from arclet.alconna import Alconna, Option, Config, Args
alc = Alconna("test", Args.foo(int), Option("BAR", Args.baz(str), compact=True), Config(compact=True))
assert alc.parse("test123 BARabc").matched
2
3
4
5
这使得我们可以实现如下命令:
>>> 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']
2
3
4
当 Option
的 action
为 count
时,其自动支持 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
2
3
4
帮助信息
每个 Alconna 命令 都可以通过 --help
或 -h
触发命令的帮助信息,并且可以通过继承 arclet.alconna.formatter.TextFormatter
来个性化信息样式,如:
from arclet.alconna import Alconna, Args, Metadata
from arclet.alconna.tools import MarkdownTextFormatter
alc = Alconna("test", Args.count(int, notice="这是一个注释"), Metadata(description="Hello World"), formatter_type=MarkdownTextFormatter)
alc.parse("test --help")
2
3
4
5
输出:
## Hello World
指令:
**test <count:int>**
### 注释:
```
count: 这是一个注释
```
2
3
4
5
6
7
8
9
除此之外,你可以通过 command_manager
获取当前程序下的所有命令:
from arclet.alconna import command_manager
...
print(command_manager.all_command_help())
2
3
输出:
# 当前可用的命令有:
- foo : Unknown
- bar : Unknown
- baz : Unknown
- qux : Unknown
# 输入'命令名 --help' 查看特定命令的语法
2
3
4
5
6
TIP
Alconna 可以设置 config.hide
参数以不被 command_manager 打印出来。
from arclet.alconna import Alconna, Config, command_manager
foo = Alconna("foo", Config(hide=True))
...
print(command_manager.all_command_help())
2
3
4
输出:
# 当前可用的命令有:
- bar : Unknown
- baz : Unknown
- qux : Unknown
# 输入'命令名 --help' 查看特定命令的语法
2
3
4
5
命名空间配置
命名空间配置 (以下简称命名空间) 相当于 Alconna
的默认配置,其优先度低于 Config
。
Alconna
默认使用 "Alconna" 命名空间。
命名空间有以下几个属性:
- name: 命名空间名称
- config: 默认配置
- prefixes: 默认前缀配置
- separators: 默认分隔符配置
- formatter_type: 默认格式化器类型
- ...
新建命名空间并替换
from arclet.alconna import Alconna, namespace, Namespace, Subcommand, Args, config
ns = Namespace("foo", prefixes=["/"]) # 创建 "foo"命名空间配置, 它要求创建的Alconna的主命令前缀必须是/
alc = Alconna("pip", Subcommand("install", Args.package(str)), namespace=ns) # 在创建Alconna时候传入命名空间以替换默认命名空间
# 可以通过with方式创建命名空间
with namespace("bar") as np1:
np1.prefixes = ["!"] # 以上下文管理器方式配置命名空间,此时配置会自动注入上下文内创建的命令
np1.formatter_type = ShellTextFormatter # 设置此命名空间下的命令的 formatter 默认为 ShellTextFormatter
np1.builtin_option_name["help"] = {"帮助", "-h"} # 设置此命名空间下的命令的帮助选项名称
# 你还可以使用config来管理所有命名空间并切换至任意命名空间
config.namespaces["foo"] = ns # 将命名空间挂载到 config 上
alc = Alconna("pip", Subcommand("install", Args.package(str)), namespace=config.namespaces["foo"]) # 也是同样可以切换到"foo"命名空间
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
修改默认的命名空间
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 = [...]
2
3
4
5
6
7
8
9
10
快捷指令
快捷指令顾名思义,可以为基础指令创建便捷的触发方式, 并且传递参数给原命令
一般情况下你可以通过 Alconna.shortcut
进行快捷指令操作 (创建,删除)。
shortcut
的第一个参数为快捷指令名称,第二个参数为 ShortcutArgs
,作为快捷指令的配置
class ShortcutArgs(TypedDict):
"""快捷指令参数"""
command: NotRequired[str]
"""快捷指令的命令"""
args: NotRequired[list[Any]]
"""快捷指令的附带参数"""
fuzzy: NotRequired[bool]
"""是否允许命令后随参数"""
prefix: NotRequired[bool]
"""是否调用时保留指令前缀"""
wrapper: NotRequired[ShortcutRegWrapper]
"""快捷指令的正则匹配结果的额外处理函数"""
humanized: NotRequired[str]
"""快捷指令的人类可读描述"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
当 fuzzy
为 False 时,传入 "涩图1张 abc"
之类的快捷指令将视为解析失败
快捷指令允许三类特殊的 placeholder:
{%X}
: 只用于command
, 如setu {%0}
,表示此处填入快截指令后随的第 X 个参数。例如,若快捷指令为
涩图
, 配置为{"command": "setu {%0}"}
, 则指令涩图 1
相当于setu 1
{*}
: 只用于command
, 表示此处填入所有后随参数,并且可以通过{*X}
的方式指定组合参数之间的分隔符。{X}
: 用于command
与args
, 表示此处填入可能的正则匹配的组:- 若
command
中存在匹配组(xxx)
,则{X}
表示第 X 个匹配组的内容 - 若
command
中存储匹配组(?P<xxx>...)
, 则{X}
表示名字为 X 的匹配结果
- 若
除此之外, 通过内置选项 --shortcut
可以动态操作快捷指令。
示例
args
的使用:
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
2
3
4
5
6
7
command
的使用:
from arclet.alconna import Alconna, Args
alc = Alconna("eval", Args.content(str))
alc.shortcut("echo", {"command": "eval print(\\'{*}\\')"})
# 'Alconna::eval 的快捷指令: "echo" 添加成功'
alc.shortcut("echo", delete=True) # 删除快捷指令
# 'Alconna::eval 的快捷指令: "echo" 删除成功'
@alc.bind() # 绑定一个命令执行器, 若匹配成功则会传入参数, 自动执行命令执行器
def cb(content: str):
eval(content, {}, {})
alc.parse('eval print(\\"hello world\\")')
# hello world
alc.parse("echo hello world!")
# hello world!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
半自动补全
半自动补全为用户提供了推荐后续输入的功能。
补全默认通过 --comp
或 -cp
或 ?
触发:(命名空间配置可修改名称)
from arclet.alconna import Alconna, Args, Option
alc = Alconna("test", Args.abc(int)) + Option("foo") + Option("bar")
alc.parse("test --comp")
2
3
4
输出:
以下是建议的输入:
* <abc: int>
* --help
* -h
* -sct
* --shortcut
* foo
* bar
2
3
4
5
6
7
8
补全会话
补全会话基于半自动补全,提供了交互操作补全的接口
补全会话通过创建 CompSession
并进入上下文触发:
from arclet.alconna import Alconna, Args, Option, CompSession
alc = Alconna("test", Args.abc(int)) + Option("foo") + Option("bar")
with CompSession(alc) as comp:
alc.parse("test")
if comp.available:
print("current completion:", comp.current())
print("next completion:", comp.tab())
with comp:
res = comp.enter(["1"])
assert res.matched
2
3
4
5
6
7
8
9
10
11
12
13
使用模糊匹配
模糊匹配通过在 Alconna 中设置其 Config.fuzzy_match
开启。
模糊匹配会应用在任意需要进行名称判断的地方,如命令名称,选项名称和参数名称(如指定需要传入参数名称)。
from arclet.alconna import Alconna, Config
alc = Alconna("test_fuzzy", Config(fuzzy_match=True))
alc.parse("test_fuzy")
# output: test_fuzy is not matched. Do you mean "test_fuzzy"?
2
3
4
5
上下文插值
当 context_style
条目被设置后,传入的命令中符合上下文插值的字段会被自动替换成当前上下文中的信息。
上下文可以在 parse
中传入:
from arclet.alconna import Alconna, Args, Config
alc = Alconna("test", Args.foo(int), Config(context_style="parentheses"))
alc.parse("test $(bar)", {"bar": 123})
# {"foo": 123}
2
3
4
5
6
context_style 的值分两种:
"bracket"
: 插值格式为{...}
,例如{foo}
"parentheses"
: 插值格式为$(...)
,例如$(bar)
自定义语言文件
Alconna 的 i18n 支持使用了 Tarina.lang
其语言文件配置位于 arclet.alconna.i18n
下
您可以通过 tarina.lang.select
切换语言配置,也可以通过 tarina.lang.set
对单一文本进行修改。
参考链接
QQ 交流群: 🔗链接
要了解如何使用 Alconna-Graia, 参考 消息链匹配/Alconna
要了解如何使用 nonebot-plugin-alconna, 参考 nonebot-plugin-alconna 文档