Skip to main content

use_archive_entry/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Archive entry metadata primitives for `RustUse`.
5
6use core::fmt;
7
8/// Generic archive entry kinds.
9#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
10pub enum ArchiveEntryKind {
11    /// Regular file entry.
12    #[default]
13    File,
14    /// Directory entry.
15    Directory,
16    /// Symbolic link entry.
17    Symlink,
18    /// Hard link entry.
19    Hardlink,
20    /// Device entry.
21    Device,
22    /// FIFO entry.
23    Fifo,
24    /// Unknown or intentionally unspecified entry kind.
25    Unknown,
26}
27
28impl ArchiveEntryKind {
29    /// Returns a stable lowercase label.
30    #[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    /// Returns whether the entry kind is link-like.
44    #[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/// Generic archive entry metadata.
57#[derive(Clone, Debug, Eq, Hash, PartialEq)]
58pub struct ArchiveEntry {
59    /// Archive-internal path.
60    pub path: String,
61    /// Archive entry kind.
62    pub kind: ArchiveEntryKind,
63    /// Entry payload size in bytes, when known.
64    pub size: Option<u64>,
65    /// Entry mode or permission bits, when known.
66    pub mode: Option<u32>,
67    /// Unix timestamp seconds for the modified time, when known.
68    pub modified_unix_seconds: Option<i64>,
69}
70
71impl ArchiveEntry {
72    /// Creates archive entry metadata from a path and kind.
73    #[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    /// Creates a regular file entry.
85    #[must_use]
86    pub fn file(path: impl Into<String>) -> Self {
87        Self::new(path, ArchiveEntryKind::File)
88    }
89
90    /// Creates a directory entry.
91    #[must_use]
92    pub fn directory(path: impl Into<String>) -> Self {
93        Self::new(path, ArchiveEntryKind::Directory)
94    }
95
96    /// Creates a symbolic link entry.
97    #[must_use]
98    pub fn symlink(path: impl Into<String>) -> Self {
99        Self::new(path, ArchiveEntryKind::Symlink)
100    }
101
102    /// Adds known entry size metadata.
103    #[must_use]
104    pub const fn with_size(mut self, size: u64) -> Self {
105        self.size = Some(size);
106        self
107    }
108
109    /// Adds known entry mode metadata.
110    #[must_use]
111    pub const fn with_mode(mut self, mode: u32) -> Self {
112        self.mode = Some(mode);
113        self
114    }
115
116    /// Adds known modified timestamp metadata.
117    #[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    /// Returns the archive-internal path.
124    #[must_use]
125    pub fn path(&self) -> &str {
126        &self.path
127    }
128
129    /// Returns the archive entry kind.
130    #[must_use]
131    pub const fn kind(&self) -> ArchiveEntryKind {
132        self.kind
133    }
134
135    /// Returns the known entry size.
136    #[must_use]
137    pub const fn size(&self) -> Option<u64> {
138        self.size
139    }
140
141    /// Returns the known entry mode.
142    #[must_use]
143    pub const fn mode(&self) -> Option<u32> {
144        self.mode
145    }
146
147    /// Returns the known modified timestamp.
148    #[must_use]
149    pub const fn modified_unix_seconds(&self) -> Option<i64> {
150        self.modified_unix_seconds
151    }
152
153    /// Returns whether this is a regular file entry.
154    #[must_use]
155    pub const fn is_file(&self) -> bool {
156        matches!(self.kind, ArchiveEntryKind::File)
157    }
158
159    /// Returns whether this is a directory entry.
160    #[must_use]
161    pub const fn is_directory(&self) -> bool {
162        matches!(self.kind, ArchiveEntryKind::Directory)
163    }
164
165    /// Returns whether this is a symbolic link entry.
166    #[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}