silkenweb_inline_html/
lib.rs1use std::{
5 env, fs,
6 path::{Path, PathBuf},
7};
8
9use proc_macro::TokenStream;
10use proc_macro2::{Ident, Span};
11use proc_macro_error::{abort_call_site, proc_macro_error};
12use quote::quote;
13use silkenweb_parse::html_to_tokens;
14use syn::{parse_macro_input, LitStr};
15
16#[proc_macro]
34#[proc_macro_error]
35pub fn inline_html(input: TokenStream) -> TokenStream {
36 let html: LitStr = parse_macro_input!(input);
37 let html_text = html.value();
38 let mut element_iter = html_to_tokens(quote! {D}.into(), &html_text).into_iter();
39 let element: proc_macro2::TokenStream = element_iter
40 .next()
41 .unwrap_or_else(|| abort_call_site!("Unable to parse any elements"))
42 .into();
43
44 if element_iter.next().is_some() {
45 abort_call_site!("Multiple elements found");
46 }
47
48 quote! {{
49 pub fn node<D: ::silkenweb::dom::Dom>() -> ::silkenweb::node::Node<D> {
50 #element
51 }
52
53 node()
54 }}
55 .into()
56}
57
58#[proc_macro]
69#[proc_macro_error]
70pub fn html_file(input: TokenStream) -> TokenStream {
71 let file: LitStr = parse_macro_input!(input);
72 let file_path = root_dir().join(file.value());
73 html_from_path(&file_path).into()
74}
75
76#[proc_macro]
85#[proc_macro_error]
86pub fn html_dir(input: TokenStream) -> TokenStream {
87 let dir_literal: LitStr = parse_macro_input!(input);
88 let dir = dir_literal.value();
89 let fns = fs::read_dir(root_dir().join(&dir))
90 .unwrap_or_else(|_| abort_call_site!("Unable to read dir '{}'", dir))
91 .filter_map(|entry| {
92 let path = entry
93 .unwrap_or_else(|_| abort_call_site!("Unable to read dir entry"))
94 .path();
95
96 if path.is_file() {
97 Some(html_from_path(&path))
98 } else {
99 None
100 }
101 });
102
103 quote!(#(#fns)*).into()
104}
105
106fn html_from_path(file_path: &Path) -> proc_macro2::TokenStream {
107 let html_text = fs::read_to_string(file_path)
108 .unwrap_or_else(|_| abort_call_site!("Unable to read file '{:?}'", &file_path));
109 let mut element_iter = html_to_tokens(quote! {D}.into(), &html_text).into_iter();
110 let element: proc_macro2::TokenStream = element_iter
111 .next()
112 .unwrap_or_else(|| abort_call_site!("Unable to parse any elements for '{:?}'", &file_path))
113 .into();
114
115 if element_iter.next().is_some() {
116 abort_call_site!("Multiple elements found in '{:?}'", &file_path);
117 }
118
119 let fn_name = filename_to_ident(
120 file_path
121 .file_stem()
122 .unwrap_or_else(|| {
123 abort_call_site!("Unable to extract file stem from '{:?}'", file_path)
124 })
125 .to_str()
126 .unwrap(),
127 );
128
129 quote! {
130 pub fn #fn_name<D: ::silkenweb::dom::Dom>() -> ::silkenweb::node::Node<D> {
131 #element
132 }
133 }
134}
135
136fn root_dir() -> PathBuf {
137 const CARGO_MANIFEST_DIR: &str = "CARGO_MANIFEST_DIR";
138
139 PathBuf::from(
140 env::var(CARGO_MANIFEST_DIR)
141 .unwrap_or_else(|_| abort_call_site!("Couldn't read '{CARGO_MANIFEST_DIR}' variable")),
142 )
143}
144
145fn filename_to_ident(file: &str) -> Ident {
146 let ident = file.replace(|c: char| !c.is_alphanumeric(), "_");
147
148 if let Some(first) = ident.chars().next() {
149 if !first.is_alphabetic() && first != '_' {
150 abort_call_site!("Illegal first char in '{}'", ident);
151 }
152 } else {
153 abort_call_site!("Empty identifier");
154 }
155
156 Ident::new(&ident, Span::call_site())
157}