#links #extract #documentation #common-mark #validate #link

app linky

从 Markdown 文件中提取链接并检查链接是否损坏

10 个版本

0.2.0 2023年6月15日
0.1.8 2019年12月15日
0.1.6 2019年5月31日
0.1.4 2018年3月30日
0.1.1 2017年11月25日

#13 in #common-mark

38 次每月下载

Apache-2.0

54KB
1K SLoC

Linky

Crates.io Changelog

从 Markdown 文件中提取链接并检查链接是否损坏。

动机

维护 Markdown 文档时,你经常会遇到很多需要处理的链接。Linky 提取所有链接并检查它们,指出损坏的链接,以便您可以修复它们。具体来说,Linky 是为了简化在 GitHub 上维护 Markdown 文档而创建的。

安装

安装稳定版本的 Rust 和 Cargo:

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

使用 Cargo 编译和安装 linky

$ cargo install linky

用法

您可以使用 linky 从 Markdown 文件中提取链接,这是您能做的最简单的事情

$ linky example_site/path/to/example.md
example_site/path/to/example.md:3:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md
example_site/path/to/example.md:4:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#existing
example_site/path/to/example.md:5:  other.md
example_site/path/to/example.md:6:  non-existing.md
example_site/path/to/example.md:7:  other.md#existing
example_site/path/to/example.md:8:  other.md#non-existing
example_site/path/to/example.md:9:  #heading
example_site/path/to/example.md:10:  #non-existing
example_site/path/to/example.md:11:  #heading-with-code
example_site/path/to/example.md:12:  #HEADING

输出列出了所有提取的链接及其相应的输入文件和行号。

启用 --check 选项以解析这些链接

$ linky --check example_site/path/to/example.md
example_site/path/to/example.md:3: OK https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md
example_site/path/to/example.md:4: NO_FRAG https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#existing
example_site/path/to/example.md:5: OK other.md
example_site/path/to/example.md:6: NO_DOC non-existing.md
example_site/path/to/example.md:7: OK other.md#existing
example_site/path/to/example.md:8: NO_FRAG other.md#non-existing
example_site/path/to/example.md:9: OK #heading
example_site/path/to/example.md:10: NO_FRAG #non-existing
example_site/path/to/example.md:11: OK #heading-with-code
example_site/path/to/example.md:12: CASE_FRAG #HEADING

现在,每行都添加了一个状态标记,指示解析的结果。一个 OK 标记表示解析成功,没有备注。有关链接解析的详细信息,请参阅链接解析部分

递归目录遍历

Linky 不会自动执行目录遍历。相反,它与 find 和 xargs 集成得很好

$ find example_site -type f -print0 | xargs -0 linky
example_site/path/to/absolute.md:2:  /path/to/other.md
example_site/path/to/absolute.md:3:  /path/to/non-existing.md
example_site/path/to/absolute.md:4:  /path/to/other.md#existing
example_site/path/to/absolute.md:5:  /path/to/other.md#non-existing
example_site/path/to/example.md:3:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md
example_site/path/to/example.md:4:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#existing
example_site/path/to/example.md:5:  other.md
example_site/path/to/example.md:6:  non-existing.md
example_site/path/to/example.md:7:  other.md#existing
example_site/path/to/example.md:8:  other.md#non-existing
example_site/path/to/example.md:9:  #heading
example_site/path/to/example.md:10:  #non-existing
example_site/path/to/example.md:11:  #heading-with-code
example_site/path/to/example.md:12:  #HEADING
example_site/path/to/filename with spaces.md:1:  #
example_site/path/to/follow.md:2:  http://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md
example_site/path/to/follow.md:3:  http://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#non-existing
example_site/path/to/fragment.md:2:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#existing
example_site/path/to/fragment.md:3:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#non-existing
example_site/path/to/other.md:2:  example.md
example_site/path/to/transform.md:2:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/non-existing.md
example_site/path/to/transform.md:3:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/only-on-example-branch.md

