ori_macro/
example.rs

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use proc_macro2::Span;
use quote::ToTokens;
use syn::{
    parse::{Parse, ParseStream},
    punctuated::Punctuated,
    Item, Token,
};

enum Arg {
    Name(String),
    Width(u32),
    Height(u32),
}

impl Parse for Arg {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let name: syn::Ident = input.parse()?;
        input.parse::<Token![=]>()?;

        match name.to_string().as_str() {
            "name" => {
                let name: syn::LitStr = input.parse()?;
                Ok(Self::Name(name.value()))
            }
            "width" => {
                let width: syn::LitInt = input.parse()?;
                Ok(Self::Width(width.base10_parse()?))
            }
            "height" => {
                let height: syn::LitInt = input.parse()?;
                Ok(Self::Height(height.base10_parse()?))
            }
            _ => Err(syn::Error::new(
                name.span(),
                "expected `name`, `width`, or `height`",
            )),
        }
    }
}

struct Args {
    name: String,
    width: u32,
    height: u32,
}

impl Parse for Args {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut name = None;
        let mut width = None;
        let mut height = None;

        let items = Punctuated::<Arg, Token![,]>::parse_terminated(input)?;

        for item in items {
            match item {
                Arg::Name(value) => {
                    if name.is_some() {
                        return Err(syn::Error::new(
                            Span::call_site(),
                            "duplicate `name` argument",
                        ));
                    }
                    name = Some(value);
                }
                Arg::Width(value) => {
                    if width.is_some() {
                        return Err(syn::Error::new(
                            Span::call_site(),
                            "duplicate `width` argument",
                        ));
                    }
                    width = Some(value);
                }
                Arg::Height(value) => {
                    if height.is_some() {
                        return Err(syn::Error::new(
                            Span::call_site(),
                            "duplicate `height` argument",
                        ));
                    }
                    height = Some(value);
                }
            }
        }

        Ok(Self {
            name: name.ok_or_else(|| syn::Error::new(input.span(), "missing `name` argument"))?,
            width: width
                .ok_or_else(|| syn::Error::new(input.span(), "missing `width` argument"))?,
            height: height
                .ok_or_else(|| syn::Error::new(input.span(), "missing `height` argument"))?,
        })
    }
}

pub fn example(
    args: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> manyhow::Result<proc_macro::TokenStream> {
    let args = syn::parse::<Args>(args)?;
    let mut input = syn::parse::<Item>(input)?;

    let iframe = format!(
        "<iframe 
            src=\"https://ori-ui.github.io/ori-examples/?example={}\"
            style=\"width: {}px; height: {}px; border: none; overflow: hidden; border-radius: 6px;\"
        ></iframe>",
        args.name, args.width, args.height
    );

    match input {
        Item::Fn(ref mut item) => {
            item.attrs.push(syn::parse_quote!(#[doc = #iframe]));
        }
        Item::Struct(ref mut item) => {
            item.attrs.push(syn::parse_quote!(#[doc = #iframe]));
        }
        _ => manyhow::bail!("expected a function or a struct"),
    }

    Ok(input.into_token_stream().into())
}