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