scout-essentials 1.0.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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.vimproject +78 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +18 -0
  7. data/Rakefile +47 -0
  8. data/VERSION +1 -0
  9. data/lib/scout/cmd.rb +348 -0
  10. data/lib/scout/concurrent_stream.rb +284 -0
  11. data/lib/scout/config.rb +168 -0
  12. data/lib/scout/exceptions.rb +77 -0
  13. data/lib/scout/indiferent_hash/case_insensitive.rb +30 -0
  14. data/lib/scout/indiferent_hash/options.rb +115 -0
  15. data/lib/scout/indiferent_hash.rb +96 -0
  16. data/lib/scout/log/color.rb +224 -0
  17. data/lib/scout/log/color_class.rb +269 -0
  18. data/lib/scout/log/fingerprint.rb +69 -0
  19. data/lib/scout/log/progress/report.rb +244 -0
  20. data/lib/scout/log/progress/util.rb +173 -0
  21. data/lib/scout/log/progress.rb +106 -0
  22. data/lib/scout/log/trap.rb +107 -0
  23. data/lib/scout/log.rb +441 -0
  24. data/lib/scout/meta_extension.rb +100 -0
  25. data/lib/scout/misc/digest.rb +63 -0
  26. data/lib/scout/misc/filesystem.rb +25 -0
  27. data/lib/scout/misc/format.rb +255 -0
  28. data/lib/scout/misc/helper.rb +31 -0
  29. data/lib/scout/misc/insist.rb +56 -0
  30. data/lib/scout/misc/monitor.rb +66 -0
  31. data/lib/scout/misc/system.rb +73 -0
  32. data/lib/scout/misc.rb +10 -0
  33. data/lib/scout/named_array.rb +138 -0
  34. data/lib/scout/open/lock/lockfile.rb +587 -0
  35. data/lib/scout/open/lock.rb +68 -0
  36. data/lib/scout/open/remote.rb +135 -0
  37. data/lib/scout/open/stream.rb +491 -0
  38. data/lib/scout/open/util.rb +244 -0
  39. data/lib/scout/open.rb +170 -0
  40. data/lib/scout/path/find.rb +204 -0
  41. data/lib/scout/path/tmpfile.rb +8 -0
  42. data/lib/scout/path/util.rb +127 -0
  43. data/lib/scout/path.rb +51 -0
  44. data/lib/scout/persist/open.rb +17 -0
  45. data/lib/scout/persist/path.rb +15 -0
  46. data/lib/scout/persist/serialize.rb +157 -0
  47. data/lib/scout/persist.rb +104 -0
  48. data/lib/scout/resource/open.rb +8 -0
  49. data/lib/scout/resource/path.rb +80 -0
  50. data/lib/scout/resource/produce/rake.rb +69 -0
  51. data/lib/scout/resource/produce.rb +151 -0
  52. data/lib/scout/resource/scout.rb +3 -0
  53. data/lib/scout/resource/software.rb +178 -0
  54. data/lib/scout/resource/util.rb +59 -0
  55. data/lib/scout/resource.rb +40 -0
  56. data/lib/scout/simple_opt/accessor.rb +54 -0
  57. data/lib/scout/simple_opt/doc.rb +126 -0
  58. data/lib/scout/simple_opt/get.rb +57 -0
  59. data/lib/scout/simple_opt/parse.rb +67 -0
  60. data/lib/scout/simple_opt/setup.rb +26 -0
  61. data/lib/scout/simple_opt.rb +5 -0
  62. data/lib/scout/tmpfile.rb +129 -0
  63. data/lib/scout-essentials.rb +10 -0
  64. data/scout-essentials.gemspec +143 -0
  65. data/share/color/color_names +507 -0
  66. data/share/color/diverging_colors.hex +12 -0
  67. data/share/software/install_helpers +523 -0
  68. data/test/scout/indiferent_hash/test_case_insensitive.rb +16 -0
  69. data/test/scout/indiferent_hash/test_options.rb +46 -0
  70. data/test/scout/log/test_color.rb +0 -0
  71. data/test/scout/log/test_progress.rb +108 -0
  72. data/test/scout/misc/test_digest.rb +30 -0
  73. data/test/scout/misc/test_filesystem.rb +30 -0
  74. data/test/scout/misc/test_insist.rb +13 -0
  75. data/test/scout/misc/test_system.rb +21 -0
  76. data/test/scout/open/test_lock.rb +52 -0
  77. data/test/scout/open/test_remote.rb +25 -0
  78. data/test/scout/open/test_stream.rb +676 -0
  79. data/test/scout/open/test_util.rb +73 -0
  80. data/test/scout/path/test_find.rb +110 -0
  81. data/test/scout/path/test_util.rb +22 -0
  82. data/test/scout/persist/test_open.rb +37 -0
  83. data/test/scout/persist/test_path.rb +37 -0
  84. data/test/scout/persist/test_serialize.rb +114 -0
  85. data/test/scout/resource/test_path.rb +58 -0
  86. data/test/scout/resource/test_produce.rb +94 -0
  87. data/test/scout/resource/test_software.rb +24 -0
  88. data/test/scout/resource/test_util.rb +38 -0
  89. data/test/scout/simple_opt/test_doc.rb +16 -0
  90. data/test/scout/simple_opt/test_get.rb +11 -0
  91. data/test/scout/simple_opt/test_parse.rb +10 -0
  92. data/test/scout/simple_opt/test_setup.rb +77 -0
  93. data/test/scout/test_cmd.rb +85 -0
  94. data/test/scout/test_concurrent_stream.rb +29 -0
  95. data/test/scout/test_config.rb +66 -0
  96. data/test/scout/test_indiferent_hash.rb +26 -0
  97. data/test/scout/test_log.rb +32 -0
  98. data/test/scout/test_meta_extension.rb +80 -0
  99. data/test/scout/test_misc.rb +6 -0
  100. data/test/scout/test_named_array.rb +43 -0
  101. data/test/scout/test_open.rb +146 -0
  102. data/test/scout/test_path.rb +54 -0
  103. data/test/scout/test_persist.rb +186 -0
  104. data/test/scout/test_resource.rb +26 -0
  105. data/test/scout/test_tmpfile.rb +53 -0
  106. data/test/test_helper.rb +50 -0
  107. metadata +247 -0
