#glob-pattern #directory-tree #glob #file-path #pattern #regex

wax

具有观点且可移植的 globs,可以与路径和目录树匹配

7 个版本 (破坏性)

0.6.0 2023 年 9 月 30 日
0.5.0 2022 年 5 月 26 日
0.4.0 2022 年 1 月 21 日
0.3.0 2021 年 12 月 13 日
0.0.0 2021 年 8 月 27 日

#45 in 文件系统

Download history 5424/week @ 2024-04-08 4696/week @ 2024-04-15 4702/week @ 2024-04-22 5587/week @ 2024-04-29 4196/week @ 2024-05-06 3924/week @ 2024-05-13 4644/week @ 2024-05-20 4112/week @ 2024-05-27 4156/week @ 2024-06-03 3772/week @ 2024-06-10 3587/week @ 2024-06-17 4221/week @ 2024-06-24 3919/week @ 2024-07-01 4615/week @ 2024-07-08 4845/week @ 2024-07-15 4149/week @ 2024-07-22

17,839 每月下载量
用于 84 Crates(33 个直接使用)

MIT 许可证

255KB
5K SLoC

Wax

Wax 是一个 Rust 库,提供具有观点且可移植的 globs,可以与文件路径和目录树匹配。Globs 使用熟悉的语法,并支持强调组件边界的表达性功能。

GitHub docs.rs crates.io

基本用法

将路径与 globs 匹配

use wax::{Glob, Pattern};

let glob = Glob::new("*.png").unwrap();
assert!(glob.is_match("logo.png"));

将路径与 globs 匹配,带有匹配的文本(捕获)

use wax::{CandidatePath, Glob, Pattern};

let glob = Glob::new("**/{*.{go,rs}}").unwrap();

let path = CandidatePath::from("src/main.go");
let matched = glob.matched(&path).unwrap();

assert_eq!("main.go", matched.get(2).unwrap());

将目录树与 globs 匹配

use wax::Glob;

let glob = Glob::new("**/*.{md,txt}").unwrap();
for entry in glob.walk("doc") {
    let entry = entry.unwrap();
    // ...
}

将目录树与带有否定的 globs 匹配

use wax::{Glob, LinkBehavior};

let glob = Glob::new("**/*.{md,txt}").unwrap();
for entry in glob
    .walk_with_behavior("doc", LinkBehavior::ReadTarget)
    .not(["**/secret/**"])
    .unwrap()
{
    let entry = entry.unwrap();
    // ...
}

将路径与多个 globs 匹配

use wax::{Glob, Pattern};

let any = wax::any([
    "src/**/*.rs",
    "tests/**/*.rs",
    "doc/**/*.md",
    "pkg/**/PKGBUILD",
]).unwrap();
assert!(any.is_match("src/token/mod.rs"));

更多详细信息请见下文。

构造

Globs 编码为 UTF-8 字符串,称为 glob 表达式,类似于由分隔符分隔的名义组件组成的 Unix 路径。Wax API 中最基本的数据类型是 Glob,它通过固有函数或标准转换特性从 glob 表达式构建。大多数 API 中尽可能借用数据,但可以使用 into_owned 方法将数据复制到拥有的实例中。

use wax::Glob;

let glob = Glob::new("site/img/logo.svg").unwrap();

不仅 API 设计用于可移植性,glob 表达式也是如此。无论平台或操作系统如何,globs 都支持相同的功能并使用相同的语法。glob 表达式与路径不同,它们在每个平台上的语法和功能都不同。

在 glob 表达式中,正斜杠 / 是唯一的路径组件分隔符,反斜杠 \ 是禁止的(反斜杠用于转义序列,但不支持字面序列 \\)。这意味着无法在名义路径组件中表示 \,但通常禁止此类表示,并且其不使用可以避免混淆。

全局模式强制执行各种关于元字符、模式和组件边界的规则,拒绝无意义表达式。虽然这些规则有时会使全局模式表达式稍微难以编写,但它们也使得全局模式表达式更加一致、易于推理,并且更不容易出错。

模式

全局模式类似于Unix路径,但还支持可以与路径和目录树匹配的模式。模式使用与Unix shell和类似git等工具中的globbing类似的语法,尽管存在一些重要差异。

use wax::Glob;

let glob = Glob::new("**/*.{go,rs}").unwrap();
assert!(glob.is_match("src/lib.rs"));

