#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
pub const CPIO_EXTENSION: &str = "cpio";
pub const CPIO_GZIP_EXTENSION: &str = "cpio.gz";
pub const CPIO_XZ_EXTENSION: &str = "cpio.xz";
pub const CPIO_ZSTD_EXTENSION: &str = "cpio.zst";
pub const CPIO_EXTENSIONS: &[&str] = &["cpio", "cpio.gz", "cpio.xz", "cpio.zst"];
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum CpioFormat {
Binary,
OldAscii,
NewAscii,
CrcAscii,
#[default]
Unknown,
}
impl CpioFormat {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Binary => "binary",
Self::OldAscii => "old-ascii",
Self::NewAscii => "new-ascii",
Self::CrcAscii => "crc-ascii",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for CpioFormat {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum CpioEntryKind {
File,
Directory,
Symlink,
Device,
Fifo,
Socket,
#[default]
Unknown,
}
impl CpioEntryKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::File => "file",
Self::Directory => "directory",
Self::Symlink => "symlink",
Self::Device => "device",
Self::Fifo => "fifo",
Self::Socket => "socket",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for CpioEntryKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[must_use]
pub fn is_cpio_extension(extension: &str) -> bool {
matches!(
normalize_extension(extension).as_str(),
"cpio" | "cpio.gz" | "cpio.xz" | "cpio.zst" | "cpio.zstd"
)
}
#[must_use]
pub fn is_cpio_filename(name: &str) -> bool {
let parts = filename_parts(name);
match parts.as_slice() {
[.., last] if last == "cpio" => true,
[.., previous, last]
if previous == "cpio" && matches!(last.as_str(), "gz" | "xz" | "zst" | "zstd") =>
{
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::{CPIO_EXTENSIONS, CpioEntryKind, CpioFormat, is_cpio_extension, is_cpio_filename};
#[test]
fn detects_cpio_extensions() {
assert!(is_cpio_extension(".cpio"));
assert!(is_cpio_extension("cpio.gz"));
assert!(is_cpio_extension("cpio.zst"));
assert_eq!(CPIO_EXTENSIONS[0], "cpio");
}
#[test]
fn detects_cpio_filenames() {
assert!(is_cpio_filename("initramfs.cpio"));
assert!(is_cpio_filename("initramfs.CPIO.XZ"));
assert!(!is_cpio_filename("bundle.tar"));
}
#[test]
fn exposes_default_and_unknown_labels() {
assert_eq!(CpioFormat::default(), CpioFormat::Unknown);
assert_eq!(CpioFormat::CrcAscii.as_str(), "crc-ascii");
assert_eq!(CpioEntryKind::default(), CpioEntryKind::Unknown);
assert_eq!(CpioEntryKind::Socket.as_str(), "socket");
}
}