// Verwende nun die Module mod cli; mod config; mod sanitizer; use anyhow::{Context, Result}; use clap::Parser; use cli::Cli; use config::Config; use glob::Pattern; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, info}; use rayon::prelude::*; use sanitizer::{clean_filename, is_excluded, is_safe_rename}; use std::fs; use std::path::PathBuf; use walkdir::WalkDir; // Standard-Ausschlussmuster (ähnlich wie detox) const DEFAULT_EXCLUDES: &[&str] = &[ ".git", ".git/**", ".svn", ".svn/**", "node_modules", "node_modules/**", ".cache", ".cache/**", "__pycache__", "__pycache__/**", ]; // Schwellwert für parallele Verarbeitung (bei weniger Dateien lohnt sich Overhead nicht) const PARALLEL_THRESHOLD: usize = 100; /// Repräsentiert eine geplante Umbenennungsoperation #[derive(Debug)] struct RenameOperation { old_path: PathBuf, new_path: PathBuf, } /// Startpunkt des Programms fn main() -> Result<()> { // Initialisiere Logger env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); // Argumente parsen let args = Cli::parse(); // Optional Konfigurationsdatei laden let config = Config::from_default_locations(args.verbose)?; // let config = Config::load(".NameToUnix.conf", args.verbose)?; // Ausschlussmuster (Glob-Patterns) vorbereiten - Default-Excludes + User-Excludes let mut all_excludes = DEFAULT_EXCLUDES .iter() .map(|s| s.to_string()) .collect::>(); all_excludes.extend(args.exclude.clone()); let exclude_patterns = all_excludes .iter() .map(|pattern| { Pattern::new(pattern) .with_context(|| format!("Ungültiges Ausschlussmuster: {}", pattern)) }) .collect::>>()?; if args.verbose && !exclude_patterns.is_empty() { debug!("Folgende Exclude-Pattern werden genutzt:"); for p in &exclude_patterns { debug!(" {}", p.as_str()); } } // Für alle angegebenen Pfade for path in &args.paths { // Alle Einträge sammeln, damit zuerst die tiefsten umbenannt werden let mut entries = Vec::new(); for entry_result in WalkDir::new(path) .into_iter() .filter_entry(|e| !is_excluded(e, &exclude_patterns)) { if let Ok(entry) = entry_result { entries.push(entry); } else if let Err(e) = entry_result { error!("Fehler beim Durchlaufen von {}: {}", path.display(), e); } } // Aufsteigend nach Tiefe sortieren, dann umkehren => tiefste Einträge zuerst entries.sort_by_key(|e| e.depth()); entries.reverse(); // Fortschrittsbalken bei größeren Dateimengen let progress_bar = if !args.quiet && entries.len() > 50 { let bar = ProgressBar::new(entries.len() as u64); bar.set_style(ProgressStyle::default_bar() .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}")? .progress_chars("#>-")); Some(bar) } else { None }; // Berechne Umbenennungen (parallel bei vielen Dateien) let rename_ops: Vec = if entries.len() >= PARALLEL_THRESHOLD { // Parallel mit rayon entries .par_iter() .filter_map(|entry| { let old_path = entry.path(); // Ebenentiefe 0 -> überspringen if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root { return None; } // Special Files nur mit --special let file_type = entry.file_type(); if !args.special && (!file_type.is_file() && !file_type.is_dir()) { return None; } // Dateiname ermitteln und bereinigen let filename = old_path.file_name()?; let new_name = clean_filename(filename, &config, false)?; let new_path = old_path.with_file_name(&new_name); Some(RenameOperation { old_path: old_path.to_path_buf(), new_path, }) }) .collect() } else { // Sequenziell bei wenigen Dateien entries .iter() .filter_map(|entry| { let old_path = entry.path(); if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root { return None; } let file_type = entry.file_type(); if !args.special && (!file_type.is_file() && !file_type.is_dir()) { return None; } let filename = old_path.file_name()?; let new_name = clean_filename(filename, &config, false)?; let new_path = old_path.with_file_name(&new_name); Some(RenameOperation { old_path: old_path.to_path_buf(), new_path, }) }) .collect() }; // Statistiken let total_processed = entries.len(); let total_planned = rename_ops.len(); let mut renamed_count = 0; let mut skipped_count = 0; // Umbenennungen sequenziell ausführen for op in rename_ops { if let Some(bar) = &progress_bar { bar.inc(1); } if !args.quiet { info!("{} -> {}", op.old_path.display(), op.new_path.display()); } if args.verbose { debug!("Rename: {:?} -> {:?}", op.old_path, op.new_path); } if !args.dry_run { if is_safe_rename(&op.old_path, &op.new_path, args.force) { match fs::rename(&op.old_path, &op.new_path) { Ok(_) => renamed_count += 1, Err(e) => { error!( "Fehler beim Umbenennen: {} -> {}: {}", op.old_path.display(), op.new_path.display(), e ); skipped_count += 1; } } } else { skipped_count += 1; } } } if let Some(bar) = &progress_bar { bar.finish_with_message("Verarbeitung abgeschlossen"); } // Zusammenfassung ausgeben (außer im quiet mode) if !args.quiet { info!(""); info!("=== Zusammenfassung für {} ===", path.display()); info!("Verarbeitete Dateien/Verzeichnisse: {}", total_processed); info!("Umbenennungen geplant: {}", total_planned); if args.dry_run { info!("Modus: Dry-run (keine Änderungen)"); } else { info!("Erfolgreich umbenannt: {}", renamed_count); if skipped_count > 0 { info!("Übersprungen/Fehler: {}", skipped_count); } } } } Ok(()) }