Skip to main content

holt_macros/
lib.rs

1mod doc_comments;
2mod registration_generator;
3mod story_generator;
4mod story_macro_args;
5mod variant_generator;
6
7use proc_macro::TokenStream as TS1;
8use quote::quote;
9use syn::{ItemConst, ItemFn, Meta, Token, parse, parse::Parser, punctuated::Punctuated};
10
11use crate::{
12    registration_generator::RegistrationGenerator, story_generator::StoryGenerator,
13    story_macro_args::StoryMacroArgs, variant_generator::VariantGenerator,
14};
15
16/// Story generation macro
17///
18/// This macro is used to generate a [Story][holt_book::Story] from a constant
19/// expression, as well as registering the story with the inventory.
20///
21/// Define a `const` that's a list of variants (defined with the [variant]
22/// macro). You can set the const type to `()`, since we overwrite the whole
23/// const to be a [Story][holt_book::Story].
24///
25/// You have to pass the following args:
26///
27/// - `id`: The unique identifier for the story, URL-safe.
28/// - `name`: The UI name of the story.
29///
30/// You can optionally document the story using regular Rust doc comments.
31/// Additionally, you can set extra documentation (appended at the end) using
32/// the `extra_docs` argument. This should be a reference to a `const &'static
33/// str`. This is useful for generated documentation: you can generate a static
34/// string in `build.rs` and include it with the `include!` macro.
35///
36/// # Examples
37///
38/// ```
39/// # use leptos::prelude::*;
40/// # use holt_book::StoryVariant;
41/// use holt_macros::{story, variant};
42///
43/// #[variant]
44/// fn default() {
45///     view! { <button>"Click me!"</button> }.into_any()
46/// }
47///
48/// /// Buttons are for clicking and doing button things
49/// #[story(id = "my-story", name = "My Story")]
50/// const MY_STORY: () = &[
51///     default,
52/// ];
53/// ```
54///
55/// Or with extra documentation:
56///
57/// ```
58/// # use leptos::prelude::*;
59/// # use holt_book::StoryVariant;
60/// # use holt_macros::{story, variant};
61/// #
62/// # #[variant]
63/// # fn default() {
64/// #     view! { <button>"Click me!"</button> }.into_any()
65/// # }
66/// #
67/// const EXTRA: &str = "Extra documentation for my story";
68///
69/// /// Buttons are for clicking and doing button things
70/// #[story(id = "my-story", name = "My Story", extra_docs = EXTRA)]
71/// const MY_STORY: () = &[
72///     default,
73/// ];
74/// ```
75#[proc_macro_attribute]
76pub fn story(args: TS1, body: TS1) -> TS1 {
77    let parsed_body: ItemConst = parse(body).expect("failed to parse body");
78    let parsed_args: Punctuated<Meta, Token![,]> = Punctuated::parse_terminated
79        .parse(args)
80        .expect("failed to parse args");
81
82    let args = StoryMacroArgs::new(parsed_args);
83
84    let story_generator = StoryGenerator::new(args, parsed_body);
85    let inventory_generator = RegistrationGenerator::new(&story_generator);
86
87    let full_story_const = story_generator.full_story_const();
88    let submit_story_to_inventory = inventory_generator.submit_story();
89
90    let output = quote! {
91        #full_story_const
92
93        #submit_story_to_inventory
94    };
95
96    output.into()
97}
98
99/// Story variant generation macro
100///
101/// This macro converts a function into a [holt_book::StoryVariant] constant,
102/// extracting the variant name from the function name and inlining the function
103/// body into the render closure.
104///
105/// # Examples
106///
107/// ```
108/// # use leptos::prelude::*;
109/// use holt_macros::variant;
110///
111/// #[variant]
112/// fn default() -> AnyView {
113///     view! { <button>Default</button> }.into_any()
114/// }
115/// ```
116///
117/// If you want more control over the generated variant, you should create the
118/// [holt_book::StoryVariant] struct directly. Note that you'll need to name the
119/// variant `xxx_VARIANT` (like `DEFAULT_VARIANT` for the above) so that you can
120/// use it in the [story] macro as `default`.
121#[proc_macro_attribute]
122pub fn variant(_args: TS1, body: TS1) -> TS1 {
123    let parsed_body: ItemFn = parse(body).expect("failed to parse function");
124
125    let variant_generator = VariantGenerator::new(parsed_body);
126    let variant_const = variant_generator.generate_variant_const();
127
128    variant_const.into()
129}