18 个版本

0.7.3 2024 年 6 月 17 日
0.7.0 2024 年 3 月 30 日
0.6.2 2023 年 12 月 18 日
0.6.1 2023 年 11 月 6 日
0.4.1 2022 年 11 月 26 日

#644 in 开发工具

MIT/Apache

63KB
787

珍贵 - 一把统治所有代码质量工具的利剑

谁不喜欢代码检查器和整理器(又称美化器)呢?我当然喜欢。我非常喜欢,以至于在我的许多项目中可能会有五到十个!

如果能够通过一个命令运行所有这些工具,那不是很好吗?如果这个命令只需要一个配置文件来定义项目各部分要运行哪些工具,那不是很好吗?如果索伦是我们的统治者,那不是很好吗?

现在,有了珍贵,你可以对所有这些问题说“是的”。

简而言之

珍贵是一个代码质量工具,允许您通过一个命令运行所有的代码检查器和整理器。它的功能包括:

  • 一个文件,precious.toml,定义了所有的代码检查器和整理器命令,以及它们操作的文件。
  • 尊重 VCS 忽略文件,并允许全局和按命令排除。
  • 语言无关,与单语言或多语言项目一样工作。
  • 易于与提交钩子和 CI 系统集成。
  • 默认情况下,命令并行执行,每个 CPU 一个进程。
  • 可以通过标签对命令进行分组,例如,仅运行提交钩子的一组子命令和在 CI 中运行的所有命令。

安装

有几种安装此工具的方法。

使用 ubi

安装我的 通用二进制安装程序 (ubi) 工具,您可以使用它下载 precious 和许多其他工具。

$> ubi --project houseabsolute/precious --in ~/bin

二进制发布

您可以从 发布页面 获取二进制发布。解压 tarball,并将其中包含的可执行文件放置在您的路径中的某个位置,然后您就可以开始使用了。

Cargo

您还可以通过运行以下命令来使用 cargo 安装此程序:cargo install precious。有关二进制文件安装位置的更多信息,请参阅 cargo 文档

入门指南

precious 二进制文件有一个 config init 子命令,可以为您生成配置文件。此子命令支持以下选项:

选项 说明
---auto 自动确定要创建的组件
-‑‑component <COMPONENT> 要为哪些组件生成配置(见下文)
-‑‑path <PATH> 配置文件应写入的路径。默认为 ./precious.toml

您必须传递 --auto 或至少一个 --component。在 --auto 模式下,precious 将检查您的项目中的所有文件,并根据找到的文件类型生成配置。

以下是一个 Rust 项目的示例

$> precious config init --component rust --component gitignore --component yaml

组件

以下组件受支持:

  • go - 为使用 golangci-lint 进行代码检查和整理的 Go 项目生成配置。
  • perl - 为使用各种工具(包括 perlcriticperltidy)的 Perl 项目生成配置。
  • rust - 为使用 rustfmt 进行整理和 clippy 进行代码检查的 Rust 项目生成配置。
  • shell - 生成使用 shfmt 进行整理和 shellcheck 进行代码检查的配置。
  • gitignore - 使用 omegasort.gitignore 文件进行代码检查和整理(通过排序)。
  • markdown - 使用 prettier 对 Markdown 文件进行代码检查和整理。
  • toml - 使用 taplo 对 TOML 文件进行代码检查和整理。
  • yaml - 使用 prettier 对 YAML 文件进行代码检查和整理。

示例

此存储库的 示例目录 中包含为几种语言生成的 precious.toml 配置文件。欢迎为其他语言做出贡献!

示例中的配置与 precious config init 生成的配置相匹配,文件中包含有关如何更改此配置的更多详细注释。

还可以查看 示例脚本 install-dev-tools.sh,该脚本可安装项目中所有代码检查和整理依赖项。您可以根据需要对其进行自定义,以便仅安装项目所需的工具。

配置

珍贵配置通过一个位于项目根目录的单个precious.toml.precious.toml文件来完成。该文件采用TOML格式

配置文件顶层表中只能设置一个键。

类型 是否必需? 说明
exclude 字符串数组 每个数组成员都是一个模式,当运行precious时,该模式将与潜在文件进行匹配。这些模式与gitignore文件中的模式匹配方式相同。
您可以使用以!开头的行来否定列表中先前规则的意义,这样即使匹配了先前规则,也不会排除匹配项。

