Skip to main content

use_ar/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Unix `ar` archive labels and entry kind metadata for `RustUse`.
5
6use core::fmt;
7
8/// Common static-library archive extension.
9pub const AR_STATIC_LIBRARY_EXTENSION: &str = "a";
10/// Common Unix ar extension.
11pub const AR_EXTENSION: &str = "ar";
12/// Common Debian package extension backed by ar containers.
13pub const DEBIAN_PACKAGE_EXTENSION: &str = "deb";
14/// Common ar-related extensions.
15pub const AR_EXTENSIONS: &[&str] = &["a", "ar", "deb"];
16
17/// Unix `ar` format labels.
18#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub enum ArFormat {
20    /// GNU ar variant.
21    Gnu,
22    /// BSD ar variant.
23    Bsd,
24    /// Common ar container label.
25    Common,
26    /// Unknown or intentionally unspecified ar format.
27    #[default]
28    Unknown,
29}
30
31impl ArFormat {
32    /// Returns a stable lowercase label.
33    #[must_use]
34    pub const fn as_str(self) -> &'static str {
35        match self {
36            Self::Gnu => "gnu",
37            Self::Bsd => "bsd",
38            Self::Common => "common",
39            Self::Unknown => "unknown",
40        }
41    }
42}
43
44impl fmt::Display for ArFormat {
45    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
46        formatter.write_str(self.as_str())
47    }
48}
49
50/// Unix `ar` entry kind labels.
51#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
52pub enum ArEntryKind {
53    /// Regular archive member.
54    Member,
55    /// Symbol table member.
56    SymbolTable,
57    /// String table member.
58    StringTable,
59    /// Unknown or unsupported ar entry kind.
60    #[default]
61    Unknown,
62}
63
64impl ArEntryKind {
65    /// Returns a stable lowercase label.
66    #[must_use]
67    pub const fn as_str(self) -> &'static str {
68        match self {
69            Self::Member => "member",
70            Self::SymbolTable => "symbol-table",
71            Self::StringTable => "string-table",
72            Self::Unknown => "unknown",
73        }
74    }
75}
76
77impl fmt::Display for ArEntryKind {
78    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79        formatter.write_str(self.as_str())
80    }
81}
82
83/// Returns whether `extension` is a known ar extension label.
84#[must_use]
85pub fn is_ar_extension(extension: &str) -> bool {
86    matches!(normalize_extension(extension).as_str(), "a" | "ar" | "deb")
87}
88
89/// Returns whether `name` has a known ar filename encoding.
90#[must_use]
91pub fn is_ar_filename(name: &str) -> bool {
92    let parts = filename_parts(name);
93
94    matches!(parts.as_slice(), [.., last] if matches!(last.as_str(), "a" | "ar" | "deb"))
95}
96
97fn normalize_extension(extension: &str) -> String {
98    extension
99        .trim()
100        .trim_start_matches('.')
101        .to_ascii_lowercase()
102}
103
104fn filename_parts(name: &str) -> Vec<String> {
105    name.trim()
106        .to_ascii_lowercase()
107        .rsplit(['/', '\\'])
108        .next()
109        .unwrap_or_default()
110        .trim_start_matches('.')
111        .split('.')
112        .filter(|part| !part.is_empty())
113        .map(str::to_owned)
114        .collect()
115}
116
117#[cfg(test)]
118mod tests {
119    use super::{AR_EXTENSIONS, ArEntryKind, ArFormat, is_ar_extension, is_ar_filename};
120
121    #[test]
122    fn detects_ar_extensions() {
123        assert!(is_ar_extension(".a"));
124        assert!(is_ar_extension("ar"));
125        assert!(is_ar_extension("deb"));
126        assert_eq!(AR_EXTENSIONS[0], "a");
127    }
128
129    #[test]
130    fn detects_ar_filenames() {
131        assert!(is_ar_filename("libexample.a"));
132        assert!(is_ar_filename("package.DEB"));
133        assert!(!is_ar_filename("bundle.zip"));
134    }
135
136    #[test]
137    fn exposes_default_and_unknown_labels() {
138        assert_eq!(ArFormat::default(), ArFormat::Unknown);
139        assert_eq!(ArFormat::Bsd.as_str(), "bsd");
140        assert_eq!(ArEntryKind::default(), ArEntryKind::Unknown);
141        assert_eq!(ArEntryKind::StringTable.as_str(), "string-table");
142    }
143}