Skip to main content

euv_macros/
lib.rs

1//! euv_macros
2//!
3//! Procedural macros for the euv UI framework, including the `html!` macro
4//! for declarative UI syntax, the `class!` macro for CSS class definitions,
5//! the `css_vars!` macro for CSS custom properties, the `watch!` macro for
6//! reactive side effects, the `computed!` macro for reactive computed signals,
7//! and the `component` attribute macro.
8
9mod class;
10mod computed;
11mod html;
12mod kebab;
13mod var;
14mod watch;
15
16pub(crate) use {class::*, computed::*, html::*, kebab::*, var::*, watch::*};
17
18use std::{
19    collections::HashMap,
20    env,
21    ffi::OsStr,
22    fs::{read_dir, read_to_string},
23    iter::Peekable,
24    mem::MaybeUninit,
25    path::PathBuf,
26};
27
28use {
29    lombok_macros::*,
30    proc_macro::TokenStream,
31    proc_macro2::Span,
32    quote::{ToTokens, quote, quote_spanned},
33    syn::{
34        Attribute, Expr, ExprLit, Field, File, Ident, Item, Lit, LitStr, Path, Stmt, Token, Type,
35        Visibility, braced, parenthesized, parse,
36        parse::{Parse, ParseBuffer, ParseStream},
37        parse_file, parse2,
38        token::{Brace, Colon, Paren, Semi},
39    },
40};
41
42/// The `html!` macro for writing declarative UI in euv.
43///
44/// This macro accepts a syntax similar to Dioxus HTML:
45///
46/// ```ignore
47/// html! {
48///     div {
49///         class: c_container()
50///         h1 { "Hello, euv!" }
51///         button {
52///             onclick: move |_| { /* handle click */ },
53///             "Click me"
54///         }
55///     }
56/// }
57/// ```
58#[proc_macro]
59pub fn html(input: TokenStream) -> TokenStream {
60    parse_html(input)
61}
62
63/// The `class!` macro for defining CSS classes with style properties.
64///
65/// Each class definition creates a `Css` function that can be used
66/// in `html!` via the `class:` attribute. Styles are automatically injected
67/// into the DOM on first use.
68///
69/// ```ignore
70/// class! {
71///     pub container {
72///         max_width: "800px";
73///         margin: "0 auto";
74///     }
75///     pub(crate) header {
76///         font_size: "28px";
77///     }
78///     hidden {
79///         display: "none";
80///     }
81/// }
82/// ```
83#[proc_macro]
84pub fn class(input: TokenStream) -> TokenStream {
85    parse_class(input)
86}
87
88/// The `watch!` macro for creating reactive side effects.
89///
90/// Watches one or more signals and executes a closure whenever any of them changes.
91/// The closure is also executed once immediately with the current signal values
92/// during initialisation. This initial execution is wrapped in a suppressed-update
93/// scope so that any `.set()` calls inside the body do not trigger unnecessary
94/// DynamicNode re-renders.
95///
96/// The number of signal expressions must match the number of closure parameters.
97/// Each closure parameter receives the current value (via `.get()`) of the
98/// corresponding signal. Parameter types are optional and can be annotated
99/// after a colon.
100///
101/// ```ignore
102/// let count = use_signal(|| 0_i32);
103/// let name = use_signal(|| String::from("euv"));
104/// watch!(count, name, |count_val: i32, name_val: String| {
105///     web_sys::console::log_1(&format!("count={}, name={}", count_val, name_val).into());
106/// });
107/// ```
108#[proc_macro]
109pub fn watch(input: TokenStream) -> TokenStream {
110    parse_watch(input)
111}
112
113/// The `computed!` macro for creating reactive computed signals.
114///
115/// Watches one or more signals and derives a new signal whose value is
116/// automatically computed from the closure return value whenever any input
117/// signal changes. The closure must return a value of the specified return type.
118///
119/// The number of signal expressions must match the number of closure parameters.
120/// Each closure parameter receives the current value (via `.get()`) of the
121/// corresponding signal. Parameter types are optional and can be annotated
122/// after a colon. The return type must be specified after `->`.
123///
124/// The result signal is created via `use_signal` and updated via `set_silent`
125/// to avoid cascading re-renders. The initial value is computed immediately
126/// during first render.
127///
128/// ```ignore
129/// let first_name = use_signal(|| String::from("John"));
130/// let last_name = use_signal(|| String::from("Doe"));
131/// let full_name: Signal<String> = computed!(first_name, last_name, |first: String, last: String| -> String {
132///     format!("{} {}", first, last)
133/// });
134/// ```
135#[proc_macro]
136pub fn computed(input: TokenStream) -> TokenStream {
137    parse_computed(input)
138}
139
140/// The `css_vars!` macro for defining CSS custom properties.
141///
142/// Each variable block creates a `Css` function that, when called,
143/// injects the CSS custom properties into the DOM. Variable names are
144/// automatically prefixed with `--`.
145///
146/// Variable names can be written as unquoted kebab-case identifiers
147/// (e.g., `bg-primary`) or as quoted string literals (e.g., `"bg-primary"`).
148///
149/// ```ignore
150/// css_vars! {
151///     pub c_theme_light {
152///         bg-primary: "#f8f9fb";
153///         text-primary: "#1a1a2e";
154///     }
155/// }
156/// ```
157#[proc_macro]
158pub fn css_vars(input: TokenStream) -> TokenStream {
159    parse_css_vars(input)
160}
161
162/// The `var!` macro for referencing CSS custom properties defined via `css_vars!`.
163///
164/// The variable name can be written as an unquoted kebab-case identifier
165/// (e.g., `bg-primary`) or as a quoted string literal (e.g., `"bg-primary"`),
166/// and expands to the CSS string `"var(--bg-primary)"`.
167///
168/// ```ignore
169/// css_vars! {
170///     pub c_theme {
171///         bg-primary: "#f8f9fb";
172///     }
173/// }
174/// class! {
175///     pub c_container {
176///         background: var!(bg-primary);
177///     }
178/// }
179/// ```
180#[proc_macro]
181pub fn var(input: TokenStream) -> TokenStream {
182    parse_var(input)
183}
184
185/// The `component` attribute macro for marking component functions.
186///
187/// Only functions annotated with `#[component]` are treated as components
188/// in the `html!` macro. All other identifier tags are treated as native
189/// HTML elements (with `Tag::Element`).
190///
191/// The `html!` macro scans the project source to find `#[component]`-annotated
192/// functions at compile time, so this attribute must be present for the
193/// `html!` macro to generate a component function call.
194///
195/// # Arguments
196///
197/// - `TokenStream` - The attribute arguments (unused).
198/// - `TokenStream` - The item being annotated (passed through unchanged).
199///
200/// # Returns
201///
202/// - `TokenStream` - The original item unchanged.
203#[proc_macro_attribute]
204pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
205    item
206}