perf: drop ini-core crate for simpler parser using memchr and bstr
This commit is contained in:
parent
44edef9673
commit
8b045f90c7
6 changed files with 413 additions and 94 deletions
10
Cargo.toml
10
Cargo.toml
|
|
@ -10,12 +10,14 @@ readme = "README.md"
|
||||||
keywords = ["icons", "gui", "freedesktop"]
|
keywords = ["icons", "gui", "freedesktop"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bstr = "1.12.1"
|
||||||
|
btoi = "0.5.0"
|
||||||
dirs = "6.0"
|
dirs = "6.0"
|
||||||
thiserror = "2.0"
|
memchr = "2.7.6"
|
||||||
xdg = "3.0"
|
|
||||||
tracing = "0.1.41"
|
|
||||||
ini_core = "0.2.0"
|
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
|
thiserror = "2.0"
|
||||||
|
tracing = "0.1.41"
|
||||||
|
xdg = "3.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
speculoos = "0.13.0"
|
speculoos = "0.13.0"
|
||||||
|
|
|
||||||
22
src/lib.rs
22
src/lib.rs
|
|
@ -133,7 +133,7 @@ pub fn default_theme_gtk() -> Option<String> {
|
||||||
if gsettings.status.success() {
|
if gsettings.status.success() {
|
||||||
let name = String::from_utf8(gsettings.stdout).ok()?;
|
let name = String::from_utf8(gsettings.stdout).ok()?;
|
||||||
let name = name.trim().trim_matches('\'');
|
let name = name.trim().trim_matches('\'');
|
||||||
THEMES.get(name).and_then(|themes| {
|
THEMES.get(name.as_bytes()).and_then(|themes| {
|
||||||
themes.first().and_then(|path| {
|
themes.first().and_then(|path| {
|
||||||
let file = std::fs::File::open(&path.index)
|
let file = std::fs::File::open(&path.index)
|
||||||
.and_then(|file| unsafe { Mmap::map(&file) })
|
.and_then(|file| unsafe { Mmap::map(&file) })
|
||||||
|
|
@ -323,8 +323,8 @@ impl<'a> LookupBuilder<'a> {
|
||||||
|
|
||||||
// Then lookup in the given theme
|
// Then lookup in the given theme
|
||||||
THEMES
|
THEMES
|
||||||
.get(self.theme)
|
.get(self.theme.as_bytes())
|
||||||
.or_else(|| THEMES.get("hicolor"))
|
.or_else(|| THEMES.get("hicolor".as_bytes()))
|
||||||
.and_then(|icon_themes| {
|
.and_then(|icon_themes| {
|
||||||
let icon = icon_themes
|
let icon = icon_themes
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -337,13 +337,13 @@ impl<'a> LookupBuilder<'a> {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// Search the cosmic icon theme
|
// Search the cosmic icon theme
|
||||||
.or_else(|| self.search_inherited_theme(searched_themes, "Cosmic"))
|
.or_else(|| self.search_inherited_theme(searched_themes, "Cosmic".as_bytes()))
|
||||||
// Search the hicolor icon theme if it was not previously searched
|
// Search the hicolor icon theme if it was not previously searched
|
||||||
.or_else(|| self.search_inherited_theme(searched_themes, "hicolor"))
|
.or_else(|| self.search_inherited_theme(searched_themes, "hicolor".as_bytes()))
|
||||||
// GNOME applications may rely on the gnome theme
|
// GNOME applications may rely on the gnome theme
|
||||||
.or_else(|| self.search_inherited_theme(searched_themes, "gnome"))
|
.or_else(|| self.search_inherited_theme(searched_themes, "gnome".as_bytes()))
|
||||||
// Ubuntu applications may require Yaru
|
// Ubuntu applications may require Yaru
|
||||||
.or_else(|| self.search_inherited_theme(searched_themes, "Yaru"))
|
.or_else(|| self.search_inherited_theme(searched_themes, "Yaru".as_bytes()))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
for theme_base_dir in BASE_PATHS.iter() {
|
for theme_base_dir in BASE_PATHS.iter() {
|
||||||
let mut path = theme_base_dir.clone();
|
let mut path = theme_base_dir.clone();
|
||||||
|
|
@ -430,13 +430,9 @@ impl<'a> LookupBuilder<'a> {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(file) = std::str::from_utf8(file.as_ref()) else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Search all inherited themes that we haven't already searched
|
// Search all inherited themes that we haven't already searched
|
||||||
return theme
|
return theme
|
||||||
.inherits(file)
|
.inherits(file.as_ref())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find_map(|parent| self.search_inherited_theme(searched_themes, parent));
|
.find_map(|parent| self.search_inherited_theme(searched_themes, parent));
|
||||||
}
|
}
|
||||||
|
|
@ -448,7 +444,7 @@ impl<'a> LookupBuilder<'a> {
|
||||||
fn search_inherited_theme(
|
fn search_inherited_theme(
|
||||||
&self,
|
&self,
|
||||||
searched_themes: &mut Vec<u64>,
|
searched_themes: &mut Vec<u64>,
|
||||||
theme: &str,
|
theme: &[u8],
|
||||||
) -> Option<PathBuf> {
|
) -> Option<PathBuf> {
|
||||||
THEMES
|
THEMES
|
||||||
.get(theme)?
|
.get(theme)?
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,9 @@ impl Default for DirectoryType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for DirectoryType {
|
impl From<&[u8]> for DirectoryType {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &[u8]) -> Self {
|
||||||
match value.as_bytes()[0] {
|
match value[0] {
|
||||||
b'F' => DirectoryType::Fixed,
|
b'F' => DirectoryType::Fixed,
|
||||||
b'S' => DirectoryType::Scalable,
|
b'S' => DirectoryType::Scalable,
|
||||||
_ => DirectoryType::Threshold,
|
_ => DirectoryType::Threshold,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use memmap2::Mmap;
|
||||||
pub(crate) use paths::BASE_PATHS;
|
pub(crate) use paths::BASE_PATHS;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
|
@ -10,7 +11,7 @@ mod directories;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod paths;
|
mod paths;
|
||||||
|
|
||||||
pub static THEMES: LazyLock<BTreeMap<String, Vec<Theme>>> = LazyLock::new(get_all_themes);
|
pub static THEMES: LazyLock<BTreeMap<Vec<u8>, Vec<Theme>>> = LazyLock::new(get_all_themes);
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_ini_theme(path: &Path) -> std::io::Result<Mmap> {
|
pub fn read_ini_theme(path: &Path) -> std::io::Result<Mmap> {
|
||||||
|
|
@ -33,15 +34,14 @@ impl Theme {
|
||||||
force_svg: bool,
|
force_svg: bool,
|
||||||
) -> Option<PathBuf> {
|
) -> Option<PathBuf> {
|
||||||
let file = read_ini_theme(&self.index).ok()?;
|
let file = read_ini_theme(&self.index).ok()?;
|
||||||
let file = std::str::from_utf8(file.as_ref()).ok()?;
|
self.try_get_icon_exact_size(file.as_ref(), name, size, scale, force_svg)
|
||||||
self.try_get_icon_exact_size(file, name, size, scale, force_svg)
|
.or_else(|| self.try_get_icon_closest_size(file.as_ref(), name, size, scale, force_svg))
|
||||||
.or_else(|| self.try_get_icon_closest_size(file, name, size, scale, force_svg))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn try_get_icon_exact_size(
|
fn try_get_icon_exact_size(
|
||||||
&self,
|
&self,
|
||||||
file: &str,
|
file: &[u8],
|
||||||
name: &str,
|
name: &str,
|
||||||
size: u16,
|
size: u16,
|
||||||
scale: u16,
|
scale: u16,
|
||||||
|
|
@ -53,7 +53,7 @@ impl Theme {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn match_size<'a>(
|
fn match_size<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
file: &'a str,
|
file: &'a [u8],
|
||||||
size: u16,
|
size: u16,
|
||||||
scale: u16,
|
scale: u16,
|
||||||
) -> impl Iterator<Item = &'a str> + 'a {
|
) -> impl Iterator<Item = &'a str> + 'a {
|
||||||
|
|
@ -65,7 +65,7 @@ impl Theme {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn try_get_icon_closest_size(
|
fn try_get_icon_closest_size(
|
||||||
&self,
|
&self,
|
||||||
file: &str,
|
file: &[u8],
|
||||||
name: &str,
|
name: &str,
|
||||||
size: u16,
|
size: u16,
|
||||||
scale: u16,
|
scale: u16,
|
||||||
|
|
@ -104,7 +104,7 @@ impl Theme {
|
||||||
|
|
||||||
fn closest_match_size<'a>(
|
fn closest_match_size<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
file: &'a str,
|
file: &'a [u8],
|
||||||
size: u16,
|
size: u16,
|
||||||
scale: u16,
|
scale: u16,
|
||||||
) -> impl Iterator<Item = &'a str> + 'a {
|
) -> impl Iterator<Item = &'a str> + 'a {
|
||||||
|
|
@ -155,8 +155,8 @@ fn try_build_ext(path: &mut PathBuf, name_buf: &mut String, name: &str, ext: &'s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iter through the base paths and get all theme directories
|
// Iter through the base paths and get all theme directories
|
||||||
pub(super) fn get_all_themes() -> BTreeMap<String, Vec<Theme>> {
|
pub(super) fn get_all_themes() -> BTreeMap<Vec<u8>, Vec<Theme>> {
|
||||||
let mut icon_themes = BTreeMap::<_, Vec<_>>::new();
|
let mut icon_themes = BTreeMap::<Vec<u8>, Vec<_>>::new();
|
||||||
let mut found_indices = BTreeMap::new();
|
let mut found_indices = BTreeMap::new();
|
||||||
let mut to_revisit = Vec::new();
|
let mut to_revisit = Vec::new();
|
||||||
|
|
||||||
|
|
@ -176,8 +176,10 @@ pub(super) fn get_all_themes() -> BTreeMap<String, Vec<Theme>> {
|
||||||
if fallback_index.is_none() {
|
if fallback_index.is_none() {
|
||||||
found_indices.insert(name.clone(), theme.index.clone());
|
found_indices.insert(name.clone(), theme.index.clone());
|
||||||
}
|
}
|
||||||
let name = name.to_string_lossy().to_string();
|
icon_themes
|
||||||
icon_themes.entry(name).or_default().push(theme);
|
.entry(name.as_bytes().to_owned())
|
||||||
|
.or_default()
|
||||||
|
.push(theme);
|
||||||
} else if entry.path().is_dir() {
|
} else if entry.path().is_dir() {
|
||||||
to_revisit.push(entry);
|
to_revisit.push(entry);
|
||||||
}
|
}
|
||||||
|
|
@ -188,9 +190,10 @@ pub(super) fn get_all_themes() -> BTreeMap<String, Vec<Theme>> {
|
||||||
let name = entry.file_name();
|
let name = entry.file_name();
|
||||||
let fallback_index = found_indices.get(&name);
|
let fallback_index = found_indices.get(&name);
|
||||||
if let Some(theme) = Theme::from_path(entry.path(), fallback_index) {
|
if let Some(theme) = Theme::from_path(entry.path(), fallback_index) {
|
||||||
if let Some(name) = name.to_str() {
|
icon_themes
|
||||||
icon_themes.entry(name.to_owned()).or_default().push(theme);
|
.entry(name.as_bytes().to_owned())
|
||||||
}
|
.or_default()
|
||||||
|
.push(theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,24 +233,22 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_one_icon() {
|
fn get_one_icon() {
|
||||||
let themes = THEMES.get("Adwaita").unwrap();
|
let themes = THEMES.get(&b"Adwaita"[..]).unwrap();
|
||||||
println!(
|
println!(
|
||||||
"{:?}",
|
"{:?}",
|
||||||
themes.iter().find_map(|t| {
|
themes.iter().find_map(|t| {
|
||||||
let file = super::read_ini_theme(&t.index).ok()?;
|
let file = super::read_ini_theme(&t.index).ok()?;
|
||||||
let file = std::str::from_utf8(file.as_ref()).ok()?;
|
t.try_get_icon_exact_size(file.as_ref(), "edit-delete-symbolic", 24, 1, false)
|
||||||
t.try_get_icon_exact_size(file, "edit-delete-symbolic", 24, 1, false)
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_get_png_first() {
|
fn should_get_png_first() {
|
||||||
let themes = THEMES.get("hicolor").unwrap();
|
let themes = THEMES.get(&b"hicolor"[..]).unwrap();
|
||||||
let icon = themes.iter().find_map(|t| {
|
let icon = themes.iter().find_map(|t| {
|
||||||
let file = super::read_ini_theme(&t.index).ok()?;
|
let file = super::read_ini_theme(&t.index).ok()?;
|
||||||
let file = std::str::from_utf8(file.as_ref()).ok()?;
|
t.try_get_icon_exact_size(file.as_ref(), "blueman", 24, 1, true)
|
||||||
t.try_get_icon_exact_size(file, "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/22x22/apps/blueman.png",
|
"/usr/share/icons/hicolor/22x22/apps/blueman.png",
|
||||||
|
|
@ -256,11 +257,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(&b"hicolor"[..]).unwrap();
|
||||||
let icon = themes.iter().find_map(|t| {
|
let icon = themes.iter().find_map(|t| {
|
||||||
let file = super::read_ini_theme(&t.index).ok()?;
|
let file = super::read_ini_theme(&t.index).ok()?;
|
||||||
let file = std::str::from_utf8(file.as_ref()).ok()?;
|
t.try_get_icon_exact_size(file.as_ref(), "blueman", 24, 1, false)
|
||||||
t.try_get_icon_exact_size(file, "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,42 +1,11 @@
|
||||||
use crate::theme::Theme;
|
use crate::theme::Theme;
|
||||||
use crate::theme::directories::{Directory, DirectoryType};
|
use crate::theme::directories::{Directory, DirectoryType};
|
||||||
|
use bstr::{BStr, ByteSlice};
|
||||||
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<'a>(
|
pub(super) fn get_all_directories<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
file: &'a str,
|
file: &'a [u8],
|
||||||
) -> impl Iterator<Item = Directory<'a>> + 'a {
|
) -> impl Iterator<Item = Directory<'a>> + 'a {
|
||||||
let mut iterator = sections(file);
|
let mut iterator = sections(file);
|
||||||
|
|
||||||
|
|
@ -59,19 +28,19 @@ impl Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
match key {
|
match key {
|
||||||
"Size" => size = str::parse(value).ok(),
|
b"Size" => size = btoi::btoi(value).ok(),
|
||||||
"Scale" => scale = str::parse(value).ok(),
|
b"Scale" => scale = btoi::btoi(value).ok(),
|
||||||
// "Context" => context = Some(value),
|
// "Context" => context = Some(value),
|
||||||
"Type" => dtype = DirectoryType::from(value),
|
b"Type" => dtype = DirectoryType::from(value),
|
||||||
"MaxSize" => max_size = str::parse(value).ok(),
|
b"MaxSize" => max_size = btoi::btoi(value).ok(),
|
||||||
"MinSize" => min_size = str::parse(value).ok(),
|
b"MinSize" => min_size = btoi::btoi(value).ok(),
|
||||||
"Threshold" => threshold = str::parse(value).ok(),
|
b"Threshold" => threshold = btoi::btoi(value).ok(),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DirectorySection::Section(new_name) => {
|
DirectorySection::Section(new_name) => {
|
||||||
name = new_name;
|
name = std::str::from_utf8(new_name).unwrap_or("");
|
||||||
size = None;
|
size = None;
|
||||||
max_size = None;
|
max_size = None;
|
||||||
min_size = None;
|
min_size = None;
|
||||||
|
|
@ -105,28 +74,380 @@ impl Theme {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inherits<'a>(&self, file: &'a str) -> impl Iterator<Item = &'a str> {
|
pub fn inherits<'a>(&self, file: &'a [u8]) -> impl Iterator<Item = &'a [u8]> {
|
||||||
icon_theme_section(file)
|
icon_theme_section(file)
|
||||||
.find(|&(key, _)| key == "Inherits")
|
.find(|&(key, _)| key == b"Inherits")
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|(_, parents)| {
|
.flat_map(|(_, parents)| {
|
||||||
parents
|
BStr::new(parents)
|
||||||
.split(',')
|
.split(|&char| char == b',')
|
||||||
// Filtering out 'hicolor' since we are going to fallback there anyway
|
// Filtering out 'hicolor' since we are going to fallback there anyway
|
||||||
.filter(|parent| parent != &"hicolor")
|
.filter(|parent| parent != &b"hicolor")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DirectorySection<'a> {
|
||||||
|
Property(&'a [u8], &'a [u8]),
|
||||||
|
EndSection,
|
||||||
|
Section(&'a [u8]),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sections(file: &[u8]) -> impl Iterator<Item = DirectorySection<'_>> {
|
||||||
|
let mut finished = false;
|
||||||
|
let mut table_found = false;
|
||||||
|
let mut section: &[u8] = b"";
|
||||||
|
let mut prev = 0;
|
||||||
|
let mut line_indices = memchr::memchr_iter(b'\n', file);
|
||||||
|
|
||||||
|
std::iter::from_fn(move || {
|
||||||
|
if finished {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !section.is_empty() {
|
||||||
|
let new_section = section;
|
||||||
|
section = b"";
|
||||||
|
return Some(DirectorySection::Section(new_section));
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let line_pos = match line_indices.next() {
|
||||||
|
Some(pos) => pos,
|
||||||
|
None => {
|
||||||
|
let value = if !finished {
|
||||||
|
Some(DirectorySection::EndSection)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
finished = true;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let line = BStr::new(&file[prev..line_pos]).trim_ascii();
|
||||||
|
prev = line_pos + 1;
|
||||||
|
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line[0] == b'[' {
|
||||||
|
section = &line[1..line.len() - 1];
|
||||||
|
if table_found {
|
||||||
|
return Some(DirectorySection::EndSection);
|
||||||
|
} else {
|
||||||
|
table_found = true;
|
||||||
|
return Some(DirectorySection::Section(section));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((key, value)) = memchr::memchr(b'=', line).map(|pos| unsafe {
|
||||||
|
// Position was already validated by memchr.
|
||||||
|
line.split_at_unchecked(pos)
|
||||||
|
}) {
|
||||||
|
return Some(DirectorySection::Property(key, &value[1..]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn icon_theme_section(file: &[u8]) -> impl Iterator<Item = (&[u8], &[u8])> + '_ {
|
||||||
|
let mut found_table = false;
|
||||||
|
let mut prev = 0;
|
||||||
|
let mut line_indices = memchr::memchr_iter(b'\n', file);
|
||||||
|
|
||||||
|
std::iter::from_fn(move || {
|
||||||
|
loop {
|
||||||
|
let line_pos = line_indices.next()?;
|
||||||
|
let line = BStr::new(&file[prev..line_pos]).trim_ascii();
|
||||||
|
prev = line_pos + 1;
|
||||||
|
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line[0] == b'[' {
|
||||||
|
if found_table {
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
let section = &line[1..line.len() - 1];
|
||||||
|
found_table = section == b"Icon Theme";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((key, value)) = memchr::memchr(b'=', line).map(|pos| unsafe {
|
||||||
|
// Position was already validated by memchr.
|
||||||
|
line.split_at_unchecked(pos)
|
||||||
|
}) {
|
||||||
|
return Some((key, &value[1..]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[cfg(feature = "local_tests")]
|
|
||||||
mod test {
|
mod test {
|
||||||
use crate::THEMES;
|
const ADWAITA_INDEX: &str = "[Icon Theme]
|
||||||
use speculoos::prelude::*;
|
Name=Adwaita\u{0020}
|
||||||
|
Comment=The Only One
|
||||||
|
Example=folder
|
||||||
|
Inherits=hicolor
|
||||||
|
|
||||||
|
# KDE Specific Stuff
|
||||||
|
DisplayDepth=32
|
||||||
|
|
||||||
|
# Directory list
|
||||||
|
Directories=16x16/actions,16x16/apps,16x16/categories,16x16/devices,16x16/emblems,16x16/emotes,16x16/legacy,16x16/mimetypes,16x16/places,16x16/status,16x16/ui,scalable/devices,scalable/mimetypes,scalable/places,scalable/status,scalable/actions,scalable/apps,scalable/categories,scalable/emblems,scalable/emotes,scalable/legacy,scalable/ui,symbolic-up-to-32/status,symbolic/actions,symbolic/apps,symbolic/categories,symbolic/devices,symbolic/emblems,symbolic/emotes,symbolic/mimetypes,symbolic/places,symbolic/status,symbolic/legacy,symbolic/ui,
|
||||||
|
|
||||||
|
[16x16/actions]
|
||||||
|
Context=Actions
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/apps]
|
||||||
|
Context=Applications
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/categories]
|
||||||
|
Context=Categories
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/devices]
|
||||||
|
Context=Devices
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/emblems]
|
||||||
|
Context=Emblems
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/emotes]
|
||||||
|
Context=Emotes
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/legacy]
|
||||||
|
Context=Legacy
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/mimetypes]
|
||||||
|
Context=MimeTypes
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/places]
|
||||||
|
Context=Places
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/status]
|
||||||
|
Context=Status
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[16x16/ui]
|
||||||
|
Context=UI
|
||||||
|
Size=16
|
||||||
|
Type=Fixed
|
||||||
|
|
||||||
|
[scalable/devices]
|
||||||
|
Context=Devices
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/mimetypes]
|
||||||
|
Context=MimeTypes
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/places]
|
||||||
|
Context=Places
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/status]
|
||||||
|
Context=Status
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/actions]
|
||||||
|
Context=Actions
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/apps]
|
||||||
|
Context=Applications
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/categories]
|
||||||
|
Context=Categories
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/emblems]
|
||||||
|
Context=Emblems
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/emotes]
|
||||||
|
Context=Emotes
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/legacy]
|
||||||
|
Context=Legacy
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[scalable/ui]
|
||||||
|
Context=UI
|
||||||
|
Size=128
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic-up-to-32/status]
|
||||||
|
Context=Status
|
||||||
|
Size=16
|
||||||
|
MinSize=16
|
||||||
|
MaxSize=32
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/actions]
|
||||||
|
Context=Actions
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/apps]
|
||||||
|
Context=Applications
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/categories]
|
||||||
|
Context=Categories
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/devices]
|
||||||
|
Context=Devices
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/emblems]
|
||||||
|
Context=Emblems
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/emotes]
|
||||||
|
Context=Emotes
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/mimetypes]
|
||||||
|
Context=MimeTypes
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/places]
|
||||||
|
Context=Places
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/status]
|
||||||
|
Context=Status
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/legacy]
|
||||||
|
Context=Legacy
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable
|
||||||
|
|
||||||
|
[symbolic/ui]
|
||||||
|
Context=UI
|
||||||
|
Size=16
|
||||||
|
MinSize=8
|
||||||
|
MaxSize=512
|
||||||
|
Type=Scalable";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
fn icon_theme_section() {
|
||||||
|
let mut iterator = super::icon_theme_section(ADWAITA_INDEX.as_bytes());
|
||||||
|
|
||||||
|
let (key, value) = iterator.next().unwrap();
|
||||||
|
assert_eq!(key, b"Name");
|
||||||
|
assert_eq!(value, b"Adwaita");
|
||||||
|
let (key, value) = iterator.next().unwrap();
|
||||||
|
assert_eq!(key, b"Comment");
|
||||||
|
assert_eq!(value, b"The Only One");
|
||||||
|
let (key, value) = iterator.next().unwrap();
|
||||||
|
assert_eq!(key, b"Example");
|
||||||
|
assert_eq!(value, b"folder");
|
||||||
|
let (key, value) = iterator.next().unwrap();
|
||||||
|
assert_eq!(key, b"Inherits");
|
||||||
|
assert_eq!(value, b"hicolor");
|
||||||
|
let (key, value) = iterator.next().unwrap();
|
||||||
|
assert_eq!(key, b"DisplayDepth");
|
||||||
|
assert_eq!(value, b"32");
|
||||||
|
let (key, value) = iterator.next().unwrap();
|
||||||
|
assert_eq!(key, b"Directories");
|
||||||
|
assert_eq!(value, b"16x16/actions,16x16/apps,16x16/categories,16x16/devices,16x16/emblems,16x16/emotes,16x16/legacy,16x16/mimetypes,16x16/places,16x16/status,16x16/ui,scalable/devices,scalable/mimetypes,scalable/places,scalable/status,scalable/actions,scalable/apps,scalable/categories,scalable/emblems,scalable/emotes,scalable/legacy,scalable/ui,symbolic-up-to-32/status,symbolic/actions,symbolic/apps,symbolic/categories,symbolic/devices,symbolic/emblems,symbolic/emotes,symbolic/mimetypes,symbolic/places,symbolic/status,symbolic/legacy,symbolic/ui,");
|
||||||
|
assert_eq!(iterator.next(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "local_tests")]
|
||||||
fn should_get_theme_parents() {
|
fn should_get_theme_parents() {
|
||||||
for theme in THEMES.get("Arc").unwrap() {
|
use speculoos::prelude::*;
|
||||||
|
for theme in crate::THEMES.get("Arc").unwrap() {
|
||||||
let file = crate::theme::read_ini_theme(&theme.index).ok().unwrap();
|
let file = crate::theme::read_ini_theme(&theme.index).ok().unwrap();
|
||||||
let file = std::str::from_utf8(file.as_ref()).ok().unwrap();
|
let file = std::str::from_utf8(file.as_ref()).ok().unwrap();
|
||||||
let parents = theme.inherits(file);
|
let parents = theme.inherits(file);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn should_get_all_themes() {
|
fn should_get_all_themes() {
|
||||||
let themes = get_all_themes();
|
let themes = get_all_themes();
|
||||||
assert_that!(themes.get("hicolor")).is_some();
|
assert_that!(themes.get(&b"hicolor"[..])).is_some();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue