/
germanubis
/
tera
Обзор
Документация
Войти
/
germanubis
/
tera
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/parser/whitespace.rs
188 строк
7 KB
Vincent Prouillet
Allow whitespace handling to be used with comments tag
28 фев 2021, 11:59
28 фев 2021, 11:59
b229e1e
Код
Авторство
О чём код?
use crate::parser::ast::*; macro_rules! trim_right_previous { ($vec: expr) => { if let Some(last) = $vec.pop() { if let Node::Text(mut s) = last { s = s.trim_end().to_string(); if !s.is_empty() { $vec.push(Node::Text(s)); } } else { $vec.push(last); } } }; ($cond: expr, $vec: expr) => { if $cond { trim_right_previous!($vec); } }; } /// Removes whitespace from the AST nodes according to the `{%-` and `-%}` defined in the template. /// Empty string nodes will be discarded. /// /// The `ws` param is used when recursing through nested bodies to know whether to know /// how to handle the whitespace for that whole body: /// - set the initial `trim_left_next` to `ws.left` /// - trim last node if it is a text node if `ws.right == true` pub fn remove_whitespace(nodes: Vec<Node>, body_ws: Option<WS>) -> Vec<Node> { let mut res = Vec::with_capacity(nodes.len()); // Whether the node we just added to res is a Text node let mut previous_was_text = false; // Whether the previous block ended wth `-%}` and we need to trim left the next text node let mut trim_left_next = body_ws.map_or(false, |ws| ws.left); for n in nodes { match n { Node::Text(s) => { previous_was_text = true; if !trim_left_next { res.push(Node::Text(s)); continue; } trim_left_next = false; let new_val = s.trim_start(); if !new_val.is_empty() { res.push(Node::Text(new_val.to_string())); } // empty text nodes will be skipped continue; } Node::VariableBlock(ws, _) | Node::ImportMacro(ws, _, _) | Node::Extends(ws, _) | Node::Include(ws, _, _) | Node::Set(ws, _) | Node::Break(ws) | Node::Comment(ws, _) | Node::Continue(ws) => { trim_right_previous!(previous_was_text && ws.left, res); trim_left_next = ws.right; } Node::Raw(start_ws, ref s, end_ws) => { trim_right_previous!(previous_was_text && start_ws.left, res); previous_was_text = false; trim_left_next = end_ws.right; if start_ws.right || end_ws.left { let val = if start_ws.right && end_ws.left { s.trim() } else if start_ws.right { s.trim_start() } else { s.trim_end() }; res.push(Node::Raw(start_ws, val.to_string(), end_ws)); continue; } } // Those nodes have a body surrounded by 2 tags Node::Forloop(start_ws, _, end_ws) | Node::MacroDefinition(start_ws, _, end_ws) | Node::FilterSection(start_ws, _, end_ws) | Node::Block(start_ws, _, end_ws) => { trim_right_previous!(previous_was_text && start_ws.left, res); previous_was_text = false; trim_left_next = end_ws.right; // let's remove ws from the bodies now and append the cleaned up node let body_ws = WS { left: start_ws.right, right: end_ws.left }; match n { Node::Forloop(_, mut forloop, _) => { forloop.body = remove_whitespace(forloop.body, Some(body_ws)); res.push(Node::Forloop(start_ws, forloop, end_ws)); } Node::MacroDefinition(_, mut macro_def, _) => { macro_def.body = remove_whitespace(macro_def.body, Some(body_ws)); res.push(Node::MacroDefinition(start_ws, macro_def, end_ws)); } Node::FilterSection(_, mut filter_section, _) => { filter_section.body = remove_whitespace(filter_section.body, Some(body_ws)); res.push(Node::FilterSection(start_ws, filter_section, end_ws)); } Node::Block(_, mut block, _) => { block.body = remove_whitespace(block.body, Some(body_ws)); res.push(Node::Block(start_ws, block, end_ws)); } _ => unreachable!(), }; continue; } // The ugly one Node::If(If { conditions, otherwise }, end_ws) => { trim_left_next = end_ws.right; let mut new_conditions: Vec<(_, _, Vec<_>)> = Vec::with_capacity(conditions.len()); for mut condition in conditions { if condition.0.left { // We need to trim the text node before the if tag if new_conditions.is_empty() && previous_was_text { trim_right_previous!(res); } else if let Some(&mut (_, _, ref mut body)) = new_conditions.last_mut() { trim_right_previous!(body); } } // we can't peek at the next one to know whether we need to trim right since // are consuming conditions. We'll find out at the next iteration. condition.2 = remove_whitespace( condition.2, Some(WS { left: condition.0.right, right: false }), ); new_conditions.push(condition); } previous_was_text = false; // We now need to look for the last potential `{%-` bit for if/elif // That can be a `{%- else` if let Some((else_ws, body)) = otherwise { if else_ws.left { if let Some(&mut (_, _, ref mut body)) = new_conditions.last_mut() { trim_right_previous!(body); } } let mut else_body = remove_whitespace(body, Some(WS { left: else_ws.right, right: false })); // if we have an `else`, the `endif` will affect the else node so we need to check if end_ws.left { trim_right_previous!(else_body); } res.push(Node::If( If { conditions: new_conditions, otherwise: Some((else_ws, else_body)) }, end_ws, )); continue; } // Or `{%- endif` if end_ws.left { if let Some(&mut (_, _, ref mut body)) = new_conditions.last_mut() { trim_right_previous!(true, body); } } res.push(Node::If(If { conditions: new_conditions, otherwise }, end_ws)); continue; } Node::Super => (), }; // If we are there, that means it's not a text node and we didn't have to modify the node previous_was_text = false; res.push(n); } if let Some(whitespace) = body_ws { trim_right_previous!(whitespace.right, res); } res }