use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CommandSemanticResult {
pub is_error: bool,
pub message: Option<String>,
}
type CommandSemantic = fn(exit_code: i32, _stdout: &str, _stderr: &str) -> CommandSemanticResult;
fn default_semantic(exit_code: i32, _stdout: &str, _stderr: &str) -> CommandSemanticResult {
CommandSemanticResult {
is_error: exit_code != 0,
message: if exit_code != 0 {
Some(format!("Command failed with exit code {exit_code}"))
} else {
None
},
}
}
fn get_command_semantic(command: &str) -> CommandSemantic {
let base_command = heuristically_extract_base_command(command);
COMMAND_SEMANTICS
.get(&base_command)
.copied()
.unwrap_or(default_semantic)
}
fn extract_base_command(command: &str) -> String {
command
.trim()
.split_whitespace()
.next()
.unwrap_or("")
.to_string()
}
fn split_commands(command: &str) -> Vec<&str> {
command
.split(|c: char| c == ';' || c == '\n')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect()
}
fn heuristically_extract_base_command(command: &str) -> String {
let segments = split_commands(command);
let last_command = segments.last().copied().unwrap_or(command);
extract_base_command(last_command)
}
lazy_static::lazy_static! {
static ref COMMAND_SEMANTICS: HashMap<&'static str, CommandSemantic> = {
let mut m = HashMap::new();
m.insert("grep", |exit_code: i32, _stdout: &str, _stderr: &str| {
CommandSemanticResult {
is_error: exit_code >= 2,
message: if exit_code == 1 { Some("No matches found".to_string()) } else { None },
}
});
m.insert("rg", |exit_code: i32, _stdout: &str, _stderr: &str| {
CommandSemanticResult {
is_error: exit_code >= 2,
message: if exit_code == 1 { Some("No matches found".to_string()) } else { None },
}
});
m.insert("find", |exit_code: i32, _stdout: &str, _stderr: &str| {
CommandSemanticResult {
is_error: exit_code >= 2,
message: if exit_code == 1 { Some("Some directories were inaccessible".to_string()) } else { None },
}
});
m.insert("diff", |exit_code: i32, _stdout: &str, _stderr: &str| {
CommandSemanticResult {
is_error: exit_code >= 2,
message: if exit_code == 1 { Some("Files differ".to_string()) } else { None },
}
});
m.insert("test", |exit_code: i32, _stdout: &str, _stderr: &str| {
CommandSemanticResult {
is_error: exit_code >= 2,
message: if exit_code == 1 { Some("Condition is false".to_string()) } else { None },
}
});
m.insert("[", |exit_code: i32, _stdout: &str, _stderr: &str| {
CommandSemanticResult {
is_error: exit_code >= 2,
message: if exit_code == 1 { Some("Condition is false".to_string()) } else { None },
}
});
m
};
}
pub fn interpret_command_result(
command: &str,
exit_code: i32,
stdout: &str,
stderr: &str,
) -> CommandSemanticResult {
let semantic = get_command_semantic(command);
semantic(exit_code, stdout, stderr)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grep_no_matches() {
let result = interpret_command_result("grep pattern file.txt", 1, "", "");
assert!(!result.is_error);
assert_eq!(result.message, Some("No matches found".to_string()));
}
#[test]
fn test_grep_error() {
let result = interpret_command_result("grep pattern file.txt", 2, "", "grep: file.txt: No such file");
assert!(result.is_error);
}
#[test]
fn test_grep_match() {
let result = interpret_command_result("grep pattern file.txt", 0, "matched line", "");
assert!(!result.is_error);
assert_eq!(result.message, None);
}
#[test]
fn test_diff_files_differ() {
let result = interpret_command_result("diff a.txt b.txt", 1, "", "");
assert!(!result.is_error);
assert_eq!(result.message, Some("Files differ".to_string()));
}
#[test]
fn test_unknown_command_default_semantic() {
let result = interpret_command_result("unknown_cmd", 1, "", "");
assert!(result.is_error);
}
#[test]
fn test_unknown_command_success() {
let result = interpret_command_result("unknown_cmd", 0, "output", "");
assert!(!result.is_error);
assert_eq!(result.message, None);
}
}