use crate::tui::theme::colors;
use ratatui::prelude::*;
pub fn render_vuln_cwe_lines(cwes: &[String], max_display: usize) -> Vec<Line<'static>> {
if cwes.is_empty() {
return vec![];
}
let cwe_list = cwes
.iter()
.take(max_display)
.cloned()
.collect::<Vec<_>>()
.join(", ");
vec![Line::from(vec![
Span::styled("CWEs: ", Style::default().fg(colors().text_muted)),
Span::styled(cwe_list, Style::default().fg(colors().accent)),
])]
}
pub fn render_kev_badge_spans(
is_kev: bool,
scheme: &crate::tui::theme::ColorScheme,
) -> Vec<Span<'static>> {
if is_kev {
vec![
Span::styled(
"KEV",
Style::default()
.fg(scheme.kev_badge_fg())
.bg(scheme.kev())
.bold(),
),
Span::raw(" "),
]
} else {
vec![]
}
}
pub fn render_depth_badge_spans(
depth: Option<usize>,
scheme: &crate::tui::theme::ColorScheme,
) -> Vec<Span<'static>> {
let Some(depth) = depth else {
return vec![];
};
let (label, bg_color) = if depth == 1 {
("DIR", scheme.direct_dep())
} else {
("TRN", scheme.transitive_dep())
};
vec![
Span::styled(
label,
Style::default()
.fg(scheme.badge_fg_dark)
.bg(bg_color)
.bold(),
),
Span::raw(" "),
]
}
pub fn render_vex_badge_spans(
vex_state: Option<&crate::model::VexState>,
scheme: &crate::tui::theme::ColorScheme,
) -> Vec<Span<'static>> {
let Some(state) = vex_state else {
return vec![];
};
use crate::model::VexState;
let (label, bg_color) = match state {
VexState::NotAffected => ("NA", scheme.low),
VexState::Fixed => ("FX", scheme.low),
VexState::Affected => ("AF", scheme.critical),
VexState::UnderInvestigation => ("UI", scheme.medium),
};
vec![
Span::styled(
label,
Style::default()
.fg(scheme.badge_fg_dark)
.bg(bg_color)
.bold(),
),
Span::raw(" "),
]
}
pub fn cvss_score_color(score: f32, scheme: &crate::tui::theme::ColorScheme) -> Color {
if score >= 9.0 {
scheme.critical
} else if score >= 7.0 {
scheme.high
} else if score >= 4.0 {
scheme.medium
} else {
scheme.low
}
}
pub fn source_color(source: &str, scheme: &crate::tui::theme::ColorScheme) -> Color {
match source.to_uppercase().as_str() {
"OSV" => scheme.accent,
"NVD" => scheme.highlight,
"GHSA" => scheme.info,
_ => scheme.text_muted,
}
}
pub fn severity_rank(severity: &str) -> u8 {
match severity.to_lowercase().as_str() {
"critical" => 0,
"high" => 1,
"medium" | "moderate" => 2,
"low" => 3,
"info" | "informational" | "none" => 4,
_ => 5,
}
}
pub fn word_wrap(text: &str, max_width: usize) -> Vec<String> {
let mut lines = Vec::new();
let mut current_line = String::new();
for word in text.split_whitespace() {
if current_line.is_empty() {
current_line = word.to_string();
} else if current_line.len() + 1 + word.len() <= max_width {
current_line.push(' ');
current_line.push_str(word);
} else {
lines.push(current_line);
current_line = word.to_string();
}
}
if !current_line.is_empty() {
lines.push(current_line);
}
if lines.is_empty() {
lines.push(String::new());
}
lines
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_word_wrap_basic() {
let result = word_wrap("hello world foo bar", 11);
assert_eq!(result, vec!["hello world", "foo bar"]);
}
#[test]
fn test_word_wrap_empty() {
let result = word_wrap("", 80);
assert_eq!(result, vec![""]);
}
#[test]
fn test_word_wrap_single_long_word() {
let result = word_wrap("superlongword", 5);
assert_eq!(result, vec!["superlongword"]);
}
#[test]
fn test_render_cwe_lines_empty() {
let lines = render_vuln_cwe_lines(&[], 3);
assert!(lines.is_empty());
}
#[test]
fn test_render_cwe_lines_some() {
let cwes = vec!["CWE-79".to_string(), "CWE-89".to_string()];
let lines = render_vuln_cwe_lines(&cwes, 3);
assert_eq!(lines.len(), 1);
}
}