3 个版本

0.2.3 2022年10月1日
0.2.2 2022年9月24日
0.2.1 2022年9月19日

#753 in 文件系统

MIT/Apache

15KB
405

珍贵 - 一键统治所有代码质量工具

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

如果你可以用一条命令运行所有这些该有多好?如果这条命令只需要一个配置文件来定义在项目的每个部分运行哪些工具该有多好?如果索伦是我们的统治者该有多好?

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

简单来说

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

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

安装

安装此工具有多种方法。

使用 ubi

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

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

二进制版本

您可以从 版本页面 获取二进制版本。解压 tar 包,将包含的可执行文件放在您的路径中即可。

Cargo

您还可以通过运行 cargo install precious 命令通过 cargo 安装此工具。有关二进制文件安装位置的详细信息,请参阅 cargo 文档

入门指南

precious》二进制文件有一个config init子命令,可以为您生成配置文件。此子命令接受以下标志:

标志 描述
-a--auto 自动确定要创建的组件
-c‑‑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》通过位于项目根目录的单个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键告诉precious在运行命令时工作目录应该是什么。

描述
"root" 工作目录是项目根目录。这是默认设置。
"dir" 工作目录是包含匹配文件的目录。这意味着precious在执行命令时会逐个将当前目录切换到每个匹配的目录。
.chdir—to= "path" 在执行命令时,工作目录将是给定的路径。这个路径必须是相对于项目根目录的。
working-dir.chdir-to= "path"

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 table - 值是字符串 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--quiet 抑制大部分输出
-a--ascii 将超级有趣的Unicode符号替换为无聊的ASCII符号
-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 谓词,通常在 $XDG_CONFIG_HOME/git/ignore 中找到。

这是使用 rust ignore crate 实现的,因此应在那里提出对其他 VCS 系统的支持。

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

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

包含和排除如何应用

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

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

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                         │
└─────────────────────┴──────┴────────────────────────────────────────────────────────┘

配置建议

以下是获得 precious 最佳体验的一些建议。

选择如何调用命令的 invoke

一些命令可能对 invoke 设置为 per-dironce 都同样有效。正确的运行模式取决于您如何使用 precious。

一般来说,如果您只有非常少的目录,或者您正在一次运行 precious 的大多数或所有目录,则 once 将更快。

但是,如果您有大量的目录,并且通常只需要一次检查这些目录中的小部分,则 per-dir 模式将更快。

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

命令的静默标志

许多命令会接受某种形式的“静默”标志。通常,您可能不希望以静默模式使用 precious 运行命令。

在成功执行整理或代码清理命令的情况下,precious会隐藏它运行的命令的所有标准输出(stdout)。如果命令以某种方式失败,precious将打印出命令的stdout和stderr输出。

默认情况下,precious将任何输出到stderr视为命令的错误(而不是代码清理失败)。您可以使用ignore-stderr指定一个或多个允许的stderr输出正则表达式。

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

总的来说,在quiet模式下运行命令对于precious来说没有价值。这样做只会使得在代码清理失败或其他问题发生时调试该命令变得更加困难。

退出代码

--tidy模式下运行时,如果整理过程中没有错误,precious始终以0退出,无论是否整理了任何文件。

--lint模式下运行时,如果所有文件都通过了代码清理,precious将以0退出。如果任何代码清理命令失败,它将以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。如果任何代码清理命令指示存在代码清理问题,它将以非零状态退出。

您想要以特定顺序运行命令

截至版本0.1.2,命令的运行顺序与配置文件中出现的顺序相同。

构建状态

构建和测试

Build Status

Cargo审计夜间

Cargo Audit Nightly

Cargo审计推送到

Cargo Audit On Push

依赖关系

~2–11MB
~131K SLoC