use once_cell::sync::Lazy;
use regex::Regex;
static ERROR_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)(?:^|\n)\s*(?:Error|ERROR|error:|✗|❌)").unwrap());
#[allow(dead_code)]
pub(crate) fn common_error_pattern() -> &'static Regex {
&ERROR_PATTERN
}
pub(crate) fn detect_error_common(content: &str, tail_bytes: usize) -> Option<String> {
let recent = safe_tail(content, tail_bytes);
if ERROR_PATTERN.is_match(recent) {
for line in recent.lines().rev().take(10) {
let trimmed = line.trim();
if trimmed.to_lowercase().contains("error")
|| trimmed.contains('✗')
|| trimmed.contains('❌')
{
return Some(trimmed.to_string());
}
}
return Some("Error detected".to_string());
}
None
}
pub(crate) fn strip_box_drawing(text: &str) -> &str {
if let Some(pos) = text.find(|c: char| ('\u{2500}'..='\u{257F}').contains(&c)) {
text[..pos].trim()
} else {
text
}
}
pub(crate) fn safe_tail(s: &str, n: usize) -> &str {
if s.len() <= n {
s
} else {
let start = s.len() - n;
let start = s
.char_indices()
.map(|(i, _)| i)
.find(|&i| i >= start)
.unwrap_or(s.len());
&s[start..]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_tail_short_string() {
assert_eq!(safe_tail("hello", 10), "hello");
}
#[test]
fn test_safe_tail_exact_length() {
assert_eq!(safe_tail("hello", 5), "hello");
}
#[test]
fn test_safe_tail_truncated() {
let result = safe_tail("hello world", 5);
assert_eq!(result, "world");
}
#[test]
fn test_safe_tail_utf8_boundary() {
let s = "あいう";
let result = safe_tail(s, 4);
assert_eq!(result, "う");
}
#[test]
fn test_strip_box_drawing_with_border() {
assert_eq!(strip_box_drawing("Option A │ preview"), "Option A");
}
#[test]
fn test_strip_box_drawing_no_border() {
assert_eq!(strip_box_drawing("Option A"), "Option A");
}
#[test]
fn test_detect_error_common_found() {
let content = "some output\nError: something failed\nmore output";
assert!(detect_error_common(content, 500).is_some());
}
#[test]
fn test_detect_error_common_not_found() {
let content = "some normal output\nall good";
assert!(detect_error_common(content, 500).is_none());
}
}