1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::quote;
8
9#[proc_macro]
11pub fn input(input: TokenStream) -> TokenStream {
12 let Message { msg } = syn::parse_macro_input!(input as Message);
13
14 let print_msg = msg
16 .map(|Format { expr, args }| {
17 let msg = match expr {
18 syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(fmt), .. }) => {
20 if args.is_some() {
22 quote! { &::std::format!(#fmt, #args) }
23 }
24 else if fmt.value().contains(&['{', '}'][..]) {
26 quote! { &::std::format!(#fmt) }
27 }
28 else {
30 quote! { #fmt }
31 }
32 },
33
34 _ => panic!()
36 };
37
38 quote! {
39 use ::std::io::Write;
40
41 ::std::io::stdout().write_all(#msg.as_bytes()).unwrap();
42 ::std::io::stdout().flush().unwrap();
43 }
44 })
45 .unwrap_or(quote! {});
46
47 quote! {{
49 #print_msg
50
51 use ::std::io::BufRead;
52
53 ::std::io::stdin().lock().lines()
54 }}
55 .into()
56}
57
58struct Message {
60 pub msg: Option<Format>
61}
62
63impl syn::parse::Parse for Message {
64 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
65 Ok(Self { msg: input.parse().ok() })
66 }
67}
68
69struct Format {
71 pub expr: syn::Expr,
72 pub args: Option<TokenStream2>,
73}
74
75impl syn::parse::Parse for Format {
76 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
77 let expr = input.parse()?;
79
80 if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(_), .. }) = &expr {}
82 else { return Err(syn::Error::new_spanned(expr, "Expected literal string")) }
83
84 let args = if input.peek(syn::token::Comma) {
86 Some(input.parse()?)
87 } else {
88 None
89 };
90
91 Ok(Self { expr, args })
92 }
93}