模式形成可以用来提取匹配文本的捕获(如在许多正则表达式引擎中看到的那样)。在上面的示例中,有三个模式可以查询匹配文本:**/*{go,rs}。每个全局表达式都有一个隐含的捕获,用于完整匹配的文本。

全局模式使用一致且具有个人观点的格式,模式不可配置;特定全局模式的语义始终相同。例如,* 从不跨越组件边界匹配。组件是路径和文件系统树的重要组成部分,只有树通配符**(见下文)隐式地跨越它们。

通配符

通配符匹配路径中的任意文本量,是全局模式提供的基本模式(也可能是最熟悉的)。

零个或多个通配符*$匹配组件内任意数量的任意字符(从不路径分隔符)。零个或多个通配符不能相邻。通配符*是贪婪的,会匹配尽可能长的文本,而通配符$是懒惰的,会匹配尽可能短的文本。当跟在字面量后面时,通配符*会在最后一个字面量的出现处停止,而通配符$会在第一个出现处停止。

恰好一个通配符?匹配组件内的任意单个字符(从不路径分隔符)。恰好一个通配符不会自动分组,因此连续通配符的模式,如???,会为每个?通配符形成不同的捕获。一种替代方法可以将恰好一个通配符组合成一个单独的捕获,例如{???}

树通配符**匹配零个或多个组件中的任意字符。这是唯一一个隐式匹配任意组件边界的模式;所有其他模式都不会隐式地跨越组件边界。当一个树通配符参与匹配并且不终止模式时,其捕获的文本包括尾部分隔符。如果一个树通配符没有参与匹配,那么其捕获的文本是一个空字符串。

树形通配符必须由正斜杠或结束符(表达式的开始和/或结束)分隔。 树形通配符和路径分隔符是不同的,任何相邻的正斜杠形成树形通配符都将一起解析。树形通配符中的根正斜杠是有意义的,glob表达式**/*.txt/**/*.txt不同,前者是相对的(没有根),后者有根。

如果glob表达式仅由树形通配符组成,则它匹配任何路径和任何目录树的全部内容,包括根。

字符类

字符类匹配组件中一组字符和范围中的任何单个字符(永不匹配路径分隔符)。类由方括号[...]分隔。单个字符字面量按原样指定,例如[ab]来匹配ab。字符范围由两个由连字符分隔的字符组成,例如[x-z]来匹配xyz。字符类匹配字符正好,并且总是区分大小写,所以表达式[ab]{a,b}不一定相同。

可以在单个字符类中使用任意数量的字符字面量和范围。例如,[qa-cX-Z]匹配qabcXYZ中的任何一个。

可以通过在类模式的开头包含一个感叹号!来否定字符类。例如,[!a]匹配除a以外的任何字符。这些是唯一支持否定的模式。

可以使用字符类来转义元字符,如*$等,尽管glob也支持通过反斜杠\进行转义。要匹配字符类中的控制字符[]-,它们必须通过反斜杠转义,例如[a\-]来匹配a-

字符类在特定平台上具有显著的特定行为,因为它们匹配原生路径中的任意字符,但从不匹配路径分隔符。这意味着,如果字符类只包含给定平台上的路径分隔符,则该字符类被视为空,不匹配任何内容。例如,在表达式 a[/]b 中,字符类 [/] 在 Unix 和 Windows 上都不匹配。这类字符类不会被拒绝,因为任意字符的作用取决于平台。实际上,这种情况很少成为问题,但 应避免此类模式

字符类本身具有有限的用途,但与 重复 结合得很好。

替代方案

替代方案匹配由花括号 {...,...} 定界的一个或多个以逗号分隔的子模式。例如,{a?c,x?z,foo} 匹配子模式 a?cx?zfoo 中的任何一个。替代方案可以任意嵌套并与其他 重复 结合。

替代方案形成一个捕获组,无论其子模式的内容如何。此捕获是由子模式的完整匹配形成的,因此如果替代方案 {a?c,x?z} 匹配 abc,则捕获的文本将是 abc而不是 b)。替代方案可以用于使用单个子模式分组捕获,例如 {*.{go,rs}} 来捕获具有特定扩展名的整个文件名或 {???} 来分组一系列正好一个通配符。

替代方案必须考虑相邻规则和邻近模式。例如,*{a,b*} 是允许的,但 *{a,*b} 是不允许的。此外,它们不能包含由单个树通配符 ** 组成的子模式,也不能作为 glob 表达式的根,因为这可能导致表达式匹配或遍历重叠的树。

