-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmod.rs
More file actions
141 lines (127 loc) · 4.55 KB
/
mod.rs
File metadata and controls
141 lines (127 loc) · 4.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Provides icons for filepaths.
use crate::ext::PathExt as _;
use std::path::Path;
use std::sync::LazyLock;
/// Gets an icon for a path.
pub fn for_path<P>(path: P) -> Option<&'static str>
where
P: AsRef<Path>,
{
let path = path.as_ref();
path.file_name()
.and_then(|s| s.to_str())
.and_then(for_filename)
.or_else(|| {
path.double_extension()
.and_then(|(prefix, suffix)| {
prefix
.to_str()
.and_then(|prefix| suffix.to_str().map(|suffix| (prefix, suffix)))
})
.and_then(for_double_extension)
})
.or_else(|| {
path.extension()
.and_then(|extension| extension.to_str())
.and_then(for_extension)
})
.or_else(|| for_filename_glob(path))
}
/// Gets an icon for a filename.
fn for_filename(filename: &str) -> Option<&'static str> {
// NOTE These should be in alphabetical order and ignoring any leading `.` for
// easier code review.
let icon = match filename {
"CONTRIBUTING.md" => shared::DOC,
".editorconfig" => "\u{e652}", //
".git" | ".gitattributes" | ".gitignore" | ".gitmodules" | ".git-blame-ignore-revs" => {
"\u{e702}"
} //
".github" => "\u{e709}", //
"LICENCE" | "LICENSE" | "licence" | "license" => shared::LICENSE,
"package-lock.json" | "pnpm-lock.yaml" => shared::LOCK,
"README" | "README.md" => shared::DOC,
".vscode" => "\u{e8da}", //
_ => return None,
};
Some(icon)
}
/// Gets an icon for a file extension.
fn for_extension(extension: &str) -> Option<&'static str> {
// NOTE These should be in alphabetical order for easier code review.
let icon = match extension {
"7z" | "tar" | "zip" => shared::ARCHIVE,
"bak" => "\u{f006f}", //
"cfg" => "\u{e615}", //
"gif" | "jpeg" | "jpg" | "png" => shared::IMAGE,
"lock" => shared::LOCK,
"sqlite" | "sqlite3" => shared::DATABASE,
_ => return None,
};
Some(icon)
}
/// Gets an icon for the double extension.
fn for_double_extension(double_extension: (&str, &str)) -> Option<&'static str> {
let color = match double_extension {
("tar", "gz") => shared::ARCHIVE,
_ => return None,
};
Some(color)
}
/// Gets an icon based on a matching glob for a path.
fn for_filename_glob(path: &Path) -> Option<&'static str> {
use glob::{MatchOptions, Pattern};
/// Maps a raw glob pattern to an icon with `(glob, icon)` tuples.
const RAW_MAPPINGS: &[(&str, &str)] = &[("LICEN[CS]E-*", shared::LICENSE)];
const OPTIONS: MatchOptions = MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
};
/// The compiled glob-to-icon mappings.
static COMPILED_MAPPINGS: LazyLock<Vec<(Pattern, &'static str)>> = LazyLock::new(|| {
RAW_MAPPINGS
.iter()
.map(|(raw, icon)| (Pattern::new(raw).expect("Pattern should be valid"), *icon))
.collect()
});
// NOTE This may receive a path with `./`, so we'll clean to just the prefix.
path.file_name().and_then(|s| s.to_str()).and_then(|path| {
COMPILED_MAPPINGS
.iter()
.find_map(|(glob, icon)| glob.matches_with(path, OPTIONS).then_some(*icon))
})
}
/// Icons that represent one file type, but have multiple filenames and/or extensions
/// for that file type.
mod shared {
/// Icon for archive files, like `.zip` or `.tar.gz`.
pub const ARCHIVE: &str = "\u{ea98}"; //
/// Icon for database files.
pub const DATABASE: &str = "\u{e706}"; //
/// Icon for documentation files, like READMEs.
pub const DOC: &str = "\u{eaa4}"; //
/// Icon for license files.
pub const LICENSE: &str = "\u{e60a}"; //
/// Icon for lock files.
pub const LOCK: &str = "\u{e672}"; //
/// Icon for image files.
pub const IMAGE: &str = "\u{f1c5}"; //
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case("example.tar.gz", Some(shared::ARCHIVE))]
#[case("example.gif", Some(shared::IMAGE))]
#[case("example.jpeg", Some(shared::IMAGE))]
#[case("example.jpg", Some(shared::IMAGE))]
#[case("example.png", Some(shared::IMAGE))]
fn test_for_path<P>(#[case] path: P, #[case] expected: Option<&str>)
where
P: AsRef<Path>,
{
assert_eq!(expected, for_path(path));
}
}