Skip to main content

use_bzip2/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Bzip2 labels and option metadata for `RustUse`.
5
6use core::fmt;
7
8/// Common bzip2 file extension.
9pub const BZIP2_EXTENSION: &str = "bz2";
10/// Common abbreviated bzip2-compressed tar extension.
11pub const TBZ_EXTENSION: &str = "tbz";
12/// Common abbreviated bzip2-compressed tar extension.
13pub const TBZ2_EXTENSION: &str = "tbz2";
14/// Common bzip2-compressed tar extension.
15pub const TAR_BZIP2_EXTENSION: &str = "tar.bz2";
16/// Common bzip2-related extensions.
17pub const BZIP2_EXTENSIONS: &[&str] = &["bz2", "bzip2", "tbz", "tbz2", "tar.bz2"];
18
19/// Bzip2 extension labels.
20#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
21pub enum Bzip2Extension {
22    /// `.bz2` payload.
23    Bz2,
24    /// `.tbz` tar shorthand.
25    Tbz,
26    /// `.tbz2` tar shorthand.
27    Tbz2,
28    /// `.tar.bz2` tar plus bzip2 extension.
29    TarBz2,
30    /// Unknown or unsupported bzip2 extension label.
31    #[default]
32    Unknown,
33}
34
35impl Bzip2Extension {
36    /// Returns a stable lowercase label.
37    #[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    /// Detects a bzip2 extension label.
49    #[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/// Bzip2 compression level labels.
68#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
69pub enum Bzip2Level {
70    /// Prefer fastest compression.
71    Fastest,
72    /// Balance speed and compressed size.
73    #[default]
74    Balanced,
75    /// Prefer best compressed size.
76    Best,
77    /// Caller-specified numeric level.
78    Numeric(u32),
79    /// Unknown or intentionally unspecified level.
80    Unknown,
81}
82
83impl Bzip2Level {
84    /// Returns a stable lowercase label.
85    #[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    /// Returns the numeric level when this is [`Bzip2Level::Numeric`].
97    #[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/// High-level bzip2 usage profile.
107#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
108pub enum Bzip2Profile {
109    /// Prefer compression speed.
110    Speed,
111    /// Prefer compressed size.
112    Size,
113    /// Balance speed and size.
114    #[default]
115    Balanced,
116    /// Prefer compatibility with broad tooling.
117    Compatibility,
118    /// Unknown or intentionally unspecified profile.
119    Unknown,
120}
121
122impl Bzip2Profile {
123    /// Returns a stable lowercase label.
124    #[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/// Returns whether `extension` is a known bzip2 extension label.
137#[must_use]
138pub fn is_bzip2_extension(extension: &str) -> bool {
139    !matches!(
140        Bzip2Extension::from_extension(extension),
141        Bzip2Extension::Unknown
142    )
143}
144
145/// Returns whether `name` has a known bzip2 filename encoding.
146#[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}