scout-gear 2.0.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
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