注意: 如果您的路径包含空格,您可能需要使用 find -print0 和 xargs -0 选项。

默认情况下,linky 不会解析绝对本地链接。这样,您可以知道哪些链接会重定向。此外,linky 也不知道文档根的位置。

$ linky --check example_site/path/to/absolute.md
example_site/path/to/absolute.md:2: ABSOLUTE /path/to/other.md
example_site/path/to/absolute.md:3: ABSOLUTE /path/to/non-existing.md
example_site/path/to/absolute.md:4: ABSOLUTE /path/to/other.md#existing
example_site/path/to/absolute.md:5: ABSOLUTE /path/to/other.md#non-existing

如果您使用 --root 选项指定文档根,linky 将相对该目录执行解析

$ linky --check --root=example_site example_site/path/to/absolute.md
example_site/path/to/absolute.md:2: OK /path/to/other.md
example_site/path/to/absolute.md:3: NO_DOC /path/to/non-existing.md
example_site/path/to/absolute.md:4: OK /path/to/other.md#existing
example_site/path/to/absolute.md:5: NO_FRAG /path/to/other.md#non-existing

HTTP 重定向

默认情况下,linky 不会遵循 HTTP 重定向。这样,您可以知道哪些链接会重定向。

$ linky --check example_site/path/to/follow.md
example_site/path/to/follow.md:2: HTTP_301 http://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md
example_site/path/to/follow.md:3: HTTP_301 http://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#non-existing

启用 --follow 选项,使 linky 在 HTTP 重定向中进行解析

$ linky --check --follow example_site/path/to/follow.md
example_site/path/to/follow.md:2: OK http://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md
example_site/path/to/follow.md:3: NO_FRAG http://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#non-existing

URI 片段标识符

有时当 Markdown 标题转换为 HTML id 属性时,会添加一个前缀到 id 属性中。例如,GitHub 添加了 "user-content-" 前缀。

首先,让我们尝试不带前缀的示例文件

$ linky --check example_site/path/to/fragment.md
example_site/path/to/fragment.md:2: NO_FRAG https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#existing
example_site/path/to/fragment.md:3: NO_FRAG https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#non-existing

现在,让我们尝试添加那个前缀

$ linky --check --prefix='user-content-' example_site/path/to/fragment.md
example_site/path/to/fragment.md:2: PREFIXED https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#existing
example_site/path/to/fragment.md:3: NO_FRAG https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#non-existing

例如,如果您想检查链接针对姐妹网站的开发版本,您可以将链接通过 sed 管道传递以转换基本 URL。

首先,让我们从示例文件中提取所有链接

$ linky example_site/path/to/transform.md
example_site/path/to/transform.md:2:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/non-existing.md
example_site/path/to/transform.md:3:  https://github.com/mattias-p/linky/blob/master/example_site/path/to/only-on-example-branch.md

使用sed编辑链接,使它们指向姐妹网站

$ linky example_site/path/to/transform.md | sed 's,/master/,/example/,'
example_site/path/to/transform.md:2:  https://github.com/mattias-p/linky/blob/example/example_site/path/to/non-existing.md
example_site/path/to/transform.md:3:  https://github.com/mattias-p/linky/blob/example/example_site/path/to/only-on-example-branch.md

注意: 您可能需要小心使用sed表达式,以免无意中转换路径前缀。

最后,将编辑后的链接输出管道传输到另一个实际检查链接的链接过程

$ linky example_site/path/to/transform.md | sed 's,/master/,/example/,' | linky --check
example_site/path/to/transform.md:2: HTTP_404 https://github.com/mattias-p/linky/blob/example/example_site/path/to/non-existing.md
example_site/path/to/transform.md:3: OK https://github.com/mattias-p/linky/blob/example/example_site/path/to/only-on-example-branch.md

解析速度

