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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
#![doc = include_str!("../README.proj.md")]
/*!
## Features

- `live-reload` -- enables reloading the browser automatically when you make changes to your app
- `hsr` -- enables *hot state reloading*, which reloads the state of your app right before you made code changes in development, allowing you to pick up where you left off

## Packages

This is the API documentation for the `perseus-macro` package, which manages Perseus' procedural macros. Note that Perseus mostly uses [the book](https://arctic-hen7.github.io/perseus/en-US) for
documentation, and this should mostly be used as a secondary reference source. You can also find full usage examples [here](https://github.com/arctic-hen7/perseus/tree/main/examples).
*/

mod entrypoint;
mod head;
mod rx_state;
mod state_fns;
mod template;
mod template_rx;
mod test;

use darling::FromMeta;
use proc_macro::TokenStream;
use quote::quote;
use state_fns::StateFnType;
use syn::{ItemStruct, Path};

/// Annotates functions used for generating state at build time to support
/// automatic serialization/deserialization of app state and client/server
/// division. This supersedes the old `autoserde` macro for build state
/// functions.
#[proc_macro_attribute]
pub fn build_state(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as state_fns::StateFn);

    state_fns::state_fn_impl(parsed, StateFnType::BuildState).into()
}

/// Annotates functions used for generating paths at build time to support
/// automatic serialization/deserialization of app state and client/server
/// division. This supersedes the old `autoserde` macro for build paths
/// functions.
#[proc_macro_attribute]
pub fn build_paths(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as state_fns::StateFn);

    state_fns::state_fn_impl(parsed, StateFnType::BuildPaths).into()
}

/// Annotates functions used for generating global state at build time to
/// support automatic serialization/deserialization of app state and
/// client/server division. This supersedes the old `autoserde` macro for global
/// build state functions.
#[proc_macro_attribute]
pub fn global_build_state(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as state_fns::StateFn);

    state_fns::state_fn_impl(parsed, StateFnType::GlobalBuildState).into()
}

/// Annotates functions used for generating state at request time to support
/// automatic serialization/deserialization of app state and client/server
/// division. This supersedes the old `autoserde` macro for request state
/// functions.
#[proc_macro_attribute]
pub fn request_state(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as state_fns::StateFn);

    state_fns::state_fn_impl(parsed, StateFnType::RequestState).into()
}

/// Annotates functions used for generating state at build time to support
/// automatic serialization/deserialization of app state and client/server
/// division. This supersedes the old `autoserde` macro for build state
/// functions.
#[proc_macro_attribute]
pub fn set_headers(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as state_fns::StateFn);

    state_fns::state_fn_impl(parsed, StateFnType::SetHeaders).into()
}

/// Annotates functions used for amalgamating build-time and request-time states
/// to support automatic serialization/deserialization of app state and
/// client/server division. This supersedes the old `autoserde` macro for state
/// amalgamation functions.
#[proc_macro_attribute]
pub fn amalgamate_states(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as state_fns::StateFn);

    state_fns::state_fn_impl(parsed, StateFnType::AmalgamateStates).into()
}

/// Annotates functions used for checking if a template should revalidate and
/// request-time states to support automatic serialization/deserialization
/// of app state and client/server division. This supersedes the old `autoserde`
/// macro for revalidation determination functions.
#[proc_macro_attribute]
pub fn should_revalidate(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as state_fns::StateFn);

    state_fns::state_fn_impl(parsed, StateFnType::ShouldRevalidate).into()
}

/// Labels a Sycamore component as a Perseus template, turning it into something
/// that can be easily inserted into the `.template()` function, avoiding the
/// need for you to manually serialize/deserialize things. This should be
/// provided the name of the Sycamore component (same as given to Sycamore's
/// `#[component()]`, but without the `<G>`).
#[proc_macro_attribute]
pub fn template(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as template::TemplateFn);

    template::template_impl(parsed).into()
}

