跳转到内容

13. 变更记录

每个版本改了什么,以及这些改动可能在你这边破坏什么。

版本仍处于 0.x,因此次版本号的提升允许带来破坏。一旦发生,条目会写清楚破坏 了什么、如何适配。这里不会有悄悄的改动。


  • 一个脚本可以让进程异常终止,而不是被拒绝。 源码里的嵌套变成了调用栈上的 嵌套:大约两千个括号就撑爆栈,运行以 SIGABRT 死掉——没有退出码 3,没有消息, 也无从捕获。六种写法都能做到:((((…))))、!!!!…、----…、嵌套数组、嵌套 代码块,以及 "#{"#{…}"}"。解析器现在会计数深度,超过 128 层就拒绝——远多于 任何人手写的脚本,也远低于栈耗尽的位置。最要命的是:这正是你拿到别人的脚本时 第一个会运行的命令——pdfl lint 不是报告,而是直接死了。
  • 一个包可以指定安装目录之外的文件。 检查拒绝了 .. 和开头的 /,却放过了 C:/Windows/evil.dll 和 \\server\share\x——两者都不含前面那些,而在 Windows 上它们是绝对路径,与安装目录拼接时会把目录本身丢掉。现在每个路径的每个 组成部分都必须是普通名字,判定规则不随解包的平台而变。
  • 一个包可以在读取过程中耗尽内存。 每个条目在查阅清单之前就被整个读进内存 ——包括清单从未提到的条目——于是 299 KB 的压缩零字节占用 380 MB,再大一点就 把机器拿走了。解包现在到 64 MB 为止,远高于任何由脚本和查找表组成的真实包。
  • --pages 1-20000000 在文档还没打开时就分配了页码——用掉 214 MB,然后什么也 没说。超过一百万页的范围会被拒绝。
  • 上面的修复在原本没有上限的地方设了上限,因此少数以前能通过的东西现在不行了: 嵌套超过 128 层的脚本、解包超过 64 MB 的包、文件名里带 : 或 \ 的包,以及 超过一百万页的页码范围。每一项都远超真实脚本、包或文档的实际内容——但它们以前 都不会失败,所以写下来,而不是留给你去发现。

查看器的模式被移除了。 Flip、Fade、Blend 滑块和 Differences 滑块,以及驱动它们的 space 和 d 键,都不再存在。

受影响的只有生成的查看器——报告、退出码和选项都没有变化,因此任何读取 pdfl 输出的东西都察觉不到。三个面板已经回答了那些模式要回答的问题:两个文件同时在 屏幕上,而不是轮流出现。如果你自己写过提到某个模式或某个键的说明,需要过一遍。

  • pixelcompare 的查看器改成三个面板并排——原件、新件,以及两者叠加并把差异 着色——不再是一个面板加一组模式。第三个面板擦除时,左右两个参照保持不动,于是 你能看清擦除是在哪两者之间切换。
  • 每个面板都带着同样的一对线——一竖一横——位置相同,六条一起移动。它们跟着指针 走:没有要抓的东西,也没有要瞄的目标,交点就是鼠标所在的位置。在差异面板里 两条线都是切口,新件出现在竖线右边、横线下边,两线相交处就是被揭开区域的那个 角;在另外两个面板里,它们是落在页面同一列、同一行上的尺。横线从最上方开始, 所以在指针进入面板之前,竖线仍是一条通高的普通擦除。
  • 滚轮让三个面板一起缩放,最多 8 倍,围绕指针下的那一点;缩小时到整页适配为止。 按住任意一个鼠标键拖动可以移动页面本身,三个面板一起动,且无论缩放多少都与 指针同速。任何缩放下线条都保持粗细。Reset view 把缩放、平移和线条送回 起始状态,在没有东西可还原时禁用;换页也会这样,因为缩放属于设定它的那一页。
  • 面板按窗口定尺寸:任何窗口形状下,整个比较都不用滚动就在屏幕上,并且各自 保持页面的比例。若两个文件对某页的尺寸各执一词,每一页都完整放进共同的框里, 而不是被拉伸。
  • 它一打开就停在有差异的页面上,All 会把其余的放回来。在长文档里,那些 页面正是打开它的原因。

loading 所见的标识符会变。 它由消息推导而来,而消息不再带 pdfium 放进去的 那些换行。

