central 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +5 -5
  2. data/bin/central +45 -9
  3. data/lib/central.rb +327 -218
  4. metadata +4 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 59a223e19b875e1e3d7dc50f70f60697816d47e3
4
- data.tar.gz: 8037442fffcaaccbe55bf1209dae265890432836
2
+ SHA256:
3
+ metadata.gz: 9ac8e8ea400a706c511823313cf3832bc3fde937077e8388e0b79e03ccb6f2e3
4
+ data.tar.gz: 8174b7da6cadf72d7c3aa3c5a1ed11fe87ed11413029e7de71712c307746e0ac
5
5
  SHA512:
6
- metadata.gz: 1ef58f6d6da2bf0f3148677cc86d5cf5779c4d2fa766102c1dfd5cd4ac5aad643d7e55ed78a575b83a6e2d38735bfefdc0bb8a6eebe5935dde1ec4326bf03fa6
7
- data.tar.gz: 025ab8f80c6e4cc29038ff1cacb276206527da789865732539d64887b23b590007c9debe723ab7224ed02de28ac705be2a331cdeaf04ae568ed7a1ade5392858
6
+ metadata.gz: 270220f138029359a37a99d8b79d22a9484d05fd8a0a01efea438031f17b10a3497c98a845a98ba9ef2f36838557f81b2a00f3e318e8ee2eb93cbbf4a5c2223d
7
+ data.tar.gz: 6d6b11e171213305af47dcc4dd351c8f1abe4ab841f37ca870d549ed6251bff677766dc49ec7263baa33c34282a416061b8c5d7cf85b0166fa839d209f38f916
@@ -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,51 @@
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.4"
14
- exit 0
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
- if ARGV[0] == '-h' || ARGV[0] == '--help' || ARGV[0] == '-help'
17
- puts "central [path/to/configuration.rb]"
18
- exit 0
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)
@@ -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,114 +54,110 @@ 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'
73
+ end
74
+
75
+ def macos?
76
+ os == 'osx'
36
77
  end
37
78
 
38
79
  def freebsd?
39
- return os == 'freebsd'
80
+ os == 'freebsd'
40
81
  end
41
82
 
42
83
  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,verbose: false, silent: true)
49
- puts "Executing: #{command}" if verbose
50
- stdout = ''
51
- stdout_line = ''
52
- stderr = ''
53
- stderr_line = ''
54
- Open3.popen3(command) do |i,o,e,t|
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 or stderr_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.1) unless stderr_open
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
- if stderr_open
76
- begin
77
- ch = e.read_nonblock(1)
78
- stderr.insert(-1,ch)
79
- unless silent
80
- stderr_line.insert(-1,ch)
81
- if ch == "\n"
82
- STDERR.puts stderr_line
83
- stderr_line = ''
84
- end
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
- $shell_return_value = t.value
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
- return shell('sudo '+command, verbose: verbose, silent: silent)
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
- begin
110
- output = shell(check+' 2>&1').downcase
111
- if output == '' or output.include?('command not found')
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
- path = File.absolute_path(File.expand_path(path))
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 directory name of a file
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").strip
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
- unless dir_exists?(path)
175
- out = shell("mkdir -p \"#{path}\" 2>&1")
176
- unless $shell_return_value.success?
177
- STDERR.puts out
178
- STDERR.puts "Couldn't create directory: #{path}"
179
- exit 1
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
- if recursive
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 $shell_return_value.success?
197
- STDERR.puts out
198
- STDERR.puts "Couldn't remove path: #{path}"
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
- puts "Removed directory: #{path}"
267
+ info 'Removed directory', path
203
268
  elsif is_symlink
204
- puts "Removed symlink: #{path}"
269
+ info 'Removed symlink', path
205
270
  else
