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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#![recursion_limit = "256"]
#![cfg_attr(feature = "cargo-clippy", feature(tool_lints))]
#![cfg_attr(feature = "cargo-clippy", warn(clippy::all))]
//! The crate which removes most of the boilerplate from Ruukh apps.
//!
//! This lib defines `#[component]`, `#[derive(Lifecycle)]` and `html!` macros.
extern crate proc_macro;

use crate::{component::ComponentMeta, html::HtmlRoot};
use proc_macro2::Span;
use quote::quote;
use syn::{parse::Error, parse_macro_input, spanned::Spanned, DeriveInput, Item};

mod component;
mod html;
mod suffix;

/// A convenient auto derive for `Lifecycle` trait. It could be simply written
/// as `impl Lifecycle for MyComponent {}` instead, but why not save some chars.
/// You may use it like:
///
/// # Example
/// ```ignore,compile_fail
/// #[derive(Lifecycle)]
/// struct Button;
/// ```
#[proc_macro_derive(Lifecycle)]
pub fn derive_lifecycle(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let ident = input.ident;

    let expanded = quote! {
        impl Lifecycle for #ident {}
    };

    expanded.into()
}

/// `#[component]` macro to derive `Component` trait as well as to do
/// modifications to the struct. It does all the heavy lifting which the user
/// would have to do, to make the component work.
///
/// # Example
/// ```ignore,compile_fail
/// #[component]
/// struct MyButton {
///     disabled: bool,
/// }
/// ```
///
/// You may declare events available on the component by placing `#[events]`
/// attribute. Like:
/// # Example
/// ```ignore,compile_fail
/// #[component]
/// #[events(
///     fn event_name(&self, arg: type) -> return_type;  
/// )]
/// struct MyButton {
///     disabled: bool,
/// }
/// ```
/// You may place multiple event declarations. Also, these event declarations
/// require that the event handlers be passed compulsorily.
///
/// `#[component]` also allows to annotate the struct fields with additional
/// attributes. Such as:
///
/// 1. `#[prop]` attribute: This attribute defines that a field is a prop
/// field, though this attribute is optional. This attribute allows passing a
/// default value for the prop field. Like `#[prop(default)]` which delegates
/// it to the types `Default` implementation or `#[prop(default = val)]` which
/// uses the `val` as its default value.
/// Any fields which has a given `default` value or any `Option` type is
/// optional while passing props.
///
/// 2. `#[state]` attribute: This attributes is required to define a field as a
/// state field. If a `#[state]` or `#[state(default)]` is specified then the
/// `Default` value of the field is used. If you want to provide a more
/// specific value, then pass it by using `#[state(default = val)]` attribute.
#[proc_macro_attribute]
#[cfg_attr(
    feature = "cargo-clippy",
    allow(clippy::needless_pass_by_value)
)]
pub fn component(
    metadata: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    if !metadata.is_empty() {
        return Error::new(
            Span::call_site(),
            "`#[component]` does not support attribute arguments.",
        ).to_compile_error()
        .into();
    }

    let input = parse_macro_input!(input as Item);

    let expanded = match input {
        Item::Struct(struct_) => ComponentMeta::parse(struct_)
            .map(|s| s.expand())
            .unwrap_or_else(|e| e.to_compile_error()),
        _ => {
            Error::new(input.span(), "Only structs are allowed to be Component").to_compile_error()
        }
    };

    expanded.into()
}

/// `html!` macro to parse `vue`-inspired syntax to generate Markup.
///
/// The basics of using html! macro:
///
/// ## Text
/// ```ignore,compile_fail
/// html! {
///     "This is a sample text."
/// }
/// ```
///
/// ## Empty Markup
/// ```ignore,compile_fail
/// html!()
/// ```
///
/// ## Self-closing tags
/// Only html specified self-closing tags can be self-closing tags.
///
/// ```ignore,compile_fail
/// html! {
///     <br>
/// }
/// ```
///
/// ## Normal tags
/// ```ignore,compile_fail
/// html! {
///     <div></div>
///     <my-custom-tag></my-custom-tag>
/// }
/// ```
///
/// ## Component tags
/// ```ignore,compile_fail
/// html! {
///     <MyComponent></MyComponent>
/// }
/// ```
///
/// ## List of tags
/// ```ignore,compile_fail
/// html! {
///     <div></div>
///     <span></span>
///     <button></button>
/// }
/// ```
///
/// ## Nested markup
/// ```ignore,compile_fail
/// html! {
///     <div>
///         <span></span>
///     </div>
/// }
/// ```
///
/// ## Expressions in between
/// ```ignore,compile_fail
/// html! {
///     "There are "{ count }" people."
/// }
/// ```
#[proc_macro]
pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let parsed = parse_macro_input!(input as HtmlRoot);
    parsed.expand().into()
}