cmark_writer_macros/
lib.rs1extern crate proc_macro;
2use proc_macro::TokenStream;
3use proc_macro2::Span;
4use quote::quote;
5use syn::{
6 parse::Parse, parse::ParseStream, parse_macro_input, DeriveInput, Ident, LitBool, LitStr, Token,
7};
8
9struct CustomNodeArgs {
11 is_block: Option<bool>,
12 html_impl: Option<bool>,
13}
14
15impl Parse for CustomNodeArgs {
16 fn parse(input: ParseStream) -> syn::Result<Self> {
17 let mut is_block = None;
18 let mut html_impl = None;
19
20 if input.is_empty() {
21 return Ok(CustomNodeArgs {
22 is_block,
23 html_impl,
24 });
25 }
26
27 loop {
28 if input.is_empty() {
29 break;
30 }
31
32 let ident: Ident = input.parse()?;
33
34 if ident == "block" {
35 let _: Token![=] = input.parse()?;
36 let value: LitBool = input.parse()?;
37 is_block = Some(value.value);
38 } else if ident == "html_impl" {
39 let _: Token![=] = input.parse()?;
40 let value: LitBool = input.parse()?;
41 html_impl = Some(value.value);
42 } else {
43 return Err(syn::Error::new_spanned(
44 ident,
45 "Unknown attribute parameter",
46 ));
47 }
48
49 if input.peek(Token![,]) {
51 let _: Token![,] = input.parse()?;
52 }
53 }
54
55 Ok(CustomNodeArgs {
56 is_block,
57 html_impl,
58 })
59 }
60}
61
62#[derive(Default)]
63struct StructureErrorArgs {
64 format: Option<LitStr>,
65}
66
67impl Parse for StructureErrorArgs {
68 fn parse(input: ParseStream) -> syn::Result<Self> {
69 let mut format = None;
70
71 while !input.is_empty() {
72 let ident: Ident = input.parse()?;
73
74 if ident == "format" {
75 let _: Token![=] = input.parse()?;
76 let value: LitStr = input.parse()?;
77 format = Some(value);
78 } else {
79 return Err(syn::Error::new_spanned(
80 ident,
81 "Unknown attribute parameter",
82 ));
83 }
84
85 if input.peek(Token![,]) {
86 let _: Token![,] = input.parse()?;
87 } else {
88 break;
89 }
90 }
91
92 Ok(Self { format })
93 }
94}
95
96#[proc_macro_attribute]
156pub fn custom_node(attr: TokenStream, item: TokenStream) -> TokenStream {
157 let args = syn::parse_macro_input!(attr as CustomNodeArgs);
158 let input = parse_macro_input!(item as DeriveInput);
159 let name = &input.ident;
160
161 let is_block_impl = if let Some(is_block) = args.is_block {
163 quote! {
164 fn is_block(&self) -> bool {
165 #is_block
166 }
167 }
168 } else {
169 quote! {
170 fn is_block(&self) -> bool {
171 self.is_block_custom()
172 }
173 }
174 };
175
176 let is_block = args.is_block.unwrap_or(false);
177
178 let html_write_impl = if args.html_impl.unwrap_or(false) {
180 quote! {
182 fn html_write(
183 &self,
184 writer: &mut ::cmark_writer::writer::HtmlWriter,
185 ) -> ::cmark_writer::writer::HtmlWriteResult<()> {
186 self.write_html_custom(writer)
187 }
188 }
189 } else {
190 quote! {
192 fn html_write(
193 &self,
194 writer: &mut ::cmark_writer::writer::HtmlWriter,
195 ) -> ::cmark_writer::writer::HtmlWriteResult<()> {
196 writer.write_trusted_html(&format!(
197 "<!-- HTML rendering not implemented for Custom Node: {} -->",
198 self.type_name()
199 ))?;
200 Ok(())
201 }
202 }
203 };
204
205 let write_block_impl = if is_block {
206 quote! {
207 fn write_block(
208 &self,
209 writer: &mut ::cmark_writer::writer::BlockWriterProxy,
210 ) -> ::cmark_writer::error::WriteResult<()> {
211 self.write_custom(writer)
212 }
213 }
214 } else {
215 quote! {}
216 };
217
218 let write_inline_impl = if !is_block {
219 quote! {
220 fn write_inline(
221 &self,
222 writer: &mut ::cmark_writer::writer::InlineWriterProxy,
223 ) -> ::cmark_writer::error::WriteResult<()> {
224 self.write_custom(writer)
225 }
226 }
227 } else {
228 quote! {}
229 };
230
231 let expanded = quote! {
232 #input
233
234 impl ::cmark_writer::ast::CustomNode for #name {
235 #write_block_impl
236
237 #write_inline_impl
238
239 #html_write_impl
240
241 fn clone_box(&self) -> Box<dyn ::cmark_writer::ast::CustomNode> {
242 Box::new(self.clone())
243 }
244
245 fn eq_box(&self, other: &dyn ::cmark_writer::ast::CustomNode) -> bool {
246 if let Some(other) = other.as_any().downcast_ref::<Self>() {
247 self == other
248 } else {
249 false
250 }
251 }
252
253 #is_block_impl
254
255 fn as_any(&self) -> &dyn std::any::Any {
256 self
257 }
258
259 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
260 self
261 }
262 }
263
264 impl #name {
265 pub fn matches(node: &dyn ::cmark_writer::ast::CustomNode) -> bool {
266 node.type_name() == std::any::type_name::<#name>() ||
267 node.as_any().downcast_ref::<#name>().is_some()
268 }
269
270 pub fn extract(node: Box<dyn ::cmark_writer::ast::CustomNode>) -> Option<#name> {
271 node.as_any().downcast_ref::<#name>().map(|n| n.clone())
272 }
273 }
274 };
275
276 TokenStream::from(expanded)
277}
278
279#[proc_macro_attribute]
290pub fn structure_error(attr: TokenStream, item: TokenStream) -> TokenStream {
291 let args = if attr.is_empty() {
292 StructureErrorArgs::default()
293 } else {
294 parse_macro_input!(attr as StructureErrorArgs)
295 };
296 let input = parse_macro_input!(item as DeriveInput);
297 let name = &input.ident;
298
299 let format_lit = args
301 .format
302 .unwrap_or_else(|| LitStr::new("{}", Span::call_site()));
303
304 let expanded = quote! {
305 #input
306
307 impl #name {
308 pub fn new(message: &'static str) -> Self {
309 Self(message)
310 }
311
312 pub fn into_error(self) -> ::cmark_writer::error::WriteError {
313 let mut error_factory = ::cmark_writer::error::StructureError::new(#format_lit);
314
315 let arg = self.0.to_string();
316 error_factory = error_factory.arg(arg);
317
318 <::cmark_writer::error::StructureError as ::cmark_writer::error::CustomErrorFactory>::create_error(&error_factory)
319 }
320 }
321
322 impl From<#name> for ::cmark_writer::error::WriteError {
323 fn from(factory: #name) -> Self {
324 factory.into_error()
325 }
326 }
327
328 impl ::cmark_writer::error::CustomErrorFactory for #name {
329 fn create_error(&self) -> ::cmark_writer::error::WriteError {
330 let mut error_factory = ::cmark_writer::error::StructureError::new(#format_lit);
331
332 let arg = self.0.to_string();
333 error_factory = error_factory.arg(arg);
334
335 <::cmark_writer::error::StructureError as ::cmark_writer::error::CustomErrorFactory>::create_error(&error_factory)
336 }
337 }
338 };
339
340 TokenStream::from(expanded)
341}
342
343#[proc_macro_attribute]
354pub fn coded_error(_attr: TokenStream, item: TokenStream) -> TokenStream {
355 let input = parse_macro_input!(item as DeriveInput);
356 let name = &input.ident;
357
358 let expanded = quote! {
359 #input
360
361 impl #name {
362 pub fn new(message: &str, code: &str) -> Self {
363 Self(message.to_string(), code.to_string())
364 }
365
366 pub fn into_error(self) -> ::cmark_writer::error::WriteError {
367 let coded_error = ::cmark_writer::error::CodedError::new(self.0, self.1);
368 <::cmark_writer::error::CodedError as ::cmark_writer::error::CustomErrorFactory>::create_error(&coded_error)
369 }
370 }
371
372 impl From<#name> for ::cmark_writer::error::WriteError {
373 fn from(factory: #name) -> Self {
374 factory.into_error()
375 }
376 }
377
378 impl ::cmark_writer::error::CustomErrorFactory for #name {
379 fn create_error(&self) -> ::cmark_writer::error::WriteError {
380 let coded_error = ::cmark_writer::error::CodedError::new(self.0.clone(), self.1.clone());
381 <::cmark_writer::error::CodedError as ::cmark_writer::error::CustomErrorFactory>::create_error(&coded_error)
382 }
383 }
384 };
385
386 TokenStream::from(expanded)
387}