blob: 407bf10561c24c6bba9ac2ee2b1802b947400ad4 [file] [log] [blame]
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 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");
}
}