1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
7
8pub const LZ4_EXTENSION: &str = "lz4";
10pub const TAR_LZ4_EXTENSION: &str = "tar.lz4";
12pub const LZ4_EXTENSIONS: &[&str] = &["lz4", "tar.lz4"];
14
15#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
17pub enum Lz4Extension {
18 Lz4,
20 TarLz4,
22 #[default]
24 Unknown,
25}
26
27impl Lz4Extension {
28 #[must_use]
30 pub const fn as_str(self) -> &'static str {
31 match self {
32 Self::Lz4 => "lz4",
33 Self::TarLz4 => "tar.lz4",
34 Self::Unknown => "unknown",
35 }
36 }
37
38 #[must_use]
40 pub fn from_extension(extension: &str) -> Self {
41 match normalize_extension(extension).as_str() {
42 "lz4" => Self::Lz4,
43 "tar.lz4" => Self::TarLz4,
44 _ => Self::Unknown,
45 }
46 }
47}
48
49impl fmt::Display for Lz4Extension {
50 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51 formatter.write_str(self.as_str())
52 }
53}
54
55#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
57pub enum Lz4FrameKind {
58 #[default]
60 Standard,
61 Legacy,
63 Skippable,
65 Block,
67 Unknown,
69}
70
71impl Lz4FrameKind {
72 #[must_use]
74 pub const fn as_str(self) -> &'static str {
75 match self {
76 Self::Standard => "standard",
77 Self::Legacy => "legacy",
78 Self::Skippable => "skippable",
79 Self::Block => "block",
80 Self::Unknown => "unknown",
81 }
82 }
83}
84
85#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
87pub enum Lz4Profile {
88 Speed,
90 Size,
92 #[default]
94 Balanced,
95 Unknown,
97}
98
99impl Lz4Profile {
100 #[must_use]
102 pub const fn as_str(self) -> &'static str {
103 match self {
104 Self::Speed => "speed",
105 Self::Size => "size",
106 Self::Balanced => "balanced",
107 Self::Unknown => "unknown",
108 }
109 }
110}
111
112#[must_use]
114pub fn is_lz4_extension(extension: &str) -> bool {
115 !matches!(
116 Lz4Extension::from_extension(extension),
117 Lz4Extension::Unknown
118 )
119}
120
121#[must_use]
123pub fn is_lz4_filename(name: &str) -> bool {
124 let parts = filename_parts(name);
125
126 match parts.as_slice() {
127 [.., last] if last == "lz4" => true,
128 [.., previous, last] if previous == "tar" && last == "lz4" => true,
129 _ => false,
130 }
131}
132
133fn normalize_extension(extension: &str) -> String {
134 extension
135 .trim()
136 .trim_start_matches('.')
137 .to_ascii_lowercase()
138}
139
140fn filename_parts(name: &str) -> Vec<String> {
141 name.trim()
142 .to_ascii_lowercase()
143 .rsplit(['/', '\\'])
144 .next()
145 .unwrap_or_default()
146 .trim_start_matches('.')
147 .split('.')
148 .filter(|part| !part.is_empty())
149 .map(str::to_owned)
150 .collect()
151}
152
153#[cfg(test)]
154mod tests {
155 use super::{
156 LZ4_EXTENSIONS, Lz4Extension, Lz4FrameKind, Lz4Profile, is_lz4_extension, is_lz4_filename,
157 };
158
159 #[test]
160 fn detects_lz4_extensions() {
161 assert!(is_lz4_extension(".lz4"));
162 assert!(is_lz4_extension("tar.lz4"));
163 assert_eq!(
164 Lz4Extension::from_extension("tar.lz4"),
165 Lz4Extension::TarLz4
166 );
167 assert_eq!(LZ4_EXTENSIONS[0], "lz4");
168 }
169
170 #[test]
171 fn detects_lz4_filenames() {
172 assert!(is_lz4_filename("release.tar.lz4"));
173 assert!(is_lz4_filename("payload.LZ4"));
174 assert!(!is_lz4_filename("bundle.zip"));
175 }
176
177 #[test]
178 fn exposes_default_and_unknown_labels() {
179 assert_eq!(Lz4Extension::default(), Lz4Extension::Unknown);
180 assert_eq!(Lz4FrameKind::default().as_str(), "standard");
181 assert_eq!(Lz4Profile::Unknown.as_str(), "unknown");
182 }
183}