1use std::fs;
10use std::io::{self, Write};
11use std::path::Path;
12
13use crate::formatter::{get_formatter, OutputFormat};
14use crate::tree::generate_tree;
15use crate::walker::FileEntry;
16
17pub struct ContextResult {
19 pub content: String,
20 pub file_count: usize,
21 pub total_size: u64,
22 pub output_bytes: usize,
24}
25
26pub fn generate_context(
28 root: &Path,
29 entries: &[FileEntry],
30 format: OutputFormat,
31 include_tree: bool,
32 show_sizes: bool,
33) -> io::Result<ContextResult> {
34 let formatter = get_formatter(format);
35
36 let root_name = root
38 .file_name()
39 .map(|s| s.to_string_lossy().to_string())
40 .unwrap_or_else(|| "project".to_string());
41
42 let tree_block = if include_tree {
44 let tree = generate_tree(&root_name, entries, show_sizes);
45 Some(formatter.format_tree(&tree))
46 } else {
47 None
48 };
49
50 let mut file_blocks = Vec::new();
52 let mut total_size = 0u64;
53 let mut processed_count = 0usize;
54
55 for entry in entries {
56 match read_file_content(&entry.absolute_path) {
57 Ok(content) => {
58 let block = formatter.format_file(entry, &content);
59 file_blocks.push(block);
60 total_size += entry.size;
61 processed_count += 1;
62 }
63 Err(e) => {
64 eprintln!(
65 "Warning: could not read {}: {}",
66 entry.relative_path.display(),
67 e
68 );
69 }
70 }
71 }
72
73 let separator = get_separator(format);
75 let files_block = file_blocks.join(&separator);
76
77 let content = formatter.wrap(tree_block.as_deref(), &files_block);
79
80 Ok(ContextResult {
81 output_bytes: content.len(),
82 content,
83 file_count: processed_count,
84 total_size,
85 })
86}
87
88pub fn stream_context(
90 root: &Path,
91 entries: &[FileEntry],
92 format: OutputFormat,
93 include_tree: bool,
94 show_sizes: bool,
95) -> io::Result<ContextResult> {
96 let formatter = get_formatter(format);
97 let mut stdout = io::stdout().lock();
98
99 let root_name = root
101 .file_name()
102 .map(|s| s.to_string_lossy().to_string())
103 .unwrap_or_else(|| "project".to_string());
104
105 let tree_block = if include_tree {
107 let tree = generate_tree(&root_name, entries, show_sizes);
108 Some(formatter.format_tree(&tree))
109 } else {
110 None
111 };
112
113 let start = formatter.stream_start(tree_block.as_deref());
115 let mut output_bytes = 0usize;
116 if !start.is_empty() {
117 writeln!(stdout, "{}", start)?;
118 output_bytes += start.len() + 1;
119 }
120
121 let mut total_size = 0u64;
123 let mut processed_count = 0usize;
124 let separator = formatter.separator();
125
126 for (i, entry) in entries.iter().enumerate() {
127 match read_file_content(&entry.absolute_path) {
128 Ok(content) => {
129 let block = formatter.format_file(entry, &content);
130 if i > 0 {
131 write!(stdout, "{}", separator)?;
132 output_bytes += separator.len();
133 }
134 write!(stdout, "{}", block)?;
135 output_bytes += block.len();
136 stdout.flush()?;
137 total_size += entry.size;
138 processed_count += 1;
139 }
140 Err(e) => {
141 eprintln!(
142 "Warning: could not read {}: {}",
143 entry.relative_path.display(),
144 e
145 );
146 }
147 }
148 }
149
150 let end = formatter.stream_end();
152 if !end.is_empty() {
153 writeln!(stdout, "\n{}", end)?;
154 output_bytes += end.len() + 2;
155 } else {
156 writeln!(stdout)?;
157 output_bytes += 1;
158 }
159
160 Ok(ContextResult {
161 content: String::new(), file_count: processed_count,
163 total_size,
164 output_bytes,
165 })
166}
167
168fn read_file_content(path: &Path) -> io::Result<String> {
170 let bytes = fs::read(path)?;
171
172 match String::from_utf8(bytes.clone()) {
174 Ok(s) => Ok(s),
175 Err(_) => {
176 Ok(String::from_utf8_lossy(&bytes).into_owned())
178 }
179 }
180}
181
182fn get_separator(format: OutputFormat) -> String {
184 match format {
185 OutputFormat::Xml => "\n".to_string(),
186 OutputFormat::Markdown => "\n\n".to_string(),
187 OutputFormat::Plain => "\n".to_string(),
188 OutputFormat::Json => ",".to_string(), }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_separator_xml() {
198 assert_eq!(get_separator(OutputFormat::Xml), "\n");
199 }
200
201 #[test]
202 fn test_separator_markdown() {
203 assert_eq!(get_separator(OutputFormat::Markdown), "\n\n");
204 }
205}