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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use crml_core::{TokenStream, TokenType, Parser};
use std::{fs::File, io::Read};
/// Generate valid Rust from a given [`TokenStream`].
pub struct Generator(TokenStream);
impl Generator {
/// Create a new [`Generator`] from a [`File`].
pub fn from_file(mut file: File) -> Self {
// read file
let mut content = String::new();
file.read_to_string(&mut content)
.expect("failed to read file");
// return
Self(Parser::new(content).parse())
}
/// Generate valid Rust from the given `input`.
///
/// The function output has a single argument with the type of the given `props_type`.
/// This type is imported from `crate::crml::data::*`. That module should export all
/// types which are going to be used as page data.
///
/// The example below shows you how to use the data given from calling `consume` with
/// the name of `test_template` and the `props_type` of `TestProps`.
///
/// After building all templates, the `src/crml/mod.rs` file should be created
/// with the combined contents of all functions, as well as the `use super::data::*` line
/// in order to satisfy all type requirements.
///
/// ```rust
/// //! main.rs
/// mod crml;
///
/// pub(crate) struct TestProps {
/// a: i32
/// }
///
/// fn main() {
/// println!("rendered: {}", crml::test_template(TestProps {
/// a: 1
/// }))
/// }
/// ```
///
/// ```rust
/// //! crml/mod.rs
/// // @generated crml build
/// use super::data::*;
/// pub fn test_template(page: TestProps) -> String {
/// // ...
/// }
/// ```
///
/// ```rust
/// //! crml/data.rs - this should be written before building crml templates
/// pub use crate::TestProps;
/// ```
pub fn consume(mut self) -> String {
let mut out = format!("let mut crml_rendered = String::new();\n").to_string();
let mut last_indent_levels: Vec<usize> = Vec::new();
let mut last_tags: Vec<String> = Vec::new();
let whitespace_sensitive = &["script", "style", "pre", "html", "body", "head"]; // these must be closed manually
while let Some(mut token) = self.0.next() {
let mut last_tag = last_tags.last().unwrap_or(&String::new()).to_owned();
let last_indent_level = last_indent_levels.last().unwrap_or(&0).to_owned();
if (token.indent < last_indent_level)
&& !last_tag.is_empty()
&& !whitespace_sensitive.contains(&last_tag.as_str())
{
// automatically close previous element
out.push_str(&format!(
"crml_rendered.push_str(&format!(\"</{last_tag}>\"));\n"
));
last_tags.pop();
last_indent_levels.pop();
}
if token.indent != last_indent_level {
// push this indent level to the stack
last_indent_levels.push(token.indent);
}
match token.r#type {
TokenType::RustString => {
if !token.raw.ends_with("{") && token.raw != "}" {
token.raw += ";";
}
out.push_str(&format!("{}//line: {}\n", token.raw, token.line));
}
TokenType::PushedRustString => {
out.push_str(&format!(
"crml_rendered.push_str(&{});//line: {}\n",
token.raw, token.line
));
}
_ => {
if token.raw == "\n" {
out.push_str(&format!("crml_rendered.push_str(\"\\n\");\n"));
continue;
} else if token.raw == "end" {
out.push_str(&format!(
"crml_rendered.push_str(&format!(\"</{last_tag}>\"));\n"
));
continue;
}
if let Some(selector) = token.selector {
if !selector.tag.starts_with("/") {
last_tags.push(selector.tag.clone());
last_tag = selector.tag;
} else {
last_tags.pop();
}
}
if token.html.contains("</") {
// token closed tag itself
last_tags.pop();
}
if whitespace_sensitive.contains(&last_tag.as_str()) {
// whitespace sensitive blocks do not accept format params
token.html = token.html.replace("{", "{{").replace("}", "}}")
}
out.push_str(&format!(
"crml_rendered.push_str(&format!(\"{}\"));//line: {}\n",
token.html.replace('"', "\\\""),
token.line
));
}
}
}
format!("{out}\ncrml_rendered\n")
}
}