use-lz4 0.1.0

LZ4 compression labels, extensions, and option metadata for RustUse
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

//! LZ4 labels and option metadata for `RustUse`.

use core::fmt;

/// Common LZ4 file extension.
pub const LZ4_EXTENSION: &str = "lz4";
/// Common LZ4-compressed tar extension.
pub const TAR_LZ4_EXTENSION: &str = "tar.lz4";
/// Common LZ4-related extensions.
pub const LZ4_EXTENSIONS: &[&str] = &["lz4", "tar.lz4"];

/// LZ4 extension labels.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Lz4Extension {
    /// `.lz4` payload.
    Lz4,
    /// `.tar.lz4` tar plus LZ4 extension.
    TarLz4,
    /// Unknown or unsupported LZ4 extension label.
    #[default]
    Unknown,
}

impl Lz4Extension {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Lz4 => "lz4",
            Self::TarLz4 => "tar.lz4",
            Self::Unknown => "unknown",
        }
    }

    /// Detects an LZ4 extension label.
    #[must_use]
    pub fn from_extension(extension: &str) -> Self {
        match normalize_extension(extension).as_str() {
            "lz4" => Self::Lz4,
            "tar.lz4" => Self::TarLz4,
            _ => Self::Unknown,
        }
    }
}

impl fmt::Display for Lz4Extension {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.as_str())
    }
}

/// LZ4 frame kind labels.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Lz4FrameKind {
    /// Standard LZ4 frame.
    #[default]
    Standard,
    /// Legacy LZ4 frame.
    Legacy,
    /// Skippable frame.
    Skippable,
    /// Raw block label.
    Block,
    /// Unknown or intentionally unspecified frame kind.
    Unknown,
}

impl Lz4FrameKind {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Standard => "standard",
            Self::Legacy => "legacy",
            Self::Skippable => "skippable",
            Self::Block => "block",
            Self::Unknown => "unknown",
        }
    }
}

/// High-level LZ4 usage profile.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Lz4Profile {
    /// Prefer compression speed.
    Speed,
    /// Prefer compressed size.
    Size,
    /// Balance speed and size.
    #[default]
    Balanced,
    /// Unknown or intentionally unspecified profile.
    Unknown,
}

impl Lz4Profile {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Speed => "speed",
            Self::Size => "size",
            Self::Balanced => "balanced",
            Self::Unknown => "unknown",
        }
    }
}

/// Returns whether `extension` is a known LZ4 extension label.
#[must_use]
pub fn is_lz4_extension(extension: &str) -> bool {
    !matches!(
        Lz4Extension::from_extension(extension),
        Lz4Extension::Unknown
    )
}

/// Returns whether `name` has a known LZ4 filename encoding.
#[must_use]
pub fn is_lz4_filename(name: &str) -> bool {
    let parts = filename_parts(name);

    match parts.as_slice() {
        [.., last] if last == "lz4" => true,
        [.., previous, last] if previous == "tar" && last == "lz4" => true,
        _ => false,
    }
}

fn normalize_extension(extension: &str) -> String {
    extension
        .trim()
        .trim_start_matches('.')
        .to_ascii_lowercase()
}

fn filename_parts(name: &str) -> Vec<String> {
    name.trim()
        .to_ascii_lowercase()
        .rsplit(['/', '\\'])
        .next()
        .unwrap_or_default()
        .trim_start_matches('.')
        .split('.')
        .filter(|part| !part.is_empty())
        .map(str::to_owned)
        .collect()
}

#[cfg(test)]
mod tests {
    use super::{
        LZ4_EXTENSIONS, Lz4Extension, Lz4FrameKind, Lz4Profile, is_lz4_extension, is_lz4_filename,
    };

    #[test]
    fn detects_lz4_extensions() {
        assert!(is_lz4_extension(".lz4"));
        assert!(is_lz4_extension("tar.lz4"));
        assert_eq!(
            Lz4Extension::from_extension("tar.lz4"),
            Lz4Extension::TarLz4
        );
        assert_eq!(LZ4_EXTENSIONS[0], "lz4");
    }

    #[test]
    fn detects_lz4_filenames() {
        assert!(is_lz4_filename("release.tar.lz4"));
        assert!(is_lz4_filename("payload.LZ4"));
        assert!(!is_lz4_filename("bundle.zip"));
    }

    #[test]
    fn exposes_default_and_unknown_labels() {
        assert_eq!(Lz4Extension::default(), Lz4Extension::Unknown);
        assert_eq!(Lz4FrameKind::default().as_str(), "standard");
        assert_eq!(Lz4Profile::Unknown.as_str(), "unknown");
    }
}