#config-file #json-configuration #format-json #file-path #json-file #json-path #clang-format

bin+lib run-clang-format

跨平台 CLI 包装器,用于执行基于 JSON 输入文件中指定的路径或 globs 的 clang-format

8 个稳定版本

1.5.0 2024 年 3 月 8 日
1.4.14 2023 年 12 月 1 日
1.4.13 2023 年 10 月 6 日
1.4.12 2023 年 9 月 19 日
1.4.8 2023 年 1 月 29 日

#250解析器实现

自定义许可

6.5MB
1.5K SLoC

run-clang-format

Build status

CLI 应用程序,用于在指定使用 .json 配置文件中 glob 指定的文件集上运行 clang-format

快速入门

执行此操作的简要命令如下

$ run-clang-format path/to/format.json

执行 run-clang-format --help 获取更多详细信息,或 run-clang-format schema 获取配置文件的完整模式描述。

对急于求成用户的提示

  • 除非在配置文件中更改设置,否则隐藏路径和文件将被排除
  • 此工具假设 clang-format 已安装并在您的路径中。该命令可以指定在您的 配置文件 或作为 命令行参数
  • 路径可以使用 glob 或 Unix 风格路径语法 指定。
  • 如果指定了 -j 选项,则格式化将在并行中执行。
  • 要检查文件格式而不会更改内容,此工具可以在 --check 模式 下执行。

内容

JSON 配置文件

此 CLI 工具的核心是一个 .json 配置文件,该文件指定了所有应格式化的文件的位置。我们将使用一个示例文件,逐步构建,以解释各个字段。该 .json 文件的结构也在 schema 子命令中进行了文档说明(执行 run-clang-format schema)。要开始,我们创建一个空的 .json 文件,其中包含一个空对象。

{
}

添加路径

此配置文件中唯一真正必需的字段是 paths。此字段包含相对于配置文件父目录的路径或 globs。考虑以下文件夹结构

ProjectRoot
│
├── Some
│   └── Path
│       ├── header.h
│       └── source.c
│
└── Settings
    ├── format.json
    └── <...>

在配置文件 format.json 中,两个文件的路径需要如下指定

{
  "paths": [
    "../Some/Path/header.h",
    "../Some/Path/source.c",
  ],
}

备注: 此工具是为软件开发者设计的,因此任何用户都应该知道路径本身可能相当复杂:考虑链接、字符编码等,你就能明白这一点。因此,任何在路径中使用笑脸或其他超现实事物的用户,如果遇到问题,可以为此存储库做出贡献,但并非所有场景都可以或将会被测试。

显然,没有人愿意手动指定所有路径,这就是为什么此工具支持使用 Unix 风格的 globs。以下模式都会解析到相同的路径,但只是提供参考

{
  "paths": [
    "../**/*.[ch]",
    "../Some/*/*.*",
  ],
}

假设你已经安装了 clang-format 并在你的源代码的父目录之一中有一个 .clang-format 文件,例如在 ProjectRoot 中,这就是你所需要的:

$ run-clang-format path/to/format.json

请注意,工具的工作目录无关紧要,因为所有路径都是相对于提供的 format.json 指定的。目前,你需要知道的就是这些,我们将在稍后详细讨论支持的场景,并继续探索 .json 文件中的配置选项。

Glob 和路径语法

此工具使用 globset Rust 包来解析 globs。因此,它还依赖于其 语法。我们在这里借用了解释。当使用 globs 时,支持 标准 Unix 风格的 glob 语法

  • ? 匹配任何单个字符。它不匹配路径分隔符。
  • * 匹配零个或多个字符,但不匹配跨越目录边界的情况,即它不匹配路径分隔符。你必须使用 ** 来匹配。
  • ** 递归地匹配目录,如果使用不带路径分隔符,则表示“匹配所有内容”。
  • {a,b}ab 匹配,其中 ab 是任意的通配模式。不支持嵌套 {...}
  • [ab]ab 匹配,其中 ab字符。使用 [!ab] 来匹配任何 除了 ab 之外的字符。
  • 元字符,如 *?,可以用字符类符号来转义。例如,[*] 匹配 *
  • 反斜杠 \ 会转义 glob 中的所有元字符,但因为它在 .json 配置文件中定义,所以必须指定为双反斜杠 \\。如果它位于非元字符之前,则斜杠将被忽略。

