forked from lskillen/dotfiles
-
Notifications
You must be signed in to change notification settings - Fork 0
/
symlink.py
141 lines (114 loc) · 4.4 KB
/
symlink.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
#!/usr/bin/env python
import errno
import itertools
import os
import logging
import shutil
import sys
SELF = os.path.dirname(os.path.realpath(__file__))
class SymLinker(object):
def __init__(self, dest_dir=None, dotfiles_dir=None, ignore=None,
verbose=False):
self.dest_dir = dest_dir or os.environ["HOME"]
self.dotfiles_dir = dotfiles_dir
self.backup_dir = os.path.join(self.dotfiles_dir, "backup")
self.ignore = ignore or [
"backup", "README.md", "requirements.txt", "packages.txt",
"stages.txt", "setup.sh", "symlink.py", "symlink.pyc"
]
self.verbose = verbose
@property
def dotfiles_dir(self):
return self._dotfiles_dir
@dotfiles_dir.setter
def dotfiles_dir(self, value):
if not value:
try:
value = sys.argv[1]
except IndexError:
value = SELF
value = os.path.realpath(value)
self._dotfiles_dir = value
def resolve(self, path=None):
from_path = self.source_path(path)
if path:
self._link(path)
if not os.path.isdir(from_path):
return
listdir = os.listdir(from_path)
if not path:
def not_ignored(x):
return x not in self.ignore
def not_dotfile(x):
return not x.startswith('.')
listdir = itertools.ifilter(not_ignored, listdir)
listdir = itertools.ifilter(not_dotfile, listdir)
for name in sorted(listdir):
rel_path = os.path.join(path, name) if path else name
if self.verbose:
logging.info("Checking: {}".format(rel_path))
self.resolve(path=rel_path)
def _link(self, path):
from_path = self.source_path(path)
to_path = self.dest_path(path)
if os.path.isdir(from_path):
# Ensure target exists and is a directory
if not os.path.lexists(to_path):
os.makedirs(to_path)
elif os.path.isdir(to_path):
return True
else:
try:
os.symlink(from_path, to_path)
logging.info("Linked: {} to {}".format(path, to_path))
except OSError as ex:
if ex.errno != errno.EEXIST:
logging.error("Could not link: {} ({})".format(path, ex))
return False
if os.path.samefile(from_path, to_path):
# Already linked, and the same
return True
if os.path.islink(to_path) or os.path.isfile(to_path):
# Only backup and remove if target isn't a directory
self._backup(path)
# Try again ...
try:
os.symlink(from_path, to_path)
logging.info("Linked: {} to {}".format(path, to_path))
except (shutil.Error, OSError) as ex:
logging.error("Could not link: {} ({})".format(path))
return
if os.path.isdir(from_path) != os.path.isdir(to_path):
logging.error("Could not link: {} (Source/dest type mismatch)"
.format(path))
return False
logging.info("Created: {}".format(to_path))
return True
def _backup(self, path, remove=False):
from_path = self.dest_path(path)
to_path = self.backup_path(path)
if not os.path.exists(os.path.dirname(to_path)):
os.makedirs(os.path.dirname(to_path))
logging.info("Backing up: {}".format(path))
if os.path.exists(to_path):
if os.path.isfile(from_path) != os.path.isfile(to_path):
if os.path.isfile(to_path):
os.unlink(to_path)
else:
shutil.rmtree(to_path)
shutil.move(from_path, to_path)
def source_path(self, path):
if path:
return os.path.join(self.dotfiles_dir, path)
else:
return self.dotfiles_dir
def dest_path(self, path):
assert path is not None
return os.path.join(self.dest_dir, '.{}'.format(path))
def backup_path(self, path):
assert path is not None
return os.path.join(self.backup_dir, path)
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
linker = SymLinker()
linker.resolve()