1 use crate::attr::Display;
2 use proc_macro2::TokenStream;
3 use quote::quote_spanned;
4 use syn::{Ident, LitStr};
5 
6 macro_rules! peek_next {
7     ($read:ident) => {
8         match $read.chars().next() {
9             Some(next) => next,
10             None => return,
11         }
12     };
13 }
14 
15 impl Display {
16     // Transform `"error {var}"` to `"error {}", var`.
expand_shorthand(&mut self)17     pub(crate) fn expand_shorthand(&mut self) {
18         let span = self.fmt.span();
19         let fmt = self.fmt.value();
20         let mut read = fmt.as_str();
21         let mut out = String::new();
22         let mut args = TokenStream::new();
23 
24         while let Some(brace) = read.find('{') {
25             out += &read[..=brace];
26             read = &read[brace + 1..];
27 
28             // skip cases where we find a {{
29             if read.starts_with('{') {
30                 out.push('{');
31                 read = &read[1..];
32                 continue;
33             }
34 
35             let next = peek_next!(read);
36 
37             let var = match next {
38                 '0'..='9' => take_int(&mut read),
39                 'a'..='z' | 'A'..='Z' | '_' => take_ident(&mut read),
40                 _ => return,
41             };
42 
43             let ident = Ident::new(&var, span);
44 
45             let next = peek_next!(read);
46 
47             let arg = if cfg!(feature = "std") && next == '}' {
48                 quote_spanned!(span=> , #ident.__displaydoc_display())
49             } else {
50                 quote_spanned!(span=> , #ident)
51             };
52 
53             args.extend(arg);
54         }
55 
56         out += read;
57         self.fmt = LitStr::new(&out, self.fmt.span());
58         self.args = args;
59     }
60 }
61 
take_int(read: &mut &str) -> String62 fn take_int(read: &mut &str) -> String {
63     let mut int = String::new();
64     int.push('_');
65     for (i, ch) in read.char_indices() {
66         match ch {
67             '0'..='9' => int.push(ch),
68             _ => {
69                 *read = &read[i..];
70                 break;
71             }
72         }
73     }
74     int
75 }
76 
take_ident(read: &mut &str) -> String77 fn take_ident(read: &mut &str) -> String {
78     let mut ident = String::new();
79     for (i, ch) in read.char_indices() {
80         match ch {
81             'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
82             _ => {
83                 *read = &read[i..];
84                 break;
85             }
86         }
87     }
88     ident
89 }
90 
91 #[cfg(test)]
92 mod tests {
93     use super::*;
94     use pretty_assertions::assert_eq;
95     use proc_macro2::Span;
96 
assert(input: &str, fmt: &str, args: &str)97     fn assert(input: &str, fmt: &str, args: &str) {
98         let mut display = Display {
99             fmt: LitStr::new(input, Span::call_site()),
100             args: TokenStream::new(),
101         };
102         display.expand_shorthand();
103         assert_eq!(fmt, display.fmt.value());
104         assert_eq!(args, display.args.to_string());
105     }
106 
107     #[test]
test_expand()108     fn test_expand() {
109         assert("fn main() {{ }}", "fn main() {{ }}", "");
110     }
111 
112     #[test]
113     #[cfg_attr(not(feature = "std"), ignore)]
test_std_expand()114     fn test_std_expand() {
115         assert(
116             "{v} {v:?} {0} {0:?}",
117             "{} {:?} {} {:?}",
118             ", v . __displaydoc_display () , v , _0 . __displaydoc_display () , _0",
119         );
120         assert("error {var}", "error {}", ", var . __displaydoc_display ()");
121 
122         assert(
123             "error {var1}",
124             "error {}",
125             ", var1 . __displaydoc_display ()",
126         );
127 
128         assert(
129             "error {var1var}",
130             "error {}",
131             ", var1var . __displaydoc_display ()",
132         );
133 
134         assert(
135             "The path {0}",
136             "The path {}",
137             ", _0 . __displaydoc_display ()",
138         );
139         assert("The path {0:?}", "The path {:?}", ", _0");
140     }
141 
142     #[test]
143     #[cfg_attr(feature = "std", ignore)]
test_nostd_expand()144     fn test_nostd_expand() {
145         assert(
146             "{v} {v:?} {0} {0:?}",
147             "{} {:?} {} {:?}",
148             ", v , v , _0 , _0",
149         );
150         assert("error {var}", "error {}", ", var");
151 
152         assert("The path {0}", "The path {}", ", _0");
153         assert("The path {0:?}", "The path {:?}", ", _0");
154 
155         assert("error {var1}", "error {}", ", var1");
156 
157         assert("error {var1var}", "error {}", ", var1var");
158     }
159 }
160