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.
Files changed (4) hide show
  1. checksums.yaml +5 -5
  2. data/bin/central +32 -9
  3. data/lib/central.rb +310 -223
  4. metadata +4 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c28cbd68d80c217678f5b6d81dc82cd2d4de4eed
4
- data.tar.gz: 152e89721d347f991b177e6e81165619c3bfff70
2
+ SHA256:
3
+ metadata.gz: ecf6eb46fbc67795241445bc38629214a0d9eb2b63237f3ade4ccc5009b2eb4f
4
+ data.tar.gz: d105df04c79771d9ae767c471185ec80147fbf7db6d552affa7d01185e4fe965
5
5
  SHA512:
6
- metadata.gz: b873dba32172c267f1419657c5d500736880dbcfe9f04b431984e489406e91ac55a809c9358bb71a361f1f5bf120e238cdf89dc5ee91d9fda7c8bd1c76070dcc
7
- data.tar.gz: 70e5070de43a592bb09234e82e130025516139fbea193caf0cc6d1892ca9a90f98681b83be4118670a3ee01f0fdafcb6d738bf8f61ec2ae6e1a6d6e3a9796d4d
6
+ metadata.gz: b3ec6fa7e823098fc75b55c66399b25da990bc3bdb58d9f54b1c9cb7f26b00eb123699c6772800cfc54aa68826b6152b61284c76555dd248183fbb2d9b497010
7
+ data.tar.gz: 5501d4249660a4d1d288aa6421439045f1ba66da1c316c62517a09d0ed1e7fb14f99f40fbef82966ae7d12ce411c61d0411f64872e4ace77bfdd2c52087ddd11
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- # encoding: utf-8
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
- if ARGV.length > 0
12
- if ARGV[0] == '-v' || ARGV[0] == '--version' || ARGV[0] == '-version'
13
- puts "central v0.2.2"
14
- exit 0
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
- if ARGV[0] == '-h' || ARGV[0] == '--help' || ARGV[0] == '-help'
17
- puts "central [path/to/configuration.rb]"
18
- exit 0
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)
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
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
- return 'linux'
57
+ 'linux'
21
58
  elsif RUBY_PLATFORM.include?('darwin')
22
- return 'osx'
59
+ 'osx'
23
60
  elsif RUBY_PLATFORM.include?('freebsd')
24
- return 'freebsd'
61
+ 'freebsd'
25
62
  elsif RUBY_PLATFORM.include?('solaris')
26
- return 'solaris'
63
+ 'solaris'
27
64
  end
28
65
  end
29
66
 
30
67
  def linux?
31
- return os == 'linux'
68
+ os == 'linux'
32
69
  end
33
70
 
34
71
  def osx?
35
- return os == 'osx'
72
+ os == 'osx'
36
73
  end
37
74
 
38
75
  def freebsd?
39
- return os == 'freebsd'
76
+ os == 'freebsd'
40
77
  end
41
78
 
42
79
  def solaris?
43
- return os == 'solaris'
44
- end
45
-
46
- # run shell command and get output, optionaly can print command running if verbose and if not silent will also print to stdout and stderr
47
- $shell_return_value = nil
48
- def shell(command,options={:verbose => false, :silent => true})
49
- silent = options[:silent]
50
- puts "Executing: #{command}" if options[:verbose]
51
- stdout = ''
52
- stdout_line = ''
53
- stderr = ''
54
- stderr_line = ''
55
- Open3.popen3(command) do |i,o,e,t|
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 or stderr_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.1) unless stderr_open
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
- if stderr_open
77
- begin
78
- ch = e.read_nonblock(1)
79
- stderr.insert(-1,ch)
80
- unless silent
81
- stderr_line.insert(-1,ch)
82
- if ch == "\n"
83
- STDERR.puts stderr_line
84
- stderr_line = ''
85
- end
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
- $shell_return_value = t.value
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,options)
105
- return shell('sudo '+command, options)
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
- begin
111
- output = shell(check+' 2>&1').downcase
112
- if output == '' or output.include?('command not found')
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
- path = File.absolute_path(File.expand_path(path))
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.exists?(path)
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").strip
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
- unless dir_exists?(path)
176
- out = shell("mkdir -p \"#{path}\" 2>&1")
177
- unless $shell_return_value.success?
178
- STDERR.puts out
179
- STDERR.puts "Couldn't create directory: #{path}"
180
- exit 1
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=false)
240
+ def rm(path, recursive: false)
188
241
  path = abs(path)
189
- if recursive
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 $shell_return_value.success?
198
- STDERR.puts out
199
- STDERR.puts "Couldn't remove path: #{path}"
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
- puts "Removed directory: #{path}"
251
+ info 'Removed directory', path
204
252
  elsif is_symlink
