#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
pub const SEVEN_ZIP_EXTENSION: &str = "7z";
pub const SEVEN_ZIP_FIRST_VOLUME_EXTENSION: &str = "7z.001";
pub const SEVEN_ZIP_EXTENSIONS: &[&str] = &["7z", "7z.001"];
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum SevenZipFormat {
SevenZip,
SplitVolume,
#[default]
Unknown,
}
impl SevenZipFormat {
#[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())
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum SevenZipCompressionMethod {
Copy,
Lzma,
Lzma2,
Bzip2,
Deflate,
Ppmd,
#[default]
Unknown,
}
impl SevenZipCompressionMethod {
#[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())
}
}
#[must_use]
pub fn is_7z_extension(extension: &str) -> bool {
let normalized = normalize_extension(extension);
normalized == "7z" || is_7z_volume_extension(&normalized)
}
#[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");
}
}