Skip to content

Commit 3a2432d

Browse files
committed
Fix build and EDITOR parsing, add recursive option
1 parent 87f278d commit 3a2432d

5 files changed

Lines changed: 167 additions & 57 deletions

File tree

README.md

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,87 @@
1-
# emv
2-
3-
A Python re-write of [vimv](https://github.com/thameera/vimv).
1+
# emv
2+
3+
A Python re-write of [vimv](https://github.com/thameera/vimv) — bulk rename files by editing their names in your preferred text editor.
4+
5+
## Installation
6+
7+
```bash
8+
pip install .
9+
```
10+
11+
Or install in development mode:
12+
13+
```bash
14+
pip install -e .
15+
```
16+
17+
## Usage
18+
19+
### Basic — rename files in the current directory
20+
21+
```bash
22+
emv
23+
```
24+
25+
This lists all files in the current directory. Edit the names in your editor, save, and close. `emv` renames each file whose name changed.
26+
27+
### Rename specific files
28+
29+
```bash
30+
emv file1.txt file2.txt image.png
31+
```
32+
33+
Only the specified files are shown for renaming.
34+
35+
### Recursive mode
36+
37+
```bash
38+
emv -r
39+
```
40+
41+
Walks the full directory tree and lists all files with their relative paths. Renaming a path can also **move** a file into a different directory.
42+
43+
### Example workflow
44+
45+
```
46+
$ ls
47+
photos/
48+
001.jpg
49+
002.jpg
50+
003.jpg
51+
52+
$ emv photos/*.jpg
53+
# Editor opens with:
54+
# photos/001.jpg
55+
# photos/002.jpg
56+
# photos/003.jpg
57+
58+
# Edit to:
59+
# photos/vacation_001.jpg
60+
# photos/vacation_002.jpg
61+
# photos/vacation_003.jpg
62+
63+
# Save & close → output:
64+
# 3 files renamed.
65+
```
66+
67+
If you add or remove lines (so the number of names doesn't match the original), the operation is aborted with a warning. If a destination file already exists, that file is skipped.
68+
69+
## Editor selection
70+
71+
By default, `emv` opens Notepad. To use a different editor, set the `EDITOR` environment variable:
72+
73+
```bash
74+
# VS Code (wait flag so the CLI waits for you to close the tab)
75+
set EDITOR=code -w
76+
emv
77+
78+
# Vim
79+
set EDITOR=vim
80+
emv
81+
82+
# Editor with a quoted path containing spaces
83+
set EDITOR="C:\Program Files\Notepad++\notepad++.exe" -multiInst
84+
emv
85+
```
86+
87+
The value of `EDITOR` is parsed safely to handle quoted paths and command-line flags.

cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from emv.emv import main
1+
from emv import main
22

33
if __name__ == "__main__":
4-
main()
4+
main()

emv/__init__.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import argparse
2+
import os
3+
import shlex
4+
import tempfile
5+
import subprocess
6+
import sys
7+
8+
__version__ = "1.1.0"
9+
10+
11+
def main():
12+
parser = argparse.ArgumentParser(description="Bulk rename files via a text editor.")
13+
parser.add_argument(
14+
"-V",
15+
"--version",
16+
action="version",
17+
version=f"%(prog)s {__version__}",
18+
)
19+
parser.add_argument(
20+
"-r",
21+
"--recursive",
22+
action="store_true",
23+
help="Recursively list files in the current directory tree.",
24+
)
25+
parser.add_argument("files", nargs="*", help="Files to rename.")
26+
args = parser.parse_args()
27+
28+
files = args.files
29+
30+
with tempfile.NamedTemporaryFile(
31+
mode="w+t", delete=False, encoding="utf-8"
32+
) as tmpfile:
33+
filenames_file = tmpfile.name
34+
35+
if files:
36+
src = files
37+
elif args.recursive:
38+
src = []
39+
for root, _, filenames in os.walk("."):
40+
for f in filenames:
41+
path = os.path.relpath(os.path.join(root, f), ".")
42+
src.append(path.replace(os.sep, "/"))
43+
src.sort()
44+
else:
45+
src = [f for f in os.listdir(".") if os.path.isfile(f)]
46+
tmpfile.write("\n".join(src))
47+
48+
editor = os.environ.get("EDITOR", "notepad.exe")
49+
editor_cmd = shlex.split(editor) + [filenames_file]
50+
subprocess.call(editor_cmd)
51+
52+
with open(filenames_file, "r", encoding="utf-8") as tmpfile:
53+
dest = tmpfile.read().splitlines()
54+
55+
os.remove(filenames_file)
56+
57+
if len(src) != len(dest):
58+
print(
59+
"WARN: Number of files changed. Did you delete a line by accident? Aborting..",
60+
file=sys.stderr,
61+
)
62+
sys.exit(1)
63+
64+
count = 0
65+
for s, d in zip(src, dest):
66+
if s != d:
67+
dd = os.path.dirname(d)
68+
69+
if dd:
70+
os.makedirs(dd, exist_ok=True)
71+
if os.path.exists(d):
72+
print(f"SKIP: '{d}' already exists, skipping.", file=sys.stderr)
73+
continue
74+
os.rename(s, d)
75+
count += 1
76+
77+
print(f"{count} files renamed.")

emv/emv.py

Lines changed: 0 additions & 51 deletions
This file was deleted.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
packages=find_packages(),
1313
entry_points={
1414
"console_scripts": [
15-
"emv=emv.emv:main",
15+
"emv=emv:main",
1616
],
1717
},
1818
zip_safe=False,

0 commit comments

Comments
 (0)