#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
pub const MTREE_EXTENSION: &str = "mtree";
pub const MTREE_GZIP_EXTENSION: &str = "mtree.gz";
pub const MTREE_EXTENSIONS: &[&str] = &["mtree", "mtree.gz"];
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum MtreeFormat {
Bsd,
NetBsd,
FreeBsd,
#[default]
Unknown,
}
impl MtreeFormat {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Bsd => "bsd",
Self::NetBsd => "netbsd",
Self::FreeBsd => "freebsd",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for MtreeFormat {
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 MtreeEntryKind {
File,
Directory,
Link,
Device,
#[default]
Unknown,
}
impl MtreeEntryKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::File => "file",
Self::Directory => "directory",
Self::Link => "link",
Self::Device => "device",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for MtreeEntryKind {
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 MtreeKeyword {
Type,
Uid,
Gid,
Mode,
Size,
Time,
Sha256Digest,
#[default]
Unknown,
}
impl MtreeKeyword {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Type => "type",
Self::Uid => "uid",
Self::Gid => "gid",
Self::Mode => "mode",
Self::Size => "size",
Self::Time => "time",
Self::Sha256Digest => "sha256digest",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for MtreeKeyword {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[must_use]
pub fn is_mtree_extension(extension: &str) -> bool {
matches!(
normalize_extension(extension).as_str(),
"mtree" | "mtree.gz"
)
}
#[must_use]
pub fn is_mtree_filename(name: &str) -> bool {
let parts = filename_parts(name);
match parts.as_slice() {
[.., last] if last == "mtree" => true,
[.., previous, last] if previous == "mtree" && last == "gz" => 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::{
MTREE_EXTENSIONS, MtreeEntryKind, MtreeFormat, MtreeKeyword, is_mtree_extension,
is_mtree_filename,
};
#[test]
fn detects_mtree_extensions() {
assert!(is_mtree_extension(".mtree"));
assert!(is_mtree_extension("mtree.gz"));
assert_eq!(MTREE_EXTENSIONS[0], "mtree");
}
#[test]
fn detects_mtree_filenames() {
assert!(is_mtree_filename("manifest.mtree"));
assert!(is_mtree_filename("manifest.MTREE.GZ"));
assert!(!is_mtree_filename("bundle.zip"));
}
#[test]
fn exposes_default_and_unknown_labels() {
assert_eq!(MtreeFormat::default(), MtreeFormat::Unknown);
assert_eq!(MtreeFormat::NetBsd.as_str(), "netbsd");
assert_eq!(MtreeEntryKind::default(), MtreeEntryKind::Unknown);
assert_eq!(MtreeEntryKind::Link.as_str(), "link");
assert_eq!(MtreeKeyword::default(), MtreeKeyword::Unknown);
assert_eq!(MtreeKeyword::Sha256Digest.as_str(), "sha256digest");
}
}