use std::collections::BTreeMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Icons {
own: BTreeMap<String, String>,
generic: BTreeMap<String, String>,
}
impl Icons {
pub fn load_from(data_dirs: &[PathBuf]) -> Icons {
let mut icons = Icons::default();
for dir in data_dirs {
let mime = dir.join("mime");
if let Ok(text) = std::fs::read_to_string(mime.join("icons")) {
add(&mut icons.own, &text);
}
if let Ok(text) = std::fs::read_to_string(mime.join("generic-icons")) {
add(&mut icons.generic, &text);
}
}
icons
}
pub fn names_for(&self, mime: &str, parents: &[String]) -> Vec<String> {
let mut names: Vec<String> = Vec::new();
let mut push = |name: String| {
if !names.contains(&name) {
names.push(name);
}
};
if let Some(own) = self.own.get(mime) {
push(own.clone());
}
push(mime.replace('/', "-"));
for parent in parents.iter().filter(|p| *p != "application/octet-stream") {
push(parent.replace('/', "-"));
}
if let Some(generic) = self.generic.get(mime) {
push(generic.clone());
}
if let Some((media, _)) = mime.split_once('/') {
push(format!("{media}-x-generic"));
}
names
}
}
fn add(into: &mut BTreeMap<String, String>, text: &str) {
for line in text.lines().map(str::trim) {
if line.starts_with('#') {
continue;
}
if let Some((mime, icon)) = line.split_once(':') {
into.entry(mime.trim().to_string()).or_insert_with(|| icon.trim().to_string());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn icons(own: &str, generic: &str) -> Icons {
let mut icons = Icons::default();
add(&mut icons.own, own);
add(&mut icons.generic, generic);
icons
}
#[test]
fn a_type_is_named_by_itself_first_and_its_media_last() {
let names = icons("", "").names_for("image/png", &[]);
assert_eq!(names, ["image-png", "image-x-generic"]);
}
#[test]
fn the_databases_own_answers_come_before_the_guesses() {
let db = icons("application/x-godot-scene:x-godot-scene", "application/xhtml+xml:text-html");
assert_eq!(db.names_for("application/x-godot-scene", &[])[0], "x-godot-scene");
assert_eq!(
db.names_for("application/xhtml+xml", &[]),
["application-xhtml+xml", "text-html", "application-x-generic"]
);
}
#[test]
fn a_parent_type_is_tried_before_the_generic_family() {
let parents = ["text/plain".to_string(), "application/octet-stream".to_string()];
let names = icons("", "").names_for("text/markdown", &parents);
assert_eq!(names, ["text-markdown", "text-plain", "text-x-generic"]);
}
}