Implement sequences feature v1.1.0

- Add -s/--sequence option to select transformation sequences
- Add -L flag to list all available sequences
- Implement 5 hardcoded sequences: default, lower, upper, minimal, utf-8
- Refactor clean_filename() to support sequence-based transformations
- Update all tests to pass sequence parameter (25 tests passing)
- Add 8 new integration tests for sequence functionality
- Update documentation (README, CHANGELOG, manpage)
- Update shell completions (bash, zsh, fish)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-02-10 18:38:23 +01:00
commit 2ec4d12d6c
12 changed files with 501 additions and 52 deletions

View file

@ -12,7 +12,7 @@ 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 sanitizer::{clean_filename, is_excluded, is_safe_rename, Sequence};
use std::fs;
use std::io::IsTerminal;
use std::path::PathBuf;
@ -60,6 +60,28 @@ fn main() -> Result<()> {
colored::control::set_override(false);
}
// -L Option: Liste Sequences und beende
if args.list_sequences {
list_sequences(&args);
return Ok(());
}
// Sequence auswählen
let sequence = if let Some(seq_name) = &args.sequence {
Sequence::find(seq_name).ok_or_else(|| {
anyhow::anyhow!(
"Unbekannte Sequence: '{}'. Nutze -L um verfügbare Sequences anzuzeigen.",
seq_name
)
})?
} else {
Sequence::default()
};
if args.verbose {
info!("Verwende Sequence: {}", sequence.name);
}
// 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)
@ -150,7 +172,7 @@ fn main() -> Result<()> {
// Dateiname ermitteln und bereinigen
let filename = old_path.file_name()?;
let new_name = clean_filename(filename, &config, false)?;
let new_name = clean_filename(filename, &config, &sequence, false)?;
let new_path = old_path.with_file_name(&new_name);
Some(RenameOperation {
@ -176,7 +198,7 @@ fn main() -> Result<()> {
}
let filename = old_path.file_name()?;
let new_name = clean_filename(filename, &config, false)?;
let new_name = clean_filename(filename, &config, &sequence, false)?;
let new_path = old_path.with_file_name(&new_name);
Some(RenameOperation {
@ -264,3 +286,41 @@ fn main() -> Result<()> {
Ok(())
}
/// Listet alle verfügbaren Sequences auf
fn list_sequences(args: &Cli) {
println!("Verfügbare Sequences:");
println!();
for seq in Sequence::all() {
println!(" {}", seq.name.bold());
if args.verbose {
println!(" Description: {}", seq.description);
println!(
" Umlauts → ASCII: {}",
if seq.apply_umlauts { "yes" } else { "no" }
);
println!(" Case transform: {:?}", seq.apply_case);
println!(
" Emoji handling: {}",
if seq.apply_emojis {
"replace"
} else {
"keep"
}
);
println!(
" Mode: {}",
if seq.minimal_mode { "minimal" } else { "full" }
);
} else {
println!(" {}", seq.description);
}
println!();
}
if !args.verbose {
println!("Nutze -L -v für detaillierte Informationen über jede Sequence.");
}
}