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