所有其他配置均基于每个命令。命令是指可以整理(即美化打印或美化)、检查或两者都做的操作。这些命令是珍贵将根据需要执行的第三方程序。

每个命令都在一个类似[commands.command-name]的块中定义。在commands.前缀之后的每个名称都必须是唯一的。只要每个命令都有一个唯一的名称,您就可以用不同的命令以不同的方式运行相同的可执行文件。

命令的运行顺序与它们在配置文件中出现的顺序相同。

命令调用

有三个配置键用于命令调用。它们都是可选的。如果没有指定,precious将默认使用此设置:

invoke      = "per-file"
working-dir = "root"
path-args   = "file"

这将针对每个文件运行一次命令,命令的工作目录为项目根目录。命令将作为单个参数传递一个从根目录到文件的相对路径。

invoke

invoke键告诉precious如何调用命令。

说明
"per-file" 为每个匹配的文件运行此命令。这是默认设置。
"per-dir" 为每个匹配的目录运行此命令。
"once" 只运行一次此命令。

还有一些针对invoke键的实验性选项。 它们的确切名称或操作细节可能会在未来版本中更改。

说明
 .per-file-or-dir= n  如果匹配的文件数小于n,则为此每个匹配的文件运行此命令。否则,为每个匹配的目录运行一次。
 .per-file-or-once= n  如果匹配的文件数小于n,则为每个匹配的文件运行一次此命令。否则,只运行一次。
 .per-dir-or-dir= n  如果匹配的目录数小于n,则为每个匹配的目录运行一次此命令。否则,只运行一次。

这些选项的写法如下:

[commands.some-command]
invoke.per-file-or-dir = 42

这些实验性选项对于优化命令运行速度很有用。在某些情况下,命令可以以多种方式运行,完成速度取决于需要检查或整理的文件或目录数量。

golangci-lint工具是一个很好的例子。为几个目录多次调用它可能比在整个仓库中调用它要快得多。然而,一旦有足够的目录进行检查,一次性调用整个仓库会更高效。

请注意,path-args设置需要与这些选项的可能情况一起工作。对于golangci-lint,这意味着在使用per-dir-or-once时将其设置为dir

working-dir

working-dir键告诉珍贵在运行命令时应使用什么工作目录。

说明
"root" 工作目录是项目根目录。 这是默认设置。
"dir" 工作目录是包含匹配文件的目录。这意味着 precious 在执行命令时将逐个将 chdir 进入每个匹配的目录。
.chdir—to= "路径" 执行命令时,工作目录将是给定的路径。 此路径必须相对于项目根目录。
working-dir.chdir-to= "路径"

working-dir 的最后一个选项是将显式路径设置为工作目录。

使用此选项,当执行命令时,工作目录将设置为给定的子目录。传递给命令的相对路径将相对于此子目录,而不是项目根目录。

path-args

path-args 键告诉 precious 在运行命令时如何传递路径。

说明
"file" 传递相对于根的匹配文件的路径。 这是默认设置。
使用 working-directory.chdir-to 时,路径相对于给定的工作目录。
"dir" 传递相对于根的包含匹配文件的目录的路径。
使用 working-directory.chdir-to 时,路径相对于给定的工作目录。
"none" 根本不向命令传递任何路径。
"dot" 始终传递 . 作为路径。这在 working-dir = "dir" 且命令仍需要传递路径时很有用。
"absolute-file" 以从文件系统根目录的绝对路径传递匹配文件的路径。
"absolute-dir" 以从文件系统根目录的绝对路径传递包含匹配文件的目录的路径。

不合理组合

这些配置键的大多数组合都是允许的,但有些不合理的组合会导致 precious 错误退出。

invoke = "per-file"
path-args = "dir", "none", "dot", or "absolute-dir"

不传递文件名,您无法为每个文件调用一次命令。

invoke = "per-dir"
path-args = "none" or "dot"
working-dir = "root"
# ... or ...
working-dir.chdir-to = "whatever"

您不能在没有传递目录名或文件名列表的情况下从根目录为每个目录调用一次命令。如果您想在没有路径参数或使用 . 作为路径的情况下为每个目录运行一次命令,那么您 必须 设置 working-dir = "dir"

