-
Notifications
You must be signed in to change notification settings - Fork 0
/
apply_symlinks.py
executable file
·197 lines (167 loc) · 6.58 KB
/
apply_symlinks.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
#!/usr/bin/python3
"""Checks that all files in the root and home directories have symlinks on the system,
and prompts to create or warns about missing them"""
import difflib
import os
import os.path
from shutil import copyfile, move
from typing import Literal
FIXFILE = "fix_links.sh"
def main() -> None:
"""do all the stuffs"""
symlink_files("home", os.path.expanduser("~"), ".")
commands = copy_folder_contents("root", "/")
commands += check_submodules()
if not commands:
print("all symlinks OK")
if os.path.isfile("fix_links.sh"):
os.remove("fix_links.sh")
return
with open("fix_links.sh", "w", encoding="utf-8") as file:
file.write(" &&\n".join(commands))
os.chmod(FIXFILE, 0o755)
print("run fix_links.sh")
def check_submodules() -> list[str]:
"""check for submodules"""
if not os.path.isfile("vim-plug/plug.vim"):
return ["git submodule init && git submodule update\n"]
return []
def copy_folder_contents(folder: str, replace: str) -> list[str]:
"""checks if file contents differ, returns list of commands needed to rectify"""
commands = []
for dirpath, _dirnames, filenames in os.walk(folder):
# root/ -> /
# root/dir/ -> /dir/
base = os.path.join(replace, dirpath[len(folder) + 1 :], "")
# basepath = base
for filename in filenames:
remove = False
path = base + filename
abstarget = os.path.realpath(os.path.join(dirpath, filename))
target = os.path.join(dirpath, filename)
if not os.access(base, mode=os.R_OK):
print(f'No permissions to check {path}')
continue
elif not (os.path.isfile(path) or os.path.islink(path)):
print(f"{path} missing")
if input("copy? [Y/n] ").lower() == "n":
continue
elif os.path.islink(path):
actual_target = os.readlink(path)
if actual_target == abstarget:
print(f"{path} dangerous symlink")
remove = True
else:
print(f"{path} symlinked to {actual_target}")
continue
else:
match diff_files(path, target):
case "n":
continue
case "y":
remove = True
case "r":
commands.append(f"cp -iv {path} {target}")
continue
case _:
raise ValueError("unknown value")
if remove:
commands.append(f"sudo rm -iv {path}")
commands.append(f"sudo cp -iv {target} {path}")
return commands
def diff_files(path: str, target: str) -> Literal["y"] | Literal["n"] | Literal["r"]:
"""check file contents, prompt whether to overwrite.
returns 'y' if it should be overwritten"""
# check if read rights
path_content = []
target_content = []
#with open(path, encoding="utf-8") as file:
# path_content = file.readlines()
#with open(target, encoding="utf-8") as file:
# target_content = file.readlines()
try:
with open(path, encoding='utf-8') as file:
path_content = file.readlines()
with open(target, encoding='utf-8') as file:
target_content = file.readlines()
except PermissionError:
res = input(f'No permission to view {path}, overwrite? [Y/n] ')
if res.lower() == "n":
return "n"
diff = list(difflib.context_diff(path_content, target_content))
if not diff:
return "n"
print(
f"{path} content differs. The former is on system, the latter in Git.\n"
"`+` means it's in git and not in system."
"`-` means it's on system and not in git."
)
print("".join(diff))
match input("overwrite? [Y/n/r[everse]] ").lower():
case "" | "y":
return "y"
case "n":
return "n"
case "r":
return "r"
case _:
raise ValueError("must be y, n or r")
raise ValueError("must be y, n or r")
def symlink_files(folder: str, replace: str, prefix: str = "") -> None:
"""symlink user-writable files."""
for dirpath, _, filenames in os.walk(folder):
if dirpath == folder:
# home/ -> ~/.
base = os.path.join(replace, prefix)
basepath = replace
else:
# home/dir/ -> ~/.dir/
base = os.path.join(replace, prefix + dirpath[len(folder) + 1 :], "")
basepath = base
for filename in filenames:
path = base + filename
real_target_path = os.path.realpath(os.path.join(dirpath, filename))
target = os.path.relpath(real_target_path, basepath)
if not os.path.isfile(path) and not os.path.islink(path):
print(f"{path} missing")
match input("create symlink? [Y/n] "):
case "n":
continue
case x if x.lower() in "y":
os.makedirs(os.path.dirname(path), exist_ok=True)
os.symlink(target, path)
continue
case _:
raise ValueError("must be y, n or r")
elif not os.path.islink(path):
match diff_files(path, real_target_path):
case "n":
continue
case "r":
os.remove(target)
move(path, target)
os.symlink(target, path)
case "y":
os.remove(path)
os.symlink(target, path)
elif actual_target := os.readlink(path) not in (target, real_target_path):
print(
f"{path} incorrect target\n\t{actual_target} "
f"should be\n\t{target}"
)
def fix(path: str, target: str) -> None:
"""fix, or suggest how to fix, an incorrect symlink"""
response = input("\tresolve? [Y/n/r[everse]]: ")
if "r" in response:
os.remove(target)
copyfile(path, target)
elif "n" not in response:
if os.path.islink(path):
os.remove(path)
if not os.path.isdir(os.path.dirname(path)):
print(f"creating directory {os.path.dirname(path)}")
os.makedirs(os.path.dirname(path))
os.symlink(target, path)
if __name__ == "__main__":
# print(__file__)
main()