diff --git a/CHANGELOG.md b/CHANGELOG.md index 404f33c..7f440fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.0] - 2025-02-10 + +### ⚠️ BREAKING CHANGES +- **Recursion is now opt-in**: By default, `ntu` only processes the specified + paths and their immediate children. Use `-r`/`--recursive` flag to enable + recursive directory traversal. +- Migration: Add `-r` to existing commands to preserve v0.x behavior + +### Added +- **`--conf ` option**: Specify a single configuration file, bypassing + the default hierarchy (/etc → ~/.config → ./). Errors if file doesn't exist. +- **`-r/--recursive` flag**: Enable recursive directory processing + +### Changed +- Default behavior is now non-recursive (use `-r` for recursive processing) +- All integration tests updated to explicitly use `-r` flag + +### Migration Guide +```bash +# Old command (v0.x - always recursive): +ntu /path/to/files + +# New equivalent (v1.x - explicit recursion): +ntu -r /path/to/files +``` + ## [0.3.0] - 2025-02-10 ### Added diff --git a/Cargo.lock b/Cargo.lock index 89d358a..8818daf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "NameToUnix" -version = "0.3.0" +version = "1.0.0" dependencies = [ "anyhow", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index 00a8366..4e5d9b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "NameToUnix" -version = "0.3.0" +version = "1.0.0" edition = "2021" authors = ["Dieter Schlüter "] description = "Ein Tool zum Anpassen von Verzeichnis- und Dateinamen an Linux-Konventionen" diff --git a/README.md b/README.md index c1ce457..c186904 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,18 @@ Dies ist mein erstes Programm in Rust. (Bitte seid gnädig.) (c) 2025 Dieter Schlüter +## ⚠️ BREAKING CHANGE in v1.0.0 + +**Recursion is now opt-in**: As of version 1.0.0, `ntu` processes only the +specified paths and their immediate children by default. Use the `-r` or +`--recursive` flag to enable recursive directory traversal. + +**Migration**: Add `-r` to your existing commands: +```bash +# Old (v0.x): ntu /path/to/files +# New (v1.x): ntu -r /path/to/files +``` + ## Functions / Funktionen - Replaces spaces and special characters in file and directory names with underscores @@ -103,30 +115,36 @@ sudo mandb # Update man database ## Usage ```bash -# Basic usage +# Basic usage (non-recursive: only immediate children) ntu /path/to/files +# Recursive processing (process subdirectories) +ntu -r /path/to/files + # Dry-run: only preview the changes without actual renaming -ntu --dry-run /path/to/files -ntu -n /path/to/files # Short form +ntu --dry-run -r /path/to/files +ntu -n -r /path/to/files # Short form + +# Use specific config file +ntu --conf /path/to/custom.toml /path/to/files # Process multiple paths -ntu /path1 /path2 /path3 +ntu -r /path1 /path2 /path3 # Exclude specific files -ntu -e "*.tmp" -e "backup_*" /path/to/files +ntu -r -e "*.tmp" -e "backup_*" /path/to/files # Process symlinks and special files (normally skipped) -ntu --special /path/to/files +ntu -r --special /path/to/files # Increase verbosity -ntu -v /path/to/files +ntu -r -v /path/to/files # Also rename the root directory -ntu --modify-root /path/to/files +ntu -r --modify-root /path/to/files # Combine options -ntu --dry-run -v --special /path/to/files +ntu --dry-run -r -v --special /path/to/files ``` @@ -136,30 +154,36 @@ ntu --dry-run -v --special /path/to/files ## Verwendung ```bash -# Grundlegende Verwendung +# Grundlegende Verwendung (nicht-rekursiv: nur direkte Kinder) ntu /pfad/zu/dateien +# Rekursive Verarbeitung (Unterverzeichnisse verarbeiten) +ntu -r /pfad/zu/dateien + # Dry-run: Nur Vorschau der Änderungen ohne tatsächliche Umbenennung -ntu --dry-run /pfad/zu/dateien -ntu -n /pfad/zu/dateien # Kurzform +ntu --dry-run -r /pfad/zu/dateien +ntu -n -r /pfad/zu/dateien # Kurzform + +# Spezifische Config-Datei verwenden +ntu --conf /pfad/zu/custom.toml /pfad/zu/dateien # Mehrere Pfade verarbeiten -ntu /pfad1 /pfad2 /pfad3 +ntu -r /pfad1 /pfad2 /pfad3 # Bestimmte Dateien ausschließen -ntu -e "*.tmp" -e "backup_*" /pfad/zu/dateien +ntu -r -e "*.tmp" -e "backup_*" /pfad/zu/dateien # Symlinks und Special Files verarbeiten (normalerweise übersprungen) -ntu --special /pfad/zu/dateien +ntu -r --special /pfad/zu/dateien # Verbosity erhöhen -ntu -v /pfad/zu/dateien +ntu -r -v /pfad/zu/dateien # Auch das Wurzelverzeichnis umbenennen -ntu --modify-root /pfad/zu/dateien +ntu -r --modify-root /pfad/zu/dateien # Optionen kombinieren -ntu --dry-run -v --special /pfad/zu/dateien +ntu --dry-run -r -v --special /pfad/zu/dateien ``` diff --git a/completions/_ntu b/completions/_ntu index 47fc572..3722c77 100644 --- a/completions/_ntu +++ b/completions/_ntu @@ -7,6 +7,8 @@ _ntu() { typeset -A opt_args _arguments -C \ + '(-r --recursive)'{-r,--recursive}'[Process directories recursively]' \ + '--conf[Use specific configuration file]:config file:_files' \ '(-n --dry-run --no-changes)'{-n,--dry-run,--no-changes}'[Only preview changes without renaming]' \ '(-q --quiet)'{-q,--quiet}'[Suppress output]' \ '(-f --force)'{-f,--force}'[Overwrite existing files]' \ @@ -14,6 +16,7 @@ _ntu() { '(-v --verbose)'{-v,--verbose}'[Verbose debug output]' \ '--modify-root[Also rename root directory]' \ '--special[Process symlinks and special files]' \ + '--no-color[Disable colored output]' \ '(-h --help)'{-h,--help}'[Print help]' \ '(-V --version)'{-V,--version}'[Print version]' \ '*:path:_files' diff --git a/completions/ntu.bash b/completions/ntu.bash index 8ee4093..7cc0926 100644 --- a/completions/ntu.bash +++ b/completions/ntu.bash @@ -7,7 +7,7 @@ _ntu_completion() { prev="${COMP_WORDS[COMP_CWORD-1]}" # All available options - opts="--dry-run --no-changes --quiet --force --exclude --verbose --modify-root --special --help --version -n -q -f -e -v -h -V" + opts="--recursive --conf --dry-run --no-changes --quiet --force --exclude --verbose --modify-root --special --no-color --help --version -r -n -q -f -e -v -h -V" # Handle options that require arguments case "${prev}" in @@ -16,6 +16,11 @@ _ntu_completion() { COMPREPLY=( $(compgen -W '"*.tmp" "*.log" "*.bak" "*.swp" "*~"' -- ${cur}) ) return 0 ;; + --conf) + # Suggest files for config option + COMPREPLY=( $(compgen -f -- ${cur}) ) + return 0 + ;; *) ;; esac diff --git a/completions/ntu.fish b/completions/ntu.fish index f1c898f..d8f08fd 100644 --- a/completions/ntu.fish +++ b/completions/ntu.fish @@ -4,6 +4,8 @@ complete -c ntu -f -d 'Sanitize file and directory names to Unix conventions' # Options +complete -c ntu -s r -l recursive -d 'Process directories recursively' +complete -c ntu -l conf -d 'Use specific configuration file' -r -F complete -c ntu -s q -l quiet -d 'Suppress output (no rename information)' complete -c ntu -s n -l dry-run -d 'Show what would be renamed without making changes' complete -c ntu -l no-changes -d 'Alias for --dry-run' diff --git a/index.md b/index.md index b758693..6b8c90b 100644 --- a/index.md +++ b/index.md @@ -1,10 +1,10 @@ # Willkommen bei meinem Projekt -NameToUnix - Rust Command line tool for cleaning up directory & file names according to Linux conventions. Recursively replacing offending characters or spaces. +NameToUnix - Rust Command line tool for cleaning up directory & file names according to Linux conventions. Replacing offending characters or spaces (recursive with `-r` flag). Dieses Projekt bietet unter Linux eine einfache Lösung für das automatische Umbenennen von Verzeichnissen und Dateien nach dem Entpacken von gezippten Windows-Dateien mit Leerzeichen oder Sonderzeichen im Namen. ## Zielsetzung -Das Ziel ist es, diese unkonventionellen Dateinamen rekursiv und automatisch sinnvoll umzubenennen. +Das Ziel ist es, diese unkonventionellen Dateinamen automatisch sinnvoll umzubenennen. Ab v1.0.0 ist die rekursive Verarbeitung opt-in (`-r` Flag erforderlich). ## Inhaltsverzeichnis diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..2d969a4 --- /dev/null +++ b/install.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Installation script for ntu (NameToUnix) + +set -e + +echo "=== NameToUnix Installation ===" +echo + +# Check if binary exists +if [ ! -f "target/release/ntu" ]; then + echo "Error: Binary not found. Please run 'cargo build --release' first." + exit 1 +fi + +# Install binary +echo "Installing binary..." +sudo cp target/release/ntu /usr/local/bin/ +sudo chmod 755 /usr/local/bin/ntu +echo "✓ Binary installed to /usr/local/bin/ntu" + +# Install man page +echo "Installing man page..." +sudo cp man/ntu.1 /usr/share/man/man1/ +sudo chmod 644 /usr/share/man/man1/ntu.1 +sudo mandb -q +echo "✓ Man page installed to /usr/share/man/man1/ntu.1" + +# Install config (user-specific) +echo "Installing configuration..." +mkdir -p ~/.config/NameToUnix/ +cp .NameToUnix.conf ~/.config/NameToUnix/config.toml +echo "✓ Config installed to ~/.config/NameToUnix/config.toml" + +# Install shell completions +echo "Installing shell completions..." + +# Bash +if [ -d "/etc/bash_completion.d" ]; then + sudo cp completions/ntu.bash /etc/bash_completion.d/ntu + echo "✓ Bash completion installed" +fi + +# Zsh +if [ -d "/usr/share/zsh/site-functions" ]; then + sudo cp completions/_ntu /usr/share/zsh/site-functions/_ntu + echo "✓ Zsh completion installed" +fi + +# Fish +if [ -d "/usr/share/fish/vendor_completions.d" ]; then + sudo cp completions/ntu.fish /usr/share/fish/vendor_completions.d/ntu.fish + echo "✓ Fish completion installed" +fi + +echo +echo "=== Installation complete ===" +echo +echo "Test the installation:" +echo " ntu --version" +echo " man ntu" +echo +echo "For shell completions to work, restart your shell or run:" +echo " source ~/.bashrc (Bash)" +echo " source ~/.zshrc (Zsh)" diff --git a/man/ntu.1 b/man/ntu.1 index f45b0fb..4bd38d2 100644 --- a/man/ntu.1 +++ b/man/ntu.1 @@ -1,4 +1,4 @@ -.TH NTU 1 "2025-02-10" "NameToUnix 0.3.0" "User Commands" +.TH NTU 1 "2025-02-10" "NameToUnix 1.0.0" "User Commands" .SH NAME ntu \- sanitize file and directory names to Unix conventions .SH SYNOPSIS @@ -11,11 +11,19 @@ to make them compatible with Unix/Linux naming conventions. It replaces spaces with underscores, converts German umlauts to their ASCII equivalents, and removes or replaces problematic special characters. .PP -The tool processes files recursively, starting from the deepest level to -avoid conflicts with parent directory renames. It preserves file extensions, -including double extensions like .tar.gz, and handles hidden files correctly. +By default, the tool processes only the specified paths and their immediate +children. Use the \fB\-r\fR flag to enable recursive processing of subdirectories. +When recursive, it starts from the deepest level to avoid conflicts with parent +directory renames. It preserves file extensions, including double extensions +like .tar.gz, and handles hidden files correctly. .SH OPTIONS .TP +.BR \-r ", " \-\-recursive +Process directories recursively (default: only immediate children) +.TP +.BR \-\-conf " \fIFILE\fR" +Use a specific configuration file instead of the default hierarchy +.TP .BR \-q ", " \-\-quiet Suppress output (no rename information on stdout) .TP diff --git a/src/cli.rs b/src/cli.rs index 377a694..9bba884 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -13,6 +13,14 @@ pub struct Cli { /// Pfade (Dateien und Verzeichnisse) zum rekursiven Anpassen pub paths: Vec, + /// Explizite Konfigurationsdatei (bypassed Standard-Hierarchie) + #[clap(long = "conf", value_name = "FILE")] + pub config_file: Option, + + /// Rekursive Verarbeitung von Unterverzeichnissen aktivieren + #[clap(short = 'r', long)] + pub recursive: bool, + /// Ausgaben unterdrücken (keine Umbenennungsinfos auf stdout) #[clap(short, long)] pub quiet: bool, diff --git a/src/config.rs b/src/config.rs index 6ab6534..2e5c3f7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -57,6 +57,19 @@ impl Config { } } } + + /// Lädt Konfiguration aus spezifischer Datei (fehlschlägt bei nicht-existierender Datei) + pub fn from_file(path: &Path, verbose: bool) -> Result { + if !path.exists() { + return Err(anyhow::anyhow!( + "Konfigurationsdatei nicht gefunden: {}", + path.display() + )); + } + + Self::load_internal(path, verbose) + } + /// Sucht nach Konfigurationsdateien in verschiedenen Orten und kombiniert sie pub fn from_default_locations(verbose: bool) -> Result { // Prioritätenreihenfolge (später überschreibt früher): diff --git a/src/main.rs b/src/main.rs index b0af0b3..f1a68a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,9 +60,13 @@ fn main() -> Result<()> { colored::control::set_override(false); } - // Optional Konfigurationsdatei laden - let config = Config::from_default_locations(args.verbose)?; - // let config = Config::load(".NameToUnix.conf", args.verbose)?; + // Config-Datei laden: entweder --conf oder Standard-Hierarchie + let config = if let Some(config_path) = &args.config_file { + Config::from_file(config_path, args.verbose) + .with_context(|| format!("Fehler beim Laden der Konfiguration: {}", config_path.display()))? + } else { + Config::from_default_locations(args.verbose)? + }; // Ausschlussmuster (Glob-Patterns) vorbereiten - Default-Excludes + User-Excludes let mut all_excludes = DEFAULT_EXCLUDES @@ -90,7 +94,16 @@ fn main() -> Result<()> { 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) + + let walker = if args.recursive { + // Recursive: unbegrenzte Tiefe + WalkDir::new(path) + } else { + // Non-recursive: max_depth(1) verarbeitet nur direkte Kinder + WalkDir::new(path).max_depth(1) + }; + + for entry_result in walker .into_iter() .filter_entry(|e| !is_excluded(e, &exclude_patterns)) { diff --git a/target/.rustc_info.json b/target/.rustc_info.json index f020545..6ad3766 100644 --- a/target/.rustc_info.json +++ b/target/.rustc_info.json @@ -1 +1 @@ -{"rustc_fingerprint":18315677830234863098,"outputs":{"8185672236408668984":{"success":true,"status":"","code":0,"stdout":"rustc 1.90.0 (1159e78c4 2025-09-14)\nbinary: rustc\ncommit-hash: 1159e78c4747b02ef996e55082b704c09b970588\ncommit-date: 2025-09-14\nhost: x86_64-unknown-linux-gnu\nrelease: 1.90.0\nLLVM version: 20.1.8\n","stderr":""},"11742744481059712885":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/dschlueter/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file +{"rustc_fingerprint":4740973386762217857,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.90.0 (1159e78c4 2025-09-14)\nbinary: rustc\ncommit-hash: 1159e78c4747b02ef996e55082b704c09b970588\ncommit-date: 2025-09-14\nhost: x86_64-unknown-linux-gnu\nrelease: 1.90.0\nLLVM version: 20.1.8\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/dschlueter/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e22ecf6..0262e4b 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -30,7 +30,7 @@ fn test_dry_run_no_changes() { fs::write(&file_path, "test content").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg("--dry-run").arg(temp_dir.path()); + cmd.arg("--dry-run").arg("-r").arg(temp_dir.path()); cmd.assert().success(); // File should still have spaces (not renamed) @@ -45,7 +45,7 @@ fn test_actual_rename() { fs::write(&file_path, "test content").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg(temp_dir.path()); + cmd.arg("-r").arg(temp_dir.path()); cmd.assert().success(); // File should be renamed @@ -60,7 +60,7 @@ fn test_hidden_files_preserved() { fs::write(&file_path, "*.tmp").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg(temp_dir.path()); + cmd.arg("-r").arg(temp_dir.path()); cmd.assert().success(); // Hidden file should not be renamed @@ -74,7 +74,7 @@ fn test_hidden_file_with_spaces() { fs::write(&file_path, "config content").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg(temp_dir.path()); + cmd.arg("-r").arg(temp_dir.path()); cmd.assert().success(); // Hidden file should be renamed but keep leading dot @@ -89,7 +89,7 @@ fn test_umlaut_conversion() { fs::write(&file_path, "test").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg(temp_dir.path()); + cmd.arg("-r").arg(temp_dir.path()); cmd.assert().success(); // Umlaut should be converted @@ -104,7 +104,7 @@ fn test_double_extension() { fs::write(&file_path, "archive").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg(temp_dir.path()); + cmd.arg("-r").arg(temp_dir.path()); cmd.assert().success(); // Should keep .tar.gz intact @@ -123,6 +123,7 @@ fn test_exclude_pattern() { let mut cmd = Command::new(cargo_bin!("ntu")); cmd.arg("--exclude") .arg("*.tmp") + .arg("-r") .arg(temp_dir.path()); cmd.assert().success(); @@ -139,7 +140,7 @@ fn test_quiet_mode() { fs::write(&file_path, "test").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg("--quiet").arg(temp_dir.path()); + cmd.arg("--quiet").arg("-r").arg(temp_dir.path()); cmd.assert() .success() .stdout(predicate::str::is_empty()); @@ -155,7 +156,7 @@ fn test_multiple_paths() { fs::write(&file2, "test2").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg(temp_dir1.path()).arg(temp_dir2.path()); + cmd.arg("-r").arg(temp_dir1.path()).arg(temp_dir2.path()); cmd.assert().success(); // Both files should be renamed @@ -170,7 +171,7 @@ fn test_parentheses_removed() { fs::write(&file_path, "test").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg(temp_dir.path()); + cmd.arg("-r").arg(temp_dir.path()); cmd.assert().success(); // Parentheses should be replaced with underscores @@ -185,10 +186,74 @@ fn test_special_identifiers_preserved() { fs::write(&file_path, "test").unwrap(); let mut cmd = Command::new(cargo_bin!("ntu")); - cmd.arg(temp_dir.path()); + cmd.arg("-r").arg(temp_dir.path()); cmd.assert().success(); // C++ should be preserved assert!(!file_path.exists()); assert!(temp_dir.path().join("C++_Guide.pdf").exists()); } + +#[test] +fn test_non_recursive_default() { + let temp_dir = TempDir::new().unwrap(); + fs::create_dir(temp_dir.path().join("subdir")).unwrap(); + let file1 = temp_dir.path().join("top file.txt"); + let file2 = temp_dir.path().join("subdir").join("nested file.txt"); + fs::write(&file1, "test1").unwrap(); + fs::write(&file2, "test2").unwrap(); + + let mut cmd = Command::new(cargo_bin!("ntu")); + cmd.arg(temp_dir.path()); + cmd.assert().success(); + + // Top-Level umbenannt + assert!(temp_dir.path().join("top_file.txt").exists()); + // Nested NICHT umbenannt (non-recursive) + assert!(file2.exists()); +} + +#[test] +fn test_recursive_flag() { + let temp_dir = TempDir::new().unwrap(); + fs::create_dir(temp_dir.path().join("subdir")).unwrap(); + let file1 = temp_dir.path().join("top file.txt"); + let file2 = temp_dir.path().join("subdir").join("nested file.txt"); + fs::write(&file1, "test1").unwrap(); + fs::write(&file2, "test2").unwrap(); + + let mut cmd = Command::new(cargo_bin!("ntu")); + cmd.arg("-r").arg(temp_dir.path()); + cmd.assert().success(); + + // Beide umbenannt + assert!(temp_dir.path().join("top_file.txt").exists()); + assert!(temp_dir.path().join("subdir").join("nested_file.txt").exists()); +} + +#[test] +fn test_conf_option_valid_file() { + let temp_dir = TempDir::new().unwrap(); + let config_file = temp_dir.path().join("custom.toml"); + fs::write(&config_file, "[replacements]\n\"test\" = \"xyz\"").unwrap(); + let file_path = temp_dir.path().join("test_file.txt"); + fs::write(&file_path, "content").unwrap(); + + let mut cmd = Command::new(cargo_bin!("ntu")); + cmd.arg("--conf").arg(&config_file).arg(temp_dir.path()); + cmd.assert().success(); + + assert!(temp_dir.path().join("xyz_file.txt").exists()); +} + +#[test] +fn test_conf_option_missing_file() { + let temp_dir = TempDir::new().unwrap(); + let nonexistent = temp_dir.path().join("nonexistent.toml"); + + let mut cmd = Command::new(cargo_bin!("ntu")); + cmd.arg("--conf").arg(&nonexistent).arg(temp_dir.path()); + cmd.assert() + .failure() + .stderr(predicate::str::contains("nicht gefunden")); +}