use std::fs;
use std::path::{Path, PathBuf};
use crate::text_utils::read_text;
pub fn process_includes(
content: &str,
base_dir: &Path,
stack: &mut Vec<PathBuf>,
) -> Result<String, String> {
let mut result = Vec::new();
for (line_num, line) in content.split('\n').enumerate() {
let trimed = line.trim();
if let Some(rest) = trimed.strip_prefix("---$include") {
let rest = rest.trim();
if let Some(include_path_str) = rest.strip_prefix('"').and_then(|s| s.strip_suffix('"'))
{
let include_path = base_dir.join(include_path_str);
let canonical = fs::canonicalize(&include_path).map_err(|e| {
format!(
"Failed to resolve include path {} at line {}: {}",
include_path.display(),
line_num + 1,
e
)
})?;
if stack.contains(&canonical) {
return Err(format!(
"Circular include detected: {}",
canonical.display()
));
}
stack.push(canonical.clone());
let include_content = read_text(&canonical).map_err(|e| {
format!(
"Failed to read included file {}: {}",
canonical.display(),
e
)
})?;
let included_result = process_includes(
&include_content,
canonical.parent().unwrap_or(Path::new("")),
stack,
)?;
stack.pop();
result.push(included_result);
} else {
return Err(format!(
"Malformed $include at line {}: expected quoted path",
line_num + 1
));
}
} else {
result.push(line.to_string());
}
}
Ok(result.join("\n"))
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use crate::common::get_fixture_path;
#[test]
fn test_nested_includes() {
let input_path = get_fixture_path("include_main.lua");
let input = read_text(&input_path).unwrap();
let expected_path = get_fixture_path("include_main_out.lua");
let expected = read_text(&expected_path).unwrap();
let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let mut stack = Vec::new();
let result = process_includes(&input, &base_dir, &mut stack);
match result {
Ok(output) => assert_eq!(output, expected),
Err(e) => panic!("Unexpected error: {e}"),
}
}
#[test]
fn test_cycle_includes() {
let input_path = get_fixture_path("include_cycle_a.lua");
let input = read_text(&input_path).unwrap();
let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let mut stack = Vec::new();
let result = process_includes(&input, &base_dir, &mut stack);
assert!(result.is_err());
let error_message = result.unwrap_err();
assert!(error_message.starts_with("Circular include detected"));
}
#[test]
fn test_duplicate_includes_allowed() {
let input_path = get_fixture_path("include_duplicate_main.lua");
let input = read_text(&input_path).unwrap();
let expected_path = get_fixture_path("include_duplicate_out.lua");
let expected = read_text(&expected_path).unwrap();
let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let mut stack = Vec::new();
let result = process_includes(&input, &base_dir, &mut stack).unwrap();
assert_eq!(result, expected);
}
}