1//! The icon a file or folder gets in the explorer: one of Jerry's pixel 2//! icons (third-party/jerrys-pixel-icons), chosen the way the icon theme 3//! chooses, by whole name first and then by extension. `icons_theme.rs` is 4//! the theme's own map; `OURS` adds the few names this repository has that 5//! the theme has never met. 6 7#[path = "icons_theme.rs"] 8mod theme; 9 10/// Names and extensions the theme has no entry for, given the nearest icon 11/// it has. A whole name comes before an extension, and these before the 12/// theme's. 13const OURS: &[(&str, &str)] = &[ 14 // What an agent is told: the robot. 15 ("CLAUDE.md", "file-robot"), 16 ("AGENTS.md", "file-robot"), 17 ("MEMORY.md", "file-robot"), 18 (".envrc", "file-env"), 19 ("BUCK", "file-conf"), 20 (".buckconfig", "file-conf"), 21 ("lock", "file-conf"), 22 ("nix", "file-conf"), 23 ("woff2", "file-ase"), 24 ("gif", "file-png"), 25]; 26 27fn found(table: &[(&'static str, &'static str)], key: &str) -> Option<&'static str> { 28 table.iter().find(|(name, _)| *name == key).map(|(_, icon)| *icon) 29} 30 31/// The icon for the file called `name`. 32pub fn file(name: &str) -> &'static str { 33 let extension = name.rsplit_once('.').map_or("", |(_, extension)| extension); 34 found(OURS, name) 35 .or_else(|| found(theme::NAMES, name)) 36 .or_else(|| found(OURS, extension)) 37 .or_else(|| found(theme::EXTENSIONS, &extension.to_ascii_lowercase())) 38 .unwrap_or(theme::FILE) 39} 40 41/// The icon for the folder called `name`, shut. Open, it is the same with 42/// `-open` (`open`). 43pub fn folder(name: &str) -> &'static str { 44 found(theme::FOLDERS, &name.to_ascii_lowercase()).unwrap_or(theme::FOLDER) 45} 46 47/// A repository: the theme's git folder. It is asked for by this name and 48/// not through `folder`, because no repository here is a folder called 49/// `.git`. 50pub const REPOSITORY: &str = "folder-git"; 51 52/// A folder's icon when it is open. 53pub fn open(folder: &str) -> String { 54 format!("{folder}-open") 55} 56 57/// Every icon a name can be given, for a test that each one is a file. 58pub fn all() -> Vec<String> { 59 let files = OURS.iter().chain(theme::NAMES).chain(theme::EXTENSIONS).map(|(_, icon)| (*icon).to_owned()).chain([theme::FILE.to_owned()]); 60 let folders = theme::FOLDERS.iter().map(|(_, icon)| *icon).chain([theme::FOLDER, REPOSITORY]).flat_map(|icon| [icon.to_owned(), open(icon)]); 61 let mut all: Vec<String> = files.chain(folders).collect(); 62 all.sort(); 63 all.dedup(); 64 all 65} 66 67#[cfg(test)] 68mod tests { 69 use super::*; 70 71 #[test] 72 fn a_name_gets_the_icon_the_theme_would_give_it() { 73 for (name, icon) in [ 74 ("lib.rs", "file-rust"), 75 ("Cargo.toml", "file-toml"), 76 ("README.md", "file-readme"), 77 ("notes.md", "file-md"), 78 ("CLAUDE.md", "file-robot"), 79 (".gitignore", "file-git"), 80 ("LICENSE", "file-license"), 81 ("page.CSS", "file-css"), 82 ("flake.lock", "file-conf"), 83 ("emoji.woff2", "file-ase"), 84 ("who-knows", "file-txt"), 85 ] { 86 assert_eq!(file(name), icon, "{name}"); 87 } 88 assert_eq!(folder("src"), "folder-src"); 89 assert_eq!(folder("Docs"), "folder-docs"); 90 assert_eq!(folder("packages"), "folder-folder"); 91 assert_eq!(open(folder("src")), "folder-src-open"); 92 assert_eq!((REPOSITORY, open(REPOSITORY).as_str()), ("folder-git", "folder-git-open")); 93 } 94 95 #[test] 96 fn every_icon_a_name_can_get_is_a_file() { 97 let folder = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../third-party/jerrys-pixel-icons"); 98 for icon in all() { 99 assert!(folder.join(format!("{icon}.png")).is_file(), "{icon}.png is not in third-party/jerrys-pixel-icons"); 100 } 101 } 102}