Skip to main content

hadris_common/types/
file.rs

1use core::{fmt, ops::Range};
2
3/// A fixed-length, stack-allocated filename buffer.
4///
5/// Stores up to `N` bytes of filename data on the stack without
6/// heap allocation. Useful for no-std filesystem implementations
7/// where filenames have a known maximum length.
8///
9/// # Example
10///
11/// ```rust
12/// use hadris_common::types::file::FixedFilename;
13///
14/// let name = FixedFilename::<64>::from(b"readme.txt".as_slice());
15/// assert_eq!(name.len(), 10);
16/// assert_eq!(name.as_str(), "readme.txt");
17/// assert!(!name.is_empty());
18/// ```
19#[derive(Clone, Copy, PartialEq, Eq)]
20pub struct FixedFilename<const N: usize> {
21    pub data: [u8; N],
22    pub len: usize,
23}
24
25impl<const N: usize> FixedFilename<N> {
26    pub const fn empty() -> Self {
27        Self {
28            data: [0; N],
29            len: 0,
30        }
31    }
32
33    pub const fn with_size(size: usize) -> Self {
34        assert!(size <= N);
35        Self {
36            data: [0; N],
37            len: size,
38        }
39    }
40
41    pub fn as_str(&self) -> &str {
42        unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
43    }
44
45    pub fn allocate(&mut self, bytes: usize) {
46        let len = self.len;
47        assert!(bytes + len <= N);
48        self.len += bytes;
49        //self.data[len..self.len]
50    }
51
52    pub fn as_bytes(&self) -> &[u8] {
53        &self.data[0..self.len]
54    }
55
56    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
57        &mut self.data[0..self.len]
58    }
59
60    pub fn truncate(&mut self, new_size: usize) {
61        assert!(new_size <= N);
62        self.len = new_size;
63    }
64
65    pub fn len(&self) -> usize {
66        self.len
67    }
68
69    pub fn is_empty(&self) -> bool {
70        self.len == 0
71    }
72
73    pub fn push_slice(&mut self, slice: &[u8]) -> Range<usize> {
74        assert!(self.len + slice.len() <= self.data.len());
75        let start = self.len;
76        self.len += slice.len();
77        self.data[start..self.len].copy_from_slice(slice);
78        start..self.len
79    }
80
81    pub fn push_byte(&mut self, b: u8) -> usize {
82        assert!(self.len < N);
83        self.data[self.len] = b;
84        self.len += 1;
85        self.len - 1
86    }
87
88    pub fn try_push_slice(&mut self, slice: &[u8]) -> Option<Range<usize>> {
89        if self.len + slice.len() > N {
90            return None;
91        }
92        Some(self.push_slice(slice))
93    }
94
95    pub fn try_push_byte(&mut self, b: u8) -> Option<usize> {
96        if self.len >= N {
97            return None;
98        }
99        Some(self.push_byte(b))
100    }
101
102    pub fn remaining_capacity(&self) -> usize {
103        N - self.len
104    }
105}
106
107impl<const N: usize> fmt::Debug for FixedFilename<N> {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.debug_tuple("FixedFilename")
110            .field(&self.as_str())
111            .finish()
112    }
113}
114
115impl<const N: usize> fmt::Display for FixedFilename<N> {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.write_str(self.as_str())
118    }
119}
120
121impl<const N: usize> From<&[u8]> for FixedFilename<N> {
122    fn from(value: &[u8]) -> Self {
123        assert!(value.len() <= N);
124        let mut str = FixedFilename::with_size(value.len());
125        str.data[0..value.len()].copy_from_slice(value);
126        str
127    }
128}