Implementiere farbige Terminal-Ausgabe

- 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 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-02-10 13:39:30 +01:00
commit 0f61e0fbd9
5 changed files with 75 additions and 27 deletions

17
Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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,
}

View file

@ -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());
}
}
}

View file

@ -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();