@@ -0,0 +1,135 @@
1
+ require_relative '../misc'
2
+ require_relative '../path'
3
+ require_relative '../cmd'
4
+
5
+ module Open
6
+ class << self
7
+ attr_accessor :remote_cache_dir
8
+
9
+ def remote_cache_dir
10
+ @remote_cache_dir ||= Path.setup("var/cache/open-remote/").find
11
+ end
12
+ end
13
+
14
+ def self.remote?(file)
15
+ !! (file =~ /^(?:https?|ftp|ssh):\/\//)
16
+ end
17
+
18
+ def self.ssh?(file)
19
+ !! (file =~ /^ssh:\/\//)
20
+ end
21
+
22
+ def self.ssh(file, options = {})
23
+ m = file.match(/ssh:\/\/([^:]+):(.*)/)
24
+ server = m[1]
25
+ file = m[2]
26
+ if server == 'localhost'
27
+ Open.open(file)
28
+ else
29
+ CMD.cmd("ssh '#{server}' cat '#{file}'", :pipe => true, :autojoin => true)
30
+ end
31
+ end
32
+
33
+ def self.wget(url, options = {})
34
+ if ! (options[:force] || options[:nocache]) && cache_file = in_cache(url, options)
35
+ return file_open(cache_file)
36
+ end
37
+
38
+ Log.low "WGET:\n -URL: #{ url }\n -OPTIONS: #{options.inspect}"
39
+ options = IndiferentHash.add_defaults options, "--user-agent=" => 'rbbt', :pipe => true, :autojoin => true
40
+
41
+ wait(options[:nice], options[:nice_key]) if options[:nice]
42
+ options.delete(:nice)
43
+ options.delete(:nice_key)
44
+
45
+ pipe = options.delete(:pipe)
46
+ quiet = options.delete(:quiet)
47
+ post = options.delete(:post)
48
+ cookies = options.delete(:cookies)
49
+ nocache = options.delete(:nocache)
50
+
51
+ options["--quiet"] = quiet if options["--quiet"].nil?
52
+ options["--post-data="] ||= post if post
53
+
54
+ if cookies
55
+ options["--save-cookies"] = cookies
56
+ options["--load-cookies"] = cookies
57
+ options["--keep-session-cookies"] = true
58
+ end
59
+
60
+ stderr = case
61
+ when options['stderr']
62
+ options['stderr']
63
+ when options['--quiet']
64
+ false
65
+ else
66
+ nil
67
+ end
68
+
69
+ begin
70
+ wget_options = options.dup
71
+ wget_options = wget_options.merge( '-O' => '-') unless options.include?('--output-document')
72
+ wget_options[:pipe] = pipe unless pipe.nil?
73
+ wget_options[:stderr] = stderr unless stderr.nil?
74
+
75
+ io = CMD.cmd("wget '#{ url }'", wget_options)
76
+ if nocache && nocache.to_s != 'update'
77
+ io
78
+ else
79
+ add_cache(url, io, options)
80
+ open_cache(url, options)
81
+ end
82
+ rescue
83
+ STDERR.puts $!.backtrace.inspect
84
+ raise OpenURLError, "Error reading remote url: #{ url }.\n#{$!.message}"
85
+ end
86
+ end
87
+
88
+ def self.download(url, file)
89
+ CMD.cmd_log(:wget, "'#{url}' -O '#{file}'")
90
+ end
91
+
92
+ def self.digest_url(url, options = {})
93
+ params = [url, options.values_at("--post-data", "--post-data="), (options.include?("--post-file")? Open.read(options["--post-file"]).split("\n").sort * "\n" : "")]
94
+ Misc.digest([url, params])
95
+ end
96
+
97
+ def self.cache_file(url, options)
98
+ File.join(self.remote_cache_dir, digest_url(url, options))
99
+ end
100
+
101
+ def self.in_cache(url, options = {})
102
+ filename = cache_file(url, options)
103
+ if File.exist? filename
104
+ return filename
105
+ else
106
+ nil
107
+ end
108
+ end
109
+
110
+ def self.remove_from_cache(url, options = {})
111
+ filename = cache_file(url, options)
112
+ if File.exist? filename
113
+ FileUtils.rm filename
114
+ else
115
+ nil
116
+ end
117
+ end
118
+
119
+ def self.add_cache(url, data, options = {})
120
+ filename = cache_file(url, options)
121
+ Open.sensible_write(filename, data, :force => true)
122
+ end
123
+
124
+ def self.open_cache(url, options = {})
125
+ filename = cache_file(url, options)
126
+ Open.open(filename)
127
+ end
128
+
129
+ def self.scp(source_file, target_file, target: nil, source: nil)
130
+ CMD.cmd_log("ssh #{target} mkdir -p #{File.dirname(target_file)}")
131
+ target_file = [target, target_file] * ":" if target && ! target_file.start_with?(target+":")
132
+ source_file = [source, source_file] * ":" if source && ! source_file.start_with?(source+":")
133
+ CMD.cmd_log("scp -r '#{ source_file }' #{target_file}")
134
+ end
135
+ end
@@ -0,0 +1,491 @@
1
+ module Open
2
+ BLOCK_SIZE = 1024 * 8
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
+
35
+ io.threads.push(consumer_thread) if io.respond_to?(:threads)
36
+ Thread.pass until consumer_thread["name"]
37
+
38
+ consumer_thread
39
+ else
40
+ if into
41
+ Log.low "Consuming stream #{Log.fingerprint io} -> #{Log.fingerprint into}"
42
+ else
43
+ Log.low "Consuming stream #{Log.fingerprint io}"
44
+ end
45
+
46
+ begin
47
+ into = into.find if Path === into
48
+
49
+ if String === into
50
+ dir = File.dirname(into)
51
+ Open.mkdir dir unless File.exist?(dir)
52
+ into_path, into = into, File.open(into, 'w')
53
+ end
54
+
55
+ into_close = false unless into.respond_to? :close
56
+
57
+ while c = io.read(BLOCK_SIZE)
58
+ into << c if into
59
+ last_c = c if c
60
+ break if io.closed?
61
+ end
62
+
63
+ io.join if io.respond_to? :join
64
+ io.close unless io.closed?
65
+ into.join if into and into_close and into.respond_to?(:joined?) and not into.joined?
66
+ into.close if into and into_close and not into.closed?
67
+ block.call if block_given?
68
+
69
+ last_c
70
+ rescue Aborted
71
+ Thread.current["exception"] = true
72
+ Log.low "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
+ Thread.current["exception"] = true
78
+ Log.low "Consume stream Exception reading #{Log.fingerprint io} into #{into_path || into} - #{$!.message}"
79
+ exception = (io.respond_to?(:stream_exception) && io.stream_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
+ Log.low "Sensible write stream #{Log.fingerprint content} -> #{Log.fingerprint path}" if IO === content
115
+ begin
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
+ while block = content.read(BLOCK_SIZE)
124
+ f.write block
125
+ break if content.closed?
126
+ end
127
+ end
128
+ else
129
+ File.open(tmp_path, 'wb') do |f| end
130
+ end
131
+
132
+ begin
133
+ Misc.insist do
134
+ Open.mv tmp_path, path, lock_options
135
+ end
136
+ rescue Exception
137
+ raise $! unless File.exist? path
138
+ end
139
+
140
+ Open.touch path if File.exist? path
141
+ content.join if content.respond_to?(:join) and not Path === content and not (content.respond_to?(:joined?) && content.joined?)
142
+
143
+ Open.notify_write(path)
144
+ rescue Aborted
145
+ Log.low "Aborted sensible_write -- #{ Log.reset << path }"
146
+ content.abort if content.respond_to? :abort
147
+ Open.rm path if File.exist? path
148
+ rescue Exception
149
+ exception = (AbortedStream === content and content.exception) ? content.exception : $!
150
+ Log.low "Exception in sensible_write: [#{Process.pid}] #{exception.message} -- #{ path }"
151
+ content.abort(exception) if content.respond_to? :abort
152
+ Open.rm path if File.exist? path
153
+ raise exception
154
+ rescue
155
+ raise $!
156
+ ensure
157
+ FileUtils.rm_f tmp_path if File.exist? tmp_path
158
+ if Lockfile === lock_options[:lock] and lock_options[:lock].locked?
159
+ lock_options[:lock].unlock
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ PIPE_MUTEX = Mutex.new
167
+
168
+ OPEN_PIPE_IN = []
169
+ def self.pipe
170
+ OPEN_PIPE_IN.delete_if{|pipe| pipe.closed? }
171
+ res = PIPE_MUTEX.synchronize do
172
+ sout, sin = IO.pipe
173
+ OPEN_PIPE_IN << sin
174
+
175
+ [sout, sin]
176
+ end
177
+ Log.low{"Creating pipe #{[Log.fingerprint(res.last), Log.fingerprint(res.first)] * " -> "}"}
178
+ res
179
+ end
180
+
181
+ def self.with_fifo(path = nil, clean = true, &block)
182
+ begin
183
+ erase = path.nil?
184
+ path = TmpFile.tmp_file if path.nil?
185
+ File.rm path if clean && File.exist?(path)
186
+ File.mkfifo path
187
+ yield path
188
+ ensure
189
+ FileUtils.rm path if erase && File.exist?(path)
190
+ end
191
+ end
192
+
193
+ def self.release_pipes(*pipes)
194
+ PIPE_MUTEX.synchronize do
195
+ pipes.flatten.each do |pipe|
196
+ pipe.close unless pipe.closed?
197
+ end
198
+ end
199
+ end
200
+
201
+ def self.purge_pipes(*save)
202
+ PIPE_MUTEX.synchronize do
203
+ OPEN_PIPE_IN.each do |pipe|
204
+ next if save.include? pipe
205
+ pipe.close unless pipe.closed?
206
+ end
207
+ end
208
+ end
209
+
210
+ def self.open_pipe(do_fork = false, close = true)
211
+ raise "No block given" unless block_given?
212
+
213
+ sout, sin = Open.pipe
214
+
215
+ if do_fork
216
+
217
+ pid = Process.fork {
218
+ begin
219
+ purge_pipes(sin)
220
+ sout.close
221
+
222
+ yield sin
223
+ sin.close if close and not sin.closed?
224
+
225
+ rescue Exception
226
+ Log.exception $!
227
+ Kernel.exit!(-1)
228
+ end
229
+ Kernel.exit! 0
230
+ }
231
+ sin.close
232
+
233
+ ConcurrentStream.setup sout, :pids => [pid]
234
+ else
235
+
236
+ ConcurrentStream.setup sin, :pair => sout
237
+ ConcurrentStream.setup sout, :pair => sin
238
+
239
+ thread = Thread.new do
240
+ begin
241
+ ConcurrentStream.process_stream(sin, :message => "Open pipe") do
242
+ Thread.current.report_on_exception = false
243
+ Thread.current["name"] = "Pipe input #{Log.fingerprint sin} => #{Log.fingerprint sout}"
244
+
245
+ yield sin
246
+ end
247
+ end
248
+ end
249
+
250
+ sin.threads = [thread]
251
+ sout.threads = [thread]
252
+
253
+ Thread.pass until thread["name"]
254
+ end
255
+
256
+ sout
257
+ end
258
+
259
+ def self.tee_stream_thread_multiple(stream, num = 2)
260
+ in_pipes = []
261
+ out_pipes = []
262
+ num.times do
263
+ sout, sin = Open.pipe
264
+ in_pipes << sin
265
+ out_pipes << sout
266
+ end
267
+
268
+ Log.low("Tee stream #{Log.fingerprint stream} -> #{Log.fingerprint out_pipes}")
269
+
270
+ filename = stream.filename if stream.respond_to? :filename
271
+
272
+ splitter_thread = Thread.new(Thread.current) do |parent|
273
+ begin
274
+ Thread.current.report_on_exception = false
275
+ Thread.current["name"] = "Splitter #{Log.fingerprint stream}"
276
+
277
+ skip = [false] * num
278
+ while block = stream.read(BLOCK_SIZE)
279
+
280
+ in_pipes.each_with_index do |sin,i|
281
+ begin
282
+ sin.write block
283
+ rescue IOError
284
+ Log.warn("Tee stream #{i} #{Log.fingerprint stream} IOError: #{$!.message} (#{Log.fingerprint sin})");
285
+ skip[i] = true
286
+ rescue
287
+ Log.warn("Tee stream #{i} #{Log.fingerprint stream} Exception: #{$!.message} (#{Log.fingerprint sin})");
288
+ raise $!
289
+ end unless skip[i]
290
+ end
291
+ break if stream.closed?
292
+ end
293
+
294
+ stream.join if stream.respond_to? :join
295
+ in_pipes.first.close unless in_pipes.first.closed?
296
+ rescue Aborted, Interrupt
297
+ stream.abort if stream.respond_to?(:abort) && ! stream.aborted?
298
+ out_pipes.reverse.each do |sout|
299
+ sout.threads.delete(Thread.current)
300
+ begin
301
+ sout.abort($!) if sout.respond_to?(:abort) && ! sout.aborted?
302
+ rescue
303
+ end
304
+ end
305
+ in_pipes.each do |sin|
306
+ sin.close unless sin.closed?
307
+ end
308
+ Log.low "Tee aborting #{Log.fingerprint stream}"
309
+ raise $!
310
+ rescue Exception
311
+ begin
312
+ stream.abort($!) if stream.respond_to?(:abort) && ! stream.aborted?
313
+ out_pipes.reverse.each do |sout|
314
+ sout.threads.delete(Thread.current)
315
+ begin
316
+ sout.abort($!) if sout.respond_to?(:abort) && ! sout.aborted?
317
+ rescue
318
+ end
319
+ end
320
+ in_pipes.each do |sin|
321
+ sin.close unless sin.closed?
322
+ end
323
+ Log.low "Tee exception #{Log.fingerprint stream}"
324
+ rescue
325
+ ensure
326
+ begin
327
+ in_pipes.each do |sin|
328
+ sin.close unless sin.closed?
329
+ end
330
+ ensure
331
+ raise $!
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ Thread.pass until splitter_thread["name"]
338
+
339
+ main_pipe = out_pipes.first
340
+
341
+ ConcurrentStream.setup(main_pipe, :threads => [splitter_thread], :filename => filename, :autojoin => true)
342
+
343
+ out_pipes[1..-1].each do |sout|
344
+ ConcurrentStream.setup sout, :filename => filename, :threads => [splitter_thread]
345
+ end
346
+
347
+ main_pipe.callback = proc do
348
+ begin
349
+ stream.join if stream.respond_to?(:join) && ! stream.joined?
350
+ in_pipes[1..-1].each do |sin|
351
+ sin.close unless sin.closed?
352
+ end
353
+ rescue
354
+ main_pipe.abort_callback.call($!)
355
+ raise $!
356
+ end
357
+ end
358
+
359
+ main_pipe.abort_callback = proc do |exception|
360
+ stream.abort(exception)
361
+ out_pipes[1..-1].each do |sout|
362
+ sout.abort(exception)
363
+ end
364
+ end
365
+
366
+ out_pipes
367
+ end
368
+
369
+ def self.tee_stream_thread(stream)
370
+ tee_stream_thread_multiple(stream, 2)
371
+ end
372
+
373
+ def self.tee_stream(stream)
374
+ tee_stream_thread(stream)
375
+ end
376
+
377
+ def self.read_stream(stream, size)
378
+ str = nil
379
+ Thread.pass while IO.select([stream],nil,nil,1).nil?
380
+ while not str = stream.read(size)
381
+ IO.select([stream],nil,nil,1)
382
+ Thread.pass
383
+ raise ClosedStream if stream.eof?
384
+ end
385
+
386
+ while str.length < size
387
+ raise ClosedStream if stream.eof?
388
+ IO.select([stream],nil,nil,1)
389
+ if new = stream.read(size-str.length)
390
+ str << new
391
+ end
392
+ end
393
+ str
394
+ end
395
+
396
+ def self.read_stream(stream, size)
397
+ str = ""
398
+ while str.length < size
399
+ missing = size - str.length
400
+ more = stream.read(missing)
401
+ str << more
402
+ end
403
+ str
404
+ end
405
+
406
+ def self.sort_stream(stream, header_hash: "#", cmd_args: "-u", memory: false)
407
+ sout = Open.open_pipe do |sin|
408
+ ConcurrentStream.process_stream(stream) do
409
+ line = stream.gets
410
+ while line && line.start_with?(header_hash) do
411
+ sin.puts line
412
+ line = stream.gets
413
+ end
414
+
415
+ line_stream = Open.open_pipe do |line_stream_in|
416
+ line_stream_in.puts line if line
417
+ Open.consume_stream(stream, false, line_stream_in)
418
+ end
419
+ Log.low "Sub-sort stream #{Log.fingerprint stream} -> #{Log.fingerprint line_stream}"
420
+
421
+ if memory
422
+ line_stream.read.split("\n").sort.each do |line|
423
+ sin.puts line
424
+ end
425
+ else
426
+ io = CMD.cmd("env LC_ALL=C sort #{cmd_args || ""}", :in => line_stream, :pipe => true)
427
+ Open.consume_stream(io, false, sin)
428
+ end
429
+ end
430
+ end
431
+ Log.low "Sort #{Log.fingerprint stream} -> #{Log.fingerprint sout}"
432
+ sout
433
+ end
434
+
435
+ #def self.sort_stream(stream, header_hash = "#", cmd_args = "-u")
436
+ # StringIO.new stream.read.split("\n").sort.uniq * "\n"
437
+ #end
438
+
439
+ def self.collapse_stream(s, line: nil, sep: "\t", header: nil, &block)
440
+ sep ||= "\t"
441
+ Open.open_pipe do |sin|
442
+
443
+ sin.puts header if header
444
+
445
+ line ||= s.gets
446
+
447
+ current_parts = []
448
+ while line
449
+ key, *parts = line.chomp.split(sep, -1)
450
+ case
451
+ when key.nil?
452
+ when current_parts.nil?
453
+ current_parts = parts
454
+ current_key = key
455
+ when current_key == key
456
+ parts.each_with_index do |part,i|
457
+ if current_parts[i].nil?
458
+ current_parts[i] = "|" << part
459
+ else
460
+ current_parts[i] = current_parts[i] << "|" << part
461
+ end
462
+ end
463
+
464
+ (parts.length..current_parts.length-1).to_a.each do |pos|
465
+ current_parts[pos] = current_parts[pos] << "|" << ""
466
+ end
467
+ when current_key.nil?
468
+ current_key = key
469
+ current_parts = parts
470
+ when current_key != key
471
+ if block_given?
472
+ res = block.call(current_parts)
473
+ sin.puts [current_key, res] * sep
474
+ else
475
+ sin.puts [current_key, current_parts].flatten * sep
476
+ end
477
+ current_key = key
478
+ current_parts = parts
479
+ end
480
+ line = s.gets
481
+ end
482
+
483
+ if block_given?
484
+ res = block.call(current_parts)
485
+ sin.puts [current_key, res] * sep
486
+ else
487
+ sin.puts [current_key, current_parts].flatten * sep
488
+ end unless current_key.nil?
489
+ end
490
+ end
491
+ end