重复

重复匹配子全局指定的次数。重复由尖括号和分隔冒号界定 <...:...>,其中子全局位于冒号之前,可选的界限指定位于其后。例如,<a*/:0,> 匹配子全局 a*/ 零次或多次。虽然不像树形 通配符 那样隐式,但重复可以跨越组件边界(并且可以包含树形通配符)。重复可以是任意嵌套并可以用 备选 组合。

界限指定由分隔的上下界限组成,用逗号 , 分隔,例如 :1,4 匹配一次或四次。上限是可选的,可以省略。例如,:1, 匹配一次或多次(注意尾随逗号 ,)。单个界限是收敛的,因此 :3 匹配正好三次(上下界限都是三)。如果没有指定上下界限,则子全局匹配一次或多次,因此 <a:><a:1,> 是等价的。同样,如果省略了冒号 :,则子全局匹配零次或多次,因此 <a><a:0,> 是等价的。

重复形成一个单独的捕获组,无论其子全局的内容如何。捕获由子全局的完整匹配形成。如果重复 <abc/> 匹配 abc/abc/,则捕获的文本将是 abc/abc/

重复与字符类结合使用效果良好。最常见的glob表达式如下:{????},但更具体的表达式<[0-9]:4>将进一步限制匹配字符为数字,例如。重复也可以更加简洁,如<?:8>。此外,重复还可以形成树表达式,进一步限制组件,例如<[!.]*/>[!.]*以匹配任何组件中不包含前导点.的路径。

重复必须考虑相邻规则和邻近模式。例如,a/<b/**:1,>是允许的,但<a/**:1,>/b则不允许。此外,它们不能包含由单个分隔符/、单个零或更多通配符*$组成的子glob,也不能包含单个树通配符**。具有零下界的重复不能作为glob表达式的根,因为这可能导致表达式匹配或遍历重叠的树。

组合器

可以使用any组合器将glob模式组合并匹配在一起。any接受一个IntoIterator,其项是编译后的Patternstr切片。输出是一个Any,它实现了Pattern并高效地匹配其输入模式中的任何一个。

use wax::{Glob, Pattern};

let any = wax::any(["**/*.txt", "src/**/*.rs"]).unwrap();
assert!(any.is_match("src/lib.rs"));

替代不同,Any支持具有重叠树(根和未根表达式)的模式。然而,组合器只能执行逻辑匹配,并且无法将Any与目录树匹配(如使用Glob::walk)。

标志和大小写敏感性

