style: Fix rustfmt formatting issues

This commit is contained in:
2025-12-29 00:54:29 +05:30
parent d25dcaa0de
commit 06eff6fcf8
13 changed files with 214 additions and 268 deletions

View File

@@ -46,18 +46,16 @@ impl Cache {
pub fn load_or_create(cache_path: &Path) -> Self {
if cache_path.exists() {
match fs::read_to_string(cache_path) {
Ok(content) => {
match serde_json::from_str::<Cache>(&content) {
Ok(cache) => {
println!("Loaded cache with {} entries", cache.entries.len());
cache
}
Err(_) => {
println!("Cache corrupted, creating new cache");
Self::new()
}
Ok(content) => match serde_json::from_str::<Cache>(&content) {
Ok(cache) => {
println!("Loaded cache with {} entries", cache.entries.len());
cache
}
}
Err(_) => {
println!("Cache corrupted, creating new cache");
Self::new()
}
},
Err(_) => {
println!("Failed to read cache, creating new cache");
Self::new()
@@ -73,13 +71,17 @@ impl Cache {
if let Some(parent) = cache_path.parent() {
fs::create_dir_all(parent)?;
}
let content = serde_json::to_string_pretty(self)?;
fs::write(cache_path, content)?;
Ok(())
}
pub fn get_cached_response(&self, filenames: &[String], base_path: &Path) -> Option<OrganizationPlan> {
pub fn get_cached_response(
&self,
filenames: &[String],
base_path: &Path,
) -> Option<OrganizationPlan> {
let cache_key = self.generate_cache_key(filenames);
if let Some(entry) = self.entries.get(&cache_key) {
@@ -163,10 +165,7 @@ impl Cache {
fn get_file_metadata(file_path: &Path) -> Result<FileMetadata, Box<dyn std::error::Error>> {
let metadata = fs::metadata(file_path)?;
let modified = metadata
.modified()?
.duration_since(UNIX_EPOCH)?
.as_secs();
let modified = metadata.modified()?.duration_since(UNIX_EPOCH)?.as_secs();
Ok(FileMetadata {
size: metadata.len(),
@@ -182,9 +181,8 @@ impl Cache {
let initial_count = self.entries.len();
self.entries.retain(|_, entry| {
current_time - entry.timestamp < max_age_seconds
});
self.entries
.retain(|_, entry| current_time - entry.timestamp < max_age_seconds);
let removed_count = initial_count - self.entries.len();
if removed_count > 0 {

View File

@@ -76,7 +76,8 @@ fn test_cache_response_file_changed() {
std::thread::sleep(std::time::Duration::from_millis(100));
let mut file = File::create(&file_path).unwrap();
file.write_all(b"modified content longer than original").unwrap();
file.write_all(b"modified content longer than original")
.unwrap();
let cached = cache.get_cached_response(&filenames, base_path);
assert!(cached.is_none());

View File

@@ -74,8 +74,9 @@ impl Config {
pub fn get_or_prompt_api_key() -> Result<String, Box<dyn std::error::Error>> {
if let Ok(config) = Config::load()
&& !config.api_key.is_empty() {
return Ok(config.api_key);
&& !config.api_key.is_empty()
{
return Ok(config.api_key);
}
println!();
@@ -94,8 +95,10 @@ pub fn get_or_prompt_api_key() -> Result<String, Box<dyn std::error::Error>> {
pub fn get_or_prompt_download_folder() -> Result<PathBuf, Box<dyn std::error::Error>> {
if let Ok(config) = Config::load()
&& !config.download_folder.as_os_str().is_empty() && config.download_folder.exists() {
return Ok(config.download_folder);
&& !config.download_folder.as_os_str().is_empty()
&& config.download_folder.exists()
{
return Ok(config.download_folder);
}
println!();

View File

@@ -18,30 +18,42 @@ fn test_config_serialization() {
#[test]
fn test_validate_api_key_valid() {
assert!(crate::prompt::Prompter::validate_api_key("AIzaSyB1234567890123456789012345678"));
assert!(crate::prompt::Prompter::validate_api_key("AIzaSyB123456789012345678901234567890"));
assert!(crate::prompt::Prompter::validate_api_key(
"AIzaSyB1234567890123456789012345678"
));
assert!(crate::prompt::Prompter::validate_api_key(
"AIzaSyB123456789012345678901234567890"
));
}
#[test]
fn test_validate_api_key_invalid() {
assert!(!crate::prompt::Prompter::validate_api_key(""));
assert!(!crate::prompt::Prompter::validate_api_key("invalid_key"));
assert!(!crate::prompt::Prompter::validate_api_key("BizaSyB1234567890123456789012345678"));
assert!(!crate::prompt::Prompter::validate_api_key(
"BizaSyB1234567890123456789012345678"
));
assert!(!crate::prompt::Prompter::validate_api_key("short"));
}
#[test]
fn test_validate_folder_path_valid() {
let temp_dir = tempfile::tempdir().unwrap();
assert!(crate::prompt::Prompter::validate_folder_path(temp_dir.path()));
assert!(crate::prompt::Prompter::validate_folder_path(
temp_dir.path()
));
}
#[test]
fn test_validate_folder_path_invalid() {
assert!(!crate::prompt::Prompter::validate_folder_path(Path::new("/nonexistent/path/that/does/not/exist")));
assert!(!crate::prompt::Prompter::validate_folder_path(Path::new(
"/nonexistent/path/that/does/not/exist"
)));
let temp_file = tempfile::NamedTempFile::new().unwrap();
assert!(!crate::prompt::Prompter::validate_folder_path(temp_file.path()));
assert!(!crate::prompt::Prompter::validate_folder_path(
temp_file.path()
));
}
#[test]

View File

@@ -39,10 +39,11 @@ impl FileBatch {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file()
&& let Ok(relative_path) = path.strip_prefix(&root_path) {
filenames.push(relative_path.to_string_lossy().into_owned());
paths.push(path);
}
&& let Ok(relative_path) = path.strip_prefix(&root_path)
{
filenames.push(relative_path.to_string_lossy().into_owned());
paths.push(path);
}
}
FileBatch { filenames, paths }
@@ -91,10 +92,7 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
eprint!("\nDo you want to apply these changes? [y/N]: ");
let mut input = String::new();
if io::stdin()
.read_line(&mut input)
.is_err()
{
if io::stdin().read_line(&mut input).is_err() {
eprintln!("\n{}", "Failed to read input. Operation cancelled.".red());
return;
}
@@ -144,11 +142,7 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
match move_file_cross_platform(&source, &target) {
Ok(_) => {
if item.sub_category.is_empty() {
println!(
"Moved: {} -> {}/",
item.filename,
item.category.green()
);
println!("Moved: {} -> {}/", item.filename, item.category.green());
} else {
println!(
"Moved: {} -> {}/{}",
@@ -187,52 +181,19 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
moved_count.to_string().green(),
error_count.to_string().red()
);
} pub fn is_text_file(path: &Path) -> bool {
}
pub fn is_text_file(path: &Path) -> bool {
let text_extensions = [
"txt",
"md",
"rs",
"py",
"js",
"ts",
"jsx",
"tsx",
"html",
"css",
"json",
"xml",
"csv",
"yaml",
"yml",
"toml",
"ini",
"cfg",
"conf",
"log",
"sh",
"bat",
"ps1",
"sql",
"c",
"cpp",
"h",
"hpp",
"java",
"go",
"rb",
"php",
"swift",
"kt",
"scala",
"lua",
"r",
"m",
"txt", "md", "rs", "py", "js", "ts", "jsx", "tsx", "html", "css", "json", "xml", "csv",
"yaml", "yml", "toml", "ini", "cfg", "conf", "log", "sh", "bat", "ps1", "sql", "c", "cpp",
"h", "hpp", "java", "go", "rb", "php", "swift", "kt", "scala", "lua", "r", "m",
];
if let Some(ext) = path.extension()
&& let Some(ext_str) = ext.to_str() {
return text_extensions.contains(&ext_str.to_lowercase().as_str());
}
&& let Some(ext_str) = ext.to_str()
{
return text_extensions.contains(&ext_str.to_lowercase().as_str());
}
false
}

View File

@@ -65,7 +65,8 @@ fn test_read_file_sample_with_limit() {
let file_path = temp_dir.path().join("test.txt");
let mut file = File::create(&file_path).unwrap();
file.write_all(b"Hello, World! This is a long text.").unwrap();
file.write_all(b"Hello, World! This is a long text.")
.unwrap();
let content = read_file_sample(&file_path, 5);
assert_eq!(content, Some("Hello".to_string()));
@@ -92,13 +93,11 @@ fn test_read_file_sample_nonexistent() {
#[test]
fn test_organization_plan_serialization() {
let plan = OrganizationPlan {
files: vec![
FileCategory {
filename: "test.txt".to_string(),
category: "Documents".to_string(),
sub_category: "Text".to_string(),
},
],
files: vec![FileCategory {
filename: "test.txt".to_string(),
category: "Documents".to_string(),
sub_category: "Text".to_string(),
}],
};
let json = serde_json::to_string(&plan).unwrap();

View File

@@ -1,8 +1,8 @@
use crate::cache::Cache;
use crate::files::OrganizationPlan;
use crate::gemini_errors::GeminiError;
use crate::gemini_helpers::PromptBuilder;
use crate::gemini_types::{GeminiResponse, OrganizationPlanResponse};
use crate::files::OrganizationPlan;
use reqwest::Client;
use serde_json::json;
use std::path::Path;
@@ -71,8 +71,9 @@ impl GeminiClient {
let url = self.build_url();
if let (Some(cache), Some(base_path)) = (cache.as_ref(), base_path)
&& let Some(cached_response) = cache.get_cached_response(&filenames, base_path) {
return Ok(cached_response);
&& let Some(cached_response) = cache.get_cached_response(&filenames, base_path)
{
return Ok(cached_response);
}
let prompt = PromptBuilder::new(filenames.clone()).build_categorization_prompt();
@@ -107,8 +108,8 @@ impl GeminiClient {
return Err(GeminiError::from_response(res).await);
}
let gemini_response: GeminiResponse = res.json().await
.map_err(GeminiError::NetworkError)?;
let gemini_response: GeminiResponse =
res.json().await.map_err(GeminiError::NetworkError)?;
let raw_text = self.extract_text_from_response(&gemini_response)?;
let plan_response: OrganizationPlanResponse = serde_json::from_str(&raw_text)?;
@@ -116,10 +117,7 @@ impl GeminiClient {
Ok(plan_response.to_organization_plan())
}
fn extract_text_from_response(
&self,
response: &GeminiResponse,
) -> Result<String, GeminiError> {
fn extract_text_from_response(&self, response: &GeminiResponse) -> Result<String, GeminiError> {
response
.candidates
.first()
@@ -244,7 +242,11 @@ impl GeminiClient {
self.extract_subcategory_from_response(&gemini_response, filename)
}
fn extract_subcategory_from_response(&self, response: &GeminiResponse, _filename: &str) -> String {
fn extract_subcategory_from_response(
&self,
response: &GeminiResponse,
_filename: &str,
) -> String {
match self.extract_text_from_response(response) {
Ok(text) => {
let sub_category = text.trim();

View File

@@ -93,24 +93,27 @@ impl GeminiError {
fn from_gemini_error(error_detail: GeminiErrorDetail, status: u16) -> Self {
let details = error_detail.details;
match error_detail.status.as_str() {
"RESOURCE_EXHAUSTED" => {
if let Some(retry_info) = details.iter().find(|d| d.retry_delay.is_some())
&& let Some(retry_delay) = &retry_info.retry_delay
&& let Ok(seconds) = retry_delay.parse::<u32>() {
return GeminiError::RateLimitExceeded { retry_after: seconds };
}
if let Some(quota_info) = details.iter().find(|d| d.quota_limit.is_some()) {
let limit = quota_info.quota_limit.as_deref().unwrap_or("unknown");
return GeminiError::QuotaExceeded {
limit: limit.to_string()
&& let Ok(seconds) = retry_delay.parse::<u32>()
{
return GeminiError::RateLimitExceeded {
retry_after: seconds,
};
}
GeminiError::QuotaExceeded {
limit: "usage limit".to_string()
if let Some(quota_info) = details.iter().find(|d| d.quota_limit.is_some()) {
let limit = quota_info.quota_limit.as_deref().unwrap_or("unknown");
return GeminiError::QuotaExceeded {
limit: limit.to_string(),
};
}
GeminiError::QuotaExceeded {
limit: "usage limit".to_string(),
}
}
"NOT_FOUND" => {
@@ -118,69 +121,57 @@ impl GeminiError {
let model = extract_model_name(&error_detail.message);
GeminiError::ModelNotFound { model }
}
"UNAUTHENTICATED" => {
GeminiError::InvalidApiKey
}
"UNAUTHENTICATED" => GeminiError::InvalidApiKey,
"PERMISSION_DENIED" => {
if error_detail.message.to_lowercase().contains("policy") {
GeminiError::ContentPolicyViolation {
reason: error_detail.message
GeminiError::ContentPolicyViolation {
reason: error_detail.message,
}
} else {
GeminiError::InvalidRequest {
details: error_detail.message
GeminiError::InvalidRequest {
details: error_detail.message,
}
}
}
"INVALID_ARGUMENT" => {
GeminiError::InvalidRequest {
details: error_detail.message
}
}
"UNAVAILABLE" => {
GeminiError::ServiceUnavailable {
reason: error_detail.message
}
}
"DEADLINE_EXCEEDED" => {
GeminiError::Timeout { seconds: 60 }
}
"INTERNAL" => {
GeminiError::InternalError {
details: error_detail.message
}
}
_ => {
GeminiError::ApiError {
status,
message: error_detail.message
}
}
"INVALID_ARGUMENT" => GeminiError::InvalidRequest {
details: error_detail.message,
},
"UNAVAILABLE" => GeminiError::ServiceUnavailable {
reason: error_detail.message,
},
"DEADLINE_EXCEEDED" => GeminiError::Timeout { seconds: 60 },
"INTERNAL" => GeminiError::InternalError {
details: error_detail.message,
},
_ => GeminiError::ApiError {
status,
message: error_detail.message,
},
}
}
fn from_status_code(status: reqwest::StatusCode, error_text: &str) -> Self {
match status.as_u16() {
400 => GeminiError::InvalidRequest {
details: error_text.to_string()
400 => GeminiError::InvalidRequest {
details: error_text.to_string(),
},
401 => GeminiError::InvalidApiKey,
403 => GeminiError::ContentPolicyViolation {
reason: error_text.to_string()
403 => GeminiError::ContentPolicyViolation {
reason: error_text.to_string(),
},
404 => GeminiError::ModelNotFound {
model: "unknown".to_string()
404 => GeminiError::ModelNotFound {
model: "unknown".to_string(),
},
429 => GeminiError::RateLimitExceeded { retry_after: 60 },
500 => GeminiError::InternalError {
details: error_text.to_string()
500 => GeminiError::InternalError {
details: error_text.to_string(),
},
502..=504 => GeminiError::ServiceUnavailable {
reason: error_text.to_string()
502..=504 => GeminiError::ServiceUnavailable {
reason: error_text.to_string(),
},
_ => GeminiError::ApiError {
status: status.as_u16(),
message: error_text.to_string()
_ => GeminiError::ApiError {
status: status.as_u16(),
message: error_text.to_string(),
},
}
}
@@ -216,8 +207,9 @@ fn extract_model_name(message: &str) -> String {
// Try to extract model name from error message
// Example: "Model 'gemini-1.5-flash' not found"
if let Some(start) = message.find('\'')
&& let Some(end) = message[start + 1..].find('\'') {
return message[start + 1..start + 1 + end].to_string();
}
&& let Some(end) = message[start + 1..].find('\'')
{
return message[start + 1..start + 1 + end].to_string();
}
"unknown".to_string()
}
}

View File

@@ -60,7 +60,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
};
println!("{}", "Gemini Plan received! Performing deep inspection...".green());
println!(
"{}",
"Gemini Plan received! Performing deep inspection...".green()
);
let client = Arc::new(client);
let semaphore = Arc::new(tokio::sync::Semaphore::new(args.max_concurrent));
@@ -81,7 +84,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _permit = semaphore.acquire().await.unwrap();
if let Some(content) = noentropy::files::read_file_sample(&path, 5000) {
println!("Reading content of {}...", filename.green());
client.get_ai_sub_category(&filename, &category, &content).await
client
.get_ai_sub_category(&filename, &category, &content)
.await
} else {
String::new()
}
@@ -101,10 +106,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("{}", "Deep inspection complete! Moving Files.....".green());
if args.dry_run {
println!(
"{} Dry run mode - skipping file moves.",
"INFO:".cyan()
);
println!("{} Dry run mode - skipping file moves.", "INFO:".cyan());
} else {
execute_move(&download_path, plan);
}
@@ -119,62 +121,77 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
fn handle_gemini_error(error: GeminiError) {
use colored::*;
match error {
GeminiError::RateLimitExceeded { retry_after } => {
println!("{} API rate limit exceeded. Please wait {} seconds before trying again.",
"ERROR:".red(), retry_after);
println!(
"{} API rate limit exceeded. Please wait {} seconds before trying again.",
"ERROR:".red(),
retry_after
);
}
GeminiError::QuotaExceeded { limit } => {
println!("{} Quota exceeded: {}. Please check your Gemini API usage.",
"ERROR:".red(), limit);
println!(
"{} Quota exceeded: {}. Please check your Gemini API usage.",
"ERROR:".red(),
limit
);
}
GeminiError::ModelNotFound { model } => {
println!("{} Model '{}' not found. Please check the model name in the configuration.",
"ERROR:".red(), model);
println!(
"{} Model '{}' not found. Please check the model name in the configuration.",
"ERROR:".red(),
model
);
}
GeminiError::InvalidApiKey => {
println!("{} Invalid API key. Please check your GEMINI_API_KEY environment variable.",
"ERROR:".red());
println!(
"{} Invalid API key. Please check your GEMINI_API_KEY environment variable.",
"ERROR:".red()
);
}
GeminiError::ContentPolicyViolation { reason } => {
println!("{} Content policy violation: {}",
"ERROR:".red(), reason);
println!("{} Content policy violation: {}", "ERROR:".red(), reason);
}
GeminiError::ServiceUnavailable { reason } => {
println!("{} Gemini service is temporarily unavailable: {}",
"ERROR:".red(), reason);
println!(
"{} Gemini service is temporarily unavailable: {}",
"ERROR:".red(),
reason
);
}
GeminiError::NetworkError(e) => {
println!("{} Network error: {}",
"ERROR:".red(), e);
println!("{} Network error: {}", "ERROR:".red(), e);
}
GeminiError::Timeout { seconds } => {
println!("{} Request timed out after {} seconds.",
"ERROR:".red(), seconds);
println!(
"{} Request timed out after {} seconds.",
"ERROR:".red(),
seconds
);
}
GeminiError::InvalidRequest { details } => {
println!("{} Invalid request: {}",
"ERROR:".red(), details);
println!("{} Invalid request: {}", "ERROR:".red(), details);
}
GeminiError::ApiError { status, message } => {
println!("{} API error (HTTP {}): {}",
"ERROR:".red(), status, message);
println!(
"{} API error (HTTP {}): {}",
"ERROR:".red(),
status,
message
);
}
GeminiError::InvalidResponse(msg) => {
println!("{} Invalid response from Gemini: {}",
"ERROR:".red(), msg);
println!("{} Invalid response from Gemini: {}", "ERROR:".red(), msg);
}
GeminiError::InternalError { details } => {
println!("{} Internal server error: {}",
"ERROR:".red(), details);
println!("{} Internal server error: {}", "ERROR:".red(), details);
}
GeminiError::SerializationError(e) => {
println!("{} JSON serialization error: {}",
"ERROR:".red(), e);
println!("{} JSON serialization error: {}", "ERROR:".red(), e);
}
}
println!("\n{} Check the following:", "HINT:".yellow());
println!(" • Your GEMINI_API_KEY is correctly set");
println!(" • Your internet connection is working");

View File

@@ -10,7 +10,10 @@ pub struct Prompter;
impl Prompter {
pub fn prompt_api_key() -> Result<String, Box<dyn std::error::Error>> {
println!();
println!("Get your API key at: {}", "https://ai.google.dev/".cyan().underline());
println!(
"Get your API key at: {}",
"https://ai.google.dev/".cyan().underline()
);
println!("Enter your API Key (starts with 'AIza'):");
let mut attempts = 0;
@@ -29,7 +32,10 @@ impl Prompter {
}
attempts += 1;
Self::print_validation_error("Invalid API key format. Must start with 'AIza' and be around 39 characters.", attempts);
Self::print_validation_error(
"Invalid API key format. Must start with 'AIza' and be around 39 characters.",
attempts,
);
}
Err("Max retries exceeded. Please run again with a valid API key.".into())
@@ -102,10 +108,7 @@ impl Prompter {
}
pub fn validate_api_key(key: &str) -> bool {
!key.is_empty()
&& key.starts_with("AIza")
&& key.len() >= 35
&& key.len() <= 50
!key.is_empty() && key.starts_with("AIza") && key.len() >= 35 && key.len() <= 50
}
pub fn validate_folder_path(path: &Path) -> bool {
@@ -120,9 +123,10 @@ impl Prompter {
pub fn expand_home(path: &str) -> String {
if path.starts_with("~/")
&& let Some(base_dirs) = BaseDirs::new() {
let home = base_dirs.home_dir();
return path.replacen("~", &home.to_string_lossy(), 1);
&& let Some(base_dirs) = BaseDirs::new()
{
let home = base_dirs.home_dir();
return path.replacen("~", &home.to_string_lossy(), 1);
}
path.to_string()
}