Skip to main content

todo_tree/printer/
json.rs

1use super::options::PrintOptions;
2use serde::Serialize;
3use std::collections::HashMap;
4use std::io::{self, Write};
5use todo_tree_core::ScanResult;
6
7#[derive(Debug, Serialize)]
8pub struct JsonOutput {
9    pub files: Vec<JsonFileEntry>,
10    pub summary: JsonSummary,
11}
12
13#[derive(Debug, Serialize)]
14pub struct JsonFileEntry {
15    pub path: String,
16    pub items: Vec<JsonTodoItem>,
17}
18
19#[derive(Debug, Serialize)]
20pub struct JsonTodoItem {
21    pub tag: String,
22    pub message: String,
23    pub line: usize,
24    pub column: usize,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub author: Option<String>,
27    pub priority: String,
28}
29
30#[derive(Debug, Serialize)]
31pub struct JsonSummary {
32    pub total_count: usize,
33    pub files_with_todos: usize,
34    pub files_scanned: usize,
35    pub tag_counts: HashMap<String, usize>,
36}
37
38pub fn print_json<W: Write>(
39    writer: &mut W,
40    result: &ScanResult,
41    options: &PrintOptions,
42) -> io::Result<()> {
43    let json_result = JsonOutput::from_scan_result(result, options);
44    let json_str = serde_json::to_string_pretty(&json_result).map_err(io::Error::other)?;
45    writeln!(writer, "{}", json_str)?;
46    Ok(())
47}
48
49impl JsonOutput {
50    pub fn from_scan_result(result: &ScanResult, options: &PrintOptions) -> Self {
51        let mut files: Vec<JsonFileEntry> = result
52            .sorted_files()
53            .iter()
54            .map(|(path, items)| {
55                let display_path = if options.full_paths {
56                    path.display().to_string()
57                } else if let Some(base) = &options.base_path {
58                    path.strip_prefix(base)
59                        .map(|p| p.display().to_string())
60                        .unwrap_or_else(|_| path.display().to_string())
61                } else {
62                    path.display().to_string()
63                };
64
65                JsonFileEntry {
66                    path: display_path,
67                    items: items
68                        .iter()
69                        .map(|item| JsonTodoItem {
70                            tag: item.tag.clone(),
71                            message: item.message.clone(),
72                            line: item.line,
73                            column: item.column,
74                            author: item.author.clone(),
75                            priority: format!("{:?}", item.priority),
76                        })
77                        .collect(),
78                }
79            })
80            .collect();
81
82        files.sort_by(|a, b| a.path.cmp(&b.path));
83
84        let summary = JsonSummary {
85            total_count: result.summary.total_count,
86            files_with_todos: result.summary.files_with_todos,
87            files_scanned: result.summary.files_scanned,
88            tag_counts: result.summary.tag_counts.clone(),
89        };
90
91        Self { files, summary }
92    }
93}