-
Notifications
You must be signed in to change notification settings - Fork 0
/
dotfiles.py
executable file
·378 lines (315 loc) · 12.4 KB
/
dotfiles.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/usr/bin/env python3
# Dotfiles symlinks handler script, with support for storing defined files and
# symlink paths in a file.json file, each directory represents a package
# paths are expanded as /home-dir/dotdir/package/file and a simlink is placed
# /home-dir/"defined_path"
# options for testing symlinked files, listing packages in json , and
# prewiewing what symlinks may be created are included as aguments
# TODO:
# options for backing up existing configurations files that might be found
# create a tk gui
import os
from io import StringIO
import json
import shutil
import argparse
import sys
home_dir = os.environ["HOME"]
dotdir = os.path.join(home_dir,'.dotfiles')
backup_dir = os.path.join(home_dir ,'.dotfiles.backup')
version = "0.1"
#TODO use these:
#dot_local_repo = os.environ["HOME"] + "/.dotfiles/repo/"
#dot_repo_url = ""
dot_files = { }
class CLS:
""" Colors and tags for terminal """
dark = '\033[30m'
red = '\033[31m'
green = '\033[32m'
yellow = '\033[33m'
blue = '\033[34m'
magenta = '\033[35m'
lightblue = '\033[36m'
none = '\033[0m'
bdark = '\033[40m'
bred = '\033[41m'
bgreen = '\033[42m'
byellow = '\033[43m'
bblue = '\033[44m'
bmagenta = '\033[45m'
blightblue = '\033[46m'
tags = {
'symlinked': bgreen+dark +' Symlinked! '+none+' ',
'warn_not_symlink': bred+dark +' Not Symlinked! '+none+' ',
'warn_wrong_symlink': bmagenta+dark+' Wrong Symlink! '+none+' ',
'warn_fnoexist': bred+dark +' No File! '+none+' ',
'package': bgreen+dark +' Package '+none+' ',
}
# TODO: Implement DotPath object for handling multi-os cases
#class DotPath(object):
#"""docstring for Path"""
#def __init__(self, package, source, dest_dict ):
#super(Path, self).__init__()
#self.source = source
#self.dest_dict = dest_dict
#def check(self):
#"""docstring for check"""
#def expand(self):
#"""docstring for check"""
class WrongSymlinkError(Exception):
"""docstring for WrongSymlinkError"""
def __init__(self, bad_dest, file_in_home):
self.file_in_home = file_in_home
self.bad_dest = bad_dest
def __str__(self):
return repr(self.bad_dest),repr(self.file_in_home)
class DotFile:
"""docstring for DotFile"""
def __init__(self, package_name = "", paths = dict()):
self.package_name = package_name
self.paths_dict = paths
self.expanded_paths_dict = self.expandPaths(paths)
def addPath(self):
pass
def readPaths(self):
"""docstring for readPaths"""
pass
def backup(self, file_name):
source, dest = self.expandLink(file_name)
if not os.path.exists(backup_dir):
os.mkdir(backup_dir)
backup_path=os.path.join(backup_dir,self.package_name,file_name)
os.makedirs(os.path.dirname(backup_path))
if os.path.isdir(dest):
print("copying a dir")
print(dest)
print("to a dir")
print(backup_path)
#shutil.copytree()
else:
shutil.copyfile(dest,backup_path)
def symlinkPaths(self):
"""docstring for symlinkPaths"""
paths_dict = self.paths_dict
(cols, rows) = shutil.get_terminal_size()
print ( CLS.yellow + (cols * "-")+ CLS.none)
print (" Symlinking package: " + CLS.magenta + self.package_name + CLS.none)
print ( CLS.yellow + (cols * "-")+ CLS.none)
for file_name in paths_dict:
source, dest = self.expandLink(file_name)
try:
if self.isLinked(file_name):
print(CLS.tags["symlinked"] + CLS.green + dest +
CLS.none + " -> " + CLS.lightblue + source + CLS.none +
" was symlinked")
#Symlink done go to next
continue
except WrongSymlinkError as e:
print(CLS.tags["warn_wrong_symlink"] + CLS.red + dest +
CLS.none + " -> " + CLS.yellow + os.readlink(dest)+ CLS.none)
print("Correcting! delting link " + dest)
os.remove(dest)
except FileExistsError as e:
print(CLS.tags["warn_not_symlink"] + CLS.red + dest + CLS.none)
print("Correcting! making backup of found file ")
self.backup(file_name)
os.remove(dest)
except FileNotFoundError as e:
print(CLS.tags["warn_fnoexist"] + dest )
base_path = os.path.dirname(dest)
if not os.path.exists(base_path):
os.makedirs(base_path)
#Doing symlink
print (CLS.tags["package"]+self.package_name + ">> symlinking "+
" source: " + CLS.green+source + CLS.none +
" dest: " + CLS.lightblue+dest )
os.symlink(source,dest)
def isLinked(self, file_name):
source, dest = self.expandLink(file_name)
if os.path.exists(dest) and os.path.exists(source):
if os.path.islink(dest):
if os.readlink(dest) == source :
return True
else:
raise WrongSymlinkError(dest, os.readlink(dest))
else:
raise FileExistsError(dest)
else:
raise FileNotFoundError(dest)
def removePaths(self):
"""docstring for symlinkPaths"""
pass
def restorePaths(self):
"""docstring for restorePaths"""
pass
def printPaths(self, compact=False):
"""""
Prints the paths in this DotConfig, with a source and destination
for the symlink, the paths are expanded by default
"""
(cols, rows) = shutil.get_terminal_size()
print( CLS.lightblue + (cols * "-")+ CLS.none)
print(" Files for package: "+ CLS.green + self.package_name + CLS.none)
print( CLS.lightblue + (cols * "-")+ CLS.none)
paths_dict = dict()
if compact :
paths_dict = self.paths_dict
else:
paths_dict = self.expanded_paths_dict
for f in paths_dict:
source = f
dest = paths_dict[f]
print(CLS.yellow+"Symlink: " +
CLS.green + dest +
CLS.none+" >> "+
CLS.lightblue + source + CLS.none)
def expandPaths(self, paths={}):
expanded_paths={}
for file_name in paths:
src, dest = self.expandLink(file_name)
expanded_paths[src]=dest
return expanded_paths
def expandLink(self, file_name):
"""
Expands a set of paths to symlink,
returns file_in_repo, file_in_home
"""
file_in_repo = os.path.join(dotdir,self.package_name,file_name)
file_in_home = os.path.join(home_dir,self.paths_dict[file_name])
return file_in_repo, file_in_home
def testPaths(self):
"""Test if expanded paths are symlinked"""
paths_dict = self.paths_dict
(cols, rows) = shutil.get_terminal_size()
print ( CLS.yellow + (cols * "-")+ CLS.none)
print (" Test package: " + CLS.magenta + self.package_name + CLS.none)
print ( CLS.yellow + (cols * "-")+ CLS.none)
for file_name in paths_dict:
source, dest = self.expandLink(file_name)
try:
if self.isLinked(file_name):
print(CLS.tags["symlinked"] + CLS.green + dest +
CLS.none + " -> " + CLS.lightblue + source + CLS.none)
except WrongSymlinkError as e:
print(CLS.tags["warn_wrong_symlink"] + CLS.red + dest +
CLS.none + " -> " + CLS.yellow + os.readlink(dest)+ CLS.none)
except FileExistsError as e:
print(CLS.tags["warn_not_symlink"] + CLS.red + dest + CLS.none)
except FileNotFoundError as e:
print(CLS.tags["warn_fnoexist"] + dest )
def createDotfiles():
"""docstring for createDotfiles"""
#io = StringIO()
jd = json.JSONDecoder()
f_dotstore = open(dotdir +"/files.json")
dotdict = jd.decode(f_dotstore.read())
f_dotstore.close()
#print(dotdict)
dot_files = dict()
for package in dotdict:
paths_dict = dotdict[package]
dot_files[package] = DotFile(package, paths_dict)
return dot_files
def updateDotfiles():
"""docstring for updateDotfiles"""
#TODO
pass
def restoreDotfiles():
"""docstring for restoreDotfiles"""
pass
def disableDotfile():
"""docstring for restoreDotfiles"""
pass
def checkDotfiles(packages=None):
(cols, rows) = shutil.get_terminal_size()
text = " Printing configuration files for packages in files.json "
fill = int( (cols - len(text))/2 )
fill2 = (cols - len(text)) - fill
print ( CLS.bgreen+fill*" " + CLS.dark+text +
CLS.bgreen+fill2*" " + CLS.none)
#Printing
if len(packages)== 0 or packages == ['all']:
for package in dot_files :
dot_files[package].printPaths()
else :
for package in packages :
dot_files[package].printPaths()
def listDotfiles():
(cols, rows) = shutil.get_terminal_size()
text = " Printing packages available in files.json "
fill = int( (cols - len(text))/2 )
fill2 = (cols - len(text)) - fill
print ( CLS.bgreen+fill*" " + CLS.dark+text +
CLS.bgreen+fill2*" " + CLS.none)
#Printing
for package in dot_files :
print(CLS.tags["package"]+ package)
def testDotfiles(packages=None):
(cols, rows) = shutil.get_terminal_size()
text = " Testing configuration files for packages in files.json "
fill = int( (cols - len(text))/2 )
fill2 = (cols - len(text)) - fill
print ( CLS.byellow+fill*" " + CLS.dark+text +
CLS.byellow+fill2*" " + CLS.none)
#Testing
if len(packages) == 0 or packages == ['all']:
for package in dot_files :
dot_files[package].testPaths()
else :
for package in packages :
dot_files[package].testPaths()
def enableDotfiles(packages=[]):
"""docstring for restoreDotfiles"""
if packages == ['all'] :
for pac in dot_files:
dot_files[pac].symlinkPaths()
else:
for pac in packages:
dot_files[pac].symlinkPaths()
def commitPush():
"""docstring for restoreDotfiles"""
pass
def getArgs():
"""Parses command-line arguments and returns args if sys.argv has more
than 1 argument, otherwise prints help and exits"""
parser = argparse.ArgumentParser(
prog="dotfiles.py",
description="Dotfiles symlinks handler")
parser.add_argument("-s", "--show", action="store_true",
help="Prints the list of dotfiles in files.json")
parser.add_argument("-t", "--test", action="store_true",
help="Prints a test for the symlinks that should be placed")
parser.add_argument("-l", "--list", action="store_true",
help="Prints packages avilabe in files.json")
parser.add_argument("-e", "--enable", action="store_true",
help="Enables a set of cofiguration files for package")
parser.add_argument("-d", "--disable", action="store_true",
help="Disables a set of cofiguration files for package")
parser.add_argument("-u", "--update", action="store_true",
help="Update a set of cofiguration files for package")
parser.add_argument("-r", "--restore", action="store_true",
help="Restore a set of cofiguration files for package")
parser.add_argument("packages", nargs=argparse.REMAINDER )
parser.add_argument("-c", "--chage-repo", dest="repo", action="store",
help="Change the repository of cofiguration files to repo")
parser.add_argument("-p", "--push-changes",action="store_true",
help="Push changes in the repository repo to remote origin")
args = parser.parse_args()
if len(sys.argv) < 2:
parser.print_help()
exit(1)
else:
return args
if __name__ == '__main__':
dot_files = createDotfiles()
args = getArgs()
if args.show:
checkDotfiles(args.packages)
if args.test:
testDotfiles(args.packages)
if args.enable:
enableDotfiles(args.packages)
if args.list:
listDotfiles()
exit()