1pub mod flat;
5pub mod json;
7pub mod options;
9pub mod summary;
11pub mod tree;
13pub mod utils;
15
16use crate::core::ScanResult;
17use flat::print_flat;
18use json::print_json;
19pub use options::{OutputFormat, PrintOptions};
20use std::io::{self, Write};
21use summary::print_summary;
22use tree::print_tree;
23
24pub struct Printer {
26 options: PrintOptions,
27}
28
29impl Printer {
30 pub fn new(options: PrintOptions) -> Self {
34 if !options.colored {
35 colored::control::set_override(false);
36 }
37 Self { options }
38 }
39
40 pub fn print(&self, result: &ScanResult) -> io::Result<()> {
42 let stdout = io::stdout();
43 let mut handle = stdout.lock();
44 self.print_to(&mut handle, result)
45 }
46
47 pub fn print_to<W: Write>(&self, writer: &mut W, result: &ScanResult) -> io::Result<()> {
49 match self.options.format {
50 OutputFormat::Tree => print_tree(writer, result, &self.options)?,
51 OutputFormat::Flat => print_flat(writer, result, &self.options)?,
52 OutputFormat::Json => print_json(writer, result, &self.options)?,
53 }
54
55 if self.options.show_summary && self.options.format != OutputFormat::Json {
56 writeln!(writer)?;
57 print_summary(writer, result, &self.options)?;
58 }
59
60 Ok(())
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::core::TodoItem;
68 use std::path::PathBuf;
69
70 fn result_with_one_item() -> ScanResult {
71 let mut result = ScanResult::new(PathBuf::from("."));
72 result.add_file(
73 PathBuf::from("a.rs"),
74 vec![TodoItem {
75 tag: "TODO".to_string(),
76 message: "msg".to_string(),
77 line: 1,
78 column: 1,
79 line_content: None,
80 author: None,
81 priority: crate::core::TodoPriority::Medium,
82 }],
83 );
84 result
85 }
86
87 fn opts(format: OutputFormat, show_summary: bool) -> PrintOptions {
88 PrintOptions {
89 format,
90 colored: false,
91 clickable_links: false,
92 show_summary,
93 ..PrintOptions::default()
94 }
95 }
96
97 #[test]
98 fn print_to_renders_tree_with_summary() {
99 let printer = Printer::new(opts(OutputFormat::Tree, true));
100 let mut buf = Vec::new();
101 printer.print_to(&mut buf, &result_with_one_item()).unwrap();
102 let output = String::from_utf8(buf).unwrap();
103
104 assert!(output.contains("a.rs"));
105 assert!(output.contains("Found 1 TODO items"));
106 }
107
108 #[test]
109 fn print_to_renders_flat_without_summary() {
110 let printer = Printer::new(opts(OutputFormat::Flat, false));
111 let mut buf = Vec::new();
112 printer.print_to(&mut buf, &result_with_one_item()).unwrap();
113 let output = String::from_utf8(buf).unwrap();
114
115 assert!(output.contains("a.rs"));
116 assert!(!output.contains("Found"));
117 }
118
119 #[test]
120 fn print_to_renders_json_and_never_appends_summary() {
121 let printer = Printer::new(opts(OutputFormat::Json, true));
122 let mut buf = Vec::new();
123 printer.print_to(&mut buf, &result_with_one_item()).unwrap();
124 let output = String::from_utf8(buf).unwrap();
125
126 assert!(output.trim_start().starts_with('{'));
127 assert!(!output.contains("Found"));
128 }
129
130 #[test]
131 fn new_disables_global_color_override_when_uncolored() {
132 let _printer = Printer::new(opts(OutputFormat::Tree, false));
133 assert!(!colored::control::SHOULD_COLORIZE.should_colorize());
134 colored::control::unset_override();
135 }
136
137 #[test]
138 fn print_writes_to_stdout_without_error() {
139 let printer = Printer::new(opts(OutputFormat::Flat, false));
140 printer.print(&result_with_one_item()).unwrap();
141 }
142}