Compare commits

...

18 commits

Author SHA1 Message Date
92a3d66645 docs: Version 1.2.1, Dokumentation und Man-Page aktualisieren
- Versionsnummer auf 1.2.1 hochgesetzt
- Man-Page: NO_COLOR Umgebungsvariable dokumentiert (ENVIRONMENT-Abschnitt)
- Man-Page: Konfigurationsreihenfolge mit Prioritäten korrigiert
- Man-Page: --no-color Beschreibung um NO_COLOR-Referenz ergänzt
- README: Hilfe-Ausgabe aktualisiert (fehlende Optionen: -r, --max-depth,
  -s, -L, --conf, --no-color)
- README: NO_COLOR Hinweis in deutsch und englisch ergänzt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 04:41:19 +01:00
a72dae1a60 fix: Fortschrittsbalken zeigt jetzt korrekte Gesamtzahl
Progress-Bar basiert nun auf der Anzahl geplanter Umbenennungen
statt auf der Gesamtzahl aller Einträge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 04:36:31 +01:00
bd82fd7b30 fix: Cargo.lock tracken, verbose durchreichen, Config-Fehler melden, Platzhalter-Kollision beheben
- Cargo.lock aus .gitignore entfernt (Rust-Konvention: für Binaries committen)
- verbose-Parameter in clean_filename() wird jetzt korrekt von args.verbose
  durchgereicht statt hardcoded false
- Config::load() gibt bei Parse-Fehlern eine Warnung aus statt den Fehler
  still zu schlucken
