1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
7
8#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
10pub enum ArchiveEntryKind {
11 #[default]
13 File,
14 Directory,
16 Symlink,
18 Hardlink,
20 Device,
22 Fifo,
24 Unknown,
26}
27
28impl ArchiveEntryKind {
29 #[must_use]
31 pub const fn as_str(self) -> &'static str {
32 match self {
33 Self::File => "file",
34 Self::Directory => "directory",
35 Self::Symlink => "symlink",
36 Self::Hardlink => "hardlink",
37 Self::Device => "device",
38 Self::Fifo => "fifo",
39 Self::Unknown => "unknown",
40 }
41 }
42
43 #[must_use]
45 pub const fn is_link(self) -> bool {
46 matches!(self, Self::Symlink | Self::Hardlink)
47 }
48}
49
50impl fmt::Display for ArchiveEntryKind {
51 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52 formatter.write_str(self.as_str())
53 }
54}
55
56#[derive(Clone, Debug, Eq, Hash, PartialEq)]
58pub struct ArchiveEntry {
59 pub path: String,
61 pub kind: ArchiveEntryKind,
63 pub size: Option<u64>,
65 pub mode: Option<u32>,
67 pub modified_unix_seconds: Option<i64>,
69}
70
71impl ArchiveEntry {
72 #[must_use]
74 pub fn new(path: impl Into<String>, kind: ArchiveEntryKind) -> Self {
75 Self {
76 path: path.into(),
77 kind,
78 size: None,
79 mode: None,
80 modified_unix_seconds: None,
81 }
82 }
83
84 #[must_use]
86 pub fn file(path: impl Into<String>) -> Self {
87 Self::new(path, ArchiveEntryKind::File)
88 }
89
90 #[must_use]
92 pub fn directory(path: impl Into<String>) -> Self {
93 Self::new(path, ArchiveEntryKind::Directory)
94 }
95
96 #[must_use]
98 pub fn symlink(path: impl Into<String>) -> Self {
99 Self::new(path, ArchiveEntryKind::Symlink)
100 }
101
102 #[must_use]
104 pub const fn with_size(mut self, size: u64) -> Self {
105 self.size = Some(size);
106 self
107 }
108
109 #[must_use]
111 pub const fn with_mode(mut self, mode: u32) -> Self {
112 self.mode = Some(mode);
113 self
114 }
115
116 #[must_use]
118 pub const fn with_modified_unix_seconds(mut self, seconds: i64) -> Self {
119 self.modified_unix_seconds = Some(seconds);
120 self
121 }
122
123 #[must_use]
125 pub fn path(&self) -> &str {
126 &self.path
127 }
128
129 #[must_use]
131 pub const fn kind(&self) -> ArchiveEntryKind {
132 self.kind
133 }
134
135 #[must_use]
137 pub const fn size(&self) -> Option<u64> {
138 self.size
139 }
140
141 #[must_use]
143 pub const fn mode(&self) -> Option<u32> {
144 self.mode
145 }
146
147 #[must_use]
149 pub const fn modified_unix_seconds(&self) -> Option<i64> {
150 self.modified_unix_seconds
151 }
152
153 #[must_use]
155 pub const fn is_file(&self) -> bool {
156 matches!(self.kind, ArchiveEntryKind::File)
157 }
158
159 #[must_use]
161 pub const fn is_directory(&self) -> bool {
162 matches!(self.kind, ArchiveEntryKind::Directory)
163 }
164
165 #[must_use]
167 pub const fn is_symlink(&self) -> bool {
168 matches!(self.kind, ArchiveEntryKind::Symlink)
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::{ArchiveEntry, ArchiveEntryKind};
175
176 #[test]
177 fn creates_file_entry_metadata() {
178 let entry = ArchiveEntry::file("docs/readme.md")
179 .with_size(128)
180 .with_mode(0o644)
181 .with_modified_unix_seconds(1_700_000_000);
182
183 assert_eq!(entry.path(), "docs/readme.md");
184 assert_eq!(entry.kind(), ArchiveEntryKind::File);
185 assert_eq!(entry.size(), Some(128));
186 assert_eq!(entry.mode(), Some(0o644));
187 assert!(entry.is_file());
188 }
189
190 #[test]
191 fn identifies_link_like_kinds() {
192 assert!(ArchiveEntryKind::Symlink.is_link());
193 assert!(ArchiveEntryKind::Hardlink.is_link());
194 assert!(!ArchiveEntryKind::Directory.is_link());
195 }
196}