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
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}
与a
或b
匹配,其中a
和b
是任意的通配模式。不支持嵌套{...}
。[ab]
与a
或b
匹配,其中a
和b
是 字符。使用[!ab]
来匹配任何 除了a
和b
之外的字符。- 元字符,如
*
和?
,可以用字符类符号来转义。例如,[*]
匹配*
。 - 反斜杠
\
会转义 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-format
和 application.clang-format
。
当格式化文件时,run-clang-format
将
- 将提供的样式文件复制到指定的根目录(如果需要,将其重命名为
.clang-format
), - 为所有解析的路径执行
clang-format
, - 最后删除临时文件。
只有当你终止工具的执行(例如,通过 CTRL+C)时,它才无法删除临时文件。
备注:指定根目录是必要的,因为无法确定所有路径的共同分母。另外,终止工具的执行将防止删除临时文件,因此可能会使你的工作空间中出现格式文件,因为添加新的 glob 或路径可能会导致不同的根目录。
备注:工具将检查
styleRoot
中是否已存在具有 不同内容 的.clang-format
文件——如果存在,则中止并报错。如果内容匹配,则工具不会复制或删除任何文件,并且会像没有指定styleRoot
和styleFile
一样执行。
如果提供,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
└── <...>
在这种情况下,只需在您的配置文件中指定 paths
和 styleRoot
即可。
{
"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