custom-format

version Minimum supported Rust version Documentation

This crate extends the standard formatting syntax with custom format specifiers, by providing custom formatting macros.

It uses : (a space and a colon) as a separator before the format specifier, which is not a syntax currently accepted and allows supporting standard specifiers in addition to custom specifiers.

This library comes in two flavors, corresponding to the following features:

Documentation

Documentation is hosted on docs.rs.

Example with the compile-time feature

Code

```rust use customformat::compiletime as cfmt; use customformat::customformatter;

use core::fmt;

pub struct DateTime { year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8, nanoseconds: u32, }

macrorules! implcustomformatfor_datetime { (match spec { $($spec:literal => $func:expr $(,)?)* }) => { $( impl cfmt::CustomFormat<{ cfmt::spec($spec) }> for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ($func as fn(&Self, &mut fmt::Formatter) -> fmt::Result)(self, f) } } )* }; }

implcustomformatfordatetime!(match spec { // Year with pad for at least 4 digits "%Y" => |this, f| write!(f, "{:04}", this.year), // Year % 100 (00..99) "%y" => |this, f| write!(f, "{:02}", (this.year % 100).abs()), // Month of the year, zero-padded (01..12) "%m" => |this, f| write!(f, "{:02}", this.month), // Day of the month, zero-padded (01..31) "%d" => |this, f| write!(f, "{:02}", this.monthday), // Hour of the day, 24-hour clock, zero-padded (00..23) "%H" => |this, f| write!(f, "{:02}", this.hour), // Minute of the hour (00..59) "%M" => |this, f| write!(f, "{:02}", this.minute), // Second of the minute (00..60) "%S" => |this, f| write!(f, "{:02}", this.second), // Nanosecond (9 digits) "%9N" => |this, f| write!(f, "{:09}", this.nanoseconds), // Date (%m/%d/%y) "%D" => { |this, f| { let month = customformatter!("%m", this); let day = customformatter!("%d", this); let year = customformatter!("%y", this); write!(f, "{}/{}/{}", month, day, year) } } // The ISO 8601 date format (%Y-%m-%d) "%F" => { |this, f| { let year = customformatter!("%Y", this); let month = customformatter!("%m", this); let day = customformatter!("%d", this); write!(f, "{}-{}-{}", year, month, day) } } // 24-hour time (%H:%M:%S) "%T" => { |this, f| { let hour = customformatter!("%H", this); let minute = customformatter!("%M", this); let second = customformatter!("%S", this); write!(f, "{}:{}:{}", hour, minute, second) } } });

let datetime = DateTime { year: 1836, month: 5, monthday: 18, hour: 23, minute: 45, second: 54, nanoseconds: 123456789, };

// Expands to: // // match (&(datetime), &("The date time is")) { // (arg0, arg1) => { // ::std::println!( // "{0}: {1}-{2}-{3} {4}:{5}:{6}.{7}", // arg1, // ::customformat::customformatter!("%Y", arg0), // ::customformat::customformatter!("%m", arg0), // ::customformat::customformatter!("%d", arg0), // ::customformat::customformatter!("%H", arg0), // ::customformat::customformatter!("%M", arg0), // ::customformat::customformatter!("%S", arg0), // ::customformat::customformatter!("%9N", arg0) // ) // } // } // // Output: "The date time is: 1836-05-18 23:45:54.123456789" // cfmt::println!( "{prefix}: {0 :%Y}-{0 :%m}-{0 :%d} {0 :%H}:{0 :%M}:{0 :%S}.{0 :%9N}", datetime, prefix = "The date time is", );

// Compile-time error since "%h" is not a valid format specifier // cfmt::println!("{0 :%h}", date_time); ```

Example with the runtime feature

Code

