fren
File renamer that understands dates - with slugify, Unicode, and CamelCase smarts
fren is a command-line tool for batch-renaming files and directories. It detects dates inside filenames in 17+ formats, converts them to ISO 8601, slugifies the rest of the name, splits CamelCase, normalizes Unicode, and lowercases extensions. It also includes a separate merge command for combining directories with automatic conflict resolution.
Features
- ๐ Date Detection: Recognizes 17+ formats inside filenames (human-readable, ISO, datetime, minute-precision) and rewrites them as ISO 8601
- ๐ค Slugification: Cleans filenames by replacing spaces and punctuation with a single separator (default
-) - ๐ซ CamelCase Splitting:
WhatsAppbecomesWhats-App,JSONFilebecomesJSON-File - ๐ Unicode Normalization: Strips accents (
Bancรกrios->Bancarios) via NFKC + ASCII transliteration - ๐ Directory Merging: A separate
fren mergecommand moves files between directories, appending_Copy/_Copy1/_Copy2on conflicts - ๐ Exclusions: Skip specific paths with
-x - ๐ Hidden Files: Entries starting with
.are skipped automatically - ๐ Dry-run by default:
fren rename DIRpreviews;--applyis required to actually rename - ๐จ Colored output: Files in bright green, directories in bright blue, with the unchanged parent path dimmed
- ๐ Transaction log: Every applied batch is recorded as JSONL under
${XDG_STATE_HOME:-~/.local/state}/fren/log/
Installation
From source:
This installs the fren binary to ~/.cargo/bin/.
Commands
fren rename
Rename files and directories with slugify + ISO date detection.
Options:
-x, --exclude PATH: Exclude one or more paths (can be repeated)--apply: Actually perform the renames (without this,frenonly prints what it would do)--no-log: Skip writing the transaction log--log-dir DIR: Override transaction-log directory
Examples:
# Preview (dry-run is the default)
# Actually rename
# Multiple directories with exclusions
fren merge
Merge source directories into a target directory. Move-only - filenames are preserved (with _Copy suffixes on conflicts). If you also want to rename the merged contents, run fren rename afterwards.
Options:
--apply: Actually perform the moves
Examples:
# Preview
# Apply
# Merge several into the current directory
fren completions
Print shell completions.
How rename works
The pipeline transforms filenames in this order:
- Unicode normalize (NFKC) and transliterate non-ASCII to ASCII
- Inject separator at CamelCase boundaries (
WhatsApp->Whats_App) - Inject separator at "at"-time patterns (
2019-08-21 at 14.24.19->2019-08-21_14_24_19) - Slugify: replace whitespace and punctuation with the internal separator
- Detect dates and rewrite them as ISO 8601
- Apply case mode (default: preserve original case)
- Collapse consecutive separators and substitute to user separator (default
-) - Lowercase the file extension
Examples
Hello World 2024-01-15.txt -> Hello-World-2024-01-15.txt
WhatsApp Image 2024-01-15 at 12.30.45.jpg -> Whats-App-Image-2024-01-15T12-30-45.jpg
CamelCaseFile.PDF -> Camel-Case-File.pdf
Bancรกrios.txt -> Bancarios.txt
report-25-04-2017.pdf -> report-2017-04-25.pdf
photo_20191020.jpg -> photo-2019-10-20.jpg
2026-05-03-18-57.log -> 2026-05-03T18-57-00.log
Supported date formats
- Human-readable:
DD_MM_YYYY,DD/MM/YYYY,DD.MM.YYYY,DD-MM-YY,DDMMYYYY,DDMMYY,MM_YYYY - ISO / inverted:
YYYY-MM-DD,YYYY_MM_DD,YYYYMMDD,YYYY_MM - Datetime (full):
DD_MM_YYYY_HH_mm_ss,YYYY_MM_DD_HH_mm_ss,YYYYMMDDHHmmss,YYYYMMDD_HHmmss,DD_MM_YY_HH_mm_ss,YY_MM_DD_HH_mm_ss - Datetime (minute-precision, zero-second pad):
DD_MM_YYYY_HH_mm,YYYY_MM_DD_HH_mm,DDMMYYYYHHmm
Two-digit years between (current_year + 10) and 99 are interpreted as 19YY; otherwise 20YY. With the system clock at 2026 this means 30..=99 -> 1930..1999 and 00..=29 -> 2000..2029, except dates more than 10 years in the future, which roll back a century.
How merge works
fren merge TARGET SOURCES...:
- Walks each source recursively
- Computes the target path =
TARGET / relative_subpath_from_source - If the target file already exists (or another move in the batch already claimed that path), appends
_Copy,_Copy1,_Copy2, ... to the stem until a free name is found - Creates intermediate directories as needed
- Moves the file with
std::fs::rename - Skips
.DS_Storeand similar metadata files
Source directory structure is preserved verbatim. Only files are moved; empty source directories remain.
Architecture
fren is a Cargo workspace with three crates:
crates/slug-preserve(internal): a case-preserving slugifier. Unlike most Rust slug crates that always lowercase, this one supports five case modes:Preserve,Lower,Upper,Title,Capitalize.crates/fren: the library. Exposesslugify_camel_iso,plan,execute,merge_directories,unique_file_name, plus the public type surface (RenamePlan,DetectedDate,FrenError,ConflictPolicy,SlugOpts,LogSink,JsonlLogSink, etc.). No CLI dependencies; library discipline lints denyprint_stdout,print_stderr,panic,unwrap_used,expect_used.crates/fren-cli: a thin clap-derive binary that consumes the library.
The library is the source of truth. The binary parses arguments, builds option structs, and formats output. Other Rust projects can embed fren directly without spawning a subprocess.
Safety
- Dry-run is the default.
--applyis required to mutate the filesystem. - No silent overwrites. Every rename pre-checks the target and refuses to proceed if it exists outside the batch (the
Abortconflict policy). - Within-batch collisions are detected at planning time. If two source paths would rename to the same target, the batch aborts before any I/O happens.
- Bottom-up execution. Deeper paths are renamed first, so a directory rename never invalidates the queued paths of its children. This fixes a class of bugs that affected the original Python implementation.
- Case-only renames on case-insensitive filesystems (macOS APFS, Windows NTFS) route through a temporary name to avoid silent no-ops.
- Transaction log. Every applied batch writes a JSONL file under
${XDG_STATE_HOME:-~/.local/state}/fren/log/<timestamp>-<batch-uuid>.jsonlso applied changes can be audited or, in the future, undone.
Development
Pre-commit:
License
See the project repository for license information.
Author
W. Augusto Andreoli (andreoliwa@sent.com)