rnmtool 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rnmtool/__init__.py +7 -0
- rnmtool/__main__.py +210 -0
- rnmtool-1.0.0.dist-info/LICENSE +21 -0
- rnmtool-1.0.0.dist-info/METADATA +468 -0
- rnmtool-1.0.0.dist-info/RECORD +8 -0
- rnmtool-1.0.0.dist-info/WHEEL +5 -0
- rnmtool-1.0.0.dist-info/entry_points.txt +2 -0
- rnmtool-1.0.0.dist-info/top_level.txt +1 -0
rnmtool/__init__.py
ADDED
rnmtool/__main__.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
rnmtool.__main__ - Entry point for the rnmtool CLI.
|
|
3
|
+
|
|
4
|
+
Features:
|
|
5
|
+
- Batch rename files in a directory
|
|
6
|
+
- Add prefix or suffix to filenames
|
|
7
|
+
- Find-and-replace text in filenames (with optional regex support)
|
|
8
|
+
- Change file extensions
|
|
9
|
+
- Convert filenames to lowercase, uppercase, or title case
|
|
10
|
+
- Dry-run mode to preview changes before applying them
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
rnmtool --help
|
|
14
|
+
python -m rnmtool --help
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import argparse
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ── Helpers ────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
def collect_files(directory: str, recursive: bool, pattern: str) -> list[Path]:
|
|
26
|
+
"""Return a sorted list of files matching *pattern* inside *directory*."""
|
|
27
|
+
base = Path(directory)
|
|
28
|
+
glob = "**/" + pattern if recursive else pattern
|
|
29
|
+
return sorted(p for p in base.glob(glob) if p.is_file())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def apply_transformations(name: str, args: argparse.Namespace) -> str:
|
|
33
|
+
"""Apply all requested transformations to a bare filename (no directory)."""
|
|
34
|
+
stem, suffix = os.path.splitext(name)
|
|
35
|
+
|
|
36
|
+
# 1. Find-and-replace (plain text or regex)
|
|
37
|
+
if args.find is not None:
|
|
38
|
+
if args.regex:
|
|
39
|
+
stem = re.sub(args.find, args.replace or "", stem)
|
|
40
|
+
else:
|
|
41
|
+
stem = stem.replace(args.find, args.replace or "")
|
|
42
|
+
|
|
43
|
+
# 2. Case conversion
|
|
44
|
+
if args.case == "lower":
|
|
45
|
+
stem = stem.lower()
|
|
46
|
+
elif args.case == "upper":
|
|
47
|
+
stem = stem.upper()
|
|
48
|
+
elif args.case == "title":
|
|
49
|
+
stem = stem.title()
|
|
50
|
+
|
|
51
|
+
# 3. Prefix / suffix
|
|
52
|
+
if args.prefix:
|
|
53
|
+
stem = args.prefix + stem
|
|
54
|
+
if args.suffix:
|
|
55
|
+
stem = stem + args.suffix
|
|
56
|
+
|
|
57
|
+
# 4. Extension replacement
|
|
58
|
+
if args.ext:
|
|
59
|
+
ext = args.ext if args.ext.startswith(".") else "." + args.ext
|
|
60
|
+
else:
|
|
61
|
+
ext = suffix
|
|
62
|
+
|
|
63
|
+
return stem + ext
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def preview_table(pairs: list[tuple[Path, Path]]) -> None:
|
|
67
|
+
"""Print a formatted before/after table."""
|
|
68
|
+
if not pairs:
|
|
69
|
+
print(" (no files matched)")
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
col = max(len(p[0].name) for p in pairs)
|
|
73
|
+
print(f"\n {'BEFORE':<{col}} AFTER")
|
|
74
|
+
print(" " + "─" * col + " " + "─" * 40)
|
|
75
|
+
for old, new in pairs:
|
|
76
|
+
changed = "✓" if old.name != new.name else " "
|
|
77
|
+
print(f" {old.name:<{col}} {new.name} {changed}")
|
|
78
|
+
print()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ── Core logic ─────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
def run(args: argparse.Namespace) -> None:
|
|
84
|
+
files = collect_files(args.directory, args.recursive, args.pattern)
|
|
85
|
+
|
|
86
|
+
if not files:
|
|
87
|
+
print(f"[rnmtool] No files matching '{args.pattern}' found in '{args.directory}'.")
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
pairs: list[tuple[Path, Path]] = []
|
|
91
|
+
for old_path in files:
|
|
92
|
+
new_name = apply_transformations(old_path.name, args)
|
|
93
|
+
new_path = old_path.parent / new_name
|
|
94
|
+
pairs.append((old_path, new_path))
|
|
95
|
+
|
|
96
|
+
if args.dry_run:
|
|
97
|
+
print("[rnmtool] DRY RUN - no files will be changed.")
|
|
98
|
+
preview_table(pairs)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
renamed, skipped, errors = 0, 0, 0
|
|
102
|
+
for old_path, new_path in pairs:
|
|
103
|
+
if old_path == new_path:
|
|
104
|
+
skipped += 1
|
|
105
|
+
continue
|
|
106
|
+
if new_path.exists() and not args.overwrite:
|
|
107
|
+
print(f" [SKIP] '{new_path.name}' already exists. Use --overwrite to replace.")
|
|
108
|
+
skipped += 1
|
|
109
|
+
continue
|
|
110
|
+
try:
|
|
111
|
+
old_path.rename(new_path)
|
|
112
|
+
print(f" [OK] '{old_path.name}' → '{new_path.name}'")
|
|
113
|
+
renamed += 1
|
|
114
|
+
except OSError as exc:
|
|
115
|
+
print(f" [ERR] '{old_path.name}': {exc}")
|
|
116
|
+
errors += 1
|
|
117
|
+
|
|
118
|
+
print(f"\n[rnmtool] Done. Renamed: {renamed}, Skipped: {skipped}, Errors: {errors}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ── CLI ────────────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
124
|
+
p = argparse.ArgumentParser(
|
|
125
|
+
prog="rnmtool",
|
|
126
|
+
description="Flexible file renaming utility.",
|
|
127
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
128
|
+
epilog="""
|
|
129
|
+
Examples:
|
|
130
|
+
# Preview renaming all .txt files in the current directory
|
|
131
|
+
rnmtool --dry-run --pattern "*.txt"
|
|
132
|
+
|
|
133
|
+
# Add a prefix to every JPEG
|
|
134
|
+
rnmtool --pattern "*.jpg" --prefix "2025_"
|
|
135
|
+
|
|
136
|
+
# Replace spaces with underscores in all filenames
|
|
137
|
+
rnmtool --find " " --replace "_"
|
|
138
|
+
|
|
139
|
+
# Convert all .mp3 filenames to lowercase
|
|
140
|
+
rnmtool --pattern "*.mp3" --case lower
|
|
141
|
+
|
|
142
|
+
# Change extension from .jpeg to .jpg
|
|
143
|
+
rnmtool --pattern "*.jpeg" --ext .jpg
|
|
144
|
+
|
|
145
|
+
# Regex: remove leading digits from filenames
|
|
146
|
+
rnmtool --regex --find "^\\d+_?" --replace ""
|
|
147
|
+
""",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
p.add_argument(
|
|
151
|
+
"directory",
|
|
152
|
+
nargs="?",
|
|
153
|
+
default=".",
|
|
154
|
+
help="Target directory (default: current directory).",
|
|
155
|
+
)
|
|
156
|
+
p.add_argument(
|
|
157
|
+
"--pattern", "-p",
|
|
158
|
+
default="*",
|
|
159
|
+
metavar="GLOB",
|
|
160
|
+
help="Glob pattern to filter files (default: '*').",
|
|
161
|
+
)
|
|
162
|
+
p.add_argument(
|
|
163
|
+
"--recursive", "-r",
|
|
164
|
+
action="store_true",
|
|
165
|
+
help="Search for files recursively.",
|
|
166
|
+
)
|
|
167
|
+
p.add_argument(
|
|
168
|
+
"--dry-run", "-n",
|
|
169
|
+
action="store_true",
|
|
170
|
+
help="Preview changes without renaming anything.",
|
|
171
|
+
)
|
|
172
|
+
p.add_argument(
|
|
173
|
+
"--overwrite",
|
|
174
|
+
action="store_true",
|
|
175
|
+
help="Allow overwriting existing files.",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Transformation options
|
|
179
|
+
t = p.add_argument_group("transformations")
|
|
180
|
+
t.add_argument("--find", "-f", metavar="TEXT", help="Text (or regex) to search for in filenames.")
|
|
181
|
+
t.add_argument("--replace", "-s", metavar="TEXT", default="", help="Replacement text (default: remove match).")
|
|
182
|
+
t.add_argument("--regex", "-x", action="store_true", help="Treat --find as a regular expression.")
|
|
183
|
+
t.add_argument("--prefix", metavar="TEXT", help="Prepend text to each filename.")
|
|
184
|
+
t.add_argument("--suffix", metavar="TEXT", help="Append text to each filename (before extension).")
|
|
185
|
+
t.add_argument("--ext", metavar="EXT", help="Replace the file extension (e.g. '.jpg').")
|
|
186
|
+
t.add_argument(
|
|
187
|
+
"--case", "-c",
|
|
188
|
+
choices=["lower", "upper", "title"],
|
|
189
|
+
help="Convert filename case.",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return p
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def main() -> None:
|
|
196
|
+
parser = build_parser()
|
|
197
|
+
args = parser.parse_args()
|
|
198
|
+
|
|
199
|
+
if not Path(args.directory).is_dir():
|
|
200
|
+
parser.error(f"Directory not found: '{args.directory}'")
|
|
201
|
+
|
|
202
|
+
if args.find is None and args.prefix is None and args.suffix is None \
|
|
203
|
+
and args.ext is None and args.case is None and not args.dry_run:
|
|
204
|
+
parser.error("No transformation specified. Use --help for usage or --dry-run to preview files.")
|
|
205
|
+
|
|
206
|
+
run(args)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aayush Goswami
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: rnmtool
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A powerful, zero-dependency cross-platform CLI file renaming utility
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/AayushGoswami/rnmtool
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/AayushGoswami/rnmtool/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/AayushGoswami/rnmtool/blob/main/CHANGELOG.md
|
|
9
|
+
Keywords: rename,cli,files,batch,utility,tool,terminal
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Environment :: Console
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Classifier: Topic :: System :: Filesystems
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Classifier: Intended Audience :: System Administrators
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
|
|
26
|
+
# 🗂️ rnmtool - File Rename Utility
|
|
27
|
+
|
|
28
|
+
> A powerful, **zero-dependency**, cross-platform CLI for batch renaming files - with a safe dry-run preview.
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+

|
|
32
|
+

|
|
33
|
+

|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pipx install rnmtool
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> Works on **Windows, macOS, and Linux** - invoke from any folder, any terminal.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ✨ Why rnmtool?
|
|
45
|
+
|
|
46
|
+
| Feature | rnmtool | Manual renaming | PowerShell scripts |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| Safe dry-run preview | ✅ | ❌ | ❌ |
|
|
49
|
+
| Regex support | ✅ | ❌ | ⚠️ complex |
|
|
50
|
+
| Zero dependencies | ✅ | - | ✅ |
|
|
51
|
+
| Works on Win / Mac / Linux | ✅ | ❌ | ❌ |
|
|
52
|
+
| One-word invocation | ✅ | ❌ | ❌ |
|
|
53
|
+
| Glob file filtering | ✅ | ❌ | ⚠️ complex |
|
|
54
|
+
| Recursive directory support | ✅ | ❌ | ⚠️ complex |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 📋 Table of Contents
|
|
59
|
+
|
|
60
|
+
- [Installation](#installation)
|
|
61
|
+
- [Quick Start](#quick-start)
|
|
62
|
+
- [All Options](#all-options)
|
|
63
|
+
- [Tutorials](#tutorials)
|
|
64
|
+
- [1. Dry-Run Preview](#1-dry-run-preview)
|
|
65
|
+
- [2. Add a Prefix](#2-add-a-prefix)
|
|
66
|
+
- [3. Add a Suffix](#3-add-a-suffix)
|
|
67
|
+
- [4. Find & Replace Text](#4-find--replace-text)
|
|
68
|
+
- [5. Regex Find & Replace](#5-regex-find--replace)
|
|
69
|
+
- [6. Convert Case](#6-convert-case)
|
|
70
|
+
- [7. Change File Extension](#7-change-file-extension)
|
|
71
|
+
- [8. Filter by Glob Pattern](#8-filter-by-glob-pattern)
|
|
72
|
+
- [9. Recursive Renaming](#9-recursive-renaming)
|
|
73
|
+
- [10. Combine Multiple Transformations](#10-combine-multiple-transformations)
|
|
74
|
+
- [Test Files](#test-files)
|
|
75
|
+
- [Contributing](#contributing)
|
|
76
|
+
- [Safety Notes](#safety-notes)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Installation
|
|
81
|
+
|
|
82
|
+
### Recommended: `pipx` (cross-platform, isolated)
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pipx install rnmtool
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> Don't have pipx? Install it first: `pip install pipx`
|
|
89
|
+
|
|
90
|
+
### Alternative: `pip`
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install rnmtool
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### One-command installer scripts
|
|
97
|
+
|
|
98
|
+
**Windows (PowerShell):**
|
|
99
|
+
```powershell
|
|
100
|
+
irm https://raw.githubusercontent.com/AayushGoswami/rnmtool/main/scripts/install.ps1 | iex
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Linux / macOS (Bash):**
|
|
104
|
+
```bash
|
|
105
|
+
curl -fsSL https://raw.githubusercontent.com/AayushGoswami/rnmtool/main/scripts/install.sh | bash
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Verify installation
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
rnmtool --help
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Quick Start
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Always preview first - no files are changed
|
|
120
|
+
rnmtool test_files --dry-run --find " " --replace "_"
|
|
121
|
+
|
|
122
|
+
# If the preview looks good, apply (remove --dry-run)
|
|
123
|
+
rnmtool test_files --find " " --replace "_"
|
|
124
|
+
|
|
125
|
+
# Run from inside the target folder (no directory argument needed)
|
|
126
|
+
cd /path/to/my/folder
|
|
127
|
+
rnmtool --dry-run --case lower
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## All Options
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
usage: rnmtool [-h] [--pattern GLOB] [--recursive] [--dry-run] [--overwrite]
|
|
136
|
+
[--find TEXT] [--replace TEXT] [--regex] [--prefix TEXT]
|
|
137
|
+
[--suffix TEXT] [--ext EXT] [--case {lower,upper,title}]
|
|
138
|
+
[directory]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Positional Arguments
|
|
142
|
+
|
|
143
|
+
| Argument | Description | Default |
|
|
144
|
+
|-------------|-------------------------------------------|----------------------|
|
|
145
|
+
| `directory` | The folder containing files to rename | `.` (current folder) |
|
|
146
|
+
|
|
147
|
+
### General Options
|
|
148
|
+
|
|
149
|
+
| Flag | Short | Description |
|
|
150
|
+
|------------------|-------|---------------------------------------------------------|
|
|
151
|
+
| `--pattern GLOB` | `-p` | Only rename files matching this glob (e.g. `"*.txt"`) |
|
|
152
|
+
| `--recursive` | `-r` | Search subdirectories recursively |
|
|
153
|
+
| `--dry-run` | `-n` | Preview changes without modifying any file |
|
|
154
|
+
| `--overwrite` | | Allow overwriting a file if the new name already exists |
|
|
155
|
+
| `--help` | `-h` | Show help message and exit |
|
|
156
|
+
|
|
157
|
+
### Transformation Options
|
|
158
|
+
|
|
159
|
+
| Flag | Short | Description |
|
|
160
|
+
|------------------|-------|----------------------------------------------------------|
|
|
161
|
+
| `--find TEXT` | `-f` | Text (or regex pattern) to search for in filenames |
|
|
162
|
+
| `--replace TEXT` | `-s` | Replacement text (default: empty - deletes the match) |
|
|
163
|
+
| `--regex` | `-x` | Treat `--find` as a regular expression |
|
|
164
|
+
| `--prefix TEXT` | | Text to prepend to every filename |
|
|
165
|
+
| `--suffix TEXT` | | Text to append to every filename (before the extension) |
|
|
166
|
+
| `--ext EXT` | | Replace the file extension (e.g. `.jpg` or `jpg`) |
|
|
167
|
+
| `--case` | `-c` | Convert case: `lower`, `upper`, or `title` |
|
|
168
|
+
|
|
169
|
+
> **Transformation order:** find/replace → case → prefix/suffix → extension
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Tutorials
|
|
174
|
+
|
|
175
|
+
All examples below use the included `test_files/` folder.
|
|
176
|
+
**Always run with `--dry-run` first to preview changes safely.**
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### 1. Dry-Run Preview
|
|
181
|
+
|
|
182
|
+
Preview what *would* happen without touching any file.
|
|
183
|
+
A ✓ mark appears next to filenames that would be changed.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
rnmtool test_files --dry-run --case lower
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Preview output:**
|
|
190
|
+
```
|
|
191
|
+
BEFORE AFTER
|
|
192
|
+
──────────────────────────── ────────────────────────
|
|
193
|
+
04 helper SCRIPT.py 04 helper script.py ✓
|
|
194
|
+
07 settings.XML 07 settings.xml ✓
|
|
195
|
+
12 USER preferences.ini 12 user preferences.ini ✓
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
No files are modified. Remove `--dry-run` to apply changes.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### 2. Add a Prefix
|
|
203
|
+
|
|
204
|
+
Prepend a label or date to every filename in a folder.
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Dry-run first
|
|
208
|
+
rnmtool test_files --dry-run --prefix "2025_"
|
|
209
|
+
|
|
210
|
+
# Apply
|
|
211
|
+
rnmtool test_files --prefix "2025_"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Result:**
|
|
215
|
+
```
|
|
216
|
+
01 My Notes.txt → 2025_01 My Notes.txt
|
|
217
|
+
02 Sales Data 2024.csv → 2025_02 Sales Data 2024.csv
|
|
218
|
+
03 AppConfig.json → 2025_03 AppConfig.json
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Tip:** Combine with `--pattern` to prefix only specific file types:
|
|
222
|
+
```bash
|
|
223
|
+
rnmtool test_files --pattern "*.csv" --prefix "report_"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
### 3. Add a Suffix
|
|
229
|
+
|
|
230
|
+
Append a label to every filename (inserted *before* the extension).
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Dry-run first
|
|
234
|
+
rnmtool test_files --dry-run --suffix "_backup"
|
|
235
|
+
|
|
236
|
+
# Apply
|
|
237
|
+
rnmtool test_files --suffix "_backup"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Result:**
|
|
241
|
+
```
|
|
242
|
+
01 My Notes.txt → 01 My Notes_backup.txt
|
|
243
|
+
05 README draft.md → 05 README draft_backup.md
|
|
244
|
+
11 RunBackup.bat → 11 RunBackup_backup.bat
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### 4. Find & Replace Text
|
|
250
|
+
|
|
251
|
+
Replace any text string inside filenames - spaces, words, anything.
|
|
252
|
+
|
|
253
|
+
#### Example A - Replace spaces with underscores
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
rnmtool test_files --dry-run --find " " --replace "_"
|
|
257
|
+
rnmtool test_files --find " " --replace "_"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Result:**
|
|
261
|
+
```
|
|
262
|
+
01 My Notes.txt → 01_My_Notes.txt
|
|
263
|
+
02 Sales Data 2024.csv → 02_Sales_Data_2024.csv
|
|
264
|
+
08 app ERROR log.log → 08_app_ERROR_log.log
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### Example B - Remove a specific word
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
rnmtool test_files --find " draft" --replace ""
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Result:**
|
|
274
|
+
```
|
|
275
|
+
05 README draft.md → 05 README.md
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### Example C - Replace a word with another
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
rnmtool test_files --find "ERROR" --replace "error"
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Result:**
|
|
285
|
+
```
|
|
286
|
+
08 app ERROR log.log → 08 app error log.log
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### 5. Regex Find & Replace
|
|
292
|
+
|
|
293
|
+
Use regular expressions for advanced pattern-based renaming.
|
|
294
|
+
Enable with the `--regex` (or `-x`) flag alongside `--find`.
|
|
295
|
+
|
|
296
|
+
#### Example A - Strip leading numbers from filenames
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
rnmtool test_files --dry-run --regex --find "^\d+\s"
|
|
300
|
+
rnmtool test_files --regex --find "^\d+\s"
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Result:**
|
|
304
|
+
```
|
|
305
|
+
01 My Notes.txt → My Notes.txt
|
|
306
|
+
02 Sales Data 2024.csv → Sales Data 2024.csv
|
|
307
|
+
03 AppConfig.json → AppConfig.json
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### Example B - Remove all digits from filenames
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
rnmtool test_files --regex --find "\d+" --replace ""
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### Example C - Replace multiple spaces with a single underscore
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
rnmtool test_files --regex --find "\s+" --replace "_"
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
### 6. Convert Case
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Lowercase
|
|
328
|
+
rnmtool test_files --case lower
|
|
329
|
+
|
|
330
|
+
# Uppercase
|
|
331
|
+
rnmtool test_files --case upper
|
|
332
|
+
|
|
333
|
+
# Title Case
|
|
334
|
+
rnmtool test_files --case title
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Title case result:**
|
|
338
|
+
```
|
|
339
|
+
08 app ERROR log.log → 08 App Error Log.log
|
|
340
|
+
10 CREATE tables.sql → 10 Create Tables.sql
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
### 7. Change File Extension
|
|
346
|
+
|
|
347
|
+
#### Normalize `.XML` to `.xml`
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
rnmtool test_files --pattern "*.XML" --ext .xml
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### Convert `.yaml` to `.yml`
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
rnmtool test_files --pattern "*.yaml" --ext .yml
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### Change `.txt` to `.md`
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
rnmtool test_files --pattern "*.txt" --ext .md
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
> The leading dot is optional: `--ext yml` and `--ext .yml` both work.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
### 8. Filter by Glob Pattern
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
# Only .log files
|
|
373
|
+
rnmtool test_files --pattern "*.log" --prefix "archived_"
|
|
374
|
+
|
|
375
|
+
# Files starting with "0"
|
|
376
|
+
rnmtool test_files --pattern "0*" --case lower
|
|
377
|
+
|
|
378
|
+
# Only .json files
|
|
379
|
+
rnmtool test_files --pattern "*.json" --suffix "_v2"
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
### 9. Recursive Renaming
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# Rename all .txt files in test_files and any subfolders
|
|
388
|
+
rnmtool test_files --recursive --pattern "*.txt" --case lower
|
|
389
|
+
|
|
390
|
+
# Always dry-run first with recursive!
|
|
391
|
+
rnmtool test_files --recursive --dry-run --find " " --replace "_"
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
### 10. Combine Multiple Transformations
|
|
397
|
+
|
|
398
|
+
Transformations are applied in order: **find/replace → case → prefix/suffix → extension**
|
|
399
|
+
|
|
400
|
+
#### Clean up all filenames at once
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
rnmtool test_files --find " " --replace "_" --case lower --prefix "2025_"
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Result:**
|
|
407
|
+
```
|
|
408
|
+
01 My Notes.txt → 2025_01_my_notes.txt
|
|
409
|
+
08 app ERROR log.log → 2025_08_app_error_log.log
|
|
410
|
+
12 USER preferences.ini → 2025_12_user_preferences.ini
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
#### Strip numbers, title-case, add suffix
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
rnmtool test_files --regex --find "^\d+\s" --case title --suffix "_final"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
#### Pattern-scoped multi-transform
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
rnmtool test_files --pattern "*.py" --case lower --prefix "script_"
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Test Files
|
|
428
|
+
|
|
429
|
+
A `test_files/` folder is included with 12 dummy files of varied types to safely experiment:
|
|
430
|
+
|
|
431
|
+
| File | Extension |
|
|
432
|
+
|------|-----------|
|
|
433
|
+
| `01 My Notes.txt` | `.txt` |
|
|
434
|
+
| `02 Sales Data 2024.csv` | `.csv` |
|
|
435
|
+
| `03 AppConfig.json` | `.json` |
|
|
436
|
+
| `04 helper SCRIPT.py` | `.py` |
|
|
437
|
+
| `05 README draft.md` | `.md` |
|
|
438
|
+
| `06 index PAGE.html` | `.html` |
|
|
439
|
+
| `07 settings.XML` | `.XML` |
|
|
440
|
+
| `08 app ERROR log.log` | `.log` |
|
|
441
|
+
| `09 docker-compose.yaml` | `.yaml` |
|
|
442
|
+
| `10 CREATE tables.sql` | `.sql` |
|
|
443
|
+
| `11 RunBackup.bat` | `.bat` |
|
|
444
|
+
| `12 USER preferences.ini` | `.ini` |
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Contributing
|
|
449
|
+
|
|
450
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
451
|
+
|
|
452
|
+
- 🐛 [Report a bug](https://github.com/AayushGoswami/rnmtool/issues)
|
|
453
|
+
- 💡 [Request a feature](https://github.com/AayushGoswami/rnmtool/issues)
|
|
454
|
+
- 📖 [Read the changelog](CHANGELOG.md)
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Safety Notes
|
|
459
|
+
|
|
460
|
+
- ✅ **Always use `--dry-run` before applying any changes.**
|
|
461
|
+
- ✅ Files whose names are unchanged by a transformation are automatically skipped.
|
|
462
|
+
- ⚠️ If a rename would cause a **name collision**, the tool skips that file and warns you. Use `--overwrite` only if you are sure.
|
|
463
|
+
- ⚠️ **Recursive mode** (`--recursive`) can affect many files. Always dry-run first.
|
|
464
|
+
- ℹ️ The tool only renames files - it **never moves, deletes, or modifies file contents.**
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
*Built with Python's standard library - zero dependencies. Works on Windows, macOS, and Linux.*
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
rnmtool/__init__.py,sha256=IBr45_PcWqztsKHyTXvBFooHGvyjWydaGtd2Dhs_2bw,139
|
|
2
|
+
rnmtool/__main__.py,sha256=5UnQb-iAs_kgzQxpxFF7YWaYtdkAClOuuFHlUy0R95I,6997
|
|
3
|
+
rnmtool-1.0.0.dist-info/LICENSE,sha256=aISDkcq2bEeVc-7jleiuUtB3vck5kIeZHFlSrOjUwmY,1092
|
|
4
|
+
rnmtool-1.0.0.dist-info/METADATA,sha256=oRItEK81dgrHpUHxS1AZA8GQ2_LzDjSKvboAG-v64os,13021
|
|
5
|
+
rnmtool-1.0.0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
6
|
+
rnmtool-1.0.0.dist-info/entry_points.txt,sha256=7p5Mf2kJZInOB437eBpRlot1j-AR5-TfzTR8SZkmeUc,50
|
|
7
|
+
rnmtool-1.0.0.dist-info/top_level.txt,sha256=5CnrJZWSCuQLSweQHjMKU4dYk_dqWsYMSFrT0tQ85wk,8
|
|
8
|
+
rnmtool-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rnmtool
|