|  | 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"); | 
|  | } | 
|  | } |