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
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_error::*;
use quote::quote;
#[proc_macro_attribute]
#[proc_macro_error]
pub fn p5doc(_attr: TokenStream, item: TokenStream) -> TokenStream {
p5doc2(item.into()).into()
}
fn p5doc2(item: TokenStream2) -> TokenStream2 {
if let Ok(mut item) = syn::parse2(item.clone()) {
match item {
syn::Item::Const(syn::ItemConst { ref mut attrs, .. })
| syn::Item::Enum(syn::ItemEnum { ref mut attrs, .. })
| syn::Item::Fn(syn::ItemFn { ref mut attrs, .. })
| syn::Item::ForeignMod(syn::ItemForeignMod { ref mut attrs, .. })
| syn::Item::Macro(syn::ItemMacro { ref mut attrs, .. })
| syn::Item::Macro2(syn::ItemMacro2 { ref mut attrs, .. })
| syn::Item::Mod(syn::ItemMod { ref mut attrs, .. })
| syn::Item::Static(syn::ItemStatic { ref mut attrs, .. })
| syn::Item::Struct(syn::ItemStruct { ref mut attrs, .. })
| syn::Item::Trait(syn::ItemTrait { ref mut attrs, .. })
| syn::Item::TraitAlias(syn::ItemTraitAlias { ref mut attrs, .. })
| syn::Item::Type(syn::ItemType { ref mut attrs, .. })
| syn::Item::Union(syn::ItemUnion { ref mut attrs, .. }) => {
convert(attrs);
return quote! { #item };
}
_ => {}
}
}
item
}
fn convert(attrs: &mut Vec<syn::Attribute>) {
let mut in_quote = false;
for attr in attrs {
let path = &attr.path;
if path.segments.len() != 1 || path.segments[0].ident != "doc" {
continue;
}
let mut iter = attr.tokens.clone().into_iter();
let doc = match (iter.next(), iter.next(), iter.next()) {
(Some(_eq), Some(lit), None) => {
lit.to_string().trim_matches('"').trim().to_string()
}
_ => continue,
};
let start_tag = "```p5doc";
if doc.starts_with(start_tag) {
let re = regex::Regex::new(r"(\d*)x(\d*)").unwrap();
let (width, height) = re
.captures(&doc)
.and_then(|cap| {
let width = cap.get(1)?.as_str();
let height = cap.get(2)?.as_str();
Some((width.parse().ok()?, height.parse().ok()?))
})
.expect("p5doc must have width and height like `200x100` form");
let lit = format!(
r#"<div id={CANVAS_ID}></div> {CDN_P5JS} <script> {} function draw() {{"#,
setup(width, height)
);
attr.tokens = quote! { = #lit };
in_quote = true;
}
if in_quote && doc.ends_with("```") {
attr.tokens = quote! { = "} </script>"};
return;
}
}
}
const CDN_P5JS: &str = r#"<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>"#;
const CANVAS_ID: &str = "p5doc";
fn setup(width: u64, height: u64) -> String {
format!(
r#"function setup() {{ var canvas = createCanvas({width}, {height}); canvas.parent("{CANVAS_ID}"); }}"#
)
}