1 unstable release
Uses new Rust 2024
| new 0.1.2 | Jan 3, 2026 |
|---|---|
| 0.1.1 |
|
| 0.1.0 |
|
#843 in Filesystem
Used in frencli
79KB
1K
SLoC
freneng
A file renaming engine for batch file renaming. This library provides a robust, safe, and powerful DSL for batch file renaming, suitable for use in CLI tools, GUI applications, or web backends.
Inspired by the workings of the legendary Total Commander Multi-Rename Tool.
Contributions are always welcome, whether through pull requests or issues. It’s difficult—if not impossible—to exhaustively test this kind of code across all environments.
📋 Changelog - See what's new in each version
Features
- Async-First Design: All operations are asynchronous and non-blocking, perfect for GUI applications and modern async Rust
- Recursive File Finding: Support for recursive directory searches with
**glob patterns, preserving full directory structure - Sequential Pattern Parsing: Supports a wide range of tokens (
%N,%E,%C, etc.) and modifiers (%L,%U,%R,%X) - Order-Aware Processing: Modifiers apply to accumulated results, making patterns like
%N%L.%Ework correctly - Path Preservation: When processing recursively found files, full paths are preserved - files stay in their original directories
- Safety First: Built-in detection for empty filenames, OS-aware validation, and permission checks
- Context Awareness: Access to file metadata (modification dates) and parent directory information
- Undo Facility: History tracking logic to safely reverse rename operations
- Comprehensive Validation: OS-specific filename validation, permission checks, and circular rename detection
- Audit Logging: Immutable audit trail for all rename operations
Pattern Processing
Patterns are processed in two phases:
- Placeholder Expansion: Tokens like
%N,%E,%Care replaced with their values - Modifier Application: Modifiers like
%L,%U,%Rare applied left-to-right to the accumulated result
When a modifier is encountered, it operates on everything accumulated so far in the result string. This means:
%L%N.%Eand%N%L.%Eboth work (modifier before or after placeholder)- Order matters:
%U%N%L.%Ewill uppercase then lowercase the name
Placeholders
| Token | Description | Example (file.txt) |
|---|---|---|
%N |
Filename without extension | file |
%E |
Extension without the dot | txt |
%F |
Full filename (name + extension) | file.txt |
%C |
Counter (starts at 1) | 1, 2, ... |
%C3 |
Counter with padding (3 digits) | 001, 002, ... |
%P |
Immediate parent directory name | Documents |
%P1-3 |
Substring of parent directory | Doc |
%D |
Current date (YYYY-MM-DD) | 2025-12-18 |
%H |
Current time (HH-MM-SS) | 14-30-05 |
%FD |
File modification date | 2025-12-10 |
%FH |
File modification time | 09-15-00 |
Substring Selection
You can extract parts of the name or extension using start-end indices (1-indexed). Use a double hyphen -- to count from the end.
%N1-3: Chars 1 to 3 of the name.%N5-: Chars from index 5 to the end of the name.%N-5: Chars from the beginning up to index 5.%N--3: The name minus the last 3 characters (shorthand for from beginning to 3rd from end).%N3--4: Chars starting from index 3 up to the 4th character from the end.%E1-2: Chars 1 to 2 of the extension.
Modifiers
%L: Lowercase the entire accumulated result.%U: Uppercase the entire accumulated result.%T: Title case the entire accumulated result (capitalizes after spaces, dots, dashes, underscores).%M: Trim leading and trailing whitespace from the accumulated result.%R/old/new: Replace occurrences ofoldwithnewin the accumulated result. Supports multiple delimiters:/,|,:,,,@.%X/pattern/new: Regex replacement in the accumulated result. Supports capturing groups and standard regex syntax.
Installation
Add this to your Cargo.toml:
[dependencies]
freneng = "0.1.2"
tokio = { version = "1", features = ["fs", "io-util", "macros", "rt-multi-thread"] }
futures = "0.3"
Note: freneng requires a tokio runtime. Make sure your application uses #[tokio::main] or provides a tokio runtime.
Usage Example
use freneng::RenamingEngine;
use freneng::find_matching_files_recursive;
use std::path::PathBuf;
#[tokio::main]
async fn main() {
let engine = RenamingEngine;
// Find files recursively
let files = find_matching_files_recursive("**/*.jpg", true)
.await
.unwrap();
// Generate a preview plan (async)
let preview = engine.generate_preview(&files, "%L%N_v2.%E")
.await
.unwrap();
for rename in preview.renames {
// old_path and new_path contain full paths, preserving directory structure
println!("{} -> {}", rename.old_path.display(), rename.new_path.display());
// new_name contains just the filename
println!(" New filename: {}", rename.new_name);
}
// Validate renames (async)
let validation = engine.validate(&preview.renames, false).await.unwrap();
if !validation.issues.is_empty() {
for issue in validation.issues {
eprintln!("Validation issue: {:?}", issue);
}
}
}
Path Handling
When generate_preview processes files (including recursively found files), it preserves the full directory structure:
old_path: Full original file path (e.g.,/path/to/docs/document.pdf)new_path: Full new file path in the same directory (e.g.,/path/to/docs/document_v2.pdf)new_name: Just the filename portion (e.g.,document_v2.pdf)
Files found recursively maintain their directory structure - a file in subdir/nested/file.txt will be renamed to subdir/nested/<new_name>, not moved to the root directory.
Async API
All operations in freneng are asynchronous. You'll need a Tokio runtime to use the library:
- Use
#[tokio::main]for standalone applications - Use
tokio::spawnortokio::task::spawn_blockingin existing async contexts - All file system operations are non-blocking and suitable for GUI event loops
Library Architecture
freneng is designed as a library-first crate, making it easy to use as a backend for GUI applications, web backends, or other tools. The core logic is contained in the RenamingEngine struct.
Example Library Usage
use freneng::RenamingEngine;
use std::path::PathBuf;
#[tokio::main]
async fn main() {
let engine = RenamingEngine;
let files = vec![PathBuf::from("photo.jpg")];
let preview = engine.generate_preview(&files, "%L%N_v2.%E").await.unwrap();
for rename in preview.renames {
println!("{} -> {}", rename.old_path.display(), rename.new_name);
}
}
License
This project is licensed under the MIT License.
Dependencies
~6–11MB
~197K SLoC