cmark_writer_macros/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{
5    parse::Parse, parse::ParseStream, parse_macro_input, DeriveInput, Ident, LitBool, Token,
6};
7
8/// Parse custom_node attribute parameters
9struct CustomNodeArgs {
10    is_block: Option<bool>,
11}
12
13impl Parse for CustomNodeArgs {
14    fn parse(input: ParseStream) -> syn::Result<Self> {
15        let mut is_block = None;
16
17        if input.is_empty() {
18            return Ok(CustomNodeArgs { is_block });
19        }
20
21        let ident: Ident = input.parse()?;
22        if ident == "block" {
23            let _: Token![=] = input.parse()?;
24            let value: LitBool = input.parse()?;
25            is_block = Some(value.value);
26        }
27
28        Ok(CustomNodeArgs { is_block })
29    }
30}
31
32/// Custom node attribute macro, replaces the original derive_custom_node! macro
33///
34/// This macro automatically implements the CustomNode trait. Users can specify
35/// whether the node is a block element using the `block` parameter.
36///
37/// # Example
38///
39/// ```rust
40/// use cmark_writer_macros::custom_node;
41///
42/// // Specified as an inline element
43/// #[derive(Debug, Clone, PartialEq)]
44/// #[custom_node(block=false)]
45/// struct HighlightNode {
46///     content: String,
47///     color: String,
48/// }
49///
50/// impl HighlightNode {
51///     fn write_custom(&self, writer: &mut dyn CustomNodeWriter) -> WriteResult<()> {
52///         writer.write_str("<span style=\"background-color: ")?;
53///         writer.write_str(&self.color)?;
54///         writer.write_str("\">")?;
55///         writer.write_str(&self.content)?;
56///         writer.write_str("</span>")?;
57///         Ok(())
58///     }
59/// }
60///
61/// // Specified as a block element
62/// #[derive(Debug, Clone, PartialEq)]
63/// #[custom_node(block=true)]
64/// struct AlertNode {
65///     content: String,
66/// }
67///
68/// impl AlertNode {
69///     fn write_custom(&self, writer: &mut dyn CustomNodeWriter) -> WriteResult<()> {
70///         writer.write_str("<div class=\"alert\">")?;
71///         writer.write_str(&self.content)?;
72///         writer.write_str("</div>")?;
73///         Ok(())
74///     }
75/// }
76/// ```
77#[proc_macro_attribute]
78pub fn custom_node(attr: TokenStream, item: TokenStream) -> TokenStream {
79    let args = syn::parse_macro_input!(attr as CustomNodeArgs);
80    let input = parse_macro_input!(item as DeriveInput);
81    let name = &input.ident;
82
83    // Determine if it's a block element. If not specified, the user needs to provide an is_block_custom method
84    let is_block_impl = if let Some(is_block) = args.is_block {
85        quote! {
86            fn is_block(&self) -> bool {
87                #is_block
88            }
89        }
90    } else {
91        quote! {
92            fn is_block(&self) -> bool {
93                self.is_block_custom()
94            }
95        }
96    };
97
98    let expanded = quote! {
99        #input
100
101        impl ::cmark_writer::ast::CustomNode for #name {
102            fn write(
103                &self,
104                writer: &mut dyn ::cmark_writer::ast::CustomNodeWriter,
105            ) -> ::cmark_writer::error::WriteResult<()> {
106                self.write_custom(writer)
107            }
108
109            fn clone_box(&self) -> Box<dyn ::cmark_writer::ast::CustomNode> {
110                Box::new(self.clone())
111            }
112
113            fn eq_box(&self, other: &dyn ::cmark_writer::ast::CustomNode) -> bool {
114                if let Some(other) = other.as_any().downcast_ref::<Self>() {
115                    self == other
116                } else {
117                    false
118                }
119            }
120
121            #is_block_impl
122
123            fn as_any(&self) -> &dyn std::any::Any {
124                self
125            }
126
127            fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
128                self
129            }
130        }
131
132        // Implementing the CustomNode trait for Box<dyn CustomNode>
133        impl #name {
134            pub fn matches(node: &dyn ::cmark_writer::ast::CustomNode) -> bool {
135                node.type_name() == std::any::type_name::<#name>() ||
136                    node.as_any().downcast_ref::<#name>().is_some()
137            }
138
139            pub fn extract(node: Box<dyn ::cmark_writer::ast::CustomNode>) -> Option<#name> {
140                node.as_any().downcast_ref::<#name>().map(|n| n.clone())
141            }
142        }
143    };
144
145    TokenStream::from(expanded)
146}
147
148/// Custom error attribute macro, replaces the struct form errors in the original define_custom_errors! macro
149///
150/// # Example
151///
152/// ```rust
153/// use cmark_writer_macros::structure_error;
154///
155/// #[structure_error(format = "Table column mismatch: {}")]
156/// struct TableColumnMismatchError(pub &'static str);
157/// ```
158#[proc_macro_attribute]
159pub fn structure_error(attr: TokenStream, item: TokenStream) -> TokenStream {
160    let attr_str = attr.to_string();
161    let input = parse_macro_input!(item as DeriveInput);
162    let name = &input.ident;
163
164    // Parse format attribute
165    let format = if attr_str.starts_with("format") {
166        let format_str = attr_str
167            .replace("format", "")
168            .replace("=", "")
169            .trim()
170            .trim_matches('"')
171            .to_string();
172        format_str
173    } else {
174        // Default error message if format not specified
175        "{}".to_string()
176    };
177
178    let expanded = quote! {
179        #input
180
181        impl #name {
182            pub fn new(message: &'static str) -> Self {
183                Self(message)
184            }
185
186            pub fn into_error(self) -> ::cmark_writer::error::WriteError {
187                let mut error_factory = ::cmark_writer::error::StructureError::new(#format);
188
189                let arg = self.0.to_string();
190                error_factory = error_factory.arg(arg);
191
192                <::cmark_writer::error::StructureError as ::cmark_writer::error::CustomErrorFactory>::create_error(&error_factory)
193            }
194        }
195
196        impl From<#name> for ::cmark_writer::error::WriteError {
197            fn from(factory: #name) -> Self {
198                factory.into_error()
199            }
200        }
201
202        impl ::cmark_writer::error::CustomErrorFactory for #name {
203            fn create_error(&self) -> ::cmark_writer::error::WriteError {
204                let mut error_factory = ::cmark_writer::error::StructureError::new(#format);
205
206                let arg = self.0.to_string();
207                error_factory = error_factory.arg(arg);
208
209                <::cmark_writer::error::StructureError as ::cmark_writer::error::CustomErrorFactory>::create_error(&error_factory)
210            }
211        }
212    };
213
214    TokenStream::from(expanded)
215}
216
217/// Custom coded error attribute macro, replaces the coded form errors in the original define_custom_errors! macro
218///
219/// # Example
220///
221/// ```rust
222/// use cmark_writer_macros::coded_error;
223///
224/// #[coded_error]
225/// struct MarkdownSyntaxError(pub &'static str, pub &'static str);
226/// ```
227#[proc_macro_attribute]
228pub fn coded_error(_attr: TokenStream, item: TokenStream) -> TokenStream {
229    let input = parse_macro_input!(item as DeriveInput);
230    let name = &input.ident;
231
232    let expanded = quote! {
233        #input
234
235        impl #name {
236            pub fn new(message: &str, code: &str) -> Self {
237                Self(message.to_string(), code.to_string())
238            }
239
240            pub fn into_error(self) -> ::cmark_writer::error::WriteError {
241                let coded_error = ::cmark_writer::error::CodedError::new(self.0, self.1);
242                <::cmark_writer::error::CodedError as ::cmark_writer::error::CustomErrorFactory>::create_error(&coded_error)
243            }
244        }
245
246        impl From<#name> for ::cmark_writer::error::WriteError {
247            fn from(factory: #name) -> Self {
248                factory.into_error()
249            }
250        }
251
252        impl ::cmark_writer::error::CustomErrorFactory for #name {
253            fn create_error(&self) -> ::cmark_writer::error::WriteError {
254                let coded_error = ::cmark_writer::error::CodedError::new(self.0.clone(), self.1.clone());
255                <::cmark_writer::error::CodedError as ::cmark_writer::error::CustomErrorFactory>::create_error(&coded_error)
256            }
257        }
258    };
259
260    TokenStream::from(expanded)
261}