对于 Windows 路径,所有 glob 都是不区分大小写的。

注意:由于在 .json 文件中反斜杠必须转义,并且 glob 中的反斜杠根据后面的字符是否是元字符而表现不同,因此强烈建议在任何平台上都使用正斜杠 / 作为路径分隔符。在 Windows 上,可以使用 \\ 作为路径分隔符,但前提是它不位于元字符之前。

预过滤

默认情况下,此工具将 排除 所有隐藏的文件和文件夹。可以通过字段 filterPre 配置此行为。该字段设置了一个在递归搜索文件时应用的过滤器,因此是在将提供的 glob 与字段 paths 中的 glob 匹配之前执行的。这种过滤器的典型模式是排除版本控制系统使用的文件夹,例如 .git(或 .svn)文件夹。

对于此字段,您仍然可以使用 glob,但请注意,这种过滤器也应用于目录,因此如果过滤器匹配,则不会搜索该目录,因此不需要在名称后使用,例如,**。以下示例显示了配置为排除 .git 文件夹内所有文件以及所有隐藏文件和目录的预过滤器。

{
  "paths": [
    "../**/*.[ch]",
    "../Some/*/*.*",
  ],
  "filterPre": ["**/.git", ".*"],
}

如果不应跳过任何隐藏文件夹,只需将此字段设置为空列表 []

后过滤

使用之前的配置文件,我们匹配了所有文件和文件夹,除了隐藏文件。然而,有时在匹配所有路径之后应用一个过滤器也很有用,例如,排除重复出现的特定文件名,或简化字段 paths 中的模式。这可以通过 filterPost 实现。

{
  "paths": [
    "../**/*.[ch]",
    "../Some/*/*.*",
  ],
  "filterPre": ["**/.git", ".*"],
  "filterPost": ["FreeRTOS.h", "**/Hal*/**"],
}

在上面的示例中,任何路径中的 Hal* 文件夹都将被过滤,而无需创建一个复杂的 glob 用于 paths

指定 .clang-format 风格文件和根目录

如果你的项目根目录(假设存在)中没有放置 .clang-format 文件,执行 run-clang-format 而不带任何额外的命令行参数(下面将解释)不会产生预期结果——恰恰相反,因为 clang-format 会检查任何根文件夹,直到可能遇到一个 .clang-format 文件。因此,配置文件允许使用 styleFile 字段指定格式文件,并使用 styleRoot 指定所有路径的公共根目录。

ProjectRoot
│
├── Some
│   └── Path
│       ├── header.h
│       └── source.c
│
└── Settings
    ├── format.json
    ├── style.clang-format
    └── <...>
{
  "paths": [
    "../**/*.[ch]",
    "../Some/*/*.*",
  ],
  "filterPre": ["**/.git", ".*"],
  "filterPost": ["FreeRTOS.h", "**/Hal*/**"],
  "styleFile": "./style.clang-format",
  "styleRoot": "../"
}

styleFile 的名称 或扩展名 必须是 .clang-format。这允许你在同一目录中存储多个 .clang-format 文件,例如,driver.clang-formatapplication.clang-format

当格式化文件时,run-clang-format

  • 将提供的样式文件复制到指定的根目录(如果需要,将其重命名为 .clang-format),
  • 为所有解析的路径执行 clang-format
  • 最后删除临时文件。

只有当你终止工具的执行(例如,通过 CTRL+C)时,它才无法删除临时文件。

备注:指定根目录是必要的,因为无法确定所有路径的共同分母。另外,终止工具的执行将防止删除临时文件,因此可能会使你的工作空间中出现格式文件,因为添加新的 glob 或路径可能会导致不同的根目录。

备注:工具将检查 styleRoot 中是否已存在具有 不同内容.clang-format 文件——如果存在,则中止并报错。如果内容匹配,则工具不会复制或删除任何文件,并且会像没有指定 styleRootstyleFile 一样执行。