- Platzhalter für C++/C# von CPLUSPLUS/CSHARP zu NTUxCPLUSPLUSx/NTUxCSHARPx
  geändert um Kollisionen mit echten Dateinamen zu vermeiden

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 04:32:59 +01:00
de9bb4fd03 refactor: Code-Qualität und Usability verbessern
- Ungenutzte Dependencies entfernt (itertools, thiserror)
- 560 Build-Artefakte (target/) aus Git-Tracking entfernt
- NO_COLOR Umgebungsvariable unterstützen (https://no-color.org/)
- if let Ok/Err durch idiomatisches match ersetzt
- Fehlermeldung bei Aufruf ohne Pfade hinzugefügt
- Sequence::default() zu Sequence::standard() umbenannt
  um Verwechslung mit dem Rust Default-Trait zu vermeiden

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 04:26:47 +01:00
b08ff38a49 perf: Parallelverarbeitung wiederherstellen (2.4-2.9x Speedup)
rayon's par_iter() auf indexierten Collections bewahrt die Reihenfolge,
daher war die Entfernung im vorherigen Commit unnötig. Mapping-Logik
in Closure extrahiert um Code-Duplizierung zu vermeiden.

Benchmark (dry-run, quiet):
  5.050 Einträge: 48ms seq → 20ms par (2.4x)
  20.050 Einträge: 186ms seq → 63ms par (2.9x)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 04:21:31 +01:00
ad44139e21 fix: Race Condition bei paralleler Umbenennung, Clippy/Deprecation-Warnings und Formatierung
- Parallele Berechnung (par_iter) entfernt, da sie die tiefenbasierte
  Sortierung zerstörte und Parent-Verzeichnisse vor ihren Kindern
  umbenannt werden konnten
- Duplizierten Code zwischen parallelem und sequentiellem Pfad entfernt
- Clippy-Warning behoben: collapsible str::replace in sanitizer.rs
- Deprecation-Warning behoben: #[allow(deprecated)] für cargo_bin Import
- cargo fmt angewendet

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 04:08:23 +01:00
0c26e5244c Version 1.2.0 produktionsreif 2026-02-12 14:23:44 +01:00
2eab4e7592 build: Add config and shell completions to .deb package assets
- Config-Beispiel: /usr/share/doc/NameToUnix/config.toml.example
- Bash-Completion: /etc/bash_completion.d/ntu
- Zsh-Completion: /usr/share/zsh/vendor-completions/_ntu
- Fish-Completion: /usr/share/fish/vendor_completions.d/ntu.fish

Package Size: 868 KB
Installed Size: ~3 MB
2026-02-12 14:06:53 +01:00
b84dd70f80 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>
2026-02-12 13:39:41 +01:00
cf091f4d4b chore: Migrate from GitHub to Forgejo
- Update repository URL to kitux.de/forgejo/dschlueter/ntu
- Replace GitHub badges with simple repository link
- Update email addresses: dieter.schlueter@linix.de -> dschlueter@kitux.de
- Update all installation and clone instructions
- Update bug report and documentation URLs in manpage
- Mark GitHub Actions workflows as legacy
2026-02-12 13:06:45 +01:00
2ec4d12d6c 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>
2026-02-10 18:38:23 +01:00
d78e318d8a neue Optionen (-r, Installskript) installiert 2026-02-10 15:38:53 +01:00
0bab728d62 Bump Version auf 0.3.0
Neue Features seit 0.2.0:
- CI/CD Pipeline mit automatischen Builds
- Shell-Completions für Bash, Zsh und Fish
- Professionelle Manpage
- Farbige Terminal-Ausgabe
- 13 Integration-Tests

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 13:42:24 +01:00
e872cefe54 Füge Fish Shell Completion hinzu
- Vollständige Completion für Fish Shell (completions/ntu.fish)
- Unterstützt alle Flags und Optionen
- Path-Completion für Verzeichnisse und Dateien
- README um Fish-Installation erweitert

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 13:41:17 +01:00
e4c8b9e780 Füge professionelle Manpage hinzu
- Vollständige groff-formatierte Manpage (man/ntu.1)
- Dokumentiert alle Optionen, Flags und Transformationen
- Beispiele für typische Anwendungsfälle
- Sicherheitshinweise und Best Practices
- Beschreibt Default-Excludes und Parallelverarbeitung
- Konfigurationsdatei-Hierarchie dokumentiert
- Manpage in Debian-Paket-Assets integriert
- README um Installationsanweisung für Manpage erweitert

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 13:40:48 +01:00
0f61e0fbd9 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>
2026-02-10 13:39:30 +01:00
b9f8e9592e test: 13 Integration-Tests hinzugefügt + .gitignore optimiert
Integration-Tests:
- test_help_flag, test_version_flag
- test_dry_run_no_changes, test_actual_rename
- test_hidden_files_preserved, test_hidden_file_with_spaces
- test_umlaut_conversion, test_double_extension
- test_exclude_pattern, test_quiet_mode
- test_multiple_paths, test_parentheses_removed
- test_special_identifiers_preserved

.gitignore:
- Besser organisiert (Kategorien)
- Cargo.lock hinzugefügt
- Temporäre Dateien (*.tmp, *.log, *.bak)
- OS-spezifische Dateien (Thumbs.db)
- Test-Artefakte

Gesamt: 22 Tests (9 Unit + 13 Integration)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 10:41:57 +01:00
53c10f1913 feat: CI/CD, Bash-Completion und README-Verbesserungen
- GitHub Actions CI Pipeline (Tests, Clippy, Format-Checks)
- GitHub Actions Release Pipeline (Multi-Platform Builds)
- Bash-Completion Script für Shell-Autovervollständigung
- Zsh-Completion Script (_ntu)
- README Badges (CI, Release, Version, License)
- Installationsanleitung für Pre-built Binaries
- Alte build.yaml entfernt (ersetzt durch moderne ci.yml)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 10:39:44 +01:00
580 changed files with 1904 additions and 1859 deletions

View file

@ -1,62 +0,0 @@
name: Build and Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
rust: [stable]
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- name: Format check
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Clippy check
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --verbose
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose

107
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,107 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- beta
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v4
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Run tests
run: cargo test --verbose
- name: Run tests (release mode)
run: cargo test --release --verbose
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings
build:
name: Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
target: ${{ matrix.target }}
- name: Build
run: cargo build --release --target ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ntu-${{ matrix.target }}
path: target/${{ matrix.target }}/release/ntu

66
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,66 @@
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
release:
name: Release
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: ntu
asset_name: ntu-linux-x86_64
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
artifact_name: ntu
asset_name: ntu-linux-x86_64-musl
- os: macos-latest
target: x86_64-apple-darwin
artifact_name: ntu
asset_name: ntu-macos-x86_64
- os: macos-latest
target: aarch64-apple-darwin
artifact_name: ntu
asset_name: ntu-macos-arm64
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
target: ${{ matrix.target }}
- name: Install musl-tools (Linux musl only)
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get update && sudo apt-get install -y musl-tools
- name: Build
run: cargo build --release --target ${{ matrix.target }}
- name: Strip binary (Linux only)
if: startsWith(matrix.os, 'ubuntu')
run: strip target/${{ matrix.target }}/release/${{ matrix.artifact_name }}
- name: Compress binary
run: |
cd target/${{ matrix.target }}/release
tar czf ${{ matrix.asset_name }}.tar.gz ${{ matrix.artifact_name }}
mv ${{ matrix.asset_name }}.tar.gz ../../..
- name: Upload binary to release
uses: softprops/action-gh-release@v2
with:
files: ${{ matrix.asset_name }}.tar.gz
body_path: CHANGELOG.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

28
.gitignore vendored Normal file
View file

@ -0,0 +1,28 @@
# Rust / Cargo
/target
**/*.rs.bk
*.pdb
# IDEs
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Project-specific
info/
CLAUDE.md
# Test artifacts
/test/testverzeichnis
# Temporary files
*.tmp
*.log
*.bak

View file

@ -5,6 +5,104 @@ 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.2.0] - 2025-02-12
### Added
- **`--max-depth N` option**: Limit recursion depth to N levels (requires `-r/--recursive`)
- **Explicit symlink handling**: Symlinks are no longer followed (`follow_links(false)`)
- Default: Symlinks are completely skipped (not renamed, not followed)
- With `--special`: Only the symlink name is sanitized, target remains unchanged
- Prevents infinite loops from circular symlinks
- Prevents unintended changes outside target directory
- **Verbose symlink logging**: Shows which symlinks are skipped when using `-v`
### Changed
- `WalkDir` now explicitly uses `follow_links(false)` for safety
- Improved verbose logging to indicate when max-depth is active
### Technical
- Added 5 new integration tests for max-depth and symlink behavior
- All tests passing (30 total: 25 existing + 5 new)
- Unix-specific symlink tests use `#[cfg(unix)]` attribute
## [1.1.0] - 2025-02-10
### Added
- **`-s/--sequence <name>` option**: Select transformation sequence (default, lower, upper, minimal, utf-8)
- **`-L` option**: List all available sequences (use with `-v` for details)
- **5 hardcoded sequences**:
- `default`: Current behavior (umlauts→ASCII, spaces→underscores)
- `lower`: Like default + convert to lowercase
- `upper`: Like default + convert to UPPERCASE
- `minimal`: Only replace spaces, keep UTF-8 characters
- `utf-8`: UTF-8 friendly (keep umlauts, remove special chars)
### Changed
- Refactored `clean_filename()` to support sequence-based transformations
- Umlaut replacements moved from hardcoded to sequence-specific logic
- Case transformations now also apply to file extensions
### Technical
- Added `Sequence` struct and `CaseTransform` enum in `sanitizer.rs`
- Extended CLI with `-s` and `-L` options
- Added `list_sequences()` function in `main.rs`
- Updated all unit tests to pass `Sequence` parameter
- Added 8 new integration tests for sequence functionality
## [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 <FILE>` 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
- **CI/CD Pipeline**: Automated testing and release builds via GitHub Actions
- CI workflow: Tests on Rust stable and beta, Clippy, rustfmt checks
- Release workflow: Multi-platform builds (Linux x86_64, Linux musl, macOS Intel, macOS ARM)
- **Shell Completions**: Auto-completion support for all major shells
- Bash completion (`completions/ntu.bash`)
- Zsh completion (`completions/_ntu`)
- Fish completion (`completions/ntu.fish`)
- **Manpage**: Professional manual page (`man/ntu.1`) with full documentation
- **Colored Output**: Terminal colors for better visual feedback
- Green for successful renames
- Yellow for dry-run mode
- Red for errors
- Cyan/bold for statistics
- `--no-color` flag to disable colors
- **Integration Tests**: 13 comprehensive integration tests using `assert_cmd`
- **README**: Installation instructions for pre-built binaries, badges (CI, Release, Version, License)
### Changed
- Improved `.gitignore` with better organization
- Better error messages with colored output
- Updated test framework to use modern `cargo_bin!` macro
### Fixed
- Removed unused `warn` import
## [0.2.0] - 2025-02-10
### Added

56
Cargo.lock generated
View file

@ -4,17 +4,18 @@ version = 4
[[package]]
name = "NameToUnix"
version = "0.2.0"
version = "1.2.1"
dependencies = [
"anyhow",
"assert_cmd",
"assert_fs",
"clap",
"colored",
"dirs",
"emojis",
"env_logger",
"glob",
"indicatif",
"itertools",
"log",
"once_cell",
"predicates",
@ -22,7 +23,6 @@ dependencies = [
"regex",
"serde",
"tempfile",
"thiserror",
"toml",
"unicode-segmentation",
"walkdir",
@ -93,6 +93,21 @@ version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "assert_cmd"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514"
dependencies = [
"anstyle",
"bstr",
"libc",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
]
[[package]]
name = "assert_fs"
version = "1.1.2"
@ -127,6 +142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
dependencies = [
"memchr",
"regex-automata",
"serde",
]
@ -188,6 +204,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"
@ -450,15 +476,6 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "js-sys"
version = "0.3.77"
@ -469,6 +486,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"
@ -840,6 +863,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]]
name = "walkdir"
version = "2.5.0"

View file

@ -1,12 +1,12 @@
[package]
name = "NameToUnix"
version = "0.2.0"
version = "1.2.1"
edition = "2021"
authors = ["Dieter Schlüter <dieter.schlueter@linix.de>"]
authors = ["Dieter Schlüter <dschlueter@kitux.de>"]
description = "Ein Tool zum Anpassen von Verzeichnis- und Dateinamen an Linux-Konventionen"
license = "MIT"
readme = "README.md"
repository = "https://github.com/jamulix/NameToUnix"
repository = "https://kitux.de/forgejo/dschlueter/ntu"
keywords = ["filesystem", "rename", "sanitize", "cli"]
categories = ["command-line-utilities", "filesystem"]
@ -26,20 +26,20 @@ emojis = "0.6.1"
regex = "1.10.3"
dirs = "5.0.1"
# Neue empfohlene Abhängigkeiten
# Weitere Abhängigkeiten
anyhow = "1.0.80" # Verbesserte Fehlerbehandlung
thiserror = "1.0.57" # Typisierte Fehler
once_cell = "1.19.0" # Lazy-Initialisierung für statische Werte
rayon = "1.9.0" # Parallelverarbeitung
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
assert_fs = "1.1.1" # Dateisystem-Assertions für Tests
predicates = "3.1.0" # Prädikate für Tests
assert_cmd = "2.0" # Command-Line Testing
[profile.release]
lto = true # Link-Time-Optimierung
@ -49,7 +49,7 @@ panic = "abort" # Kleinere Binärdatei durch Abbrechen bei Panic
strip = true # Entfernen von Debug-Symbolen
[package.metadata.deb]
maintainer = "Dieter Schlüter <dieter.schlueter@linix.de>"
maintainer = "Dieter Schlüter <dschlueter@kitux.de>"
copyright = "2025, Dieter Schlüter"
license-file = ["LICENSE", "4"]
extended-description = """
@ -63,5 +63,10 @@ priority = "optional"
assets = [
["target/release/ntu", "usr/bin/", "755"],
["README.md", "usr/share/doc/NameToUnix/README", "644"],
["man/ntu.1", "usr/share/man/man1/", "644"],
[".NameToUnix.conf", "usr/share/doc/NameToUnix/config.toml.example", "644"],
["completions/ntu.bash", "etc/bash_completion.d/ntu", "644"],
["completions/_ntu", "usr/share/zsh/vendor-completions/_ntu", "644"],
["completions/ntu.fish", "usr/share/fish/vendor_completions.d/ntu.fish", "644"],
]

235
README.md
View file

@ -1,5 +1,10 @@
# Filename Repair Tool for Linux
# "NameToUnix"
# NameToUnix
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
**Filename Repair Tool for Linux** · Binary: `ntu`
🔗 Repository: [kitux.de/forgejo/dschlueter/ntu](https://kitux.de/forgejo/dschlueter/ntu)
(german and english)
@ -19,7 +24,40 @@ Ich benutze ein ähnliches Programm - ein Perl-Skript - seit ca. 15 Jahren. Es h
Dies ist mein erstes Programm in Rust. (Bitte seid gnädig.)
(c) 2025 Dieter Schlüter <dieter.schlueter@linix.de>
(c) 2025 Dieter Schlüter <dschlueter@kitux.de>
## ⚠️ 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
```
## Sequences
Starting with v1.1.0, `ntu` supports transformation sequences similar to detox. Sequences control how filenames are transformed:
- **default**: Standard transformation (umlauts→ASCII, spaces→underscores, remove special chars)
- **lower**: Like default, but convert to lowercase
- **upper**: Like default, but convert to UPPERCASE
- **minimal**: Only replace spaces, keep UTF-8 characters
- **utf-8**: UTF-8 friendly (keep umlauts, replace spaces, remove special chars)
```bash
# Use specific sequence
ntu -s lower /path/to/files
# List all available sequences
ntu -L
# List sequences with details
ntu -L -v
```
## Functions / Funktionen
@ -41,13 +79,21 @@ Dies ist mein erstes Programm in Rust. (Bitte seid gnädig.)
## Installation
### Option 1: Pre-built Binary
Pre-built binaries are available from [Forgejo Releases](https://kitux.de/forgejo/dschlueter/ntu/releases).
Download and extract the appropriate binary for your platform, then move it to `/usr/local/bin/`.
### Option 2: Build from Source (Recommended)
Die ausführbare Datei wird unter `target/release/ntu` erstellt. Du solltest sie mit 'sudo cp target/release/ntu /usr/local/bin/' kopieren. Sie ist dann für alle User verfügbar. Denke daran, die Konfiguationsdatei (s. u.) ebenfalls zu kopieren. Sie kann für jeden User individuell angepasst werden, wenn sie im home-Verzeichnis des Users liegt.
The executable file is created under `target/release/ntu`. You should copy it with 'sudo cp target/release/ntu /usr/local/bin/'. It is then available for all users. Remember to copy the configuration file (see below) as well. It can be customized for each user individually if it is located in the user's home directory.
```bash
git clone https://github.com/jamulix/NameToUnix.git # Download repository
cd NameToUnix # Change to download directory
git clone https://kitux.de/forgejo/dschlueter/ntu.git # Download repository
cd ntu # Change to download directory
cargo build --release # Build binary
sudo cp target/release/ntu /usr/local/bin/ # copy binary to local bin directory
@ -58,36 +104,73 @@ sudo cp .NameToUnix.conf /etc/NameToUnix/config.toml # Copy config file to
# Lokale Einstellungen / Local settings
mkdir -p ~/.config/NameToUnix/ # Create a personal config directory for NameToUnix
cp .NameToUnix.conf ~/.config/NameToUnix/config.toml # Copy config file to this personal directory
# Shell-Completion (optional)
sudo cp completions/ntu.bash /etc/bash_completion.d/ntu # Bash completion
# Oder für Zsh:
sudo cp completions/_ntu /usr/share/zsh/site-functions/_ntu # Zsh completion
# Oder für Fish:
sudo cp completions/ntu.fish /usr/share/fish/vendor_completions.d/ntu.fish # Fish completion
# Manpage (optional)
sudo cp man/ntu.1 /usr/share/man/man1/ # Install manual page
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
# Use lowercase sequence
ntu -r -s lower /path/to/files
# Minimal mode (only spaces, keep UTF-8)
ntu -s minimal /path/to/files
# UTF-8 friendly mode
ntu -s utf-8 /path/to/files
# List available sequences
ntu -L
# List sequences with details
ntu -L -v
# Limit recursion depth
ntu -r --max-depth 3 /path/to/files
# Rename symlink names (but don't follow them)
ntu -r --special /path/to/files
```
**Note:** The following directories/files are automatically excluded:
@ -96,36 +179,84 @@ 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
# Kleinbuchstaben-Sequenz verwenden
ntu -r -s lower /pfad/zu/dateien
# Minimal-Modus (nur Leerzeichen, UTF-8 behalten)
ntu -s minimal /pfad/zu/dateien
# UTF-8 freundlicher Modus
ntu -s utf-8 /pfad/zu/dateien
# Verfügbare Sequenzen auflisten
ntu -L
# Sequenzen mit Details auflisten
ntu -L -v
# Rekursionstiefe begrenzen
ntu -r --max-depth 3 /pfad/zu/dateien
# Symlink-Namen bereinigen (aber nicht folgen)
ntu -r --special /pfad/zu/dateien
```
**Hinweis:** Die folgenden Verzeichnisse/Dateien werden automatisch ausgeschlossen:
`.git`, `.svn`, `node_modules`, `.cache`, `__pycache__`
## Symlink Behavior / Symlink-Verhalten
By default, `ntu` **does not process symbolic links**:
- Symlinks are **skipped** (not renamed, not followed)
- This prevents unintended changes outside the target directory
- No risk of infinite loops from circular symlinks
With `--special` flag:
- Symlink **names** are sanitized (e.g., `link to file``link_to_file`)
- Symlinks are still **not followed** (targets remain unchanged)
- Safe: only the link itself is renamed, not what it points to
**Standardmäßig verarbeitet `ntu` symbolische Links NICHT**:
- Symlinks werden **übersprungen** (weder umbenannt noch gefolgt)
- Dies verhindert unbeabsichtigte Änderungen außerhalb des Zielverzeichnisses
- Keine Gefahr von Endlosschleifen durch zirkuläre Links
Mit `--special` Flag:
- Symlink-**Namen** werden bereinigt (z.B. `link zu datei``link_zu_datei`)
- Symlinks werden weiterhin **nicht gefolgt** (Ziele bleiben unverändert)
- Sicher: nur der Link selbst wird umbenannt, nicht worauf er zeigt
## Configuration File / Konfiguration
Erstelle eine Datei `.NameToUnix.conf` im persönlichen Arbeitsverzeichnis z. B. mit folgendem Inhalt
@ -207,21 +338,28 @@ Ein Tool zum Anpassen von Verzeichnis- und Dateinamen an Linux-Konventionen
Usage: ntu [OPTIONS] [PATHS]...
Arguments:
[PATHS]... Pfade (Dateien und Verzeichnisse) zum rekursiven Anpassen
[PATHS]... Pfade (Dateien und Verzeichnisse) zum rekursiven Anpassen
Options:
-q, --quiet Ausgaben unterdrücken (keine Umbenennungsinfos auf stdout)
-n, --dry-run Nur anzeigen, aber keine realen Änderungen vornehmen (dry-run)
-f, --force Existierende Dateien überschreiben
-e, --exclude <PATTERN> Zu ignorierende Muster (-e "*.py", mehrere können angegeben werden)
-v, --verbose Ausführliche Debug-Informationen
--modify-root Erlaubt, auch das Wurzelverzeichnis anzupassen
--special Auch symbolische Links und Special Files verarbeiten
-h, --help Print help
-V, --version Print version
--conf <FILE> Explizite Konfigurationsdatei (bypassed Standard-Hierarchie)
-r, --recursive Rekursive Verarbeitung von Unterverzeichnissen aktivieren
--max-depth <N> Maximale Rekursionstiefe (erfordert --recursive)
-s, --sequence <NAME> Wählt eine Transformations-Sequenz aus (default, lower, upper, minimal, utf-8)
-L Listet alle verfügbaren Sequences auf
-q, --quiet Ausgaben unterdrücken (keine Umbenennungsinfos auf stdout)
-n, --dry-run Nur anzeigen, aber keine realen Änderungen vornehmen (dry-run)
-f, --force Existierende Dateien überschreiben
-e, --exclude <PATTERN> Zu ignorierende Muster (-e "*.py", mehrere können angegeben werden)
-v, --verbose Ausführliche Debug-Informationen
--modify-root Erlaubt, auch das Wurzelverzeichnis anzupassen
--special Auch symbolische Links und Special Files verarbeiten
--no-color Deaktiviert farbige Ausgabe
-h, --help Print help
-V, --version Print version
```
Farbige Ausgabe kann auch über die Umgebungsvariable `NO_COLOR` deaktiviert werden (siehe https://no-color.org/).
### Usage of NameToUnix
```text
@ -230,21 +368,28 @@ A tool for adapting directories and file names to Linux conventions
Usage: ntu [OPTIONS] [PATHS]...
Arguments:
[PATHS]... Paths (files and directories) for recursive customization
[PATHS]... Paths (files and directories) for recursive customization
Options:
-q, --quiet Suppress output (no renaming info on stdout)
-n, --dry-run Only display, but do not make any real changes (dry-run)
-f, --force Overwrite existing files
-e, --exclude <PATTERN> Patterns to be ignored (-e "*.py", several can be specified)
-v, --verbose Detailed debug information
--modify-root Allows you to customize the root directory as well
--special Also process symbolic links and special files
-h, --help Print help
-V, --version Print version
--conf <FILE> Explicit config file (bypasses default hierarchy)
-r, --recursive Enable recursive processing of subdirectories
--max-depth <N> Maximum recursion depth (requires --recursive)
-s, --sequence <NAME> Select a transformation sequence (default, lower, upper, minimal, utf-8)
-L List all available sequences
-q, --quiet Suppress output (no renaming info on stdout)
-n, --dry-run Only display, but do not make any real changes (dry-run)
-f, --force Overwrite existing files
-e, --exclude <PATTERN> Patterns to be ignored (-e "*.py", several can be specified)
-v, --verbose Detailed debug information
--modify-root Allows you to customize the root directory as well
--special Also process symbolic links and special files
--no-color Disable colored output
-h, --help Print help
-V, --version Print version
```
Colored output can also be disabled via the `NO_COLOR` environment variable (see https://no-color.org/).
## Test
Im Verzeichnis [***./test***](./test) gibt es ein bash-Skript [***create_test_tree.sh***](test/create_test_tree.sh), das lokal 21 Test-Verzeichnisse und 400 Dateien mit skurrilen Zufallsnamen erzeugt. Damit kannst Du ***NameToUnix*** ausprobieren:

27
completions/_ntu Normal file
View file

@ -0,0 +1,27 @@
#compdef ntu
# Zsh completion for ntu (NameToUnix)
_ntu() {
local curcontext="$curcontext" state line
typeset -A opt_args
_arguments -C \
'(-r --recursive)'{-r,--recursive}'[Process directories recursively]' \
'(-s --sequence)'{-s,--sequence}'[Use transformation sequence]:sequence:(default lower upper minimal utf-8)' \
'-L[List available sequences]' \
'--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]' \
'*'{-e,--exclude}'[Exclude pattern]:pattern:' \
'(-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'
}
_ntu "$@"

45
completions/ntu.bash Normal file
View file

@ -0,0 +1,45 @@
# Bash completion for ntu (NameToUnix)
_ntu_completion() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
# All available options
opts="--recursive --sequence --conf --dry-run --no-changes --quiet --force --exclude --verbose --modify-root --special --no-color --help --version -r -s -L -n -q -f -e -v -h -V"
# Handle options that require arguments
case "${prev}" in
-s|--sequence)
# Suggest available sequences
COMPREPLY=( $(compgen -W "default lower upper minimal utf-8" -- ${cur}) )
return 0
;;
-e|--exclude)
# Suggest glob patterns
COMPREPLY=( $(compgen -W '"*.tmp" "*.log" "*.bak" "*.swp" "*~"' -- ${cur}) )
return 0
;;
--conf)
# Suggest files for config option
COMPREPLY=( $(compgen -f -- ${cur}) )
return 0
;;
*)
;;
esac
# If current word starts with -, complete with options
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
# Otherwise complete with directories and files
COMPREPLY=( $(compgen -f -- ${cur}) )
return 0
}
# Register completion function
complete -F _ntu_completion ntu

24
completions/ntu.fish Normal file
View file

@ -0,0 +1,24 @@
# Fish completion for ntu (NameToUnix)
# Main command
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 -s s -l sequence -d 'Use transformation sequence' -xa 'default lower upper minimal utf-8'
complete -c ntu -s L -d 'List available sequences'
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'
complete -c ntu -s f -l force -d 'Overwrite existing files'
complete -c ntu -s e -l exclude -d 'Exclude files matching pattern' -r
complete -c ntu -s v -l verbose -d 'Show verbose debug information'
complete -c ntu -l modify-root -d 'Allow renaming the root directory'
complete -c ntu -l special -d 'Process symbolic links and special files'
complete -c ntu -l no-color -d 'Disable colored output'
complete -c ntu -s h -l help -d 'Print help information'
complete -c ntu -s V -l version -d 'Print version information'
# File/directory completion for paths
complete -c ntu -a '(__fish_complete_path)' -d 'Path to process'

View file

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

64
install.sh Executable file
View file

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

235
man/ntu.1 Normal file
View file

@ -0,0 +1,235 @@
.TH NTU 1 "2026-02-13" "NameToUnix 1.2.1" "User Commands"
.SH NAME
ntu \- sanitize file and directory names to Unix conventions
.SH SYNOPSIS
.B ntu
[\fIOPTIONS\fR] \fIPATH\fR...
.SH DESCRIPTION
.B ntu
(NameToUnix) is a command-line tool that renames files and directories
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
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 \-\-max\-depth " \fIN\fR"
Limit recursion to N levels deep (requires \fB\-r\fR). Useful for processing only a few levels of a deep directory tree (e.g., limiting node_modules traversal).
.TP
.BR \-s ", " \-\-sequence " \fINAME\fR"
Use a specific transformation sequence. Available sequences: default, lower, upper, minimal, utf-8. Use \fB\-L\fR to list all sequences.
.TP
.BR \-L
List all available transformation sequences. Use with \fB\-v\fR for detailed information.
.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
.BR \-n ", " \-\-dry\-run
Show what would be renamed without making actual changes (alias: \-\-no\-changes)
.TP
.BR \-f ", " \-\-force
Overwrite existing files if target already exists
.TP
.BR \-e ", " \-\-exclude " \fIPATTERN\fR"
Exclude files matching glob pattern (can be specified multiple times)
.TP
.BR \-v ", " \-\-verbose
Show verbose debug information
.TP
.BR \-\-modify\-root
Allow renaming of the root directory itself (normally skipped)
.TP
.BR \-\-special
Process symbolic links and special files (normally skipped)
.TP
.BR \-\-no\-color
Disable colored output. The \fBNO_COLOR\fR environment variable is also
respected (see \fIhttps://no-color.org/\fR).
.TP
.BR \-h ", " \-\-help
Print help information
.TP
.BR \-V ", " \-\-version
Print version information
.SH TRANSFORMATIONS
.B ntu
applies the following transformations to filenames:
.TP
.B Spaces
Replaced with underscores (_)
.TP
.B German Umlauts
ä → ae, ö → oe, ü → ue, Ä → Ae, Ö → Oe, Ü → Ue, ß → ss
.TP
.B Special Characters
Most special characters are replaced with underscores. Some are preserved
in specific contexts (e.g., C++ is kept intact).
.TP
.B Hidden Files
Files starting with a dot (.) are recognized and preserved correctly.
.TP
.B Double Extensions
Extensions like .tar.gz, .tar.bz2, .tar.xz are preserved as a unit.
.TP
.B Parentheses
Replaced with underscores.
.TP
.B Multiple Underscores
Consecutive underscores are collapsed to a single underscore.
.SH SEQUENCES
.B ntu
supports different transformation sequences that can be selected with the \fB\-s\fR option:
.TP
.B default
Standard transformation: spaces become underscores, German umlauts are converted
to ASCII equivalents, special characters are removed or replaced.
.TP
.B lower
Like default, but converts all text to lowercase.
.TP
.B upper
Like default, but converts all text to UPPERCASE.
.TP
.B minimal
Minimal changes: only replaces spaces with underscores, keeps umlauts and
other UTF-8 characters.
.TP
.B utf-8
UTF-8 friendly: keeps umlauts and UTF-8 characters, replaces spaces,
removes special characters.
.SH EXCLUDED PATTERNS
By default, the following directories are automatically excluded:
.PP
.nf
.RS
.git/, .svn/, node_modules/, .cache/, __pycache__/
.RE
.fi
.PP
Additional patterns can be excluded using the \fB\-e\fR option.
.SH PARALLEL PROCESSING
For directories with 100 or more files, \fBntu\fR automatically uses
parallel processing to improve performance. For smaller directories,
it processes files sequentially to avoid overhead.
.SH EXAMPLES
.TP
Rename files in current directory (dry-run):
.B ntu \-n .
.TP
Rename files in specific directory:
.B ntu /path/to/directory
.TP
Exclude specific patterns:
.B ntu \-e "*.tmp" \-e "*.bak" /path/to/directory
.TP
Force overwrite existing files:
.B ntu \-f /path/to/directory
.TP
Process multiple directories:
.B ntu /path/one /path/two /path/three
.TP
Verbose output with no colors:
.B ntu \-v \-\-no\-color /path/to/directory
.TP
Use lowercase sequence:
.B ntu \-r \-s lower /path/to/files
.TP
Minimal mode (only spaces, keep UTF-8):
.B ntu \-s minimal /path/to/files
.TP
UTF-8 friendly mode:
.B ntu \-s utf-8 /path/to/files
.TP
List all available sequences:
.B ntu \-L
.TP
List sequences with details:
.B ntu \-L \-v
.TP
Limit recursion depth to 3 levels:
.B ntu \-r \-\-max\-depth 3 /path/to/files
.TP
Process symlink names (but don't follow them):
.B ntu \-r \-\-special /path/to/files
.SH SYMLINK BEHAVIOR
By default, \fBntu\fR does not process symbolic links:
.IP \(bu 2
Symlinks are skipped (not renamed, not followed)
.IP \(bu 2
Prevents unintended changes outside the target directory
.IP \(bu 2
No risk of infinite loops from circular symlinks
.PP
With the \fB\-\-special\fR flag:
.IP \(bu 2
Symlink names are sanitized (e.g., "link to file" \(-> "link_to_file")
.IP \(bu 2
Symlinks are still not followed (targets remain unchanged)
.IP \(bu 2
Safe: only the link itself is renamed, not what it points to
.SH CONFIGURATION
.B ntu
looks for configuration files in the following locations. Later files
override earlier ones (highest priority last):
.PP
.nf
.RS
1. /etc/NameToUnix/config.toml (system-wide, lowest priority)
2. ~/.config/NameToUnix/config.toml (user-specific)
3. ./.NameToUnix.conf (working directory, highest priority)
.RE
.fi
.PP
When using \fB\-\-conf\fR, the standard hierarchy is bypassed and only the
specified file is loaded.
.PP
Configuration files use TOML format with a \fB[replacements]\fR section
for custom character replacements. See the project documentation for details.
.SH ENVIRONMENT
.TP
.B NO_COLOR
When set (to any value), colored output is disabled. This follows the
convention described at \fIhttps://no-color.org/\fR. The \fB\-\-no\-color\fR
flag has the same effect.
.SH EXIT STATUS
.TP
.B 0
Success
.TP
.B 1
Error occurred during processing
.SH SAFETY
.B ntu
performs several safety checks:
.IP \(bu 2
Checks if target file already exists (unless \fB\-\-force\fR is used)
.IP \(bu 2
Verifies write permissions before attempting rename
.IP \(bu 2
Processes files from deepest to shallowest to avoid parent conflicts
.IP \(bu 2
Skips special files unless explicitly requested with \fB\-\-special\fR
.SH BUGS
Report bugs at: https://kitux.de/forgejo/dschlueter/ntu/issues
.SH AUTHOR
Written by Dieter Schlüter <dschlueter@kitux.de>
.SH COPYRIGHT
Copyright \(co 2025 Dieter Schlüter. Licensed under MIT License.
.SH SEE ALSO
.BR rename (1),
.BR detox (1),
.BR mv (1)
.PP
Full documentation at: https://kitux.de/forgejo/dschlueter/ntu

View file

@ -31,8 +31,8 @@ cargo install NameToUnix
### Manueller Build
```bash
git clone https://github.com/username/NameToUnix.git
cd NameToUnix
git clone https://kitux.de/forgejo/dschlueter/ntu.git
cd ntu
cargo build --release
```
@ -658,7 +658,7 @@ fn main() -> Result<()> {
```
## 8. ./github/workflows/build.yaml -- GitHub Actions Workflow für CI/CD
## 8. .github/workflows/build.yaml -- CI/CD Workflow (Legacy)
```yaml
name: Build and Test

View file

@ -13,6 +13,26 @@ pub struct Cli {
/// Pfade (Dateien und Verzeichnisse) zum rekursiven Anpassen
pub paths: Vec<PathBuf>,
/// Explizite Konfigurationsdatei (bypassed Standard-Hierarchie)
#[clap(long = "conf", value_name = "FILE")]
pub config_file: Option<PathBuf>,
/// Rekursive Verarbeitung von Unterverzeichnissen aktivieren
#[clap(short = 'r', long)]
pub recursive: bool,
/// Maximale Rekursionstiefe (erfordert --recursive)
#[clap(long, value_name = "N", requires = "recursive")]
pub max_depth: Option<usize>,
/// Wählt eine Transformations-Sequenz aus (default, lower, upper, minimal, utf-8)
#[clap(short = 's', long, value_name = "NAME")]
pub sequence: Option<String>,
/// Listet alle verfügbaren Sequences auf
#[clap(short = 'L')]
pub list_sequences: bool,
/// Ausgaben unterdrücken (keine Umbenennungsinfos auf stdout)
#[clap(short, long)]
pub quiet: bool,
@ -40,4 +60,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

@ -1,5 +1,5 @@
use anyhow::{Context, Result};
use log::{debug, info};
use log::{debug, info, warn};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
@ -41,22 +41,44 @@ impl Config {
}
}
/// Öffentliche Methode zum Laden einer Konfiguration aus einem Pfad
/// Öffentliche Methode zum Laden einer Konfiguration aus einem Pfad.
/// Gibt Standardwerte zurück wenn die Datei nicht existiert,
/// propagiert aber Parse-Fehler als Warnung.
pub fn load(path: &str, verbose: bool) -> Result<Self> {
let cfg_path = Path::new(path);
if !cfg_path.exists() {
if verbose {
info!(
"Keine Konfigurationsdatei '{}' gefunden. Verwende Standardwerte.",
path
);
}
return Ok(Self::default());
}
match Self::load_internal(cfg_path, verbose) {
Ok(config) => Ok(config),
Err(_) => {
if verbose {
info!(
"Keine Konfigurationsdatei '{}' gefunden. Verwende Standardwerte.",
path
);
}
Err(e) => {
warn!(
"Fehler beim Laden der Konfigurationsdatei '{}': {}. Verwende Standardwerte.",
path, e
);
Ok(Self::default())
}
}
}
/// Lädt Konfiguration aus spezifischer Datei (fehlschlägt bei nicht-existierender Datei)
pub fn from_file(path: &Path, verbose: bool) -> Result<Self> {
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<Self> {
// Prioritätenreihenfolge (später überschreibt früher):

View file

@ -6,13 +6,15 @@ mod sanitizer;
use anyhow::{Context, Result};
use clap::Parser;
use cli::Cli;
use colored::*;
use config::Config;
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;
use walkdir::WalkDir;
@ -30,7 +32,7 @@ const DEFAULT_EXCLUDES: &[&str] = &[
"__pycache__/**",
];
// Schwellwert für parallele Verarbeitung (bei weniger Dateien lohnt sich Overhead nicht)
// Schwellwert für parallele Verarbeitung (bei weniger Dateien lohnt sich der Overhead nicht)
const PARALLEL_THRESHOLD: usize = 100;
/// Repräsentiert eine geplante Umbenennungsoperation
@ -40,6 +42,13 @@ struct RenameOperation {
new_path: PathBuf,
}
/// Prüft ob farbige Ausgabe aktiviert sein soll.
/// Respektiert --no-color Flag, NO_COLOR Umgebungsvariable (https://no-color.org/)
/// und ob stdout ein Terminal ist.
fn should_use_color(no_color_flag: bool) -> bool {
!no_color_flag && std::env::var_os("NO_COLOR").is_none() && std::io::stdout().is_terminal()
}
/// Startpunkt des Programms
fn main() -> Result<()> {
// Initialisiere Logger
@ -48,9 +57,44 @@ fn main() -> Result<()> {
// Argumente parsen
let args = Cli::parse();
// Optional Konfigurationsdatei laden
let config = Config::from_default_locations(args.verbose)?;
// let config = Config::load(".NameToUnix.conf", args.verbose)?;
// Farben konfigurieren
if !should_use_color(args.no_color) {
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::standard()
};
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).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
@ -74,18 +118,42 @@ fn main() -> Result<()> {
}
}
// Prüfe ob Pfade angegeben wurden
if args.paths.is_empty() {
eprintln!("Keine Pfade angegeben. Nutze 'ntu --help' für Hilfe.");
std::process::exit(1);
}
// Für alle angegebenen Pfade
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: mit optionaler max_depth
let mut w = WalkDir::new(path).follow_links(false); // Symlinks nicht folgen
if let Some(depth) = args.max_depth {
w = w.max_depth(depth);
if args.verbose {
info!("Maximale Rekursionstiefe: {}", depth);
}
}
w
} else {
// Non-recursive: max_depth(1) verarbeitet nur direkte Kinder
WalkDir::new(path).max_depth(1).follow_links(false)
};
for entry_result in walker
.into_iter()
.filter_entry(|e| !is_excluded(e, &exclude_patterns))
{
if let Ok(entry) = entry_result {
entries.push(entry);
} else if let Err(e) = entry_result {
error!("Fehler beim Durchlaufen von {}: {}", path.display(), e);
match entry_result {
Ok(entry) => entries.push(entry),
Err(e) => error!(
"{}",
format!("Fehler beim Durchlaufen von {}: {}", path.display(), e).red()
),
}
}
@ -93,73 +161,44 @@ fn main() -> Result<()> {
entries.sort_by_key(|e| e.depth());
entries.reverse();
// Fortschrittsbalken bei größeren Dateimengen
let progress_bar = if !args.quiet && entries.len() > 50 {
let bar = ProgressBar::new(entries.len() as u64);
bar.set_style(ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}")?
.progress_chars("#>-"));
Some(bar)
} else {
None
// Berechne Umbenennungen (parallel bei vielen Dateien, sequentiell bei wenigen)
// Hinweis: rayon's par_iter() auf indexierten Collections bewahrt die Reihenfolge,
// sodass die tiefenbasierte Sortierung erhalten bleibt.
let map_entry = |entry: &walkdir::DirEntry| -> Option<RenameOperation> {
let old_path = entry.path();
// Ebenentiefe 0 -> überspringen
if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root {
return None;
}
// Special Files (Symlinks, Sockets, etc.) nur mit --special
let file_type = entry.file_type();
if !args.special && (!file_type.is_file() && !file_type.is_dir()) {
if args.verbose && file_type.is_symlink() {
debug!(
"Überspringe Symlink: {} (nutze --special um Symlink-Namen zu bereinigen)",
old_path.display()
);
}
return None;
}
// Dateiname ermitteln und bereinigen
let filename = old_path.file_name()?;
let new_name = clean_filename(filename, &config, &sequence, args.verbose)?;
let new_path = old_path.with_file_name(&new_name);
Some(RenameOperation {
old_path: old_path.to_path_buf(),
new_path,
})
};
// Berechne Umbenennungen (parallel bei vielen Dateien)
let rename_ops: Vec<RenameOperation> = if entries.len() >= PARALLEL_THRESHOLD {
// Parallel mit rayon
entries
.par_iter()
.filter_map(|entry| {
let old_path = entry.path();
// Ebenentiefe 0 -> überspringen
if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root {
return None;
}
// Special Files nur mit --special
let file_type = entry.file_type();
if !args.special && (!file_type.is_file() && !file_type.is_dir()) {
return None;
}
// Dateiname ermitteln und bereinigen
let filename = old_path.file_name()?;
let new_name = clean_filename(filename, &config, false)?;
let new_path = old_path.with_file_name(&new_name);
Some(RenameOperation {
old_path: old_path.to_path_buf(),
new_path,
})
})
.collect()
entries.par_iter().filter_map(map_entry).collect()
} else {
// Sequenziell bei wenigen Dateien
entries
.iter()
.filter_map(|entry| {
let old_path = entry.path();
if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root {
return None;
}
let file_type = entry.file_type();
if !args.special && (!file_type.is_file() && !file_type.is_dir()) {
return None;
}
let filename = old_path.file_name()?;
let new_name = clean_filename(filename, &config, false)?;
let new_path = old_path.with_file_name(&new_name);
Some(RenameOperation {
old_path: old_path.to_path_buf(),
new_path,
})
})
.collect()
entries.iter().filter_map(map_entry).collect()
};
// Statistiken
@ -168,6 +207,17 @@ fn main() -> Result<()> {
let mut renamed_count = 0;
let mut skipped_count = 0;
// Fortschrittsbalken basierend auf tatsächlichen Umbenennungen
let progress_bar = if !args.quiet && total_planned > 50 {
let bar = ProgressBar::new(total_planned as u64);
bar.set_style(ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}")?
.progress_chars("#>-"));
Some(bar)
} else {
None
};
// Umbenennungen sequenziell ausführen
for op in rename_ops {
if let Some(bar) = &progress_bar {
@ -175,7 +225,21 @@ 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 {
@ -188,10 +252,14 @@ fn main() -> Result<()> {
Ok(_) => renamed_count += 1,
Err(e) => {
error!(
"Fehler beim Umbenennen: {} -> {}: {}",
op.old_path.display(),
op.new_path.display(),
e
"{}",
format!(
"Fehler beim Umbenennen: {} -> {}: {}",
op.old_path.display(),
op.new_path.display(),
e
)
.red()
);
skipped_count += 1;
}
@ -209,15 +277,35 @@ 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()
);
}
}
}
@ -225,3 +313,37 @@ 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.");
}
}

View file

@ -13,14 +13,85 @@ static RE_INVALID: Lazy<Regex> = Lazy::new(|| Regex::new(r"[^\w.\-]").unwrap());
static RE_ADJACENT: Lazy<Regex> = Lazy::new(|| Regex::new(r"_\.|\._").unwrap());
static RE_MULTI: Lazy<Regex> = Lazy::new(|| Regex::new(r"[_\.]{2,}").unwrap());
/// Repräsentiert eine Transformations-Sequenz
#[derive(Debug, Clone)]
pub struct Sequence {
pub name: &'static str,
pub description: &'static str,
pub apply_umlauts: bool,
pub apply_case: CaseTransform,
pub apply_emojis: bool,
pub minimal_mode: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CaseTransform {
None,
Lower,
Upper,
}
impl Sequence {
/// Gibt alle verfügbaren Sequences zurück
pub fn all() -> Vec<Sequence> {
vec![
Sequence {
name: "default",
description: "Standard transformation: spaces→underscores, umlauts→ASCII, remove special chars",
apply_umlauts: true,
apply_case: CaseTransform::None,
apply_emojis: true,
minimal_mode: false,
},
Sequence {
name: "lower",
description: "Like default, but convert everything to lowercase",
apply_umlauts: true,
apply_case: CaseTransform::Lower,
apply_emojis: true,
minimal_mode: false,
},
Sequence {
name: "upper",
description: "Like default, but convert everything to UPPERCASE",
apply_umlauts: true,
apply_case: CaseTransform::Upper,
apply_emojis: true,
minimal_mode: false,
},
Sequence {
name: "minimal",
description: "Minimal changes: only replace spaces, keep umlauts and UTF-8",
apply_umlauts: false,
apply_case: CaseTransform::None,
apply_emojis: false,
minimal_mode: true,
},
Sequence {
name: "utf-8",
description: "UTF-8 friendly: spaces→underscores, keep umlauts, remove special chars",
apply_umlauts: false,
apply_case: CaseTransform::None,
apply_emojis: true,
minimal_mode: false,
},
]
}
/// Findet eine Sequence nach Namen
pub fn find(name: &str) -> Option<Sequence> {
Self::all().into_iter().find(|s| s.name == name)
}
/// Gibt die Standard-Sequence zurück
pub fn standard() -> Sequence {
Self::find("default").unwrap()
}
}
// Bekannte Doppel-Extensions (z.B. .tar.gz)
const DOUBLE_EXTENSIONS: &[&str] = &[
".tar.gz",
".tar.bz2",
".tar.xz",
".tar.zst",
".tar.lz",
".tar.Z",
".tar.gz", ".tar.bz2", ".tar.xz", ".tar.zst", ".tar.lz", ".tar.Z",
];
/// Trennt Dateiname in Basis und Extension, berücksichtigt Doppel-Extensions
@ -30,10 +101,7 @@ fn split_filename(filename: &str) -> (String, String) {
if filename.ends_with(double_ext) {
let base_len = filename.len() - double_ext.len();
if base_len > 0 {
return (
filename[..base_len].to_string(),
double_ext.to_string(),
);
return (filename[..base_len].to_string(), double_ext.to_string());
}
}
}
@ -45,8 +113,13 @@ fn split_filename(filename: &str) -> (String, String) {
}
}
/// Bereinigt den übergebenen Dateinamen oder Verzeichnisnamen.
pub fn clean_filename(name: &OsStr, config: &Config, verbose: bool) -> Option<String> {
/// Bereinigt den übergebenen Dateinamen mit gegebener Sequence.
pub fn clean_filename(
name: &OsStr,
config: &Config,
sequence: &Sequence,
verbose: bool,
) -> Option<String> {
let original = name.to_string_lossy();
// Versteckte Dateien (mit führendem Punkt) korrekt behandeln
@ -62,19 +135,47 @@ pub fn clean_filename(name: &OsStr, config: &Config, verbose: bool) -> Option<St
base = preserve_special_identifiers(&base);
ext = preserve_special_identifiers(&ext);
// 1) Konfig-Replacements anwenden (zuerst)
// 1) Config-Replacements anwenden (immer zuerst)
for (k, v) in &config.replacements {
base = base.replace(k, v);
}
// 2) Danach hart-codierte Ersetzungen anwenden
// 2) Sequence-basierte Umlaut-Ersetzung
if sequence.apply_umlauts {
base = apply_umlaut_replacements(&base);
}
// 3) Hardcoded replacements (Apostroph etc.)
base = apply_hardcoded_replacements(&base);
// 3) Emojis und hochgestellte Zeichen ersetzen
base = replace_emojis_and_superscript(&base);
// 4) Case-Transformation (auf base UND extension anwenden)
match sequence.apply_case {
CaseTransform::Lower => {
base = base.to_lowercase();
ext = ext.to_lowercase();
}
CaseTransform::Upper => {
base = base.to_uppercase();
ext = ext.to_uppercase();
}
CaseTransform::None => {}
}
// 4) Entfernen/Ersetzen aller übrigen ungültigen Zeichen
base = RE_INVALID.replace_all(&base, "_").to_string();
// 5) Emojis ersetzen (wenn aktiviert)
if sequence.apply_emojis {
base = replace_emojis_and_superscript(&base);
}
// 6) Ungültige Zeichen behandeln
if sequence.minimal_mode {
// Minimal: Nur Leerzeichen und gefährliche Zeichen
base = base.replace(' ', "_");
// Entferne nur absolut gefährliche Zeichen
base = base.replace(['/', '\\', '\0', '\n'], "_");
} else {
// Standard: Alle ungültigen Zeichen → Unterstrich
base = RE_INVALID.replace_all(&base, "_").to_string();
}
// Ungültige Kombinationen aus Punkt und Unterstrich
base = RE_ADJACENT.replace_all(&base, ".").to_string();
@ -124,22 +225,30 @@ pub fn clean_filename(name: &OsStr, config: &Config, verbose: bool) -> Option<St
}
}
// Eindeutige Platzhalter, die in echten Dateinamen praktisch nicht vorkommen.
// Bestehen nur aus \w-Zeichen (für RE_INVALID), ohne Mehrfach-Unterstriche (für RE_MULTI),
// ohne führende Unterstriche (für trim_leading_underscores).
const PH_CPLUSPLUS: &str = "NTUxCPLUSPLUSx";
const PH_CPLUSPLUS_LC: &str = "NTUxcplusplusx";
const PH_CSHARP: &str = "NTUxCSHARPx";
const PH_CSHARP_LC: &str = "NTUxcsharpx";
/// Schützt spezielle Identifikatoren vor der Umwandlung
fn preserve_special_identifiers(input: &str) -> String {
input
.replace("C++", "CPLUSPLUS")
.replace("c++", "cplusplus")
.replace("C#", "CSHARP")
.replace("c#", "csharp")
.replace("C++", PH_CPLUSPLUS)
.replace("c++", PH_CPLUSPLUS_LC)
.replace("C#", PH_CSHARP)
.replace("c#", PH_CSHARP_LC)
}
/// Stellt spezielle Identifikatoren wieder her
fn restore_special_identifiers(input: &str) -> String {
input
.replace("CPLUSPLUS", "C++")
.replace("cplusplus", "c++")
.replace("CSHARP", "C#")
.replace("csharp", "c#")
.replace(PH_CPLUSPLUS, "C++")
.replace(PH_CPLUSPLUS_LC, "c++")
.replace(PH_CSHARP, "C#")
.replace(PH_CSHARP_LC, "c#")
}
/// Fasst alle fest eingebauten Ersetzungen zusammen.
@ -149,6 +258,18 @@ fn apply_hardcoded_replacements(input: &str) -> String {
.replace("ˆ", "_")
}
/// Ersetzt deutsche Umlaute durch ASCII-Äquivalente
fn apply_umlaut_replacements(input: &str) -> String {
input
.replace("ä", "ae")
.replace("ö", "oe")
.replace("ü", "ue")
.replace("Ä", "Ae")
.replace("Ö", "Oe")
.replace("Ü", "Ue")
.replace("ß", "ss")
}
/// Entfernt am Anfang nur Unterstriche, einen führenden Punkt (.) bewahrt es.
fn trim_leading_underscores_preserve_leading_dot(s: &str) -> String {
let mut chars = s.chars().peekable();
@ -232,10 +353,7 @@ pub fn is_safe_rename(src: &Path, dst: &Path, force: bool) -> bool {
use std::os::unix::fs::PermissionsExt;
let permissions = metadata.permissions();
if permissions.mode() & 0o200 == 0 {
warn!(
"Keine Schreibrechte im Verzeichnis: {}",
parent.display()
);
warn!("Keine Schreibrechte im Verzeichnis: {}", parent.display());
return false;
}
}
@ -290,33 +408,29 @@ mod tests {
use std::ffi::OsStr;
fn make_test_config() -> Config {
let mut replacements = std::collections::HashMap::new();
replacements.insert("ä".to_string(), "ae".to_string());
replacements.insert("ö".to_string(), "oe".to_string());
replacements.insert("ü".to_string(), "ue".to_string());
replacements.insert("ß".to_string(), "ss".to_string());
Config { replacements }
Config::default()
}
#[test]
fn test_clean_filename_basic() {
let config = Config::default();
let sequence = Sequence::standard();
// Spaces should become underscores
assert_eq!(
clean_filename(OsStr::new("test file.txt"), &config, false),
clean_filename(OsStr::new("test file.txt"), &config, &sequence, false),
Some("test_file.txt".to_string())
);
// Parentheses should become underscores
assert_eq!(
clean_filename(OsStr::new("file (1).txt"), &config, false),
clean_filename(OsStr::new("file (1).txt"), &config, &sequence, false),
Some("file_1.txt".to_string())
);
// Multiple underscores should be collapsed
assert_eq!(
clean_filename(OsStr::new("test__file.txt"), &config, false),
clean_filename(OsStr::new("test__file.txt"), &config, &sequence, false),
Some("test_file.txt".to_string())
);
}
@ -324,28 +438,29 @@ mod tests {
#[test]
fn test_clean_filename_hidden_files() {
let config = Config::default();
let sequence = Sequence::standard();
// Hidden files should keep their leading dot
assert_eq!(
clean_filename(OsStr::new(".gitignore"), &config, false),
clean_filename(OsStr::new(".gitignore"), &config, &sequence, false),
None // No change needed
);
// Hidden files with spaces
assert_eq!(
clean_filename(OsStr::new(".my config"), &config, false),
clean_filename(OsStr::new(".my config"), &config, &sequence, false),
Some(".my_config".to_string())
);
// Hidden files with extension
assert_eq!(
clean_filename(OsStr::new(".test file.txt"), &config, false),
clean_filename(OsStr::new(".test file.txt"), &config, &sequence, false),
Some(".test_file.txt".to_string())
);
// Multiple leading dots
assert_eq!(
clean_filename(OsStr::new("...strange"), &config, false),
clean_filename(OsStr::new("...strange"), &config, &sequence, false),
Some(".unnamed.strange".to_string())
);
}
@ -353,20 +468,21 @@ mod tests {
#[test]
fn test_clean_filename_umlauts() {
let config = make_test_config();
let sequence = Sequence::standard();
// German umlauts
assert_eq!(
clean_filename(OsStr::new("Müller.pdf"), &config, false),
clean_filename(OsStr::new("Müller.pdf"), &config, &sequence, false),
Some("Mueller.pdf".to_string())
);
assert_eq!(
clean_filename(OsStr::new("schön.txt"), &config, false),
clean_filename(OsStr::new("schön.txt"), &config, &sequence, false),
Some("schoen.txt".to_string())
);
assert_eq!(
clean_filename(OsStr::new("Größe.doc"), &config, false),
clean_filename(OsStr::new("Größe.doc"), &config, &sequence, false),
Some("Groesse.doc".to_string())
);
}
@ -374,33 +490,34 @@ mod tests {
#[test]
fn test_clean_filename_extensions() {
let config = Config::default();
let sequence = Sequence::standard();
// Single extension
assert_eq!(
clean_filename(OsStr::new("test file.txt"), &config, false),
clean_filename(OsStr::new("test file.txt"), &config, &sequence, false),
Some("test_file.txt".to_string())
);
// Double extension with spaces in base name
assert_eq!(
clean_filename(OsStr::new("my archive.tar.gz"), &config, false),
clean_filename(OsStr::new("my archive.tar.gz"), &config, &sequence, false),
Some("my_archive.tar.gz".to_string())
);
// Other double extensions
assert_eq!(
clean_filename(OsStr::new("backup file.tar.bz2"), &config, false),
clean_filename(OsStr::new("backup file.tar.bz2"), &config, &sequence, false),
Some("backup_file.tar.bz2".to_string())
);
assert_eq!(
clean_filename(OsStr::new("data set.tar.xz"), &config, false),
clean_filename(OsStr::new("data set.tar.xz"), &config, &sequence, false),
Some("data_set.tar.xz".to_string())
);
// Multiple dots (not a double extension)
assert_eq!(
clean_filename(OsStr::new("foo..bar.txt"), &config, false),
clean_filename(OsStr::new("foo..bar.txt"), &config, &sequence, false),
Some("foo.bar.txt".to_string())
);
}
@ -434,16 +551,17 @@ mod tests {
#[test]
fn test_clean_filename_special_identifiers() {
let config = Config::default();
let sequence = Sequence::standard();
// C++ should be preserved
assert_eq!(
clean_filename(OsStr::new("test C++.txt"), &config, false),
clean_filename(OsStr::new("test C++.txt"), &config, &sequence, false),
Some("test_C++.txt".to_string())
);
// C# should be preserved
assert_eq!(
clean_filename(OsStr::new("guide C#.pdf"), &config, false),
clean_filename(OsStr::new("guide C#.pdf"), &config, &sequence, false),
Some("guide_C#.pdf".to_string())
);
}
@ -451,15 +569,16 @@ mod tests {
#[test]
fn test_clean_filename_no_change_needed() {
let config = Config::default();
let sequence = Sequence::standard();
// Already clean filenames should return None
assert_eq!(
clean_filename(OsStr::new("clean_file.txt"), &config, false),
clean_filename(OsStr::new("clean_file.txt"), &config, &sequence, false),
None
);
assert_eq!(
clean_filename(OsStr::new("another-file.pdf"), &config, false),
clean_filename(OsStr::new("another-file.pdf"), &config, &sequence, false),
None
);
}
@ -467,10 +586,11 @@ mod tests {
#[test]
fn test_clean_filename_empty_after_cleaning() {
let config = Config::default();
let sequence = Sequence::standard();
// File with only special chars should become "unnamed"
assert_eq!(
clean_filename(OsStr::new("###.txt"), &config, false),
clean_filename(OsStr::new("###.txt"), &config, &sequence, false),
Some("unnamed.txt".to_string())
);
}
@ -478,10 +598,11 @@ mod tests {
#[test]
fn test_clean_filename_apostrophe() {
let config = Config::default();
let sequence = Sequence::standard();
// Apostrophes should be removed (not replaced with underscore)
assert_eq!(
clean_filename(OsStr::new("O'Reilly.pdf"), &config, false),
clean_filename(OsStr::new("O'Reilly.pdf"), &config, &sequence, false),
Some("OReilly.pdf".to_string())
);
}

View file

@ -1 +0,0 @@
{"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":{}}

View file

@ -1,3 +0,0 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[]","declared_features":"[]","target":6121981589821494128,"profile":3878794443245516162,"path":10602529704205407992,"deps":[[496936660758387454,"walkdir",false,16653160629585727827],[781203651122893512,"itertools",false,4856790396137166786],[2253645963862362999,"glob",false,17901889553727651475],[2751633865096478575,"once_cell",false,404175244902379717],[4861353637455856501,"emojis",false,7812852181959777352],[5957121993870933949,"indicatif",false,6846573194333882574],[6903225003750382070,"env_logger",false,12129958749168747241],[8121005825001993377,"toml",false,14529174445398999363],[8444115378192700076,"anyhow",false,15790217775176775280],[9300925758419628329,"log",false,7601692817919657737],[9722518470958012960,"clap",false,6017947166156778644],[11266840602298992523,"thiserror",false,2303909277957177506],[11641382387439738731,"regex",false,1153832817102413080],[11892580040701647366,"serde",false,16170242140613763432],[16233166307772572446,"unicode_segmentation",false,13473434130131659026],[17775862536196513609,"rayon",false,4741632797656246936],[17902303992486982172,"dirs",false,15255450532072959597]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/NameToUnix-52eb14cd7f851fcf/dep-bin-NameToUnix"}}],"rustflags":[],"metadata":11238891150943416482,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"perf-literal\", \"std\"]","declared_features":"[\"default\", \"logging\", \"perf-literal\", \"std\"]","target":9771195463141993919,"profile":11260102936901317155,"path":16307491102300075524,"deps":[[554324495028472449,"memchr",false,217317700187571089]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/aho-corasick-17f8b4266e41b6ce/dep-lib-aho_corasick"}}],"rustflags":[],"metadata":13904389431191498124,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
16e4bdea9a608b73

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"auto\", \"default\", \"wincon\"]","declared_features":"[\"auto\", \"default\", \"test\", \"wincon\"]","target":1736373845211751465,"profile":5847708262638652504,"path":15382520749374289969,"deps":[[821897733253474908,"anstyle",false,18094389414750515593],[6726333832837302156,"anstyle_query",false,6517661958451464783],[8720183142424604966,"utf8parse",false,18444629462088036825],[9119385831240683871,"is_terminal_polyfill",false,15365458187358078183],[16168342247272166835,"anstyle_parse",false,11337828736863921905],[17599588001959536047,"colorchoice",false,1614739890724786504]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/anstream-c3c9a361c5ec1896/dep-lib-anstream"}}],"rustflags":[],"metadata":7500874485387469444,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
89f1caf54d2f1cfb

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":4691279112367741833,"profile":5847708262638652504,"path":14159642671587559024,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/anstyle-86948fab8bcbae58/dep-lib-anstyle"}}],"rustflags":[],"metadata":14064844656010464607,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"default\", \"utf8\"]","declared_features":"[\"core\", \"default\", \"utf8\"]","target":985948777999996156,"profile":5847708262638652504,"path":4210820189472297458,"deps":[[8720183142424604966,"utf8parse",false,18444629462088036825]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/anstyle-parse-23f5e4434e53721e/dep-lib-anstyle_parse"}}],"rustflags":[],"metadata":9799137552285937175,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[]","declared_features":"[]","target":2663518930196293257,"profile":5847708262638652504,"path":1082465322798495850,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/anstyle-query-537770b39eee8b06/dep-lib-anstyle_query"}}],"rustflags":[],"metadata":12668695791606146315,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"default\", \"std\"]","declared_features":"[\"backtrace\", \"default\", \"std\"]","target":13708040221295731214,"profile":12092956212200378817,"path":8693219203733942013,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/anyhow-661dc695d4d2effa/dep-build-script-build-script-build"}}],"rustflags":[],"metadata":17154292783084528516,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
70767807681f22db

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"default\", \"std\"]","declared_features":"[\"backtrace\", \"default\", \"std\"]","target":9658695707525313347,"profile":11260102936901317155,"path":16004131161169875048,"deps":[[8444115378192700076,"build_script_build",false,1931718327452350288]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/anyhow-c9b83c3045989c9e/dep-lib-anyhow"}}],"rustflags":[],"metadata":17154292783084528516,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[8444115378192700076,"build_script_build",false,6721496459697306]],"local":[{"RerunIfChanged":{"output":"release/build/anyhow-e9d92686298e2aea/output","paths":["build/probe.rs"]}},{"RerunIfEnvChanged":{"var":"RUSTC_BOOTSTRAP","val":null}}],"rustflags":[],"metadata":0,"config":0,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
94ecd72b100b8453

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"color\", \"default\", \"derive\", \"error-context\", \"help\", \"std\", \"suggestions\", \"usage\"]","declared_features":"[\"cargo\", \"color\", \"debug\", \"default\", \"deprecated\", \"derive\", \"env\", \"error-context\", \"help\", \"std\", \"string\", \"suggestions\", \"unicode\", \"unstable-derive-ui-tests\", \"unstable-doc\", \"unstable-ext\", \"unstable-styles\", \"unstable-v5\", \"usage\", \"wrap_help\"]","target":12724100863246979317,"profile":11126873348164009224,"path":7288412796159717841,"deps":[[4771800536484884094,"clap_derive",false,9333237172541925279],[10543150655979683594,"clap_builder",false,11544520963803449429]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/clap-e9c6ad76aa568e1d/dep-lib-clap"}}],"rustflags":[],"metadata":13636260659328210681,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"color\", \"error-context\", \"help\", \"std\", \"suggestions\", \"usage\"]","declared_features":"[\"cargo\", \"color\", \"debug\", \"default\", \"deprecated\", \"env\", \"error-context\", \"help\", \"std\", \"string\", \"suggestions\", \"unicode\", \"unstable-doc\", \"unstable-ext\", \"unstable-styles\", \"unstable-v5\", \"usage\", \"wrap_help\"]","target":4540639333657397710,"profile":11126873348164009224,"path":17035136592378992176,"deps":[[821897733253474908,"anstyle",false,18094389414750515593],[967775003968733193,"strsim",false,4892122836260516362],[2754101768631515696,"anstream",false,8325854554604037142],[3140197793370367388,"clap_lex",false,8909592952030357950]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/clap_builder-de574c5a5b478a16/dep-lib-clap_builder"}}],"rustflags":[],"metadata":13636260659328210681,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"default\"]","declared_features":"[\"debug\", \"default\", \"deprecated\", \"raw-deprecated\", \"unstable-v5\"]","target":3781261180330156922,"profile":6102714352311999568,"path":11331898117932305525,"deps":[[13033644984628948268,"proc_macro2",false,8300779761218470861],[13203937751714536251,"syn",false,9862373649362752304],[16133888191189175860,"quote",false,3817173719678386389],[17175234422038868540,"heck",false,18199109636904376427]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/clap_derive-bf5a7f3714bb7b87/dep-lib-clap_derive"}}],"rustflags":[],"metadata":9083421305396387959,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
bee917b7953ba57b

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[]","declared_features":"[]","target":5587326852571317598,"profile":11126873348164009224,"path":2020453500475058870,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/clap_lex-9ac9b1caeb1d3c0f/dep-lib-clap_lex"}}],"rustflags":[],"metadata":14823610342382530208,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[]","declared_features":"[]","target":10544268938077819509,"profile":5847708262638652504,"path":17421671175600072335,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/colorchoice-ca24d91229698482/dep-lib-colorchoice"}}],"rustflags":[],"metadata":5376015212253958680,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
5492b3adc5f8f811

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"ansi-parsing\", \"unicode-width\"]","declared_features":"[\"ansi-parsing\", \"default\", \"unicode-width\", \"windows-console-colors\"]","target":8374820256266716131,"profile":11260102936901317155,"path":842659123297130874,"deps":[[2751633865096478575,"once_cell",false,404175244902379717],[4024328380392812020,"unicode_width",false,350253125875139862],[11698369227143406027,"libc",false,12256160239455567883]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/console-3b2c3b25f57d32dc/dep-lib-console"}}],"rustflags":[],"metadata":8886294787439230123,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":8674947694680330387,"profile":10468086882058334769,"path":734875100910984840,"deps":[[13100939403401765317,"crossbeam_utils",false,9951845909837846733],[17638357056475407756,"crossbeam_epoch",false,2548151048078050890]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/crossbeam-deque-fda071f68d178793/dep-lib-crossbeam_deque"}}],"rustflags":[],"metadata":14304628380895324452,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"alloc\", \"std\"]","declared_features":"[\"alloc\", \"default\", \"loom\", \"loom-crate\", \"nightly\", \"std\"]","target":3011025219128477647,"profile":11260102936901317155,"path":17679954352219338837,"deps":[[13100939403401765317,"crossbeam_utils",false,9951845909837846733]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/crossbeam-epoch-0f0085168bed6545/dep-lib-crossbeam_epoch"}}],"rustflags":[],"metadata":8562320424510714295,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"loom\", \"nightly\", \"std\"]","target":14378767424822979028,"profile":10468086882058334769,"path":16187828673734176999,"deps":[[13100939403401765317,"build_script_build",false,7972816566730721956]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/crossbeam-utils-75f633aca295a9b6/dep-lib-crossbeam_utils"}}],"rustflags":[],"metadata":1609393243086812936,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[13100939403401765317,"build_script_build",false,11237325336941604364]],"local":[{"RerunIfChanged":{"output":"release/build/crossbeam-utils-bf70109116b76756/output","paths":["no_atomic.rs"]}}],"rustflags":[],"metadata":0,"config":0,"compile_kind":0}

View file

@ -1 +0,0 @@
{"rustc":2484451964687019519,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"loom\", \"nightly\", \"std\"]","target":9652763411108993936,"profile":1689406044471684405,"path":6340828273201808646,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/crossbeam-utils-ddf01c9607bc9735/dep-build-script-build-script-build"}}],"rustflags":[],"metadata":1609393243086812936,"config":2202906307356721367,"compile_kind":0}

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

View file

@ -1 +0,0 @@
This file has an mtime of when this was started.

Some files were not shown because too many files have changed in this diff Show more