use std::io::IsTerminal;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Palette {
pub(crate) green: &'static str,
pub(crate) red: &'static str,
pub(crate) yellow: &'static str,
pub(crate) cyan: &'static str,
pub(crate) dim: &'static str,
pub(crate) bold: &'static str,
pub(crate) reset: &'static str,
}
const ANSI: Palette = Palette {
green: "\x1b[32m",
red: "\x1b[31m",
yellow: "\x1b[33m",
cyan: "\x1b[36m",
dim: "\x1b[2m",
bold: "\x1b[1m",
reset: "\x1b[0m",
};
const PLAIN: Palette = Palette {
green: "",
red: "",
yellow: "",
cyan: "",
dim: "",
bold: "",
reset: "",
};
pub(crate) fn terminal_palette(is_tty: bool, no_color: bool) -> Palette {
if is_tty && !no_color {
ANSI
} else {
PLAIN
}
}
pub(crate) fn terminal_clear_line_prefix(is_tty: bool) -> &'static str {
if is_tty {
"\x1b[2K\r"
} else {
""
}
}
pub(crate) fn for_stdout() -> Palette {
terminal_palette(std::io::stdout().is_terminal(), no_color_requested())
}
pub(crate) fn for_stderr() -> Palette {
terminal_palette(std::io::stderr().is_terminal(), no_color_requested())
}
pub(crate) fn pass(label: &str, palette: &Palette) -> String {
format!("{}{}{}", palette.green, label, palette.reset)
}
pub(crate) fn fail(label: &str, palette: &Palette) -> String {
format!("{}{}{}", palette.red, label, palette.reset)
}
pub(crate) fn warn(label: &str, palette: &Palette) -> String {
format!("{}{}{}", palette.yellow, label, palette.reset)
}
pub(crate) fn info(label: &str, palette: &Palette) -> String {
format!("{}{}{}", palette.cyan, label, palette.reset)
}
pub(crate) const SEV_BRAND: &str = "\x1b[38;2;255;214;10m";
pub(crate) const SEV_CRITICAL: &str = "\x1b[38;2;255;69;58m";
pub(crate) const SEV_HIGH: &str = "\x1b[38;2;255;159;10m";
pub(crate) const SEV_MEDIUM: &str = "\x1b[38;2;255;214;10m";
pub(crate) const SEV_LOW: &str = "\x1b[38;2;100;210;255m";
pub(crate) const SEV_SAFE: &str = "\x1b[38;2;48;209;88m";
pub(crate) const SEV_AMBER: &str = "\x1b[38;2;255;159;10m";
pub(crate) const SEV_RAIL: &str = "\x1b[38;2;74;74;82m";
pub(crate) const SEV_MUTED: &str = "\x1b[38;2;138;138;150m";
pub(crate) const SEV_BOLD: &str = "\x1b[1m";
pub(crate) const SEV_RESET: &str = "\x1b[0m";
pub(crate) fn paint(text: String, color_code: &str, color: bool) -> String {
if color {
format!("{color_code}{text}{SEV_RESET}")
} else {
text
}
}
pub(crate) fn no_color_requested() -> bool {
no_color_from_env(std::env::var_os("NO_COLOR").as_deref())
}
pub(crate) fn no_color_from_env(value: Option<&std::ffi::OsStr>) -> bool {
value.is_some_and(|value| !value.is_empty())
}
pub(crate) fn print_diagnostic_finding(
prefix: &str,
detector_id: &str,
file_path: &str,
line: Option<usize>,
severity: keyhog_core::Severity,
confidence: Option<f64>,
credential_redacted: &str,
) -> std::io::Result<()> {
let mut stdout = std::io::stdout();
write_diagnostic_finding(
&mut stdout,
prefix,
detector_id,
file_path,
line,
severity,
confidence,
credential_redacted,
)
}
pub(crate) fn write_diagnostic_finding<W: std::io::Write>(
w: &mut W,
prefix: &str,
detector_id: &str,
file_path: &str,
line: Option<usize>,
severity: keyhog_core::Severity,
confidence: Option<f64>,
credential_redacted: &str,
) -> std::io::Result<()> {
let line_str = match line {
Some(line) => format!(":{line}"),
None => String::new(),
};
let conf_str = match confidence {
Some(confidence) => format!(" ({confidence:.2})"),
None => String::new(),
};
writeln!(
w,
"{} {} {}{} {}{} {}",
prefix,
detector_id,
file_path,
line_str,
severity.as_str().to_uppercase(),
conf_str,
credential_redacted
)
}