2022-05-12 14:41:08 +02:00
|
|
|
use crate::theme::Theme;
|
2025-11-06 16:08:48 +01:00
|
|
|
use crate::theme::directories::{Directory, DirectoryType};
|
2025-12-01 15:19:01 +01:00
|
|
|
use bstr::{BStr, ByteSlice};
|
2022-05-12 14:41:08 +02:00
|
|
|
|
|
|
|
|
impl Theme {
|
2025-01-23 11:18:33 +01:00
|
|
|
pub(super) fn get_all_directories<'a>(
|
|
|
|
|
&'a self,
|
2025-12-01 15:19:01 +01:00
|
|
|
file: &'a [u8],
|
2025-01-23 11:18:33 +01:00
|
|
|
) -> impl Iterator<Item = Directory<'a>> + 'a {
|
|
|
|
|
let mut iterator = sections(file);
|
2022-05-12 14:41:08 +02:00
|
|
|
|
2025-01-23 11:18:33 +01:00
|
|
|
std::iter::from_fn(move || {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match key {
|
2025-12-01 15:19:01 +01:00
|
|
|
b"Size" => size = btoi::btoi(value).ok(),
|
|
|
|
|
b"Scale" => scale = btoi::btoi(value).ok(),
|
2025-01-23 11:18:33 +01:00
|
|
|
// "Context" => context = Some(value),
|
2025-12-01 15:19:01 +01:00
|
|
|
b"Type" => dtype = DirectoryType::from(value),
|
|
|
|
|
b"MaxSize" => max_size = btoi::btoi(value).ok(),
|
|
|
|
|
b"MinSize" => min_size = btoi::btoi(value).ok(),
|
|
|
|
|
b"Threshold" => threshold = btoi::btoi(value).ok(),
|
2025-01-23 11:18:33 +01:00
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-12 14:41:08 +02:00
|
|
|
|
2025-01-23 11:18:33 +01:00
|
|
|
DirectorySection::Section(new_name) => {
|
2025-12-01 15:19:01 +01:00
|
|
|
name = std::str::from_utf8(new_name).unwrap_or("");
|
2025-01-23 11:18:33 +01:00
|
|
|
size = None;
|
|
|
|
|
max_size = None;
|
|
|
|
|
min_size = None;
|
|
|
|
|
threshold = None;
|
|
|
|
|
scale = None;
|
|
|
|
|
dtype = DirectoryType::default();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DirectorySection::EndSection => {
|
|
|
|
|
if name.is_empty() || name == "Icon Theme" {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
})
|
2022-05-12 14:41:08 +02:00
|
|
|
}
|
|
|
|
|
|
2025-12-01 15:19:01 +01:00
|
|
|
pub fn inherits<'a>(&self, file: &'a [u8]) -> impl Iterator<Item = &'a [u8]> {
|
2025-01-23 11:18:33 +01:00
|
|
|
icon_theme_section(file)
|
2025-12-01 15:19:01 +01:00
|
|
|
.find(|&(key, _)| key == b"Inherits")
|
2025-11-25 06:24:34 +01:00
|
|
|
.into_iter()
|
|
|
|
|
.flat_map(|(_, parents)| {
|
2025-12-01 15:19:01 +01:00
|
|
|
BStr::new(parents)
|
|
|
|
|
.split(|&char| char == b',')
|
2022-05-13 10:11:02 +02:00
|
|
|
// Filtering out 'hicolor' since we are going to fallback there anyway
|
2025-12-01 15:19:01 +01:00
|
|
|
.filter(|parent| parent != &b"hicolor")
|
2022-05-13 10:11:02 +02:00
|
|
|
})
|
2022-05-12 14:41:08 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-05-13 08:00:52 +02:00
|
|
|
|
2025-12-01 15:19:01 +01:00
|
|
|
#[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..]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-13 08:00:52 +02:00
|
|
|
#[cfg(test)]
|
2025-12-01 15:19:01 +01:00
|
|
|
|
2022-05-13 08:00:52 +02:00
|
|
|
mod test {
|
2025-12-01 15:19:01 +01:00
|
|
|
const ADWAITA_INDEX: &str = "[Icon Theme]
|
|
|
|
|
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]
|
|
|
|
|
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);
|
|
|
|
|
}
|
2022-05-13 08:00:52 +02:00
|
|
|
|
|
|
|
|
#[test]
|
2025-12-01 15:19:01 +01:00
|
|
|
#[cfg(feature = "local_tests")]
|
2022-05-13 08:00:52 +02:00
|
|
|
fn should_get_theme_parents() {
|
2025-12-01 15:19:01 +01:00
|
|
|
use speculoos::prelude::*;
|
|
|
|
|
for theme in crate::THEMES.get("Arc").unwrap() {
|
2025-04-03 14:26:51 +02:00
|
|
|
let file = crate::theme::read_ini_theme(&theme.index).ok().unwrap();
|
|
|
|
|
let file = std::str::from_utf8(file.as_ref()).ok().unwrap();
|
|
|
|
|
let parents = theme.inherits(file);
|
2022-05-13 10:11:02 +02:00
|
|
|
|
2023-01-09 23:54:07 +01:00
|
|
|
assert_that!(parents).does_not_contain("hicolor");
|
2022-05-13 10:11:02 +02:00
|
|
|
|
2023-01-09 23:54:07 +01:00
|
|
|
assert_that!(parents).is_equal_to(vec![
|
|
|
|
|
"Moka",
|
|
|
|
|
"Faba",
|
|
|
|
|
"elementary",
|
|
|
|
|
"Adwaita",
|
|
|
|
|
"gnome",
|
|
|
|
|
]);
|
|
|
|
|
}
|
2022-05-13 08:00:52 +02:00
|
|
|
}
|
|
|
|
|
}
|