dyf/lib.rs
1#![deny(unused_imports)]
2#![deny(missing_docs)]
3//! # Dynamic String Formatting for Rust
4//!
5//! The `dyf` crate brings dynamic string formatting to Rust while supporting the whole variety of string formats available in Rust.
6//! It provides an easy way to implement dynamic formatting for custom types with the implementation of the `DynDisplay` trait.
7//!
8//! ## Features
9//!
10//! - Support for (almost) all standard Rust format specifiers
11//! - Dynamic formatting for custom types via the `DynDisplay` trait
12//! - Macro support for convenient usage
13//! - Support for various standard library types
14//!
15//! ## Usage
16//!
17//! Add the crate to your project:
18//!
19//! ```sh
20//! cargo add dyf
21//! ```
22//!
23//! ### Serde Support
24//!
25//! The `dyf` crate provides optional support for serialization and deserialization using the `serde` crate.
26//! To enable this feature, add the `serde` feature when adding the crate to your project:
27//!
28//! ```sh
29//! cargo add dyf --features serde
30//! ```
31//!
32//! Once the `serde` feature is enabled, the `FormatString` structure derives the `Serialize` and `Deserialize` traits.
33//! This allows you to easily serialize and deserialize `FormatString` instances using the `serde` crate.
34//!
35//! ### Basic Formatting
36//!
37//! ```rust
38//! use dyf::{dformat, FormatString};
39//!
40//! let fmt = FormatString::from_string("Hello, {}!".to_string()).unwrap();
41//! let result = dformat!(&fmt, "world").unwrap();
42//! assert_eq!(result, format!("Hello, {}!", "world"));
43//!
44//! let num_fmt = FormatString::from_string("The answer is: {:>5}".to_string()).unwrap();
45//! let num = 42;
46//! let result = dformat!(&num_fmt, num).unwrap();
47//! assert_eq!(result, format!("The answer is: {:>5}", num));
48//! ```
49//!
50//! ### Advanced Formatting
51//!
52//! ```rust
53//! use dyf::{dformat, FormatString};
54//!
55//! let fmt = FormatString::from_string("{:05} {:<10.2} {:^10}".to_string()).unwrap();
56//! let result = dformat!(&fmt, 42, 42.1234, "hello").unwrap();
57//! assert_eq!(result, format!("{:05} {:<10.2} {:^10}", 42, 42.1234, "hello"));
58//! ```
59//!
60//! ### Custom Type Formatting
61//!
62//! ```rust
63//! use dyf::{DynDisplay, Error, FormatSpec, dformat, FormatString};
64//! use std::fmt::Write;
65//!
66//! struct Point {
67//! x: i32,
68//! y: i32,
69//! }
70//!
71//! impl DynDisplay for Point {
72//! fn dyn_fmt(&self, f: &mut dyf::Formatter<'_>) -> dyf::Result {
73//! write!(f, "Point({}, {})", self.x, self.y)?;
74//! Ok(())
75//! }
76//! }
77//!
78//! impl std::fmt::Display for Point {
79//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80//! write!(f, "Point({}, {})", self.x, self.y)
81//! }
82//! }
83//!
84//! let point = Point { x: 10, y: 20 };
85//! let fmt = FormatString::from_string("{}".to_string()).unwrap();
86//! let result = dformat!(&fmt, point).unwrap();
87//! assert_eq!(result, format!("{}", point));
88//! ```
89//!
90//! ### Integer Formatting
91//!
92//! ```rust
93//! use dyf::{dformat, FormatString};
94//!
95//! // Decimal formatting
96//! let fmt = FormatString::from_string("{}".to_string()).unwrap();
97//! let result = dformat!(&fmt, 42).unwrap();
98//! assert_eq!(result, format!("{}", 42));
99//!
100//! let fmt = FormatString::from_string("{:5}".to_string()).unwrap();
101//! let result = dformat!(&fmt, 42).unwrap();
102//! assert_eq!(result, format!("{:5}", 42));
103//!
104//! let fmt = FormatString::from_string("{:05}".to_string()).unwrap();
105//! let result = dformat!(&fmt, 42).unwrap();
106//! assert_eq!(result, format!("{:05}", 42));
107//!
108//! let fmt = FormatString::from_string("{:+}".to_string()).unwrap();
109//! let result = dformat!(&fmt, 42).unwrap();
110//! assert_eq!(result, format!("{:+}", 42));
111//!
112//! // Hexadecimal formatting
113//! let fmt = FormatString::from_string("{:x}".to_string()).unwrap();
114//! let result = dformat!(&fmt, 42).unwrap();
115//! assert_eq!(result, format!("{:x}", 42));
116//!
117//! let fmt = FormatString::from_string("{:X}".to_string()).unwrap();
118//! let result = dformat!(&fmt, 42).unwrap();
119//! assert_eq!(result, format!("{:X}", 42));
120//!
121//! // Octal formatting
122//! let fmt = FormatString::from_string("{:o}".to_string()).unwrap();
123//! let result = dformat!(&fmt, 42).unwrap();
124//! assert_eq!(result, format!("{:o}", 42));
125//!
126//! // Binary formatting
127//! let fmt = FormatString::from_string("{:b}".to_string()).unwrap();
128//! let result = dformat!(&fmt, 42).unwrap();
129//! assert_eq!(result, format!("{:b}", 42));
130//! ```
131//!
132//! ### Float Formatting
133//!
134//! ```rust
135//! use dyf::{dformat, FormatString};
136//!
137//! let fmt = FormatString::from_string("{}".to_string()).unwrap();
138//! let result = dformat!(&fmt, 42.0).unwrap();
139//! assert_eq!(result, format!("{}", 42.0));
140//!
141//! let fmt = FormatString::from_string("{:e}".to_string()).unwrap();
142//! let result = dformat!(&fmt, 42.0).unwrap();
143//! assert_eq!(result, format!("{:e}", 42.0));
144//!
145//! let fmt = FormatString::from_string("{:.2}".to_string()).unwrap();
146//! let result = dformat!(&fmt, 42.1234).unwrap();
147//! assert_eq!(result, format!("{:.2}", 42.1234));
148//!
149//! let fmt = FormatString::from_string("{:10.2}".to_string()).unwrap();
150//! let result = dformat!(&fmt, 42.1234).unwrap();
151//! assert_eq!(result, format!("{:10.2}", 42.1234));
152//! ```
153//!
154//! ### String Formatting
155//!
156//! ```rust
157//! use dyf::{dformat, FormatString};
158//!
159//! let fmt = FormatString::from_string("{}".to_string()).unwrap();
160//! let result = dformat!(&fmt, "hello").unwrap();
161//! assert_eq!(result, format!("{}", "hello"));
162//!
163//! let fmt = FormatString::from_string("{:10}".to_string()).unwrap();
164//! let result = dformat!(&fmt, "hello").unwrap();
165//! assert_eq!(result, format!("{:10}", "hello"));
166//!
167//! let fmt = FormatString::from_string("{:.3}".to_string()).unwrap();
168//! let result = dformat!(&fmt, "hello").unwrap();
169//! assert_eq!(result, format!("{:.3}", "hello"));
170//! ```
171//!
172//! ## Supported Format Specifiers
173//!
174//! The crate supports several standard Rust format specifiers, including:
175//!
176//! | Category | Specifiers |
177//! |----------|------------|
178//! | Fill/Alignment | `<` `>` `^` |
179//! | Sign | `+` `-` |
180//! | Alternate | `#` |
181//! | Zero-padding | `0` |
182//! | Width | `{:5}` |
183//! | Precision | `{:.2}` |
184//! | Type | `?` `x` `X` `o` `b` `e` `E` `p` |
185//!
186//! ## Performance Considerations
187//!
188//! The crate is designed with performance in mind. The `FormatString` can be created once and reused multiple times with different arguments.
189//! This is particularly useful in scenarios where the same format string is used repeatedly.
190//!
191//! ```rust
192//! use dyf::{dformat, FormatString};
193//!
194//! let fmt = FormatString::from_string("The value is: {:>10}".to_string()).unwrap();
195//! let result1 = dformat!(&fmt, 42).unwrap();
196//! let result2 = dformat!(&fmt, 100).unwrap();
197//! assert_eq!(result1, format!("The value is: {:>10}", 42));
198//! assert_eq!(result2, format!("The value is: {:>10}", 100));
199//! ```
200//!
201//! ## Limitations
202//!
203//! While this crate aims to support all standard Rust format specifiers, there might be some edge cases that are not yet covered.
204//! If you encounter any unsupported format specifiers or have suggestions for improvements, please open an issue on the GitHub repository.
205//!
206//! ## Contributing
207//!
208//! Contributions are welcome! Please open an issue or submit a pull request on the GitHub repository.
209//!
210//! ## License
211//!
212//! This project is dual-licensed under either:
213//! - GNU General Public License v3.0
214//! - BSD 2-Clause License
215//!
216//! You may choose either license at your option.
217//! See LICENSE-GPL-3 and LICENSE-BSD-2-Clause files for full text.
218
219use std::{
220 borrow::Cow,
221 fmt::{Debug, Display, Write},
222};
223
224use pest::{Parser, iterators::Pair};
225use thiserror::Error;
226
227mod imp;
228mod parser;
229use parser::{FmtParser, Rule};
230
231/// Errors that can occur during dynamic formatting.
232///
233/// This enum represents all possible errors that can occur during the parsing and
234/// application of format specifications.
235#[derive(Debug, Error)]
236pub enum Error {
237 /// An unsupported format specification was encountered.
238 ///
239 /// This error occurs when a format specification contains options or combinations
240 /// that are not supported by the formatting machinery.
241 #[error("unsupported format spec {0}")]
242 UnsupportedSpec(FormatSpec),
243
244 /// The number of arguments doesn't match the number of format specifications.
245 ///
246 /// This error occurs when the number of arguments provided to a format string
247 /// doesn't match the number of format specifications in the string.
248 ///
249 /// # Examples
250 ///
251 /// Providing too few arguments:
252 ///
253 /// ```rust
254 /// use dyf::{FormatString, dformat, Error};
255 ///
256 /// let fmt = FormatString::from_string("{}, {}".to_string()).unwrap();
257 /// let result = dformat!(&fmt, "only one argument");
258 /// assert!(matches!(result, Err(Error::ArgumentCountMismatch(2, 1))));
259 /// ```
260 ///
261 /// Providing too many arguments:
262 ///
263 /// ```rust
264 /// use dyf::{FormatString, dformat, Error};
265 ///
266 /// let fmt = FormatString::from_string("{}".to_string()).unwrap();
267 /// let result = dformat!(&fmt, "one", "extra");
268 /// assert!(matches!(result, Err(Error::ArgumentCountMismatch(1, 2))));
269 /// ```
270 #[error(
271 "number of arguments doesn't match number of format specifications expected={0} found={1}"
272 )]
273 ArgumentCountMismatch(usize, usize),
274
275 /// An error occurred during format string parsing.
276 ///
277 /// This error wraps parsing errors from the underlying [`pest`] parser and provides
278 /// information about syntax errors in format strings.
279 #[error("format parsing error: {0}")]
280 Parse(#[from] Box<pest::error::Error<Rule>>),
281
282 /// A write error occurred while writing formatted output.
283 #[error("format write error")]
284 Write(#[from] std::fmt::Error),
285
286 /// Named or positional argument syntax (`{0}`, `{name}`) is not supported.
287 ///
288 /// Arguments are always matched positionally in the order they are passed.
289 #[error("named/positional arguments are not supported: {0}")]
290 NamedArgument(String),
291}
292
293/// Type alias for `std::result::Result<(), Error>`.
294///
295/// Returned by [`DynDisplay::dyn_fmt`] and the padding helpers on [`Formatter`].
296/// Mirrors [`std::fmt::Result`] in the same way that [`DynDisplay`] mirrors
297/// [`std::fmt::Display`].
298pub type Result = std::result::Result<(), Error>;
299
300/// The per-value formatting context passed to [`DynDisplay::dyn_fmt`].
301///
302/// This type mirrors [`std::fmt::Formatter`]: it bundles the [`FormatSpec`] with
303/// the output sink and exposes convenience methods for writing padded output.
304///
305/// It implements [`std::fmt::Write`], so `write!(f, ...)` works naturally inside
306/// a [`DynDisplay::dyn_fmt`] implementation.
307///
308/// # Examples
309///
310/// Using `write!` directly — padding is applied automatically:
311///
312/// ```
313/// use dyf::{DynDisplay, Formatter};
314/// use std::fmt::Write;
315///
316/// struct Point { x: i32, y: i32 }
317///
318/// impl DynDisplay for Point {
319/// fn dyn_fmt(&self, f: &mut Formatter<'_>) -> dyf::Result {
320/// Ok(write!(f, "Point({}, {})", self.x, self.y)?)
321/// }
322/// }
323/// ```
324pub struct Formatter<'a> {
325 spec: &'a FormatSpec,
326 out: &'a mut dyn Write,
327 default_align: Align,
328 /// Buffer for writes made via the `Write` trait. Flushed with padding by
329 /// [`Formatter::finish`] or emptied (without padding) if `pad`/`pad_integral`
330 /// is called explicitly.
331 buf: String,
332}
333
334impl<'a> Formatter<'a> {
335 /// Creates a new `Formatter` wrapping the given spec and output sink.
336 #[inline]
337 pub fn new(spec: &'a FormatSpec, out: &'a mut dyn Write) -> Self {
338 Self {
339 spec,
340 out,
341 default_align: Align::Left,
342 buf: String::new(),
343 }
344 }
345
346 /// Returns the full [`FormatSpec`].
347 #[inline]
348 pub fn spec(&self) -> &FormatSpec {
349 self.spec
350 }
351
352 /// Returns the fill character (defaults to `' '`).
353 #[inline]
354 pub fn fill(&self) -> char {
355 self.spec.fill.unwrap_or(' ')
356 }
357
358 /// Returns the effective alignment: the value from the format spec if one was
359 /// explicitly requested, otherwise the default set by [`Formatter::set_default_align`].
360 #[inline]
361 pub fn align(&self) -> Align {
362 self.spec.align.unwrap_or(self.default_align)
363 }
364
365 /// Returns the minimum field width, if specified.
366 #[inline]
367 pub fn width(&self) -> Option<usize> {
368 self.spec.width
369 }
370
371 /// Returns the precision, if specified.
372 #[inline]
373 pub fn precision(&self) -> Option<usize> {
374 self.spec.precision
375 }
376
377 /// Returns the sign option, if any.
378 #[inline]
379 pub fn sign(&self) -> Option<Sign> {
380 self.spec.sign
381 }
382
383 /// Returns `true` if the alternate form (`#`) was requested.
384 #[inline]
385 pub fn alternate(&self) -> bool {
386 self.spec.alternate
387 }
388
389 /// Returns `true` if zero-padding (`0`) was requested.
390 #[inline]
391 pub fn zero(&self) -> bool {
392 self.spec.zero
393 }
394
395 /// Returns the format type (e.g. [`FmtType::LowerHex`], [`FmtType::Debug`]).
396 #[inline]
397 pub fn fmt_type(&self) -> FmtType {
398 self.spec.ty
399 }
400
401 /// Sets the default alignment used when none is specified in the format string.
402 ///
403 /// Overrides the initial `Left` default. Call this before `write!(f, ...)` when
404 /// implementing a type that should right-align by default (e.g. a custom numeric type).
405 #[inline]
406 pub fn set_default_align(&mut self, align: Align) {
407 self.default_align = align
408 }
409
410 /// Flushes buffered `write!` content to the output with `Left` as the default
411 /// alignment.
412 ///
413 /// Called automatically by the formatting pipeline after [`DynDisplay::dyn_fmt`]
414 /// returns; there is no need to call this manually.
415 pub(crate) fn finish(&mut self) -> Result {
416 if !self.buf.is_empty() {
417 let buf = std::mem::take(&mut self.buf);
418 self.spec.write_aligned(
419 &buf,
420 self.spec.align.unwrap_or(self.default_align),
421 self.out,
422 )?;
423 }
424 Ok(())
425 }
426}
427
428impl std::fmt::Write for Formatter<'_> {
429 fn write_str(&mut self, s: &str) -> std::fmt::Result {
430 self.buf.push_str(s);
431 Ok(())
432 }
433
434 fn write_char(&mut self, c: char) -> std::fmt::Result {
435 self.buf.push(c);
436 Ok(())
437 }
438}
439
440/// A trait for dynamic display formatting.
441///
442/// This trait provides a way to implement custom formatting for types that need to support
443/// dynamic format specifications at runtime. It's similar to the standard [`std::fmt::Display`] trait
444/// but with additional formatting control through [`FormatSpec`].
445///
446/// # Examples
447///
448/// Basic implementation for a custom type:
449///
450/// ```
451/// use dyf::{DynDisplay, FormatSpec, Error};
452/// use std::fmt::Write;
453///
454/// struct Point {
455/// x: i32,
456/// y: i32,
457/// }
458///
459/// impl DynDisplay for Point {
460/// fn dyn_fmt(&self, f: &mut dyf::Formatter<'_>) -> dyf::Result {
461/// write!(f, "Point({}, {})", self.x, self.y)?;
462/// Ok(())
463/// }
464/// }
465/// ```
466///
467/// Implementation with format-aware behavior:
468///
469/// ```
470/// use dyf::{DynDisplay, FmtType, Error};
471/// use std::fmt::Write;
472///
473/// struct Color {
474/// r: u8,
475/// g: u8,
476/// b: u8,
477/// }
478///
479/// impl DynDisplay for Color {
480/// fn dyn_fmt(&self, f: &mut dyf::Formatter<'_>) -> dyf::Result {
481/// match f.fmt_type() {
482/// FmtType::LowerHex => write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b).map_err(Error::from),
483/// FmtType::UpperHex => write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b).map_err(Error::from),
484/// FmtType::Debug => write!(f, "Color {{ r: {}, g: {}, b: {} }}", self.r, self.g, self.b).map_err(Error::from),
485/// _ => write!(f, "RGB({}, {}, {})", self.r, self.g, self.b).map_err(Error::from),
486/// }
487/// }
488/// }
489/// ```
490pub trait DynDisplay {
491 /// Formats the value using the given format specification.
492 ///
493 /// # Arguments
494 ///
495 /// * `f` - The formatting context carrying the spec and the output sink
496 ///
497 /// # Errors
498 ///
499 /// Returns an error if the format specification is not supported for this type
500 /// or if writing to the output sink fails.
501 fn dyn_fmt(&self, f: &mut Formatter<'_>) -> Result;
502}
503
504/// Specifies the type of formatting to apply to a value.
505///
506/// The [`FmtType`] enum represents the various format types that can be specified
507/// in a format string after the colon. These determine how values are converted
508/// to strings, including different representations for numbers, debugging output,
509/// and other special formats.
510///
511/// # Format String Representation
512///
513/// In format strings, these types are represented as follows:
514///
515/// | FmtType | Format Specifier | Description |
516/// |---------|------------------|-------------|
517/// | Default | (none) | Default formatting for the type |
518/// | Debug | `?` | Debug representation |
519/// | DebugLowHex | `x?` | Debug representation with lowercase hexadecimal |
520/// | DebugUpHex | `X?` | Debug representation with uppercase hexadecimal |
521/// | LowerHex | `x` | Lowercase hexadecimal |
522/// | UpperHex | `X` | Uppercase hexadecimal |
523/// | Octal | `o` | Octal representation |
524/// | Ptr | `p` | Pointer address |
525/// | Bin | `b` | Binary representation |
526/// | LowExp | `e` | Lowercase exponential notation |
527/// | UpperExp | `E` | Uppercase exponential notation |
528///
529/// # Examples
530///
531/// Basic usage with format specifications:
532///
533/// ```
534/// use dyf::{FmtType, FormatSpec};
535///
536/// // Create a format specification for hexadecimal output
537/// let hex_spec = FormatSpec {
538/// ty: FmtType::LowerHex,
539/// ..Default::default()
540/// };
541///
542/// // Create a format specification for debug output
543/// let debug_spec = FormatSpec {
544/// ty: FmtType::Debug,
545/// ..Default::default()
546/// };
547/// ```
548///
549/// Using with custom formatting:
550///
551/// ```
552/// use dyf::{FmtType, FormatSpec, DynDisplay, Error};
553/// use std::fmt::Write;
554///
555/// struct Color {
556/// r: u8,
557/// g: u8,
558/// b: u8,
559/// }
560///
561/// impl DynDisplay for Color {
562/// fn dyn_fmt(&self, f: &mut dyf::Formatter<'_>) -> dyf::Result {
563/// match f.fmt_type() {
564/// FmtType::LowerHex => Ok(write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?),
565/// FmtType::UpperHex => Ok(write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)?),
566/// FmtType::Debug => Ok(write!(f, "Color {{ r: {}, g: {}, b: {} }}", self.r, self.g, self.b)?),
567/// _ => Ok(write!(f, "RGB({}, {}, {})", self.r, self.g, self.b)?),
568/// }
569/// }
570/// }
571/// ```
572#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
573#[derive(Debug, Clone, Copy)]
574pub enum FmtType {
575 /// Default formatting for the type.
576 ///
577 /// This uses the standard display formatting for the type, equivalent to not
578 /// specifying a format type in the format string.
579 Default,
580
581 /// Debug representation.
582 ///
583 /// This uses the debug formatting for the type, equivalent to the `{:?}` format specifier.
584 Debug,
585
586 /// Debug representation with lowercase hexadecimal.
587 ///
588 /// This combines debug formatting with lowercase hexadecimal representation.
589 DebugLowHex,
590
591 /// Debug representation with uppercase hexadecimal.
592 ///
593 /// This combines debug formatting with uppercase hexadecimal representation.
594 DebugUpHex,
595
596 /// Lowercase hexadecimal representation.
597 ///
598 /// This formats numbers in lowercase hexadecimal, equivalent to the `{:x}` format specifier.
599 LowerHex,
600
601 /// Uppercase hexadecimal representation.
602 ///
603 /// This formats numbers in uppercase hexadecimal, equivalent to the `{:X}` format specifier.
604 UpperHex,
605
606 /// Octal representation.
607 ///
608 /// This formats numbers in octal (base-8), equivalent to the `{:o}` format specifier.
609 Octal,
610
611 /// Pointer address representation.
612 ///
613 /// This formats pointer values as memory addresses, equivalent to the `{:p}` format specifier.
614 Ptr,
615
616 /// Binary representation.
617 ///
618 /// This formats numbers in binary (base-2), equivalent to the `{:b}` format specifier.
619 Bin,
620
621 /// Lowercase exponential notation.
622 ///
623 /// This formats floating-point numbers in scientific notation with lowercase 'e',
624 /// equivalent to the `{:e}` format specifier.
625 LowExp,
626
627 /// Uppercase exponential notation.
628 ///
629 /// This formats floating-point numbers in scientific notation with uppercase 'E',
630 /// equivalent to the `{:E}` format specifier.
631 UpperExp,
632}
633
634impl Display for FmtType {
635 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
636 match self {
637 FmtType::Default => Ok(()),
638 FmtType::Debug => write!(f, "?"),
639 FmtType::DebugLowHex => write!(f, "x?"),
640 FmtType::DebugUpHex => write!(f, "X?"),
641 FmtType::LowerHex => write!(f, "x"),
642 FmtType::UpperHex => write!(f, "X"),
643 FmtType::Octal => write!(f, "o"),
644 FmtType::Ptr => write!(f, "p"),
645 FmtType::Bin => write!(f, "b"),
646 FmtType::LowExp => write!(f, "e"),
647 FmtType::UpperExp => write!(f, "E"),
648 }
649 }
650}
651
652/// Specifies the alignment of formatted text within a field width.
653///
654/// The [`Align`] enum determines how text should be aligned when a width is specified
655/// in a format specification. It controls whether the text is left-aligned, right-aligned,
656/// or centered within the allocated space.
657#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
658#[derive(Debug, Clone, Copy)]
659pub enum Align {
660 /// Left-align the text within the field.
661 ///
662 /// When this alignment is used, text is placed at the beginning of the field,
663 /// with any padding added to the right.
664 Left,
665
666 /// Center the text within the field.
667 ///
668 /// When this alignment is used, text is placed in the middle of the field,
669 /// with padding distributed equally on both sides when possible.
670 Center,
671
672 /// Right-align the text within the field.
673 ///
674 /// When this alignment is used, text is placed at the end of the field,
675 /// with any padding added to the left.
676 Right,
677}
678
679impl Display for Align {
680 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
681 match self {
682 Align::Center => write!(f, "^"),
683 Align::Left => write!(f, "<"),
684 Align::Right => write!(f, ">"),
685 }
686 }
687}
688
689/// Specifies how signs should be displayed for numeric values.
690///
691/// The [`Sign`] enum controls the display of signs for numeric values in formatted output.
692/// It determines whether positive numbers should show a plus sign, only negative numbers
693/// should show a minus sign, or no special sign handling should be applied.
694///
695/// # Format Specification
696///
697/// In format strings, these correspond to:
698/// - `+` for `Sign::Positive` (show signs for both positive and negative numbers)
699/// - `-` for `Sign::Negative` (show signs only for negative numbers, default behavior)
700#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
701#[derive(Debug, Clone, Copy)]
702pub enum Sign {
703 /// Always show the sign for numeric values.
704 ///
705 /// Positive numbers will be prefixed with `+`, and negative numbers with `-`.
706 /// This corresponds to the `+` format specifier.
707 Positive,
708
709 /// Only show the sign for negative numbers.
710 ///
711 /// Negative numbers will be prefixed with `-`, while positive numbers will
712 /// have no sign prefix. This is the default behavior and corresponds to
713 /// the `-` format specifier.
714 Negative,
715}
716
717impl Display for Sign {
718 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
719 match self {
720 Sign::Positive => write!(f, "+"),
721 Sign::Negative => write!(f, "-"),
722 }
723 }
724}
725
726/// A complete format specification for dynamic formatting.
727///
728/// [`FormatSpec`] represents all the components of a format specification that can appear
729/// between the colons in a format string: `"{:<5.2}"`. It controls how values are formatted
730/// including alignment, padding, width, precision, and type-specific formatting.
731///
732/// # Format String Components
733///
734/// A format specification in a string typically looks like:
735/// `:[fill][align][sign][#][0][width][.precision][type]`
736#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
737#[derive(Debug, Clone)]
738pub struct FormatSpec {
739 /// The fill character to use for padding.
740 ///
741 /// If `None`, spaces will be used for padding.
742 pub fill: Option<char>,
743
744 /// The alignment of the formatted value within the field.
745 ///
746 /// If `None`, the default alignment (typically right for numbers, left for text) will be used.
747 pub align: Option<Align>,
748
749 /// The sign display option for numeric values.
750 ///
751 /// If `None`, signs will only be shown for negative numbers.
752 pub sign: Option<Sign>,
753
754 /// Whether to use alternate formatting.
755 ///
756 /// For example, adding `0x` prefix to hexadecimal numbers or always showing the decimal point.
757 pub alternate: bool,
758
759 /// Whether to pad with zeros instead of the fill character.
760 ///
761 /// This is typically used for numeric types to ensure a minimum number of digits.
762 pub zero: bool,
763
764 /// The minimum width of the formatted field.
765 ///
766 /// If the formatted value is shorter than this width, it will be padded according to the alignment.
767 pub width: Option<usize>,
768
769 /// The precision for floating-point numbers or maximum length for strings.
770 pub precision: Option<usize>,
771
772 /// The format type specification.
773 pub ty: FmtType,
774}
775
776impl Display for FormatSpec {
777 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
778 if let Some(c) = self.fill {
779 write!(f, "{c}")?;
780 }
781 if let Some(a) = self.align {
782 write!(f, "{a}")?;
783 }
784 if let Some(s) = self.sign {
785 write!(f, "{s}")?;
786 }
787 if self.alternate {
788 write!(f, "#")?;
789 }
790 if self.zero {
791 write!(f, "0")?;
792 }
793 if let Some(w) = self.width {
794 write!(f, "{w}")?;
795 }
796 if let Some(p) = self.precision {
797 write!(f, ".{p}")?;
798 }
799 write!(f, "{}", self.ty)
800 }
801}
802
803impl Default for FormatSpec {
804 fn default() -> Self {
805 Self {
806 fill: None,
807 align: None,
808 sign: None,
809 alternate: false,
810 zero: false,
811 width: None,
812 precision: None,
813 ty: FmtType::Default,
814 }
815 }
816}
817
818impl FormatSpec {
819 fn from_pair(p: Pair<'_, Rule>) -> Self {
820 let mut out = Self::default();
821 for p in p.into_inner() {
822 match p.as_rule() {
823 Rule::fill => out.fill = p.as_str().chars().nth(0),
824 Rule::align => {
825 out.align = match p.as_str() {
826 "^" => Some(Align::Center),
827 "<" => Some(Align::Left),
828 ">" => Some(Align::Right),
829 _ => None,
830 }
831 }
832 Rule::sign => {
833 out.sign = match p.as_str() {
834 "-" => Some(Sign::Negative),
835 "+" => Some(Sign::Positive),
836 _ => None,
837 }
838 }
839 Rule::alternate => out.alternate = true,
840 Rule::zero_pad => out.zero = true,
841 Rule::width => out.width = p.as_str().parse::<usize>().ok(),
842 Rule::precision => out.precision = p.as_str().parse::<usize>().ok(),
843 Rule::type_fmt => {
844 if let Some(type_fmt) = p.into_inner().next() {
845 match type_fmt.as_rule() {
846 Rule::type_debug => out.ty = FmtType::Debug,
847 Rule::type_debug_low_hex => out.ty = FmtType::DebugLowHex,
848 Rule::type_debug_up_hex => out.ty = FmtType::DebugUpHex,
849 Rule::type_oct => out.ty = FmtType::Octal,
850 Rule::type_low_hex => out.ty = FmtType::LowerHex,
851 Rule::type_up_hex => out.ty = FmtType::UpperHex,
852 Rule::type_ptr => out.ty = FmtType::Ptr,
853 Rule::type_bin => out.ty = FmtType::Bin,
854 Rule::type_low_exp => out.ty = FmtType::LowExp,
855 Rule::type_upper_exp => out.ty = FmtType::UpperExp,
856 _ => {}
857 }
858 }
859 }
860
861 _ => {}
862 }
863 }
864 out
865 }
866
867 fn is_empty(&self) -> bool {
868 self.fill.is_none()
869 && self.align.is_none()
870 && self.sign.is_none()
871 && !self.alternate
872 && !self.zero
873 && self.width.is_none()
874 && self.precision.is_none()
875 && matches!(self.ty, FmtType::Default)
876 }
877
878 /// Writes `s` into `out`, applying fill and alignment from this format specification.
879 ///
880 /// # Arguments
881 ///
882 /// * `s` - The already-formatted string to align/pad
883 /// * `default_align` - Alignment to use when none is specified in the spec
884 /// * `out` - The output sink to write into
885 pub fn write_aligned(&self, s: &str, default_align: Align, out: &mut dyn Write) -> Result {
886 let Some(width) = self.width else {
887 out.write_str(s)?;
888 return Ok(());
889 };
890 if width <= s.len() {
891 out.write_str(s)?;
892 return Ok(());
893 }
894 let pad = width - s.len();
895 let fill = self.fill.unwrap_or(' ');
896 let align = self.align.unwrap_or(default_align);
897 match align {
898 Align::Left => {
899 out.write_str(s)?;
900 for _ in 0..pad {
901 out.write_char(fill)?;
902 }
903 }
904 Align::Center => {
905 let r_pad = pad / 2;
906 let l_pad = pad.div_ceil(2);
907 for _ in 0..r_pad {
908 out.write_char(fill)?;
909 }
910 out.write_str(s)?;
911 for _ in 0..l_pad {
912 out.write_char(fill)?;
913 }
914 }
915 Align::Right => {
916 for _ in 0..pad {
917 out.write_char(fill)?;
918 }
919 out.write_str(s)?;
920 }
921 }
922 Ok(())
923 }
924}
925
926#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
927#[derive(Debug, Clone)]
928struct Format {
929 start: usize,
930 end: usize,
931 arg: Option<String>,
932 spec: FormatSpec,
933}
934
935impl Display for Format {
936 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
937 write!(f, "{{")?;
938 if let Some(a) = self.arg.as_ref() {
939 write!(f, "{a}")?;
940 }
941
942 if !self.spec.is_empty() {
943 write!(f, ":")?;
944 }
945 write!(f, "{}", self.spec)?;
946 write!(f, "}}")
947 }
948}
949
950impl Format {
951 fn from_pair(pairs: Pair<'_, Rule>) -> Self {
952 let start = pairs.as_span().start();
953 let end = pairs.as_span().end();
954 let mut spec = None;
955 let mut arg = None;
956
957 for p in pairs.into_inner() {
958 match p.as_rule() {
959 Rule::argument => {
960 // we ignore it for the moment
961 arg = Some(p.as_str().to_string())
962 }
963 Rule::format_spec => spec = Some(FormatSpec::from_pair(p)),
964 _ => {}
965 }
966 }
967
968 Self {
969 start,
970 end,
971 arg,
972 spec: spec.unwrap_or_default(),
973 }
974 }
975
976 fn dyn_fmt_arg<D: DynDisplay>(&self, arg: &D, out: &mut dyn Write) -> Result {
977 let mut f = Formatter::new(&self.spec, out);
978 DynDisplay::dyn_fmt(arg, &mut f)?;
979 f.finish()
980 }
981}
982
983/// A parsed format string that can be used for dynamic formatting.
984///
985/// [`FormatString`] represents a string with embedded format specifications that has been
986/// parsed and can be used with the [`dformat!`] macro to perform dynamic formatting operations.
987/// It contains the original string along with information about the format specifications
988/// found within it.
989///
990/// # Examples
991///
992/// Basic usage:
993///
994/// ```
995/// use dyf::FormatString;
996///
997/// let fmt = FormatString::from_string("Hello, {}!".to_string()).unwrap();
998/// assert!(fmt.contains_format());
999/// ```
1000///
1001/// Creating and using a format string:
1002///
1003/// ```
1004/// use dyf::{FormatString, dformat};
1005///
1006/// let fmt = FormatString::from_string("{:>10} {:.2}".to_string()).unwrap();
1007/// let result = dformat!(&fmt, 42, 3.14159).unwrap();
1008/// assert_eq!(result, " 42 3.14");
1009/// ```
1010///
1011/// Converting between string types:
1012///
1013/// ```
1014/// use dyf::FormatString;
1015///
1016/// let fmt = FormatString::from_string("Value: {:05}".to_string()).unwrap();
1017/// let fmt_str = fmt.to_string_lossy();
1018/// let owned_str = fmt.into_string();
1019/// ```
1020#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1021#[derive(Debug, Clone)]
1022pub struct FormatString {
1023 s: String,
1024 fmts: Vec<Format>,
1025}
1026
1027impl FormatString {
1028 #[inline]
1029 fn new_from_str<S: AsRef<str>>(s: S) -> std::result::Result<Self, Error> {
1030 let pairs = FmtParser::parse(Rule::format_string, s.as_ref())
1031 .map_err(|e| Error::from(Box::new(e)))?;
1032
1033 let mut fmts = vec![];
1034
1035 for p in pairs {
1036 // WARNING: here anything else than Rule::format is ignored
1037 if p.as_rule() == Rule::format {
1038 fmts.push(Format::from_pair(p))
1039 }
1040 }
1041
1042 Ok(Self {
1043 s: s.as_ref().to_string(),
1044 fmts,
1045 })
1046 }
1047
1048 /// Creates a new [`FormatString`] from a string.
1049 ///
1050 /// # Arguments
1051 ///
1052 /// * `s` - The string containing format specifications
1053 ///
1054 /// # Returns
1055 ///
1056 /// A `Result` containing the parsed [`FormatString`] or an error if parsing fails.
1057 ///
1058 /// # Errors
1059 ///
1060 /// This function may return an error if the input string contains invalid format
1061 /// specifications that cannot be parsed.
1062 ///
1063 /// # Examples
1064 ///
1065 /// ```
1066 /// use dyf::FormatString;
1067 ///
1068 /// let fmt = FormatString::from_string("Hello, {}!".to_string()).unwrap();
1069 /// ```
1070 pub fn from_string(s: String) -> std::result::Result<Self, Error> {
1071 Self::new_from_str(s)
1072 }
1073
1074 /// Converts the [`FormatString`] into its inner string.
1075 ///
1076 /// This consumes the [`FormatString`] and returns the original string that was used
1077 /// to create it.
1078 ///
1079 /// # Examples
1080 ///
1081 /// ```
1082 /// use dyf::FormatString;
1083 ///
1084 /// let fmt = FormatString::from_string("Test: {}".to_string()).unwrap();
1085 /// let s = fmt.into_string();
1086 /// assert_eq!(s, "Test: {}");
1087 /// ```
1088 pub fn into_string(self) -> String {
1089 self.s
1090 }
1091
1092 /// Returns a borrowed version of the format string.
1093 ///
1094 /// This provides access to the original string without consuming the [`FormatString`].
1095 ///
1096 /// # Examples
1097 ///
1098 /// ```
1099 /// use dyf::FormatString;
1100 ///
1101 /// let fmt = FormatString::from_string("Value: {:.2}".to_string()).unwrap();
1102 /// let borrowed = fmt.to_string_lossy();
1103 /// assert_eq!(&*borrowed, "Value: {:.2}");
1104 /// ```
1105 pub fn to_string_lossy(&self) -> Cow<'_, str> {
1106 Cow::Borrowed(&self.s)
1107 }
1108
1109 /// Returns `true` if the format string contains any format specifications.
1110 ///
1111 /// # Examples
1112 ///
1113 /// ```
1114 /// use dyf::FormatString;
1115 ///
1116 /// let with_fmt = FormatString::from_string("Hello, {}!".to_string()).unwrap();
1117 /// assert!(with_fmt.contains_format());
1118 ///
1119 /// let without_fmt = FormatString::from_string("Hello, world!".to_string()).unwrap();
1120 /// assert!(!without_fmt.contains_format());
1121 /// ```
1122 pub fn contains_format(&self) -> bool {
1123 !self.fmts.is_empty()
1124 }
1125}
1126
1127/// Drives the full formatting pipeline for a [`FormatString`].
1128///
1129/// `Writer` collects arguments via [`Writer::push_arg`] and then writes the
1130/// fully formatted string in one pass with [`Writer::format`].
1131/// Most callers should use the [`dformat!`] macro instead.
1132///
1133/// # Examples
1134///
1135/// Basic usage with a format string:
1136///
1137/// ```
1138/// use dyf::{FormatString, Writer};
1139///
1140/// let fmt = FormatString::from_string("Hello, {}!".to_string()).unwrap();
1141/// let mut w = Writer::from(&fmt);
1142/// w.push_arg(&"world").format().unwrap();
1143/// assert_eq!(w.into_string(), "Hello, world!");
1144/// ```
1145///
1146/// Formatting multiple values:
1147///
1148/// ```
1149/// use dyf::{FormatString, Writer};
1150///
1151/// let fmt = FormatString::from_string("{}, {}!".to_string()).unwrap();
1152/// let mut w = Writer::from(&fmt);
1153/// w.push_arg(&"Hello").push_arg(&"world").format().unwrap();
1154/// assert_eq!(w.into_string(), "Hello, world!");
1155/// ```
1156///
1157/// Using with custom types:
1158///
1159/// ```
1160/// use dyf::{FormatString, Writer, DynDisplay};
1161/// use std::fmt::Write;
1162///
1163/// struct Point { x: i32, y: i32 }
1164///
1165/// impl DynDisplay for Point {
1166/// fn dyn_fmt(&self, f: &mut dyf::Formatter<'_>) -> dyf::Result {
1167/// write!(f, "Point({}, {})", self.x, self.y)?;
1168/// Ok(())
1169/// }
1170/// }
1171///
1172/// let fmt = FormatString::from_string("Point: {}".to_string()).unwrap();
1173/// let point = Point { x: 10, y: 20 };
1174/// let mut w = Writer::from(&fmt);
1175/// w.push_arg(&point).format().unwrap();
1176/// assert_eq!(w.into_string(), "Point: Point(10, 20)");
1177/// ```
1178pub struct Writer<'s> {
1179 /// index in the format string
1180 i: usize,
1181 format_string: &'s FormatString,
1182 args: Vec<&'s dyn DynDisplay>,
1183 out: String,
1184}
1185
1186impl<'s> From<&'s FormatString> for Writer<'s> {
1187 fn from(value: &'s FormatString) -> Self {
1188 Self {
1189 i: 0,
1190 format_string: value,
1191 args: vec![],
1192 out: String::new(),
1193 }
1194 }
1195}
1196
1197impl<'s> Writer<'s> {
1198 /// Adds an argument to be formatted.
1199 ///
1200 /// Arguments are matched to format placeholders in the order they are pushed.
1201 /// Supports method chaining.
1202 ///
1203 /// # Examples
1204 ///
1205 /// ```
1206 /// use dyf::{FormatString, Writer};
1207 ///
1208 /// let fmt = FormatString::from_string("{}, {}!".to_string()).unwrap();
1209 /// let mut w = Writer::from(&fmt);
1210 /// w.push_arg(&"Hello").push_arg(&"world");
1211 /// ```
1212 pub fn push_arg<A>(&mut self, arg: &'s A) -> &mut Self
1213 where
1214 A: DynDisplay,
1215 {
1216 self.args.push(arg);
1217 self
1218 }
1219
1220 /// Applies all format specifications to the collected arguments.
1221 ///
1222 /// Verifies that the argument count matches the number of placeholders, then
1223 /// writes each formatted value into the internal output buffer.
1224 ///
1225 /// # Errors
1226 ///
1227 /// Returns an error if the argument count doesn't match the placeholder count,
1228 /// if a named/positional argument is used, or if a format operation fails.
1229 ///
1230 /// # Examples
1231 ///
1232 /// ```
1233 /// use dyf::{FormatString, Writer};
1234 ///
1235 /// let fmt = FormatString::from_string("{:>5}, {:<5}".to_string()).unwrap();
1236 /// let mut w = Writer::from(&fmt);
1237 /// w.push_arg(&42).push_arg(&"hello").format().unwrap();
1238 /// assert_eq!(w.into_string(), " 42, hello");
1239 /// ```
1240 pub fn format(&mut self) -> std::result::Result<&mut Self, Error> {
1241 if self.args.len() != self.format_string.fmts.len() {
1242 return Err(Error::ArgumentCountMismatch(
1243 // expected
1244 self.format_string.fmts.len(),
1245 // found
1246 self.args.len(),
1247 ));
1248 }
1249
1250 for fmt in &self.format_string.fmts {
1251 if let Some(arg) = &fmt.arg {
1252 return Err(Error::NamedArgument(arg.clone()));
1253 }
1254 }
1255
1256 for (i, a) in self.args.iter().enumerate() {
1257 // this cannot panic as lengths are equal
1258 let arg_fmt = &self.format_string.fmts[i];
1259 let slice = &self.format_string.s.as_str()[self.i..];
1260 self.out.push_str(&slice[..arg_fmt.start - self.i]);
1261 arg_fmt.dyn_fmt_arg(a, &mut self.out)?;
1262 self.i = arg_fmt.end;
1263 }
1264
1265 // we copy the rest of the string
1266 let slice = &self.format_string.s.as_str()[self.i..];
1267 self.out.push_str(slice);
1268
1269 Ok(self)
1270 }
1271
1272 /// Returns a borrowed view of the formatted output so far.
1273 ///
1274 /// # Examples
1275 ///
1276 /// ```
1277 /// use dyf::{FormatString, Writer};
1278 ///
1279 /// let fmt = FormatString::from_string("Value: {}".to_string()).unwrap();
1280 /// let mut w = Writer::from(&fmt);
1281 /// w.push_arg(&42).format().unwrap();
1282 /// assert_eq!(&*w.to_string_lossy(), "Value: 42");
1283 /// ```
1284 pub fn to_string_lossy(&self) -> Cow<'_, str> {
1285 Cow::Borrowed(&self.out)
1286 }
1287
1288 /// Consumes the writer and returns the fully formatted string.
1289 ///
1290 /// # Examples
1291 ///
1292 /// ```
1293 /// use dyf::{FormatString, Writer};
1294 ///
1295 /// let fmt = FormatString::from_string("The answer is: {}".to_string()).unwrap();
1296 /// let mut w = Writer::from(&fmt);
1297 /// w.push_arg(&42).format().unwrap();
1298 /// assert_eq!(w.into_string(), "The answer is: 42");
1299 /// ```
1300 pub fn into_string(self) -> String {
1301 self.out
1302 }
1303}
1304
1305/// Dynamically formats values according to a format string.
1306///
1307/// The `dformat!` macro provides functionality similar to Rust's standard `format!` macro,
1308/// but with dynamic formatting capabilities. It uses a pre-parsed [`FormatString`] to
1309/// apply format specifications to values at runtime.
1310///
1311/// # Syntax
1312///
1313/// The macro takes two arguments:
1314/// - A reference to a [`FormatString`] that has been created from a format string
1315/// - A list of arguments to format
1316///
1317/// ```ignore
1318/// dformat!(format_string_ref, arg1, arg2, ...)
1319/// ```
1320///
1321/// # Examples
1322///
1323/// Basic usage:
1324///
1325/// ```
1326/// use dyf::{FormatString, dformat};
1327///
1328/// let fmt = FormatString::from_string("Hello, {}!".to_string()).unwrap();
1329/// let result = dformat!(&fmt, "world").unwrap();
1330/// assert_eq!(result, "Hello, world!");
1331/// ```
1332///
1333/// Formatting with different specifications:
1334///
1335/// ```
1336/// use dyf::{FormatString, dformat};
1337///
1338/// let fmt = FormatString::from_string("{:>5}, {:.2}".to_string()).unwrap();
1339/// let result = dformat!(&fmt, 42, 3.14159).unwrap();
1340/// assert_eq!(result, " 42, 3.14");
1341/// ```
1342///
1343/// Using with custom types that implement [`DynDisplay`]:
1344///
1345/// ```
1346/// use dyf::{FormatString, dformat, DynDisplay};
1347/// use std::fmt::Write;
1348///
1349/// struct Point {
1350/// x: i32,
1351/// y: i32,
1352/// }
1353///
1354/// impl DynDisplay for Point {
1355/// fn dyn_fmt(&self, f: &mut dyf::Formatter<'_>) -> dyf::Result {
1356/// write!(f, "Point({}, {})", self.x, self.y)?;
1357/// Ok(())
1358/// }
1359/// }
1360///
1361/// let fmt = FormatString::from_string("Point: {}".to_string()).unwrap();
1362/// let point = Point { x: 10, y: 20 };
1363/// let result = dformat!(&fmt, point).unwrap();
1364/// assert_eq!(result, "Point: Point(10, 20)");
1365/// ```
1366///
1367/// # Errors
1368///
1369/// The macro returns a `Result<String, Error>`. Any error might be one
1370/// of the [`enum@Error`] variant.
1371///
1372/// # Performance Considerations
1373///
1374/// For best performance when formatting the same string multiple times:
1375/// 1. Create the `FormatString` once and reuse it
1376/// 2. Use the `dformat!` macro for each formatting operation
1377///
1378/// ```
1379/// use dyf::{FormatString, dformat};
1380///
1381/// let fmt = FormatString::from_string("Value: {:>10}".to_string()).unwrap();
1382/// let result1 = dformat!(&fmt, 42).unwrap();
1383/// let result2 = dformat!(&fmt, "text").unwrap();
1384/// ```
1385///
1386/// # Comparison with Standard `format!` Macro
1387///
1388/// While similar to Rust's standard `format!` macro, `dformat!` provides:
1389/// - Dynamic formatting capabilities through the `DynDisplay` trait
1390/// - The ability to pre-parse format strings for reuse
1391///
1392/// However, for simple cases where you don't need these features, the standard `format!`
1393/// macro is recommended.
1394#[macro_export]
1395macro_rules! dformat {
1396 ($fmt: expr, $($arg: expr),*) => {
1397 {
1398 let mut w = $crate::Writer::from($fmt);
1399 $(
1400 w.push_arg(&$arg);
1401 )*
1402
1403 match w.format() {
1404 Err(e) => Err(e),
1405 Ok(_) => Ok(w.into_string()),
1406 }
1407 }
1408 };
1409}
1410
1411#[cfg(test)]
1412mod tests {
1413 use std::{
1414 ffi::{OsStr, OsString},
1415 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
1416 path::{Path, PathBuf},
1417 rc::Rc,
1418 sync::Arc,
1419 time::{Duration, Instant, SystemTime},
1420 };
1421
1422 use pest::Parser;
1423
1424 use super::*;
1425
1426 macro_rules! dformat_lit {
1427 ($fmt: literal, $($arg: expr),*) => {
1428 {
1429 let fs = FormatString::new_from_str($fmt).unwrap();
1430 dformat!(&fs, $($arg),*)
1431 }
1432 };
1433 }
1434
1435 #[test]
1436 fn test_rule_format() {
1437 for f in [
1438 "{}", "{0}", "{name}", "{:>5}", "{:<5}", "{:^5}", "{:05}", "{:+}", "{:-}", "{:#b}",
1439 "{:#o}", "{:#x}", "{:.2}", "{:08.2}", "{:x}", "{:X}", "{:o}", "{:b}", "{:e}", "{:E}",
1440 "{:?}", "{:#?}", "{:p}",
1441 ] {
1442 let fmt = Format::from_pair(
1443 FmtParser::parse(Rule::format, f)
1444 .inspect_err(|e| println!("{e}"))
1445 .unwrap()
1446 .next()
1447 .unwrap(),
1448 );
1449
1450 assert_eq!(f, format!("{fmt}"));
1451 }
1452 }
1453
1454 #[test]
1455 fn test_integer_formatting() {
1456 // Decimal formatting
1457 assert_eq!(format!("{}", 42), dformat_lit!("{}", 42).unwrap());
1458 assert_eq!(format!("{:5}", 42), dformat_lit!("{:5}", 42).unwrap());
1459 assert_eq!(format!("{:05}", 42), dformat_lit!("{:05}", 42).unwrap());
1460 assert_eq!(format!("{:+}", 42), dformat_lit!("{:+}", 42).unwrap());
1461 assert_eq!(format!("{: }", 42), dformat_lit!("{: }", 42).unwrap());
1462 assert_eq!(format!("{:#}", 42), dformat_lit!("{:#}", 42).unwrap());
1463
1464 // Hexadecimal formatting
1465 assert_eq!(format!("{:x}", 42), dformat_lit!("{:x}", 42).unwrap());
1466 assert_eq!(format!("{:X}", 42), dformat_lit!("{:X}", 42).unwrap());
1467 assert_eq!(format!("{:#x}", 42), dformat_lit!("{:#x}", 42).unwrap());
1468 assert_eq!(format!("{:#X}", 42), dformat_lit!("{:#X}", 42).unwrap());
1469
1470 // Octal formatting
1471 assert_eq!(format!("{:o}", 42), dformat_lit!("{:o}", 42).unwrap());
1472 assert_eq!(format!("{:#o}", 42), dformat_lit!("{:#o}", 42).unwrap());
1473
1474 // Binary formatting
1475 assert_eq!(format!("{:b}", 42), dformat_lit!("{:b}", 42).unwrap());
1476 assert_eq!(format!("{:#b}", 42), dformat_lit!("{:#b}", 42).unwrap());
1477
1478 // Width and alignment
1479 assert_eq!(format!("{:5}", 42), dformat_lit!("{:5}", 42).unwrap());
1480 assert_eq!(format!("{:<5}", 42), dformat_lit!("{:<5}", 42).unwrap());
1481 assert_eq!(format!("{:>5}", 42), dformat_lit!("{:>5}", 42).unwrap());
1482 assert_eq!(format!("{:^5}", 42), dformat_lit!("{:^5}", 42).unwrap());
1483 assert_eq!(format!("{:05}", 42), dformat_lit!("{:05}", 42).unwrap());
1484 assert_eq!(format!("{:+<5}", 42), dformat_lit!("{:+<5}", 42).unwrap());
1485 assert_eq!(format!("{:->5}", 42), dformat_lit!("{:->5}", 42).unwrap());
1486 assert_eq!(format!("{:+^5}", 42), dformat_lit!("{:+^5}", 42).unwrap());
1487 }
1488
1489 #[test]
1490 fn test_float_formatting() {
1491 // Float formatting
1492 assert_eq!(format!("{}", 42.0), dformat_lit!("{}", 42.0).unwrap());
1493 assert_eq!(format!("{:e}", 42.0), dformat_lit!("{:e}", 42.0).unwrap());
1494 assert_eq!(format!("{:E}", 42.0), dformat_lit!("{:E}", 42.0).unwrap());
1495 assert_eq!(format!("{:.2}", 42.0), dformat_lit!("{:.2}", 42.0).unwrap());
1496 assert_eq!(
1497 format!("{:.2}", 42.1234),
1498 dformat_lit!("{:.2}", 42.1234).unwrap()
1499 );
1500 assert_eq!(
1501 format!("{:10.2}", 42.1234),
1502 dformat_lit!("{:10.2}", 42.1234).unwrap()
1503 );
1504 assert_eq!(
1505 format!("{:<10.2}", 42.1234),
1506 dformat_lit!("{:<10.2}", 42.1234).unwrap()
1507 );
1508 assert_eq!(
1509 format!("{:>10.2}", 42.1234),
1510 dformat_lit!("{:>10.2}", 42.1234).unwrap()
1511 );
1512 assert_eq!(
1513 format!("{:^10.2}", 42.1234),
1514 dformat_lit!("{:^10.2}", 42.1234).unwrap()
1515 );
1516 assert_eq!(format!("{:+}", 42.0), dformat_lit!("{:+}", 42.0).unwrap());
1517 assert_eq!(
1518 format!("{:+<10.2}", 42.1234),
1519 dformat_lit!("{:+<10.2}", 42.1234).unwrap()
1520 );
1521 assert_eq!(
1522 format!("{:->10.2}", 42.1234),
1523 dformat_lit!("{:->10.2}", 42.1234).unwrap()
1524 );
1525 assert_eq!(
1526 format!("{:+^10.2}", 42.1234),
1527 dformat_lit!("{:+^10.2}", 42.1234).unwrap()
1528 );
1529 }
1530
1531 #[test]
1532 fn test_string_formatting() {
1533 // String formatting
1534 assert_eq!(format!("{}", "hello"), dformat_lit!("{}", "hello").unwrap());
1535 assert_eq!(
1536 format!("Hello, {}!", "world"),
1537 dformat_lit!("Hello, {}!", "world").unwrap()
1538 );
1539 assert_eq!(
1540 format!("{:10}", "hello"),
1541 dformat_lit!("{:10}", "hello").unwrap()
1542 );
1543 assert_eq!(
1544 format!("{:<10}", "hello"),
1545 dformat_lit!("{:<10}", "hello").unwrap()
1546 );
1547 assert_eq!(
1548 format!("{:>10}", "hello"),
1549 dformat_lit!("{:>10}", "hello").unwrap()
1550 );
1551 assert_eq!(
1552 format!("{:^10}", "hello"),
1553 dformat_lit!("{:^10}", "hello").unwrap()
1554 );
1555
1556 // String precision
1557 assert_eq!(
1558 format!("{:.3}", "hello"),
1559 dformat_lit!("{:.3}", "hello").unwrap()
1560 );
1561 assert_eq!(
1562 format!("{:10.3}", "hello"),
1563 dformat_lit!("{:10.3}", "hello").unwrap()
1564 );
1565 assert_eq!(
1566 format!("{:<10.3}", "hello"),
1567 dformat_lit!("{:<10.3}", "hello").unwrap()
1568 );
1569 assert_eq!(
1570 format!("{:>10.3}", "hello"),
1571 dformat_lit!("{:>10.3}", "hello").unwrap()
1572 );
1573 assert_eq!(
1574 format!("{:^10.3}", "hello"),
1575 dformat_lit!("{:^10.3}", "hello").unwrap()
1576 );
1577 }
1578
1579 #[test]
1580 fn test_char_formatting() {
1581 // Character formatting
1582 assert_eq!(format!("{}", 'A'), dformat_lit!("{}", 'A').unwrap());
1583 assert_eq!(format!("{:5}", 'A'), dformat_lit!("{:5}", 'A').unwrap());
1584 assert_eq!(format!("{:<5}", 'A'), dformat_lit!("{:<5}", 'A').unwrap());
1585 assert_eq!(format!("{:>5}", 'A'), dformat_lit!("{:>5}", 'A').unwrap());
1586 assert_eq!(format!("{:^5}", 'A'), dformat_lit!("{:^5}", 'A').unwrap());
1587 }
1588
1589 #[test]
1590 fn test_bool_formatting() {
1591 // Boolean formatting
1592 assert_eq!(format!("{}", true), dformat_lit!("{}", true).unwrap());
1593 assert_eq!(format!("{}", false), dformat_lit!("{}", false).unwrap());
1594 assert_eq!(format!("{:5}", true), dformat_lit!("{:5}", true).unwrap());
1595 assert_eq!(format!("{:<5}", true), dformat_lit!("{:<5}", true).unwrap());
1596 assert_eq!(format!("{:>5}", true), dformat_lit!("{:>5}", true).unwrap());
1597 assert_eq!(format!("{:^5}", true), dformat_lit!("{:^5}", true).unwrap());
1598 }
1599
1600 #[test]
1601 fn test_pointer_formatting() {
1602 // Pointer formatting
1603 let x = 42;
1604 let ptr = &x as *const i32;
1605 assert_eq!(format!("{ptr:p}"), dformat_lit!("{:p}", ptr).unwrap());
1606 assert_eq!(format!("{ptr:10p}"), dformat_lit!("{:10p}", ptr).unwrap());
1607 assert_eq!(format!("{ptr:<10p}"), dformat_lit!("{:<10p}", ptr).unwrap());
1608 assert_eq!(format!("{ptr:>10p}"), dformat_lit!("{:>10p}", ptr).unwrap());
1609 assert_eq!(format!("{ptr:^10p}"), dformat_lit!("{:^10p}", ptr).unwrap());
1610 }
1611
1612 #[test]
1613 fn test_multiple_arguments() {
1614 // Multiple arguments
1615 assert_eq!(
1616 format!("{} {}", "hello", 42),
1617 dformat_lit!("{} {}", "hello", 42).unwrap()
1618 );
1619 assert_eq!(
1620 format!("{:5} {:<10.2}", 42, 42.1234),
1621 dformat_lit!("{:5} {:<10.2}", 42, 42.1234).unwrap()
1622 );
1623 assert_eq!(
1624 format!("{:>5} {:^10.2}", 42, 42.1234),
1625 dformat_lit!("{:>5} {:^10.2}", 42, 42.1234).unwrap()
1626 );
1627 }
1628
1629 #[test]
1630 fn test_complex_formatting() {
1631 // Complex formatting
1632 assert_eq!(
1633 format!("{:05} {:<10.2} {:^10}", 42, 42.1234, "hello"),
1634 dformat_lit!("{:05} {:<10.2} {:^10}", 42, 42.1234, "hello").unwrap()
1635 );
1636 assert_eq!(
1637 format!("{:+<5} {:^10.2} {:>10}", 42, 42.1234, "hello"),
1638 dformat_lit!("{:+<5} {:^10.2} {:>10}", 42, 42.1234, "hello").unwrap()
1639 );
1640 }
1641
1642 #[test]
1643 fn test_network_types() {
1644 // IpAddr, Ipv4Addr, Ipv6Addr
1645 let ipv4_addr = Ipv4Addr::new(127, 0, 0, 1);
1646 let ipv6_addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
1647 let ip_addr_v4 = IpAddr::V4(ipv4_addr);
1648 let ip_addr_v6 = IpAddr::V6(ipv6_addr);
1649
1650 assert_eq!(
1651 format!("{ipv4_addr}"),
1652 dformat_lit!("{}", ipv4_addr).unwrap()
1653 );
1654 assert_eq!(
1655 format!("{ipv4_addr:?}"),
1656 dformat_lit!("{:?}", ipv4_addr).unwrap()
1657 );
1658 assert_eq!(
1659 format!("{ipv6_addr}"),
1660 dformat_lit!("{}", ipv6_addr).unwrap()
1661 );
1662 assert_eq!(
1663 format!("{ipv6_addr:?}"),
1664 dformat_lit!("{:?}", ipv6_addr).unwrap()
1665 );
1666 assert_eq!(
1667 format!("{ip_addr_v4}"),
1668 dformat_lit!("{}", ip_addr_v4).unwrap()
1669 );
1670 assert_eq!(
1671 format!("{ip_addr_v4:?}"),
1672 dformat_lit!("{:?}", ip_addr_v4).unwrap()
1673 );
1674 assert_eq!(
1675 format!("{ip_addr_v6}"),
1676 dformat_lit!("{}", ip_addr_v6).unwrap()
1677 );
1678 assert_eq!(
1679 format!("{ip_addr_v6:?}"),
1680 dformat_lit!("{:?}", ip_addr_v6).unwrap()
1681 );
1682
1683 // SocketAddr, SocketAddrV4, SocketAddrV6
1684 let socket_addr_v4 = SocketAddrV4::new(ipv4_addr, 8080);
1685 let socket_addr_v6 = SocketAddrV6::new(ipv6_addr, 8080, 0, 0);
1686 let socket_addr = SocketAddr::V4(socket_addr_v4);
1687
1688 assert_eq!(
1689 format!("{socket_addr_v4}"),
1690 dformat_lit!("{}", socket_addr_v4).unwrap()
1691 );
1692 assert_eq!(
1693 format!("{socket_addr_v4:?}"),
1694 dformat_lit!("{:?}", socket_addr_v4).unwrap()
1695 );
1696 assert_eq!(
1697 format!("{socket_addr_v6}"),
1698 dformat_lit!("{}", socket_addr_v6).unwrap()
1699 );
1700 assert_eq!(
1701 format!("{socket_addr_v6:?}"),
1702 dformat_lit!("{:?}", socket_addr_v6).unwrap()
1703 );
1704 assert_eq!(
1705 format!("{socket_addr}"),
1706 dformat_lit!("{}", socket_addr).unwrap()
1707 );
1708 assert_eq!(
1709 format!("{socket_addr:?}"),
1710 dformat_lit!("{:?}", socket_addr).unwrap()
1711 );
1712 }
1713
1714 #[test]
1715 fn test_time_types() {
1716 // Duration, SystemTime, Instant
1717 let duration = Duration::from_secs(3600);
1718 let system_time = SystemTime::now();
1719 let instant = Instant::now();
1720
1721 assert_eq!(
1722 format!("{duration:?}"),
1723 dformat_lit!("{:?}", duration).unwrap()
1724 );
1725 assert_eq!(
1726 format!("{system_time:?}"),
1727 dformat_lit!("{:?}", system_time).unwrap()
1728 );
1729 assert_eq!(
1730 format!("{instant:?}"),
1731 dformat_lit!("{:?}", instant).unwrap()
1732 );
1733 }
1734
1735 #[test]
1736 fn test_path_types() {
1737 // Path, PathBuf
1738 let path = Path::new("/path/to/file");
1739 let path_buf = PathBuf::from("/path/to/file");
1740
1741 assert_eq!(format!("{path:?}"), dformat_lit!("{:?}", path).unwrap());
1742 assert_eq!(
1743 format!("{path_buf:?}"),
1744 dformat_lit!("{:?}", path_buf).unwrap()
1745 );
1746 }
1747
1748 #[test]
1749 fn test_ffi_types() {
1750 // OsString, OsStr
1751 let os_string = OsString::from("OS String");
1752 let os_str: &OsStr = os_string.as_os_str();
1753
1754 assert_eq!(
1755 format!("{os_string:?}"),
1756 dformat_lit!("{:?}", os_string).unwrap()
1757 );
1758 assert_eq!(format!("{os_str:?}"), dformat_lit!("{:?}", os_str).unwrap());
1759 }
1760
1761 #[test]
1762 fn test_smart_pointers() {
1763 // Box, Rc, Arc, Cow
1764 let boxed = Box::new(42);
1765 let rc = Rc::new(42);
1766 let arc = Arc::new(42);
1767 let cow_str: Cow<'_, str> = Cow::Borrowed("Hello");
1768
1769 assert_eq!(format!("{boxed}"), dformat_lit!("{}", boxed).unwrap());
1770 assert_eq!(format!("{rc}"), dformat_lit!("{}", rc).unwrap());
1771 assert_eq!(format!("{arc}"), dformat_lit!("{}", arc).unwrap());
1772 assert_eq!(format!("{cow_str}"), dformat_lit!("{}", cow_str).unwrap());
1773 }
1774
1775 #[test]
1776 #[cfg(feature = "serde")]
1777 fn test_serde() {
1778 let fs = FormatString::new_from_str("{}").unwrap();
1779 let js_fs = serde_json::to_string(&fs).unwrap();
1780 let fs: FormatString = serde_json::from_str(&js_fs).unwrap();
1781 assert_eq!(format!("{}", 42), dformat!(&fs, 42).unwrap())
1782 }
1783}