1use crate::store::AnnotationStore;
7use crate::types::ProjectRoot;
8use serde::Serialize;
9
10#[cfg(feature = "fs")]
11use crate::code_cache::glob_source_files;
12#[cfg(feature = "fs")]
13use crate::resolver::ResolverRegistry;
14
15#[derive(Debug, Clone, Serialize)]
17pub struct ProjectStats {
18 pub source_files: usize,
20 pub source_lines: usize,
22 pub source_bytes: usize,
24 pub source_tokens: usize,
26 pub annotation_files: usize,
28 pub annotation_count: usize,
30 pub annotation_tokens: usize,
32 pub compression_ratio: Option<f64>,
35}
36
37#[cfg(feature = "fs")]
42pub fn project_stats(
43 project_root: &ProjectRoot,
44 store: &AnnotationStore,
45 resolvers: &ResolverRegistry,
46) -> ProjectStats {
47 let source_files = glob_source_files(project_root, &crate::types::Scope::from(""), resolvers);
48 let file_count = source_files.len();
49
50 let mut total_lines: usize = 0;
51 let mut total_bytes: usize = 0;
52
53 for path in &source_files {
54 if let Ok(content) = std::fs::read_to_string(path) {
55 total_bytes += content.len();
56 total_lines += content.lines().count();
57 }
58 }
59
60 let source_tokens = total_bytes / 4;
61
62 let annotation_files = store.file_count();
63 let all_annotations = store.get_all_annotations();
64 let annotation_count = all_annotations.len();
65
66 let annotation_bytes = serde_json::to_string(&all_annotations)
68 .map(|s| s.len())
69 .unwrap_or(0);
70 let annotation_tokens = annotation_bytes / 4;
71
72 let compression_ratio = if annotation_tokens > 0 {
73 Some(source_tokens as f64 / annotation_tokens as f64)
74 } else {
75 None
76 };
77
78 ProjectStats {
79 source_files: file_count,
80 source_lines: total_lines,
81 source_bytes: total_bytes,
82 source_tokens,
83 annotation_files,
84 annotation_count,
85 annotation_tokens,
86 compression_ratio,
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use std::path::Path;
94
95 #[test]
96 fn stats_on_empty_store() {
97 let store = AnnotationStore::new(Path::new("/nonexistent"));
99 let resolvers = ResolverRegistry::with_defaults();
100 let root = ProjectRoot::from(Path::new("/nonexistent"));
101
102 let stats = project_stats(&root, &store, &resolvers);
104
105 assert_eq!(stats.source_files, 0, "no source files in nonexistent dir");
107 assert_eq!(stats.annotation_count, 0, "no annotations");
108 assert!(
109 stats.compression_ratio.is_none(),
110 "no ratio with zero annotations"
111 );
112 }
113}