use-iso 0.1.0

ISO image labels, extensions, and volume metadata primitives for RustUse
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

//! ISO image labels and volume metadata for `RustUse`.

use core::fmt;

/// Common ISO image extension.
pub const ISO_EXTENSION: &str = "iso";
/// Common disk image extension used for ISO-like artifacts.
pub const IMAGE_EXTENSION: &str = "img";
/// Common ISO-related extensions.
pub const ISO_EXTENSIONS: &[&str] = &["iso", "img"];

/// ISO image format labels.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum IsoFormat {
    /// ISO 9660 filesystem image.
    Iso9660,
    /// UDF filesystem image.
    Udf,
    /// Hybrid image label.
    Hybrid,
    /// Unknown or intentionally unspecified ISO image format.
    #[default]
    Unknown,
}

impl IsoFormat {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Iso9660 => "iso9660",
            Self::Udf => "udf",
            Self::Hybrid => "hybrid",
            Self::Unknown => "unknown",
        }
    }
}

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

/// ISO volume kind labels.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum IsoVolumeKind {
    /// Bootable image label.
    Bootable,
    /// Data image label.
    Data,
    /// Audio image label.
    Audio,
    /// Hybrid volume label.
    Hybrid,
    /// Unknown or intentionally unspecified volume kind.
    #[default]
    Unknown,
}

impl IsoVolumeKind {
    /// Returns a stable lowercase label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Bootable => "bootable",
            Self::Data => "data",
            Self::Audio => "audio",
            Self::Hybrid => "hybrid",
            Self::Unknown => "unknown",
        }
    }
}

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

/// Returns whether `extension` is a known ISO image extension label.
#[must_use]
pub fn is_iso_extension(extension: &str) -> bool {
    matches!(normalize_extension(extension).as_str(), "iso" | "img")
}

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

    matches!(parts.as_slice(), [.., last] if matches!(last.as_str(), "iso" | "img"))
}

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::{ISO_EXTENSIONS, IsoFormat, IsoVolumeKind, is_iso_extension, is_iso_filename};

    #[test]
    fn detects_iso_extensions() {
        assert!(is_iso_extension(".iso"));
        assert!(is_iso_extension("img"));
        assert_eq!(ISO_EXTENSIONS[0], "iso");
    }

    #[test]
    fn detects_iso_filenames() {
        assert!(is_iso_filename("installer.iso"));
        assert!(is_iso_filename("disk.IMG"));
        assert!(!is_iso_filename("bundle.zip"));
    }

    #[test]
    fn exposes_default_and_unknown_labels() {
        assert_eq!(IsoFormat::default(), IsoFormat::Unknown);
        assert_eq!(IsoFormat::Udf.as_str(), "udf");
        assert_eq!(IsoVolumeKind::default(), IsoVolumeKind::Unknown);
        assert_eq!(IsoVolumeKind::Audio.as_str(), "audio");
    }
}