Source code

Revision control

Copy as Markdown

Other Tools

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