central 0.2.8 → 0.3.2
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 +4 -4
- data/bin/central +51 -20
- data/lib/central.rb +123 -25
- metadata +3 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 81f6417d161a53c2d5b0b2785a1e45cb4af4cc5f8f7b18fd8d24fbb18d1d0410
|
|
4
|
+
data.tar.gz: 706d8b11e4d38effa588a5fe9c5b7baada931a3de3b5973cea7f1ac6c05e8dfd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e36f5d6f5c9730c95240fc674020af1d557f57054cb9d4ff1eb15ccaae14776c54060ea662759766bc899ef6f4a85a901e0eec34141742d83707e61a8c65f3c
|
|
7
|
+
data.tar.gz: cbfb6d079499dfcdadbb767f5f676292e43e933083f6d13d7008ceb07c31da0c8cc8995931219fac16a1d19a71ea9a39611c4e10f0ce82dc7b6f6db8a6358c88
|
data/bin/central
CHANGED
|
@@ -7,29 +7,60 @@
|
|
|
7
7
|
# -------------------------------------------------------------------------
|
|
8
8
|
|
|
9
9
|
require 'central'
|
|
10
|
+
require 'optparse'
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
puts 'central v0.2.8 - created by Dmitry Geurkov (d.geurkov@gmail.com)'
|
|
18
|
-
puts ' and licensed under LGPLv3'
|
|
19
|
-
puts ''
|
|
20
|
-
puts 'Usage: central [monitor] [path/to/configuration.rb]'
|
|
21
|
-
puts ''
|
|
22
|
-
puts 'Description: [monitor] - will monitor erb files for changes'
|
|
23
|
-
puts ' reprocess them automatically, disabled by default'
|
|
24
|
-
puts ' if no [path/to/configuration.rb] is specified'
|
|
25
|
-
puts ' it will use configuration.rb in current working directory'
|
|
26
|
-
exit 0
|
|
12
|
+
VERSION = 'v0.3.2'
|
|
13
|
+
|
|
14
|
+
# parse extra options
|
|
15
|
+
ARGV.each do |option|
|
|
16
|
+
if not option.empty? and option.index('-') == 0 and not ['-h', '--help', '-i', '--irb', '-n', '--no-color', '-v', '--version', '-m', '--monitor'].include?(option)
|
|
17
|
+
$options.append(option)
|
|
27
18
|
end
|
|
28
19
|
end
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
ARGV.delete_if { |option| $options.include?(option) }
|
|
21
|
+
|
|
22
|
+
# parse central options
|
|
23
|
+
options = {:color => true}
|
|
24
|
+
OptionParser.new do |opts|
|
|
25
|
+
opts.banner = %{central #{VERSION} - created by Dmitry Geurkov (d.geurkov@gmail.com) and licensed under LGPLv3
|
|
26
|
+
|
|
27
|
+
Usage: central [options] [directory|configuration.rb, ...]
|
|
28
|
+
|
|
29
|
+
Description: if directory is specified it will use configuration.rb file inside that directory
|
|
30
|
+
if no [directory|configuration.rb] is specified
|
|
31
|
+
it will use configuration.rb in current working directory
|
|
32
|
+
|
|
33
|
+
Options:}
|
|
34
|
+
opts.on("-i", "--irb", "Start interactive IRB REPL") do |_|
|
|
35
|
+
options[:irb] = true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
opts.on("-m", "--monitor", "Monitor erb files for changes and reprocess them automatically") do |_|
|
|
39
|
+
options[:monitor] = true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
opts.on("-n", "--no-color", "Do not colorize output") do |_|
|
|
43
|
+
options[:color] = false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
opts.on("-v", "--version", "Print version") do |_|
|
|
47
|
+
puts "central #{VERSION}"
|
|
48
|
+
exit
|
|
49
|
+
end
|
|
50
|
+
end.parse!
|
|
51
|
+
|
|
52
|
+
if options[:irb]
|
|
53
|
+
require 'irb'
|
|
54
|
+
$colored = true
|
|
55
|
+
$colored = options[:color] if options[:color]
|
|
56
|
+
info "Welcome to central #{VERSION} IRB repl, interactive playground for your dotcentral configuration"
|
|
57
|
+
info "Please ignore warning below as central has source function, and IRB tries to create alias for irb_source"
|
|
58
|
+
info "In order to use IRB's source functionality just call it without alias as irb_source"
|
|
59
|
+
ARGV.clear
|
|
60
|
+
IRB.start(__FILE__)
|
|
61
|
+
elsif options[:monitor]
|
|
62
|
+
run_central(ARGV,options[:color])
|
|
32
63
|
run_monitors
|
|
33
64
|
else
|
|
34
|
-
run_central(ARGV)
|
|
65
|
+
run_central(ARGV,options[:color])
|
|
35
66
|
end
|
data/lib/central.rb
CHANGED
|
@@ -9,8 +9,25 @@ require 'erb'
|
|
|
9
9
|
require 'socket'
|
|
10
10
|
require 'open3'
|
|
11
11
|
require 'fileutils'
|
|
12
|
+
require 'digest'
|
|
13
|
+
|
|
14
|
+
# options
|
|
15
|
+
$options = []
|
|
16
|
+
|
|
17
|
+
# get option, returns Array if multiple or nil if none
|
|
18
|
+
def option(opt)
|
|
19
|
+
options = $options.filter { |option| option.index(opt) == 0 }
|
|
20
|
+
if options.size == 0
|
|
21
|
+
return nil
|
|
22
|
+
elsif options.size == 1
|
|
23
|
+
return options[0]
|
|
24
|
+
else
|
|
25
|
+
return options
|
|
26
|
+
end
|
|
27
|
+
end
|
|
12
28
|
|
|
13
29
|
# cli colors
|
|
30
|
+
$colored = true
|
|
14
31
|
COLOR_RED = 31
|
|
15
32
|
COLOR_GREEN = 32
|
|
16
33
|
|
|
@@ -19,7 +36,11 @@ $monitors = {}
|
|
|
19
36
|
|
|
20
37
|
# putsc, puts with color
|
|
21
38
|
def color(message, color)
|
|
22
|
-
|
|
39
|
+
if $colored
|
|
40
|
+
"\e[#{color}m#{message}\e[0m"
|
|
41
|
+
else
|
|
42
|
+
message
|
|
43
|
+
end
|
|
23
44
|
end
|
|
24
45
|
|
|
25
46
|
# info
|
|
@@ -66,6 +87,10 @@ def osx?
|
|
|
66
87
|
os == 'osx'
|
|
67
88
|
end
|
|
68
89
|
|
|
90
|
+
def macos?
|
|
91
|
+
os == 'osx'
|
|
92
|
+
end
|
|
93
|
+
|
|
69
94
|
def freebsd?
|
|
70
95
|
os == 'freebsd'
|
|
71
96
|
end
|
|
@@ -80,24 +105,16 @@ def shell(command, verbose: false, silent: true)
|
|
|
80
105
|
info 'Executing', command if verbose
|
|
81
106
|
exit_code = nil
|
|
82
107
|
stdout = String.new
|
|
83
|
-
stdout_line = String.new
|
|
84
108
|
stderr = String.new
|
|
85
|
-
stderr_line = String.new
|
|
86
109
|
Open3.popen3(command) do |_, o, e, t|
|
|
87
110
|
stdout_open = true
|
|
88
111
|
stderr_open = true
|
|
89
112
|
while stdout_open || stderr_open
|
|
90
113
|
if stdout_open
|
|
91
114
|
begin
|
|
92
|
-
|
|
93
|
-
stdout
|
|
94
|
-
unless silent
|
|
95
|
-
stdout_line.insert(-1, ch)
|
|
96
|
-
if ch == "\n"
|
|
97
|
-
STDOUT.puts stdout_line
|
|
98
|
-
stdout_line = ''
|
|
99
|
-
end
|
|
100
|
-
end
|
|
115
|
+
buffer = o.read_nonblock(4096)
|
|
116
|
+
stdout << buffer
|
|
117
|
+
STDOUT.write(buffer) unless silent
|
|
101
118
|
rescue IO::WaitReadable
|
|
102
119
|
IO.select([o], nil, nil, 0.01) unless stderr_open
|
|
103
120
|
rescue EOFError
|
|
@@ -107,15 +124,9 @@ def shell(command, verbose: false, silent: true)
|
|
|
107
124
|
next unless stderr_open
|
|
108
125
|
|
|
109
126
|
begin
|
|
110
|
-
|
|
111
|
-
stderr
|
|
112
|
-
unless silent
|
|
113
|
-
stderr_line.insert(-1, ch)
|
|
114
|
-
if ch == "\n"
|
|
115
|
-
STDERR.puts stderr_line
|
|
116
|
-
stderr_line = ''
|
|
117
|
-
end
|
|
118
|
-
end
|
|
127
|
+
buffer = e.read_nonblock(4096)
|
|
128
|
+
stderr << buffer
|
|
129
|
+
STDERR.write(buffer) unless silent
|
|
119
130
|
rescue IO::WaitReadable
|
|
120
131
|
IO.select([e], nil, nil, 0.01) unless stdout_open
|
|
121
132
|
rescue EOFError
|
|
@@ -170,13 +181,45 @@ def file_exists?(path)
|
|
|
170
181
|
File.file?(path) && File.readable?(path)
|
|
171
182
|
end
|
|
172
183
|
|
|
184
|
+
# get file size
|
|
185
|
+
def file_size(path)
|
|
186
|
+
File.new(abs(path)).size
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# get file creation time
|
|
190
|
+
def file_ctime(path)
|
|
191
|
+
File.new(abs(path)).ctime
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# get file modification time
|
|
195
|
+
def file_mtime(path)
|
|
196
|
+
File.new(abs(path)).mtime
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# get directory entries
|
|
200
|
+
def dir_entries(path)
|
|
201
|
+
Dir.entries(abs(path)).select { |f| f != '.' && f != '..' }
|
|
202
|
+
end
|
|
203
|
+
|
|
173
204
|
# check if directory exists
|
|
174
205
|
def dir_exists?(path)
|
|
175
206
|
path = abs(path)
|
|
176
207
|
Dir.exist?(path)
|
|
177
208
|
end
|
|
178
209
|
|
|
179
|
-
# get
|
|
210
|
+
# get file name of a path, optionally strip suffix if needed
|
|
211
|
+
def file_name(path, strip_suffix: '')
|
|
212
|
+
File.basename(abs(path), strip_suffix)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# get file suffix of a path
|
|
216
|
+
def file_suffix(path)
|
|
217
|
+
path = file_name(path)
|
|
218
|
+
suffix_index = path =~ /\.[^.]+$/
|
|
219
|
+
return path[suffix_index, path.size - suffix_index] if suffix_index
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# get directory name of a path
|
|
180
223
|
def file_dir(path)
|
|
181
224
|
File.dirname(abs(path))
|
|
182
225
|
end
|
|
@@ -192,6 +235,11 @@ def symlink_path(symlink)
|
|
|
192
235
|
out.strip
|
|
193
236
|
end
|
|
194
237
|
|
|
238
|
+
# calculate SHA2 digest for a file
|
|
239
|
+
def sha2(file)
|
|
240
|
+
Digest::SHA256.hexdigest read(file)
|
|
241
|
+
end
|
|
242
|
+
|
|
195
243
|
# make directory including intermediate directories
|
|
196
244
|
def mkdir(path)
|
|
197
245
|
path = abs(path)
|
|
@@ -307,8 +355,12 @@ def git(url, path, branch: nil, silent: true, depth: nil)
|
|
|
307
355
|
end
|
|
308
356
|
|
|
309
357
|
# download url into a path using curl
|
|
310
|
-
def curl(url, path, verbose: false)
|
|
358
|
+
def curl(url, path, content_length_check: false, verbose: false)
|
|
311
359
|
path = abs(path)
|
|
360
|
+
if content_length_check and file_exists?(path)
|
|
361
|
+
content_length = curl_headers(url, verbose: verbose)['content-length'].to_i
|
|
362
|
+
return if file_size(path) == content_length
|
|
363
|
+
end
|
|
312
364
|
info 'Downloading', "#{url} → #{path}"
|
|
313
365
|
exit_code, output, = shell("curl -s -S \"#{url}\"",
|
|
314
366
|
verbose: verbose, silent: true)
|
|
@@ -320,6 +372,21 @@ def curl(url, path, verbose: false)
|
|
|
320
372
|
info 'Downloaded', "#{url} → #{path}"
|
|
321
373
|
end
|
|
322
374
|
|
|
375
|
+
# get url response headers as Hash using curl
|
|
376
|
+
def curl_headers(url, method: 'HEAD', verbose: false)
|
|
377
|
+
exit_code, output, = shell("curl -I -X #{method} -s -S \"#{url}\"",
|
|
378
|
+
verbose: verbose, silent: true)
|
|
379
|
+
unless exit_code.success?
|
|
380
|
+
error output
|
|
381
|
+
fail "Couldn't get headers from", url
|
|
382
|
+
end
|
|
383
|
+
headers = {}
|
|
384
|
+
output.scan(/^(?!HTTP)([^:]+):(.*)$/).each do |m|
|
|
385
|
+
headers[m[0].strip.downcase] = m[1].sub("\r","").strip
|
|
386
|
+
end
|
|
387
|
+
headers
|
|
388
|
+
end
|
|
389
|
+
|
|
323
390
|
# read content of a file
|
|
324
391
|
def read(file)
|
|
325
392
|
file = abs(file)
|
|
@@ -364,8 +431,17 @@ def ls(path, dotfiles: false, grep: '', dir: true, file: true)
|
|
|
364
431
|
ls
|
|
365
432
|
end
|
|
366
433
|
|
|
434
|
+
# compare_file
|
|
435
|
+
def compare_file(from, to)
|
|
436
|
+
from = abs(from)
|
|
437
|
+
to = abs(to)
|
|
438
|
+
FileUtils.compare_file(from, to)
|
|
439
|
+
end
|
|
440
|
+
|
|
367
441
|
# copy_file
|
|
368
442
|
def copy_file(from, to)
|
|
443
|
+
from = abs(from)
|
|
444
|
+
to = abs(to)
|
|
369
445
|
fail "Couldn't access file", from unless file_exists?(from)
|
|
370
446
|
|
|
371
447
|
return if file_exists?(to) && FileUtils.compare_file(from, to)
|
|
@@ -379,7 +455,27 @@ def copy(from, to)
|
|
|
379
455
|
from = abs(from)
|
|
380
456
|
to = abs(to)
|
|
381
457
|
if dir_exists?(from)
|
|
382
|
-
(
|
|
458
|
+
dir_entries(from).each do |f|
|
|
459
|
+
FileUtils.mkdir_p(to)
|
|
460
|
+
copy("#{from}/#{f}", "#{to}/#{f}")
|
|
461
|
+
end
|
|
462
|
+
else
|
|
463
|
+
copy_file(from, to)
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# mirror
|
|
468
|
+
def mirror(from, to)
|
|
469
|
+
from = abs(from)
|
|
470
|
+
to = abs(to)
|
|
471
|
+
if dir_exists?(from)
|
|
472
|
+
from_entries = dir_entries(from)
|
|
473
|
+
if dir_exists?(to)
|
|
474
|
+
dir_entries(to).each do |f|
|
|
475
|
+
rm("#{to}/#{f}", recursive: true) unless from_entries.include?(f)
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
from_entries.each do |f|
|
|
383
479
|
FileUtils.mkdir_p(to)
|
|
384
480
|
copy("#{from}/#{f}", "#{to}/#{f}")
|
|
385
481
|
end
|
|
@@ -418,6 +514,7 @@ end
|
|
|
418
514
|
def run(file)
|
|
419
515
|
cwd = pwd
|
|
420
516
|
file = abs(file)
|
|
517
|
+
file = File.join(file,'configuration.rb') if not file_exists?(file) and dir_exists?(file)
|
|
421
518
|
fail 'No configuration file found', file unless file_exists?(file)
|
|
422
519
|
|
|
423
520
|
info 'Running configuration', file
|
|
@@ -433,7 +530,8 @@ def run_if_exists(file)
|
|
|
433
530
|
end
|
|
434
531
|
|
|
435
532
|
# run central configuration
|
|
436
|
-
def run_central(configurations)
|
|
533
|
+
def run_central(configurations, colored = true)
|
|
534
|
+
$colored = colored
|
|
437
535
|
if configurations.instance_of?(Array) && !configurations.empty?
|
|
438
536
|
configurations.each { |configuration| run configuration }
|
|
439
537
|
elsif configurations.instance_of?(String)
|
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.3.2
|
|
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: 2022-02-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: central dotfile management system
|
|
14
14
|
email: d.geurkov@gmail.com
|
|
@@ -38,8 +38,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0'
|
|
40
40
|
requirements: []
|
|
41
|
-
|
|
42
|
-
rubygems_version: 2.7.6
|
|
41
|
+
rubygems_version: 3.2.22
|
|
43
42
|
signing_key:
|
|
44
43
|
specification_version: 4
|
|
45
44
|
summary: central dotfile management
|