1use std::{
2 fmt::{self, Display, Formatter},
3 path::Path,
4};
5
6pub mod dark;
7pub mod light;
8
9pub struct File<'a> {
10 path: &'a Path,
11 pub name: &'a str,
12 ext: Option<String>,
13}
14
15impl File<'_> {
16 pub fn new<'a>(path: &'a Path) -> File<'a> {
17 let path = path;
18 let name = path.file_name().unwrap().to_str().unwrap();
19 let ext = Self::ext(path);
20
21 File { path, name, ext }
22 }
23
24 fn points_to_directory(&self) -> bool {
25 self.path.display().to_string().ends_with('/')
26 }
27
28 fn ext(path: &Path) -> Option<String> {
29 if let Some(ext) = path.extension() {
30 return Some(ext.to_string_lossy().to_string());
31 }
32 let name = path.file_name().map(|f| f.to_string_lossy().to_string())?;
33
34 name.rfind('.').map(|p| name[p + 1..].to_ascii_lowercase())
35 }
36}
37
38pub enum Theme {
39 Light,
40 Dark,
41}
42
43#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
44pub struct FileIcon {
45 pub icon: char,
46 pub color: &'static str,
47}
48
49impl Display for FileIcon {
50 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
51 write!(f, "{}", self.icon)
52 }
53}
54
55impl Default for FileIcon {
56 fn default() -> Self {
57 DEFAULT_FILE_ICON
58 }
59}
60
61const DEFAULT_FILE_ICON: FileIcon = FileIcon {
62 icon: '\u{f016}',
63 color: "#7e8e91",
64};
65
66const DEFAULT_DIR_ICON: FileIcon = FileIcon {
67 icon: '\u{f115}',
68 color: "#7e8e91",
69};
70
71pub fn icon_for_file(file: &File<'_>, theme: Option<Theme>) -> FileIcon {
72 match theme {
73 Some(Theme::Light) => light_icon_for_file(file),
74 Some(Theme::Dark) => dark_icon_for_file(file),
75 None => dark_icon_for_file(file),
76 }
77}
78
79fn dark_icon_for_file(file: &File<'_>) -> FileIcon {
80 if let Some(icon) = dark::ICONS_MAP.get(file.name) {
81 return icon.clone();
82 } else if let Some(extension) = &file.ext {
83 if let Some(icon) = dark::ICONS_MAP.get(extension.as_str()) {
84 return icon.clone();
85 } else if let Some(icon) = dark::ICONS_MAP.get(extension.to_lowercase().as_str()) {
86 return icon.clone();
87 } else {
88 DEFAULT_FILE_ICON
89 }
90 } else if file.points_to_directory() {
91 DEFAULT_DIR_ICON
92 } else {
93 DEFAULT_FILE_ICON
94 }
95}
96
97fn light_icon_for_file(file: &File<'_>) -> FileIcon {
98 if let Some(icon) = light::ICONS_MAP.get(file.name) {
99 return icon.clone();
100 } else if let Some(extension) = &file.ext {
101 if let Some(icon) = light::ICONS_MAP.get(extension.as_str()) {
102 return icon.clone();
103 } else if let Some(icon) = light::ICONS_MAP.get(extension.to_lowercase().as_str()) {
104 return icon.clone();
105 } else {
106 DEFAULT_FILE_ICON
107 }
108 } else if file.points_to_directory() {
109 DEFAULT_DIR_ICON
110 } else {
111 DEFAULT_FILE_ICON
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use std::path::Path;
119
120 #[test]
121 fn test_file_new() {
122 let path = Path::new("example.txt");
123 let file = File::new(path);
124
125 assert_eq!(file.name, "example.txt");
126 assert_eq!(file.ext, Some("txt".to_string()));
127 }
128
129 #[test]
130 fn test_file_new_directory() {
131 let path = Path::new("some_directory/");
132 let file = File::new(path);
133
134 assert_eq!(file.name, "some_directory");
135 assert_eq!(file.ext, None);
136 }
137
138 #[test]
139 fn test_points_to_directory() {
140 let dir_path = Path::new("some_directory/");
141 let file = File::new(dir_path);
142
143 assert!(file.points_to_directory());
144
145 let file_path = Path::new("file.txt");
146 let file = File::new(file_path);
147
148 assert!(!file.points_to_directory());
149 }
150
151 #[test]
152 fn test_file_extension() {
153 let path = Path::new("file.txt");
154 let ext = File::ext(path);
155 assert_eq!(ext, Some("txt".to_string()));
156
157 let no_ext_path = Path::new("file");
158 let ext = File::ext(no_ext_path);
159 assert_eq!(ext, None);
160 }
161
162 #[test]
163 fn test_icon_for_file_with_light_theme() {
164 let path = Path::new("file.txt");
165 let file = File::new(path);
166
167 let icon = icon_for_file(&file, Some(Theme::Light));
168 assert_eq!(icon.icon, '');
169 assert_eq!(icon.color, "#447028");
170 }
171
172 #[test]
173 fn test_icon_for_file_with_dark_theme() {
174 let path = Path::new("file.txt");
175 let file = File::new(path);
176
177 let icon = icon_for_file(&file, Some(Theme::Dark));
178 assert_eq!(icon.icon, '');
179 assert_eq!(icon.color, "#89e051");
180 }
181
182 #[test]
183 fn test_default_icon_for_directory() {
184 let path = Path::new("some_directory/");
185 let file = File::new(path);
186
187 let icon = icon_for_file(&file, Some(Theme::Dark));
188 assert_eq!(icon.icon, '\u{f115}'); assert_eq!(icon.color, "#7e8e91");
190 }
191
192 #[test]
193 fn test_icon_for_file_with_no_theme() {
194 let path = Path::new("file.txt");
195 let file = File::new(path);
196
197 let icon = icon_for_file(&file, None); assert_eq!(icon.icon, '');
199 assert_eq!(icon.color, "#89e051");
200 }
201}