displaydoc/
fmt.rs

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::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
77fn 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)]
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}