feat: Add --max-depth option and safe symlink handling (v1.2.0)
## Neue Features - **--max-depth N**: Begrenzt Rekursionstiefe auf N Ebenen (erfordert -r) - Nützlich für sehr tiefe Verzeichnisbäume (z.B. node_modules) - Verhindert unnötige Traversierung tiefer Strukturen - **Explizites Symlink-Handling**: - Standard: Symlinks werden komplett übersprungen (sicher) - Mit --special: Nur Symlink-Namen werden bereinigt, Ziel bleibt unangetastet - follow_links(false) explizit gesetzt zur Vermeidung von Endlosschleifen - Verhindert unbeabsichtigte Änderungen außerhalb des Zielverzeichnisses - **Verbose Symlink-Logging**: Zeigt mit -v welche Symlinks übersprungen werden ## Tests - 5 neue Integration-Tests hinzugefügt: - test_max_depth_option - test_max_depth_requires_recursive - test_symlinks_default_behavior (Unix only) - test_symlinks_with_special_flag (Unix only) - test_symlinks_not_followed (Unix only) - Alle 30 Tests bestehen (25 bestehende + 5 neue) ## Dokumentation - README.md: Neue Beispiele und "Symlink Behavior" Sektion - CHANGELOG.md: v1.2.0 Eintrag mit allen Änderungen - man/ntu.1: --max-depth Option und SYMLINK BEHAVIOR Sektion - CLAUDE.md: Aktualisierte Code-Architektur Dokumentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
cf091f4d4b
commit
b84dd70f80
8 changed files with 228 additions and 7 deletions
|
|
@ -389,3 +389,128 @@ fn test_sequence_default_explicit() {
|
|||
// Default: Umlaut → ASCII
|
||||
assert!(temp_dir.path().join("Mueller_File.txt").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_depth_option() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// Erstelle Verzeichnisstruktur mit 4 Ebenen
|
||||
// depth 0: temp_dir (root)
|
||||
// depth 1: level 1
|
||||
// depth 2: level 2
|
||||
// depth 3: level 3
|
||||
let level1 = temp_dir.path().join("level 1");
|
||||
let level2 = level1.join("level 2");
|
||||
let level3 = level2.join("level 3");
|
||||
fs::create_dir_all(&level3).unwrap();
|
||||
|
||||
fs::write(level1.join("file 1.txt"), "content1").unwrap();
|
||||
fs::write(level2.join("file 2.txt"), "content2").unwrap();
|
||||
fs::write(level3.join("file 3.txt"), "content3").unwrap();
|
||||
|
||||
let mut cmd = Command::new(cargo_bin!("ntu"));
|
||||
cmd.arg("-r")
|
||||
.arg("--max-depth")
|
||||
.arg("3") // Gehe bis depth 3
|
||||
.arg(temp_dir.path());
|
||||
cmd.assert().success();
|
||||
|
||||
// Level 1, 2 und deren Dateien sollten umbenannt sein
|
||||
assert!(temp_dir.path().join("level_1").exists());
|
||||
assert!(temp_dir.path().join("level_1/file_1.txt").exists());
|
||||
assert!(temp_dir.path().join("level_1/level_2").exists());
|
||||
assert!(temp_dir.path().join("level_1/level_2/file_2.txt").exists());
|
||||
assert!(temp_dir.path().join("level_1/level_2/level_3").exists());
|
||||
|
||||
// Level 3/file sollte NICHT umbenannt sein (depth 4 > max 3)
|
||||
assert!(temp_dir.path().join("level_1/level_2/level_3/file 3.txt").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_depth_requires_recursive() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// --max-depth ohne -r sollte fehlschlagen
|
||||
let mut cmd = Command::new(cargo_bin!("ntu"));
|
||||
cmd.arg("--max-depth")
|
||||
.arg("2")
|
||||
.arg(temp_dir.path());
|
||||
|
||||
let assert = cmd.assert().failure();
|
||||
// Prüfe dass die Fehlermeldung "required" oder "recursive" enthält
|
||||
assert.stderr(
|
||||
predicate::str::contains("required")
|
||||
.or(predicate::str::contains("recursive"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_symlinks_default_behavior() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("real file.txt");
|
||||
let link_path = temp_dir.path().join("link to file");
|
||||
|
||||
fs::write(&file_path, "content").unwrap();
|
||||
symlink(&file_path, &link_path).unwrap();
|
||||
|
||||
let mut cmd = Command::new(cargo_bin!("ntu"));
|
||||
cmd.arg(temp_dir.path());
|
||||
cmd.assert().success();
|
||||
|
||||
// Echte Datei sollte umbenannt sein
|
||||
assert!(temp_dir.path().join("real_file.txt").exists());
|
||||
|
||||
// Symlink sollte NICHT umbenannt sein (default behavior)
|
||||
// Verwende symlink_metadata() um zu prüfen ob der Link selbst existiert
|
||||
let link_unchanged = temp_dir.path().join("link to file");
|
||||
assert!(link_unchanged.symlink_metadata().is_ok());
|
||||
assert!(!temp_dir.path().join("link_to_file").symlink_metadata().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_symlinks_with_special_flag() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("real_file.txt");
|
||||
let link_path = temp_dir.path().join("link to file");
|
||||
|
||||
fs::write(&file_path, "content").unwrap();
|
||||
symlink(&file_path, &link_path).unwrap();
|
||||
|
||||
let mut cmd = Command::new(cargo_bin!("ntu"));
|
||||
cmd.arg("--special").arg(temp_dir.path());
|
||||
cmd.assert().success();
|
||||
|
||||
// Symlink sollte MIT --special umbenannt sein
|
||||
assert!(temp_dir.path().join("link_to_file").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_symlinks_not_followed() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let target_dir = TempDir::new().unwrap();
|
||||
let target_file = target_dir.path().join("target file.txt");
|
||||
fs::write(&target_file, "content").unwrap();
|
||||
|
||||
let link_path = temp_dir.path().join("link_to_dir");
|
||||
symlink(target_dir.path(), &link_path).unwrap();
|
||||
|
||||
let mut cmd = Command::new(cargo_bin!("ntu"));
|
||||
cmd.arg("-r").arg("--special").arg(temp_dir.path());
|
||||
cmd.assert().success();
|
||||
|
||||
// Symlink-Name sollte bereinigt sein
|
||||
assert!(temp_dir.path().join("link_to_dir").exists());
|
||||
|
||||
// Aber das Ziel sollte NICHT verändert sein (Link nicht gefolgt)
|
||||
assert!(target_dir.path().join("target file.txt").exists());
|
||||
assert!(!target_dir.path().join("target_file.txt").exists());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue