forked from seanh/dotfilemanager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdotfilemanager.py
executable file
·204 lines (177 loc) · 7.29 KB
/
dotfilemanager.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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#!/usr/bin/env python
"""dotfilemanager.py - a dotfiles manager script. See --help for usage
and command-line arguments.
"""
import os,sys,platform
# TODO: allow setting hostname as a command-line argument also?
try:
HOSTNAME = os.environ['DOTFILEMANAGER_HOSTNAME']
except KeyError:
HOSTNAME = platform.node()
HOSTNAME_SEPARATOR = '__'
def tidy(d,report=False):
"""Find and delete any broken symlinks in directory d.
Arguments:
d -- The directory to consider (absolute path)
Keyword arguments:
report -- If report is True just report on what broken symlinks are
found, don't attempt to delete them (default: False)
"""
for f in os.listdir(d):
path = os.path.join(d,f)
if os.path.islink(path):
target_path = os.readlink(path)
target_path = os.path.abspath(os.path.expanduser(target_path))
if not os.path.exists(target_path):
# This is a broken symlink.
if report:
print 'tidy would delete broken symlink: %s->%s' % (path,target_path)
else:
print 'Deleting broken symlink: %s->%s' % (path,target_path)
os.remove(path)
def get_target_paths(to_dir,report=False):
"""Return the list of absolute paths to link to for a given to_dir.
This handles skipping various types of filename in to_dir and
resolving host-specific filenames.
"""
paths = []
filenames = os.listdir(to_dir)
for filename in filenames:
path = os.path.join(to_dir,filename)
if filename.endswith('~'):
if report:
print 'Skipping %s' % filename
continue
elif (not os.path.isfile(path)) and (not os.path.isdir(path)):
if report:
print 'Skipping %s (not a file or directory)' % filename
continue
elif filename.startswith('.'):
if report:
print 'Skipping %s (filename has a leading dot)' % filename
continue
else:
if HOSTNAME_SEPARATOR in filename:
# This appears to be a filename with a trailing
# hostname, e.g. _muttrc__dulip. If the trailing
# hostname matches the hostname of this host then we
# link to it.
hostname = filename.split(HOSTNAME_SEPARATOR)[-1]
if hostname == HOSTNAME:
paths.append(path)
else:
if report:
print 'Skipping %s (different hostname)' % filename
continue
else:
# This appears to be a filename without a trailing
# hostname.
if filename + HOSTNAME_SEPARATOR + HOSTNAME in filenames:
if report:
print 'Skipping %s (there is a host-specific version of this file for this host)' % filename
continue
else:
paths.append(path)
return paths
def link(from_dir,to_dir,report=False):
"""Make symlinks in from_dir to each file and directory in to_dir.
This handles converting leading underscores in to_dir to leading
dots in from_dir.
Arguments:
from_dir -- The directory in which symlinks will be created (string,
absolute path)
to_dir -- The directory containing the files and directories that
will be linked to (string, absolute path)
Keyword arguments:
report -- If report is True then only report on the status of
symlinks in from_dir, don't actually create any new
symlinks (default: False)
"""
# The paths in to_dir that we will be symlinking to.
to_paths = get_target_paths(to_dir,report)
# Dictionary of symlinks we will be creating, from_path->to_path
symlinks = {}
for to_path in to_paths:
to_directory, to_filename = os.path.split(to_path)
# Change leading underscores to leading dots.
if to_filename.startswith('_'):
from_filename = '.' + to_filename[1:]
else:
from_filename = to_filename
# Remove hostname specifiers.
parts = from_filename.split(HOSTNAME_SEPARATOR)
assert len(parts) == 1 or len(parts) == 2
from_filename = parts[0]
from_path = os.path.join(from_dir,from_filename)
symlinks[from_path] = to_path
# Attempt to create the symlinks that don't already exist.
for from_path,to_path in symlinks.items():
# Check that nothing already exists at from_path.
if os.path.islink(from_path):
# A link already exists.
existing_to_path = os.readlink(from_path)
existing_to_path = os.path.abspath(os.path.expanduser(existing_to_path))
if existing_to_path == to_path:
# It's already a link to the intended target. All is
# well.
continue
else:
# It's a link to somewhere else.
print from_path+" => is already symlinked to "+existing_to_path
elif os.path.isfile(from_path):
print "There's a file in the way at "+from_path
elif os.path.isdir(from_path):
print "There's a directory in the way at "+from_path
elif os.path.ismount(from_path):
print "There's a mount point in the way at "+from_path
else:
# The path is clear, make the symlink.
if report:
print 'link would make symlink: %s->%s' % (from_path,to_path)
else:
print 'Making symlink %s->%s' % (from_path,to_path)
os.symlink(to_path,from_path)
def usage():
return """Usage:
dotfilemanager link|tidy|report [FROM_DIR [TO_DIR]]
Commands:
link -- make symlinks in FROM_DIR to files and directories in TO_DIR
tidy -- remove broken symlinks from FROM_DIR
report -- report on symlinks in FROM_DIR and files and directories in TO_DIR
FROM_DIR defaults to ~ and TO_DIR defaults to ~/.dotfiles.
"""
if __name__ == "__main__":
try:
ACTION = sys.argv[1]
except IndexError:
print usage()
sys.exit(2)
try:
FROM_DIR = sys.argv[2]
except IndexError:
FROM_DIR = '~'
FROM_DIR = os.path.abspath(os.path.expanduser(FROM_DIR))
if not os.path.isdir(FROM_DIR):
print "FROM_DIR %s is not a directory!" % FROM_DIR
print usage()
sys.exit(2)
if ACTION == 'tidy':
tidy(FROM_DIR)
else:
try:
TO_DIR = sys.argv[3]
except IndexError:
TO_DIR = os.path.join('~','.dotfiles')
TO_DIR = os.path.abspath(os.path.expanduser(TO_DIR))
if not os.path.isdir(TO_DIR):
print "TO_DIR %s is not a directory!" % TO_DIR
print usage()
sys.exit(2)
if ACTION == 'link':
link(FROM_DIR,TO_DIR)
elif ACTION == 'report':
link(FROM_DIR,TO_DIR,report=True)
tidy(FROM_DIR,report=True)
else:
print usage()
sys.exit(2)