插件
plugin 指令把一个 WebAssembly 插件加载到账本中。插件可以在账本加载时转换账本、报告账本中的问题,或者在 /api/plugins/ 下提供页面。这条指令指定模块、授予插件能力,并把设置传给插件。
plugin "<Module>" ["<Argument>" …] <key>: "<value>" …| 部分 | 必填 | 说明 |
|---|---|---|
"<Module>" |
是 | .wasm 模块的路径,相对于账本根目录。 |
"<Argument>" |
否 | 位置参数,按原样传给插件。 |
<key>: "<value>" |
否 | 元数据行:张记账授予插件的能力,以及插件自己的设置。 |
plugin 指令没有日期。它可以写在账本的任何文件中。
账本用一个选项启用插件之前,插件都处于关闭状态:
option "features.plugin" "true"features.plugins 同样可用。值为 true(不区分大小写);其他任何值都让插件保持关闭。没有这个选项时,plugin 指令会被忽略,它们的模块也从不会被读取。
option "features.plugin" "true"
plugin "plugins/fx-rate.wasm" "USD" allowed_hosts: "api.frankfurter.dev" timeout: "30s" base_currency: "USD"
plugin "plugins/receipts.wasm" allowed_paths: "documents" allowed_paths: "statements/2024.csv"第一个插件可以向 api.frankfurter.dev 发送 HTTP 请求,对它的每次调用最多可以运行 30 秒,并且它会收到设置 base_currency。第二个插件可以读取 documents 下的文件和 statements/2024.csv 这个文件。
除非指令授予,否则插件不能在它自己的内存之外做任何事。以下元数据键是能力:
| 键 | 授予 | 默认值 |
|---|---|---|
allowed_hosts |
向这些主机发送 HTTP 请求。多个主机就重复这个键。 | 不能访问网络 |
allowed_paths |
以只读方式访问账本中的这些文件和目录。多个就重复这个键。 | 不能访问文件 |
timeout |
对插件的一次调用最多可以运行多久。 | 60 秒 |
seed |
不授予任何东西。任意文本,混入插件生成随机值所用的种子。 | 无 |
stage |
插件在加载账本时运行的阶段:"booked",在张记账完成记账之后;或 "raw",在记账之前。 |
"booked" |
allowed_hosts:每个值是一个主机名,例如 api.example.com,与插件发出的每个请求的主机进行匹配。其中的 * 匹配任意字符,所以 *.example.com 授予 example.com 的所有子域名。端口和路径不参与匹配。
allowed_paths:每个值是一个文件或目录,相对于账本根目录,用 / 分隔:
"."授予整个账本根目录。目录授予它下面的所有内容,按路径的各部分逐一比较:documents不授予documents-private。- 在已授予的目录下,隐藏的名称(以
.开头的名称)只有在某个值明确写出它时才可读取。所以"."不会暴露.git或.env,而".config"授予.config及其下的所有内容。 - 访问是只读的,账本根目录之外的任何内容都无法读取。单个文件最大 16 MiB,单次目录列表最多 10,000 个条目。
- 空值或绝对路径,或者含有
..部分、反斜杠或 NUL 字符的值,不授予任何东西,并报告为ParseInvalidMeta。 - 文件通过账本的数据源读取,所以这对存放在 S3、WebDAV 或 GitHub 上的账本同样有效。插件读取本地账本的某个文件时,
zhang serve会在该文件变化时重新加载账本。
timeout:整数秒("90"),或者带单位 ms、s、m 或 h 的整数("500ms"、"30s"、"2m")。它必须大于零,且不超过一天。无效的值会报告为 ParseInvalidMeta,插件使用默认值。如果一个键重复出现,以它的最后一个值为准。
stage:"booked" 让插件的 processor 和 mapper 在张记账完成记账之后运行:没有写金额的记账行带有张记账推算出的金额,成本写明它所匹配批次的单位成本和取得日期,跨多个批次的卖出是每个批次一行,与 Beancount 插件看到的一样。"raw" 让它们在记账之前运行,看到的是按原样写下的交易。其他值会报告为 ParseInvalidMeta,插件按 "booked" 运行。如果这个键重复出现,以它的最后一个值为准。见阶段顺序约定。
seed:插件的种子只取决于它的指令:按原样书写的模块、在它之前声明同一模块的 plugin 指令的数量,以及 seed 的值。修改 seed 会改变插件由它派生的随机值(例如生成的 id),而不必移动这条指令。
插件收到的设置
Section titled “插件收到的设置”插件收到一组字符串设置:
- 账本的每个选项,以选项的键为名;
- 它的指令的每个元数据键(重复的键取最后一个值),
allowed_hosts除外。这些设置优先于同名的选项; - 张记账自己设置的键:
zhang.abi,插件接口的版本;zhang.plugin,以 JSON 表示的整条指令,包括位置参数和每个元数据键的每个值;zhang.seed,插件的种子。以zhang.开头的键保留给张记账:对这三个键,张记账的值优先于同名的元数据键或选项。
插件还可以从 custom 指令中读取随时间变化的设置。
- 张记账通过账本的数据源,从账本根目录读取模块。它在启动
zhang serve的目录下的.cache/plugins中保留一份副本。本地模块发生变化时,zhang serve会重新加载账本。 - 插件按其
plugin指令的顺序运行,每次加载账本时都会运行,在张记账完成记账之后(声明了stage: "raw"的插件在记账之前运行),并且在张记账自己的步骤之前:先检查未开立的账户,然后补齐,然后检查余额。因此插件看到的是记账后的交易,此时补齐交易还不存在。插件从不会看到pad指令:由某条pad补齐的balance会以balance … with pad的样子交给它(见阶段顺序约定)。 - 同一个模块声明两次,会得到两个独立的插件,各自有自己的设置和种子。
- 模块缺失或无法加载,或者插件调用失败或运行超过
timeout,都会让账本无法加载。如果zhang serve已经在运行,它会继续提供重新加载之前的账本。 - 网页界面的设置页面列出已加载的插件。router 插件在
/api/plugins/<name>提供服务;见 Router 插件。
| 错误 | 触发条件 |
|---|---|
ParseInvalidMeta |
timeout、allowed_paths 或 stage 的值无效。错误指向这条 plugin 指令。 |
PluginError |
插件报告了账本中的问题。 |
Beancount 兼容性
Section titled “Beancount 兼容性”- Beancount 的插件是 Python 模块,例如
plugin "beancount.plugins.auto_accounts"。它们不能在张记账中运行。插件关闭时,这些指令会被忽略,账本可以加载。启用features.plugin时,张记账会查找以这个名字命名的模块文件,账本无法加载。 - Beancount 不接受
plugin指令下的元数据。授予了能力的账本无法通过bean-check。
