use crate::error::{ConfigError, ParseResult};
use crate::variables::VariableManager;
use std::path::{Path, PathBuf};
pub struct DirectiveProcessor {
if_stack: Vec<bool>,
suppress_errors: bool,
}
impl DirectiveProcessor {
pub fn new() -> Self {
Self {
if_stack: Vec::new(),
suppress_errors: false,
}
}
pub fn process_directive(
&mut self,
directive_type: &str,
args: Option<&str>,
variables: &VariableManager,
) -> ParseResult<()> {
match directive_type {
"if" => {
let var_name = args.ok_or_else(|| {
ConfigError::custom("'if' directive requires a variable name")
})?;
let var_name = var_name.trim();
let (negated, var_name) = if let Some(stripped) = var_name.strip_prefix('!') {
(true, stripped.trim())
} else {
(false, var_name)
};
let condition = variables.contains(var_name);
let final_condition = if negated { !condition } else { condition };
self.if_stack.push(final_condition);
Ok(())
}
"endif" => {
if self.if_stack.is_empty() {
return Err(ConfigError::custom("'endif' without matching 'if'"));
}
self.if_stack.pop();
Ok(())
}
"noerror" => {
let value = args.ok_or_else(|| {
ConfigError::custom("'noerror' directive requires a value (true/false)")
})?;
let value = value.trim();
self.suppress_errors = value == "true";
Ok(())
}
_ => Err(ConfigError::custom(format!(
"Unknown directive: {}",
directive_type
))),
}
}
pub fn should_execute(&self) -> bool {
self.if_stack.iter().all(|&cond| cond)
}
#[allow(dead_code)]
pub fn should_suppress_errors(&self) -> bool {
self.suppress_errors
}
pub fn reset(&mut self) {
self.if_stack.clear();
self.suppress_errors = false;
}
#[allow(dead_code)]
pub fn has_unclosed_blocks(&self) -> bool {
!self.if_stack.is_empty()
}
}
impl Default for DirectiveProcessor {
fn default() -> Self {
Self::new()
}
}
pub struct SourceResolver {
base_dir: PathBuf,
loading_stack: Vec<PathBuf>,
max_depth: usize,
}
impl SourceResolver {
pub fn new(base_dir: impl AsRef<Path>) -> Self {
Self {
base_dir: base_dir.as_ref().to_path_buf(),
loading_stack: Vec::new(),
max_depth: 50,
}
}
#[allow(dead_code)]
pub fn with_max_depth(mut self, max_depth: usize) -> Self {
self.max_depth = max_depth;
self
}
pub fn resolve_path(&self, path: &str) -> ParseResult<PathBuf> {
let expanded;
let path = if let Some(rest) = path.strip_prefix("~/") {
if let Ok(home) = std::env::var("HOME") {
expanded = format!("{}/{}", home, rest);
expanded.as_str()
} else {
path
}
} else {
path
};
let path_obj = Path::new(path);
let resolved = if path_obj.is_absolute() {
path_obj.to_path_buf()
} else {
self.base_dir.join(path_obj)
};
resolved
.canonicalize()
.map_err(|e| ConfigError::io(path, format!("failed to resolve path: {}", e)))
}
pub fn begin_load(&mut self, path: &Path) -> ParseResult<()> {
if self.loading_stack.len() >= self.max_depth {
return Err(ConfigError::custom(format!(
"Maximum source directive recursion depth ({}) exceeded",
self.max_depth
)));
}
if self.loading_stack.contains(&path.to_path_buf()) {
return Err(ConfigError::custom(format!(
"Circular source directive detected: {}",
path.display()
)));
}
self.loading_stack.push(path.to_path_buf());
Ok(())
}
pub fn end_load(&mut self) {
self.loading_stack.pop();
}
#[allow(dead_code)]
pub fn depth(&self) -> usize {
self.loading_stack.len()
}
#[allow(dead_code)]
pub fn reset(&mut self) {
self.loading_stack.clear();
}
}
pub struct MultilineProcessor;
impl MultilineProcessor {
pub fn join_lines(lines: &[String]) -> String {
lines.join(" ")
}
#[allow(dead_code)]
pub fn is_continuation(line: &str) -> bool {
line.trim_end().ends_with('\\')
}
#[allow(dead_code)]
pub fn remove_backslash(line: &str) -> String {
let trimmed = line.trim_end();
if let Some(stripped) = trimmed.strip_suffix('\\') {
stripped.to_string()
} else {
line.to_string()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_directive_if() {
let mut processor = DirectiveProcessor::new();
let mut variables = VariableManager::new();
variables.set("TEST".to_string(), "value".to_string());
processor
.process_directive("if", Some("TEST"), &variables)
.unwrap();
assert!(processor.should_execute());
processor
.process_directive("endif", None, &variables)
.unwrap();
processor
.process_directive("if", Some("MISSING"), &variables)
.unwrap();
assert!(!processor.should_execute());
processor
.process_directive("endif", None, &variables)
.unwrap();
}
#[test]
fn test_directive_noerror() {
let mut processor = DirectiveProcessor::new();
let variables = VariableManager::new();
assert!(!processor.should_suppress_errors());
processor
.process_directive("noerror", Some("true"), &variables)
.unwrap();
assert!(processor.should_suppress_errors());
processor
.process_directive("noerror", Some("false"), &variables)
.unwrap();
assert!(!processor.should_suppress_errors());
}
#[test]
fn test_multiline_join() {
let lines = vec![
"line1".to_string(),
"line2".to_string(),
"line3".to_string(),
];
assert_eq!(MultilineProcessor::join_lines(&lines), "line1 line2 line3");
}
#[test]
fn test_multiline_continuation() {
assert!(MultilineProcessor::is_continuation("line \\"));
assert!(MultilineProcessor::is_continuation("line\\ "));
assert!(!MultilineProcessor::is_continuation("line"));
}
#[test]
fn test_remove_backslash() {
assert_eq!(MultilineProcessor::remove_backslash("line\\"), "line");
assert_eq!(MultilineProcessor::remove_backslash("line\\ "), "line");
assert_eq!(MultilineProcessor::remove_backslash("line"), "line");
}
}