只有读不了的文档产生的所见会受影响——你自己的 check 不会产生这种。如果某条 基线批准了这样的标识符,需要重新记录一次。这也是本次发布是次版本而不是补丁的 原因:标识符是基线的立足点,它不该悄悄改变。

  • pdfl pack 会用打包那台机器的分隔符记录嵌套文件——在 Windows 上是 data\batches.csv。包在一台机器上打好、在另一台上安装,所以除 / 之外的写法 都会得到一个能装上、却找不到自己文件的包;我们自己的校验也找不到。在 Linux 和 macOS 上打的包从未受影响。
  • PDF 读不了时,--output 和 --output-file 会被忽略。无论要求什么格式, run、fix 和 compare 都往 stdout 打印 JSON——于是一条要求把 JUnit 写进 文件的流水线,既没拿到文件,报告又流进了它没在读的流里:明明退出码正确地给出 10,损坏的输入看上去却像这次运行从未发生。失败报告现在和其他报告走同一条路。
  • pdfium 的错误以跨三行排印的 Rust enum 形式进入报告。一条诊断是 CSV 行里的一个 字段、XML 里的一个属性,所以现在它被折成一行。

  • pdfl pixelcompare 为每个阶段显示进度条——光栅化两个文件、比较、写出查看器。 只有当 stderr 是终端时才绘制,因为进度条靠覆盖自己那一行来工作,而日志文件没有 可移动的光标;被重定向时它保持安静。--quiet 在任何情况下都会让它闭嘴。

  • pdfl pixelcompare 的 --jobs <n> 同时比较这么多页,默认每个 CPU 一个:150 dpi 的 41 页从 3.6 秒降到 1.2 秒。只有比较是并行的——pdfium 把每一次调用都串行在一把 全局锁后面,所以光栅化并行不了,这也是提速三倍而不是八倍的原因。取值不影响报告: 页面按页码顺序折回,因此诊断、它们的顺序和指纹都完全一致,查看器写出的文件也一样。

    这里默认每个 CPU 一个,而 test 和 watch 默认 --jobs 1。那边一个任务是 一个子进程,各自抱着一份文档;这边页面已经在内存里,线程共享它们。


  • pdfl pixelcompare 按外观而不是文字逐页比较两个 PDF,并报告不同像素的占比。 只是发生位移的页面会先对齐再比较,这样一个像素的偏移不会淹没真正要紧的改动。
  • pixelcompare 的 --viewer <文件夹> 会写出一个自包含的应用——不用 CDN、不用 打包器、不用服务器——可以在两个文件之间擦除、翻转或淡入淡出,差异就地着色: 红色表示消失的墨,绿色表示新增的墨,蓝色表示分量相同而颜色不同。页码条可以 筛成 Changed only,方向键也跟随筛选——在长文档里,翻过那些没动过的页面 才是真正费时的部分。

  • if / else if / else,作为表达式:它的值是所走分支的最后一个表达式, 与函数已经遵循的规则相同。因此它既能当值用 (const LIMIT = if coated { 300 } else { 260 }),也能当作语句的守卫,不需要 第二套语法。没有走到的分支返回 null,且每个分支有自己的作用域——给分支外 已经存在的变量赋值,仍然更新的是外面那个。

  • --var 现在也能到达 pdfl test 和 pdfl watch,不只是 pdfl run。此前两者 都没有把它转给自己派生的子进程,因此读取 vars.* 的脚本根本无法被测试或监视—— 不论内容是什么,每个用例或文件都会以“was not provided”失败。

  • pdfl pack 不再打包电子表格(.xlsx、.xls、.ods),并会说明留下了 哪个文件。没有任何 data:: 函数能打开它们,带着它的软件包安装顺利,却在第一次 查询时失败。若你此前打包了电子表格,请先导出为 .csv 或 .json。
  • run 的 --tags TAG,筛选运行哪些 check。可重复;check 只要带有其中任一 标签就会运行。
  • inspect 与 lint 的 --json,以及 doc 的 --output json。所有子命令 都可被程序读取了。
  • --output sarif 与 --output junit,凡是可以选择报告格式的地方都支持——run、 compare、watch 和 fix。SARIF 是 GitHub code scanning 读的格式,JUnit 是 任何 CI 的测试面板读的格式。
  • pdfl completions <shell> 输出 bash、zsh、fish、elvish 或 powershell 的补全 脚本。
  • 每个命令都可用的 --quiet,关闭 stderr 上的进度与确认信息。错误照常显示, print() 也不受影响——那是脚本自己的输出,吞掉它就改变了脚本的行为。
  • data::load_dataset 与 data::lookup_value 除 .csv 外也读 .json:数组的 数组,或对象的数组——由第一个对象按文件中的书写顺序确定列名。
  • pdfl test <script> 用脚本跑一整个文件夹的 PDF,并把每份报告与记录在旁边的那份 比对:配置文件一旦开始查出不一样的东西,失败的是测试,而不是下游某个人的一天。 --update 用于记录预期报告。
  • pdfl test 的 --jobs <n> 同时运行这么多用例,每个都是独立进程。8 个 41 页的 文件:--jobs 1 用 8.9 秒,--jobs 8 用 1.1 秒。默认仍是 1,因为每个任务都 把一份文档放在内存里;--jobs 0 表示每个 CPU 一个。
  • pdfl watch 也有 --jobs <n>:文件由子进程校验,因此批量的一轮同样可以扩展 (8 个 41 页的文件从 9.5 秒到 1.2 秒)。写出的报告与 --jobs 取值无关。
  • pdfl watch 的 --events:等待文件系统通知,而不是用定时器。这是可选项而非 默认值——在 NFS 或 SMB 挂载上,inotify 只报告本机写入的内容,网络热文件夹会 就此失声。如果监视器建不起来,watch 会说明并退回定时器。
  • pdfl watch 的 --journal <文件>:只追加的已校验记录,每个文件一行 JSON。用 同一个 journal 再跑会跳过它覆盖的文件——在五千个里跑到四千个被打断的批处理, 只需再跑剩下的一千个——但仍然报告它们的结论,所以续跑的批处理绝不会声称文件夹 是干净的。
  • pdfl watch 的 --timeout <秒>:超过这个秒数就杀掉某个文件的分析,并报告为 被拒——一条发现,check_name: "timeout"——而不是让批处理卡住。.pdfl 脚本的 递归已经有深度限制,所以这个选项是为脚本造不成的情况而设的:pdfium 在畸形或 恶意的 PDF 上死循环或卡住。
  • 没有任何 check 携带的标签会报错,而不是空通过。否则标签拼错的流水线将 什么都不校验,却报告文件干净。
  • rule 不带标签,因此 --tags 会跳过它——这与不带标签的 check 是同一个答案。
  • JSON 报告新增 checks_run,即运行过的 check 与 rule。它不提升 schema_version,因为忽略未知字段的读取方不会因此出错。JUnit 需要它:诊断只 会点到有所发现的 check,而一次干净的运行若报告为零个测试,CI 会认为这次运行 从未发生。
  • pdfl watch 现在在最新的那个文件稳定下来时就醒来,而不是最多再等一整个轮询 间隔。在 --debounce 3000 下,一个到达的文件约 3 秒后被报告,而不是最多 6 秒。

  • 脚本可以从命令行接收值:--var 名称=值,以 vars.名称 读取。未传入的值会 指出应当提供它的那个参数,而不是解析为空。
  • 四个用 visual:: 比较两份文档的完整示例:proof.pdfl、reprint.pdfl、 scope.pdfl 和 intake.pdfl。

