scout-gear 2.0.0 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +59 -2
  3. data/VERSION +1 -1
  4. data/bin/scout +231 -24
  5. data/lib/scout/cmd.rb +344 -0
  6. data/lib/scout/concurrent_stream.rb +259 -0
  7. data/lib/scout/exceptions.rb +15 -8
  8. data/lib/scout/indiferent_hash/options.rb +8 -26
  9. data/lib/scout/log/color.rb +2 -2
  10. data/lib/scout/log/fingerprint.rb +11 -1
  11. data/lib/scout/log/progress/report.rb +0 -1
  12. data/lib/scout/log/progress/util.rb +1 -1
  13. data/lib/scout/log/progress.rb +4 -4
  14. data/lib/scout/log.rb +10 -2
  15. data/lib/scout/meta_extension.rb +15 -1
  16. data/lib/scout/misc/digest.rb +56 -0
  17. data/lib/scout/misc/filesystem.rb +26 -0
  18. data/lib/scout/misc/format.rb +1 -2
  19. data/lib/scout/misc/insist.rb +56 -0
  20. data/lib/scout/misc.rb +4 -11
  21. data/lib/scout/open/lock.rb +61 -0
  22. data/lib/scout/open/remote.rb +120 -0
  23. data/lib/scout/open/stream.rb +372 -0
  24. data/lib/scout/open/util.rb +225 -0
  25. data/lib/scout/open.rb +169 -0
  26. data/lib/scout/path/find.rb +67 -21
  27. data/lib/scout/path/tmpfile.rb +8 -0
  28. data/lib/scout/path/util.rb +14 -1
  29. data/lib/scout/path.rb +6 -30
  30. data/lib/scout/persist/open.rb +17 -0
  31. data/lib/scout/persist/path.rb +15 -0
  32. data/lib/scout/persist/serialize.rb +140 -0
  33. data/lib/scout/persist.rb +54 -0
  34. data/lib/scout/resource/path.rb +15 -0
  35. data/lib/scout/resource/produce/rake.rb +69 -0
  36. data/lib/scout/resource/produce.rb +246 -0
  37. data/lib/scout/resource/scout.rb +3 -0
  38. data/lib/scout/resource.rb +37 -0
  39. data/lib/scout/simple_opt/accessor.rb +1 -1
  40. data/lib/scout/simple_opt/doc.rb +4 -22
  41. data/lib/scout/simple_opt/parse.rb +4 -3
  42. data/lib/scout/tmpfile.rb +39 -1
  43. data/lib/scout/workflow/definition.rb +72 -0
  44. data/lib/scout/workflow/documentation.rb +77 -0
  45. data/lib/scout/workflow/step/info.rb +77 -0
  46. data/lib/scout/workflow/step.rb +96 -0
  47. data/lib/scout/workflow/task/inputs.rb +112 -0
  48. data/lib/scout/workflow/task.rb +141 -0
  49. data/lib/scout/workflow/usage.rb +294 -0
  50. data/lib/scout/workflow/util.rb +11 -0
  51. data/lib/scout/workflow.rb +39 -0
  52. data/lib/scout-gear.rb +4 -0
  53. data/lib/scout.rb +1 -0
  54. data/lib/workflow-scout.rb +2 -0
  55. data/scout-gear.gemspec +66 -5
  56. data/scout_commands/alias +48 -0
  57. data/scout_commands/find +83 -0
  58. data/scout_commands/glob +0 -0
  59. data/scout_commands/rbbt +23 -0
  60. data/scout_commands/workflow/task +707 -0
  61. data/test/scout/indiferent_hash/test_options.rb +11 -1
  62. data/test/scout/misc/test_digest.rb +30 -0
  63. data/test/scout/misc/test_filesystem.rb +30 -0
  64. data/test/scout/misc/test_insist.rb +13 -0
  65. data/test/scout/open/test_lock.rb +52 -0
  66. data/test/scout/open/test_remote.rb +25 -0
  67. data/test/scout/open/test_stream.rb +515 -0
  68. data/test/scout/open/test_util.rb +73 -0
  69. data/test/scout/path/test_find.rb +28 -0
  70. data/test/scout/persist/test_open.rb +37 -0
  71. data/test/scout/persist/test_path.rb +37 -0
  72. data/test/scout/persist/test_serialize.rb +114 -0
  73. data/test/scout/resource/test_path.rb +40 -0
  74. data/test/scout/resource/test_produce.rb +62 -0
  75. data/test/scout/test_cmd.rb +85 -0
  76. data/test/scout/test_concurrent_stream.rb +29 -0
  77. data/test/scout/test_misc.rb +0 -7
  78. data/test/scout/test_open.rb +146 -0
  79. data/test/scout/test_path.rb +3 -1
  80. data/test/scout/test_persist.rb +83 -0
  81. data/test/scout/test_resource.rb +26 -0
  82. data/test/scout/test_workflow.rb +87 -0
  83. data/test/scout/workflow/step/test_info.rb +28 -0
  84. data/test/scout/workflow/task/test_inputs.rb +182 -0
  85. data/test/scout/workflow/test_step.rb +36 -0
  86. data/test/scout/workflow/test_task.rb +178 -0
  87. data/test/scout/workflow/test_usage.rb +26 -0
  88. data/test/scout/workflow/test_util.rb +17 -0
  89. data/test/test_helper.rb +17 -0
  90. data/test/test_scout-gear.rb +0 -0
  91. metadata +64 -3