```rust use custom_format::runtime::{self as cfmt, CustomFormat, CustomFormatter};

use core::fmt;

pub struct DateTime { year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8, nanoseconds: u32, }

impl CustomFormat for DateTime { fn fmt(&self, f: &mut fmt::Formatter, spec: &str) -> fmt::Result { match spec { // Year with pad for at least 4 digits "%Y" => write!(f, "{:04}", self.year), // Year % 100 (00..99) "%y" => write!(f, "{:02}", (self.year % 100).abs()), // Month of the year, zero-padded (01..12) "%m" => write!(f, "{:02}", self.month), // Day of the month, zero-padded (01..31) "%d" => write!(f, "{:02}", self.month_day), // Hour of the day, 24-hour clock, zero-padded (00..23) "%H" => write!(f, "{:02}", self.hour), // Minute of the hour (00..59) "%M" => write!(f, "{:02}", self.minute), // Second of the minute (00..60) "%S" => write!(f, "{:02}", self.second), // Nanosecond (9 digits) "%9N" => write!(f, "{:09}", self.nanoseconds), // Date (%m/%d/%y) "%D" => { let month = CustomFormatter::new("%m", self); let day = CustomFormatter::new("%d", self); let year = CustomFormatter::new("%y", self); write!(f, "{}/{}/{}", month, day, year) } // The ISO 8601 date format (%Y-%m-%d) "%F" => { let year = CustomFormatter::new("%Y", self); let month = CustomFormatter::new("%m", self); let day = CustomFormatter::new("%d", self); write!(f, "{}-{}-{}", year, month, day) } // 24-hour time (%H:%M:%S) "%T" => { let hour = CustomFormatter::new("%H", self); let minute = CustomFormatter::new("%M", self); let second = CustomFormatter::new("%S", self); write!(f, "{}:{}:{}", hour, minute, second) } // Invalid format specifier _ => Err(fmt::Error), } } }

let datetime = DateTime { year: 1836, month: 5, monthday: 18, hour: 23, minute: 45, second: 54, nanoseconds: 123456789, };

// Expands to: // // match (&(datetime), &("The date time is")) { // (arg0, arg1) => { // ::std::println!( // "{0}: {1}-{2}-{3} {4}:{5}:{6}.{7}", // arg1, // ::customformat::runtime::CustomFormatter::new("%Y", arg0), // ::customformat::runtime::CustomFormatter::new("%m", arg0), // ::customformat::runtime::CustomFormatter::new("%d", arg0), // ::customformat::runtime::CustomFormatter::new("%H", arg0), // ::customformat::runtime::CustomFormatter::new("%M", arg0), // ::customformat::runtime::CustomFormatter::new("%S", arg0), // ::customformat::runtime::CustomFormatter::new("%9N", arg0) // ) // } // } // // Output: "The date time is: 1836-05-18 23:45:54.123456789" // cfmt::println!( "{prefix}: {0 :%Y}-{0 :%m}-{0 :%d} {0 :%H}:{0 :%M}:{0 :%S}.{0 :%9N}", date_time, prefix = "The date time is", );

// Panic at runtime since "%h" is not a valid format specifier // cfmt::println!("{0 :%h}", date_time); ```

Limitations

To improve compilation time, this crate doesn't use the syn crate for parsing tokens, and instead use a simple method to separate arguments of procedural macros: the arguments are separated by the , token when not in a delimited group.

This works for 99% of cases but cannot parse correctly expressions containing commas inside turbofishs or closures without delimiters:

rust // Compilation error due to incorrect parsing cfmt::println!("{ :?}", |a, b| a); cfmt::println!("{:?}", HashMap::<u32, u32>::new());

The workaround is simply to add an additional delimited group, or to define a new variable:

```rust // No compilation error

cfmt::println!("{ :?}", (|a, b| a)); // if a custom specifier is defined cfmt::println!("{:?}", (HashMap::::new())); cfmt::println!("{:?}", { HashMap::::new() });

let map = HashMap::::new(); cfmt::println!("{map:?}"); cfmt::println!("{map:?}", map = map); ```

Compiler support

Requires rustc 1.45+ for the runtime feature and rustc 1.51+ for the compile-time feature.

License

This project is licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.