diff --git a/Cargo.toml b/Cargo.toml index bf5f8cd..bfd0aab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,9 @@ once_cell = "1.10.0" [dev-dependencies] speculoos = "0.9.0" anyhow = "1.0.57" -linicon = "2.3.0" \ No newline at end of file +linicon = "2.3.0" +criterion = "0.3" + +[[bench]] +name = "simple_lookup" +harness = false \ No newline at end of file diff --git a/benches/simple_lookup.rs b/benches/simple_lookup.rs new file mode 100644 index 0000000..3fa2a4d --- /dev/null +++ b/benches/simple_lookup.rs @@ -0,0 +1,17 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use freedesktop_icons::lookup; + +pub fn simple_lookup(c: &mut Criterion) { + c.bench_function("lookup firefox", |b| { + b.iter(|| lookup("firefox").find_one()) + }); +} + +pub fn simple_lookup_linicon(c: &mut Criterion) { + c.bench_function("lookup firefox linicon", |b| { + b.iter(|| linicon::lookup_icon("firefox").next().unwrap().unwrap()) + }); +} + +criterion_group!(benches, simple_lookup, simple_lookup_linicon); +criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs index daa32e8..401f5b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,54 +1,117 @@ -use crate::theme::{try_build_icon_path, FALL_BACK_THEMES, THEMES}; +use crate::theme::{try_build_icon_path, THEMES}; use std::path::PathBuf; pub mod theme; -pub fn lookup(name: &str, size: u16, scale: u16, theme: &str) -> Option { - if let Some(theme) = THEMES.get(theme) { - let icon = theme.try_get_icon(name, size, scale); - if icon.is_some() { - return icon; +pub fn lookup(name: &str) -> LookupBuilder { + LookupBuilder::new(name) +} + +pub struct LookupBuilder<'a> { + name: &'a str, + scale: u16, + size: u16, + theme: Option<&'a str>, +} + +impl<'a> LookupBuilder<'a> { + pub fn with_size(mut self, scale: u16) -> Self { + self.scale = scale; + self + } + + pub fn with_scale(mut self, size: u16) -> Self { + self.size = size; + self + } + + pub fn with_theme<'b: 'a>(mut self, theme: &'b str) -> Self { + self.theme = Some(theme); + self + } + + fn new<'b: 'a>(name: &'b str) -> Self { + Self { + name, + scale: 1, + size: 24, + theme: None, } } - for theme in FALL_BACK_THEMES.iter() { - let icon = theme.try_get_icon(name, size, scale); - if icon.is_some() { - return icon; - } - } + pub fn find_one(self) -> Option { + let name = self.name; + let size = self.size; + let scale = self.scale; - try_build_icon_path(name, "/usr/share/pixmaps") + // We have a theme name, lookup -> fallback - exit + if let Some(theme) = self.theme.and_then(|theme| THEMES.get(theme)) { + let icon = theme.try_get_icon(name, size, scale); + if icon.is_some() { + return icon; + } + + // hicolor fallback + if let Some(fallback) = THEMES.get("hicolor") { + let icon = fallback.try_get_icon(name, size, scale); + if icon.is_some() { + return icon; + } + } + } else { + // No theme let's look everywhere + for theme in THEMES.values() { + let icon = theme.try_get_icon(name, size, scale); + if icon.is_some() { + return icon; + } + } + } + + // Last chance + try_build_icon_path(name, "/usr/share/pixmaps") + } } #[cfg(test)] mod test { use crate::lookup; use speculoos::prelude::*; + use std::path::PathBuf; #[test] - fn compare_to_linincon() { + fn simple_lookup() { + let firefox = lookup("firefox").find_one(); + assert_that!(firefox).is_some(); + } + + #[test] + fn compare_to_linincon_with_theme() { let lin_wireshark = linicon::lookup_icon("wireshark") .next() .unwrap() .unwrap() .path; - let wireshark = lookup("wireshark", 16, 1, "Papirus"); + let wireshark = lookup("wireshark") + .with_size(16) + .with_scale(1) + .with_theme("Papirus") + .find_one(); assert_that!(wireshark).is_some().is_equal_to(lin_wireshark) } #[test] fn compare_to_linicon_in_pixmap() { - let archlinux_logo = linicon::lookup_icon("archlinux-logo").next(); - - assert_that!(archlinux_logo).is_some(); - - let archlinux_logo = lookup("archlinux-logo", 16, 1, "Papirus"); + let archlinux_logo = lookup("archlinux-logo") + .with_size(16) + .with_scale(1) + .with_theme("Papirus") + .find_one(); assert_that!(archlinux_logo) .is_some() - .has_file_name("/usr/share/pixmaps/archlinux-logo.png"); + .is_equal_to(PathBuf::from("/usr/share/pixmaps/archlinux-logo.png")); } } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 895356f..67c1356 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -1,5 +1,5 @@ use crate::theme::error::ThemeError; -use crate::theme::paths::{ThemePath, FALLBACK_PATHS}; +use crate::theme::paths::ThemePath; use ini::Ini; use once_cell::sync::Lazy; use paths::BASE_PATHS; @@ -17,9 +17,6 @@ type Result = std::result::Result; pub static THEMES: Lazy> = Lazy::new(|| get_all_themes().expect("Failed to get theme paths")); -pub static FALL_BACK_THEMES: Lazy> = - Lazy::new(|| fallback_themes().expect("Failed to get theme paths")); - pub struct Theme { path: ThemePath, index: Ini, @@ -28,7 +25,7 @@ pub struct Theme { impl Theme { pub fn try_get_icon(&self, name: &str, size: u16, scale: u16) -> Option { self.try_get_icon_exact_size(name, size, scale) - .or(self.try_get_icon_closest_size(name, size, scale)) + .or_else(|| self.try_get_icon_closest_size(name, size, scale)) } fn try_get_icon_exact_size(&self, name: &str, size: u16, scale: u16) -> Option { @@ -89,7 +86,7 @@ pub(super) fn try_build_icon_path>(name: &str, path: P) -> Option } // Iter through the base paths and get all theme directories -fn get_all_themes() -> Result> { +pub fn get_all_themes() -> Result> { let mut icon_themes = BTreeMap::new(); for theme_base_dir in BASE_PATHS.iter() { for entry in theme_base_dir.read_dir()? { @@ -103,20 +100,6 @@ fn get_all_themes() -> Result> { Ok(icon_themes) } -fn fallback_themes() -> Result> { - let mut icon_themes = vec![]; - for theme_base_dir in FALLBACK_PATHS.iter() { - for entry in theme_base_dir.read_dir()? { - let entry = entry?; - if let Some(theme) = Theme::from_path(entry.path()) { - icon_themes.push(theme); - } - } - } - - Ok(icon_themes) -} - pub fn theme_names() -> Vec<&'static str> { THEMES .values() @@ -124,7 +107,7 @@ pub fn theme_names() -> Vec<&'static str> { .filter_map(|index| { index .section(Some("Icon Theme")) - .and_then(|section| section.get("Name").map(|s| s)) + .and_then(|section| section.get("Name")) }) .collect() } diff --git a/src/theme/parse.rs b/src/theme/parse.rs index 1fcd014..65bb607 100644 --- a/src/theme/parse.rs +++ b/src/theme/parse.rs @@ -4,18 +4,14 @@ use ini::Properties; impl Theme { pub(super) fn get_all_directories(&self) -> Vec { - let dir_names = self.directories().unwrap_or(vec![]); - let mut dirs = vec![]; - for dir in dir_names { - let dir = self.get_directory(dir); - if let Some(dir) = dir { - dirs.push(dir); - } - } - - dirs + self.directories() + .unwrap_or_default() + .iter() + .filter_map(|name| self.get_directory(name)) + .collect() } + // TODO: use me fn scaled_directories(&self) -> Option> { self.get_icon_theme_section() .and_then(|props| props.get("ScaledDirectories")) diff --git a/src/theme/paths.rs b/src/theme/paths.rs index 266a5c9..ddff558 100644 --- a/src/theme/paths.rs +++ b/src/theme/paths.rs @@ -5,15 +5,7 @@ use ini::Ini; use once_cell::sync::Lazy; use std::path::PathBuf; -pub(crate) static BASE_PATHS: Lazy> = Lazy::new(|| icon_theme_base_paths()); -pub(crate) static FALLBACK_PATHS: Lazy> = Lazy::new(|| { - vec![ - data_dir() - .expect("Failed to get DATA_DIR") - .join("icons/hicolor"), - PathBuf::from("usr/share/icons/hicolor"), - ] -}); +pub(crate) static BASE_PATHS: Lazy> = Lazy::new(icon_theme_base_paths); /// Look in $HOME/.icons (for backwards compatibility), in $XDG_DATA_DIRS/icons and in /usr/share/pixmaps (in that order). /// Paths that are not found are filtered out. @@ -56,6 +48,12 @@ mod test { use anyhow::Result; use speculoos::prelude::*; + #[test] + fn should_get_all_themes() { + let themes = get_all_themes().unwrap(); + assert_that!(themes.get("hicolor")).is_some(); + } + #[test] fn should_get_theme_paths_ordered() { let base_paths = icon_theme_base_paths();