use regex::Regex;
use std::sync::LazyLock;
pub use self::builtins::builtin_patterns;
pub use self::toml::{FailureSection, PatternFile, load_user_patterns, parse_pattern_str};
#[doc(hidden)]
pub use self::toml::validate_pattern_regexes;
pub fn builtins() -> &'static [Pattern] {
&BUILTINS
}
pub struct Pattern {
pub command_match: Regex,
pub success: Option<SuccessPattern>,
pub failure: Option<FailurePattern>,
}
pub struct SuccessPattern {
pub strategy: SuccessStrategy,
}
pub struct FailurePattern {
pub strategy: FailureStrategy,
}
pub enum FailureStrategy {
Tail {
lines: usize,
},
Head {
lines: usize,
},
Grep {
pattern: Regex,
},
Between {
start: String,
end: String,
},
}
pub enum SuccessStrategy {
Regex {
pattern: Regex,
summary: String,
},
Tail {
lines: usize,
},
Head {
lines: usize,
},
Grep {
pattern: Regex,
},
}
fn extract_grep(output: &str, pattern: &Regex) -> String {
let mut result = String::new();
let mut first = true;
for line in output.lines() {
if pattern.is_match(line) {
if !first {
result.push('\n');
}
result.push_str(line);
first = false;
}
}
result
}
fn extract_tail(output: &str, lines: usize) -> Option<String> {
let all: Vec<&str> = output.lines().collect();
let start = all.len().saturating_sub(lines);
if start >= all.len() {
None
} else {
Some(all[start..].join("\n"))
}
}
fn extract_head(output: &str, lines: usize) -> Option<String> {
let all: Vec<&str> = output.lines().collect();
let end = lines.min(all.len());
if end == 0 {
None
} else {
Some(all[..end].join("\n"))
}
}
pub fn find_matching<'a>(command: &str, patterns: &'a [Pattern]) -> Option<&'a Pattern> {
patterns.iter().find(|p| p.command_match.is_match(command))
}
pub fn find_matching_ref<'a>(command: &str, patterns: &[&'a Pattern]) -> Option<&'a Pattern> {
patterns
.iter()
.find(|p| p.command_match.is_match(command))
.copied()
}
pub fn extract_summary(pat: &SuccessPattern, output: &str) -> Option<String> {
match &pat.strategy {
SuccessStrategy::Regex { pattern, summary } => {
let caps = pattern.captures(output)?;
let mut result = String::with_capacity(summary.len() + output.len());
let mut i = 0;
while i < summary.len() {
if let Some(j) = summary[i..].find('{') {
result.push_str(&summary[i..i + j]);
i += j + 1;
if let Some(k) = summary[i..].find('}') {
let placeholder = &summary[i..i + k];
if let Some(m) = caps.name(placeholder) {
result.push_str(m.as_str());
} else {
result.push('{');
result.push_str(placeholder);
result.push('}');
}
i += k + 1;
} else {
result.push('{');
result.push_str(&summary[i..]);
break;
}
} else {
result.push_str(&summary[i..]);
break;
}
}
Some(result)
}
SuccessStrategy::Tail { lines } => extract_tail(output, *lines),
SuccessStrategy::Head { lines } => extract_head(output, *lines),
SuccessStrategy::Grep { pattern } => {
let result = extract_grep(output, pattern);
if result.is_empty() {
None
} else {
Some(result)
}
}
}
}
pub fn extract_failure(pat: &FailurePattern, output: &str) -> String {
match &pat.strategy {
FailureStrategy::Tail { lines } => {
let all: Vec<&str> = output.lines().collect();
let start = all.len().saturating_sub(*lines);
all[start..].join("\n")
}
FailureStrategy::Head { lines } => {
let all: Vec<&str> = output.lines().collect();
all[..*lines.min(&all.len())].join("\n")
}
FailureStrategy::Grep { pattern, .. } => extract_grep(output, pattern),
FailureStrategy::Between { start, end } => {
let mut capturing = false;
let mut lines = Vec::new();
for line in output.lines() {
if !capturing && line.contains(start.as_str()) {
capturing = true;
}
if capturing {
lines.push(line);
if line.contains(end.as_str()) {
break;
}
}
}
lines.join("\n")
}
}
}
mod builtins;
mod toml;
static BUILTINS: LazyLock<Vec<Pattern>> = LazyLock::new(builtin_patterns);