use crate::attr::Display; use proc_macro2::TokenStream; use quote::quote_spanned; use syn::{Ident, LitStr}; macro_rules! peek_next { ($read:ident) => { match $read.chars().next() { Some(next) => next, None => return, } }; } impl Display { // Transform `"error {var}"` to `"error {}", var`. pub(crate) fn expand_shorthand(&mut self) { let span = self.fmt.span(); let fmt = self.fmt.value(); let mut read = fmt.as_str(); let mut out = String::new(); let mut args = TokenStream::new(); while let Some(brace) = read.find('{') { out += &read[..=brace]; read = &read[brace + 1..]; // skip cases where we find a {{ if read.starts_with('{') { out.push('{'); read = &read[1..]; continue; } let next = peek_next!(read); let var = match next { '0'..='9' => take_int(&mut read), 'a'..='z' | 'A'..='Z' | '_' => take_ident(&mut read), _ => return, }; let ident = Ident::new(&var, span); let next = peek_next!(read); let arg = if cfg!(feature = "std") && next == '}' { quote_spanned!(span=> , #ident.__displaydoc_display()) } else { quote_spanned!(span=> , #ident) }; args.extend(arg); } out += read; self.fmt = LitStr::new(&out, self.fmt.span()); self.args = args; } } fn take_int(read: &mut &str) -> String { let mut int = String::new(); int.push('_'); for (i, ch) in read.char_indices() { match ch { '0'..='9' => int.push(ch), _ => { *read = &read[i..]; break; } } } int } fn take_ident(read: &mut &str) -> String { let mut ident = String::new(); for (i, ch) in read.char_indices() { match ch { 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch), _ => { *read = &read[i..]; break; } } } ident } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; use proc_macro2::Span; fn assert(input: &str, fmt: &str, args: &str) { let mut display = Display { fmt: LitStr::new(input, Span::call_site()), args: TokenStream::new(), }; display.expand_shorthand(); assert_eq!(fmt, display.fmt.value()); assert_eq!(args, display.args.to_string()); } #[test] fn test_expand() { assert("fn main() {{ }}", "fn main() {{ }}", ""); } #[test] #[cfg_attr(not(feature = "std"), ignore)] fn test_std_expand() { assert( "{v} {v:?} {0} {0:?}", "{} {:?} {} {:?}", ", v . __displaydoc_display () , v , _0 . __displaydoc_display () , _0", ); assert("error {var}", "error {}", ", var . __displaydoc_display ()"); assert( "error {var1}", "error {}", ", var1 . __displaydoc_display ()", ); assert( "error {var1var}", "error {}", ", var1var . __displaydoc_display ()", ); assert( "The path {0}", "The path {}", ", _0 . __displaydoc_display ()", ); assert("The path {0:?}", "The path {:?}", ", _0"); } #[test] #[cfg_attr(feature = "std", ignore)] fn test_nostd_expand() { assert( "{v} {v:?} {0} {0:?}", "{} {:?} {} {:?}", ", v , v , _0 , _0", ); assert("error {var}", "error {}", ", var"); assert("The path {0}", "The path {}", ", _0"); assert("The path {0:?}", "The path {:?}", ", _0"); assert("error {var1}", "error {}", ", var1"); assert("error {var1var}", "error {}", ", var1var"); } }