sbom_tools/pipeline/
output.rs1use crate::reports::ReportFormat;
6use anyhow::{Context, Result};
7use std::io::IsTerminal;
8use std::path::PathBuf;
9
10#[derive(Debug, Clone)]
12pub enum OutputTarget {
13 Stdout,
15 File(PathBuf),
17}
18
19impl OutputTarget {
20 pub fn from_option(path: Option<PathBuf>) -> Self {
22 path.map_or(Self::Stdout, Self::File)
23 }
24
25 #[must_use]
27 pub fn is_terminal(&self) -> bool {
28 matches!(self, Self::Stdout) && std::io::stdout().is_terminal()
29 }
30}
31
32#[must_use]
37pub fn auto_detect_format(format: ReportFormat, target: &OutputTarget) -> ReportFormat {
38 match format {
39 ReportFormat::Auto => {
40 if target.is_terminal() {
41 ReportFormat::Tui
42 } else {
43 ReportFormat::Summary
44 }
45 }
46 other => other,
47 }
48}
49
50#[must_use]
54pub fn should_use_color(no_color_flag: bool) -> bool {
55 use std::io::IsTerminal;
56 let no_color_env = std::env::var_os("NO_COLOR").is_some_and(|v| !v.is_empty());
61 !no_color_flag && !no_color_env && std::io::stdout().is_terminal()
62}
63
64pub fn write_output(content: &str, target: &OutputTarget, quiet: bool) -> Result<()> {
66 match target {
67 OutputTarget::Stdout => {
68 println!("{content}");
69 Ok(())
70 }
71 OutputTarget::File(path) => {
72 std::fs::write(path, content)
73 .with_context(|| format!("Failed to write output to {}", path.display()))?;
74 if !quiet {
75 tracing::info!("Report written to {:?}", path);
76 }
77 Ok(())
78 }
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_output_target_from_option_none() {
88 let target = OutputTarget::from_option(None);
89 assert!(matches!(target, OutputTarget::Stdout));
90 }
91
92 #[test]
93 fn test_output_target_from_option_some() {
94 let path = PathBuf::from("/tmp/test.json");
95 let target = OutputTarget::from_option(Some(path.clone()));
96 match target {
97 OutputTarget::File(p) => assert_eq!(p, path),
98 _ => panic!("Expected File variant"),
99 }
100 }
101
102 #[test]
103 fn test_auto_detect_format_non_auto() {
104 let target = OutputTarget::Stdout;
105 assert_eq!(
106 auto_detect_format(ReportFormat::Json, &target),
107 ReportFormat::Json
108 );
109 assert_eq!(
110 auto_detect_format(ReportFormat::Sarif, &target),
111 ReportFormat::Sarif
112 );
113 }
114
115 #[test]
116 fn test_auto_detect_format_file_target() {
117 let target = OutputTarget::File(PathBuf::from("/tmp/test.json"));
118 assert_eq!(
120 auto_detect_format(ReportFormat::Auto, &target),
121 ReportFormat::Summary
122 );
123 }
124
125 #[test]
126 fn test_should_use_color_with_flag() {
127 assert!(!should_use_color(true));
128 }
129
130 #[test]
131 fn test_should_use_color_without_flag() {
132 use std::io::IsTerminal;
139 let no_color_env = std::env::var_os("NO_COLOR").is_some_and(|v| !v.is_empty());
140 let expected = !no_color_env && std::io::stdout().is_terminal();
141 assert_eq!(should_use_color(false), expected);
142 }
143}