无。从不提及 vars 的脚本行为与此前完全一致。


诊断标识符的形态变了。 原先是 PDFL-001,即本次运行内的计数;现在由发现 本身推导而来,例如 PDFL-093751a2。

凡是按 PDFL-\d+ 匹配的都不再匹配。作为交换,在某个发现之上插入 check 后, 它的标识符不再改变——这正是“已批准基线”得以维持的前提。

无法读取的输入以 10 退出,而不是 2。 此前损坏的文件与被判不合格的文件 对流水线来说无从区分。

如果你的 CI 把 2 当作“这个文件被拒绝”,那么在文件根本未被判定时它现在会 看到 10。发现仍使用 0、1、2;脚本语法错误仍是 3。

  • check 可以声明自身发现的严重级别: check "..." severity: warning { ... } — error(默认)、warning、info。 正是它让 --fail-on warning 有了作用对象。
  • JSON 报告以 schema_version 开头,消费方由此知道自己在读哪种形态。它仅在 读取旧输出的一方会被破坏时才提升;新增字段不会提升它。

  • PDF 报告有一部分是葡萄牙语:小节标题写作 Diagnósticos,每条诊断都带 (linha N)。二者现已改为英文,这本就是文档一直承诺的。

发布目标由 x64 改名为 amd64,因此所有资产名称都变了。

不再发布可移植归档,仅保留 Linux amd64 的那一个。

凡是下载 pdfl-<版本>-linux-x64.tar.gz,或使用 Linux amd64 之外任何可移植 归档的,都需要修改。在 CI 中请从 .deb 安装——本文档中的范例已相应更新—— 或者在无法安装的场合使用 Linux amd64 的 tarball。

  • 将文档与源码逐一比对后发现的两处缺漏:text::detect_personal_data 与 text::detect_pii 接受一个未被记录的可选字符串参数;fix::reorder_pages 在不同语言的文档里写法不一致。

  • 各平台的安装包:Linux 的 .deb、macOS 的 .dmg、Windows 的 -setup.exe 与 .msi。
  • macOS Intel 构建,自 Apple Silicon 运行器交叉编译。
  • Windows 安装包构建时路径解析到了错误的目录,因而从未生成过文件。
  • 发布包里带着 pdfium 的 C 头文件和构建文件,这些只与针对 pdfium 编译的人有关。 每个包约 550 KB。

  • Windows x64 加入发布平台。

随包的 pdfium.dll 位于 pdfium\bin,而非 pdfium\lib。如果你自行打包 pdfl,请保持发布时的布局:可执行文件在自身旁边寻找该库。


发布资产的名称中带上了版本号,形如 pdfl-<版本>-<目标>.tar.gz,内部目录 同样如此。

.../releases/latest/download/<名称> 不再解析,因为该端点需要精确的文件名。 请改用模式下载:gh release download --pattern 'pdfl-*-linux-amd64.*'。

  • 代码、README 和示例改为英文。文档仍保持七种语言。

首个公开版本。语言、解释器和十个 CLI 命令,附七种语言的文档。


← 实用范例 · 目录

DigitalOcean感谢 DigitalOcean 为本站提供托管服务。