macron_str/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::quote;
6
7/// Creates a new instance of [String](https://doc.rust-lang.org/stable/std/string/struct.String.html)
8#[proc_macro]
9pub fn str(input: TokenStream) -> TokenStream {
10    // empty string:
11    if input.is_empty() {
12        return quote! { ::std::string::String::new() }.into();
13    }
14    
15    // string format:
16    let Format { expr, args } = syn::parse_macro_input!(input as Format);
17    
18    match expr {
19        // literal string:
20        syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(fmt), .. }) => {
21            // str!("a format {}..", "text")
22            if args.is_some() {
23                quote! { ::std::format!(#fmt #args) }
24            }
25            // str!("a format {text}..")
26            else if fmt.value().contains(&['{', '}'][..])  {
27                quote! { ::std::format!(#fmt) }
28            }
29            // str!("a simple text..")
30            else {
31                quote! { #fmt.to_owned() }
32            }
33        },
34
35        // not a literal string:
36        _ => quote! { #expr.to_string() }
37    }.into()
38}
39
40// The string formatter
41struct Format {
42    pub expr: syn::Expr,
43    pub args: Option<TokenStream2>,
44}
45
46impl syn::parse::Parse for Format {
47    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
48        // parse expression:
49        let expr = input.parse()?;
50
51        // parse arguments:
52        let args = if input.peek(syn::token::Comma) {
53            Some(input.parse()?)
54        } else {
55            None
56        };
57        
58        Ok(Self { expr, args })
59    }
60}