attheme 0.3.0

A crate for parsing and serialization of .attheme files.
Documentation
use super::*;

/// Represents contents of an .attheme file.
#[derive(Debug, PartialEq, Clone, Default)]
pub struct Attheme {
    /// An `IndexMap` of variables of the theme.
    pub variables: Variables,
    /// The image wallpaper of the theme.
    ///
    /// Note that Telegram only recognizes `.jpg` images, but the crate doesn't
    /// check that the wallpaper is actually a valid `.jpg`. You should do it on
    /// your own.
    pub wallpaper: Option<Wallpaper>,
}

impl Attheme {
    /// Creates an empty theme.
    ///
    /// # Examples
    ///
    /// ```
    /// use attheme::Attheme;
    ///
    /// let theme = Attheme::new();
    ///
    /// assert!(theme.variables.is_empty());
    /// assert_eq!(theme.wallpaper, None);
    /// ```
    #[must_use]
    pub fn new() -> Self {
        Self {
            variables: IndexMap::new(),
            wallpaper: None,
        }
    }

    /// Creates an empty theme, with preallocation for `capacity` variables.
    #[must_use]
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            variables: IndexMap::with_capacity(capacity),
            wallpaper: None,
        }
    }

    /// Parses .attheme contents passed as bytes.
    ///
    /// # Examples
    ///
    /// ```
    /// use attheme::{Attheme, Color};
    ///
    /// let contents = b"
    /// checkbox=-1
    /// checkboxCheck=#123456
    /// divider=#40302010
    ///
    /// WPS
    /// Pretend it's Mona Lisa here
    /// WPE
    /// ";
    /// let theme = Attheme::from_bytes(contents);
    ///
    /// let mut variables = theme.variables.iter();
    /// assert_eq!(
    ///     variables.next(),
    ///     Some((&"checkbox".to_string(), &Color::new(255, 255, 255, 255))),
    /// );
    /// assert_eq!(
    ///     variables.next(),
    ///     Some((
    ///         &"checkboxCheck".to_string(),
    ///         &Color::new(0x12, 0x34, 0x56, 0xff)),
    ///     ),
    /// );
    /// assert_eq!(
    ///     variables.next(),
    ///     Some((&"divider".to_string(), &Color::new(0x30, 0x20, 0x10, 0x40))),
    /// );
    /// assert_eq!(variables.next(), None);
    ///
    /// assert_eq!(
    ///   theme.wallpaper,
    ///   Some(b"Pretend it's Mona Lisa here".to_vec()),
    /// );
    /// ```
    ///
    /// # Notes
    ///
    /// If Telegram can't parse something, it will simply ignore it. This crate
    /// resembles this behavior.
    ///
    /// Though Telegram only recognizes `.jpg` images, the crate does not check
    /// if the image is a valid `.jpg`. You should do it on your own.
    #[must_use]
    pub fn from_bytes(contents: &[u8]) -> Self {
        parser::from_bytes(contents)
    }

    /// Serializes the theme.
    ///
    /// # Examples
    /// ```
    /// use attheme::{Attheme, Color, ColorSignature};
    ///
    /// let mut theme = Attheme::new();
    ///
    /// theme.variables.insert("divider".to_string(), Color::new(1, 2, 3, 4));
    /// theme.variables.insert(
    ///     "checkbox".to_string(),
    ///     Color::new(255, 255, 255, 255),
    /// );
    ///
    /// let expected_contents = b"divider=67174915
    /// checkbox=-1
    /// ".to_vec();
    ///
    /// assert_eq!(theme.to_bytes(ColorSignature::Int), expected_contents);
    ///
    /// theme.wallpaper = Some(b"Pretend it's Mona Lisa here".to_vec());
    ///
    /// let expected_contents = b"divider=#04010203
    /// checkbox=#ffffffff
    ///
    /// WPS
    /// Pretend it's Mona Lisa here
    /// WPE
    /// ".to_vec();
    ///
    /// assert_eq!(theme.to_bytes(ColorSignature::Hex), expected_contents);
    /// ```
    #[must_use]
    pub fn to_bytes(&self, color_signature: ColorSignature) -> Vec<u8> {
        serializer::theme_to_bytes(
            &self.variables,
            self.wallpaper.as_ref(),
            color_signature,
        )
    }

    /// Fallbacks `self` to `other`:
    ///
    /// - All variables that exist in `other` but not in `self` are added to
    ///   `self`;
    /// - If `self` doesn't have a wallpaper, `other`'s wallpaper is moved to
    ///   `self`.
    ///
    /// # Examples
    ///
    /// ## Basic usage
    ///
    /// ```
    /// use attheme::{Attheme, Color};
    ///
    /// let mut first_theme = Attheme::new();
    /// let mut second_theme = Attheme::new();
    /// let wallpaper = b"Just pretending".to_vec();
    ///
    /// first_theme.variables.insert(
    ///     "checkbox".to_string(),
    ///     Color::new(255, 255, 255, 255),
    /// );
    /// first_theme.wallpaper = Some(wallpaper.clone());
    /// second_theme.variables.insert(
    ///     "checkbox".to_string(),
    ///     Color::new(0x80, 0x80, 0x80, 0x80),
    /// );
    /// second_theme.variables.insert(
    ///     "divider".to_string(),
    ///     Color::new(0x40, 0x40, 0x40, 0x40),
    /// );
    ///
    /// first_theme.fallback_to_other(second_theme.clone());
    ///
    /// assert_eq!(
    ///     first_theme.variables["checkbox"],
    ///     Color::new(255, 255, 255, 255),
    /// );
    /// assert_eq!(
    ///   first_theme.variables["divider"],
    ///   second_theme.variables["divider"],
    /// );
    /// assert_eq!(first_theme.wallpaper, Some(wallpaper));
    /// ```
    ///
    /// ## Imitating Telegram's behavior
    ///
    /// ```
    /// # let mut theme = attheme::Attheme::new();
    /// theme.fallback_to_self(attheme::FALLBACKS);
    /// theme.fallback_to_other(attheme::default_themes::default());
    /// ```
    pub fn fallback_to_other(&mut self, other: Self) {
        for (key, value) in other.variables {
            if !self.variables.contains_key(&key) {
                self.variables.insert(key, value);
            }
        }

        if self.wallpaper.is_none() {
            self.wallpaper = other.wallpaper;
        }
    }

    /// Fallbacks variables to other existing variabled according to the map.
    ///
    /// # Examples
    ///
    /// ## Basic usage
    ///
    /// ```
    /// use attheme::{Attheme, Color};
    ///
    /// let mut theme = Attheme::new();
    /// theme.variables.insert("foo".to_string(), Color::new(0, 0, 0, 0));
    /// theme.fallback_to_self(&[("bar", "foo"), ("eggs", "spam")]);
    ///
    /// assert_eq!(theme.variables.get("bar"), Some(&Color::new(0, 0, 0, 0)));
    /// assert_eq!(theme.variables.get("eggs"), None);
    /// ```
    ///
    /// ## Imitating Telegram's behavior
    ///
    /// ```
    /// # let mut theme = attheme::Attheme::new();
    /// theme.fallback_to_self(attheme::FALLBACKS);
    /// theme.fallback_to_other(attheme::default_themes::default());
    /// ```
    pub fn fallback_to_self(&mut self, fallback_map: &[(&str, &str)]) {
        for &(variable, fallback) in fallback_map {
            if !self.variables.contains_key(variable) {
                if let Some(&color) = self.variables.get(fallback) {
                    self.variables.insert(variable.to_string(), color);
                }
            }
        }
    }
}