#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
pub const BROTLI_EXTENSION: &str = "br";
pub const TAR_BROTLI_EXTENSION: &str = "tar.br";
pub const BROTLI_EXTENSIONS: &[&str] = &["br", "brotli", "tar.br"];
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum BrotliExtension {
Br,
TarBr,
#[default]
Unknown,
}
impl BrotliExtension {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Br => "br",
Self::TarBr => "tar.br",
Self::Unknown => "unknown",
}
}
#[must_use]
pub fn from_extension(extension: &str) -> Self {
match normalize_extension(extension).as_str() {
"br" | "brotli" => Self::Br,
"tar.br" | "tar.brotli" => Self::TarBr,
_ => Self::Unknown,
}
}
}
impl fmt::Display for BrotliExtension {
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 BrotliLevel {
Fastest,
#[default]
Balanced,
Best,
Numeric(u32),
Unknown,
}
impl BrotliLevel {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Fastest => "fastest",
Self::Balanced => "balanced",
Self::Best => "best",
Self::Numeric(_) => "numeric",
Self::Unknown => "unknown",
}
}
#[must_use]
pub const fn numeric(self) -> Option<u32> {
match self {
Self::Numeric(level) => Some(level),
Self::Fastest | Self::Balanced | Self::Best | Self::Unknown => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum BrotliProfile {
#[default]
Generic,
Text,
Font,
Unknown,
}
impl BrotliProfile {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Generic => "generic",
Self::Text => "text",
Self::Font => "font",
Self::Unknown => "unknown",
}
}
}
#[must_use]
pub fn is_brotli_extension(extension: &str) -> bool {
!matches!(
BrotliExtension::from_extension(extension),
BrotliExtension::Unknown
)
}
#[must_use]
pub fn is_brotli_filename(name: &str) -> bool {
let parts = filename_parts(name);
match parts.as_slice() {
[.., last] if matches!(last.as_str(), "br" | "brotli") => true,
[.., previous, last] if previous == "tar" && matches!(last.as_str(), "br" | "brotli") => {
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::{
BROTLI_EXTENSIONS, BrotliExtension, BrotliLevel, BrotliProfile, is_brotli_extension,
is_brotli_filename,
};
#[test]
fn detects_brotli_extensions() {
assert!(is_brotli_extension(".br"));
assert!(is_brotli_extension("tar.br"));
assert_eq!(
BrotliExtension::from_extension("tar.br"),
BrotliExtension::TarBr
);
assert_eq!(BROTLI_EXTENSIONS[0], "br");
}
#[test]
fn detects_brotli_filenames() {
assert!(is_brotli_filename("release.tar.br"));
assert!(is_brotli_filename("payload.BR"));
assert!(!is_brotli_filename("bundle.zip"));
}
#[test]
fn exposes_default_and_unknown_labels() {
assert_eq!(BrotliExtension::default(), BrotliExtension::Unknown);
assert_eq!(BrotliLevel::default().as_str(), "balanced");
assert_eq!(BrotliLevel::Numeric(11).numeric(), Some(11));
assert_eq!(BrotliProfile::Unknown.as_str(), "unknown");
}
}