kittycad_modeling_cmds_macros_impl/
modeling_cmd_enum.rs

1use proc_macro2::TokenStream;
2use quote::quote_spanned;
3use syn::{spanned::Spanned, ItemMod};
4
5pub fn generate(input: ItemMod) -> TokenStream {
6    let span = input.span();
7
8    // Parse all items from the module, to discover which enum variants should exist.
9    // Also, find the doc for each enum variant.
10    let items = input.content.as_ref().unwrap().1.iter();
11    let (variants, docs): (Vec<_>, Vec<_>) = items
12        .filter_map(|item| {
13            // All modeling commands are public structs.
14            let syn::Item::Struct(item) = item else {
15                return None;
16            };
17            let syn::Visibility::Public(_) = item.vis else {
18                return None;
19            };
20
21            // Copy the struct's docstring. That'll become the docstring for the enum variant.
22            let doc = item
23                .attrs
24                .iter()
25                .filter_map(|attr| match &attr.meta {
26                    syn::Meta::NameValue(syn::MetaNameValue { path, value, .. }) => {
27                        // The attribute should look like #[doc = "..."].
28                        // The attribute's key must be "doc".
29                        if !path.is_ident("doc") {
30                            return None;
31                        }
32                        // Extract the attribute's value (the docstring's contents).
33                        let syn::Expr::Lit(syn::ExprLit {
34                            lit: syn::Lit::Str(value),
35                            ..
36                        }) = value
37                        else {
38                            return None;
39                        };
40                        let doc = value.value().trim().to_owned();
41                        Some(doc)
42                    }
43                    _ => None,
44                })
45                .collect::<Vec<_>>()
46                .join("\n");
47            Some((&item.ident, doc))
48        })
49        .unzip();
50
51    // Output the generated enum.
52    quote_spanned! {span=>
53        // Emit the module again
54        /// Definition of each modeling command.
55        #input
56        /// Commands that the KittyCAD engine can execute.
57        #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58        #[cfg_attr(feature = "derive-jsonschema-on-enums", derive(schemars::JsonSchema))]
59        #[serde(rename_all = "snake_case", tag = "type")]
60        #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
61        #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
62        #[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
63        pub enum ModelingCmd {#(
64            #[doc = #docs]
65            #variants(kittycad_modeling_cmds::each_cmd::#variants),
66        )*}
67        /// Each modeling command (no parameters or fields).
68        #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, ::parse_display::Display)]
69        #[serde(rename_all = "snake_case")]
70        #[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
71        pub enum ModelingCmdEndpoint{#(
72            #[doc = #docs]
73            #variants,
74        )*}
75        /// You can easily convert each modeling command with its fields,
76        /// into a modeling command without fields.
77        impl From<ModelingCmd> for ModelingCmdEndpoint {
78            fn from(v: ModelingCmd) -> Self {
79                match v {#(
80                    ModelingCmd::#variants(_) => Self::#variants,
81                )*}
82            }
83        }
84    }
85}