-
Notifications
You must be signed in to change notification settings - Fork 0
/
dotfiles.py
213 lines (173 loc) · 6 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
import json
import os
from dotfiles import dotspec
from dotfiles import converter
from impulse.args import args
from impulse.core import debug
command = args.ArgumentParser(complete=True)
repo_location = os.path.join(os.environ['HOME'], '.dotfiles')
def CheckUnit(group, unit):
hostname = os.uname()[1]
expected_link = hostname if hostname in unit.hosts else 'default'
expected_link = os.path.join(expected_link, unit.name)
expected_link = os.path.join(repo_location, group.groupname, expected_link)
actual_file = unit.install.replace('$HOME', os.environ['HOME'])
if not os.path.exists(actual_file):
# Either does not exist, or is a _broken_ symlink
if os.path.islink(actual_file):
return '!!', actual_file, expected_link
return '!', actual_file, expected_link
if not os.path.islink(actual_file):
return '*', actual_file, expected_link
if os.readlink(actual_file) != expected_link:
return '~', actual_file, expected_link
return 'OK', actual_file, expected_link
def GetConfig():
if not os.path.exists(repo_location):
print('no dotfiles installation. exiting...')
return
dotfiles_info = os.path.join(repo_location, 'dotfiles.json')
if not os.path.exists(dotfiles_info):
print('no dotfiles installation. exiting...')
return
with open(dotfiles_info) as f:
return dotspec.ReadSpec(json.loads(f.read()), dotspec.DOTFILE_SPEC)
@command
def init():
if os.path.exists(repo_location):
print('dotfiles directory already exists, exiting...')
return
os.makedirs(repo_location)
with open(os.path.join(repo_location, 'dotfiles.json'), 'w') as f:
f.write('[]')
@command
def convert():
if not os.path.exists(repo_location):
print('no existing dotfiles installation. exiting...')
return
dotfiles_info = os.path.join(repo_location, 'dotfiles')
if not os.path.exists(dotfiles_info):
print('no existing dotfiles installation. exiting...')
return
with open(os.path.join(repo_location, 'dotfiles.json'), 'w') as f:
config = converter.ReadOldConfig(dotfiles_info)
f.write(json.dumps(dotspec.ToGlob(config), indent=2))
f.write('\n')
@command
def info(inspect:str='groups'):
config = GetConfig()
for seg in inspect.split(':'):
if type(config) is list:
index = int(seg)
if index < len(config):
config = config[index]
else:
print(f'invalid index {index} in {config}')
return
continue
cfg = getattr(config, seg, None)
if cfg is None:
print(f'invalid {seg} in {config.__slots__}')
return
config = cfg
print(config)
@command
def status(showall:bool=False):
hostname = os.uname()[1]
config = GetConfig()
for group in config.groups:
for unit in group.units:
status, _, link = CheckUnit(group, unit)
if showall or status != 'OK':
print(f'[{status}] {unit.install} -> {link}')
@command
def sync(group:str=None):
hostname = os.uname()[1]
config = GetConfig()
for g in config.groups:
if group is not None and g.groupname != group:
continue
for unit in g.units:
status, link_from, link_to = CheckUnit(g, unit)
if 'OK' in status:
print(f'Skipping {g.groupname}/{unit.name} [OK]')
elif status in ('~', '!!'):
print(f'Creating link {link_from} -> {link_to}')
os.system(f'rm {link_from} && ln -s {link_to} {link_from}')
elif status == '!':
print(f'Creating link {link_from} -> {link_to}')
os.system(f'ln -s {link_to} {link_from}')
elif status == '*':
print(f'Skipping {g.groupname}/{unit.name}')
print('file exists and is not a symlink. please check manually')
@command
def mkhosted(dotfile:str, group:str):
config = GetConfig()
grps = [g for g in config.groups if g.groupname == group]
if not grps:
print(f'Invalid group: {group}')
return
grp = grps[0]
units = [u for u in grp.units if u.name == dotfile]
if not units:
avail = [u.name for u in grp.units]
print(f'Invalid dotfile: {dotfile}. Choose from {avail}')
return
hostname = os.uname()[1]
unit = units[0]
if hostname in unit.hosts:
print(f'Already a host specific entry for {dotfile}@{hostname}')
return
status, linkname, oldentry = CheckUnit(grp, unit)
if status != 'OK':
print(f'Invalid dotfile setup: [{status}]')
return
newentry = os.path.join(repo_location, group, hostname, dotfile)
os.system(f'mkdir -p {os.path.dirname(newentry)}')
os.system(f'cp {oldentry} {newentry}')
unit.hosts.append(hostname)
with open(os.path.join(repo_location, 'dotfiles.json'), 'w') as f:
f.write(json.dumps(dotspec.ToGlob(config), indent=2))
f.write('\n')
os.system(f'rm {linkname} && ln -s {newentry} {linkname}')
@command
def track(dotfile:args.File, group:str, host:bool=False, rename:str=None):
dotfile = dotfile.value()
if not os.path.exists(dotfile) or os.path.islink(dotfile):
print('Can only track a non-link existing file')
return
config = GetConfig()
grp = None
for g in config.groups:
if g.groupname == group:
grp = g
break
if grp == None:
grp = dotspec.GetType(dotspec.GROUP_SPEC)(group, [])
config.groups.append(grp)
name = dotfile.split('/')[-1].strip()
if name.startswith('.'):
name = name[1:].strip()
if rename is not None:
name = rename
for unit in grp.units:
if unit.name == name:
print('dotfile already exists, cant overwrite')
return
unit = dotspec.GetType(dotspec.UNIT_SPEC)(name, ['default'], dotfile, [])
hostname = 'default'
if host:
hostname = os.uname()[1]
unit.hosts.append(hostname)
print(f'WARNING: creating stub file {name} in {group}/default')
destination = os.path.join(repo_location, group, hostname, name)
grp.units.append(unit)
with open(os.path.join(repo_location, 'dotfiles.json'), 'w') as f:
f.write(json.dumps(dotspec.ToGlob(config), indent=2))
f.write('\n')
os.system(
f'mkdir -p {os.path.dirname(destination)} && '
f'mv {dotfile} {destination} && '
f'ln -s {destination} {dotfile}')
def main():
command.eval()