206
- puts "Removed file: #{path}"
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
- unless file_exists?(path)
219
- out = shell("touch \"#{path}\" 2>&1")
220
- unless $shell_return_value.success?
221
- STDERR.puts out
222
- STDERR.puts "Couldn't touch file: #{path}"
223
- exit 1
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
- if recursive
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
- STDERR.puts "File #{from} exists in place of symlink..."
251
- exit 1
310
+ fail "File #{from} exists in place of symlink..."
252
311
  elsif dir_exists?(from)
253
- STDERR.puts "Directory #{from} exists in place of symlink..."
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 $shell_return_value.success?
258
- STDERR.puts out
259
- STDERR.puts "Couldn't create symlink: #{from} → #{to}"
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
- puts "Created symlink: #{from} → #{to}"
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: false,depth: nil)
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',{:silent => silent})
275
- if out.size > 0
276
- puts out if silent
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",{:silent => silent})
337
+ _, out, = shell("git pull origin #{branch} 2>&1", silent: silent)
283
338
  else
284
- out = shell('git pull 2>&1',{:silent => silent})
339
+ _, out, = shell('git pull 2>&1', silent: silent)
285
340
  end
286
- unless out.downcase.include? "already up-to-date"
341
+ unless out.downcase.include? 'already up'
287
342
  puts out if silent
288
- puts "Git repository pulled: #{url} → #{path}"
343
+ info 'Git repository pulled', "#{url} → #{path}"
289
344
  end
290
345
  chdir cwd
291
346
  else
292
- if branch
293
- branch = "-b #{branch} "
294
- else
295
- branch = ''
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
- puts "Git repository cloned: #{url} → #{path}"
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
- puts "Downloading #{url} → #{path}"
312
- output = shell("curl -s -S \"#{url}\"",verbose: verbose, silent: true)
313
- unless $shell_return_value.success?
314
- STDERR.puts output
315
- STDERR.puts "Couldn't download file from #{url}..."
316
- exit 1
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
- puts "Downloaded #{url} → #{path}"
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
- return File.read(file)
327
- else
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
- if out == ""
346
- shell("echo '#{source_line}' >> \"#{file}\"")
347
- puts "Added source #{source} line to #{file}"
348
- end
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
- if dotfiles
355
- dotfiles = '-a '
356
- else
357
- dotfiles = ''
358
- end
418
+ dotfiles = dotfiles ? '-a ' : ''
359
419
  command = "ls -1 #{dotfiles}\"#{path}\" 2>&1"
360
- if grep.length > 0
361
- command += " | grep #{grep}"
362
- end
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
- STDERR.puts "Couldn't ls directory #{path}..."
366
- exit 1
424
+ fail "Couldn't ls directory", path
367
425
  end
426
+
368
427
  ls = output.split("\n")
369
- unless dir
370
- ls = ls.keep_if {|f| !File.directory?("#{path}/#{f}") }
371
- end
372
- unless file
373
- ls = ls.keep_if {|f| !File.file?("#{path}/#{f}") }
374
- end
375
- return ls
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
- (Dir.entries(from).select { |f| f != "." and f != ".." }).each do |f|
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
- unless file_exists?(from)
389
- STDERR.puts "Couldn't access file #{from}..."
390
- exit 1
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
- if !file_exists?(to) or !FileUtils.compare_file(from,to)
393
- FileUtils.copy_file(from,to)
394
- puts "Copied file: #{from} #{to}"
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
- if output_file == nil
403
- if file.end_with?('.erb')
404
- output_file = file[0...-4]
405
- else
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
- unless file_exists?(file)
427
- STDERR.puts "No configuration file: #{file} found"
428
- exit 1
429
- end
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 central(configurations)
446
- if configurations.instance_of?(Array) && configurations.length > 0
447
- configurations.each {|configuration| run configuration }
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.2.4
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: 2019-05-19 00:00:00.000000000 Z
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: '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.1
41
+ rubygems_version: 3.2.3
43
42
  signing_key:
44
43
  specification_version: 4
45
44
  summary: central dotfile management