use super::dots_dollars::strip_dollar_vars;
use super::positional::{try_rewrite_piped, try_rewrite_standalone};
use super::static_regex;
use super::tokens::{Token, significant_tokens, token_to_str, tokenize_block};
use regex::Regex;
use std::sync::LazyLock;
static GO_IF_RE: LazyLock<Regex> =
LazyLock::new(|| static_regex(r"^\{\{(-?)\s*if\s+(.+?)\s*(-?)\}\}"));
static GO_ELSE_IF_RE: LazyLock<Regex> =
LazyLock::new(|| static_regex(r"^\{\{(-?)\s*else\s+if\s+(.+?)\s*(-?)\}\}"));
static GO_ELSE_RE: LazyLock<Regex> = LazyLock::new(|| static_regex(r"^\{\{(-?)\s*else\s*(-?)\}\}"));
static GO_END_RE: LazyLock<Regex> = LazyLock::new(|| static_regex(r"^\{\{(-?)\s*end\s*(-?)\}\}"));
static GO_RANGE_KV_RE: LazyLock<Regex> = LazyLock::new(|| {
static_regex(r"^\{\{(-?)\s*range\s+\$(\w+)\s*,\s*\$(\w+)\s*:=\s*(.+?)\s*(-?)\}\}")
});
static GO_RANGE_V_RE: LazyLock<Regex> = LazyLock::new(|| {
static_regex(r"^\{\{(-?)\s*range\s+(?:\$(\w+)\s*:=\s*)?(.+?)\s*(-?)\}\}")
});
static GO_WITH_RE: LazyLock<Regex> =
LazyLock::new(|| static_regex(r"^\{\{(-?)\s*with\s+(.+?)\s*(-?)\}\}"));
static GO_VAR_ASSIGN_RE: LazyLock<Regex> = LazyLock::new(|| {
static_regex(r"^\{\{(-?)\s*\$(\w+)\s*:=\s*(.+?)\s*(-?)\}\}")
});
static GO_DOT_RE: LazyLock<Regex> = LazyLock::new(|| static_regex(r"^\{\{(-?)\s*\.\s*(-?)\}\}"));
fn tera_block(ltrim: &str, content: &str, rtrim: &str) -> String {
let l = if ltrim == "-" { "{%-" } else { "{%" };
let r = if rtrim == "-" { "-%}" } else { "%}" };
format!("{l} {content} {r}")
}
pub(super) fn preprocess_go_blocks(template: &str) -> String {
let mut result = String::with_capacity(template.len());
let mut block_stack: Vec<(&str, Option<String>)> = Vec::new();
let mut pos = 0;
let bytes = template.as_bytes();
while pos < bytes.len() {
if pos + 1 < bytes.len() && bytes[pos] == b'{' && bytes[pos + 1] == b'{' {
let remaining = &template[pos..];
if let Some(cap) = GO_DOT_RE.captures(remaining) {
let full = &cap[0];
let ltrim = &cap[1];
let rtrim = &cap[2];
let context_var = block_stack
.iter()
.rev()
.find_map(|(_, var)| var.as_deref())
.unwrap_or(".");
let l = if ltrim == "-" { "{{-" } else { "{{" };
let r = if rtrim == "-" { "-}}" } else { "}}" };
result.push_str(&format!("{l} {context_var} {r}"));
pos += full.len();
continue;
}
if let Some(cap) = GO_VAR_ASSIGN_RE.captures(remaining) {
let full = &cap[0];
let inner_trimmed = remaining[2..].trim_start_matches('-').trim_start();
if !inner_trimmed.starts_with("if ")
&& !inner_trimmed.starts_with("else")
&& !inner_trimmed.starts_with("end")
&& !inner_trimmed.starts_with("range ")
&& !inner_trimmed.starts_with("with ")
{
let ltrim = &cap[1];
let var = &cap[2];
let expr = &cap[3];
let rtrim = &cap[4];
result.push_str(&tera_block(ltrim, &format!("set {var} = {expr}"), rtrim));
pos += full.len();
continue;
}
}
if let Some(cap) = GO_ELSE_IF_RE.captures(remaining) {
let full = &cap[0];
result.push_str(&tera_block(&cap[1], &format!("elif {}", &cap[2]), &cap[3]));
pos += full.len();
continue;
}
if let Some(cap) = GO_IF_RE.captures(remaining) {
let full = &cap[0];
result.push_str(&tera_block(&cap[1], &format!("if {}", &cap[2]), &cap[3]));
block_stack.push(("if", None));
pos += full.len();
continue;
}
if let Some(cap) = GO_ELSE_RE.captures(remaining) {
let full = &cap[0];
result.push_str(&tera_block(&cap[1], "else", &cap[2]));
pos += full.len();
continue;
}
if let Some(cap) = GO_END_RE.captures(remaining) {
let full = &cap[0];
let end_tag = match block_stack.pop() {
Some(("for", _)) => "endfor",
_ => "endif", };
result.push_str(&tera_block(&cap[1], end_tag, &cap[2]));
pos += full.len();
continue;
}
if let Some(cap) = GO_RANGE_KV_RE.captures(remaining) {
let full = &cap[0];
let (key, val, collection) = (&cap[2], &cap[3], &cap[4]);
result.push_str(&tera_block(
&cap[1],
&format!("for {key}, {val} in {collection}"),
&cap[5],
));
block_stack.push(("for", Some(val.to_string())));
pos += full.len();
continue;
}
if let Some(cap) = GO_RANGE_V_RE.captures(remaining) {
let full = &cap[0];
let loop_var = cap.get(2).map(|m| m.as_str()).unwrap_or("val");
let collection = &cap[3];
result.push_str(&tera_block(
&cap[1],
&format!("for {loop_var} in {collection}"),
&cap[4],
));
block_stack.push(("for", Some(loop_var.to_string())));
pos += full.len();
continue;
}
if let Some(cap) = GO_WITH_RE.captures(remaining) {
let full = &cap[0];
let field = cap[2].to_string();
result.push_str(&tera_block(&cap[1], &format!("if {}", &field), &cap[3]));
block_stack.push(("with", Some(field)));
pos += full.len();
continue;
}
}
result.push(bytes[pos] as char);
pos += 1;
}
strip_dollar_vars(&result)
}
pub(super) fn extract_block_parts(block: &str) -> (&str, &str, &str) {
let open_len = if block.starts_with("{{-") || block.starts_with("{%-") {
3
} else {
2
};
let close_len = if block.ends_with("-}}") || block.ends_with("-%}") {
3
} else {
2
};
let open = &block[..open_len];
let close = &block[block.len() - close_len..];
let inner = &block[open_len..block.len() - close_len];
(open, inner, close)
}
pub(super) fn try_rewrite_control_block(inner: &str) -> Option<String> {
let tokens = tokenize_block(inner);
let sig = significant_tokens(&tokens);
if sig.is_empty() {
return None;
}
let keyword = match sig.first() {
Some(Token::Ident(k)) => k.as_str(),
_ => return None,
};
if keyword != "if" && keyword != "elif" {
return None;
}
let keyword_end_idx = tokens
.iter()
.position(|t| matches!(t, Token::Ident(k) if k == keyword))
.map(|i| i + 1)?;
let expr_tokens: Vec<Token> = tokens[keyword_end_idx..].to_vec();
if let Some(rewritten) = try_rewrite_standalone(&expr_tokens) {
let prefix: String = tokens[..keyword_end_idx]
.iter()
.map(|t| token_to_str(t))
.collect();
return Some(format!("{}{}", prefix, rewritten));
}
if let Some(rewritten) = try_rewrite_piped(&expr_tokens) {
let prefix: String = tokens[..keyword_end_idx]
.iter()
.map(|t| token_to_str(t))
.collect();
return Some(format!("{}{}", prefix, rewritten));
}
None
}