perf: eliminate unnecessary clones and improve API ergonomics

- PromptBuilder::new now takes &[String] instead of Vec<String>
- GeminiClient::new now takes &str, &[String] instead of owned values
- FileBatch::from_path now takes &Path instead of PathBuf
- categorize_files_offline now takes Vec<String> (ownership) instead of &[String]
- handle_offline_organization now takes FileBatch by value

These changes eliminate ~5-50 KB of unnecessary allocations for typical
file counts, reduce allocator pressure, and improve API clarity by properly
expressing ownership semantics.

No functional changes - all tests pass.
This commit is contained in:
2026-01-08 23:42:10 +05:30
parent eeb07983cb
commit ba0ea3f221
8 changed files with 32 additions and 25 deletions

View File

@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
#[derive(Debug)]
@@ -8,7 +8,7 @@ pub struct FileBatch {
}
impl FileBatch {
pub fn from_path(root_path: PathBuf, recursive: bool) -> Self {
pub fn from_path(root_path: &Path, recursive: bool) -> Self {
let mut filenames = Vec::new();
let mut paths = Vec::new();
let walker = if recursive {
@@ -45,6 +45,7 @@ impl FileBatch {
mod tests {
use super::*;
use std::fs::{self, File};
use std::path::Path;
#[test]
fn test_file_batch_from_path() {
@@ -55,7 +56,7 @@ mod tests {
File::create(dir_path.join("file2.rs")).unwrap();
fs::create_dir(dir_path.join("subdir")).unwrap();
let batch = FileBatch::from_path(dir_path.to_path_buf(), false);
let batch = FileBatch::from_path(dir_path, false);
assert_eq!(batch.count(), 2);
assert!(batch.filenames.contains(&"file1.txt".to_string()));
assert!(batch.filenames.contains(&"file2.rs".to_string()));
@@ -63,7 +64,7 @@ mod tests {
#[test]
fn test_file_batch_from_path_nonexistent() {
let batch = FileBatch::from_path(PathBuf::from("/nonexistent/path"), false);
let batch = FileBatch::from_path(Path::new("/nonexistent/path"), false);
assert_eq!(batch.count(), 0);
}
@@ -75,7 +76,7 @@ mod tests {
File::create(dir_path.join("file2.rs")).unwrap();
fs::create_dir(dir_path.join("subdir")).unwrap();
File::create(dir_path.join("subdir").join("file3.txt")).unwrap();
let batch = FileBatch::from_path(dir_path.to_path_buf(), false);
let batch = FileBatch::from_path(dir_path, false);
assert_eq!(batch.count(), 2);
assert!(batch.filenames.contains(&"file1.txt".to_string()));
assert!(batch.filenames.contains(&"file2.rs".to_string()));
@@ -93,7 +94,7 @@ mod tests {
File::create(dir_path.join("subdir1").join("nested").join("file3.md")).unwrap();
fs::create_dir(dir_path.join("subdir2")).unwrap();
File::create(dir_path.join("subdir2").join("file4.py")).unwrap();
let batch = FileBatch::from_path(dir_path.to_path_buf(), true);
let batch = FileBatch::from_path(dir_path, true);
assert_eq!(batch.count(), 4);
assert!(batch.filenames.contains(&"file1.txt".to_string()));
assert!(batch.filenames.contains(&"subdir1/file2.rs".to_string()));

View File

@@ -120,21 +120,21 @@ pub struct OfflineCategorizationResult {
/// Categorizes a list of filenames using extension-based rules.
/// Returns categorized files and a list of skipped filenames.
pub fn categorize_files_offline(filenames: &[String]) -> OfflineCategorizationResult {
pub fn categorize_files_offline(filenames: Vec<String>) -> OfflineCategorizationResult {
let mut files = Vec::with_capacity(filenames.len());
let mut skipped = Vec::new();
for filename in filenames {
match categorize_by_extension(filename) {
match categorize_by_extension(&filename) {
Some(category) => {
files.push(FileCategory {
filename: filename.clone(),
filename,
category: category.to_string(),
sub_category: String::new(),
});
}
None => {
skipped.push(filename.clone());
skipped.push(filename);
}
}
}
@@ -187,7 +187,7 @@ mod tests {
"file.xyz".to_string(),
];
let result = categorize_files_offline(&filenames);
let result = categorize_files_offline(filenames);
assert_eq!(result.plan.files.len(), 2);
assert_eq!(result.skipped.len(), 2);