central 0.2.3 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/bin/central +32 -9
- data/lib/central.rb +310 -223
- metadata +4 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ecf6eb46fbc67795241445bc38629214a0d9eb2b63237f3ade4ccc5009b2eb4f
|
4
|
+
data.tar.gz: d105df04c79771d9ae767c471185ec80147fbf7db6d552affa7d01185e4fe965
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3ec6fa7e823098fc75b55c66399b25da990bc3bdb58d9f54b1c9cb7f26b00eb123699c6772800cfc54aa68826b6152b61284c76555dd248183fbb2d9b497010
|
7
|
+
data.tar.gz: 5501d4249660a4d1d288aa6421439045f1ba66da1c316c62517a09d0ed1e7fb14f99f40fbef82966ae7d12ce411c61d0411f64872e4ace77bfdd2c52087ddd11
|
data/bin/central
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
#
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# -------------------------------------------------------------------------
|
5
5
|
# # central - dot files manager licensed under LGPLv3 (see LICENSE file) |
|
@@ -7,15 +7,38 @@
|
|
7
7
|
# -------------------------------------------------------------------------
|
8
8
|
|
9
9
|
require 'central'
|
10
|
+
require 'optparse'
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
VERSION = 'v0.2.9'
|
13
|
+
|
14
|
+
options = {:color => true}
|
15
|
+
OptionParser.new do |opts|
|
16
|
+
opts.banner = %{central #{VERSION} - created by Dmitry Geurkov (d.geurkov@gmail.com) and licensed under LGPLv3
|
17
|
+
|
18
|
+
Usage: central [options] [directory|configuration.rb, ...]
|
19
|
+
|
20
|
+
Description: if directory is specified it will use configuration.rb file inside that directory
|
21
|
+
if no [directory|configuration.rb] is specified
|
22
|
+
it will use configuration.rb in current working directory
|
23
|
+
|
24
|
+
Options:}
|
25
|
+
opts.on("-m", "--monitor", "Monitor erb files for changes and reprocess them automatically") do |_|
|
26
|
+
options[:monitor] = true
|
15
27
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
28
|
+
|
29
|
+
opts.on("-n", "--no-color", "Do not colorize output") do |_|
|
30
|
+
options[:color] = false
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-v", "--version", "Print version") do |_|
|
34
|
+
puts "central #{VERSION}"
|
35
|
+
exit
|
19
36
|
end
|
37
|
+
end.parse!
|
38
|
+
|
39
|
+
if options[:monitor]
|
40
|
+
run_central(ARGV,options[:color])
|
41
|
+
run_monitors
|
42
|
+
else
|
43
|
+
run_central(ARGV,options[:color])
|
20
44
|
end
|
21
|
-
central(ARGV)
|
data/lib/central.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# -------------------------------------------------------------------------
|
3
4
|
# # central - dot files manager licensed under LGPLv3 (see LICENSE file) |
|
4
5
|
# # written in Ruby by Dmitry Geurkov (d.geurkov@gmail.com) |
|
@@ -8,6 +9,42 @@ require 'erb'
|
|
8
9
|
require 'socket'
|
9
10
|
require 'open3'
|
10
11
|
require 'fileutils'
|
12
|
+
require 'digest'
|
13
|
+
|
14
|
+
# cli colors
|
15
|
+
$colored = true
|
16
|
+
COLOR_RED = 31
|
17
|
+
COLOR_GREEN = 32
|
18
|
+
|
19
|
+
# monitors
|
20
|
+
$monitors = {}
|
21
|
+
|
22
|
+
# putsc, puts with color
|
23
|
+
def color(message, color)
|
24
|
+
if $colored
|
25
|
+
"\e[#{color}m#{message}\e[0m"
|
26
|
+
else
|
27
|
+
message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# info
|
32
|
+
def info(message, param = nil)
|
33
|
+
puts color(message, COLOR_GREEN) +
|
34
|
+
(param.nil? ? '' : ': ' + param)
|
35
|
+
end
|
36
|
+
|
37
|
+
# error
|
38
|
+
def error(message, param = nil)
|
39
|
+
puts color(message, COLOR_RED) +
|
40
|
+
(param.nil? ? '' : ': ' + param)
|
41
|
+
end
|
42
|
+
|
43
|
+
# fail, print message to stderr and exit with 1
|
44
|
+
def fail(message, param = nil)
|
45
|
+
error message, param
|
46
|
+
exit 1
|
47
|
+
end
|
11
48
|
|
12
49
|
# get hostname
|
13
50
|
def hostname
|
@@ -17,115 +54,106 @@ end
|
|
17
54
|
# get operating system
|
18
55
|
def os
|
19
56
|
if RUBY_PLATFORM.include?('linux')
|
20
|
-
|
57
|
+
'linux'
|
21
58
|
elsif RUBY_PLATFORM.include?('darwin')
|
22
|
-
|
59
|
+
'osx'
|
23
60
|
elsif RUBY_PLATFORM.include?('freebsd')
|
24
|
-
|
61
|
+
'freebsd'
|
25
62
|
elsif RUBY_PLATFORM.include?('solaris')
|
26
|
-
|
63
|
+
'solaris'
|
27
64
|
end
|
28
65
|
end
|
29
66
|
|
30
67
|
def linux?
|
31
|
-
|
68
|
+
os == 'linux'
|
32
69
|
end
|
33
70
|
|
34
71
|
def osx?
|
35
|
-
|
72
|
+
os == 'osx'
|
36
73
|
end
|
37
74
|
|
38
75
|
def freebsd?
|
39
|
-
|
76
|
+
os == 'freebsd'
|
40
77
|
end
|
41
78
|
|
42
79
|
def solaris?
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
# run shell command and get output, optionaly can print command running
|
47
|
-
|
48
|
-
def shell(command,
|
49
|
-
|
50
|
-
|
51
|
-
stdout =
|
52
|
-
stdout_line =
|
53
|
-
stderr =
|
54
|
-
stderr_line =
|
55
|
-
Open3.popen3(command) do |
|
80
|
+
os == 'solaris'
|
81
|
+
end
|
82
|
+
|
83
|
+
# run shell command and get output, optionaly can print command running
|
84
|
+
# if verbose and if not silent will also print to stdout and stderr
|
85
|
+
def shell(command, verbose: false, silent: true)
|
86
|
+
info 'Executing', command if verbose
|
87
|
+
exit_code = nil
|
88
|
+
stdout = String.new
|
89
|
+
stdout_line = String.new
|
90
|
+
stderr = String.new
|
91
|
+
stderr_line = String.new
|
92
|
+
Open3.popen3(command) do |_, o, e, t|
|
56
93
|
stdout_open = true
|
57
94
|
stderr_open = true
|
58
|
-
while stdout_open
|
95
|
+
while stdout_open || stderr_open
|
59
96
|
if stdout_open
|
60
97
|
begin
|
61
98
|
ch = o.read_nonblock(1)
|
62
|
-
stdout.insert(-1,ch)
|
99
|
+
stdout.insert(-1, ch)
|
63
100
|
unless silent
|
64
|
-
stdout_line.insert(-1,ch)
|
101
|
+
stdout_line.insert(-1, ch)
|
65
102
|
if ch == "\n"
|
66
103
|
STDOUT.puts stdout_line
|
67
104
|
stdout_line = ''
|
68
105
|
end
|
69
106
|
end
|
70
107
|
rescue IO::WaitReadable
|
71
|
-
IO.select([o],nil,nil,0.
|
108
|
+
IO.select([o], nil, nil, 0.01) unless stderr_open
|
72
109
|
rescue EOFError
|
73
110
|
stdout_open = false
|
74
111
|
end
|
75
112
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
113
|
+
next unless stderr_open
|
114
|
+
|
115
|
+
begin
|
116
|
+
ch = e.read_nonblock(1)
|
117
|
+
stderr.insert(-1, ch)
|
118
|
+
unless silent
|
119
|
+
stderr_line.insert(-1, ch)
|
120
|
+
if ch == "\n"
|
121
|
+
STDERR.puts stderr_line
|
122
|
+
stderr_line = ''
|
86
123
|
end
|
87
|
-
rescue IO::WaitReadable
|
88
|
-
IO.select([e],nil,nil,0.1) unless stdout_open
|
89
|
-
rescue EOFError
|
90
|
-
stderr_open = false
|
91
124
|
end
|
125
|
+
rescue IO::WaitReadable
|
126
|
+
IO.select([e], nil, nil, 0.01) unless stdout_open
|
127
|
+
rescue EOFError
|
128
|
+
stderr_open = false
|
92
129
|
end
|
93
130
|
end
|
94
|
-
|
95
|
-
end
|
96
|
-
if stderr == ''
|
97
|
-
return stdout
|
98
|
-
else
|
99
|
-
return stdout, stderr
|
131
|
+
exit_code = t.value
|
100
132
|
end
|
133
|
+
[exit_code, stdout, stderr]
|
101
134
|
end
|
102
135
|
|
103
136
|
# run shell command with sudo prefix, acts same as shell
|
104
|
-
def sudo(command,
|
105
|
-
|
137
|
+
def sudo(command, verbose:, silent:)
|
138
|
+
shell('sudo ' + command, verbose: verbose, silent: silent)
|
106
139
|
end
|
107
140
|
|
108
141
|
# function used to check that system has all required tools installed
|
109
|
-
def check_tool(name,check)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
STDERR.puts "#{name} not found, please install it to use central"
|
114
|
-
exit 1
|
115
|
-
end
|
116
|
-
rescue Errno::ENOENT
|
117
|
-
STDERR.puts "#{name} not found, please install it to use central"
|
118
|
-
exit 1
|
142
|
+
def check_tool(name, check)
|
143
|
+
_, output, = shell(check + ' 2>&1')
|
144
|
+
if output == '' || output.downcase.include?('command not found')
|
145
|
+
fail "#{name} not found, please install it to use central"
|
119
146
|
end
|
147
|
+
rescue Errno::ENOENT
|
148
|
+
fail "#{name} not found, please install it to use central"
|
120
149
|
end
|
121
150
|
|
122
|
-
check_tool('file','file --version')
|
123
|
-
check_tool('grep','grep --version')
|
124
|
-
check_tool('ln','ln --version')
|
125
|
-
check_tool('readlink','readlink --version')
|
126
|
-
check_tool('git','git --version')
|
127
|
-
check_tool('curl','curl --version')
|
128
|
-
|
151
|
+
check_tool('file', 'file --version')
|
152
|
+
check_tool('grep', 'grep --version')
|
153
|
+
check_tool('ln', 'ln --version')
|
154
|
+
check_tool('readlink', 'readlink --version')
|
155
|
+
check_tool('git', 'git --version')
|
156
|
+
check_tool('curl', 'curl --version')
|
129
157
|
|
130
158
|
# current working directory
|
131
159
|
def pwd
|
@@ -134,7 +162,7 @@ end
|
|
134
162
|
|
135
163
|
# absolute path
|
136
164
|
def abs(path)
|
137
|
-
|
165
|
+
File.absolute_path(File.expand_path(path))
|
138
166
|
end
|
139
167
|
|
140
168
|
# change current working directory
|
@@ -148,10 +176,30 @@ def file_exists?(path)
|
|
148
176
|
File.file?(path) && File.readable?(path)
|
149
177
|
end
|
150
178
|
|
179
|
+
# get file size
|
180
|
+
def file_size(path)
|
181
|
+
File.new(abs(path)).size
|
182
|
+
end
|
183
|
+
|
184
|
+
# get file creation time
|
185
|
+
def file_ctime(path)
|
186
|
+
File.new(abs(path)).ctime
|
187
|
+
end
|
188
|
+
|
189
|
+
# get file modification time
|
190
|
+
def file_mtime(path)
|
191
|
+
File.new(abs(path)).mtime
|
192
|
+
end
|
193
|
+
|
194
|
+
# get directory entries
|
195
|
+
def dir_entries(path)
|
196
|
+
Dir.entries(abs(path)).select { |f| f != '.' && f != '..' }
|
197
|
+
end
|
198
|
+
|
151
199
|
# check if directory exists
|
152
200
|
def dir_exists?(path)
|
153
201
|
path = abs(path)
|
154
|
-
Dir.
|
202
|
+
Dir.exist?(path)
|
155
203
|
end
|
156
204
|
|
157
205
|
# get directory name of a file
|
@@ -166,80 +214,75 @@ end
|
|
166
214
|
|
167
215
|
# get full path of symlink
|
168
216
|
def symlink_path(symlink)
|
169
|
-
shell("readlink \"#{abs(symlink)}\" 2>&1")
|
217
|
+
_, out, = shell("readlink \"#{abs(symlink)}\" 2>&1")
|
218
|
+
out.strip
|
219
|
+
end
|
220
|
+
|
221
|
+
# calculate SHA2 digest for a file
|
222
|
+
def sha2(file)
|
223
|
+
Digest::SHA256.hexdigest read(file)
|
170
224
|
end
|
171
225
|
|
172
226
|
# make directory including intermediate directories
|
173
227
|
def mkdir(path)
|
174
228
|
path = abs(path)
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
end
|
182
|
-
puts "Created directory: #{path}"
|
229
|
+
return if dir_exists?(path)
|
230
|
+
|
231
|
+
exit_code, out, = shell("mkdir -p \"#{path}\" 2>&1")
|
232
|
+
unless exit_code.success?
|
233
|
+
error out
|
234
|
+
fail "Couldn't create directory", path
|
183
235
|
end
|
236
|
+
info 'Created directory', path
|
184
237
|
end
|
185
238
|
|
186
239
|
# remove file/directory
|
187
|
-
def rm(path,recursive
|
240
|
+
def rm(path, recursive: false)
|
188
241
|
path = abs(path)
|
189
|
-
|
190
|
-
recursive = '-R '
|
191
|
-
else
|
192
|
-
recursive = ''
|
193
|
-
end
|
242
|
+
recursive = recursive ? '-R ' : ''
|
194
243
|
is_dir = dir_exists?(path)
|
195
244
|
is_symlink = symlink?(path)
|
196
|
-
out = shell("rm #{recursive}-f \"#{path}\" 2>&1")
|
197
|
-
unless
|
198
|
-
|
199
|
-
|
200
|
-
exit 1
|
245
|
+
exit_code, out, = shell("rm #{recursive}-f \"#{path}\" 2>&1")
|
246
|
+
unless exit_code.success?
|
247
|
+
error out
|
248
|
+
fail "Couldn't remove path", path
|
201
249
|
end
|
202
250
|
if is_dir
|
203
|
-
|
251
|
+
info 'Removed directory', path
|
204
252
|
elsif is_symlink
|
205
|
-
|
253
|
+
info 'Removed symlink', path
|
206
254
|
else
|
207
|
-
|
255
|
+
info 'Removed file', path
|
208
256
|
end
|
209
257
|
end
|
210
258
|
|
211
259
|
# remove directory recursively
|
212
260
|
def rmdir(path)
|
213
|
-
rm(path,true)
|
261
|
+
rm(path, recursive: true)
|
214
262
|
end
|
215
263
|
|
216
264
|
# touch file
|
217
265
|
def touch(path)
|
218
266
|
path = abs(path)
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
end
|
226
|
-
puts "Touched file: #{path}"
|
267
|
+
return if file_exists?(path)
|
268
|
+
|
269
|
+
exit_code, out, = shell("touch \"#{path}\" 2>&1")
|
270
|
+
unless exit_code.success?
|
271
|
+
error out
|
272
|
+
fail "Couldn't touch file", path
|
227
273
|
end
|
274
|
+
info 'Touched file', path
|
228
275
|
end
|
229
276
|
|
230
277
|
# change file permissions
|
231
|
-
def chmod(path,permissions,recursive
|
278
|
+
def chmod(path, permissions, recursive: false)
|
232
279
|
path = abs(path)
|
233
|
-
|
234
|
-
recursive = '-R '
|
235
|
-
else
|
236
|
-
recursive = ''
|
237
|
-
end
|
280
|
+
recursive = recursive ? '-R ' : ''
|
238
281
|
shell("chmod #{recursive}#{permissions} \"#{path}\"")
|
239
282
|
end
|
240
283
|
|
241
284
|
# symlink path
|
242
|
-
def symlink(from,to)
|
285
|
+
def symlink(from, to)
|
243
286
|
from = abs(from)
|
244
287
|
to = abs(to)
|
245
288
|
if symlink?(from)
|
@@ -248,192 +291,216 @@ def symlink(from,to)
|
|
248
291
|
symlink from, to
|
249
292
|
end
|
250
293
|
elsif file_exists?(from)
|
251
|
-
|
252
|
-
exit 1
|
294
|
+
fail "File #{from} exists in place of symlink..."
|
253
295
|
elsif dir_exists?(from)
|
254
|
-
|
255
|
-
exit 1
|
296
|
+
fail "Directory #{from} exists in place of symlink..."
|
256
297
|
else
|
257
|
-
out = shell("ln -s \"#{to}\" \"#{from}\" 2>&1")
|
258
|
-
unless
|
259
|
-
|
260
|
-
|
261
|
-
exit 1
|
298
|
+
exit_code, out, = shell("ln -s \"#{to}\" \"#{from}\" 2>&1")
|
299
|
+
unless exit_code.success?
|
300
|
+
error out
|
301
|
+
fail "Couldn't create symlink", "#{from} → #{to}"
|
262
302
|
end
|
263
|
-
|
303
|
+
info 'Created symlink', "#{from} → #{to}"
|
264
304
|
end
|
265
305
|
end
|
266
306
|
|
267
307
|
# git clone url into a path
|
268
|
-
def git(url,path,branch
|
308
|
+
def git(url, path, branch: nil, silent: true, depth: nil)
|
269
309
|
path = abs(path)
|
270
310
|
if dir_exists?(path) && dir_exists?("#{path}/.git")
|
271
|
-
cwd = pwd
|
311
|
+
cwd = pwd
|
272
312
|
chdir path
|
273
313
|
out = nil
|
274
314
|
if branch
|
275
|
-
out = shell('git fetch 2>&1',
|
276
|
-
if out.size
|
277
|
-
|
278
|
-
end
|
279
|
-
out = shell("git checkout #{branch} 2>&1",{:silent => silent})
|
315
|
+
_, out, = shell('git fetch 2>&1', silent: silent)
|
316
|
+
puts out if silent && out.size.positive?
|
317
|
+
_, out, = shell("git checkout #{branch} 2>&1", silent: silent)
|
280
318
|
unless out.downcase.include? 'is now at'
|
281
319
|
puts out if silent
|
282
320
|
end
|
283
|
-
out = shell("git pull origin #{branch} 2>&1",
|
321
|
+
_, out, = shell("git pull origin #{branch} 2>&1", silent: silent)
|
284
322
|
else
|
285
|
-
out = shell('git pull 2>&1',
|
323
|
+
_, out, = shell('git pull 2>&1', silent: silent)
|
286
324
|
end
|
287
|
-
unless out.downcase.include?
|
325
|
+
unless out.downcase.include? 'already up'
|
288
326
|
puts out if silent
|
289
|
-
|
327
|
+
info 'Git repository pulled', "#{url} → #{path}"
|
290
328
|
end
|
291
329
|
chdir cwd
|
292
330
|
else
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
end
|
298
|
-
out = shell("git clone #{branch}#{url} \"#{path}\" 2>&1",{:silent => silent})
|
331
|
+
branch = branch ? "-b #{branch} " : ''
|
332
|
+
depth = depth ? "--depth #{depth} " : ''
|
333
|
+
_, out, = shell("git clone #{depth}#{branch}#{url} \"#{path}\" 2>&1",
|
334
|
+
silent: silent)
|
299
335
|
puts out if silent
|
300
|
-
|
336
|
+
info 'Git repository cloned', "#{url} → #{path}"
|
301
337
|
end
|
302
338
|
end
|
303
339
|
|
304
340
|
# download url into a path using curl
|
305
|
-
def curl(url,path,verbose
|
341
|
+
def curl(url, path, content_length_check: false, verbose: false)
|
306
342
|
path = abs(path)
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
STDERR.puts "Couldn't download file from #{url}..."
|
311
|
-
exit 1
|
343
|
+
if content_length_check and file_exists?(path)
|
344
|
+
content_length = curl_headers(url, verbose: verbose)['content-length'].to_i
|
345
|
+
return if file_size(path) == content_length
|
312
346
|
end
|
313
|
-
|
314
|
-
|
347
|
+
info 'Downloading', "#{url} → #{path}"
|
348
|
+
exit_code, output, = shell("curl -s -S \"#{url}\"",
|
349
|
+
verbose: verbose, silent: true)
|
350
|
+
unless exit_code.success?
|
351
|
+
error output
|
352
|
+
fail "Couldn't download file from", url
|
315
353
|
end
|
316
|
-
File.write(path,output)
|
317
|
-
|
354
|
+
File.write(path, output)
|
355
|
+
info 'Downloaded', "#{url} → #{path}"
|
356
|
+
end
|
357
|
+
|
358
|
+
# get url response headers as Hash using curl
|
359
|
+
def curl_headers(url, method: 'HEAD', verbose: false)
|
360
|
+
exit_code, output, = shell("curl -I -X #{method} -s -S \"#{url}\"",
|
361
|
+
verbose: verbose, silent: true)
|
362
|
+
unless exit_code.success?
|
363
|
+
error output
|
364
|
+
fail "Couldn't get headers from", url
|
365
|
+
end
|
366
|
+
headers = {}
|
367
|
+
output.scan(/^(?!HTTP)([^:]+):(.*)$/).each do |m|
|
368
|
+
headers[m[0].strip.downcase] = m[1].sub("\r","").strip
|
369
|
+
end
|
370
|
+
headers
|
318
371
|
end
|
319
372
|
|
320
373
|
# read content of a file
|
321
374
|
def read(file)
|
322
375
|
file = abs(file)
|
323
|
-
if file_exists?(file)
|
324
|
-
|
325
|
-
|
326
|
-
STDERR.puts "Couldn't read file #{file}..."
|
327
|
-
exit 1
|
328
|
-
end
|
376
|
+
return File.read(file) if file_exists?(file)
|
377
|
+
|
378
|
+
fail "Couldn't read file", file
|
329
379
|
end
|
330
380
|
|
331
381
|
# write content into a file
|
332
|
-
def write(file,content)
|
382
|
+
def write(file, content)
|
333
383
|
file = abs(file)
|
334
|
-
File.write(file,content)
|
384
|
+
File.write(file, content)
|
335
385
|
end
|
336
386
|
|
337
387
|
# source file in sh/bash/zsh script
|
338
|
-
def source(file,source)
|
388
|
+
def source(file, source)
|
339
389
|
file = abs(file)
|
340
390
|
source = abs(source)
|
341
391
|
source_line = "source \"#{source}\""
|
342
|
-
out = shell("grep -Fx '#{source_line}' \"#{file}\"")
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
392
|
+
_, out, = shell("grep -Fx '#{source_line}' \"#{file}\"")
|
393
|
+
return unless out == ''
|
394
|
+
|
395
|
+
shell("echo '#{source_line}' >> \"#{file}\"")
|
396
|
+
info 'Added source', "#{source} line to #{file}"
|
347
397
|
end
|
348
398
|
|
349
399
|
# list directory content
|
350
|
-
def ls(path,
|
400
|
+
def ls(path, dotfiles: false, grep: '', dir: true, file: true)
|
351
401
|
path = abs(path)
|
352
|
-
|
353
|
-
dotfiles = '-a '
|
354
|
-
else
|
355
|
-
dotfiles = ''
|
356
|
-
end
|
402
|
+
dotfiles = dotfiles ? '-a ' : ''
|
357
403
|
command = "ls -1 #{dotfiles}\"#{path}\" 2>&1"
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
output = shell(command)
|
404
|
+
command += " | grep #{grep}" unless grep.empty?
|
405
|
+
|
406
|
+
_, output, = shell(command)
|
362
407
|
if output.downcase.end_with?('no such file or directory')
|
363
|
-
|
364
|
-
exit 1
|
408
|
+
fail "Couldn't ls directory", path
|
365
409
|
end
|
410
|
+
|
366
411
|
ls = output.split("\n")
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
412
|
+
ls = ls.keep_if { |f| !File.directory?("#{path}/#{f}") } unless dir
|
413
|
+
ls = ls.keep_if { |f| !File.file?("#{path}/#{f}") } unless file
|
414
|
+
ls
|
415
|
+
end
|
416
|
+
|
417
|
+
# compare_file
|
418
|
+
def compare_file(from, to)
|
419
|
+
from = abs(from)
|
420
|
+
to = abs(to)
|
421
|
+
FileUtils.compare_file(from, to)
|
422
|
+
end
|
423
|
+
|
424
|
+
# copy_file
|
425
|
+
def copy_file(from, to)
|
426
|
+
from = abs(from)
|
427
|
+
to = abs(to)
|
428
|
+
fail "Couldn't access file", from unless file_exists?(from)
|
429
|
+
|
430
|
+
return if file_exists?(to) && FileUtils.compare_file(from, to)
|
431
|
+
|
432
|
+
FileUtils.copy_file(from, to)
|
433
|
+
info 'Copied file', "#{from} → #{to}"
|
382
434
|
end
|
383
435
|
|
384
436
|
# copy
|
385
|
-
def copy(from,to)
|
437
|
+
def copy(from, to)
|
386
438
|
from = abs(from)
|
387
439
|
to = abs(to)
|
388
440
|
if dir_exists?(from)
|
389
|
-
(
|
441
|
+
dir_entries(from).each do |f|
|
390
442
|
FileUtils.mkdir_p(to)
|
391
|
-
copy("#{from}/#{f}","#{to}/#{f}")
|
443
|
+
copy("#{from}/#{f}", "#{to}/#{f}")
|
392
444
|
end
|
393
445
|
else
|
394
|
-
|
395
|
-
|
396
|
-
|
446
|
+
copy_file(from, to)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
# mirror
|
451
|
+
def mirror(from, to)
|
452
|
+
from = abs(from)
|
453
|
+
to = abs(to)
|
454
|
+
if dir_exists?(from)
|
455
|
+
from_entries = dir_entries(from)
|
456
|
+
if dir_exists?(to)
|
457
|
+
dir_entries(to).each do |f|
|
458
|
+
rm("#{to}/#{f}", recursive: true) unless from_entries.include?(f)
|
459
|
+
end
|
397
460
|
end
|
398
|
-
|
399
|
-
FileUtils.
|
400
|
-
|
461
|
+
from_entries.each do |f|
|
462
|
+
FileUtils.mkdir_p(to)
|
463
|
+
copy("#{from}/#{f}", "#{to}/#{f}")
|
401
464
|
end
|
465
|
+
else
|
466
|
+
copy_file(from, to)
|
402
467
|
end
|
403
468
|
end
|
404
469
|
|
405
470
|
# process erb template into an output_file
|
406
|
-
def erb(file,output_file = nil)
|
471
|
+
def erb(file, output_file = nil, monitor = true)
|
407
472
|
file = abs(file)
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
output_file = file+'.out'
|
413
|
-
end
|
414
|
-
end
|
415
|
-
if file_exists?(file)
|
416
|
-
output = ERB.new(File.read(file)).result
|
417
|
-
if File.exists?(output_file) && File.read(output_file) == output
|
418
|
-
return
|
419
|
-
end
|
420
|
-
File.write(output_file,output)
|
421
|
-
puts "Processed erb #{file} → #{output_file}"
|
422
|
-
else
|
423
|
-
STDERR.puts "Couldn't process erb file #{file}..."
|
424
|
-
exit 1
|
473
|
+
fail 'No erb file found', file unless file_exists?(file)
|
474
|
+
|
475
|
+
if output_file.nil?
|
476
|
+
output_file = file.end_with?('.erb') ? file[0...-4] : file + '.out'
|
425
477
|
end
|
478
|
+
|
479
|
+
$monitors[file] = proc { erb(file, output_file, false) } if monitor
|
480
|
+
|
481
|
+
out = ERB.new(File.read(file)).result
|
482
|
+
return if File.exist?(output_file) && File.read(output_file) == out
|
483
|
+
|
484
|
+
File.write(output_file, out)
|
485
|
+
info 'Processed erb', "#{file} → #{output_file}"
|
486
|
+
end
|
487
|
+
|
488
|
+
# monitor file for changes and execute proc if file changed
|
489
|
+
def monitor(file, &block)
|
490
|
+
file = abs(file)
|
491
|
+
fail 'No file found', file unless file_exists?(file)
|
492
|
+
|
493
|
+
$monitors[file] = block
|
426
494
|
end
|
427
495
|
|
428
496
|
# run configuration.rb file
|
429
497
|
def run(file)
|
430
|
-
cwd = pwd
|
498
|
+
cwd = pwd
|
431
499
|
file = abs(file)
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
puts "Running configuration: "+file
|
500
|
+
file = File.join(file,'configuration.rb') if not file_exists?(file) and dir_exists?(file)
|
501
|
+
fail 'No configuration file found', file unless file_exists?(file)
|
502
|
+
|
503
|
+
info 'Running configuration', file
|
437
504
|
file_cwd = file_dir(file)
|
438
505
|
chdir file_cwd
|
439
506
|
load file
|
@@ -442,15 +509,14 @@ end
|
|
442
509
|
|
443
510
|
# run configuration.rb file only if it exists
|
444
511
|
def run_if_exists(file)
|
445
|
-
if file_exists?(file)
|
446
|
-
run file
|
447
|
-
end
|
512
|
+
run file if file_exists?(file)
|
448
513
|
end
|
449
514
|
|
450
515
|
# run central configuration
|
451
|
-
def
|
452
|
-
|
453
|
-
|
516
|
+
def run_central(configurations, colored = true)
|
517
|
+
$colored = colored
|
518
|
+
if configurations.instance_of?(Array) && !configurations.empty?
|
519
|
+
configurations.each { |configuration| run configuration }
|
454
520
|
elsif configurations.instance_of?(String)
|
455
521
|
run configurations
|
456
522
|
else
|
@@ -458,3 +524,24 @@ def central(configurations)
|
|
458
524
|
end
|
459
525
|
end
|
460
526
|
|
527
|
+
# run monitors
|
528
|
+
def run_monitors
|
529
|
+
info 'Monitoring files for changes (press Ctrl-C to stop)'
|
530
|
+
file_mtimes = {}
|
531
|
+
$monitors.keys.each { |f| file_mtimes[f] = File.mtime(f) }
|
532
|
+
loop do
|
533
|
+
$monitors.keys.each do |f|
|
534
|
+
file_mtime = File.mtime(f)
|
535
|
+
next if file_mtime == file_mtimes[f]
|
536
|
+
|
537
|
+
info 'File modified', f
|
538
|
+
$monitors[f].call
|
539
|
+
file_mtimes[f] = file_mtime
|
540
|
+
end
|
541
|
+
begin
|
542
|
+
sleep(0.5)
|
543
|
+
rescue Interrupt
|
544
|
+
exit 0
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: central
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitry Geurkov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-31 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: central dotfile management system
|
14
14
|
email: d.geurkov@gmail.com
|
@@ -31,15 +31,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
34
|
+
version: '2.3'
|
35
35
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '0'
|
40
40
|
requirements: []
|
41
|
-
|
42
|
-
rubygems_version: 2.5.2
|
41
|
+
rubygems_version: 3.1.4
|
43
42
|
signing_key:
|
44
43
|
specification_version: 4
|
45
44
|
summary: central dotfile management
|