Skip to main content

fs/flatten

1#!/usr/bin/env python3
2"""
3Run inside a directory, and it moves every non-trivial file up to the
4top-level.
6I use this to flatten out heavily-nested directory hierarchies, when I need
7to sort through the files by hand and want to see what I've got.
9It tries to handle cases where different files exist with the same name, but
10it may not be robust. Use with caution.
12By default it only prints what it's going to do, you need to add the flag `--run`
13for it to do anything.
14"""
16import filecmp
17import os
18import secrets
19import shutil
20import sys
23def mv(src, dst):
24 print(f'mv {src} ~> {dst}')
25 if '--run' in sys.argv:
26 os.rename(src, dst)
29def rm(path):
30 print(f'rm {path}')
31 if '--run' in sys.argv:
32 os.unlink(path)
35if __name__ == '__main__':
36 for root, _, filenames in os.walk('.'):
37 if root == '.':
38 continue
40 for f in filenames:
41 f_src = os.path.join(root, f)
43 if f == '.DS_Store':
44 rm(f_src)
45 continue
47 f_dst = f
49 if os.path.exists(f_dst) and filecmp.cmp(f_src, f_dst):
50 rm(f_src)
51 continue
53 # Add a bit of noise to filenames to avoid duplication
54 while os.path.exists(f_dst):
55 name, ext = os.path.splitext(f)
56 f_dst = name + '__' + secrets.token_hex(3) + ext
58 mv(src=f_src, dst=f_dst)
60 # Clean up empty directories
61 while True:
62 is_finished = True
63 for root, dirnames, filenames in os.walk('.'):
64 if not dirnames and not filenames:
65 shutil.rmtree(root)
66 is_finished = False
68 if is_finished:
69 break