Thanks to visit codestin.com
Credit goes to lib.rs

#file-rename #batch #async #pattern

freneng

A useful, async-first file renaming library

1 unstable release

Uses new Rust 2024

new 0.1.2 Jan 3, 2026
0.1.1 Dec 21, 2025
0.1.0 Dec 20, 2025

#843 in Filesystem


Used in frencli

MIT license

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.%E work 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:

  1. Placeholder Expansion: Tokens like %N, %E, %C are replaced with their values
  2. Modifier Application: Modifiers like %L, %U, %R are 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.%E and %N%L.%E both work (modifier before or after placeholder)
  • Order matters: %U%N%L.%E will 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 of old with new in 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::spawn or tokio::task::spawn_blocking in 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