claude_native/scan/
file_stats.rs1use std::fs::File;
2use std::io::{BufRead, BufReader, Read};
3use std::path::Path;
4
5pub fn count_lines(path: &Path) -> usize {
7 let file = match File::open(path) {
8 Ok(f) => f,
9 Err(_) => return 0,
10 };
11 BufReader::new(file).lines().count()
12}
13
14pub fn longest_function(path: &Path) -> (usize, usize) {
17 let content = match read_file(path) {
18 Some(c) => c,
19 None => return (0, 0),
20 };
21 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
22 dispatch_by_language(ext, &content)
23}
24
25fn read_file(path: &Path) -> Option<String> {
26 let mut content = String::new();
27 let file = File::open(path).ok()?;
28 BufReader::new(file).read_to_string(&mut content).ok()?;
29 Some(content)
30}
31
32fn dispatch_by_language(ext: &str, content: &str) -> (usize, usize) {
33 match ext {
34 "rs" => longest_brace_fn(content, &["fn "]),
35 "go" => longest_brace_fn(content, &["func "]),
36 "ts" | "tsx" | "js" | "jsx" | "mjs" => {
37 longest_brace_fn(content, &["function ", "=> {", "async "])
38 }
39 "java" | "kt" | "kts" | "cs" | "dart" | "swift" => {
40 longest_brace_fn(content, &["fun ", "func ", "void ", "int ", "string "])
41 }
42 "py" => longest_indented_fn(content),
43 "rb" => longest_ruby_fn(content),
44 _ => (0, 0),
45 }
46}
47
48fn longest_brace_fn(content: &str, markers: &[&str]) -> (usize, usize) {
50 let lines: Vec<&str> = content.lines().collect();
51 let mut max_len = 0;
52 let mut fn_count = 0;
53
54 for (i, line) in lines.iter().enumerate() {
55 if !is_fn_start(line, markers) {
56 continue;
57 }
58 fn_count += 1;
59 let len = count_brace_block(&lines, i);
60 if len > max_len {
61 max_len = len;
62 }
63 }
64 (max_len, fn_count)
65}
66
67fn is_fn_start(line: &str, markers: &[&str]) -> bool {
68 let trimmed = line.trim();
69 markers.iter().any(|m| trimmed.contains(m))
70 && (trimmed.contains('(') || trimmed.contains('{'))
71}
72
73fn count_brace_block(lines: &[&str], start: usize) -> usize {
74 let initial_depth = brace_depth_at(lines, start);
75 let mut depth = initial_depth;
76 let mut fn_lines = 0;
77 let mut found_open = false;
78
79 for j in start..lines.len() {
80 for ch in non_string_chars(lines[j]) {
81 if ch == '{' { depth += 1; found_open = true; }
82 else if ch == '}' { depth -= 1; }
83 }
84 fn_lines += 1;
85 if found_open && depth <= initial_depth { break; }
86 }
87 fn_lines
88}
89
90fn brace_depth_at(lines: &[&str], line: usize) -> i32 {
91 let mut depth = 0i32;
92 for j in 0..line {
93 for ch in non_string_chars(lines[j]) {
94 if ch == '{' { depth += 1; }
95 else if ch == '}' { depth -= 1; }
96 }
97 }
98 depth
99}
100
101fn non_string_chars(line: &str) -> Vec<char> {
103 let mut result = Vec::new();
104 let chars: Vec<char> = line.chars().collect();
105 let mut i = 0;
106 while i < chars.len() {
107 let ch = chars[i];
108 if ch == '"' {
109 i += 1;
111 while i < chars.len() {
112 if chars[i] == '"' && (i == 0 || chars[i - 1] != '\\') { break; }
113 i += 1;
114 }
115 } else if ch == '\'' && i + 2 < chars.len() {
116 if chars.get(i + 2) == Some(&'\'') || chars.get(i + 3) == Some(&'\'') {
118 i += if chars.get(i + 1) == Some(&'\\') { 3 } else { 2 };
120 } else {
121 result.push(ch);
122 }
123 } else {
124 result.push(ch);
125 }
126 i += 1;
127 }
128 result
129}
130
131fn longest_indented_fn(content: &str) -> (usize, usize) {
133 let lines: Vec<&str> = content.lines().collect();
134 let mut max_len = 0;
135 let mut fn_count = 0;
136 let mut i = 0;
137
138 while i < lines.len() {
139 let trimmed = lines[i].trim();
140 if !trimmed.starts_with("def ") && !trimmed.starts_with("async def ") {
141 i += 1;
142 continue;
143 }
144 fn_count += 1;
145 let len = count_indent_block(&lines, i);
146 if len > max_len { max_len = len; }
147 i += len;
148 }
149 (max_len, fn_count)
150}
151
152fn count_indent_block(lines: &[&str], start: usize) -> usize {
153 let base_indent = lines[start].len() - lines[start].trim_start().len();
154 let mut fn_lines = 1;
155 let mut j = start + 1;
156 while j < lines.len() {
157 let line = lines[j];
158 if line.trim().is_empty() { fn_lines += 1; j += 1; continue; }
159 let indent = line.len() - line.trim_start().len();
160 if indent <= base_indent { break; }
161 fn_lines += 1;
162 j += 1;
163 }
164 fn_lines
165}
166
167fn longest_ruby_fn(content: &str) -> (usize, usize) {
169 let lines: Vec<&str> = content.lines().collect();
170 let mut max_len = 0;
171 let mut fn_count = 0;
172
173 for (i, line) in lines.iter().enumerate() {
174 if !line.trim().starts_with("def ") { continue; }
175 fn_count += 1;
176 let len = count_ruby_def(&lines, i);
177 if len > max_len { max_len = len; }
178 }
179 (max_len, fn_count)
180}
181
182fn count_ruby_def(lines: &[&str], start: usize) -> usize {
183 let base_indent = lines[start].len() - lines[start].trim_start().len();
184 let mut fn_lines = 1;
185 for j in (start + 1)..lines.len() {
186 fn_lines += 1;
187 let indent = lines[j].len() - lines[j].trim_start().len();
188 if lines[j].trim() == "end" && indent == base_indent { break; }
189 }
190 fn_lines
191}