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}