如果提供,styleFile 配置将被 --style 命令行参数替换。

指定 clang-format 命令

默认情况下,工具会尝试使用 clang-format 命令格式化所有解析的路径。如果此命令不在你的路径中,或者你使用不同的名称作为可执行文件(例如,clang-format-10),则需要通过命令行参数 --command 或使用配置文件中的 command 字段指定命令或完整路径。

{
  "paths": [
    "../**/*.[ch]",
    "../Some/*/*.*",
  ],
  "filterPre": ["**/.git", ".*"],
  "filterPost": ["FreeRTOS.h", "**/Hal*/**"],
  "styleFile": "./style.clang-format",
  "styleRoot": "../",
  "command": "/path/to/clang-format"
}

与 'paths' 字段中的模式相反,命令可以指定为相对于配置文件的路径、绝对路径或简单的可执行文件名。

styleFile 字段类似,如果提供,此配置将被 --command 命令行参数替换。当指定作为命令行参数的相对路径时,路径是相对于当前 工作目录 解析的。

注意: 确保您的样式文件与您正在使用的 clang-format 版本兼容非常重要。这也是为什么 clang-format 没有随本工具一起安装的主要原因。

注意: 配置文件旨在跨平台。因此,对于 clang-format 可执行文件,可以省略 .exe 扩展名。这也适用于 --command 参数。

命令行参数

所有可用的命令行参数都应该由工具本身充分描述,当提供以下选项之一时:-h, --help, help。此外,可以通过使用 schema 子命令显示配置文件的 JSON 架构。此 JSON 架构还包含上述每个选项的描述。

$ run-clang-format --help
$ run-clang-format help
$ run-clang-format schema

以下简要描述了最重要的选项。

详细程度和 --quiet

最好通过使用 -v 选项来配置详细程度。

  • -v 是默认选项;工具将提供一个包含进度条的“美化打印”输出(由 rust crate indicatif 实现)。

“美化”输出仅在 -v 日志级别下可用,对于其他任何日志级别,工具将切换到调试样式输出。这种输出不适合重定向到文件,因为进度条会重写之前的行。相反,请使用 -vv 调试选项。

  • -vv 切换到“调试”日志级别,提供时间戳和纯顺序输出:不会覆盖任何行,每条消息都会记录到新行。

  • -vvv 及以上切换到“跟踪”日志级别,可能包含更多(可能无关紧要的)消息。这主要适用于在遇到问题时调试工具。

要关闭除错误消息之外的所有输出,请使用 --quiet 选项。这将覆盖 --verbosity 级别。

加速执行

默认情况下,工具将逐个处理每个解析的路径。对于大型项目来说,这可能会相当慢。命令行选项 -j, --jobs 允许指定用于格式化的作业数量。

  • 如果没有指定值,例如,run-clang-format format.json -j,则将使用所有可用的逻辑核心进行格式化。
  • 如果指定了值,例如,run-clang-format format.json -j 3,则工具将仅启动指定数量的作业。

备注: 在较慢的机器上,在正常日志级别下执行时,由于终端可能无法快速重绘新行,进度条可能会闪烁。目前尚无解决此问题的方法。

指定替代样式文件和命令

命令行选项 --style--command 允许指定一个 .clang-format 文件以及用于执行 clang-format 的命令。请参考 .json 配置文件中 字段 styleFile command 的描述。

注意:指定 --style 需要配置字段 styleRoot

检查格式是否与提供的样式匹配

当指定命令行选项 --check 时,此工具将以检查模式执行,即,此工具不会尝试格式化从给定字段 paths 解析出的所有文件,而是执行 clang-format 并使用参数 --dry-run -WError 来检查样式是否与 .clang-format 中的配置匹配。

注意:指定 --check 需要 clang-format 版本 10 或更高,因为 --dry-run 标志只在此版本的 clang-format 中引入。此工具将检查指定命令的版本,如果不支持该选项,则会产生错误。

启用严格的 styleRoot 检查

