-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathfix_mangled_paths.py
executable file
·94 lines (81 loc) · 3.34 KB
/
fix_mangled_paths.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/usr/bin/env python3
# written by Joshua Boudreau <jboudreau@45drives.com> 2022
import os
import argparse
import sys
def get_unique_name(root, name, taken_paths):
i = 0
ext_ind = name.find('.')
new_name = name
new_path = os.path.join(root, new_name)
while new_path in taken_paths or os.path.exists(new_path):
i += 1
new_name = (
name[0:ext_ind] + f"({i})" + name[ext_ind:]
if ext_ind != -1 else name + f"({i})"
)
new_path = os.path.join(root, new_name)
taken_paths.append(new_path)
return new_name
def legalize_name(string: str):
windows_ill = ['\\', '/', ':', '*', '?', '"', '<', '>', '|']
def printable(c): return ord(c) in range(0x20, 0x7F)
def windows_allowed(c): return c not in windows_ill
# weird case of long dash instead of '-'
string = string.replace(u'–', '-')
return "".join(map(lambda c: c if printable(c) and windows_allowed(c) else '_', string))
def rename_mangled(paths, dry_run, recursive):
taken_paths = []
change_count = 0
def rename_entries(root, entries, rootfd):
changes = []
nonlocal change_count
for src in entries:
dst = legalize_name(src)
if dst != src:
dst = get_unique_name(root, dst, taken_paths)
print('in', f"'{root.encode('unicode_escape').decode('utf-8')}':")
print(f"'{src.encode('unicode_escape').decode('utf-8')}'", '->')
print(f"'{dst.encode('unicode_escape').decode('utf-8')}'")
print()
change_count += 1
if dry_run:
continue
try:
os.rename(src, dst, src_dir_fd=rootfd, dst_dir_fd=rootfd)
changes.append((src, dst))
except OSError as e:
print('failed to rename file:', e)
for src, dst in changes:
entries.remove(src)
entries.append(dst)
for path in paths:
if os.path.isdir(path) and recursive:
for root, dirs, files, rootfd in os.fwalk(path, topdown=True):
if '.zfs' in dirs:
dirs.remove('.zfs')
rename_entries(root, dirs, rootfd)
rename_entries(root, files, rootfd)
if os.path.exists(path):
fullpath = os.path.realpath(path)
root = os.path.dirname(fullpath)
entries = [os.path.basename(fullpath)]
rootfd = os.open(root, os.O_RDONLY)
rename_entries(root, entries, rootfd)
return change_count
def main():
parser = argparse.ArgumentParser(
description='Rename files and directories to play nice with Windows')
parser.add_argument('roots', type=str, nargs='+', metavar='PATH',
help='Path(s) to rename')
parser.add_argument('-r', '--recursive', action='store_true', default=False, help='Recurse into directories')
args = parser.parse_args()
mangled_count = rename_mangled(args.roots, True, args.recursive) # dry run
print(f"Found {mangled_count} invalid paths")
if mangled_count == 0:
sys.exit()
response = input("Are the above changes okay? [y/N]: ")
if response.upper() in ['Y', 'YES']:
rename_mangled(args.roots, False, args.recursive) # really rename
if __name__ == '__main__':
main()