1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
7
8pub const AR_STATIC_LIBRARY_EXTENSION: &str = "a";
10pub const AR_EXTENSION: &str = "ar";
12pub const DEBIAN_PACKAGE_EXTENSION: &str = "deb";
14pub const AR_EXTENSIONS: &[&str] = &["a", "ar", "deb"];
16
17#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub enum ArFormat {
20 Gnu,
22 Bsd,
24 Common,
26 #[default]
28 Unknown,
29}
30
31impl ArFormat {
32 #[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#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
52pub enum ArEntryKind {
53 Member,
55 SymbolTable,
57 StringTable,
59 #[default]
61 Unknown,
62}
63
64impl ArEntryKind {
65 #[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#[must_use]
85pub fn is_ar_extension(extension: &str) -> bool {
86 matches!(normalize_extension(extension).as_str(), "a" | "ar" | "deb")
87}
88
89#[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}