1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use crate::{attribute_list::AttributeList, utility::parse_crate_path};
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use std::error::Error;
use syn::{Expr, ExprLit, Item, ItemFn, ItemStruct, Lit, LitStr};

const RAW_STRING_PREFIX: &str = "r#";

pub fn generate(attributes: &AttributeList, item: &Item) -> Result<TokenStream, Box<dyn Error>> {
    match item {
        Item::Fn(function) => generate_function(attributes, function),
        Item::Struct(r#struct) => generate_struct(attributes, r#struct),
        _ => Err("only functions and structs can be quoted".into()),
    }
}

fn generate_function(
    attributes: &AttributeList,
    function: &ItemFn,
) -> Result<TokenStream, Box<dyn Error>> {
    let crate_path = parse_crate_path(attributes)?;
    let ident = &function.sig.ident;
    let visibility = &function.vis;
    let quote_name = Ident::new(&get_quote_name(ident, "_fn"), ident.span());
    let name_string = get_name_string(ident);

    Ok(quote! {
        #visibility fn #quote_name() -> #crate_path::Fn {
            #crate_path::Fn::new(#name_string, syn::parse2(quote::quote!(#function)).unwrap())
        }

        #function
    }
    .into())
}

fn generate_struct(
    attributes: &AttributeList,
    r#struct: &ItemStruct,
) -> Result<TokenStream, Box<dyn Error>> {
    let crate_path = parse_crate_path(attributes)?;
    let ident = &r#struct.ident;
    let visibility = &r#struct.vis;
    let quote_name = Ident::new(&get_quote_name(ident, "_struct"), ident.span());
    let name_string = get_name_string(ident);

    Ok(quote! {
        #visibility fn #quote_name() -> #crate_path::Struct {
            #crate_path::Struct::new(#name_string, syn::parse2(quote::quote!(#r#struct)).unwrap())
        }

        #r#struct
    }
    .into())
}

fn get_quote_name(ident: &Ident, suffix: &str) -> String {
    ident
        .to_string()
        .strip_prefix(RAW_STRING_PREFIX)
        .map(ToOwned::to_owned)
        .unwrap_or_else(|| ident.to_string())
        .to_lowercase()
        + suffix
}

fn get_name_string(ident: &Ident) -> Expr {
    Expr::Lit(ExprLit {
        attrs: Vec::new(),
        lit: Lit::Str(LitStr::new(&ident.to_string(), ident.span())),
    })
}