use std::collections::HashMap;
use regex::Regex;
use anyhow::{Result, Context};
use crate::models::{ExtractRule, ExecutionResult};
#[derive(Debug, Clone)]
pub struct VariableManager {
variables: HashMap<String, String>,
}
impl VariableManager {
pub fn new(initial_variables: Option<HashMap<String, String>>) -> Self {
Self {
variables: initial_variables.unwrap_or_default(),
}
}
pub fn replace_variables(&self, content: &str) -> String {
let mut result = content.to_string();
for (key, value) in &self.variables {
let placeholder = format!("{{{{ {} }}}}", key);
result = result.replace(&placeholder, value);
}
result
}
pub fn extract_variables(&mut self, extract_rules: &[ExtractRule], execution_result: &ExecutionResult) -> Result<()> {
for rule in extract_rules {
let source_content = match rule.source.as_str() {
"stdout" => &execution_result.stdout,
"stderr" => &execution_result.stderr,
"exit_code" => &execution_result.exit_code.to_string(),
_ => {
return Err(anyhow::anyhow!("Unknown extract source: {}", rule.source));
}
};
if rule.cascade {
self.extract_with_cascade(rule, source_content)?;
} else {
self.extract_with_fallback(rule, source_content)?;
}
}
Ok(())
}
fn extract_with_cascade(&mut self, rule: &ExtractRule, source_content: &str) -> Result<()> {
let mut current_content = source_content.to_string();
let mut extracted_value = None;
for (pattern_index, pattern) in rule.patterns.iter().enumerate() {
let regex = Regex::new(pattern)
.context(format!("Invalid regex pattern {} for rule '{}': {}", pattern_index + 1, rule.name, pattern))?;
if let Some(captures) = regex.captures(¤t_content) {
let matched_value = if let Some(value) = captures.get(1) {
value.as_str().to_string()
} else {
tracing::warn!("Pattern {} for rule '{}' has no capture groups, using full match: {}",
pattern_index + 1, rule.name, pattern);
if let Some(full_match) = captures.get(0) {
full_match.as_str().to_string()
} else {
continue;
}
};
if pattern_index == rule.patterns.len() - 1 {
extracted_value = Some(matched_value);
break;
} else {
current_content = matched_value;
}
} else {
tracing::debug!("Cascade failed at pattern {} for rule '{}': no match", pattern_index + 1, rule.name);
break;
}
}
if let Some(value) = extracted_value {
self.variables.insert(rule.name.clone(), value.clone());
tracing::debug!("Cascade extraction successful for rule '{}': {}", rule.name, value);
} else {
tracing::debug!("Cascade extraction failed for rule '{}'", rule.name);
}
Ok(())
}
fn extract_with_fallback(&mut self, rule: &ExtractRule, source_content: &str) -> Result<()> {
let mut extracted = false;
for (pattern_index, pattern) in rule.patterns.iter().enumerate() {
let regex = Regex::new(pattern)
.context(format!("Invalid regex pattern {} for rule '{}': {}", pattern_index + 1, rule.name, pattern))?;
if let Some(captures) = regex.captures(source_content) {
if let Some(value) = captures.get(1) {
self.variables.insert(rule.name.clone(), value.as_str().to_string());
extracted = true;
tracing::debug!("Fallback extraction successful for rule '{}' with pattern {}: {}", rule.name, pattern_index + 1, value.as_str());
break; } else {
tracing::warn!("Pattern {} for rule '{}' has no capture groups: {}",
pattern_index + 1, rule.name, pattern);
}
}
}
if !extracted {
tracing::debug!("No pattern matched for rule '{}' in source '{}'", rule.name, rule.source);
}
Ok(())
}
pub fn get_variables(&self) -> &HashMap<String, String> {
&self.variables
}
pub fn remove_variable(&mut self, key: &str) {
self.variables.remove(key);
}
pub fn set_variable(&mut self, key: String, value: String) {
self.variables.insert(key, value);
}
pub fn get_variable(&self, key: &str) -> Option<&String> {
self.variables.get(key)
}
}