205
- puts "Removed symlink: #{path}"
253
+ info 'Removed symlink', path
206
254
  else
207
- puts "Removed file: #{path}"
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
- unless file_exists?(path)
220
- out = shell("touch \"#{path}\" 2>&1")
221
- unless $shell_return_value.success?
222
- STDERR.puts out
223
- STDERR.puts "Couldn't touch file: #{path}"
224
- exit 1
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=false)
278
+ def chmod(path, permissions, recursive: false)
232
279
  path = abs(path)
233
- if recursive
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
- STDERR.puts "File #{from} exists in place of symlink..."
252
- exit 1
294
+ fail "File #{from} exists in place of symlink..."
253
295
  elsif dir_exists?(from)
254
- STDERR.puts "Directory #{from} exists in place of symlink..."
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 $shell_return_value.success?
259
- STDERR.puts out
260
- STDERR.puts "Couldn't create symlink: #{from} → #{to}"
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
- puts "Created symlink: #{from} → #{to}"
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=nil,silent=false)
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',{:silent => silent})
276
- if out.size > 0
277
- puts out if silent
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",{:silent => silent})
321
+ _, out, = shell("git pull origin #{branch} 2>&1", silent: silent)
284
322
  else
285
- out = shell('git pull 2>&1',{:silent => silent})
323
+ _, out, = shell('git pull 2>&1', silent: silent)
286
324
  end
287
- unless out.downcase.include? "already up-to-date"
325
+ unless out.downcase.include? 'already up'
288
326
  puts out if silent
289
- puts "Git repository pulled: #{url} → #{path}"
327
+ info 'Git repository pulled', "#{url} → #{path}"
290
328
  end
291
329
  chdir cwd
292
330
  else
293
- if branch
294
- branch = "-b #{branch} "
295
- else
296
- branch = ''
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
- puts "Git repository cloned: #{url} → #{path}"
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=false)
341
+ def curl(url, path, content_length_check: false, verbose: false)
306
342
  path = abs(path)
307
- output = shell("curl -s -S \"#{url}\"",{:verbose => verbose, :silent => true})
308
- unless $shell_return_value.success?
309
- STDERR.puts output
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
- if File.exists?(path) && File.read(path) == output
314
- return
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
- puts "Downloaded #{url} → #{path}"
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
- return File.read(file)
325
- else
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
- if out == ""
344
- shell("echo '#{source_line}' >> \"#{file}\"")
345
- puts "Added source #{source} line to #{file}"
346
- end
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,options={})
400
+ def ls(path, dotfiles: false, grep: '', dir: true, file: true)
351
401
  path = abs(path)
352
- if options[:dotfiles]
353
- dotfiles = '-a '
354
- else
355
- dotfiles = ''
356
- end
402
+ dotfiles = dotfiles ? '-a ' : ''
357
403
  command = "ls -1 #{dotfiles}\"#{path}\" 2>&1"
358
- if options.key?(:grep) && options[:grep].length > 0
359
- command += " | grep #{options[:grep]}"
360
- end
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
- STDERR.puts "Couldn't ls directory #{path}..."
364
- exit 1
408
+ fail "Couldn't ls directory", path
365
409
  end
410
+
366
411
  ls = output.split("\n")
367
- dir = true
368
- file = true
369
- if options.key?(:dir)
370
- dir = options[:dir]
371
- end
372
- if options.key?(:file)
373
- file = options[:file]
374
- end
375
- unless dir
376
- ls = ls.keep_if {|f| !File.directory?("#{path}/#{f}") }
377
- end
378
- unless file
379
- ls = ls.keep_if {|f| !File.file?("#{path}/#{f}") }
380
- end
381
- return ls
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
- (Dir.entries(from).select { |f| f != "." and f != ".." }).each do |f|
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
- unless file_exists?(from)
395
- STDERR.puts "Couldn't access file #{from}..."
396
- exit 1
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
- if !file_exists?(to) or !FileUtils.compare_file(from,to)
399
- FileUtils.copy_file(from,to)
400
- puts "Copied file: #{from} #{to}"
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
- if output_file == nil
409
- if file.end_with?('.erb')
410
- output_file = file[0...-4]
411
- else
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
- unless file_exists?(file)
433
- STDERR.puts "No configuration file: #{file} found"
434
- exit 1
435
- end
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 central(configurations)
452
- if configurations.instance_of?(Array) && configurations.length > 0
453
- configurations.each {|configuration| run configuration }
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.3
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: 2018-04-14 00:00:00.000000000 Z
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: '0'
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
- rubyforge_project:
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