pak 0.7.4

An easy-to-use data pak format for games.
Documentation
use {
    crate::compression::{BrotliParams, Compression},
    serde::Deserialize,
};

/// Holds a description of top-level content files which simply group other asset files for ease of
/// use.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
pub struct Content {
    compression: Option<CompressionType>,

    // Brotli-specific compression parameter
    #[serde(rename = "buffer-size")]
    buffer_size: Option<usize>,

    // Brotli-specific compression parameter
    quality: Option<u32>,

    // Brotli-specific compression parameter
    #[serde(rename = "window-size")]
    window_size: Option<u32>,

    // Tables must follow values
    #[serde(default, rename = "group")]
    groups: Box<[Group]>,
}

impl Content {
    /// An iterator of grouped content file descriptions.
    #[allow(unused)]
    pub fn groups(&self) -> impl Iterator<Item = &Group> {
        self.groups.iter()
    }

    pub(crate) fn compression(&self) -> Option<Compression> {
        self.compression.map(|compression| match compression {
            CompressionType::Brotli => Compression::Brotli(BrotliParams {
                buffer_size: self
                    .buffer_size
                    .unwrap_or_else(|| BrotliParams::default().buffer_size),
                quality: self
                    .quality
                    .unwrap_or_else(|| BrotliParams::default().quality),
                window_size: self
                    .window_size
                    .unwrap_or_else(|| BrotliParams::default().window_size),
            }),
            CompressionType::Snap => Compression::Snap,
        })
    }
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq)]
pub enum CompressionType {
    /// Higher compression ratio but slower to decode and encode.
    #[serde(rename = "brotli")]
    Brotli,
    /// Lower compression ratio but faster to decode and encode.
    #[serde(rename = "snap")]
    Snap,
}

/// Holds a description of asset files.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
pub struct Group {
    #[serde(default)]
    assets: Vec<String>,

    #[serde(default = "Group::default_enabled")]
    enabled: bool,

    #[serde(default)]
    exclude: Vec<String>,
}

impl Group {
    const DEFAULT_ENABLED: bool = true;

    /// Individual asset file specification globs.
    ///
    /// May be a filename, might be folder/**/other.jpeg
    #[allow(unused)]
    pub fn asset_globs(&self) -> impl Iterator<Item = &String> {
        self.assets.iter()
    }

    const fn default_enabled() -> bool {
        Self::DEFAULT_ENABLED
    }

    /// Allows a group to be selectively removed with a single flag, as opposed to physically
    /// removing a group from the content file.
    ///
    /// This is useful for debugging.
    #[allow(unused)]
    pub fn enabled(&self) -> bool {
        self.enabled
    }

    /// Individual asset file specification globs to exclude from baking.
    ///
    /// May be a filename, might be folder/**/other.jpeg
    #[allow(unused)]
    pub fn exclude_globs(&self) -> impl Iterator<Item = &String> {
        self.exclude.iter()
    }
}

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

    #[test]
    fn content_deserializes_without_groups() {
        let content = toml::from_str::<Content>("compression = 'snap'")
            .expect("content groups should be optional");

        assert_eq!(content.groups().count(), 0);
    }
}