invoke = "once"
working-dir = "dir"

如果您将工作目录设置为每个匹配的目录,则无法为每个匹配的目录调用一次命令。

调用示例

请参阅调用示例文档,以了解每个可能选项集的完整示例。

其他每命令配置键

每个命令允许的其他键如下

类型 是否必需? 适用范围 默认值 说明
type 字符串 yes all 这必须是 linttidyboth。这定义了此命令的类型。一个 both 命令 必须 同时定义 lint-flagstidy-flags
include 字符串或字符串数组 yes all 每个数组成员都是 gitignore 模式,它告诉 precious 该命令应用于哪些文件。
您可以使用以 ! 开头的行来否定列表中先前规则的意义,因此即使匹配了先前的规则,也 包括匹配的内容。
exclude 字符串或字符串数组 all 数组中的每个成员都是一个 gitignore 模式,它告诉 precious 哪些文件不应应用此命令。
您可以使用以!开头的行来否定列表中先前规则的意义,这样即使匹配了先前规则,也不会排除匹配项。
cmd 字符串或字符串数组 yes all 这是要运行的执行文件,后面跟着应始终传递的任何参数。
env 表格 - 值是字符串 all 此键允许您设置一个或多个在运行命令时设置的环境变量。此表中的值必须是字符串。
path-flag 字符串 all 默认情况下,precious 将操作路径传递给执行的命令作为最后一个、位置参数。如果命令通过标志接受路径,则需要使用此键指定该标志。
lint-flags 字符串或字符串数组 结合的检查器和整理器 如果命令既是检查器又是整理器,则它可能需要额外的标志以在检查器模式下运行。这就是设置该标志的方法。
tidy-flags 字符串或字符串数组 结合的检查器和整理器 如果命令既是检查器又是整理器,则它可能需要额外的标志以在整理模式下运行。这就是设置该标志的方法。
ok-exit-codes 整数或整数数组 yes all 任何不表示异常退出的退出代码都应在此处。对于大多数命令,这只是一个 0,但某些命令可能使用其他退出代码,即使对于正常退出也是如此。
lint-failure-exit-codes 整数或整数数组 检查器 如果命令是检查器,则这些是表示检查失败的状态代码。这些需要指定,以便 precious 可以区分由于检查失败而退出与由于某些意外问题而退出的退出。
ignore-stderr 字符串或字符串数组 all all 默认情况下,precious 假设当命令将输出发送到 stderr 时,这表明检查或整理失败。此参数可以指定一个或多个正则表达式。这些正则表达式将与命令的 stderr 输出进行匹配。如果 任何 正则表达式匹配,则忽略 stderr 输出。
labels 字符串或字符串数组 all all 用于对命令进行分类的一个或多个标签。有关更多详细信息,请参见下文。

引用项目根目录

对于可以从子目录运行的命令,您可能需要按项目根目录指定配置文件。您可以通过在 cmd 配置键的任何元素中使用字符串 $PRECIOUS_ROOT 来完成此操作。例如,您可以编写如下内容

cmd = ["some-tidier", "--config", "$PRECIOUS_ROOT/some-tidier.conf"]

字符串 $PRECIOUS_ROOT 将被替换为项目根目录的绝对路径。

运行 Precious

要获取帮助,请运行 precious --help

根命令接受以下标志

选项 说明
-c--config <config> precious 配置文件的路径
-j--jobs <jobs> 要运行的并行作业(线程)数(默认为每个核心一个)
-q 抑制大部分输出
-a--ascii 用无聊的 ASCII 替换超级有趣的 Unicode 符号
-v--verbose 启用详细输出
-V, --version 打印版本信息
-d, --debug 启用调试输出
-t, --trace 启用跟踪输出(最大日志记录)
-h, --help 打印帮助信息

并行执行

Precious默认会并行执行命令,每个CPU一个进程。执行并行化基于命令的调用配置。例如,在12个CPU的系统上,具有invoke = "per-file"的命令将并行执行多达12次,每个命令执行接收一个文件。

可以通过传递--jobs 1来禁用并行执行。

子命令

precious命令有三个子命令,linttidyconfig。您必须指定其中一个。命令linttidy使用相同的标志

