1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3use std::sync::OnceLock;
4
5use crate::category::FileCategory;
6
7#[derive(Debug, Serialize, Deserialize)]
9pub struct ProcessedFile {
10 pub priority: i32,
12 pub file_index: usize,
14 pub rel_path: String,
16 pub content: String,
18 pub size_bytes: usize,
20 #[serde(skip)]
22 pub token_count: OnceLock<usize>,
23 pub formatted_content: Option<String>,
25 pub category: FileCategory,
27}
28
29impl Clone for ProcessedFile {
30 fn clone(&self) -> Self {
31 Self {
32 priority: self.priority,
33 file_index: self.file_index,
34 rel_path: self.rel_path.clone(),
35 content: self.content.clone(),
36 size_bytes: self.size_bytes,
37 token_count: OnceLock::new(),
38 formatted_content: self.formatted_content.clone(),
39 category: self.category,
40 }
41 }
42}
43
44impl ProcessedFile {
45 pub fn new(rel_path: String, content: String, priority: i32, file_index: usize) -> Self {
47 let category = crate::category::categorize_file(&rel_path);
48 let size_bytes = content.len();
49 Self {
50 priority,
51 file_index,
52 rel_path,
53 content,
54 size_bytes,
55 token_count: OnceLock::new(),
56 formatted_content: None,
57 category,
58 }
59 }
60
61 pub fn new_with_category(
63 rel_path: String,
64 content: String,
65 priority: i32,
66 file_index: usize,
67 category: FileCategory,
68 ) -> Self {
69 let size_bytes = content.len();
70 Self {
71 priority,
72 file_index,
73 rel_path,
74 content,
75 size_bytes,
76 token_count: OnceLock::new(),
77 formatted_content: None,
78 category,
79 }
80 }
81
82 pub fn get_token_count(&self) -> usize {
84 *self.token_count.get_or_init(|| self.compute_token_count())
85 }
86
87 pub fn get_formatted_content(&self, include_line_numbers: bool) -> &str {
89 if !include_line_numbers {
90 return &self.content;
91 }
92
93 self.formatted_content.as_deref().unwrap_or("")
94 }
95
96 fn compute_token_count(&self) -> usize {
98 if let Some(ref formatted) = self.formatted_content {
101 crate::count_tokens(formatted)
102 } else {
103 crate::count_tokens(&self.content)
106 }
107 }
108
109 #[allow(dead_code)]
111 fn format_content_with_line_numbers(&self) -> String {
112 if self.content.is_empty() {
113 return String::new();
114 }
115
116 let lines: Vec<&str> = self.content.lines().collect();
117 let total_lines = lines.len();
118
119 let width = if total_lines == 0 {
121 3
122 } else {
123 std::cmp::max(3, total_lines.to_string().len())
124 };
125
126 let mut result = String::with_capacity(self.content.len() + total_lines * (width + 3));
128
129 for (i, line) in lines.iter().enumerate() {
130 result.push_str(&format!("{:width$} | {}\n", i + 1, line, width = width));
131 }
132
133 if result.ends_with('\n') {
135 result.pop();
136 }
137
138 result
139 }
140
141 pub fn get_size(&self, token_mode: bool, include_line_numbers: bool) -> usize {
143 if token_mode {
144 self.get_token_count()
145 } else {
146 if include_line_numbers {
148 self.get_formatted_content(true).len()
149 } else {
150 self.size_bytes
151 }
152 }
153 }
154
155 pub fn exceeds_limit(
157 &self,
158 limit: usize,
159 token_mode: bool,
160 include_line_numbers: bool,
161 ) -> bool {
162 self.get_size(token_mode, include_line_numbers) > limit
163 }
164
165 pub fn clear_caches(&mut self) {
167 self.token_count = OnceLock::new();
168 self.formatted_content = None;
169 }
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct FilePriority {
175 pub rule_priority: i32,
177 pub git_boost: i32,
179 pub combined: i32,
181}
182
183impl FilePriority {
184 pub fn new(rule_priority: i32, git_boost: i32) -> Self {
185 Self {
186 rule_priority,
187 git_boost,
188 combined: rule_priority + git_boost,
189 }
190 }
191}
192
193#[derive(Debug, Clone)]
195pub struct RepositoryInfo {
196 pub root_path: PathBuf,
198 pub is_git_repo: bool,
200 pub commit_times: std::collections::HashMap<String, u64>,
202}
203
204impl RepositoryInfo {
205 pub fn new(root_path: PathBuf, is_git_repo: bool) -> Self {
206 Self {
207 root_path,
208 is_git_repo,
209 commit_times: std::collections::HashMap::new(),
210 }
211 }
212}
213
214#[derive(Debug, Clone)]
216pub struct InputConfig {
217 pub input_paths: Vec<String>,
219 pub ignore_patterns: Vec<glob::Pattern>,
221 pub binary_extensions: std::collections::HashSet<String>,
223 pub max_git_depth: i32,
225 pub git_boost_max: Option<i32>,
227}
228
229impl Default for InputConfig {
230 fn default() -> Self {
231 Self {
232 input_paths: Vec::new(),
233 ignore_patterns: Vec::new(),
234 binary_extensions: std::collections::HashSet::new(),
235 max_git_depth: 100,
236 git_boost_max: Some(100),
237 }
238 }
239}
240
241#[derive(Debug, Clone)]
243pub struct OutputConfig {
244 pub max_size: String,
246 pub token_mode: bool,
248 pub token_limit: Option<String>,
250 pub output_template: String,
252 pub line_numbers: bool,
254 pub json_output: bool,
256 pub tree_header: bool,
258 pub tree_only: bool,
260 pub output_dir: Option<String>,
262 pub output_name: Option<String>,
264 pub stream: bool,
266}
267
268impl Default for OutputConfig {
269 fn default() -> Self {
270 Self {
271 max_size: "10MB".to_string(),
272 token_mode: false,
273 token_limit: None,
274 output_template: ">>>> FILE_PATH\nFILE_CONTENT".to_string(),
275 line_numbers: false,
276 json_output: false,
277 tree_header: false,
278 tree_only: false,
279 output_dir: None,
280 output_name: None,
281 stream: false,
282 }
283 }
284}
285
286#[derive(Debug, Clone)]
288pub struct ProcessingConfig {
289 pub priority_rules: Vec<crate::priority::PriorityRule>,
291 pub category_weights: crate::category::CategoryWeights,
293 pub debug: bool,
295 pub parallel: bool,
297 pub max_threads: Option<usize>,
299 pub memory_limit_mb: Option<usize>,
301 pub batch_size: usize,
303}
304
305impl Default for ProcessingConfig {
306 fn default() -> Self {
307 Self {
308 priority_rules: Vec::new(),
309 category_weights: crate::category::CategoryWeights::default(),
310 debug: false,
311 parallel: true,
312 max_threads: None,
313 memory_limit_mb: None,
314 batch_size: 1000,
315 }
316 }
317}
318
319#[derive(Debug, Clone, Default)]
321pub struct ProcessingStats {
322 pub files_processed: usize,
324 pub files_skipped: usize,
326 pub bytes_processed: usize,
328 pub tokens_processed: usize,
330 pub processing_time_ms: u128,
332 pub memory_usage_bytes: usize,
334 pub cache_hit_rate: f64,
336}
337
338impl ProcessingStats {
339 pub fn new() -> Self {
341 Self::default()
342 }
343
344 pub fn add_file(&mut self, file: &ProcessedFile, was_cached: bool) {
346 self.files_processed += 1;
347 self.bytes_processed += file.size_bytes;
348 if let Some(token_count) = file.token_count.get() {
349 self.tokens_processed += *token_count;
350 }
351 if was_cached {
352 }
355 }
356
357 pub fn add_skipped_file(&mut self, size_bytes: usize) {
359 self.files_skipped += 1;
360 self.bytes_processed += size_bytes;
361 }
362}