linicon 2.3.0

Look up icons and icon theme info on Linux
Documentation
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::errors::ParseError;
use freedesktop_entry_parser::low_level::{parse_entry, SectionBytes};
use std::{fmt::Debug, str::from_utf8};

type Result<T> = std::result::Result<T, ParseError>;

#[derive(Debug, PartialEq, Eq)]
pub struct IndexHeader {
    pub name: String,
    pub inherits: Option<String>,
    pub comment: Option<String>,
}

impl<'a> IndexHeader {
    fn parse(section: &SectionBytes<'a>) -> Result<Self> {
        let mut name = None;
        let mut inherits = None;
        let mut comment = None;
        for attr in &section.attrs {
            match attr.name {
                b"Name" => name = Some(attr.value),
                b"Inherits" => inherits = Some(attr.value),
                b"Comment" => comment = Some(attr.value),
                _ => {}
            }
        }
        let inherits = if let Some(inherits) = inherits {
            Some(from_utf8(inherits)?.to_owned())
        } else {
            None
        };
        let comment = if let Some(comment) = comment {
            Some(from_utf8(comment)?.to_owned())
        } else {
            None
        };
        Ok(IndexHeader {
            name: from_utf8(name.ok_or(ParseError::MissingHeader)?)?.to_owned(),
            inherits,
            comment,
        })
    }
}

#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct Directory<'a> {
    pub name: &'a [u8],
    pub scale: u16,
    pub max_size: u16,
    pub min_size: u16,
}

impl<'a> Directory<'a> {
    fn parse(section: &SectionBytes<'a>) -> Result<Self> {
        let mut size = None;
        let mut scale = None;
        let mut min_size = None;
        let mut max_size = None;
        let mut threshold = None;
        let mut type_: &[u8] = b"Threshold";

        for attr in &section.attrs {
            match attr.name {
                b"Size" => {
                    size = Some(from_utf8(attr.value)?.parse()?);
                }
                b"Scale" => {
                    scale = Some(from_utf8(attr.value)?.parse()?);
                }
                b"Type" => {
                    type_ = attr.value;
                }
                b"MaxSize" => {
                    max_size = Some(from_utf8(attr.value)?.parse()?);
                }
                b"MinSize" => {
                    min_size = Some(from_utf8(attr.value)?.parse()?);
                }
                b"Threshold" => {
                    threshold = Some(from_utf8(attr.value)?.parse()?);
                }
                _ => {}
            }
        }

        if size.is_none() {
            return Err(ParseError::MissingSize(
                from_utf8(section.title)?.to_owned(),
            ));
        }
        let size = size.unwrap();
        let scale = scale.unwrap_or(1);

        match type_ {
            b"Threshold" | b"threshold" => {
                let threshold = threshold.unwrap_or(2);
                Ok(Directory {
                    name: section.title,
                    scale,
                    max_size: size + threshold,
                    min_size: size - threshold,
                })
            }
            b"Fixed" | b"fixed" => Ok(Directory {
                name: section.title,
                scale,
                max_size: size,
                min_size: size,
            }),
            b"Scalable" | b"scalable" => {
                let max_size = max_size.unwrap_or(size);
                let min_size = min_size.unwrap_or(size);
                Ok(Directory {
                    name: section.title,
                    scale,
                    max_size,
                    min_size,
                })
            }
            _ => {
                return Err(ParseError::InvalidType(
                    from_utf8(section.title)?.to_owned(),
                ))
            }
        }
    }
}

impl<'a> Debug for Directory<'a> {
    fn fmt(
        &self,
        f: &mut std::fmt::Formatter<'_>,
    ) -> std::result::Result<(), std::fmt::Error> {
        if let Ok(s) = from_utf8(self.name) {
            write!(
                f,
                "Directory {{ \
                    name: {}, \
                    scale: {}, \
                    min_size: {}, \
                    max_size: {} }}",
                s, self.scale, self.min_size, self.max_size
            )
        } else {
            write!(
                f,
                "Directory {{ \
                    name: {:?}, \
                    scale: {}, \
                    min_size: {}, \
                    max_size: {} }}",
                self.name, self.scale, self.min_size, self.max_size
            )
        }
    }
}

pub(crate) fn parse_index(
    input: &[u8],
) -> Result<(IndexHeader, Vec<Directory>)> {
    let mut iter = parse_entry(input);
    let first_section = iter.next().ok_or(ParseError::EmptyIndexFile)??;
    if first_section.title != b"Icon Theme" {
        return Err(ParseError::NoIconTheme);
    }
    let header = IndexHeader::parse(&first_section)?;
    let dirs = iter
        .map(|section| match section {
            Ok(section) => Directory::parse(&section),
            Err(e) => Err(e.into()),
        })
        .collect::<Result<Vec<_>>>()?;
    Ok((header, dirs))
}

#[cfg(test)]
mod test {
    use super::*;

    mod fn_parse_index {
        use super::*;

        #[test]
        fn ok() {
            let (index, mut dirs) = parse_index(
                b"[Icon Theme]\nName=Test Theme\nInherits=Papirus,hicolor\n \
                  [48x48/apps]\nSize=48\nScale=1\nType=Fixed\n\n\
                  [scalable/apps]\nSize=128\nMaxSize=128\nMinSize=8\nScale=1\nType=Scalable\n\n\
                  [128x128/apps]\nSize=128\nThreshold=10\nScale=1\nType=Threshold"
            )
            .unwrap();
            dirs.sort();
            let test_index = IndexHeader {
                name: "Test Theme".to_owned(),
                inherits: Some("Papirus,hicolor".to_owned()),
                comment: None,
            };
            let mut test_dirs = vec![
                Directory {
                    name: &b"128x128/apps"[..],
                    scale: 1,
                    max_size: 138,
                    min_size: 118,
                },
                Directory {
                    name: &b"scalable/apps"[..],
                    scale: 1,
                    max_size: 128,
                    min_size: 8,
                },
                Directory {
                    name: &b"48x48/apps"[..],
                    scale: 1,
                    max_size: 48,
                    min_size: 48,
                },
            ];
            test_dirs.sort();
            assert_eq!(index, test_index);
            assert_eq!(dirs, test_dirs);
        }
    }
}