From 0f61e0fbd95d2ce18280c9f56796d7a82a9adcdf Mon Sep 17 00:00:00 2001 From: dschlueter Date: Tue, 10 Feb 2026 13:39:30 +0100 Subject: [PATCH] Implementiere farbige Terminal-Ausgabe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fügt colored Crate hinzu für bessere visuelle Unterscheidung - Grün: erfolgreiche Umbenennungen - Gelb: Dry-run Modus - Rot: Fehlermeldungen - Cyan/Bold: Statistik-Zusammenfassung - Neues --no-color Flag zum Deaktivieren - Automatische Farberkennung via is_terminal() - Behebt ungenutzten warn Import - Aktualisiert Integration-Tests auf neues cargo_bin! Makro Co-Authored-By: Claude Sonnet 4.5 --- Cargo.lock | 17 +++++++++++++ Cargo.toml | 1 + src/cli.rs | 4 +++ src/main.rs | 52 ++++++++++++++++++++++++++++---------- tests/integration_tests.rs | 28 ++++++++++---------- 5 files changed, 75 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38641d5..42e7407 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "assert_cmd", "assert_fs", "clap", + "colored", "dirs", "emojis", "env_logger", @@ -205,6 +206,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + [[package]] name = "console" version = "0.15.11" @@ -486,6 +497,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.170" diff --git a/Cargo.toml b/Cargo.toml index 71041b5..eeedcfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ indicatif = "0.17.7" # Fortschrittsbalken env_logger = "0.11.2" # Logging-Framework log = "0.4.21" # Logging-Abstraktionen itertools = "0.12.1" # Erweiterte Iterator-Funktionalität +colored = "2.1" # Farbige Terminal-Ausgabe [dev-dependencies] tempfile = "3.10.1" # Temporäre Dateien für Tests diff --git a/src/cli.rs b/src/cli.rs index 1b2cb1b..377a694 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -40,4 +40,8 @@ pub struct Cli { /// Auch symbolische Links und Special Files verarbeiten #[clap(long)] pub special: bool, + + /// Deaktiviert farbige Ausgabe + #[clap(long)] + pub no_color: bool, } diff --git a/src/main.rs b/src/main.rs index 443cce8..b0af0b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod sanitizer; use anyhow::{Context, Result}; use clap::Parser; use cli::Cli; +use colored::*; use config::Config; use glob::Pattern; use indicatif::{ProgressBar, ProgressStyle}; @@ -13,6 +14,7 @@ use log::{debug, error, info}; use rayon::prelude::*; use sanitizer::{clean_filename, is_excluded, is_safe_rename}; use std::fs; +use std::io::IsTerminal; use std::path::PathBuf; use walkdir::WalkDir; @@ -40,6 +42,11 @@ struct RenameOperation { new_path: PathBuf, } +/// Prüft ob farbige Ausgabe aktiviert sein soll +fn should_use_color(no_color_flag: bool) -> bool { + !no_color_flag && std::io::stdout().is_terminal() +} + /// Startpunkt des Programms fn main() -> Result<()> { // Initialisiere Logger @@ -48,6 +55,11 @@ fn main() -> Result<()> { // Argumente parsen let args = Cli::parse(); + // Farben konfigurieren + if !should_use_color(args.no_color) { + colored::control::set_override(false); + } + // Optional Konfigurationsdatei laden let config = Config::from_default_locations(args.verbose)?; // let config = Config::load(".NameToUnix.conf", args.verbose)?; @@ -85,7 +97,7 @@ fn main() -> Result<()> { if let Ok(entry) = entry_result { entries.push(entry); } else if let Err(e) = entry_result { - error!("Fehler beim Durchlaufen von {}: {}", path.display(), e); + error!("{}", format!("Fehler beim Durchlaufen von {}: {}", path.display(), e).red()); } } @@ -175,7 +187,19 @@ fn main() -> Result<()> { } if !args.quiet { - info!("{} -> {}", op.old_path.display(), op.new_path.display()); + if args.dry_run { + info!("{} {} {}", + op.old_path.display().to_string().dimmed(), + "->".yellow(), + op.new_path.display().to_string().yellow() + ); + } else { + info!("{} {} {}", + op.old_path.display().to_string().dimmed(), + "->".green(), + op.new_path.display().to_string().green() + ); + } } if args.verbose { @@ -187,11 +211,13 @@ fn main() -> Result<()> { 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 + error!("{}", + format!( + "Fehler beim Umbenennen: {} -> {}: {}", + op.old_path.display(), + op.new_path.display(), + e + ).red() ); skipped_count += 1; } @@ -209,15 +235,15 @@ fn main() -> Result<()> { // 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); + info!("{}", format!("=== Zusammenfassung für {} ===", path.display()).cyan().bold()); + info!("Verarbeitete Dateien/Verzeichnisse: {}", total_processed.to_string().cyan()); + info!("Umbenennungen geplant: {}", total_planned.to_string().cyan()); if args.dry_run { - info!("Modus: Dry-run (keine Änderungen)"); + info!("Modus: {}", "Dry-run (keine Änderungen)".yellow()); } else { - info!("Erfolgreich umbenannt: {}", renamed_count); + info!("Erfolgreich umbenannt: {}", renamed_count.to_string().green().bold()); if skipped_count > 0 { - info!("Übersprungen/Fehler: {}", skipped_count); + info!("Übersprungen/Fehler: {}", skipped_count.to_string().red()); } } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 58dba45..e22ecf6 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,5 +1,5 @@ use assert_cmd::assert::OutputAssertExt; -use assert_cmd::cargo::CommandCargoExt; +use assert_cmd::cargo::cargo_bin; use predicates::prelude::*; use std::fs; use std::process::Command; @@ -7,7 +7,7 @@ use tempfile::TempDir; #[test] fn test_help_flag() { - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg("--help"); cmd.assert() .success() @@ -16,7 +16,7 @@ fn test_help_flag() { #[test] fn test_version_flag() { - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg("--version"); cmd.assert() .success() @@ -29,7 +29,7 @@ fn test_dry_run_no_changes() { let file_path = temp_dir.path().join("test file.txt"); fs::write(&file_path, "test content").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg("--dry-run").arg(temp_dir.path()); cmd.assert().success(); @@ -44,7 +44,7 @@ fn test_actual_rename() { let file_path = temp_dir.path().join("test file.txt"); fs::write(&file_path, "test content").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg(temp_dir.path()); cmd.assert().success(); @@ -59,7 +59,7 @@ fn test_hidden_files_preserved() { let file_path = temp_dir.path().join(".gitignore"); fs::write(&file_path, "*.tmp").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg(temp_dir.path()); cmd.assert().success(); @@ -73,7 +73,7 @@ fn test_hidden_file_with_spaces() { let file_path = temp_dir.path().join(".my config"); fs::write(&file_path, "config content").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg(temp_dir.path()); cmd.assert().success(); @@ -88,7 +88,7 @@ fn test_umlaut_conversion() { let file_path = temp_dir.path().join("Müller.txt"); fs::write(&file_path, "test").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg(temp_dir.path()); cmd.assert().success(); @@ -103,7 +103,7 @@ fn test_double_extension() { let file_path = temp_dir.path().join("my archive.tar.gz"); fs::write(&file_path, "archive").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg(temp_dir.path()); cmd.assert().success(); @@ -120,7 +120,7 @@ fn test_exclude_pattern() { fs::write(&file1, "test1").unwrap(); fs::write(&file2, "test2").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg("--exclude") .arg("*.tmp") .arg(temp_dir.path()); @@ -138,7 +138,7 @@ fn test_quiet_mode() { let file_path = temp_dir.path().join("test file.txt"); fs::write(&file_path, "test").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg("--quiet").arg(temp_dir.path()); cmd.assert() .success() @@ -154,7 +154,7 @@ fn test_multiple_paths() { fs::write(&file1, "test1").unwrap(); fs::write(&file2, "test2").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg(temp_dir1.path()).arg(temp_dir2.path()); cmd.assert().success(); @@ -169,7 +169,7 @@ fn test_parentheses_removed() { let file_path = temp_dir.path().join("Document (1).txt"); fs::write(&file_path, "test").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg(temp_dir.path()); cmd.assert().success(); @@ -184,7 +184,7 @@ fn test_special_identifiers_preserved() { let file_path = temp_dir.path().join("C++ Guide.pdf"); fs::write(&file_path, "test").unwrap(); - let mut cmd = Command::cargo_bin("ntu").unwrap(); + let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg(temp_dir.path()); cmd.assert().success();