选择操作的路径

当您运行precious时,您必须告诉它要操作哪些路径。为此有几个标志

模式 选项 说明
所有路径 -a, --all 在项目根目录下的所有文件上运行(包含precious配置文件的目录)。
根据git修改的文件 -g, --git 在git报告为已修改的所有文件上运行,包括暂存文件。
根据git暂存的文件 -s, --staged 在git报告为已暂存的文件上运行。
与给定git ref不同的文件 -d <REF>, ‑‑git‑diff‑from 在当前HEAD中与给定<REF>不同的所有文件上运行。该值<REF>可以是分支名,例如master,或引用名,例如HEAD~6master@{2.days.ago}。有关更多信息,请参阅git help rev-parse。请注意,这不会看到本地工作目录中有未提交更改的文件。
根据git暂存的文件,未暂存更改被暂存 ‑‑staged‑with‑stash 这类似于--stashed,但在运行时会暂存未暂存更改,并在结束时弹出暂存。这确保命令只针对代码库的暂存版本运行。这可能会与许多监视文件更改的编辑器或其他工具发生问题,因此请谨慎使用此标志。由于此问题,在脚本中使用此选项时请小心。
在CLI上提供的路径 如果您没有传递上述任何标志,则 precious 将期望在所有其他标志之后在命令行上传递一个或多个路径。如果其中任何一个路径是目录,则整个目录树将被包括在内。

运行单个命令

您可以通过传递 --command 标志来使用单个命令进行整理或检查代码风格

$> precious lint --command some-command --all

传递给 --command 的名称必须与您的配置文件中命令的名称匹配。所以在上面的例子中,它会在您的配置中查找定义为 [commands.some-command] 的命令。

使用标签选择命令

每个命令可以分配一个或多个标签。这使您可以创建任意组的命令。然后,当您整理或检查代码风格时,可以通过传递一个 --label 标志来选择标签

$> precious lint --label some-label --all

标签的工作方式如下

  • 在其配置中没有 labels 键的命令有一个标签,即 default
  • 在不传递 --label 标志的情况下运行 tidylint 使用的是 default 标签。
  • 如果您给一个命令分配了 labels,并且您希望该命令包含在 default 标签中,您必须明确包含它
    [command.some-command]
    # ...
    labels = [ "default", "some-label" ]
    

默认排除项

当选择路径时,precious 总是尊重您的忽略文件。目前它只知道这对于 git 的工作方式,它将尊重以下所有忽略文件

  • 每个目录的 .ignore.gitignore 文件。
  • 文件 .git/info/exclude
  • 全局 gitignore globs,通常位于 $XDG_CONFIG_HOME/git/ignore

这是使用 rust ignore crate 实现的,因此建议在其他 VCS 系统中添加支持。

此外,您可以通过设置全局 exclude 键来指定所有命令的排除项。

最后,您可以指定每个命令的 includeexclude 键。

如何应用包含和排除

precious 运行时,它会执行以下操作以确定哪些命令适用于哪些路径。

  • 根据指定的命令行标志选择要操作的基本文件。这是以下之一
    • --all - 项目根目录下(包含 precious 配置文件的目录)下的所有文件。
    • --git - git 仓库中所有已修改的文件,包括暂存文件。
    • --staged - git 仓库中所有已暂存的文件。
    • --git-diff-from <REF> - 与 <REF> 不同的当前 HEAD 中的所有文件。
    • 通过 CLI 传递的路径 - 如果路径是一个文件,它将原样添加到列表中。如果路径是目录,则找到该目录下的所有文件(递归)。
  • 将应用 VCS 忽略规则以从该列表中删除文件。
  • 将应用全局排除规则以从该列表中删除文件。
  • 基于命令的invoke键,生成一个待检查的文件列表,并应用命令的包含/排除规则。要被包含,一个文件必须至少匹配一个包含规则并且不匹配任何排除规则才能被接受。
    • 如果invoke设置为per-file,则逐个文件应用规则。
    • 如果invoke设置为per-dir,则如果目录中任何文件匹配规则,则将在该目录上运行命令。
    • 如果invoke设置为once,则一次性将规则应用于所有文件。如果其中任何一个文件匹配包含规则,则将运行命令。

config子命令