可以使用命令行选项 --strict-root 确保所有文件都是 styleRoot 目录的兄弟文件,因此将由 clang-format 处理。如果没有此选项,此包装器将简单地传递所有遇到的文件给 clang-format

默认情况下,clang-format 不会处理任何它没有在文件的任何父目录中遇到 .clang-format 文件的文件。请注意,如果文件路径中存在 .clang-format 文件,即使它不是此工具指定的 styleFile,也会执行格式化。

因此,--strict-root 选项确保所有文件都将使用 run-clang-format 调用配置的样式文件进行处理。

注意:应仅将 --strict-root 选项用于不使用符号链接或其他路径的文件树。此类路径可能无法正确解析。

用例

以下场景演示了在开发此工具期间考虑到的用例。

为了简化,以下所有场景中都省略了 --command 选项和配置文件中的 command 字段。

样式文件存在且位于根目录

考虑以下项目,其中所需的 .clang-format 文件已经放置在项目的根目录中。像 vscode 这样的先进编辑器将允许开发者在保存时自动格式化他们的文件。

ProjectRoot
│
├── Some
│   └── Path
│       ├── header.h
│       └── source.c
│
├── Another/Path
│   └── <...>
│
├── format.json
└── .clang-format

以下配置文件允许格式化项目中的所有文件

{
  "paths": [
    "./**/*.[ch]",
  ],
}

在这种情况下,这个工具可能看起来用途有限,因为很少会有文件未格式化。然而,有一个可以请求格式化所有文件的工具,例如在 CI 或拉取请求中,这很有帮助。

样式文件存在但存储在根目录之外

这可能看起来有点奇怪,特别是如果你习惯了 git 子模块的工作方式,但这在大项目中相当常见。

此类项目由多个没有平面文件夹结构的存储库组成。通常避免将存储库克隆到其他存储库中,因此根目录中没有文件

ProjectRoot
│
├── Layers
│   ├── RepoA
│   ├── <...>
│   └── RepoX
│       ├── header.h
│       └── source.c
│
└── SettingsRepo
    ├── format.json
    ├── .clang-format
    └── <...>

没有包装脚本,在这种文件夹结构下,需要将 .clang-format 文件复制到根目录中 - 并且在原始文件更改时更新它。可以使用以下配置文件代替

{
  "paths": [
    "./Layers/**/*.[ch]",
  ],
  "styleFile": ".clang-format",
  "styleRoot": "../"
}

备注:由于工具会检查在 styleRoot 中是否存在具有不同内容的 .clang-format 文件,因此任何手动将 .clang-format 文件复制到根文件夹(例如,与支持 clang-format 的编辑器一起使用)的用户将收到通知,如果他们的样式文件过时(内容不再匹配)。

样式文件在运行时被选中

这可能是理论上的,但仍然可能。可以使用 --style 参数在运行时指定 .clang-format 文件,例如,通过 CI 工具链或另一个构建脚本。

ProjectRoot
│
├── Some
│   └── Path
│       ├── header.h
│       └── source.c
│
└── Settings
    ├── format.json
    ├── .clang-format
    └── <...>

在这种情况下,只需在您的配置文件中指定 pathsstyleRoot 即可。

{
  "paths": [
    "./**/*.[ch]",
  ],
  "styleRoot": "../"
}

潜在问题

多个 .clang-format 文件

如果在不同文件夹中存在其他 .clang-format 文件,.clang-format 将始终在回溯到要格式化的文件时使用它找到的第一个文件。例如,对于 header.h,将使用文件 ProjectRoot/Some/Layer/.clang-format

ProjectRoot
│
├── Some
│   ├── .clang-format
│   └── Path
│       ├── header.h
│       └── source.c
│
└── .clang-format

当使用以下配置执行工具时,Some/Path 中的文件将使用 Some/.clang-format 进行格式化,而 不是 使用配置的样式文件,因为此工具不会扫描任何路径以查找现有的 .clang-format 文件。

{
  "paths": [
    "./Some/**/*.[ch]",
  ],
  "styleFile": ".clang-format",
  "styleRoot": "../"
}

依赖关系

~14–26MB
~383K SLoC