1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
7
8pub const BZIP2_EXTENSION: &str = "bz2";
10pub const TBZ_EXTENSION: &str = "tbz";
12pub const TBZ2_EXTENSION: &str = "tbz2";
14pub const TAR_BZIP2_EXTENSION: &str = "tar.bz2";
16pub const BZIP2_EXTENSIONS: &[&str] = &["bz2", "bzip2", "tbz", "tbz2", "tar.bz2"];
18
19#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
21pub enum Bzip2Extension {
22 Bz2,
24 Tbz,
26 Tbz2,
28 TarBz2,
30 #[default]
32 Unknown,
33}
34
35impl Bzip2Extension {
36 #[must_use]
38 pub const fn as_str(self) -> &'static str {
39 match self {
40 Self::Bz2 => "bz2",
41 Self::Tbz => "tbz",
42 Self::Tbz2 => "tbz2",
43 Self::TarBz2 => "tar.bz2",
44 Self::Unknown => "unknown",
45 }
46 }
47
48 #[must_use]
50 pub fn from_extension(extension: &str) -> Self {
51 match normalize_extension(extension).as_str() {
52 "bz2" | "bzip2" => Self::Bz2,
53 "tbz" => Self::Tbz,
54 "tbz2" => Self::Tbz2,
55 "tar.bz2" | "tar.bzip2" => Self::TarBz2,
56 _ => Self::Unknown,
57 }
58 }
59}
60
61impl fmt::Display for Bzip2Extension {
62 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
63 formatter.write_str(self.as_str())
64 }
65}
66
67#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
69pub enum Bzip2Level {
70 Fastest,
72 #[default]
74 Balanced,
75 Best,
77 Numeric(u32),
79 Unknown,
81}
82
83impl Bzip2Level {
84 #[must_use]
86 pub const fn as_str(self) -> &'static str {
87 match self {
88 Self::Fastest => "fastest",
89 Self::Balanced => "balanced",
90 Self::Best => "best",
91 Self::Numeric(_) => "numeric",
92 Self::Unknown => "unknown",
93 }
94 }
95
96 #[must_use]
98 pub const fn numeric(self) -> Option<u32> {
99 match self {
100 Self::Numeric(level) => Some(level),
101 Self::Fastest | Self::Balanced | Self::Best | Self::Unknown => None,
102 }
103 }
104}
105
106#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
108pub enum Bzip2Profile {
109 Speed,
111 Size,
113 #[default]
115 Balanced,
116 Compatibility,
118 Unknown,
120}
121
122impl Bzip2Profile {
123 #[must_use]
125 pub const fn as_str(self) -> &'static str {
126 match self {
127 Self::Speed => "speed",
128 Self::Size => "size",
129 Self::Balanced => "balanced",
130 Self::Compatibility => "compatibility",
131 Self::Unknown => "unknown",
132 }
133 }
134}
135
136#[must_use]
138pub fn is_bzip2_extension(extension: &str) -> bool {
139 !matches!(
140 Bzip2Extension::from_extension(extension),
141 Bzip2Extension::Unknown
142 )
143}
144
145#[must_use]
147pub fn is_bzip2_filename(name: &str) -> bool {
148 let parts = filename_parts(name);
149
150 match parts.as_slice() {
151 [.., last] if matches!(last.as_str(), "bz2" | "bzip2" | "tbz" | "tbz2") => true,
152 [.., previous, last] if previous == "tar" && matches!(last.as_str(), "bz2" | "bzip2") => {
153 true
154 },
155 _ => false,
156 }
157}
158
159fn normalize_extension(extension: &str) -> String {
160 extension
161 .trim()
162 .trim_start_matches('.')
163 .to_ascii_lowercase()
164}
165
166fn filename_parts(name: &str) -> Vec<String> {
167 name.trim()
168 .to_ascii_lowercase()
169 .rsplit(['/', '\\'])
170 .next()
171 .unwrap_or_default()
172 .trim_start_matches('.')
173 .split('.')
174 .filter(|part| !part.is_empty())
175 .map(str::to_owned)
176 .collect()
177}
178
179#[cfg(test)]
180mod tests {
181 use super::{
182 BZIP2_EXTENSIONS, Bzip2Extension, Bzip2Level, Bzip2Profile, is_bzip2_extension,
183 is_bzip2_filename,
184 };
185
186 #[test]
187 fn detects_bzip2_extensions() {
188 assert!(is_bzip2_extension(".bz2"));
189 assert!(is_bzip2_extension("tar.bz2"));
190 assert_eq!(Bzip2Extension::from_extension("tbz2"), Bzip2Extension::Tbz2);
191 assert_eq!(BZIP2_EXTENSIONS[0], "bz2");
192 }
193
194 #[test]
195 fn detects_bzip2_filenames() {
196 assert!(is_bzip2_filename("release.tar.bz2"));
197 assert!(is_bzip2_filename("bundle.TBZ2"));
198 assert!(!is_bzip2_filename("bundle.zip"));
199 }
200
201 #[test]
202 fn exposes_default_and_unknown_labels() {
203 assert_eq!(Bzip2Extension::default(), Bzip2Extension::Unknown);
204 assert_eq!(Bzip2Level::default().as_str(), "balanced");
205 assert_eq!(Bzip2Level::Numeric(9).numeric(), Some(9));
206 assert_eq!(Bzip2Profile::Unknown.as_str(), "unknown");
207 }
208}