除了init子命令外,该命令还有一个list子命令。该子命令将打印一个Unicode表格,描述配置文件中的命令。

Found config file at: /home/autarch/projects/precious/precious.toml

┌─────────────────────┬──────┬────────────────────────────────────────────────────────┐
│ Name                ┆ Type ┆ Runs                                                   │
╞═════════════════════╪══════╪════════════════════════════════════════════════════════╡
│ rustfmt             ┆ both ┆ rustfmt --edition 2021                                 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ clippy              ┆ lint ┆ cargo clippy --locked --all-targets --all-features     │
│                     ┆      ┆ --workspace -- -D clippy::all                          │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ prettier            ┆ both ┆ ./node_modules/.bin/prettier --no-config --print-width │
│                     ┆      ┆ 100 --prose-wrap always                                │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ omegasort-gitignore ┆ both ┆ omegasort --sort path --unique                         │
└─────────────────────┴──────┴────────────────────────────────────────────────────────┘

配置建议

以下是一些关于如何获得珍贵的最佳体验的建议。

选择如何调用命令invoke

一些命令可能在与invoke设置为per-dironce时效果相同。正确的运行模式取决于您如何使用珍贵。

通常,如果您有非常少的目录集,或者您一次运行珍贵于大多数或所有目录,那么once将更快。

但是,如果您有更多的目录集,并且通常只需要一次性检查这些目录的小部分,那么per-dir模式将更快。

您还可以使用实验性的invoke.per-dir-or-once=n选项,根据珍贵将操作的数量以两种方式之一调用命令。

命令的静默标志

许多命令将接受某种类型的“静默”标志。一般来说,您可能不希望用珍贵在静默模式下运行命令。

在tidy或lint命令成功执行的情况下,珍贵已经隐藏了它运行的命令的所有stdout。如果命令以某种方式失败,珍贵将打印出命令的stdout和stderr输出。

默认情况下,珍贵将任何输出到stderr视为命令中的错误(与linting失败相对)。您可以使用ignore-stderr指定一个或多个允许的stderr输出的正则表达式。

此外,您可以通过在--debug模式下运行珍贵来查看命令的所有stdout和stderr输出。

所有这些都意味着在一般情况下,在静默模式下运行命令没有价值。这只会使在lint检查失败或发生其他问题时调试该命令的问题更困难。

退出代码

--tidy模式下运行时,如果tidy没有错误,珍贵总是以0退出,无论是否整理了任何文件。

--lint模式下运行时,如果所有文件都通过了linting,珍贵将退出0。如果任何lint命令失败,它将退出1

在两种模式下,如果任何命令失败,无论是返回未列出的非正常退出码,还是意外地打印到stderr,则退出码不会是01

常见场景

有一些配置场景您可能需要处理。以下是一些示例

命令在整个源树中只运行一次

一些命令,例如rust-clippy,期望在整个源树中只运行一次,而不是在每个文件或目录中运行一次。

为了实现这一点,您应该使用以下配置

include = "**/*.rs"
invoke = "once"
path-args = "dot" # or "none"

这将导致precious在项目根目录中运行命令恰好一次。

命令在其lint的文件相同的目录中运行,且不接受路径参数

如果您想在命令中不传递正在操作的路径,请设置invoke = "per-dir"working-dir = "dir",和path-args = "none"

include     = "**/*.rs"
invoke      = "per-dir"
working-dir = "dir"
path-args   = "none"

您想排除整个目录(树)中的某个或多个文件

exclude列表中使用以!开头的忽略模式

[commands.rustfmt]
type    = "both"
include = "**/*.rs"
exclude = [
    "path/to/dir",
    "!path/to/dir/included.rs",
]
cmd     = ["rustfmt"]
lint-flags = "--check"
ok-exit-codes = [0]
lint-failure-exit-codes = [1]

您想将Precious作为提交钩子运行

只需在您的钩子中运行precious lint -s。如果任何lint命令指示linting问题,它将以非零状态退出。

您想按特定顺序运行命令

从版本0.1.2开始,命令以它们在配置文件中出现的顺序运行。

构建状态

构建和测试

Build Status

Cargo Audit Nightly

Cargo Audit Nightly

Cargo Audit On Push

Cargo Audit On Push

无运行时依赖