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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.vimproject +78 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/lib/scout/cmd.rb +348 -0
- data/lib/scout/concurrent_stream.rb +284 -0
- data/lib/scout/config.rb +168 -0
- data/lib/scout/exceptions.rb +77 -0
- data/lib/scout/indiferent_hash/case_insensitive.rb +30 -0
- data/lib/scout/indiferent_hash/options.rb +115 -0
- data/lib/scout/indiferent_hash.rb +96 -0
- data/lib/scout/log/color.rb +224 -0
- data/lib/scout/log/color_class.rb +269 -0
- data/lib/scout/log/fingerprint.rb +69 -0
- data/lib/scout/log/progress/report.rb +244 -0
- data/lib/scout/log/progress/util.rb +173 -0
- data/lib/scout/log/progress.rb +106 -0
- data/lib/scout/log/trap.rb +107 -0
- data/lib/scout/log.rb +441 -0
- data/lib/scout/meta_extension.rb +100 -0
- data/lib/scout/misc/digest.rb +63 -0
- data/lib/scout/misc/filesystem.rb +25 -0
- data/lib/scout/misc/format.rb +255 -0
- data/lib/scout/misc/helper.rb +31 -0
- data/lib/scout/misc/insist.rb +56 -0
- data/lib/scout/misc/monitor.rb +66 -0
- data/lib/scout/misc/system.rb +73 -0
- data/lib/scout/misc.rb +10 -0
- data/lib/scout/named_array.rb +138 -0
- data/lib/scout/open/lock/lockfile.rb +587 -0
- data/lib/scout/open/lock.rb +68 -0
- data/lib/scout/open/remote.rb +135 -0
- data/lib/scout/open/stream.rb +491 -0
- data/lib/scout/open/util.rb +244 -0
- data/lib/scout/open.rb +170 -0
- data/lib/scout/path/find.rb +204 -0
- data/lib/scout/path/tmpfile.rb +8 -0
- data/lib/scout/path/util.rb +127 -0
- data/lib/scout/path.rb +51 -0
- data/lib/scout/persist/open.rb +17 -0
- data/lib/scout/persist/path.rb +15 -0
- data/lib/scout/persist/serialize.rb +157 -0
- data/lib/scout/persist.rb +104 -0
- data/lib/scout/resource/open.rb +8 -0
- data/lib/scout/resource/path.rb +80 -0
- data/lib/scout/resource/produce/rake.rb +69 -0
- data/lib/scout/resource/produce.rb +151 -0
- data/lib/scout/resource/scout.rb +3 -0
- data/lib/scout/resource/software.rb +178 -0
- data/lib/scout/resource/util.rb +59 -0
- data/lib/scout/resource.rb +40 -0
- data/lib/scout/simple_opt/accessor.rb +54 -0
- data/lib/scout/simple_opt/doc.rb +126 -0
- data/lib/scout/simple_opt/get.rb +57 -0
- data/lib/scout/simple_opt/parse.rb +67 -0
- data/lib/scout/simple_opt/setup.rb +26 -0
- data/lib/scout/simple_opt.rb +5 -0
- data/lib/scout/tmpfile.rb +129 -0
- data/lib/scout-essentials.rb +10 -0
- data/scout-essentials.gemspec +143 -0
- data/share/color/color_names +507 -0
- data/share/color/diverging_colors.hex +12 -0
- data/share/software/install_helpers +523 -0
- data/test/scout/indiferent_hash/test_case_insensitive.rb +16 -0
- data/test/scout/indiferent_hash/test_options.rb +46 -0
- data/test/scout/log/test_color.rb +0 -0
- data/test/scout/log/test_progress.rb +108 -0
- data/test/scout/misc/test_digest.rb +30 -0
- data/test/scout/misc/test_filesystem.rb +30 -0
- data/test/scout/misc/test_insist.rb +13 -0
- data/test/scout/misc/test_system.rb +21 -0
- data/test/scout/open/test_lock.rb +52 -0
- data/test/scout/open/test_remote.rb +25 -0
- data/test/scout/open/test_stream.rb +676 -0
- data/test/scout/open/test_util.rb +73 -0
- data/test/scout/path/test_find.rb +110 -0
- data/test/scout/path/test_util.rb +22 -0
- data/test/scout/persist/test_open.rb +37 -0
- data/test/scout/persist/test_path.rb +37 -0
- data/test/scout/persist/test_serialize.rb +114 -0
- data/test/scout/resource/test_path.rb +58 -0
- data/test/scout/resource/test_produce.rb +94 -0
- data/test/scout/resource/test_software.rb +24 -0
- data/test/scout/resource/test_util.rb +38 -0
- data/test/scout/simple_opt/test_doc.rb +16 -0
- data/test/scout/simple_opt/test_get.rb +11 -0
- data/test/scout/simple_opt/test_parse.rb +10 -0
- data/test/scout/simple_opt/test_setup.rb +77 -0
- data/test/scout/test_cmd.rb +85 -0
- data/test/scout/test_concurrent_stream.rb +29 -0
- data/test/scout/test_config.rb +66 -0
- data/test/scout/test_indiferent_hash.rb +26 -0
- data/test/scout/test_log.rb +32 -0
- data/test/scout/test_meta_extension.rb +80 -0
- data/test/scout/test_misc.rb +6 -0
- data/test/scout/test_named_array.rb +43 -0
- data/test/scout/test_open.rb +146 -0
- data/test/scout/test_path.rb +54 -0
- data/test/scout/test_persist.rb +186 -0
- data/test/scout/test_resource.rb +26 -0
- data/test/scout/test_tmpfile.rb +53 -0
- data/test/test_helper.rb +50 -0
- 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
|