# 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](CHANGELOG.md)** - 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`:

```toml
[dependencies]
freneng = "0.1.0"
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

```rust
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

```rust
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.
