1use crate::attr::Display;
2use proc_macro2::TokenStream;
3use quote::quote_spanned;
4use syn::{Ident, LitStr};
5
6macro_rules! peek_next {
7 ($read:ident) => {
8 match $read.chars().next() {
9 Some(next) => next,
10 None => return,
11 }
12 };
13}
14
15impl Display {
16 // Transform `"error {var}"` to `"error {}", var`.
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
62fn take_int(read: &mut &str) -> String {
63 let mut int: String = String::new();
64 int.push(ch:'_');
65 for (i: usize, ch: char) 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
77fn take_ident(read: &mut &str) -> String {
78 let mut ident: String = String::new();
79 for (i: usize, ch: char) 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)]
92mod tests {
93 use super::*;
94 use pretty_assertions::assert_eq;
95 use proc_macro2::Span;
96
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]
108 fn test_expand() {
109 assert("fn main() {{ }}", "fn main() {{ }}", "");
110 }
111
112 #[test]
113 #[cfg_attr(not(feature = "std"), ignore)]
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)]
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