Compare commits
12 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92a3d66645 | |||
| a72dae1a60 | |||
| bd82fd7b30 | |||
| de9bb4fd03 | |||
| b08ff38a49 | |||
| ad44139e21 | |||
| 0c26e5244c | |||
| 2eab4e7592 | |||
| b84dd70f80 | |||
| cf091f4d4b | |||
| 2ec4d12d6c | |||
| d78e318d8a |
577 changed files with 1188 additions and 1847 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,7 +1,6 @@
|
|||
# Rust / Cargo
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
*.pdb
|
||||
|
||||
# IDEs
|
||||
|
|
|
|||
70
CHANGELOG.md
70
CHANGELOG.md
|
|
@ -5,6 +5,76 @@ 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
|
||||
|
|
|
|||
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -4,7 +4,7 @@ version = 4
|
|||
|
||||
[[package]]
|
||||
name = "NameToUnix"
|
||||
version = "0.3.0"
|
||||
version = "1.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
|
|
@ -16,7 +16,6 @@ dependencies = [
|
|||
"env_logger",
|
||||
"glob",
|
||||
"indicatif",
|
||||
"itertools",
|
||||
"log",
|
||||
"once_cell",
|
||||
"predicates",
|
||||
|
|
@ -24,7 +23,6 @@ dependencies = [
|
|||
"regex",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"unicode-segmentation",
|
||||
"walkdir",
|
||||
|
|
@ -478,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"
|
||||
|
|
|
|||
16
Cargo.toml
16
Cargo.toml
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "NameToUnix"
|
||||
version = "0.3.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,15 +26,13 @@ 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]
|
||||
|
|
@ -51,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 = """
|
||||
|
|
@ -66,5 +64,9 @@ 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
235
README.md
|
|
@ -1,12 +1,11 @@
|
|||
# NameToUnix
|
||||
|
||||
[](https://github.com/jamulix/NameToUnix/actions)
|
||||
[](https://github.com/jamulix/NameToUnix/releases)
|
||||
[](https://github.com/jamulix/NameToUnix/releases)
|
||||
[](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)
|
||||
|
||||
A powerful command line tool for cleaning up file names according to Linux conventions.
|
||||
|
|
@ -25,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
|
||||
|
||||
|
|
@ -47,36 +79,21 @@ Dies ist mein erstes Programm in Rust. (Bitte seid gnädig.)
|
|||
|
||||
## Installation
|
||||
|
||||
### Option 1: Pre-built Binary (Recommended)
|
||||
### Option 1: Pre-built Binary
|
||||
|
||||
Download the latest release for your platform from [GitHub Releases](https://github.com/jamulix/NameToUnix/releases):
|
||||
Pre-built binaries are available from [Forgejo Releases](https://kitux.de/forgejo/dschlueter/ntu/releases).
|
||||
|
||||
```bash
|
||||
# Linux x86_64
|
||||
wget https://github.com/jamulix/NameToUnix/releases/latest/download/ntu-linux-x86_64.tar.gz
|
||||
tar xzf ntu-linux-x86_64.tar.gz
|
||||
sudo mv ntu /usr/local/bin/
|
||||
Download and extract the appropriate binary for your platform, then move it to `/usr/local/bin/`.
|
||||
|
||||
# macOS Intel
|
||||
wget https://github.com/jamulix/NameToUnix/releases/latest/download/ntu-macos-x86_64.tar.gz
|
||||
tar xzf ntu-macos-x86_64.tar.gz
|
||||
sudo mv ntu /usr/local/bin/
|
||||
|
||||
# macOS Apple Silicon (M1/M2)
|
||||
wget https://github.com/jamulix/NameToUnix/releases/latest/download/ntu-macos-arm64.tar.gz
|
||||
tar xzf ntu-macos-arm64.tar.gz
|
||||
sudo mv ntu /usr/local/bin/
|
||||
```
|
||||
|
||||
### Option 2: Build from Source
|
||||
### 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
|
||||
|
||||
|
|
@ -103,31 +120,57 @@ 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:
|
||||
|
|
@ -136,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
|
||||
|
|
@ -247,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
|
||||
|
|
@ -270,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:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ _ntu() {
|
|||
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]' \
|
||||
|
|
@ -14,6 +18,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'
|
||||
|
|
|
|||
|
|
@ -7,15 +7,25 @@ _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 --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
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
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'
|
||||
|
|
|
|||
4
index.md
4
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
|
||||
|
||||
|
|
|
|||
64
install.sh
Executable file
64
install.sh
Executable 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)"
|
||||
114
man/ntu.1
114
man/ntu.1
|
|
@ -1,4 +1,4 @@
|
|||
.TH NTU 1 "2025-02-10" "NameToUnix 0.3.0" "User Commands"
|
||||
.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
|
||||
|
|
@ -11,11 +11,28 @@ 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 \-\-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
|
||||
|
|
@ -38,7 +55,8 @@ Allow renaming of the root directory itself (normally skipped)
|
|||
Process symbolic links and special files (normally skipped)
|
||||
.TP
|
||||
.BR \-\-no\-color
|
||||
Disable colored output
|
||||
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
|
||||
|
|
@ -70,6 +88,27 @@ 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
|
||||
|
|
@ -103,20 +142,67 @@ Process multiple directories:
|
|||
.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 (in order):
|
||||
looks for configuration files in the following locations. Later files
|
||||
override earlier ones (highest priority last):
|
||||
.PP
|
||||
.nf
|
||||
.RS
|
||||
./.NameToUnix.conf
|
||||
~/.config/NameToUnix/config.toml
|
||||
/etc/NameToUnix/config.toml
|
||||
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
|
||||
Configuration files can customize character replacements and other behavior.
|
||||
See the project documentation for configuration file format.
|
||||
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
|
||||
|
|
@ -136,9 +222,9 @@ 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://github.com/jamulix/NameToUnix/issues
|
||||
Report bugs at: https://kitux.de/forgejo/dschlueter/ntu/issues
|
||||
.SH AUTHOR
|
||||
Written by Dieter Schlüter <dieter.schlueter@linix.de>
|
||||
Written by Dieter Schlüter <dschlueter@kitux.de>
|
||||
.SH COPYRIGHT
|
||||
Copyright \(co 2025 Dieter Schlüter. Licensed under MIT License.
|
||||
.SH SEE ALSO
|
||||
|
|
@ -146,4 +232,4 @@ Copyright \(co 2025 Dieter Schlüter. Licensed under MIT License.
|
|||
.BR detox (1),
|
||||
.BR mv (1)
|
||||
.PP
|
||||
Full documentation at: https://github.com/jamulix/NameToUnix
|
||||
Full documentation at: https://kitux.de/forgejo/dschlueter/ntu
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
20
src/cli.rs
20
src/cli.rs
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
266
src/main.rs
266
src/main.rs
|
|
@ -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;
|
||||
|
|
@ -32,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
|
||||
|
|
@ -42,9 +42,11 @@ struct RenameOperation {
|
|||
new_path: PathBuf,
|
||||
}
|
||||
|
||||
/// Prüft ob farbige Ausgabe aktiviert sein soll
|
||||
/// 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::io::stdout().is_terminal()
|
||||
!no_color_flag && std::env::var_os("NO_COLOR").is_none() && std::io::stdout().is_terminal()
|
||||
}
|
||||
|
||||
/// Startpunkt des Programms
|
||||
|
|
@ -60,9 +62,39 @@ 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)?;
|
||||
// -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
|
||||
|
|
@ -86,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!("{}", format!("Fehler beim Durchlaufen von {}: {}", path.display(), e).red());
|
||||
match entry_result {
|
||||
Ok(entry) => entries.push(entry),
|
||||
Err(e) => error!(
|
||||
"{}",
|
||||
format!("Fehler beim Durchlaufen von {}: {}", path.display(), e).red()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,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
|
||||
|
|
@ -180,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 {
|
||||
|
|
@ -188,13 +226,15 @@ fn main() -> Result<()> {
|
|||
|
||||
if !args.quiet {
|
||||
if args.dry_run {
|
||||
info!("{} {} {}",
|
||||
info!(
|
||||
"{} {} {}",
|
||||
op.old_path.display().to_string().dimmed(),
|
||||
"->".yellow(),
|
||||
op.new_path.display().to_string().yellow()
|
||||
);
|
||||
} else {
|
||||
info!("{} {} {}",
|
||||
info!(
|
||||
"{} {} {}",
|
||||
op.old_path.display().to_string().dimmed(),
|
||||
"->".green(),
|
||||
op.new_path.display().to_string().green()
|
||||
|
|
@ -211,13 +251,15 @@ fn main() -> Result<()> {
|
|||
match fs::rename(&op.old_path, &op.new_path) {
|
||||
Ok(_) => renamed_count += 1,
|
||||
Err(e) => {
|
||||
error!("{}",
|
||||
error!(
|
||||
"{}",
|
||||
format!(
|
||||
"Fehler beim Umbenennen: {} -> {}: {}",
|
||||
op.old_path.display(),
|
||||
op.new_path.display(),
|
||||
e
|
||||
).red()
|
||||
)
|
||||
.red()
|
||||
);
|
||||
skipped_count += 1;
|
||||
}
|
||||
|
|
@ -235,15 +277,35 @@ fn main() -> Result<()> {
|
|||
// Zusammenfassung ausgeben (außer im quiet mode)
|
||||
if !args.quiet {
|
||||
info!("");
|
||||
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());
|
||||
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)".yellow());
|
||||
info!(
|
||||
"Modus: {}",
|
||||
"Dry-run (keine Änderungen)".yellow()
|
||||
);
|
||||
} else {
|
||||
info!("Erfolgreich umbenannt: {}", renamed_count.to_string().green().bold());
|
||||
info!(
|
||||
"Erfolgreich umbenannt: {}",
|
||||
renamed_count.to_string().green().bold()
|
||||
);
|
||||
if skipped_count > 0 {
|
||||
info!("Übersprungen/Fehler: {}", skipped_count.to_string().red());
|
||||
info!(
|
||||
"Übersprungen/Fehler: {}",
|
||||
skipped_count.to_string().red()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -251,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.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
235
src/sanitizer.rs
235
src/sanitizer.rs
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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":{}}
|
||||
|
|
@ -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/
|
||||
|
|
@ -1 +0,0 @@
|
|||
291cfff3ad3b4550
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
4dd8cd57cf0e9b09
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
16e4bdea9a608b73
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
89f1caf54d2f1cfb
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
f166fd702a0f589d
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
4f8a93e70463735a
|
||||
|
|
@ -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}
|
||||
|
|
@ -1 +0,0 @@
|
|||
9adcd6582ae11700
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
70767807681f22db
|
||||
|
|
@ -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}
|
||||
|
|
@ -1 +0,0 @@
|
|||
50b7e88995d7ce1a
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
94ecd72b100b8453
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
55088eada66036a0
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
9f27d541c2518681
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
bee917b7953ba57b
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
48d14d1760b56816
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
5492b3adc5f8f811
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
ffc01a41260e9eeb
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
4a46bba6e0d95c23
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
cd184fbb200f1c8a
|
||||
|
|
@ -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}
|
||||
|
|
@ -1 +0,0 @@
|
|||
a48a55f76c22a56e
|
||||
|
|
@ -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}
|
||||
|
|
@ -1 +0,0 @@
|
|||
0caee625dbfff29b
|
||||
|
|
@ -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}
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
This file has an mtime of when this was started.
|
||||
|
|
@ -1 +0,0 @@
|
|||
6daec09c6e3fb6d3
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"rustc":2484451964687019519,"features":"[]","declared_features":"[]","target":2202548160250307783,"profile":11260102936901317155,"path":16448924518955612045,"deps":[[8374856912967190420,"dirs_sys",false,15979463799541408436]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/dirs-a8018173808b5d84/dep-lib-dirs"}}],"rustflags":[],"metadata":2541453624792457215,"config":2202906307356721367,"compile_kind":0}
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue