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