reformat
A modular code transformation framework. Each transformer handles one concern -- renaming files, normalising whitespace, converting identifier case, etc. -- and the pipeline system lets you compose them into multi-step workflows that run in a single invocation.
Features
Modular transformers
Every transformation is an independent module with its own options struct,
sensible defaults, and a consistent interface (process(path) -> Result).
Transformers can be used standalone via CLI subcommands, composed into
pipelines, or called directly as a Rust library from reformat-core.
| Transformer | CLI subcommand | What it does |
|---|---|---|
FileRenamer |
rename_files |
Case transforms, prefix/suffix operations, timestamps on filenames |
CaseConverter |
convert |
Convert identifiers between 6 case formats (camel, pascal, snake, screaming snake, kebab, screaming kebab) |
WhitespaceCleaner |
clean |
Strip trailing whitespace while preserving line endings |
EmojiTransformer |
emojis |
Replace task/status emojis with text alternatives, remove decorative emojis |
FileGrouper |
group |
Organise files by common prefix into subdirectories, detect and fix broken references |
EndingsNormalizer |
endings |
Normalise line endings to LF, CRLF, or CR (skips binary files automatically) |
IndentNormalizer |
indent |
Convert between tabs and spaces with configurable width, tab-stop-aware |
ContentReplacer |
replace |
Regex find-and-replace with capture group support, multiple sequential patterns |
HeaderManager |
header |
Insert or update file headers (license, copyright) with year templating |
All transformers share common behaviours: recursive directory traversal,
file extension filtering, dry-run mode, and automatic skipping of hidden files
and build directories (.git, node_modules, target, __pycache__, etc.).
Pipelines: presets and jobs
Transformers become more useful when composed. The pipeline system chains any combination of the above steps and runs them in order on the same path.
There are two ways to define a pipeline, reflecting two different needs:
- Presets (
-p) -- Reusable, named pipelines stored inreformat.jsonat the project root. Version-controlled, shared across a team, run repeatedly. - Jobs (
--job) -- Ad-hoc, throwaway pipelines loaded from any file or stdin. No project config needed. Ideal for one-off migrations, scripted CI transforms, or quick multi-pattern replacements.
Both use the same JSON format (a steps array plus per-step config) and the
same execution engine. The only difference is where they are stored.
# As a reusable preset (stored in reformat.json under a name):
# As a throwaway job (from a file):
# As a throwaway job (piped from stdin):
|
Quick processing (default command)
For the common case of cleaning up a directory, reformat <path> runs three
transformations in a single optimised pass -- rename to lowercase, replace task
emojis, strip trailing whitespace -- without needing a config file.
Library-first design
The project is organised as a Cargo workspace:
- reformat-core -- All transformation logic. Every struct and option type is a public API. Use this crate directly if you want programmatic access.
- reformat-cli -- Thin CLI wrapper using clap. Parses arguments, loads config, calls into core.
- reformat-plugins -- Plugin system foundation (not yet active).
Observability
- Multi-level verbosity (
-v,-vv,-vvv), quiet mode (-q), file logging (--log-file) - Progress spinners, automatic operation timing, colour-coded output
- Dry-run mode on every transformer and every pipeline step
Installation
Install from crates.io:
Or install from the workspace:
Or build from source:
The binary will be at ./target/release/reformat
Library Usage
Add to your Cargo.toml:
[]
= "0.1.6"
Case Conversion
use ;
let converter = new?;
converter.process_directory?;
Whitespace Cleaning
use ;
let mut options = default;
options.dry_run = false;
options.recursive = true;
let cleaner = new;
let = cleaner.process?;
println!;
Combined Processing (Default Command)
use ;
let mut options = default;
options.recursive = true;
options.dry_run = false;
let processor = new;
let stats = processor.process?;
println!;
println!;
println!;
Line Ending Normalization
use ;
let options = EndingsOptions ;
let normalizer = new;
let = normalizer.process?;
println!;
Indentation Normalization
use ;
let options = IndentOptions ;
let normalizer = new;
let = normalizer.process?;
println!;
Regex Find-and-Replace
use ;
let options = ReplaceOptions ;
let replacer = new?;
let = replacer.process?;
println!;
File Header Management
use ;
let options = HeaderOptions ;
let manager = new?;
let = manager.process?;
println!;
File Grouping
use ;
let mut options = default;
options.strip_prefix = true; // Remove prefix from filenames
options.from_suffix = false; // Set true to split at LAST separator
options.min_count = 2; // Require at least 2 files to create a group
options.dry_run = false;
let grouper = new;
let stats = grouper.process?;
println!;
println!;
println!;
Quick Start
Default Command (Recommended)
The fastest way to clean up your code:
# Process directory (non-recursive)
# Process recursively
# Preview changes without modifying files
What it does:
- Renames files to lowercase
- Transforms task emojis: ✅ → [x], ☐ → [ ]
- Removes trailing whitespace
Example:
# Clean up an entire project directory
# Preview changes first
# Process a single file
Output:
Renamed '/tmp/TestFile.txt' -> '/tmp/testfile.txt'
Transformed emojis in '/tmp/testfile.txt'
Cleaned 2 lines in '/tmp/testfile.txt'
Processed files:
- Renamed: 1 file(s)
- Emoji transformations: 1 file(s) (1 changes)
- Whitespace cleaned: 1 file(s) (2 lines)
Usage
Case Conversion
Basic conversion (using subcommand):
Recursive directory conversion:
Dry run (preview changes):
Add prefix to all converted identifiers:
Filter files by pattern:
Only convert specific identifiers:
Whitespace Cleaning
Clean all default file types in current directory:
Clean with dry-run to preview changes:
Clean only specific file types:
Clean a single file:
Emoji Transformation
Replace task emojis with text in markdown files:
Process with dry-run to preview changes:
Only replace task emojis, keep other emojis:
Process specific file types:
File Grouping
Organize files by common prefix into subdirectories:
# Preview what groups would be created
# Dry run to see what would happen
# Group files (keep original filenames)
# Group files and strip prefix from filenames
# Group by suffix (split at LAST separator) - for multi-part prefixes
# Process subdirectories recursively
# Use custom separator (e.g., hyphen)
# Require at least 3 files to create a group
Example transformation with --strip-prefix (splits at FIRST separator):
Before: After:
templates/ templates/
├── wbs_create.tmpl ├── wbs/
├── wbs_delete.tmpl │ ├── create.tmpl
├── wbs_list.tmpl │ ├── delete.tmpl
├── work_package_create.tmpl │ └── list.tmpl
├── work_package_delete.tmpl ├── work/
└── other.txt │ ├── package_create.tmpl
│ └── package_delete.tmpl
└── other.txt
Example transformation with --from-suffix (splits at LAST separator):
Before: After:
templates/ templates/
├── activity_relationships_list.tmpl ├── activity_relationships/
├── activity_relationships_create.tmpl │ ├── list.tmpl
├── activity_relationships_delete.tmpl │ ├── create.tmpl
├── user_profile_edit.tmpl │ └── delete.tmpl
├── user_profile_view.tmpl ├── user_profile/
└── other.txt │ ├── edit.tmpl
│ └── view.tmpl
└── other.txt
Broken Reference Detection
After grouping files, reformat can scan your codebase for broken references:
# Interactive mode (default) - prompts for scanning
# Output:
# Grouping complete:
# - Directories created: 2
# - Files moved: 5
#
# Changes recorded to: changes.json
#
# Would you like to scan for broken references? [y/N]: y
# Enter directories to scan: src
#
# Found 3 broken reference(s).
# Proposed fixes written to: fixes.json
#
# Review fixes.json and apply changes? [y/N]: y
# Fixed 3 reference(s) in 2 file(s).
# Non-interactive mode with automatic scanning
# Skip reference scanning entirely
Generated files:
changes.json- Record of all file operations (for auditing)fixes.json- Proposed reference fixes (review before applying)
Line Ending Normalization
Normalize line endings across files:
# Convert to Unix line endings (LF) - default
# Convert to Windows line endings (CRLF)
# Preview changes
# Process specific file types
Indentation Normalization
Convert between tabs and spaces:
# Convert tabs to spaces (4-wide, default)
# Convert tabs to 2-space indentation
# Convert spaces to tabs
# Preview changes
Regex Find-and-Replace
Apply regex patterns across files:
# Simple text replacement
# Regex with capture groups
# Dry run
# Filter by extension
For multiple patterns, use a preset (see Presets section below).
File Header Management
Insert or update file headers:
# Insert a license header
# Insert header with automatic year
# Preview changes
# Process specific file types
Presets
Define reusable transformation pipelines in a reformat.json file in your project root:
Run a preset:
# Dry-run to preview changes
# Run a different preset
Available step configuration options:
| Step | Options |
|---|---|
rename |
case_transform (lowercase/uppercase/capitalize), space_replace (underscore/hyphen), recursive, include_symlinks |
emojis |
replace_task_emojis, remove_other_emojis, file_extensions, recursive |
clean |
remove_trailing, file_extensions, recursive |
convert |
from_format, to_format, file_extensions, recursive, prefix, suffix, glob, word_filter |
group |
separator, min_count, strip_prefix, from_suffix, recursive |
endings |
style (lf/crlf/cr), file_extensions, recursive |
indent |
style (spaces/tabs), width, file_extensions, recursive |
replace |
patterns (array of {find, replace}), file_extensions, recursive |
header |
text, update_year, file_extensions, recursive |
Steps without explicit configuration use sensible defaults.
Example preset using new transformers:
Jobs
Jobs are ad-hoc transformation pipelines for one-off tasks. A job file has the same
format as a single preset -- just a JSON object with steps and per-step config --
but is loaded from an arbitrary file (or stdin) instead of your project's reformat.json.
Run a job from a file:
Run a job from stdin:
|
Example job file for a multi-pattern replacement:
Jobs support dry-run mode:
When to use presets vs. jobs:
Presets (-p) |
Jobs (--job) |
|
|---|---|---|
| Source | reformat.json in project root |
Any file or stdin |
| Lifecycle | Reusable, version-controlled | Throwaway, ad-hoc |
| Use case | Standard project workflows | One-off migrations, scripted transforms |
Logging and Debugging
Control output verbosity:
# Info level output (-v)
# Debug level output (-vv)
# Silent mode (errors only)
# Log to file
Output example with -v:
2025-10-10T00:15:08.927Z [INFO] Converting from CamelCase to SnakeCase
2025-10-10T00:15:08.927Z [INFO] Target path: /tmp/test.py
2025-10-10T00:15:08.927Z [INFO] Recursive: false, Dry run: false
Converted '/tmp/test.py'
2025-10-10T00:15:08.931Z [INFO] Conversion completed successfully
2025-10-10T00:15:08.931Z [INFO] run_convert(), Elapsed=4.089125ms
Case Format Options
--from-camel/--to-camel- camelCase (firstName, lastName)--from-pascal/--to-pascal- PascalCase (FirstName, LastName)--from-snake/--to-snake- snake_case (first_name, last_name)--from-screaming-snake/--to-screaming-snake- SCREAMING_SNAKE_CASE (FIRST_NAME, LAST_NAME)--from-kebab/--to-kebab- kebab-case (first-name, last-name)--from-screaming-kebab/--to-screaming-kebab- SCREAMING-KEBAB-CASE (FIRST-NAME, LAST-NAME)
Examples
Case Conversion Examples
Convert Python file from camelCase to snake_case:
Convert C++ project from snake_case to PascalCase:
Preview converting JavaScript getters to snake_case:
Whitespace Cleaning Examples
Clean trailing whitespace from entire project:
Clean only Python files in src directory:
Preview what would be cleaned without making changes:
Emoji Transformation Examples
Transform task emojis in documentation:
Example transformation:
Before:
- -----
After:
- -----
Process only markdown files:
File Grouping Examples
Organize template files by prefix (split at first separator):
Organize files with multi-part prefixes (split at last separator):
# activity_relationships_list.tmpl -> activity_relationships/list.tmpl
Preview groups without making changes:
Example output:
Found 2 potential group(s):
wbs (3 files):
- wbs_create.tmpl
- wbs_delete.tmpl
- wbs_list.tmpl
work (2 files):
- work_package_create.tmpl
- work_package_delete.tmpl
Group files with hyphen separator:
Recursively organize nested directories:
Group files and automatically scan for broken references:
Example changes.json:
Example fixes.json:
Line Ending Normalization Examples
Normalize a cross-platform project to Unix endings:
Convert to Windows line endings for distribution:
Indentation Normalization Examples
Standardize a project to 4-space indentation:
Convert to 2-space indentation for JavaScript:
Convert to tabs:
Regex Find-and-Replace Examples
Update copyright year across all files:
Swap function argument order using capture groups:
File Header Examples
Add MIT license header to all Rust files:
Ensure all Python files have a header (preserves shebang):
Preset Examples
Run a multi-step cleanup preset:
# Define in reformat.json, then run:
# Output:
# rename: 3 file(s) renamed
# emojis: 2 file(s), 5 change(s)
# clean: 4 file(s), 12 line(s) cleaned
# Preset 'code' complete.
Preview preset changes without modifying files:
Case conversion preset:
License
MIT License. See LICENSE for details.