html_bindgen/generate/html/
mod.rs1use super::{CodeFile, ModuleMapping};
2use crate::merge::{MergedCategory, MergedElement};
3use crate::parse::{Attribute, AttributeType};
4use crate::{utils, Result};
5use builder::gen_builder;
6use indoc::formatdoc;
7
8mod builder;
9
10pub fn generate(
11 parsed: impl Iterator<Item = Result<MergedElement>>,
12 global_attributes: &[Attribute],
13 module_map: &[super::ModuleMapping],
14) -> Result<Vec<CodeFile>> {
15 let mut output = vec![];
16 let mut tag_names = vec![];
17
18 for el in parsed {
20 let el = el?;
21 tag_names.push(el.tag_name.clone());
22 output.push(generate_element(el, global_attributes)?);
23 }
24 tag_names.sort();
25
26 let mods = tag_names
27 .iter()
28 .map(|tag_name| format!("pub(crate) mod {tag_name};"))
29 .collect::<String>();
30
31 let all_files = tag_names
32 .iter()
33 .map(|tag_name| format!("pub(crate) use crate::generated::{tag_name}::element::*;"))
34 .collect::<String>();
35
36 let all_builders = tag_names
37 .iter()
38 .map(|tag_name| format!("pub(crate) use crate::generated::{tag_name}::builder::*;"))
39 .collect::<String>();
40
41 let all_children = tag_names
42 .iter()
43 .map(|tag_name| format!("pub(crate) use crate::generated::{tag_name}::child::*;"))
44 .collect::<String>();
45
46 let by_mapping = module_map
47 .iter()
48 .map(|ModuleMapping { name, children }| {
49 let elements = children
50 .iter()
51 .map(|tag_name| format!("pub use crate::generated::{tag_name}::element::*;"))
52 .collect::<String>();
53 let builders = children
54 .iter()
55 .map(|tag_name| format!("pub use crate::generated::{tag_name}::builder::*;"))
56 .collect::<String>();
57 let children = children
58 .iter()
59 .map(|tag_name| format!("pub use crate::generated::{tag_name}::child::*;"))
60 .collect::<String>();
61
62 format!(
63 "
64 pub mod {name} {{
65 pub mod elements {{
66 {elements}
67 }}
68 /// Child elements
69 pub mod children {{
70 {children}
71 }}
72 /// Element builders
73 pub mod builders {{
74 {builders}
75 }}
76 }}
77 "
78 )
79 })
80 .collect::<String>();
81
82 let code = format!(
83 "//! HTML elements support
84 {mods}
85
86 /// All auto-generated items in this crate
87 #[allow(unused)]
88 pub(crate) mod all {{
89 {all_files}
90
91 /// All auto-generated builders
92 pub mod builders {{
93 {all_builders}
94 }}
95
96 /// All auto-generated children
97 pub mod children {{
98 {all_children}
99 }}
100 }}
101
102 /// Modules according to the MDN mappings.
103 pub(crate) mod mdn {{
104 {by_mapping}
105 }}
106 "
107 );
108 output.push(CodeFile {
109 filename: "mod.rs".to_owned(),
110 code: utils::fmt(&code)?,
111 dir: String::new(),
112 });
113
114 Ok(output)
115}
116
117fn generate_element(el: MergedElement, global_attributes: &[Attribute]) -> Result<CodeFile> {
119 let MergedElement {
120 tag_name,
121 struct_name,
122 attributes,
123 mdn_link,
124 has_global_attributes,
125 submodule_name,
126 content_categories,
127 permitted_child_elements,
128 has_closing_tag,
129 ..
130 } = el;
131
132 let filename = format!("{}.rs", tag_name);
133 let enum_name = format!("super::child::{struct_name}Child");
134 let sys_name = format!("html_sys::{submodule_name}::{struct_name}");
135
136 let should_indent = match tag_name.as_str() {
137 "pre" => false,
138 _ => true,
139 };
140
141 let has_children = permitted_child_elements.len() != 0;
142 let categories_impl = gen_categories_impl(&content_categories, &struct_name);
143 let html_element_impl = gen_html_element_impl(&struct_name, has_global_attributes);
144 let children_enum = gen_enum(&struct_name, &permitted_child_elements, should_indent);
145 let child_methods = gen_child_methods(&struct_name, &enum_name, &permitted_child_elements);
146 let data_map_methods = gen_data_map_methods(&struct_name);
147 let display_impl = gen_fmt_impl(&struct_name, has_children, has_closing_tag, should_indent);
148
149 let method_attributes = match has_global_attributes {
150 true => {
151 let mut attrs = attributes.clone();
152 attrs.append(&mut global_attributes.to_owned());
153 attrs
154 }
155 false => attributes.clone(),
156 };
157 let builder = gen_builder(&struct_name, &permitted_child_elements, &method_attributes);
158 let getter_setter_methods = gen_methods(&struct_name, &method_attributes);
159
160 let children = match has_children {
161 true => format!("children: Vec<{enum_name}>"),
162 false => String::new(),
163 };
164
165 let gen_children = match has_children {
166 true => "children: vec![]".to_owned(),
167 false => String::new(),
168 };
169
170 let element = formatdoc!(
171 r#"/// The HTML `<{tag_name}>` element
172 ///
173 /// [MDN Documentation]({mdn_link})
174 #[doc(alias = "{tag_name}")]
175 #[non_exhaustive]
176 #[derive(PartialEq, Clone, Default)]
177 pub struct {struct_name} {{
178 sys: {sys_name},
179 {children}
180 }}
181
182 impl {struct_name} {{
183 /// Create a new builder
184 pub fn builder() -> super::builder::{struct_name}Builder {{
185 super::builder::{struct_name}Builder::new(Default::default())
186 }}
187 }}
188
189 {data_map_methods}
190 {getter_setter_methods}
191 {child_methods}
192
193 {display_impl}
194 {html_element_impl}
195 {categories_impl}
196
197 impl std::convert::Into<{sys_name}> for {struct_name} {{
198 fn into(self) -> {sys_name} {{
199 self.sys
200 }}
201 }}
202
203 impl From<{sys_name}> for {struct_name} {{
204 fn from(sys: {sys_name}) -> Self {{
205 Self {{
206 sys,
207 {gen_children}
208 }}
209 }}
210 }}
211 "#
212 );
213
214 let code = format!(
215 "
216 pub mod element {{
217 {element}
218 }}
219
220 pub mod child {{
221 {children_enum}
222 }}
223
224 pub mod builder {{
225 {builder}
226 }}
227 "
228 );
229
230 Ok(CodeFile {
231 filename,
232 code: utils::fmt(&code)?,
233 dir: "".to_owned(),
234 })
235}
236
237fn gen_fmt_impl(
238 struct_name: &str,
239 has_children: bool,
240 has_closing_tag: bool,
241 should_indent: bool,
242) -> String {
243 let write_debug_children = if has_children && should_indent {
244 format!(
245 r#"
246 if !self.children.is_empty() {{
247 write!(f, "\n")?;
248 }}
249 for el in &self.children {{
250 crate::Render::render(&el, f, depth)?;
251 write!(f, "\n")?;
252 }}"#
253 )
254 } else if has_children && !should_indent {
255 format!(
256 r#"
257 for el in &self.children {{
258 crate::Render::render(&el, f, 0)?;
259 }}"#
260 )
261 } else {
262 String::new()
263 };
264
265 let write_display_children = if has_children {
266 format!(
267 r#"
268 for el in &self.children {{
269 write!(f, "{{el}}")?;
270 }}"#
271 )
272 } else {
273 String::new()
274 };
275
276 let write_closing_tag = if has_closing_tag && should_indent {
277 r#"
278 write!(f, "{:level$}", "", level = depth * 4)?;
279 html_sys::RenderElement::write_closing_tag(&self.sys, f)?;
280 "#
281 } else if has_closing_tag && !should_indent {
282 r#"
283 html_sys::RenderElement::write_closing_tag(&self.sys, f)?;
284 "#
285 } else {
286 ""
287 };
288 format!(
289 r#"
290 impl crate::Render for {struct_name} {{
291 fn render(&self, f: &mut std::fmt::Formatter<'_>, depth: usize) -> std::fmt::Result {{
292 write!(f, "{{:level$}}", "", level = depth * 4)?;
293 html_sys::RenderElement::write_opening_tag(&self.sys, f)?;
294 {write_debug_children}
295 {write_closing_tag}
296 Ok(())
297 }}
298 }}
299
300 impl std::fmt::Debug for {struct_name} {{
301 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
302 crate::Render::render(self, f, 0)?;
303 Ok(())
304 }}
305 }}
306
307 impl std::fmt::Display for {struct_name} {{
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
309 html_sys::RenderElement::write_opening_tag(&self.sys, f)?;
310 {write_display_children}
311 html_sys::RenderElement::write_closing_tag(&self.sys, f)?;
312 Ok(())
313 }}
314 }}
315 "#
316 )
317}
318
319fn gen_child_methods(
320 struct_name: &str,
321 enum_name: &str,
322 permitted_child_elements: &[String],
323) -> String {
324 if permitted_child_elements.len() == 0 {
325 return String::new();
326 }
327
328 format!(
329 "impl {struct_name} {{
330 /// Access the element's children
331 pub fn children(&self) -> &[{enum_name}] {{
332 self.children.as_ref()
333 }}
334
335 /// Mutably access the element's children
336 pub fn children_mut(&mut self) -> &mut Vec<{enum_name}> {{
337 &mut self.children
338 }}
339 }}"
340 )
341}
342
343fn gen_data_map_methods(struct_name: &str) -> String {
344 format!(
345 "impl {struct_name} {{
346 /// Access the element's `data-*` properties
347 pub fn data_map(&self) -> &html_sys::DataMap {{
348 &self.sys.data_map
349 }}
350
351 /// Mutably access the element's `data-*` properties
352 pub fn data_map_mut(&mut self) -> &mut html_sys::DataMap {{
353 &mut self.sys.data_map
354 }}
355 }}"
356 )
357}
358
359fn gen_enum(struct_name: &str, permitted_child_elements: &[String], should_indent: bool) -> String {
360 if permitted_child_elements.len() == 0 {
361 return String::new();
362 }
363
364 fn gen_ty_path(el: &str) -> String {
368 if el == "Text" {
369 "std::borrow::Cow<'static, str>".to_owned()
370 } else {
371 format!("crate::generated::all::{el}")
372 }
373 }
374
375 let members = permitted_child_elements
376 .iter()
377 .map(|el| {
378 let ty = gen_ty_path(el);
379 format!(
380 "/// The {el} element
381 {el}({ty}),"
382 )
383 })
384 .collect::<String>();
385
386 let from = permitted_child_elements
387 .iter()
388 .map(|el| {
389 let ty = gen_ty_path(el);
390
391 let base_impl = format!(
392 "
393 impl std::convert::From<{ty}> for {struct_name}Child {{
394 fn from(value: {ty}) -> Self {{
395 Self::{el}(value)
396 }}
397 }}
398 "
399 );
400 if ty.contains("borrow") {
401 format!(
402 "
403 {base_impl}
404
405 impl std::convert::From<&'static str> for {struct_name}Child {{
406 fn from(value: &'static str) -> Self {{
407 Self::{el}(value.into())
408 }}
409 }}
410 impl std::convert::From<String> for {struct_name}Child {{
411 fn from(value: String) -> Self {{
412 Self::{el}(value.into())
413 }}
414 }}
415 "
416 )
417 } else {
418 base_impl
419 }
420 })
421 .collect::<String>();
422
423 let increase_depth = match should_indent {
424 true => "+ 1",
425 false => "",
426 };
427 let debug_patterns = permitted_child_elements
428 .iter()
429 .map(|el| {
430 format!(r#"Self::{el}(el) => crate::Render::render(el, f, depth {increase_depth}),"#)
431 })
432 .collect::<String>();
433 let display_patterns = permitted_child_elements
434 .iter()
435 .map(|el| format!(r#"Self::{el}(el) => write!(f, "{{el}}"),"#))
436 .collect::<String>();
437
438 format!(
439 r#"
440 /// The permitted child items for the `{struct_name}` element
441 #[derive(PartialEq, Clone)]
442 pub enum {struct_name}Child {{
443 {members}
444 }}
445 {from}
446
447 impl crate::Render for {struct_name}Child {{
448 fn render(&self, f: &mut std::fmt::Formatter<'_>, depth: usize) -> std::fmt::Result {{
449 match self {{
450 {debug_patterns}
451 }}
452 }}
453 }}
454
455 impl std::fmt::Debug for {struct_name}Child {{
456 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
457 crate::Render::render(self, f, 0)?;
458 Ok(())
459 }}
460 }}
461
462 impl std::fmt::Display for {struct_name}Child {{
463 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
464 match self {{
465 {display_patterns}
466 }}
467 }}
468 }}
469 "#
470 )
471}
472
473fn gen_html_element_impl(struct_name: &str, has_global_attributes: bool) -> String {
474 if has_global_attributes {
475 format!(
476 "
477 impl crate::HtmlElement for {struct_name} {{}}
478 "
479 )
480 } else {
481 String::new()
482 }
483}
484
485fn gen_categories_impl(categories: &[MergedCategory], struct_name: &str) -> String {
486 let mut output = String::new();
487 for cat in categories {
488 generate_category(cat, &mut output, struct_name);
489 }
490 output
491}
492
493fn generate_category(cat: &MergedCategory, output: &mut String, struct_name: &str) {
494 match cat {
495 MergedCategory::Metadata => output.push_str(&format!(
496 "impl crate::MetadataContent for {struct_name} {{}}"
497 )),
498 MergedCategory::Flow => {
499 output.push_str(&format!("impl crate::FlowContent for {struct_name} {{}}"))
500 }
501 MergedCategory::Sectioning => {
502 output.push_str(&format!(
503 "impl crate::SectioningContent for {struct_name} {{}}"
504 ));
505 }
507 MergedCategory::Heading => {
508 output.push_str(&format!(
509 "impl crate::HeadingContent for {struct_name} {{}}"
510 ));
511 }
513 MergedCategory::Phrasing => {
514 output.push_str(&format!(
515 "impl crate::PhrasingContent for {struct_name} {{}}"
516 ));
517 }
519 MergedCategory::Embedded => {
520 output.push_str(&format!(
521 "impl crate::EmbeddedContent for {struct_name} {{}}"
522 ));
523 }
525 MergedCategory::Interactive => {
526 output.push_str(&format!(
527 "impl crate::InteractiveContent for {struct_name} {{}}"
528 ));
529 }
531 MergedCategory::Palpable => output.push_str(&format!(
532 "impl crate::PalpableContent for {struct_name} {{}}"
533 )),
534 MergedCategory::ScriptSupporting => output.push_str(&format!(
535 "impl crate::ScriptSupportingContent for {struct_name} {{}}"
536 )),
537 MergedCategory::Transparent => output.push_str(&format!(
538 "impl crate::TransparentContent for {struct_name} {{}}"
539 )),
540 }
541}
542
543fn gen_methods(struct_name: &str, attributes: &[Attribute]) -> String {
544 fn gen_method(attr: &Attribute) -> String {
545 let name = &attr.name;
546 let field_name = &attr.field_name;
547 let return_ty = match &attr.ty {
548 AttributeType::Bool => "bool".to_owned(),
549 AttributeType::String => "std::option::Option<&str>".to_owned(),
550 ty => format!("std::option::Option<{ty}>"),
551 };
552
553 let param_ty = match &attr.ty {
554 AttributeType::Bool => "bool".to_owned(),
555 AttributeType::String => {
556 "std::option::Option<impl Into<std::borrow::Cow<'static, str>>>".to_owned()
557 }
558 ty => format!("std::option::Option<{ty}>"),
559 };
560
561 let field_access = match &attr.ty {
562 AttributeType::Integer | AttributeType::Float | AttributeType::Bool => {
563 format!("self.sys.{field_name}")
564 }
565 AttributeType::String => {
566 format!("self.sys.{field_name}.as_deref()")
567 }
568 _ => todo!("unhandled type"),
569 };
570 let field_setter = match &attr.ty {
571 AttributeType::String => format!("value.map(|v| v.into())"),
572 _ => format!("value"),
573 };
574 format!(
575 "
576 /// Get the value of the `{name}` attribute
577 pub fn {field_name}(&self) -> {return_ty} {{
578 {field_access}
579 }}
580 /// Set the value of the `{name}` attribute
581 pub fn set_{field_name}(&mut self, value: {param_ty}) {{
582 self.sys.{field_name} = {field_setter};
583 }}",
584 )
585 }
586 let methods: String = attributes.into_iter().map(gen_method).collect();
587
588 match methods.len() {
589 0 => String::new(),
590 _ => format!(
591 "
592 impl {struct_name} {{
593 {methods}
594 }}
595 "
596 ),
597 }
598}