use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub struct OutputManager {
pub parser: OutputParser,
pub compressor: SemanticCompressor,
_cache: HashMap<String, ParsedOutput>,
}
impl OutputManager {
pub fn new() -> Self {
Self {
parser: OutputParser::new(),
compressor: SemanticCompressor::new(),
_cache: HashMap::new(),
}
}
pub fn process_output(&mut self, raw_output: &str) -> Result<ProcessedOutput> {
let parsed = self.parser.parse(raw_output)?;
let highlights = self.extract_highlights(&parsed);
let compressed = if raw_output.len() > 1024 {
Some(self.compressor.compress(raw_output)?)
} else {
None
};
Ok(ProcessedOutput {
raw: raw_output.to_string(),
parsed: parsed.clone(),
highlights,
compressed,
timestamp: chrono::Utc::now(),
})
}
fn extract_highlights(&self, parsed: &ParsedOutput) -> Vec<Highlight> {
let mut highlights = Vec::new();
match parsed {
ParsedOutput::CodeExecution { result: _, metrics } => {
if metrics.execution_time > std::time::Duration::from_secs(5) {
highlights.push(Highlight {
category: HighlightCategory::Performance,
message: format!("Slow execution: {:?}", metrics.execution_time),
severity: Severity::Warning,
});
}
}
ParsedOutput::BuildOutput { status, .. } => match status {
BuildStatus::Failed(error) => {
highlights.push(Highlight {
category: HighlightCategory::Error,
message: error.clone(),
severity: Severity::Error,
});
}
BuildStatus::Warning(warning) => {
highlights.push(Highlight {
category: HighlightCategory::Warning,
message: warning.clone(),
severity: Severity::Warning,
});
}
_ => {}
},
ParsedOutput::TestResults { failed, .. } => {
if *failed > 0 {
highlights.push(Highlight {
category: HighlightCategory::TestFailure,
message: format!("{} tests failed", failed),
severity: Severity::Error,
});
}
}
ParsedOutput::StructuredLog { level, message, .. } => {
if matches!(level, LogLevel::Error | LogLevel::Warning) {
highlights.push(Highlight {
category: HighlightCategory::Log,
message: message.clone(),
severity: match level {
LogLevel::Error => Severity::Error,
LogLevel::Warning => Severity::Warning,
_ => Severity::Info,
},
});
}
}
_ => {}
}
highlights
}
}
impl Default for OutputManager {
fn default() -> Self {
Self::new()
}
}
pub struct OutputParser {
patterns: HashMap<String, regex::Regex>,
}
impl OutputParser {
pub fn new() -> Self {
let mut patterns = HashMap::new();
patterns.insert(
"error".to_string(),
regex::Regex::new(r"(?i)(error|exception|failure)").unwrap(),
);
patterns.insert(
"warning".to_string(),
regex::Regex::new(r"(?i)(warning|warn)").unwrap(),
);
patterns.insert(
"success".to_string(),
regex::Regex::new(r"(?i)(success|passed|completed)").unwrap(),
);
Self { patterns }
}
pub fn parse(&self, output: &str) -> Result<ParsedOutput> {
if output.contains("BUILD SUCCESSFUL") || output.contains("Build succeeded") {
Ok(ParsedOutput::BuildOutput {
status: BuildStatus::Success,
artifacts: Vec::new(),
})
} else if output.contains("BUILD FAILED") || output.contains("Build failed") {
Ok(ParsedOutput::BuildOutput {
status: BuildStatus::Failed("Build failed".to_string()),
artifacts: Vec::new(),
})
} else if output.contains("tests passed") || output.contains("All tests passed") {
Ok(ParsedOutput::TestResults {
passed: 1, failed: 0,
details: TestDetails::default(),
})
} else if self.patterns["error"].is_match(output) {
Ok(ParsedOutput::StructuredLog {
level: LogLevel::Error,
message: output.to_string(),
context: LogContext::default(),
})
} else {
Ok(ParsedOutput::PlainText(output.to_string()))
}
}
}
impl Default for OutputParser {
fn default() -> Self {
Self::new()
}
}
pub struct SemanticCompressor {
_compression_level: f32,
}
impl SemanticCompressor {
pub fn new() -> Self {
Self {
_compression_level: 0.5,
}
}
pub fn compress(&self, output: &str) -> Result<CompressedOutput> {
let compressed = if output.len() > 500 {
format!("{}... (truncated)", &output[..500])
} else {
output.to_string()
};
let compressed_len = compressed.len();
let original_len = output.len();
Ok(CompressedOutput {
original_size: original_len,
compressed_size: compressed_len,
content: compressed,
compression_ratio: compressed_len as f32 / original_len as f32,
})
}
}
impl Default for SemanticCompressor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ParsedOutput {
PlainText(String),
CodeExecution {
result: String,
metrics: ExecutionMetrics,
},
BuildOutput {
status: BuildStatus,
artifacts: Vec<Artifact>,
},
TestResults {
passed: usize,
failed: usize,
details: TestDetails,
},
StructuredLog {
level: LogLevel,
message: String,
context: LogContext,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionMetrics {
pub execution_time: std::time::Duration,
pub memory_usage: Option<usize>,
pub cpu_usage: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BuildStatus {
Success,
Failed(String),
Warning(String),
InProgress,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Artifact {
pub name: String,
pub path: String,
pub size: usize,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TestDetails {
pub suite: Option<String>,
pub duration: Option<std::time::Duration>,
pub failed_tests: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LogLevel {
Trace,
Debug,
Info,
Warning,
Error,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LogContext {
pub file: Option<String>,
pub line: Option<usize>,
pub fields: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct ProcessedOutput {
pub raw: String,
pub parsed: ParsedOutput,
pub highlights: Vec<Highlight>,
pub compressed: Option<CompressedOutput>,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Highlight {
pub category: HighlightCategory,
pub message: String,
pub severity: Severity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HighlightCategory {
Error,
Warning,
Performance,
TestFailure,
Log,
Success,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Severity {
Info,
Warning,
Error,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompressedOutput {
pub original_size: usize,
pub compressed_size: usize,
pub content: String,
pub compression_ratio: f32,
}
use once_cell::sync::Lazy;
static _REGEX_DEPENDENCY: Lazy<()> = Lazy::new(|| {
});
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_output_parser() {
let parser = OutputParser::new();
let output = "BUILD SUCCESSFUL";
let parsed = parser.parse(output).unwrap();
match parsed {
ParsedOutput::BuildOutput { status, .. } => {
assert!(matches!(status, BuildStatus::Success));
}
_ => panic!("Expected BuildOutput"),
}
}
#[test]
fn test_output_manager() {
let mut manager = OutputManager::new();
let output = "Error: Something went wrong";
let processed = manager.process_output(output).unwrap();
assert!(!processed.highlights.is_empty());
assert_eq!(processed.highlights[0].severity, Severity::Error);
}
}