标志切换glob的匹配行为。重要的是,标志是glob表达式的组成部分,而不是API的一部分。行为在标志之后立即切换,其顺序与glob表达式中的出现顺序相同。标志由带有前导问号的括号分隔((?...)),并且可以在glob表达式的任何位置出现,只要它们不分割树形通配符(例如,a/*(?i)*是不允许的)。每个标志由一个字符表示,可以通过在相应的字符之前加一个减号来取消其作用-。标志的切换顺序与它们在(?...)中出现的顺序相同。

唯一支持的标志是不区分大小写的标志i。默认情况下,glob表达式使用与目标平台文件系统API相同的区分大小写的方式(Unix上是区分大小写,Windows上是不区分大小写),但可以使用i来明确切换。例如,(?-i)photos/**/*.(?i){jpg,jpeg}匹配photos目录下具有区分大小写的基本名和区分大小写扩展名jpgjpeg的文件路径。

Wax在通过Glob::partition对glob表达式进行分区时,考虑了文字、它们的配置的大小写敏感性和目标平台文件系统API的大小写敏感性。没有标志的glob表达式的分区不受影响。

错误和诊断

GlobError类型表示在构建模式或遍历目录树时可能发生的错误条件。GlobError及其子错误通过thiserror实现了标准的ErrorDisplay特性。

Wax可以选择与miette crate集成,该crate可以用于捕获和显示诊断信息。这对于向用户提供glob表达式很有用。当启用时,错误类型实现Diagnostic特性。

Error: wax::glob::adjacent_zero_or_more

  x malformed glob expression: adjacent zero-or-more wildcards `*` or `$`
   ,----
 1 | doc/**/*{.md,.tex,*.txt}
   :        |^^^^^^^^|^^^^^^^
   :        |        | `-- here
   :        |        `-- in this alternative
   :        `-- here
   `----

Wax还提供了检查API,允许代码查询glob元数据,如捕获和变体。

use wax::Glob;

let glob = Glob::new("videos/**/{*.{mp4,webm}}").unwrap();
assert_eq!(2, glob.captures().count());

Cargo特性

Wax提供了一些可选的集成和特性,可以通过以下描述的Cargo特性进行切换。

特性 默认 依赖关系 描述
miette miettetardar miette集成,并提供Diagnostic错误类型和报告。
walk walkdir 提供与目录树匹配glob的API。

可以在crate的Cargo.toml清单中配置特性。

[dependency.wax]
version = "^0.x.0"
default-features = false
features = [
    "miette",
    "walk"
]

不支持的路径特性

任何既不被识别为分隔符也不被识别为模式的组件都被解释为字面量。与严格规则结合使用时,这意味着某些特定平台的路径功能不能直接在glob中使用。这种限制是设计上的,对于某些用例可能需要额外的代码来弥合这一差距。

分区和语义字面量

Glob不支持当前或父目录的概念。路径组件...被解释为字面量,并且只匹配具有相应组件的路径(即使在Unix和Windows上)。例如,glob src/../*.rs 匹配路径 src/../lib.rs,但不匹配语义等效的路径 lib.rs

当父目录组件在glob中跟随模式时,它们的意义不明确,并且用途也大大减少。然而,当它们作为变体模式的前缀时(即作为前缀),这些组件是直观的,并且对于跳出工作目录非常重要。例如,glob ../src/**/*.rs 的意图比glob src/**/../*.rs 更明显。然而,如上所述,第一个glob只会匹配字面路径组件 ..,而不会匹配用父目录替换此处的路径。

Glob::partition可用于隔离模式之前的语义组件,并将语义路径操作应用于它们(即..)。Glob::partition将glob分成一个不变的PathBuf前缀和一个可变的Glob后缀。在这里,不变意味着分区不包含glob模式,这些模式在目标平台文件系统API中使用等效的本地路径解析时会有所不同。前缀可以与glob结合使用。

use dunce; // Avoids UNC paths on Windows.
use std::path::Path;
use wax::{Glob, Pattern};

let path: &Path = /* ... */ // Candidate path.

let directory = Path::new("."); // Working directory.
let (prefix, glob) = Glob::new("../../src/**").unwrap().partition();
let prefix = dunce::canonicalize(directory.join(&prefix)).unwrap();
if dunce::canonicalize(path)
    .unwrap()
    .strip_prefix(&prefix)
    .map(|path| glob.is_match(path))
    .unwrap_or(false)
{
    // ...
}

此外,可以使用Glob::has_semantic_literals来检测glob中具有目标平台特殊语义的字面量组件。当启用miette功能时,此类字面量作为警告报告。

use wax::Glob;

let glob = Glob::new("../**/src/**/main.rs").unwrap();
assert!(glob.has_semantic_literals());

方案和前缀

虽然glob可以有根,但不能包含方案或Windows路径前缀。例如,Windows的UNC共享路径 \\server\share\src 不能直接表示为glob。

这可能有限制,但Wax的设计明确禁止了这一点:Windows前缀和其他卷组件是不可移植的。相反,当需要这样做时,必须使用额外的本地路径或工作目录,例如Nym提供的--tree选项。在大多数情况下,glob是相对于此类工作目录应用。

非规范约束

Glob是严格规范的,不支持任何非规范约束。在glob表达式中,无法直接根据额外的元数据(如修改时间戳)过滤路径或文件。然而,用户代码可以查询匹配路径的任何此类元数据,或者使用FileIterator::filter_tree在匹配目录树时有效地应用此类过滤。

有关此类附加功能,包括使用匹配文本进行元数据和转换,请参阅Nym

编码

Glob仅对UTF-8编码的文本进行操作。然而,这种编码并不是所有平台上路径的编码。Wax使用CandidatePath类型通过使用Unicode替换代码点进行有损转换来重新编码本地路径,这些代码点在路径的某个部分无法表示为有效的UTF-8时使用。在实践中,大多数路径都可以无损地编码为UTF-8,但这意味着Wax无法匹配或捕获一些字面字节字符串。

稳定性

在撰写本文时,Wax处于实验性和不稳定状态。在0.y.z系列版本之间,glob表达式语法和语义可能会在不警告和不弃用的情况下发生变化。

依赖关系

~5-14MB
~155K SLoC