1pub mod iter;
4pub mod stack;
5
6pub use crate::error::ProcessError;
7use crate::error::TracebackError;
8
9use self::iter::TokenIter;
10use self::stack::StackFrame;
11
12use crate::fs::{Node, ParsedContents};
13use crate::parse::{LocatableToken, ParsedMarkdown, Token};
14use crate::{Environment, Error, Stuart};
15
16use humphrey_json::Value;
17use pulldown_cmark::{html, Options, Parser};
18
19pub struct Scope<'a> {
21 pub tokens: &'a mut TokenIter<'a>,
26
27 pub stack: &'a mut Vec<StackFrame>,
32
33 pub processor: &'a Stuart,
35
36 pub sections: &'a mut Vec<(String, Vec<u8>)>,
41}
42
43#[derive(Default)]
45pub struct ProcessOutput {
46 pub new_contents: Option<Vec<u8>>,
48 pub new_name: Option<String>,
50}
51
52impl Node {
53 pub fn process(&self, processor: &Stuart, env: Environment) -> Result<Node, Error> {
55 let output = if self.name() != "root.html" && self.name() != "md.html" {
56 match self.parsed_contents() {
57 ParsedContents::Html(tokens) => self
58 .process_html(tokens, processor, env)
59 .map_err(Error::Process)?,
60 ParsedContents::Markdown(md) => self
61 .process_markdown(md, processor, env)
62 .map_err(Error::Process)?,
63 ParsedContents::Custom(custom) => {
64 custom.process(processor, env).map_err(Error::Plugin)?
65 }
66 _ => ProcessOutput::default(),
67 }
68 } else {
69 ProcessOutput::default()
70 };
71
72 Ok(Node::File {
73 name: output.new_name.unwrap_or_else(|| self.name().to_string()),
74 contents: output
75 .new_contents
76 .unwrap_or_else(|| self.contents().unwrap().to_vec()),
77 parsed_contents: ParsedContents::None,
78 metadata: if processor.config.save_metadata {
79 self.parsed_contents().to_json()
80 } else {
81 None
82 },
83 source: self.source().to_path_buf(),
84 })
85 }
86
87 fn process_html(
89 &self,
90 tokens: &[LocatableToken],
91 processor: &Stuart,
92 env: Environment,
93 ) -> Result<ProcessOutput, TracebackError<ProcessError>> {
94 let root = env.root.ok_or(TracebackError {
95 path: self.source().to_path_buf(),
96 line: 0,
97 column: 0,
98 kind: ProcessError::MissingHtmlRoot,
99 })?;
100
101 let mut token_iter = TokenIter::new(tokens);
102 let mut stack: Vec<StackFrame> = vec![processor.base.as_ref().unwrap().clone()];
103 let mut sections: Vec<(String, Vec<u8>)> = Vec::new();
104 let mut scope = Scope {
105 tokens: &mut token_iter,
106 stack: &mut stack,
107 processor,
108 sections: &mut sections,
109 };
110
111 while let Some(token) = scope.tokens.next() {
112 token.process(&mut scope)?;
113 }
114
115 if !scope
116 .stack
117 .pop()
118 .map(|frame| frame.name == "base")
119 .unwrap_or(false)
120 {
121 return Err(TracebackError {
122 path: self.source().to_path_buf(),
123 line: 0,
124 column: 0,
125 kind: ProcessError::StackError,
126 });
127 }
128
129 let mut token_iter = TokenIter::new(root);
130
131 scope.stack.push(processor.base.as_ref().unwrap().clone());
132 scope.tokens = &mut token_iter;
133
134 while let Some(token) = scope.tokens.next() {
135 token.process(&mut scope)?;
136 }
137
138 Ok(ProcessOutput {
139 new_contents: Some(stack.pop().unwrap().output),
140 new_name: None,
141 })
142 }
143
144 fn process_markdown(
146 &self,
147 md: &ParsedMarkdown,
148 processor: &Stuart,
149 env: Environment,
150 ) -> Result<ProcessOutput, TracebackError<ProcessError>> {
151 let root = env.root.ok_or(TracebackError {
152 path: self.source().to_path_buf(),
153 line: 0,
154 column: 0,
155 kind: ProcessError::MissingHtmlRoot,
156 })?;
157
158 let md_tokens = env.md.ok_or(TracebackError {
159 path: self.source().to_path_buf(),
160 line: 0,
161 column: 0,
162 kind: ProcessError::MissingMarkdownRoot,
163 })?;
164
165 let mut token_iter = TokenIter::new(md_tokens);
166
167 let mut stack: Vec<StackFrame> = vec![processor
168 .base
169 .as_ref()
170 .unwrap()
171 .clone()
172 .with_variable("self", md.to_value())];
173
174 let mut sections: Vec<(String, Vec<u8>)> = Vec::new();
175 let mut scope = Scope {
176 tokens: &mut token_iter,
177 stack: &mut stack,
178 processor,
179 sections: &mut sections,
180 };
181
182 while let Some(token) = scope.tokens.next() {
183 token.process(&mut scope)?;
184 }
185
186 if !scope
187 .stack
188 .pop()
189 .map(|frame| frame.name == "base")
190 .unwrap_or(false)
191 {
192 return Err(TracebackError {
193 path: self.source().to_path_buf(),
194 line: 0,
195 column: 0,
196 kind: ProcessError::StackError,
197 });
198 }
199
200 let mut token_iter = TokenIter::new(root);
201
202 scope.stack.push(processor.base.as_ref().unwrap().clone());
203 scope.tokens = &mut token_iter;
204
205 while let Some(token) = scope.tokens.next() {
206 token.process(&mut scope)?;
207 }
208
209 let new_name = format!("{}.html", self.name().strip_suffix(".md").unwrap());
210
211 Ok(ProcessOutput {
212 new_contents: Some(stack.pop().unwrap().output),
213 new_name: Some(new_name),
214 })
215 }
216
217 pub(crate) fn preprocess_markdown(
221 &mut self,
222 processor: &Stuart,
223 ) -> Result<(), TracebackError<ProcessError>> {
224 let source = self.source().to_path_buf();
225
226 let md = match self.parsed_contents_mut() {
227 ParsedContents::Markdown(md) => md,
228 _ => return Ok(()),
229 };
230
231 let mut token_iter = TokenIter::new(&md.markdown);
232 let mut stack: Vec<StackFrame> = vec![processor.base.as_ref().unwrap().clone()];
233 let mut sections: Vec<(String, Vec<u8>)> = Vec::new();
234 let mut scope = Scope {
235 tokens: &mut token_iter,
236 stack: &mut stack,
237 processor,
238 sections: &mut sections,
239 };
240
241 while let Some(token) = scope.tokens.next() {
242 token.process(&mut scope)?;
243 }
244
245 if let Some(frame) = scope.stack.pop() {
246 if frame.name == "base" {
247 let processed_markdown =
248 String::from_utf8(frame.output).map_err(|_| TracebackError {
249 path: source.clone(),
250 line: 0,
251 column: 0,
252 kind: ProcessError::StackError,
253 })?;
254
255 let parser = Parser::new_ext(&processed_markdown, Options::all());
256 let mut processed_html = String::new();
257 html::push_html(&mut processed_html, parser);
258
259 md.html = Some(processed_html);
260 return Ok(());
261 }
262 }
263
264 Err(TracebackError {
265 path: self.source().to_path_buf(),
266 line: 0,
267 column: 0,
268 kind: ProcessError::StackError,
269 })
270 }
271}
272
273impl LocatableToken {
274 pub fn process(&self, scope: &mut Scope) -> Result<(), TracebackError<ProcessError>> {
276 match &self.inner {
277 Token::Raw(raw) => scope
278 .stack
279 .last_mut()
280 .unwrap()
281 .output
282 .extend_from_slice(raw.as_bytes()),
283
284 Token::Function(function) => function.execute(scope)?,
285
286 Token::Variable(variable) => {
287 let mut variable_iter = variable.split('.');
288 let variable_name = variable_iter.next().unwrap();
289 let variable_indexes = variable_iter.collect::<Vec<_>>();
290
291 let mut string = None;
292
293 for frame in scope.stack.iter().rev() {
294 if let Some(value) = frame
295 .get_variable(variable_name)
296 .map(|v| stack::get_value(&variable_indexes, v))
297 {
298 let e = |found: &str| {
299 Err(ProcessError::InvalidDataType {
300 variable: variable.to_string(),
301 expected: "string".to_string(),
302 found: found.to_string(),
303 })
304 };
305
306 match value {
307 Value::String(s) => {
308 string = Some(s);
309 break;
310 }
311
312 Value::Null => Err(ProcessError::NullError(variable.to_string())),
313 Value::Bool(_) => e("bool"),
314 Value::Number(_) => e("number"),
315 Value::Array(_) => e("array"),
316 Value::Object(_) => e("object"),
317 }
318 .map_err(|e| self.traceback(e))?;
319 }
320 }
321
322 if let Some(s) = string {
323 scope
324 .stack
325 .last_mut()
326 .unwrap()
327 .output
328 .extend_from_slice(s.as_bytes());
329 } else {
330 return Err(
331 self.traceback(ProcessError::UndefinedVariable(variable.to_string()))
332 );
333 }
334 }
335 }
336
337 Ok(())
338 }
339}
340
341impl Scope<'_> {
342 pub fn get_variable(&self, name: &str) -> Option<Value> {
344 let mut variable_iter = name.split('.');
345 let variable_name = variable_iter.next().unwrap();
346 let variable_indexes = variable_iter.collect::<Vec<_>>();
347
348 let mut variable = None;
349
350 for frame in self.stack.iter().rev() {
351 if let Some(value) = frame
352 .get_variable(variable_name)
353 .map(|v| crate::process::stack::get_value(&variable_indexes, v))
354 {
355 variable = Some(value);
356 break;
357 }
358 }
359
360 variable
361 }
362
363 pub fn output(&mut self, output: impl AsRef<[u8]>) -> Result<(), ProcessError> {
365 self.stack
366 .last_mut()
367 .ok_or(ProcessError::StackError)?
368 .output
369 .extend_from_slice(output.as_ref());
370
371 Ok(())
372 }
373}