improv: significant memory and cpu usage reduction
This commit is contained in:
parent
4578b5fa25
commit
3dd7647411
7 changed files with 200 additions and 127 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,2 @@
|
||||||
/target
|
/target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,18 +11,17 @@ keywords = ["icons", "gui", "freedesktop"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
rust-ini = "0.20.0"
|
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.56"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
xdg = "2.5.2"
|
xdg = "2.5.2"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
ini_core = "0.2.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
speculoos = "0.11.0"
|
speculoos = "0.11.0"
|
||||||
anyhow = "1.0.79"
|
|
||||||
linicon = "2.3.0"
|
linicon = "2.3.0"
|
||||||
gtk4 = "0.4.7"
|
gtk4 = "0.9"
|
||||||
criterion = "0.3.5"
|
criterion = "0.5"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
||||||
58
src/lib.rs
58
src/lib.rs
|
|
@ -55,6 +55,7 @@ use theme::BASE_PATHS;
|
||||||
|
|
||||||
use crate::cache::{CacheEntry, CACHE};
|
use crate::cache::{CacheEntry, CACHE};
|
||||||
use crate::theme::{try_build_icon_path, THEMES};
|
use crate::theme::{try_build_icon_path, THEMES};
|
||||||
|
use std::io::BufRead;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
|
|
@ -74,15 +75,29 @@ mod theme;
|
||||||
/// "Papirus-Light", "Breeze", "Breeze Dark", "Breeze", "ePapirus", "ePapirus-Dark", "Hicolor"
|
/// "Papirus-Light", "Breeze", "Breeze Dark", "Breeze", "ePapirus", "ePapirus-Dark", "Hicolor"
|
||||||
/// ])
|
/// ])
|
||||||
/// # }
|
/// # }
|
||||||
pub fn list_themes() -> Vec<&'static str> {
|
pub fn list_themes() -> Vec<String> {
|
||||||
let mut themes = THEMES
|
let mut themes = THEMES
|
||||||
.values()
|
.values()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|path| &path.index)
|
.map(|path| &path.index)
|
||||||
.filter_map(|index| {
|
.filter_map(|index| {
|
||||||
index
|
let file = std::fs::File::open(index).ok()?;
|
||||||
.section(Some("Icon Theme"))
|
let mut reader = std::io::BufReader::new(file);
|
||||||
.and_then(|section| section.get("Name"))
|
|
||||||
|
let mut line = String::new();
|
||||||
|
while let Ok(read) = reader.read_line(&mut line) {
|
||||||
|
if read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = line.strip_prefix("Name=") {
|
||||||
|
return Some(name.trim().to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
themes.dedup();
|
themes.dedup();
|
||||||
|
|
@ -99,7 +114,7 @@ pub fn list_themes() -> Vec<&'static str> {
|
||||||
///
|
///
|
||||||
/// assert_eq!(Some("Adwaita"), theme);
|
/// assert_eq!(Some("Adwaita"), theme);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn default_theme_gtk() -> Option<&'static str> {
|
pub fn default_theme_gtk() -> Option<String> {
|
||||||
// Calling gsettings is the simplest way to retrieve the default icon theme without adding
|
// Calling gsettings is the simplest way to retrieve the default icon theme without adding
|
||||||
// GTK as a dependency. There seems to be several ways to set the default GTK theme
|
// GTK as a dependency. There seems to be several ways to set the default GTK theme
|
||||||
// including a file in XDG_CONFIG_HOME as well as an env var. Gsettings is the most
|
// including a file in XDG_CONFIG_HOME as well as an env var. Gsettings is the most
|
||||||
|
|
@ -115,9 +130,23 @@ pub fn default_theme_gtk() -> Option<&'static str> {
|
||||||
let name = name.trim().trim_matches('\'');
|
let name = name.trim().trim_matches('\'');
|
||||||
THEMES.get(name).and_then(|themes| {
|
THEMES.get(name).and_then(|themes| {
|
||||||
themes.first().and_then(|path| {
|
themes.first().and_then(|path| {
|
||||||
path.index
|
let file = std::fs::File::open(&path.index).ok()?;
|
||||||
.section(Some("Icon Theme"))
|
let mut reader = std::io::BufReader::new(file);
|
||||||
.and_then(|section| section.get("Name"))
|
|
||||||
|
let mut line = String::new();
|
||||||
|
while let Ok(read) = reader.read_line(&mut line) {
|
||||||
|
if read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = line.strip_prefix("Name=") {
|
||||||
|
return Some(name.trim().to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -261,7 +290,7 @@ impl<'a> LookupBuilder<'a> {
|
||||||
if self.cache {
|
if self.cache {
|
||||||
if let CacheEntry::Found(icon) = self.cache_lookup(self.theme) {
|
if let CacheEntry::Found(icon) = self.cache_lookup(self.theme) {
|
||||||
return Some(icon);
|
return Some(icon);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then lookup in the given theme
|
// Then lookup in the given theme
|
||||||
|
|
@ -278,11 +307,18 @@ impl<'a> LookupBuilder<'a> {
|
||||||
// Fallback to the parent themes recursively
|
// Fallback to the parent themes recursively
|
||||||
let mut parents = icon_themes
|
let mut parents = icon_themes
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|t| t.inherits())
|
.flat_map(|t| {
|
||||||
|
let file = theme::read_ini_theme(&t.index);
|
||||||
|
|
||||||
|
t.inherits(file.as_ref())
|
||||||
|
.into_iter()
|
||||||
|
.map(String::from)
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
parents.dedup();
|
parents.dedup();
|
||||||
parents.into_iter().find_map(|parent| {
|
parents.into_iter().find_map(|parent| {
|
||||||
THEMES.get(parent).and_then(|parent| {
|
THEMES.get(&parent).and_then(|parent| {
|
||||||
parent.iter().find_map(|t| {
|
parent.iter().find_map(|t| {
|
||||||
t.try_get_icon(self.name, self.size, self.scale, self.force_svg)
|
t.try_get_icon(self.name, self.size, self.scale, self.force_svg)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,4 @@ pub(crate) enum ThemeError {
|
||||||
ThemeIndexNotFound(PathBuf),
|
ThemeIndexNotFound(PathBuf),
|
||||||
#[error("IoError: {0}")]
|
#[error("IoError: {0}")]
|
||||||
IoError(#[from] io::Error),
|
IoError(#[from] io::Error),
|
||||||
#[error("IniError: {0}")]
|
|
||||||
IniError(#[from] ini::Error),
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
use crate::theme::error::ThemeError;
|
use crate::theme::error::ThemeError;
|
||||||
use crate::theme::paths::ThemePath;
|
use crate::theme::paths::ThemePath;
|
||||||
use ini::Ini;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
pub(crate) use paths::BASE_PATHS;
|
pub(crate) use paths::BASE_PATHS;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{Debug, Formatter};
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
mod directories;
|
mod directories;
|
||||||
|
|
@ -16,9 +14,14 @@ type Result<T> = std::result::Result<T, ThemeError>;
|
||||||
|
|
||||||
pub static THEMES: Lazy<BTreeMap<String, Vec<Theme>>> = Lazy::new(get_all_themes);
|
pub static THEMES: Lazy<BTreeMap<String, Vec<Theme>>> = Lazy::new(get_all_themes);
|
||||||
|
|
||||||
|
pub fn read_ini_theme(path: &Path) -> String {
|
||||||
|
std::fs::read_to_string(path).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
pub path: ThemePath,
|
pub path: ThemePath,
|
||||||
pub index: Ini,
|
pub index: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
|
|
@ -29,23 +32,30 @@ impl Theme {
|
||||||
scale: u16,
|
scale: u16,
|
||||||
force_svg: bool,
|
force_svg: bool,
|
||||||
) -> Option<PathBuf> {
|
) -> Option<PathBuf> {
|
||||||
self.try_get_icon_exact_size(name, size, scale, force_svg)
|
let file = read_ini_theme(&self.index);
|
||||||
.or_else(|| self.try_get_icon_closest_size(name, size, scale, force_svg))
|
self.try_get_icon_exact_size(file.as_str(), name, size, scale, force_svg)
|
||||||
|
.or_else(|| self.try_get_icon_closest_size(file.as_str(), name, size, scale, force_svg))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_get_icon_exact_size(
|
fn try_get_icon_exact_size(
|
||||||
&self,
|
&self,
|
||||||
|
file: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
size: u16,
|
size: u16,
|
||||||
scale: u16,
|
scale: u16,
|
||||||
force_svg: bool,
|
force_svg: bool,
|
||||||
) -> Option<PathBuf> {
|
) -> Option<PathBuf> {
|
||||||
self.match_size(size, scale)
|
self.match_size(file, size, scale)
|
||||||
.find_map(|path| try_build_icon_path(name, path, force_svg))
|
.find_map(|path| try_build_icon_path(name, path, force_svg))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_size(&self, size: u16, scale: u16) -> impl Iterator<Item = PathBuf> + '_ {
|
fn match_size<'a>(
|
||||||
let dirs = self.get_all_directories();
|
&'a self,
|
||||||
|
file: &'a str,
|
||||||
|
size: u16,
|
||||||
|
scale: u16,
|
||||||
|
) -> impl Iterator<Item = PathBuf> + 'a {
|
||||||
|
let dirs = self.get_all_directories(file);
|
||||||
|
|
||||||
dirs.filter(move |directory| directory.match_size(size, scale))
|
dirs.filter(move |directory| directory.match_size(size, scale))
|
||||||
.map(|dir| dir.name)
|
.map(|dir| dir.name)
|
||||||
|
|
@ -54,18 +64,19 @@ impl Theme {
|
||||||
|
|
||||||
fn try_get_icon_closest_size(
|
fn try_get_icon_closest_size(
|
||||||
&self,
|
&self,
|
||||||
|
file: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
size: u16,
|
size: u16,
|
||||||
scale: u16,
|
scale: u16,
|
||||||
force_svg: bool,
|
force_svg: bool,
|
||||||
) -> Option<PathBuf> {
|
) -> Option<PathBuf> {
|
||||||
self.closest_match_size(size, scale)
|
self.closest_match_size(file, size, scale)
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|path| try_build_icon_path(name, path, force_svg))
|
.find_map(|path| try_build_icon_path(name, path, force_svg))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn closest_match_size(&self, size: u16, scale: u16) -> Vec<PathBuf> {
|
fn closest_match_size(&self, file: &str, size: u16, scale: u16) -> Vec<PathBuf> {
|
||||||
let dirs = self.get_all_directories();
|
let dirs = self.get_all_directories(file);
|
||||||
|
|
||||||
let mut dirs: Vec<_> = dirs
|
let mut dirs: Vec<_> = dirs
|
||||||
.filter_map(|directory| {
|
.filter_map(|directory| {
|
||||||
|
|
@ -181,7 +192,7 @@ pub(super) fn get_all_themes() -> BTreeMap<String, Vec<Theme>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
pub(crate) fn from_path<P: AsRef<Path>>(path: P, index: Option<&Ini>) -> Option<Self> {
|
pub(crate) fn from_path<P: AsRef<Path>>(path: P, index: Option<&PathBuf>) -> Option<Self> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
let has_index = path.join("index.theme").exists() || index.is_some();
|
let has_index = path.join("index.theme").exists() || index.is_some();
|
||||||
|
|
@ -203,15 +214,6 @@ impl Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Theme {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut content = vec![];
|
|
||||||
self.index.write_to(&mut content).expect("Write error");
|
|
||||||
let content = String::from_utf8_lossy(&content);
|
|
||||||
writeln!(f, "ThemeIndex{{path: {:?}, index: {content:?}}}", self.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::THEMES;
|
use crate::THEMES;
|
||||||
|
|
@ -223,21 +225,20 @@ mod test {
|
||||||
let themes = THEMES.get("Adwaita").unwrap();
|
let themes = THEMES.get("Adwaita").unwrap();
|
||||||
println!(
|
println!(
|
||||||
"{:?}",
|
"{:?}",
|
||||||
themes.iter().find_map(|t| t.try_get_icon_exact_size(
|
themes.iter().find_map(|t| {
|
||||||
"edit-delete-symbolic",
|
let file = crate::theme::read_ini_theme(&t.index);
|
||||||
24,
|
t.try_get_icon_exact_size(file.as_str(), "edit-delete-symbolic", 24, 1, false)
|
||||||
1,
|
})
|
||||||
false
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_get_png_first() {
|
fn should_get_png_first() {
|
||||||
let themes = THEMES.get("hicolor").unwrap();
|
let themes = THEMES.get("hicolor").unwrap();
|
||||||
let icon = themes
|
let icon = themes.iter().find_map(|t| {
|
||||||
.iter()
|
let file = crate::theme::read_ini_theme(&t.index);
|
||||||
.find_map(|t| t.try_get_icon_exact_size("blueman", 24, 1, true));
|
t.try_get_icon_exact_size(file.as_str(), "blueman", 24, 1, true)
|
||||||
|
});
|
||||||
assert_that!(icon).is_some().is_equal_to(PathBuf::from(
|
assert_that!(icon).is_some().is_equal_to(PathBuf::from(
|
||||||
"/usr/share/icons/hicolor/scalable/apps/blueman.svg",
|
"/usr/share/icons/hicolor/scalable/apps/blueman.svg",
|
||||||
));
|
));
|
||||||
|
|
@ -246,9 +247,10 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn should_get_svg_first() {
|
fn should_get_svg_first() {
|
||||||
let themes = THEMES.get("hicolor").unwrap();
|
let themes = THEMES.get("hicolor").unwrap();
|
||||||
let icon = themes
|
let icon = themes.iter().find_map(|t| {
|
||||||
.iter()
|
let file = crate::theme::read_ini_theme(&t.index);
|
||||||
.find_map(|t| t.try_get_icon_exact_size("blueman", 24, 1, false));
|
t.try_get_icon_exact_size(file.as_str(), "blueman", 24, 1, false)
|
||||||
|
});
|
||||||
assert_that!(icon).is_some().is_equal_to(PathBuf::from(
|
assert_that!(icon).is_some().is_equal_to(PathBuf::from(
|
||||||
"/usr/share/icons/hicolor/22x22/apps/blueman.png",
|
"/usr/share/icons/hicolor/22x22/apps/blueman.png",
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,114 @@
|
||||||
use crate::theme::directories::{Directory, DirectoryType};
|
use crate::theme::directories::{Directory, DirectoryType};
|
||||||
use crate::theme::Theme;
|
use crate::theme::Theme;
|
||||||
use ini::Properties;
|
|
||||||
|
fn icon_theme_section(file: &str) -> impl Iterator<Item = (&str, &str)> + '_ {
|
||||||
|
ini_core::Parser::new(file)
|
||||||
|
.skip_while(|item| *item != ini_core::Item::Section("Icon Theme"))
|
||||||
|
.take_while(|item| match item {
|
||||||
|
ini_core::Item::Section(value) => *value == "Icon Theme",
|
||||||
|
_ => true,
|
||||||
|
})
|
||||||
|
.filter_map(|item| {
|
||||||
|
if let ini_core::Item::Property(key, value) = item {
|
||||||
|
Some((key, value?))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DirectorySection<'a> {
|
||||||
|
Property(&'a str, &'a str),
|
||||||
|
EndSection,
|
||||||
|
Section(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sections(file: &str) -> impl Iterator<Item = DirectorySection> {
|
||||||
|
ini_core::Parser::new(file).filter_map(move |item| match item {
|
||||||
|
ini_core::Item::Property(key, Some(value)) => Some(DirectorySection::Property(key, value)),
|
||||||
|
ini_core::Item::Section(section) => Some(DirectorySection::Section(section)),
|
||||||
|
ini_core::Item::SectionEnd => Some(DirectorySection::EndSection),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
pub(super) fn get_all_directories(&self) -> impl Iterator<Item = Directory> {
|
pub(super) fn get_all_directories<'a>(
|
||||||
self.directories()
|
&'a self,
|
||||||
.into_iter()
|
file: &'a str,
|
||||||
.filter_map(|name| self.get_directory(name))
|
) -> impl Iterator<Item = Directory<'a>> + 'a {
|
||||||
.chain(
|
let mut iterator = sections(file);
|
||||||
self.scaled_directories()
|
|
||||||
.into_iter()
|
std::iter::from_fn(move || {
|
||||||
.filter_map(|name| self.get_directory(name)),
|
let mut name = "";
|
||||||
)
|
let mut size = None;
|
||||||
|
let mut max_size = None;
|
||||||
|
let mut min_size = None;
|
||||||
|
let mut threshold = None;
|
||||||
|
let mut scale = None;
|
||||||
|
// let mut context = None;
|
||||||
|
let mut dtype = DirectoryType::default();
|
||||||
|
|
||||||
|
#[allow(clippy::while_let_on_iterator)]
|
||||||
|
while let Some(event) = iterator.next() {
|
||||||
|
match event {
|
||||||
|
DirectorySection::Property(key, value) => {
|
||||||
|
if name.is_empty() || name == "Icon Theme" {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scaled_directories(&self) -> Vec<&str> {
|
match key {
|
||||||
self.get_icon_theme_section()
|
"Size" => size = str::parse(value).ok(),
|
||||||
.and_then(|props| props.get("ScaledDirectories"))
|
"Scale" => scale = str::parse(value).ok(),
|
||||||
.map(|dirs| dirs.split(',').collect())
|
// "Context" => context = Some(value),
|
||||||
.unwrap_or_default()
|
"Type" => dtype = DirectoryType::from(value),
|
||||||
|
"MaxSize" => max_size = str::parse(value).ok(),
|
||||||
|
"MinSize" => min_size = str::parse(value).ok(),
|
||||||
|
"Threshold" => threshold = str::parse(value).ok(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_icon_theme_section(&self) -> Option<&Properties> {
|
DirectorySection::Section(new_name) => {
|
||||||
self.index.section(Some("Icon Theme"))
|
name = new_name;
|
||||||
|
size = None;
|
||||||
|
max_size = None;
|
||||||
|
min_size = None;
|
||||||
|
threshold = None;
|
||||||
|
scale = None;
|
||||||
|
dtype = DirectoryType::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inherits(&self) -> Vec<&str> {
|
DirectorySection::EndSection => {
|
||||||
self.get_icon_theme_section()
|
if name.is_empty() || name == "Icon Theme" {
|
||||||
.and_then(|props| props.get("Inherits"))
|
continue;
|
||||||
.map(|parents| {
|
}
|
||||||
|
|
||||||
|
let size = size.take()?;
|
||||||
|
|
||||||
|
return Some(Directory {
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
scale: scale.unwrap_or(1),
|
||||||
|
// context,
|
||||||
|
type_: dtype,
|
||||||
|
maxsize: max_size.unwrap_or(size),
|
||||||
|
minsize: min_size.unwrap_or(size),
|
||||||
|
threshold: threshold.unwrap_or(2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inherits<'a>(&self, file: &'a str) -> Vec<&'a str> {
|
||||||
|
icon_theme_section(file)
|
||||||
|
.find(|&(key, _)| key == "Inherits")
|
||||||
|
.map(|(_, parents)| {
|
||||||
parents
|
parents
|
||||||
.split(',')
|
.split(',')
|
||||||
// Filtering out 'hicolor' since we are going to fallback there anyway
|
// Filtering out 'hicolor' since we are going to fallback there anyway
|
||||||
|
|
@ -37,47 +117,6 @@ impl Theme {
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn directories(&self) -> Vec<&str> {
|
|
||||||
self.index
|
|
||||||
.section(Some("Icon Theme"))
|
|
||||||
.and_then(|props| props.get("Directories"))
|
|
||||||
.map(|dirs| dirs.split(',').collect())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_directory<'a>(&'a self, name: &'a str) -> Option<Directory<'a>> {
|
|
||||||
self.index.section(Some(name)).map(|props| {
|
|
||||||
let size = props
|
|
||||||
.get("Size")
|
|
||||||
.and_then(|size| str::parse(size).ok())
|
|
||||||
.expect("Size not found for icon");
|
|
||||||
Directory {
|
|
||||||
name,
|
|
||||||
size,
|
|
||||||
scale: props
|
|
||||||
.get("Scale")
|
|
||||||
.and_then(|scale| str::parse(scale).ok())
|
|
||||||
.unwrap_or(1),
|
|
||||||
type_: props
|
|
||||||
.get("Type")
|
|
||||||
.map(DirectoryType::from)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
maxsize: props
|
|
||||||
.get("MaxSize")
|
|
||||||
.and_then(|max| str::parse(max).ok())
|
|
||||||
.unwrap_or(size),
|
|
||||||
minsize: props
|
|
||||||
.get("MinSize")
|
|
||||||
.and_then(|min| str::parse(min).ok())
|
|
||||||
.unwrap_or(size),
|
|
||||||
threshold: props
|
|
||||||
.get("Threshold")
|
|
||||||
.and_then(|thrsh| str::parse(thrsh).ok())
|
|
||||||
.unwrap_or(2),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -88,7 +127,8 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn should_get_theme_parents() {
|
fn should_get_theme_parents() {
|
||||||
for theme in THEMES.get("Arc").unwrap() {
|
for theme in THEMES.get("Arc").unwrap() {
|
||||||
let parents = theme.inherits();
|
let file = crate::theme::read_ini_theme(&theme.index);
|
||||||
|
let parents = theme.inherits(&file);
|
||||||
|
|
||||||
assert_that!(parents).does_not_contain("hicolor");
|
assert_that!(parents).does_not_contain("hicolor");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use dirs::home_dir;
|
use dirs::home_dir;
|
||||||
use ini::Ini;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
|
|
@ -13,7 +12,6 @@ pub(crate) static BASE_PATHS: Lazy<Vec<PathBuf>> = Lazy::new(icon_theme_base_pat
|
||||||
/// Look in $HOME/.icons (for backwards compatibility), in $XDG_DATA_DIRS/icons, in $XDG_DATA_DIRS/pixmaps and in /usr/share/pixmaps (in that order).
|
/// Look in $HOME/.icons (for backwards compatibility), in $XDG_DATA_DIRS/icons, in $XDG_DATA_DIRS/pixmaps and in /usr/share/pixmaps (in that order).
|
||||||
/// Paths that are not found are filtered out.
|
/// Paths that are not found are filtered out.
|
||||||
fn icon_theme_base_paths() -> Vec<PathBuf> {
|
fn icon_theme_base_paths() -> Vec<PathBuf> {
|
||||||
let home_icon_dir = home_dir().expect("No $HOME directory").join(".icons");
|
|
||||||
let mut data_dirs: Vec<_> = BaseDirectories::new()
|
let mut data_dirs: Vec<_> = BaseDirectories::new()
|
||||||
.map(|bd| {
|
.map(|bd| {
|
||||||
let mut data_dirs: Vec<_> = bd
|
let mut data_dirs: Vec<_> = bd
|
||||||
|
|
@ -27,22 +25,25 @@ fn icon_theme_base_paths() -> Vec<PathBuf> {
|
||||||
data_dirs
|
data_dirs
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
data_dirs.push(home_icon_dir);
|
match home_dir().map(|home| home.join(".icons")) {
|
||||||
|
Some(home_icon_dir) => data_dirs.push(home_icon_dir),
|
||||||
|
None => tracing::warn!("No $HOME directory found"),
|
||||||
|
}
|
||||||
data_dirs.into_iter().filter(|p| p.exists()).collect()
|
data_dirs.into_iter().filter(|p| p.exists()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ThemePath(pub PathBuf);
|
pub struct ThemePath(pub PathBuf);
|
||||||
|
|
||||||
impl ThemePath {
|
impl ThemePath {
|
||||||
pub(super) fn index(&self) -> theme::Result<Ini> {
|
pub(super) fn index(&self) -> theme::Result<PathBuf> {
|
||||||
let index = self.0.join("index.theme");
|
let index = self.0.join("index.theme");
|
||||||
|
|
||||||
if !index.exists() {
|
if !index.exists() {
|
||||||
return Err(ThemeError::ThemeIndexNotFound(index));
|
return Err(ThemeError::ThemeIndexNotFound(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Ini::load_from_file(index)?)
|
Ok(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +51,6 @@ impl ThemePath {
|
||||||
mod test {
|
mod test {
|
||||||
use crate::theme::paths::icon_theme_base_paths;
|
use crate::theme::paths::icon_theme_base_paths;
|
||||||
use crate::theme::{get_all_themes, Theme};
|
use crate::theme::{get_all_themes, Theme};
|
||||||
use anyhow::Result;
|
|
||||||
use speculoos::prelude::*;
|
use speculoos::prelude::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -66,10 +66,9 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_read_theme_index() -> Result<()> {
|
fn should_read_theme_index() {
|
||||||
let themes = get_all_themes();
|
let themes = get_all_themes();
|
||||||
let themes: Vec<&Theme> = themes.values().flatten().collect();
|
let themes: Vec<&Theme> = themes.values().flatten().collect();
|
||||||
assert_that!(themes).is_not_empty();
|
assert_that!(themes).is_not_empty();
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue