use-7z 0.1.0

7-Zip archive labels, extensions, and compression method metadata for RustUse
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

//! 7-Zip archive labels and compression method metadata for `RustUse`.

use core::fmt;

/// Common 7-Zip archive extension.
pub const SEVEN_ZIP_EXTENSION: &str = "7z";
/// Common first split-volume 7-Zip extension.
pub const SEVEN_ZIP_FIRST_VOLUME_EXTENSION: &str = "7z.001";
/// Common 7-Zip-related extensions.
pub const SEVEN_ZIP_EXTENSIONS: &[&str] = &["7z", "7z.001"];

/// 7-Zip archive format labels.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum SevenZipFormat {
    /// 7-Zip archive label.
    SevenZip,
    /// Split-volume 7-Zip archive label.
    SplitVolume,
    /// Unknown or intentionally unspecified 7-Zip format label.
    #[default]
    Unknown,
}

impl SevenZipFormat {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::SevenZip => "7z",
            Self::SplitVolume => "7z-volume",
            Self::Unknown => "unknown",
        }
    }
}

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

/// 7-Zip compression method labels.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum SevenZipCompressionMethod {
    /// Copy/stored method.
    Copy,
    /// LZMA method.
    Lzma,
    /// LZMA2 method.
    Lzma2,
    /// Bzip2 method.
    Bzip2,
    /// Deflate method.
    Deflate,
    /// PPMd method.
    Ppmd,
    /// Unknown or unsupported method.
    #[default]
    Unknown,
}

impl SevenZipCompressionMethod {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Copy => "copy",
            Self::Lzma => "lzma",
            Self::Lzma2 => "lzma2",
            Self::Bzip2 => "bzip2",
            Self::Deflate => "deflate",
            Self::Ppmd => "ppmd",
            Self::Unknown => "unknown",
        }
    }
}

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

/// Returns whether `extension` is a known 7-Zip extension label.
#[must_use]
pub fn is_7z_extension(extension: &str) -> bool {
    let normalized = normalize_extension(extension);
    normalized == "7z" || is_7z_volume_extension(&normalized)
}

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

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

fn is_7z_volume_extension(extension: &str) -> bool {
    let parts = extension
        .split('.')
        .filter(|part| !part.is_empty())
        .collect::<Vec<_>>();

    matches!(parts.as_slice(), ["7z", part] if is_volume_number(part))
}

fn is_volume_number(part: &str) -> bool {
    part.len() == 3 && part.bytes().all(|byte| byte.is_ascii_digit())
}

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::{
        SEVEN_ZIP_EXTENSIONS, SevenZipCompressionMethod, SevenZipFormat, is_7z_extension,
        is_7z_filename,
    };

    #[test]
    fn detects_7z_extensions() {
        assert!(is_7z_extension(".7z"));
        assert!(is_7z_extension("7z.001"));
        assert!(is_7z_extension("7z.120"));
        assert_eq!(SEVEN_ZIP_EXTENSIONS[0], "7z");
    }

    #[test]
    fn detects_7z_filenames() {
        assert!(is_7z_filename("bundle.7z"));
        assert!(is_7z_filename("bundle.7z.001"));
        assert!(!is_7z_filename("bundle.zip"));
    }

    #[test]
    fn exposes_default_and_unknown_labels() {
        assert_eq!(SevenZipFormat::default(), SevenZipFormat::Unknown);
        assert_eq!(SevenZipFormat::SplitVolume.as_str(), "7z-volume");
        assert_eq!(
            SevenZipCompressionMethod::default(),
            SevenZipCompressionMethod::Unknown
        );
        assert_eq!(SevenZipCompressionMethod::Ppmd.as_str(), "ppmd");
    }
}