@@ -0,0 +1,372 @@
1
+ module Open
2
+ BLOCK_SIZE = 1024
3
+
4
+ class << self
5
+ attr_accessor :sensible_write_lock_dir
6
+
7
+ def sensible_write_lock_dir
8
+ @sensible_write_lock_dir ||= Path.setup("tmp/sensible_write_locks").find
9
+ end
10
+ end
11
+
12
+ class << self
13
+ attr_accessor :sensible_write_dir
14
+ def sensible_write_dir
15
+ @sensible_write_dir ||= Path.setup("tmp/sensible_write").find
16
+ end
17
+ end
18
+
19
+ def self.consume_stream(io, in_thread = false, into = nil, into_close = true, &block)
20
+ return if Path === io
21
+ return unless io.respond_to? :read
22
+
23
+ if io.respond_to? :closed? and io.closed?
24
+ io.join if io.respond_to? :join
25
+ return
26
+ end
27
+
28
+ if in_thread
29
+ consumer_thread = Thread.new(Thread.current) do |parent|
30
+ Thread.current["name"] = "Consumer #{Log.fingerprint io}"
31
+ Thread.current.report_on_exception = false
32
+ consume_stream(io, false, into, into_close)
33
+ end
34
+ io.threads.push(consumer_thread) if io.respond_to?(:threads)
35
+ consumer_thread
36
+ else
37
+ if into
38
+ Log.medium "Consuming stream #{Log.fingerprint io} -> #{Log.fingerprint into}"
39
+ else
40
+ Log.medium "Consuming stream #{Log.fingerprint io}"
41
+ end
42
+
43
+ begin
44
+ into = into.find if Path === into
45
+
46
+ if String === into
47
+ dir = File.dirname(into)
48
+ Open.mkdir dir unless File.exist?(dir)
49
+ into_path, into = into, File.open(into, 'w')
50
+ end
51
+
52
+ into.sync = true if IO === into
53
+ into_close = false unless into.respond_to? :close
54
+ io.sync = true
55
+
56
+ Log.high "started consuming stream #{Log.fingerprint io}"
57
+ begin
58
+ while c = io.readpartial(BLOCK_SIZE)
59
+ into << c if into
60
+ end
61
+ rescue EOFError
62
+ end
63
+
64
+ io.join if io.respond_to? :join
65
+ io.close unless io.closed?
66
+ into.join if into and into_close and into.respond_to?(:joined?) and not into.joined?
67
+ into.close if into and into_close and not into.closed?
68
+ block.call if block_given?
69
+
70
+ Log.high "Done consuming stream #{Log.fingerprint io} into #{into_path || into}"
71
+ rescue Aborted
72
+ Log.high "Consume stream Aborted #{Log.fingerprint io} into #{into_path || into}"
73
+ io.abort $! if io.respond_to? :abort
74
+ into.close if into.respond_to?(:closed?) && ! into.closed?
75
+ FileUtils.rm into_path if into_path and File.exist?(into_path)
76
+ rescue Exception
77
+ Log.high "Consume stream Exception reading #{Log.fingerprint io} into #{into_path || into} - #{$!.message}"
78
+ exception = io.stream_exception || $!
79
+ io.abort exception if io.respond_to? :abort
80
+ into.close if into.respond_to?(:closed?) && ! into.closed?
81
+ into_path = into if into_path.nil? && String === into
82
+ if into_path and File.exist?(into_path)
83
+ FileUtils.rm into_path
84
+ end
85
+ raise exception
86
+ end
87
+ end
88
+ end
89
+
90
+ def self.sensible_write(path, content = nil, options = {}, &block)
91
+ force = IndiferentHash.process_options options, :force
92
+
93
+ if File.exist?(path) and not force
94
+ Open.consume_stream content
95
+ return
96
+ end
97
+
98
+ lock_options = IndiferentHash.pull_keys options.dup, :lock
99
+ lock_options = lock_options[:lock] if Hash === lock_options[:lock]
100
+ tmp_path = TmpFile.tmp_for_file(path, {:dir => Open.sensible_write_dir})
101
+ tmp_path_lock = TmpFile.tmp_for_file(path, {:dir => Open.sensible_write_lock_dir})
102
+
103
+ tmp_path_lock = nil if FalseClass === options[:lock]
104
+
105
+ Open.lock tmp_path_lock, lock_options do
106
+
107
+ if File.exist? path and not force
108
+ Log.warn "Path exists in sensible_write, not forcing update: #{ path }"
109
+ Open.consume_stream content
110
+ else
111
+ FileUtils.mkdir_p File.dirname(tmp_path) unless File.directory? File.dirname(tmp_path)
112
+ FileUtils.rm_f tmp_path if File.exist? tmp_path
113
+ begin
114
+
115
+ case
116
+ when block_given?
117
+ File.open(tmp_path, 'wb', &block)
118
+ when String === content
119
+ File.open(tmp_path, 'wb') do |f| f.write content end
120
+ when (IO === content or StringIO === content or File === content)
121
+ Open.write(tmp_path) do |f|
122
+ f.sync = true
123
+ begin
124
+ while block = content.readpartial(BLOCK_SIZE)
125
+ f.write block
126
+ end
127
+ rescue EOFError
128
+ end
129
+ end
130
+ else
131
+ File.open(tmp_path, 'wb') do |f| end
132
+ end
133
+
134
+ begin
135
+ Misc.insist do
136
+ Open.mv tmp_path, path, lock_options
137
+ end
138
+ rescue Exception
139
+ raise $! unless File.exist? path
140
+ end
141
+
142
+ Open.touch path if File.exist? path
143
+ content.join if content.respond_to?(:join) and not Path === content and not (content.respond_to?(:joined?) && content.joined?)
144
+
145
+ Open.notify_write(path)
146
+ rescue Aborted
147
+ Log.medium "Aborted sensible_write -- #{ Log.reset << Log.color(:blue, path) }"
148
+ content.abort if content.respond_to? :abort
149
+ Open.rm path if File.exist? path
150
+ rescue Exception
151
+ exception = (AbortedStream === content and content.exception) ? content.exception : $!
152
+ Log.medium "Exception in sensible_write: [#{Process.pid}] #{exception.message} -- #{ Log.color :blue, path }"
153
+ content.abort if content.respond_to? :abort
154
+ Open.rm path if File.exist? path
155
+ raise exception
156
+ rescue
157
+ raise $!
158
+ ensure
159
+ FileUtils.rm_f tmp_path if File.exist? tmp_path
160
+ if Lockfile === lock_options[:lock] and lock_options[:lock].locked?
161
+ lock_options[:lock].unlock
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ PIPE_MUTEX = Mutex.new
169
+
170
+ OPEN_PIPE_IN = []
171
+ def self.pipe
172
+ OPEN_PIPE_IN.delete_if{|pipe| pipe.closed? }
173
+ res = PIPE_MUTEX.synchronize do
174
+ sout, sin = IO.pipe
175
+ OPEN_PIPE_IN << sin
176
+
177
+ [sout, sin]
178
+ end
179
+ Log.debug{"Creating pipe #{[Log.fingerprint(res.last), Log.fingerprint(res.first)] * " => "}"}
180
+ res
181
+ end
182
+
183
+ def self.with_fifo(path = nil, clean = true, &block)
184
+ begin
185
+ erase = path.nil?
186
+ path = TmpFile.tmp_file if path.nil?
187
+ File.rm path if clean && File.exist?(path)
188
+ File.mkfifo path
189
+ yield path
190
+ ensure
191
+ FileUtils.rm path if erase && File.exist?(path)
192
+ end
193
+ end
194
+
195
+ def self.release_pipes(*pipes)
196
+ PIPE_MUTEX.synchronize do
197
+ pipes.flatten.each do |pipe|
198
+ pipe.close unless pipe.closed?
199
+ end
200
+ end
201
+ end
202
+
203
+ def self.purge_pipes(*save)
204
+ PIPE_MUTEX.synchronize do
205
+ OPEN_PIPE_IN.each do |pipe|
206
+ next if save.include? pipe
207
+ pipe.close unless pipe.closed?
208
+ end
209
+ end
210
+ end
211
+
212
+ def self.open_pipe(do_fork = false, close = true)
213
+ raise "No block given" unless block_given?
214
+
215
+ sout, sin = Open.pipe
216
+
217
+ if do_fork
218
+
219
+ #parent_pid = Process.pid
220
+ pid = Process.fork {
221
+ purge_pipes(sin)
222
+ sout.close
223
+ begin
224
+
225
+ yield sin
226
+ sin.close if close and not sin.closed?
227
+
228
+ rescue Exception
229
+ Log.exception $!
230
+ #Process.kill :INT, parent_pid
231
+ Kernel.exit!(-1)
232
+ end
233
+ Kernel.exit! 0
234
+ }
235
+ sin.close
236
+
237
+ ConcurrentStream.setup sout, :pids => [pid]
238
+ else
239
+
240
+ ConcurrentStream.setup sin, :pair => sout
241
+ ConcurrentStream.setup sout, :pair => sin
242
+
243
+ thread = Thread.new do
244
+ Thread.current["name"] = "Pipe input #{Log.fingerprint sin} => #{Log.fingerprint sout}"
245
+ Thread.current.report_on_exception = false
246
+ begin
247
+
248
+ yield sin
249
+
250
+ sin.close if close and not sin.closed? and not sin.aborted?
251
+ rescue Aborted
252
+ Log.medium "Aborted open_pipe: #{$!.message}"
253
+ raise $!
254
+ rescue Exception
255
+ Log.medium "Exception in open_pipe: #{$!.message}"
256
+ begin
257
+ sout.threads.delete(Thread.current)
258
+ sout.pair = []
259
+ sout.abort($!) if sout.respond_to?(:abort)
260
+ sin.threads.delete(Thread.current)
261
+ sin.pair = []
262
+ sin.abort($!) if sin.respond_to?(:abort)
263
+ ensure
264
+ raise $!
265
+ end
266
+ end
267
+ end
268
+
269
+ sin.threads = [thread]
270
+ sout.threads = [thread]
271
+ end
272
+
273
+ sout
274
+ end
275
+
276
+ def self.tee_stream_thread_multiple(stream, num = 2)
277
+ in_pipes = []
278
+ out_pipes = []
279
+ num.times do
280
+ sout, sin = Open.pipe
281
+ in_pipes << sin
282
+ out_pipes << sout
283
+ end
284
+
285
+ filename = stream.filename if stream.respond_to? :filename
286
+
287
+ splitter_thread = Thread.new(Thread.current) do |parent|
288
+ begin
289
+ Thread.current["name"] = "Splitter #{Log.fingerprint stream}"
290
+ Thread.current.report_on_exception = false
291
+
292
+ skip = [false] * num
293
+ begin
294
+ while block = stream.readpartial(BLOCK_SIZE)
295
+
296
+ in_pipes.each_with_index do |sin,i|
297
+ begin
298
+ sin.write block
299
+ rescue IOError
300
+ Log.warn("Tee stream #{i} #{Log.fingerprint stream} IOError: #{$!.message} (#{Log.fingerprint sin})");
301
+ skip[i] = true
302
+ rescue
303
+ Log.warn("Tee stream #{i} #{Log.fingerprint stream} Exception: #{$!.message} (#{Log.fingerprint sin})");
304
+ raise $!
305
+ end unless skip[i]
306
+ end
307
+ end
308
+ rescue IOError
309
+ end
310
+
311
+ stream.join if stream.respond_to? :join
312
+ stream.close unless stream.closed?
313
+ in_pipes.first.close unless in_pipes.first.closed?
314
+ rescue Aborted, Interrupt
315
+ stream.abort if stream.respond_to? :abort
316
+ out_pipes.each do |sout|
317
+ sout.abort if sout.respond_to? :abort
318
+ end
319
+ Log.medium "Tee aborting #{Log.fingerprint stream}"
320
+ raise $!
321
+ rescue Exception
322
+ begin
323
+ stream.abort($!) if stream.respond_to?(:abort) && ! stream.aborted?
324
+ out_pipes.reverse.each do |sout|
325
+ sout.threads.delete(Thread.current)
326
+ begin
327
+ sout.abort($!) if sout.respond_to?(:abort) && ! sout.aborted?
328
+ rescue
329
+ end
330
+ end
331
+ in_pipes.each do |sin|
332
+ sin.close unless sin.closed?
333
+ end
334
+ Log.medium "Tee exception #{Log.fingerprint stream}"
335
+ rescue
336
+ Log.exception $!
337
+ ensure
338
+ in_pipes.each do |sin|
339
+ sin.close unless sin.closed?
340
+ end
341
+ raise $!
342
+ end
343
+ end
344
+ end
345
+
346
+
347
+ out_pipes.each do |sout|
348
+ ConcurrentStream.setup sout, :threads => splitter_thread, :filename => filename, :pair => stream
349
+ end
350
+ splitter_thread.wakeup until splitter_thread["name"]
351
+
352
+ main_pipe = out_pipes.first
353
+ main_pipe.autojoin = true
354
+
355
+ main_pipe.callback = Proc.new do
356
+ stream.join if stream.respond_to? :join
357
+ in_pipes[1..-1].each do |sin|
358
+ sin.close unless sin.closed?
359
+ end
360
+ end
361
+
362
+ out_pipes
363
+ end
364
+
365
+ def self.tee_stream_thread(stream)
366
+ tee_stream_thread_multiple(stream, 2)
367
+ end
368
+
369
+ def self.tee_stream(stream)
370
+ tee_stream_thread(stream)
371
+ end
372
+ end
@@ -0,0 +1,225 @@
1
+ module Open
2
+ GREP_CMD = begin
3
+ if ENV["GREP_CMD"]
4
+ ENV["GREP_CMD"]
5
+ elsif File.exist?('/bin/grep')
6
+ "/bin/grep"
7
+ elsif File.exist?('/usr/bin/grep')
8
+ "/usr/bin/grep"
9
+ else
10
+ "grep"
11
+ end
12
+ end
13
+
14
+ def self.grep(stream, grep, invert = false, fixed = nil)
15
+ case
16
+ when Array === grep
17
+ TmpFile.with_file(grep * "\n", false) do |f|
18
+ if FalseClass === fixed
19
+ CMD.cmd("#{GREP_CMD} #{invert ? '-v' : ''} -", "-f" => f, :in => stream, :pipe => true, :post => proc{FileUtils.rm f})
20
+ else
21
+ CMD.cmd("#{GREP_CMD} #{invert ? '-v' : ''} -", "-w" => true, "-F" => true, "-f" => f, :in => stream, :pipe => true, :post => proc{FileUtils.rm f})
22
+ end
23
+ end
24
+ else
25
+ CMD.cmd("#{GREP_CMD} #{invert ? '-v ' : ''} '#{grep}' -", :in => stream, :pipe => true, :post => proc{begin stream.force_close; rescue Exception; end if stream.respond_to?(:force_close)})
26
+ end
27
+ end
28
+
29
+ def self.gzip_pipe(file)
30
+ Open.gzip?(file) ? "<(gunzip -c '#{file}')" : "'#{file}'"
31
+ end
32
+
33
+ def self.bgunzip(stream)
34
+ Bgzf.setup stream
35
+ end
36
+
37
+ def self.gunzip(stream)
38
+ CMD.cmd('zcat', :in => stream, :pipe => true, :no_fail => true, :no_wait => true)
39
+ end
40
+
41
+ def self.gzip(stream)
42
+ CMD.cmd('gzip', :in => stream, :pipe => true, :no_fail => true, :no_wait => true)
43
+ end
44
+
45
+ def self.bgzip(stream)
46
+ CMD.cmd('bgzip', :in => stream, :pipe => true, :no_fail => true, :no_wait => true)
47
+ end
48
+
49
+ def self.unzip(stream)
50
+ TmpFile.with_file(stream.read) do |filename|
51
+ StringIO.new(CMD.cmd("unzip '{opt}' #{filename}", "-p" => true, :pipe => true).read)
52
+ end
53
+ end
54
+
55
+ # Questions
56
+ def self.gzip?(file)
57
+ file = file.find if Path === file
58
+ !! (file =~ /\.gz$/)
59
+ end
60
+
61
+ def self.bgzip?(file)
62
+ file = file.find if Path === file
63
+ !! (file =~ /\.bgz$/)
64
+ end
65
+
66
+ def self.zip?(file)
67
+ file = file.find if Path === file
68
+ !! (file =~ /\.zip$/)
69
+ end
70
+
71
+ def self.notify_write(file)
72
+ begin
73
+ notification_file = file + '.notify'
74
+ if Open.exists? notification_file
75
+ key = Open.read(notification_file).strip
76
+ key = nil if key.empty?
77
+ if key && key.include?("@")
78
+ to = from = key
79
+ subject = "Wrote " << file
80
+ message = "Content attached"
81
+ Misc.send_email(from, to, subject, message, :files => [file])
82
+ else
83
+ Misc.notify("Wrote " << file, nil, key)
84
+ end
85
+ Open.rm notification_file
86
+ end
87
+ rescue
88
+ Log.exception $!
89
+ Log.warn "Error notifying write of #{ file }"
90
+ end
91
+ end
92
+
93
+ def self.broken_link?(path)
94
+ File.symlink?(path) && ! File.exist?(File.readlink(path))
95
+ end
96
+
97
+ def self.exists?(file)
98
+ file = file.find if Path === file
99
+ File.exist?(file)
100
+ end
101
+ class << self; alias exist? exists? end
102
+
103
+ def self.mv(source, target, options = {})
104
+ target = target.find if Path === target
105
+ source = source.find if Path === source
106
+ FileUtils.mkdir_p File.dirname(target) unless File.exist?(File.dirname(target))
107
+ tmp_target = File.join(File.dirname(target), '.tmp_mv.' + File.basename(target))
108
+ FileUtils.mv source, tmp_target
109
+ FileUtils.mv tmp_target, target
110
+ return nil
111
+ end
112
+
113
+ def self.rm(file)
114
+ FileUtils.rm(file) if File.exist?(file) or Open.broken_link?(file)
115
+ end
116
+
117
+ def self.rm_rf(file)
118
+ FileUtils.rm_rf(file)
119
+ end
120
+
121
+ def self.touch(file)
122
+ FileUtils.touch(file)
123
+ end
124
+
125
+ def self.mkdir(target)
126
+ target = target.find if Path === target
127
+ if ! File.exist?(target)
128
+ FileUtils.mkdir_p target
129
+ end
130
+ end
131
+
132
+ def self.writable?(path)
133
+ path = path.find if Path === path
134
+ if File.symlink?(path)
135
+ File.writable?(File.dirname(path))
136
+ elsif File.exist?(path)
137
+ File.writable?(path)
138
+ else
139
+ File.writable?(File.dirname(File.expand_path(path)))
140
+ end
141
+ end
142
+
143
+ def self.ctime(file)
144
+ file = file.find if Path === file
145
+ File.ctime(file)
146
+ end
147
+
148
+ def self.realpath(file)
149
+ file = file.find if Path === file
150
+ Pathname.new(File.expand_path(file)).realpath.to_s
151
+ end
152
+
153
+ def self.mtime(file)
154
+ file = file.find if Path === file
155
+ begin
156
+ if File.symlink?(file) || File.stat(file).nlink > 1
157
+ if File.exist?(file + '.info') && defined?(Step)
158
+ done = Step::INFO_SERIALIZER.load(Open.open(file + '.info'))[:done]
159
+ return done if done
160
+ end
161
+
162
+ file = Pathname.new(file).realpath.to_s
163
+ end
164
+ return nil unless File.exist?(file)
165
+ File.mtime(file)
166
+ rescue
167
+ nil
168
+ end
169
+ end
170
+
171
+ def self.cp(source, target, options = {})
172
+ source = source.find if Path === source
173
+ target = target.find if Path === target
174
+
175
+ FileUtils.mkdir_p File.dirname(target) unless File.exist?(File.dirname(target))
176
+ FileUtils.rm target if File.exist?(target)
177
+ FileUtils.cp_r source, target
178
+ end
179
+
180
+
181
+ def self.ln_s(source, target, options = {})
182
+ source = source.find if Path === source
183
+ target = target.find if Path === target
184
+
185
+ target = File.join(target, File.basename(source)) if File.directory? target
186
+ FileUtils.mkdir_p File.dirname(target) unless File.exist?(File.dirname(target))
187
+ FileUtils.rm target if File.exist?(target)
188
+ FileUtils.rm target if File.symlink?(target)
189
+ FileUtils.ln_s source, target
190
+ end
191
+
192
+ def self.ln(source, target, options = {})
193
+ source = source.find if Path === source
194
+ target = target.find if Path === target
195
+ source = File.realpath(source) if File.symlink?(source)
196
+
197
+ FileUtils.mkdir_p File.dirname(target) unless File.exist?(File.dirname(target))
198
+ FileUtils.rm target if File.exist?(target)
199
+ FileUtils.rm target if File.symlink?(target)
200
+ FileUtils.ln source, target
201
+ end
202
+
203
+ def self.ln_h(source, target, options = {})
204
+ source = source.find if Path === source
205
+ target = target.find if Path === target
206
+
207
+ FileUtils.mkdir_p File.dirname(target) unless File.exist?(File.dirname(target))
208
+ FileUtils.rm target if File.exist?(target)
209
+ begin
210
+ CMD.cmd("ln -L '#{ source }' '#{ target }'")
211
+ rescue ProcessFailed
212
+ Log.debug "Could not hard link #{source} and #{target}: #{$!.message.gsub("\n", '. ')}"
213
+ CMD.cmd("cp -L '#{ source }' '#{ target }'")
214
+ end
215
+ end
216
+
217
+ def self.link(source, target, options = {})
218
+ begin
219
+ Open.ln(source, target, options)
220
+ rescue
221
+ Open.ln_s(source, target, options)
222
+ end
223
+ nil
224
+ end
225
+ end