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]
52pub fn should_use_color(no_color_flag: bool) -> bool {
53 !no_color_flag && std::env::var("NO_COLOR").is_err()
54}
55
56pub fn write_output(content: &str, target: &OutputTarget, quiet: bool) -> Result<()> {
58 match target {
59 OutputTarget::Stdout => {
60 println!("{content}");
61 Ok(())
62 }
63 OutputTarget::File(path) => {
64 std::fs::write(path, content)
65 .with_context(|| format!("Failed to write output to {}", path.display()))?;
66 if !quiet {
67 tracing::info!("Report written to {:?}", path);
68 }
69 Ok(())
70 }
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_output_target_from_option_none() {
80 let target = OutputTarget::from_option(None);
81 assert!(matches!(target, OutputTarget::Stdout));
82 }
83
84 #[test]
85 fn test_output_target_from_option_some() {
86 let path = PathBuf::from("/tmp/test.json");
87 let target = OutputTarget::from_option(Some(path.clone()));
88 match target {
89 OutputTarget::File(p) => assert_eq!(p, path),
90 _ => panic!("Expected File variant"),
91 }
92 }
93
94 #[test]
95 fn test_auto_detect_format_non_auto() {
96 let target = OutputTarget::Stdout;
97 assert_eq!(
98 auto_detect_format(ReportFormat::Json, &target),
99 ReportFormat::Json
100 );
101 assert_eq!(
102 auto_detect_format(ReportFormat::Sarif, &target),
103 ReportFormat::Sarif
104 );
105 }
106
107 #[test]
108 fn test_auto_detect_format_file_target() {
109 let target = OutputTarget::File(PathBuf::from("/tmp/test.json"));
110 assert_eq!(
112 auto_detect_format(ReportFormat::Auto, &target),
113 ReportFormat::Summary
114 );
115 }
116
117 #[test]
118 fn test_should_use_color_with_flag() {
119 assert!(!should_use_color(true));
120 }
121
122 #[test]
123 fn test_should_use_color_without_flag() {
124 let expected = std::env::var("NO_COLOR").is_err();
126 assert_eq!(should_use_color(false), expected);
127 }
128}