1#![cfg_attr(not(feature = "std"), no_std)]
35
36#[cfg(feature = "std")]
37mod alloc_impls;
38#[cfg(feature = "std")]
39pub use alloc_impls::*;
40
41#[cfg(feature = "std")]
42mod compiled;
43#[cfg(feature = "std")]
44pub use compiled::ParsedFmt;
45
46mod parse;
47pub use parse::{FromStr, ParseSegment};
48
49use core::cell::Cell;
50use core::fmt;
51
52#[derive(Debug, Clone, PartialEq)]
53pub enum FormatKeyError {
55 Fmt(fmt::Error),
57 UnknownKey,
59}
60
61#[cfg(feature = "std")]
62impl std::error::Error for FormatKeyError {
63 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
64 match self {
65 FormatKeyError::Fmt(f) => Some(f),
66 FormatKeyError::UnknownKey => None,
67 }
68 }
69}
70
71impl fmt::Display for FormatKeyError {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 match self {
74 FormatKeyError::Fmt(_) => f.write_str("There was an error writing to the formatter"),
75 FormatKeyError::UnknownKey => f.write_str("The requested key is unknown"),
76 }
77 }
78}
79
80impl From<fmt::Error> for FormatKeyError {
81 fn from(value: fmt::Error) -> Self {
82 FormatKeyError::Fmt(value)
83 }
84}
85
86pub trait FormatKey {
120 fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError>;
125}
126
127pub trait ToFormatParser<'a> {
129 type Parser: Iterator<Item = ParseSegment<'a>>;
131
132 fn to_parser(&'a self) -> Self::Parser;
134 fn unparsed(iter: Self::Parser) -> &'a str;
137}
138
139#[derive(Debug, Clone, PartialEq)]
140#[non_exhaustive]
141pub enum FormatError<'a> {
143 Key(&'a str),
145 Parse(&'a str),
147}
148
149#[cfg(feature = "std")]
150impl std::error::Error for FormatError<'_> {}
151
152impl fmt::Display for FormatError<'_> {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 FormatError::Key(key) => write!(f, "The requested key {key:?} is unknown"),
156 FormatError::Parse(rest) => write!(f, "Failed to parse {rest:?}"),
157 }
158 }
159}
160
161pub struct FormatArgs<'fs, 'fk, FS: ?Sized, FK: ?Sized> {
163 format_segments: &'fs FS,
164 format_keys: &'fk FK,
165 error: Cell<Option<FormatError<'fs>>>,
166}
167
168impl<'fs, 'fk, FS: ?Sized, FK: ?Sized> FormatArgs<'fs, 'fk, FS, FK> {
169 pub fn new(format_specified: &'fs FS, format_keys: &'fk FK) -> Self {
171 FormatArgs {
172 format_segments: format_specified,
173 format_keys,
174 error: Cell::new(None),
175 }
176 }
177
178 pub fn status(&self) -> Result<(), FormatError<'fs>> {
180 match self.error.take() {
181 Some(err) => Err(err),
182 None => Ok(()),
183 }
184 }
185}
186
187impl<'fs, 'fk, FS, FK> fmt::Display for FormatArgs<'fs, 'fk, FS, FK>
188where
189 FS: ?Sized + ToFormatParser<'fs>,
190 FK: ?Sized + FormatKey,
191{
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 let mut segments = self.format_segments.to_parser();
194 for segment in &mut segments {
195 match segment {
196 ParseSegment::Literal(s) => f.write_str(s)?,
197 ParseSegment::Key(key) => match self.format_keys.fmt(key, f) {
198 Ok(_) => {}
199 Err(FormatKeyError::Fmt(e)) => return Err(e),
200 Err(FormatKeyError::UnknownKey) => {
201 self.error.set(Some(FormatError::Key(key)));
202 return Err(fmt::Error);
203 }
204 },
205 }
206 }
207 let remaining = FS::unparsed(segments);
208 if !remaining.is_empty() {
209 self.error.set(Some(FormatError::Parse(remaining)));
210 Err(fmt::Error)
211 } else {
212 Ok(())
213 }
214 }
215}
216
217impl<'fs, 'fk, FS, FK> fmt::Debug for FormatArgs<'fs, 'fk, FS, FK>
218where
219 FS: ?Sized + ToFormatParser<'fs>,
220 FK: ?Sized + FormatKey,
221{
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 fmt::Display::fmt(self, f)
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use core::fmt::{self, Write};
230
231 use crate::{FormatArgs, FormatError, FormatKey, FormatKeyError};
232
233 struct WriteShim<'a> {
234 w: &'a mut [u8],
235 n: usize,
236 }
237 impl fmt::Write for WriteShim<'_> {
238 fn write_str(&mut self, s: &str) -> fmt::Result {
239 let remaining = self.w.len() - self.n;
240 if let Some(prefix) = s.as_bytes().get(..remaining) {
241 self.w[self.n..].copy_from_slice(prefix);
242 self.n = self.w.len();
243 Err(fmt::Error)
244 } else {
245 let n = self.n + s.len();
246 self.w[self.n..n].copy_from_slice(s.as_bytes());
247 self.n = n;
248 Ok(())
249 }
250 }
251 }
252
253 fn format<'a, F: FormatKey>(
254 s: &'a str,
255 fmt: &'a F,
256 f: impl FnOnce(&[u8]),
257 ) -> Result<(), FormatError<'a>> {
258 let mut bytes = WriteShim {
259 w: &mut [0; 1024],
260 n: 0,
261 };
262 let fmt = FormatArgs::new(s, fmt);
263 let _ = write!(bytes, "{}", fmt);
264 if let Some(err) = fmt.error.take() {
265 return Err(err);
266 }
267
268 f(&bytes.w[..bytes.n]);
269 Ok(())
270 }
271
272 struct Message;
273 impl FormatKey for Message {
274 fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> {
275 match key {
276 "recipient" => f.write_str("World").map_err(FormatKeyError::Fmt),
277 "time_descriptor" => f.write_str("morning").map_err(FormatKeyError::Fmt),
278 _ => Err(FormatKeyError::UnknownKey),
279 }
280 }
281 }
282
283 #[test]
284 fn happy_path() {
285 let format_str = "Hello, {recipient}. Hope you are having a nice {time_descriptor}.";
286 let expected = "Hello, World. Hope you are having a nice morning.";
287 format(format_str, &Message, |output| {
288 assert_eq!(output, expected.as_bytes())
289 })
290 .unwrap();
291 }
292
293 #[test]
294 fn missing_key() {
295 let format_str = "Hello, {recipient}. Hope you are having a nice {time_descriptr}.";
296 assert_eq!(
297 format(format_str, &Message, |_| {}),
298 Err(FormatError::Key("time_descriptr"))
299 );
300 }
301
302 #[test]
303 fn failed_parsing() {
304 let format_str = "Hello, {recipient}. Hope you are having a nice {time_descriptor.";
305 assert_eq!(
306 format(format_str, &Message, |_| {}),
307 Err(FormatError::Parse("time_descriptor."))
308 );
309 }
310
311 #[test]
312 fn escape_brackets() {
313 let format_str = "You can make custom formatting terms using {{foo}!";
314 let expected = "You can make custom formatting terms using {foo}!";
315 format(format_str, &Message, |output| {
316 assert_eq!(output, expected.as_bytes())
317 })
318 .unwrap();
319 }
320}