Linky使用线程池进行链接解析。线程池的大小会影响吞吐量,但输出本身不受影响。

您可以使用环境变量RAYON_NUM_THREADS设置线程池的大小

$ env RAYON_NUM_THREADS=16 linky --check example_site/path/to/example.md

线程池的默认大小等于进程可用的逻辑CPU核心数。您可能可以通过使用比默认更大的线程池来实现更高的吞吐量。使用time命令来基准测试合适的大小。

解析细节

如果您想知道为什么某个链接解析到了它获得的任何状态令牌,请将环境变量RUST_LOG设置为warn

$ env RUST_LOG=warn linky --check example_site/path/to/example.md
example_site/path/to/example.md:3: OK https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md
 WARN  linky > Fragment not found
 WARN  linky >   context: link = https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md
 WARN  linky >   context: fragment = #existing
example_site/path/to/example.md:4: NO_FRAG https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#existing
example_site/path/to/example.md:5: OK other.md
 WARN  linky > Document not found
 WARN  linky >   context: link = /tmp/linky/example_site/path/to/non-existing.md
 WARN  linky >   caused by: No such file or directory (os error 2)
example_site/path/to/example.md:6: NO_DOC non-existing.md
example_site/path/to/example.md:7: OK other.md#existing
 WARN  linky > Fragment not found
 WARN  linky >   context: link = /tmp/linky/example_site/path/to/other.md
 WARN  linky >   context: fragment = #non-existing
example_site/path/to/example.md:8: NO_FRAG other.md#non-existing
example_site/path/to/example.md:9: OK #heading
 WARN  linky > Fragment not found
 WARN  linky >   context: link = /tmp/linky/example_site/path/to/example.md
 WARN  linky >   context: fragment = #non-existing
example_site/path/to/example.md:10: NO_FRAG #non-existing
example_site/path/to/example.md:11: OK #heading-with-code
 WARN  linky > Fragment not found case-sensitively
 WARN  linky >   context: link = /tmp/linky/example_site/path/to/example.md
 WARN  linky >   context: fragment = #HEADING
 WARN  linky >   context: anchor = #heading
example_site/path/to/example.md:12: CASE_FRAG #HEADING

本地链接解析为本地文件系统中的可读普通文件和目录。HTTP(S)链接使用GET请求解析到200响应,可选地遵循重定向。目标文档被读取并解码为字符串。

对于HTTP(S)链接,片段解析为HTML锚点。对于本地链接,片段解析为Markdown标题。首先尝试无前缀解析片段,如果失败,则尝试使用每个前缀(如果有),进行解析。

问题

如果您在使用linky时遇到问题,提供测试Markdown文档和执行详细信息将非常有帮助。

例如

$ env RUST_LOG=debug RUST_BACKTRACE=1 linky --check  test.md 2&> linky_err.log

注意: RUST_LOG控制日志详细程度。 RUST_BACKTRACE控制在panic时打印堆栈跟踪。

只需将生成的linky_err.log文件拖放到GitHub的问题编辑器中。遗憾的是,截至2018年2月,GitHub不允许拖放Markdown文件 (*.md)。您可以选择

  • 将您的test.md文件重命名为test.md.txt

  • 使用第三方粘贴服务(例如Hastebin

  • 如果文件不大,可以在问题内部使用代码块内联

    ´´´Markdown

    您的测试Markdown内容...

    ´´´

许可证

版权所有 2017-2019 Mattias Päivärinta

遵循Apache License, Version 2.0(“许可证”);除非您遵守许可证,否则不得使用此分布中的任何文件。您可以在以下位置获得许可证副本:

http://www.apache.org/licenses/LICENSE-2.0

除非适用法律要求或书面同意,否则在许可证下分发的软件按“现状”分发,不提供任何形式的保证或条件,无论是明示的还是暗示的。有关许可证的具体语言、权限和限制,请参阅许可证。

依赖项

~12–28MB
~466K SLoC