/// The new version of `#[template]` designed for reactive state. This can
/// interface automatically with global state, and will automatically provide
/// Sycamore `#[component]` annotations. To use this, you don't need to provide
/// anything other than an optional custom type parameter letter (by default,
/// `G` will be used). Unlike with the original macro, this will automatically
/// handle component names internally.
///
/// The first argument your template function can take is state generated for it
/// (e.g. by the *build state* strategy), but the reactive version (created with
/// `#[make_rx]` usually). From this, Perseus can infer the other required types
/// and automatically make your state reactive for you.
///
/// The second argument your template function can take is a global state
/// generated with the `GlobalStateCreator`. You should also provide the
/// reactive type here, and Perseus will do all the rest in the background.
#[proc_macro_attribute]
pub fn template_rx(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as template_rx::TemplateFn);
    template_rx::template_impl(parsed).into()
}

/// Labels a function as a Perseus head function, which is very similar to a
/// template, but for the HTML metadata in the document `<head>`.
#[proc_macro_attribute]
pub fn head(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as head::HeadFn);

    head::head_impl(parsed).into()
}

/// Marks the given function as a Perseus test. Functions marked with this
/// attribute must have the following signature: `async fn foo(client: &mut
/// fantoccini::Client) -> Result<>`.
#[proc_macro_attribute]
pub fn test(args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as test::TestFn);
    let attr_args = syn::parse_macro_input!(args as syn::AttributeArgs);
    // Parse macro arguments with `darling`
    let args = match test::TestArgs::from_list(&attr_args) {
        Ok(v) => v,
        Err(e) => {
            return TokenStream::from(e.write_errors());
        }
    };

    test::test_impl(parsed, args).into()
}

/// Marks the given function as the universal entrypoint into your app. This is
/// designed for simple use-cases, and the annotated function should return
/// a `PerseusApp`. This will expand into separate `main()` functions for both
/// the browser and engine sides.
///
/// This should take an argument for the function that will produce your server.
/// In most apps using this macro (which is designed for simple use-cases), this
/// will just be something like `perseus_warp::dflt_server` (with `perseus-warp`
/// as a dependency with the `dflt-server` feature enabled).
///
/// Note that the `dflt-engine` and `client-helpers` features must be enabled on
/// `perseus` for this to work. (These are enabled by default.)
///
/// Note further that you'll need to have `wasm-bindgen` as a dependency to use
/// this.
#[proc_macro_attribute]
pub fn main(args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as entrypoint::MainFn);
    let args = syn::parse_macro_input!(args as Path);

    entrypoint::main_impl(parsed, args).into()
}

/// This is identical to `#[main]`, except it doesn't require a server
/// integration, because it sets your app up for exporting only. This is useful
/// for apps not using server-requiring features (like incremental static
/// generation and revalidation) that want to avoid bringing in another
/// dependency on the server-side.
#[proc_macro_attribute]
pub fn main_export(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as entrypoint::MainFn);

    entrypoint::main_export_impl(parsed).into()
}

/// Marks the given function as the browser entrypoint into your app. This is
/// designed for more complex apps that need to manually distinguish between the
/// engine and browser entrypoints.
///
/// If you just want to run some simple customizations, you should probably use
/// `perseus::run_client` to use the default client logic after you've made your
/// modifications. `perseus::ClientReturn` should be your return type no matter
/// what.
///
/// Note that any generics on the annotated function will not be preserved. You
/// should put the `PerseusApp` generator in a separate function.
///
/// Note further that you'll need to have `wasm-bindgen` as a dependency to use
/// this.
#[proc_macro_attribute]
pub fn browser_main(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as entrypoint::MainFn);

    entrypoint::browser_main_impl(parsed).into()
}

/// Marks the given function as the engine entrypoint into your app. This is
/// designed for more complex apps that need to manually distinguish between the
/// engine and browser entrypoints.
///
/// If you just want to run some simple customizations, you should probably use
/// `perseus::run_dflt_engine` with `perseus::builder::get_op` to use the
/// default client logic after you've made your modifications. You'll also want
/// to return an exit code from this function (use `std::process:exit(..)`).
///
/// Note that the `dflt-engine` and `client-helpers` features must be enabled on
/// `perseus` for this to work. (These are enabled by default.)
///
/// Note further that you'll need to have `tokio` as a dependency to use this.
///
/// Finally, note that any generics on the annotated function will not be
/// preserved. You should put the `PerseusApp` generator in a separate function.
#[proc_macro_attribute]
pub fn engine_main(_args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as entrypoint::EngineMainFn);

    entrypoint::engine_main_impl(parsed).into()
}

