跳到主要内容

Alconna

警告

该教程是对整个 Alconna 特性的大致归纳,以帮助读者熟悉 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 一样获取指定的解析结果并获得类型支持
  • 正则匹配支持
  • ...

实时演示 (js):

实时编辑器
结果
Loading...

安装

for Python

pdm add arclet-alconna

for Nodejs

npm install @arcletjs/alconna

组件

Alconna 拥有两大组件:OptionSubcommand

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

那么 -f, -F 与 --FOO 将等同于 --foo

Subcommand 则可以传入自己的 OptionSubcommand

Subcommand("sub", Option("sub_opt"), Subcommand("sub_sub"), Args)
Subcommand("sub", Args, [Option("sub_opt"), Subcommand("sub_sub")])

他们拥有如下共同参数:

  • 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"]))
    提示

    requires 也可以在 name 中传入:

    Option("foo bar baz qux")
  • 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

帮助信息

每个 Alconna 命令 都可以通过 --help-h 触发命令的帮助信息,并且可以通过继承 arclet.alconna.components.output.TextFormatter 来个性化信息样式,如:

from arclet.alconna import Alconna, Args
from arclet.alconna.tools import MarkdownTextFormatter

alc = Alconna("test", Args["count#这是一个注释", int], formatter_type=MarkdownTextFormatter)
alc.parse("test --help")

'''
## Unknown

指令:

**test &lt;count:int&gt;**
### 注释:
&#96;&#96;&#96;
count: 这是一个注释
&#96;&#96;&#96;
'''

除此之外,你可以通过 command_manager 获取当前程序下的所有命令:

from arclet.alconna import command_manager
...
print(command_manager.all_command_help())

'''
# 当前可用的命令有:
- foo : Unknown
- bar : Unknown
- baz : Unknown
- qux : Unknown
# 输入'命令名 --help' 查看特定命令的语法
'''
备注

Alconna 可以设置 meta.hide 参数以不被 command_manager 打印出来。

from arclet.alconna import Alconna, CommandMeta, command_manager
foo = Alconna("foo", meta=CommandMeta(hide=True))
...
print(command_manager.all_command_help())

'''
# 当前可用的命令有:
- bar : Unknown
- baz : Unknown
- qux : Unknown
# 输入'命令名 --help' 查看特定命令的语法
'''

配置

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
'''

补全会话

补全会话基于半自动补全,提供了交互操作补全的接口

补全会话通过创建 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

快捷指令

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

一般情况下你可以通过 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 可以动态操作快捷指令。

命令参数

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

其通常以 Args[name1, var1, default1][name2, var2][Arg(name3, var3), Arg(name4, var4, default4)][...] 的方式构造一个 Args。

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

name

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

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

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

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

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

var

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

var 可以是以下几类:

  • 存在于 nepattern.all_patterns 中的类型/字符串,用以替换为预制好的 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

Arg

ArgArgs 的最小单位:

Args[Arg(k1, v1), Arg(k2, v2), ...]

Arg 初始化时可以传入:

  • name: 参数名
  • value: 参数类型
  • field: 参数域
  • seps: 参数分隔符
  • notice: 参数注释
  • flags: 参数标识符

Arg 可以通过传入 seps 指定该参数如何与后续数据区分,也可通过 Argsseparators 参数统一设置

解析结果

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

使用模糊匹配

模糊匹配通过在 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"?

自定义语言文件

Alconna 的 i18n 支持使用了 Tarina.lang

其语言文件配置位于 arclet.alconna.i18n

您可以通过 tarina.lang.select 切换语言配置,也可以通过 tarina.lang.set 对单一文本进行修改。

参考链接

教程; 👉点击

QQ 交流群: 🔗链接

友链: 📚文档