Skip to main content

todo_tree/printer/
mod.rs

1//! Formatting a [`crate::core::ScanResult`] as tree, flat, or JSON output.
2
3/// Flat (one-line-per-item) rendering.
4pub mod flat;
5/// JSON rendering.
6pub mod json;
7/// [`OutputFormat`] and [`PrintOptions`].
8pub mod options;
9/// Summary-block rendering.
10pub mod summary;
11/// Tree (file- or tag-grouped) rendering.
12pub mod tree;
13/// Path formatting, terminal hyperlink, and tag-coloring helpers.
14pub 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
24/// Renders a [`ScanResult`] according to a fixed set of [`PrintOptions`].
25pub struct Printer {
26    options: PrintOptions,
27}
28
29impl Printer {
30    /// Creates a printer with the given options. If `options.colored` is
31    /// `false`, this also disables the process-wide `colored` crate
32    /// override.
33    pub fn new(options: PrintOptions) -> Self {
34        if !options.colored {
35            colored::control::set_override(false);
36        }
37        Self { options }
38    }
39
40    /// Renders `result` to stdout.
41    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    /// Renders `result` to `writer`.
48    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}