/// Processes the given `struct` to create a reactive version by wrapping each
/// field in a `Signal`. This will generate a new `struct` with the given name
/// and implement a `.make_rx()` method on the original that allows turning an
/// instance of the unreactive `struct` into an instance of the reactive one.
///
/// This macro automatically derives `serde::Serialize` and `serde::Deserialize`
/// on the original `struct`, so do NOT add these yourself, or errors will
/// occur. Note that you can still use Serde helper macros (e.g. `#[serde(rename
/// = "testField")]`) as usual. `Clone` will also be derived on both the
/// original and the new `struct`, so do NOT try to derive it yourself.
///
/// If one of your fields is itself a `struct`, by default it will just be
/// wrapped in a `Signal`, but you can also enable nested fine-grained
/// reactivity by adding the `#[rx::nested("field_name", FieldTypeRx)]` helper
/// attribute to the `struct` (not the field, that isn't supported by Rust yet),
/// where `field_name` is the name of the field you want to use ensted
/// reactivity on, and `FieldTypeRx` is the wrapper type that will be expected.
/// This should be created by using this macro on the original `struct` type.
///
/// Note that this will be deprecated or significantly altered by Sycamore's new
/// observables system (when it's released). For that reason, this doesn't
/// support more advanced features like leaving some fields unreactive, this is
/// an all-or-nothing solution for now.
///
/// # Examples
///
/// ```rust,ignore
/// use serde::{Serialize, Deserialize};
/// use perseus::make_rx;
/// // We need this trait in scope to manually invoke `.make_rx()`
/// use perseus::state::MakeRx;
///
/// #[make_rx(TestRx)]
/// // Notice that we don't need to derive `Serialize`,`Deserialize`, or `Clone`, the macro does it for us
/// #[rx::nested("nested", NestedRx)]
/// struct Test {
///     #[serde(rename = "foo_test")]
///     foo: String,
///     bar: u16,
///     // This will get simple reactivity
///     baz: Baz,
///     // This will get fine-grained reactivity
///     // We use the unreactive type in the declaration, and tell the macro what the reactive type is in the annotation above
///     nested: Nested
/// }
/// // On unreactive types, we'll need to derive `Serialize` and `Deserialize` as usual
/// #[derive(Serialize, Deserialize, Clone)]
/// struct Baz {
///     test: String
/// }
/// #[perseus_macro::make_rx(NestedRx)]
/// struct Nested {
///     test: String
/// }
///
/// let new = Test {
///     foo: "foo".to_string(),
///     bar: 5,
///     baz: Baz {
///         // We won't be able to `.set()` this
///         test: "test".to_string()
///     },
///     nested: Nested {
///         // We will be able to `.set()` this
///         test: "nested".to_string()
///     }
/// }.make_rx();
/// // Simple reactivity
/// new.bar.set(6);
/// // Simple reactivity on a `struct`
/// new.baz.set(Baz {
///     test: "updated".to_string()
/// });
/// // Nested reactivity on a `struct`
/// new.nested.test.set("updated".to_string());
/// // Our own derivations still remain
/// let _new_2 = new.clone();
/// ```
#[proc_macro_attribute]
pub fn make_rx(args: TokenStream, input: TokenStream) -> TokenStream {
    let parsed = syn::parse_macro_input!(input as ItemStruct);
    let name = syn::parse_macro_input!(args as syn::Ident);

    rx_state::make_rx_impl(parsed, name).into()
}

/// Marks the annotated code as only to be run as part of the engine (the
/// server, the builder, the exporter, etc.). This resolves to a target-gate
/// that makes the annotated code run only on targets that are not `wasm32`.
#[proc_macro_attribute]
pub fn engine(_args: TokenStream, input: TokenStream) -> TokenStream {
    let input_2: proc_macro2::TokenStream = input.into();
    quote! {
        #[cfg(not(target_arch = "wasm32"))]
        #input_2
    }
    .into()
}

/// Marks the annotated code as only to be run in the browser. This is the
/// opposite of (and mutually exclusive with) `#[engine]`. This resolves to a
/// target-gate that makes the annotated code run only on targets that are
/// `wasm32`.
#[proc_macro_attribute]
pub fn browser(_args: TokenStream, input: TokenStream) -> TokenStream {
    let input_2: proc_macro2::TokenStream = input.into();
    quote! {
        #[cfg(target_arch = "wasm32")]
        #input_2
    }
    .into()
}