local_fmt/message/
alloc.rs

1use std::{fmt::Display, str::FromStr};
2
3use super::CreateMessageError;
4
5/// Represents a format for an allocatable message, which can be either text or a placeholder.
6///
7/// # Examples
8///
9/// ```rust
10/// use local_fmt::AllocMessageFormat;
11///
12/// let text_format = AllocMessageFormat::AllocText(String::from("Hello"));
13/// let placeholder_format = AllocMessageFormat::Placeholder(0);
14///
15/// assert_eq!(format!("{}", text_format), "Hello");
16/// assert_eq!(format!("{}", placeholder_format), "{0}");
17#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
18pub enum AllocMessageFormat {
19    AllocText(String),
20    Placeholder(usize),
21}
22
23impl Display for AllocMessageFormat {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            AllocMessageFormat::AllocText(text) => write!(f, "{}", text),
27            AllocMessageFormat::Placeholder(n) => write!(f, "{{{}}}", n),
28        }
29    }
30}
31
32/// A message format that can be allocated with a fixed number of placeholders.
33///
34/// # Examples
35///
36/// ```rust
37/// use local_fmt::{AllocMessage, AllocMessageFormat};
38///
39/// let message = AllocMessage::<1>::new(vec![
40///     AllocMessageFormat::AllocText(String::from("Hello, ")),
41///     AllocMessageFormat::Placeholder(0),
42/// ]).unwrap();
43///
44/// let formatted = message.format(&["world"]);
45/// assert_eq!(formatted, "Hello, world");
46/// ```
47#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
48pub struct AllocMessage<const N: usize> {
49    format: Vec<AllocMessageFormat>,
50}
51
52impl<const N: usize> AllocMessage<N> {
53    /// Creates a new `AllocMessage` without checking the format.
54    ///
55    /// # Safety
56    /// The caller must ensure that the format is correct.
57    ///
58    /// # Examples
59    ///
60    /// ```rust
61    /// use local_fmt::{AllocMessage, AllocMessageFormat};
62    ///
63    /// let message = unsafe {
64    ///     AllocMessage::<1>::new_unchecked(vec![
65    ///         AllocMessageFormat::AllocText(String::from("Hello, ")),
66    ///         AllocMessageFormat::Placeholder(0),
67    ///     ])
68    /// };
69    ///
70    /// let formatted = message.format(&["world"]);
71    /// assert_eq!(formatted, "Hello, world");
72    /// ```
73    pub unsafe fn new_unchecked(format: Vec<AllocMessageFormat>) -> Self {
74        Self { format }
75    }
76
77    /// Creates a new `AllocMessage` with format checking.
78    ///
79    /// Returns an error if the format is invalid, such as missing placeholders.
80    ///
81    /// # Examples
82    ///
83    /// ```rust
84    /// use local_fmt::{AllocMessage, AllocMessageFormat, CreateMessageError};
85    ///
86    /// let result = AllocMessage::<1>::new(vec![
87    ///     AllocMessageFormat::AllocText(String::from("Hello, ")),
88    ///     AllocMessageFormat::Placeholder(0),
89    /// ]).unwrap();
90    ///
91    /// let formatted = result.format(&["world"]);
92    /// assert_eq!(formatted, "Hello, world");
93    /// ```
94    pub fn new(format: Vec<AllocMessageFormat>) -> Result<Self, CreateMessageError> {
95        let mut numbers = Vec::new();
96
97        let mut current = 0;
98
99        while format.len() > current {
100            if let AllocMessageFormat::Placeholder(n) = format[current] {
101                if n >= numbers.len() {
102                    numbers.resize_with(n + 1, Default::default);
103                }
104                numbers[n] = true;
105            }
106            current += 1;
107        }
108
109        let mut current = 0;
110
111        while numbers.len() > current {
112            if !numbers[current] {
113                return Err(CreateMessageError::WithoutNumber {
114                    number: current,
115                    n: N,
116                });
117            }
118            current += 1;
119        }
120
121        Ok(Self { format })
122    }
123
124    pub fn new_panic(format: Vec<AllocMessageFormat>) -> Self {
125        match Self::new(format) {
126            Ok(message) => message,
127            Err(error) => error.panic(),
128        }
129    }
130
131    /// Formats the message with the provided arguments.
132    ///
133    /// # Arguments
134    ///
135    /// * `args` - A slice of string references to be used as arguments in the placeholders.
136    ///
137    /// # Examples
138    ///
139    /// ```rust
140    /// use local_fmt::{AllocMessage, AllocMessageFormat};
141    ///
142    /// let message = AllocMessage::<1>::new(vec![
143    ///     AllocMessageFormat::AllocText(String::from("Hello, ")),
144    ///     AllocMessageFormat::Placeholder(0),
145    /// ]).unwrap();
146    ///
147    /// let formatted = message.format(&["world"]);
148    /// assert_eq!(formatted, "Hello, world");
149    /// ```
150    pub fn format(&self, args: &[&str; N]) -> String {
151        let mut result = String::new();
152
153        for format in &self.format {
154            match format {
155                AllocMessageFormat::AllocText(text) => result.push_str(text),
156                AllocMessageFormat::Placeholder(n) => result.push_str(args[*n]),
157            }
158        }
159
160        result
161    }
162
163    pub fn len(&self) -> usize {
164        self.format.len()
165    }
166
167    pub fn is_empty(&self) -> bool {
168        self.format.is_empty()
169    }
170
171    pub fn formats(&self) -> &Vec<AllocMessageFormat> {
172        &self.format
173    }
174}
175
176impl<const N: usize> Display for AllocMessage<N> {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        for format in &self.format {
179            write!(f, "{}", format)?;
180        }
181        Ok(())
182    }
183}
184
185impl<const N: usize> FromStr for AllocMessage<N> {
186    type Err = CreateMessageError;
187
188    fn from_str(s: &str) -> Result<Self, Self::Err> {
189        let mut formats = Vec::<AllocMessageFormat>::new();
190
191        let mut buffer = Vec::<u8>::new();
192
193        let mut bytes = s.bytes();
194
195        while let Some(byte) = bytes.next() {
196            match byte {
197                b'{' => {
198                    if !buffer.is_empty() {
199                        formats.push(AllocMessageFormat::AllocText(unsafe {
200                            String::from_utf8_unchecked(std::mem::take(&mut buffer))
201                        }));
202                    }
203
204                    let mut number = None::<usize>;
205
206                    loop {
207                        match bytes.next() {
208                            Some(byte) => match byte {
209                                b'}' => {
210                                    if let Some(number) = number {
211                                        formats.push(AllocMessageFormat::Placeholder(number));
212                                        break;
213                                    } else {
214                                        return Err(CreateMessageError::EmptyPlaceholder);
215                                    }
216                                }
217                                b'0'..=b'9' => {
218                                    let mut num = number.unwrap_or(0);
219                                    num *= 10;
220                                    num += (byte - b'0') as usize;
221                                    number = Some(num);
222                                }
223                                _ => match number {
224                                    Some(number) => {
225                                        return Err(CreateMessageError::InvalidNumber {
226                                            number,
227                                            n: N,
228                                        });
229                                    }
230                                    None => {
231                                        return Err(CreateMessageError::EmptyPlaceholder);
232                                    }
233                                },
234                            },
235                            None => {
236                                return Err(CreateMessageError::EmptyPlaceholder);
237                            }
238                        }
239                    }
240                }
241                b'\\' => {
242                    if let Some(byte) = bytes.next() {
243                        match byte {
244                            b'{' => buffer.push(b'{'),
245                            _ => {
246                                buffer.push(b'\\');
247                                buffer.push(byte);
248                            }
249                        }
250                    } else {
251                        buffer.push(b'\\');
252                    }
253                }
254                _ => buffer.push(byte),
255            }
256        }
257
258        if !buffer.is_empty() {
259            formats.push(AllocMessageFormat::AllocText(unsafe {
260                String::from_utf8_unchecked(buffer)
261            }));
262        }
263
264        Self::new(formats)
265    }
266}