rbbt-util 5.13.37 → 5.14.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 +4 -4
- data/bin/rbbt +6 -1
- data/lib/rbbt/fix_width_table.rb +21 -9
- data/lib/rbbt/monitor.rb +1 -1
- data/lib/rbbt/packed_index.rb +19 -5
- data/lib/rbbt/persist/tsv.rb +9 -1
- data/lib/rbbt/persist/tsv/fix_width_table.rb +1 -1
- data/lib/rbbt/persist/tsv/packed_index.rb +101 -0
- data/lib/rbbt/persist/tsv/sharder.rb +11 -3
- data/lib/rbbt/resource/path.rb +1 -1
- data/lib/rbbt/resource/rake.rb +1 -0
- data/lib/rbbt/tsv/accessor.rb +18 -13
- data/lib/rbbt/tsv/dumper.rb +2 -6
- data/lib/rbbt/tsv/manipulate.rb +6 -4
- data/lib/rbbt/tsv/parallel/traverse.rb +7 -6
- data/lib/rbbt/tsv/parser.rb +20 -16
- data/lib/rbbt/tsv/stream.rb +87 -76
- data/lib/rbbt/tsv/util.rb +8 -3
- data/lib/rbbt/util/R.rb +1 -1
- data/lib/rbbt/util/cmd.rb +0 -3
- data/lib/rbbt/util/concurrency/processes.rb +3 -0
- data/lib/rbbt/util/concurrency/processes/worker.rb +0 -1
- data/lib/rbbt/util/log.rb +45 -18
- data/lib/rbbt/util/log/progress/report.rb +3 -2
- data/lib/rbbt/util/log/progress/util.rb +1 -1
- data/lib/rbbt/util/misc/concurrent_stream.rb +12 -6
- data/lib/rbbt/util/misc/development.rb +10 -4
- data/lib/rbbt/util/misc/lock.rb +1 -1
- data/lib/rbbt/util/misc/omics.rb +2 -0
- data/lib/rbbt/util/misc/pipes.rb +90 -87
- data/lib/rbbt/workflow.rb +6 -2
- data/lib/rbbt/workflow/accessor.rb +70 -40
- data/lib/rbbt/workflow/definition.rb +23 -0
- data/lib/rbbt/workflow/step.rb +15 -3
- data/lib/rbbt/workflow/step/run.rb +18 -13
- data/lib/rbbt/workflow/usage.rb +3 -0
- data/share/Rlib/util.R +1 -1
- data/share/rbbt_commands/tsv/get +0 -2
- data/share/rbbt_commands/tsv/info +13 -5
- data/share/rbbt_commands/tsv/subset +1 -1
- data/share/rbbt_commands/workflow/info +32 -0
- data/share/rbbt_commands/workflow/task +0 -2
- data/test/rbbt/persist/tsv/test_sharder.rb +44 -0
- data/test/rbbt/test_fix_width_table.rb +1 -0
- data/test/rbbt/test_packed_index.rb +3 -0
- data/test/rbbt/tsv/test_stream.rb +55 -2
- data/test/rbbt/util/misc/test_pipes.rb +8 -6
- data/test/rbbt/workflow/test_step.rb +7 -6
- metadata +3 -2
data/lib/rbbt/util/R.rb
CHANGED
@@ -38,7 +38,7 @@ source('#{UTIL}');
|
|
38
38
|
|
39
39
|
def self.interactive(script, options = {})
|
40
40
|
TmpFile.with_file do |init_file|
|
41
|
-
|
41
|
+
Open.write(init_file) do |f|
|
42
42
|
f.puts "# Loading basic rbbt environment"
|
43
43
|
f.puts "library(utils);\n"
|
44
44
|
f.puts "source('#{R::UTIL}');\n"
|
data/lib/rbbt/util/cmd.rb
CHANGED
@@ -112,7 +112,6 @@ module CMD
|
|
112
112
|
stderr = Log::HIGH
|
113
113
|
end
|
114
114
|
|
115
|
-
# Process cmd_options
|
116
115
|
cmd_options = process_cmd_options options
|
117
116
|
if cmd =~ /'\{opt\}'/
|
118
117
|
cmd.sub!('\'{opt}\'', cmd_options)
|
@@ -203,7 +202,6 @@ module CMD
|
|
203
202
|
Log.log line, stderr if Integer === stderr and log
|
204
203
|
end
|
205
204
|
serr.close
|
206
|
-
Thread.exit!
|
207
205
|
end
|
208
206
|
|
209
207
|
#SmartIO.tie sout, pid, cmd, post, in_content, sin, serr
|
@@ -217,7 +215,6 @@ module CMD
|
|
217
215
|
err << serr.gets if Integer === stderr
|
218
216
|
end
|
219
217
|
serr.close
|
220
|
-
Thread.exit
|
221
218
|
end
|
222
219
|
|
223
220
|
ConcurrentStream.setup sout, :pids => [pid], :autojoin => true
|
data/lib/rbbt/util/log.rb
CHANGED
@@ -2,10 +2,18 @@ require 'term/ansicolor'
|
|
2
2
|
require 'rbbt/util/color'
|
3
3
|
require 'rbbt/util/log/progress'
|
4
4
|
|
5
|
+
class MockMutex
|
6
|
+
def synchronize
|
7
|
+
yield
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
5
11
|
module Log
|
6
12
|
extend Term::ANSIColor
|
7
13
|
|
8
14
|
|
15
|
+
#ToDo: I'm not sure if using a Mutex here really gives troubles in CPU concurrency
|
16
|
+
#LOG_MUTEX = MockMutex.new
|
9
17
|
LOG_MUTEX = Mutex.new
|
10
18
|
|
11
19
|
SEVERITY_NAMES ||= begin
|
@@ -16,8 +24,16 @@ module Log
|
|
16
24
|
names
|
17
25
|
end
|
18
26
|
|
27
|
+
def self.last_caller(stack)
|
28
|
+
line = nil
|
29
|
+
while line.nil? or line =~ /util\/log\.rb/ and stack.any?
|
30
|
+
line = stack.shift
|
31
|
+
end
|
32
|
+
line ||= caller.first
|
33
|
+
end
|
34
|
+
|
19
35
|
def self.ignore_stderr
|
20
|
-
|
36
|
+
LOG_MUTEX.synchronize do
|
21
37
|
backup_stderr = STDERR.dup
|
22
38
|
File.open('/dev/null', 'w') do |f|
|
23
39
|
STDERR.reopen(f)
|
@@ -28,7 +44,7 @@ module Log
|
|
28
44
|
backup_stderr.close
|
29
45
|
end
|
30
46
|
end
|
31
|
-
|
47
|
+
end
|
32
48
|
end
|
33
49
|
|
34
50
|
def self.get_level(level)
|
@@ -133,22 +149,18 @@ module Log
|
|
133
149
|
message = "" << highlight << message << color(0) if severity >= INFO
|
134
150
|
str = prefix << " " << message
|
135
151
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
152
|
+
LOG_MUTEX.synchronize do
|
153
|
+
STDERR.puts str
|
154
|
+
Log::LAST.replace "log"
|
155
|
+
logfile.puts str unless logfile.nil?
|
156
|
+
nil
|
157
|
+
end
|
142
158
|
end
|
143
159
|
|
144
160
|
def self.log_obj_inspect(obj, level, file = $stdout)
|
145
161
|
stack = caller
|
146
162
|
|
147
|
-
line =
|
148
|
-
while line.nil? or line =~ /util\/log\.rb/ and stack.any?
|
149
|
-
line = stack.shift
|
150
|
-
end
|
151
|
-
line ||= caller.first
|
163
|
+
line = Log.last_caller stack
|
152
164
|
|
153
165
|
level = Log.get_level level
|
154
166
|
name = Log::SEVERITY_NAMES[level] + ": "
|
@@ -161,11 +173,7 @@ module Log
|
|
161
173
|
def self.log_obj_fingerprint(obj, level, file = $stdout)
|
162
174
|
stack = caller
|
163
175
|
|
164
|
-
line =
|
165
|
-
while line.nil? or line =~ /util\/log\.rb/ and stack.any?
|
166
|
-
line = stack.shift
|
167
|
-
end
|
168
|
-
line ||= caller.first
|
176
|
+
line = Log.last_caller stack
|
169
177
|
|
170
178
|
level = Log.get_level level
|
171
179
|
name = Log::SEVERITY_NAMES[level] + ": "
|
@@ -210,6 +218,25 @@ module Log
|
|
210
218
|
error("BACKTRACE:\n" + e.backtrace * "\n")
|
211
219
|
end
|
212
220
|
|
221
|
+
def self.color_stack(stack)
|
222
|
+
stack.collect do |line|
|
223
|
+
line = line.sub('`',"'")
|
224
|
+
color = :green if line =~ /workflow/
|
225
|
+
color = :blue if line =~ /rbbt-/
|
226
|
+
Log.color color, line
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.stack(stack)
|
231
|
+
LOG_MUTEX.synchronize do
|
232
|
+
|
233
|
+
STDERR.puts Log.color :magenta, "Stack trace: " << Log.last_caller(caller)
|
234
|
+
color_stack(stack).each do |line|
|
235
|
+
STDERR.puts line
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
213
240
|
case ENV['RBBT_LOG']
|
214
241
|
when 'DEBUG'
|
215
242
|
self.severity = DEBUG
|
@@ -10,11 +10,11 @@ module Log
|
|
10
10
|
|
11
11
|
def print(io, str)
|
12
12
|
return if ENV["RBBT_NO_PROGRESS"] == "true"
|
13
|
-
|
13
|
+
LOG_MUTEX.synchronize do
|
14
14
|
STDERR.print str
|
15
15
|
Log.logfile.puts str unless Log.logfile.nil?
|
16
16
|
Log::LAST.replace "progress"
|
17
|
-
|
17
|
+
end
|
18
18
|
end
|
19
19
|
|
20
20
|
attr_accessor :history, :mean_max
|
@@ -90,6 +90,7 @@ module Log
|
|
90
90
|
if Log::LAST != "progress"
|
91
91
|
length = Log::ProgressBar.cleanup_bars
|
92
92
|
bars = BARS
|
93
|
+
print(io, Log.color(:yellow, "...Progress\n"))
|
93
94
|
bars.sort_by{|b| b.depth }.reverse.each do |bar|
|
94
95
|
print(io, Log.color(:yellow ,bar.report_msg) << "\n")
|
95
96
|
end
|
@@ -82,18 +82,18 @@ module ConcurrentStream
|
|
82
82
|
join_callback
|
83
83
|
|
84
84
|
@joined = true
|
85
|
-
close unless closed?
|
86
85
|
lockfile.unlock if lockfile and lockfile.locked?
|
86
|
+
close unless closed?
|
87
87
|
end
|
88
88
|
|
89
|
-
def abort_threads
|
89
|
+
def abort_threads(exception)
|
90
90
|
Log.medium "Aborting threads (#{Thread.current.inspect}) #{@threads.collect{|t| t.inspect } * ", "}"
|
91
91
|
|
92
92
|
@threads.each do |t|
|
93
93
|
@aborted = false if t == Thread.current
|
94
94
|
next if t == Thread.current
|
95
95
|
Log.medium "Aborting thread #{t.inspect}"
|
96
|
-
t.raise Aborted.new
|
96
|
+
t.raise exception ? exception : Aborted.new
|
97
97
|
end if @threads
|
98
98
|
|
99
99
|
sleeped = false
|
@@ -117,11 +117,16 @@ module ConcurrentStream
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def abort_pids
|
120
|
-
@pids.each
|
120
|
+
@pids.each do |pid|
|
121
|
+
begin
|
122
|
+
Process.kill :INT, pid
|
123
|
+
rescue Errno::ESRCH
|
124
|
+
end
|
125
|
+
end if @pids
|
121
126
|
@pids = []
|
122
127
|
end
|
123
128
|
|
124
|
-
def abort
|
129
|
+
def abort(exception = nil)
|
125
130
|
return if @aborted
|
126
131
|
Log.medium "Aborting stream #{Misc.fingerprint self} -- #{@abort_callback} [#{@aborted}]"
|
127
132
|
@aborted = true
|
@@ -131,8 +136,9 @@ module ConcurrentStream
|
|
131
136
|
@abort_callback = nil
|
132
137
|
close unless closed?
|
133
138
|
|
134
|
-
abort_threads
|
139
|
+
abort_threads(exception)
|
135
140
|
abort_pids
|
141
|
+
ensure
|
136
142
|
lockfile.unlock if lockfile and lockfile.locked?
|
137
143
|
end
|
138
144
|
Log.medium "Aborted stream #{Misc.fingerprint self} -- #{@abort_callback} [#{@aborted}]"
|
@@ -135,24 +135,30 @@ module Misc
|
|
135
135
|
try = 0
|
136
136
|
begin
|
137
137
|
yield
|
138
|
-
rescue
|
138
|
+
rescue TryAgain
|
139
|
+
sleep sleep
|
140
|
+
retry
|
141
|
+
rescue Aborted, Interrupt
|
139
142
|
if msg
|
140
143
|
Log.warn("Not Insisting after Aborted: #{$!.message} -- #{msg}")
|
141
144
|
else
|
142
145
|
Log.warn("Not Insisting after Aborted: #{$!.message}")
|
143
146
|
end
|
147
|
+
raise $!
|
144
148
|
rescue Exception
|
145
149
|
if msg
|
146
|
-
Log.warn("Insisting after exception: #{$!.message} -- #{msg}")
|
150
|
+
Log.warn("Insisting after exception: #{$!.class} #{$!.message} -- #{msg}")
|
147
151
|
else
|
148
|
-
Log.warn("Insisting after exception: #{$!.message}")
|
149
|
-
end
|
152
|
+
Log.warn("Insisting after exception: #{$!.class} #{$!.message}")
|
153
|
+
end
|
154
|
+
|
150
155
|
if sleep and try > 0
|
151
156
|
sleep sleep
|
152
157
|
sleep = sleep_array.shift if sleep_array
|
153
158
|
else
|
154
159
|
Thread.pass
|
155
160
|
end
|
161
|
+
|
156
162
|
try += 1
|
157
163
|
retry if try < times
|
158
164
|
raise $!
|
data/lib/rbbt/util/misc/lock.rb
CHANGED
@@ -28,11 +28,11 @@ module Misc
|
|
28
28
|
|
29
29
|
res = nil
|
30
30
|
|
31
|
-
lock_path = File.expand_path(file + '.lock')
|
32
31
|
if options[:lock]
|
33
32
|
lockfile = options[:lock]
|
34
33
|
lockfile.lock unless lockfile.locked?
|
35
34
|
else
|
35
|
+
lock_path = File.expand_path(file + '.lock')
|
36
36
|
lockfile = Lockfile.new(lock_path, options)
|
37
37
|
lockfile.lock
|
38
38
|
end
|
data/lib/rbbt/util/misc/omics.rb
CHANGED
@@ -125,9 +125,11 @@ module Misc
|
|
125
125
|
|
126
126
|
def self.correct_vcf_mutation(pos, ref, mut_str)
|
127
127
|
muts = mut_str.nil? ? [] : mut_str.split(',')
|
128
|
+
muts.collect!{|m| m == '<DEL>' ? '-' : m }
|
128
129
|
|
129
130
|
while ref.length >= 1 and muts.reject{|m| m[0] == ref[0]}.empty?
|
130
131
|
ref = ref[1..-1]
|
132
|
+
raise "REF nil" if ref.nil?
|
131
133
|
pos = pos + 1
|
132
134
|
muts = muts.collect{|m| m[1..-1]}
|
133
135
|
end
|
data/lib/rbbt/util/misc/pipes.rb
CHANGED
@@ -175,7 +175,7 @@ module Misc
|
|
175
175
|
Log.medium "Consuming stream #{Misc.fingerprint io}"
|
176
176
|
begin
|
177
177
|
into.sync == true if IO === into
|
178
|
-
while not io.closed? and block = io.read(2048
|
178
|
+
while not io.closed? and block = io.read(2048)
|
179
179
|
into << block if into
|
180
180
|
end
|
181
181
|
io.join if io.respond_to? :join
|
@@ -213,48 +213,47 @@ module Misc
|
|
213
213
|
end
|
214
214
|
|
215
215
|
def self.sensiblewrite(path, content = nil, options = {}, &block)
|
216
|
+
force = Misc.process_options options, :force
|
216
217
|
lock_options = Misc.pull_keys options, :lock
|
217
218
|
lock_options = lock_options[:lock] if Hash === lock_options[:lock]
|
218
|
-
return if Open.exists? path
|
219
|
+
return if Open.exists? path and not force
|
219
220
|
tmp_path = Persist.persistence_path(path, {:dir => Misc.sensiblewrite_dir})
|
220
221
|
tmp_path_lock = Persist.persistence_path(path, {:dir => Misc.sensiblewrite_lock_dir})
|
221
222
|
Misc.lock tmp_path_lock, lock_options do
|
222
|
-
return if Open.exists? path
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
f.write block
|
238
|
-
end
|
223
|
+
return if Open.exists? path and not force
|
224
|
+
FileUtils.mkdir_p File.dirname(tmp_path) unless File.directory? File.dirname(tmp_path)
|
225
|
+
FileUtils.rm_f tmp_path if File.exists? tmp_path
|
226
|
+
begin
|
227
|
+
case
|
228
|
+
when block_given?
|
229
|
+
File.open(tmp_path, 'wb', &block)
|
230
|
+
when String === content
|
231
|
+
File.open(tmp_path, 'wb') do |f| f.write content end
|
232
|
+
when (IO === content or StringIO === content or File === content)
|
233
|
+
|
234
|
+
Open.write(tmp_path) do |f|
|
235
|
+
f.sync = true
|
236
|
+
while block = content.read(2048)
|
237
|
+
f.write block
|
239
238
|
end
|
240
|
-
else
|
241
|
-
File.open(tmp_path, 'wb') do |f| end
|
242
239
|
end
|
243
|
-
|
244
|
-
|
245
|
-
content.join if content.respond_to? :join
|
246
|
-
rescue Aborted
|
247
|
-
Log.medium "Aborted sensiblewrite -- #{ Log.reset << Log.color(:blue, path) }"
|
248
|
-
content.abort if content.respond_to? :abort
|
249
|
-
Open.rm path if File.exists? path
|
250
|
-
rescue Exception
|
251
|
-
Log.medium "Exception in sensiblewrite: #{$!.message} -- #{ Log.color :blue, path }"
|
252
|
-
content.abort if content.respond_to? :abort
|
253
|
-
Open.rm path if File.exists? path
|
254
|
-
raise $!
|
255
|
-
ensure
|
256
|
-
FileUtils.rm_f tmp_path if File.exists? tmp_path
|
240
|
+
else
|
241
|
+
File.open(tmp_path, 'wb') do |f| end
|
257
242
|
end
|
243
|
+
|
244
|
+
Open.mv tmp_path, path, lock_options
|
245
|
+
content.join if content.respond_to? :join
|
246
|
+
rescue Aborted
|
247
|
+
Log.medium "Aborted sensiblewrite -- #{ Log.reset << Log.color(:blue, path) }"
|
248
|
+
content.abort if content.respond_to? :abort
|
249
|
+
Open.rm path if File.exists? path
|
250
|
+
rescue Exception
|
251
|
+
Log.medium "Exception in sensiblewrite: #{$!.message} -- #{ Log.color :blue, path }"
|
252
|
+
content.abort if content.respond_to? :abort
|
253
|
+
Open.rm path if File.exists? path
|
254
|
+
raise $!
|
255
|
+
ensure
|
256
|
+
FileUtils.rm_f tmp_path if File.exists? tmp_path
|
258
257
|
end
|
259
258
|
end
|
260
259
|
end
|
@@ -269,7 +268,7 @@ module Misc
|
|
269
268
|
end
|
270
269
|
end
|
271
270
|
|
272
|
-
def self.sort_stream(stream, header_hash = "#", cmd_args =
|
271
|
+
def self.sort_stream(stream, header_hash = "#", cmd_args = " -u ")
|
273
272
|
Misc.open_pipe do |sin|
|
274
273
|
begin
|
275
274
|
if defined? Step and Step === stream
|
@@ -343,64 +342,68 @@ module Misc
|
|
343
342
|
end
|
344
343
|
end
|
345
344
|
|
346
|
-
def self.
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
stream.get_stream || stream.join.path.open
|
354
|
-
else
|
355
|
-
stream
|
356
|
-
end
|
345
|
+
def self._paste_streams(streams, output, lines = nil, sep = "\t", header = nil)
|
346
|
+
output.puts header if header
|
347
|
+
streams = streams.collect do |stream|
|
348
|
+
if defined? Step and Step === stream
|
349
|
+
stream.get_stream || stream.join.path.open
|
350
|
+
else
|
351
|
+
stream
|
357
352
|
end
|
353
|
+
end
|
358
354
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
else
|
383
|
-
k, *p = line.strip.split(sep, -1)
|
384
|
-
keys[i] = k
|
385
|
-
parts[i] = p
|
386
|
-
end
|
355
|
+
begin
|
356
|
+
done_streams = []
|
357
|
+
lines ||= streams.collect{|s| s.gets }
|
358
|
+
keys = []
|
359
|
+
parts = []
|
360
|
+
lines.each_with_index do |line,i|
|
361
|
+
key, *p = line.strip.split(sep, -1)
|
362
|
+
keys[i] = key
|
363
|
+
parts[i] = p
|
364
|
+
end
|
365
|
+
sizes = parts.collect{|p| p.length }
|
366
|
+
last_min = nil
|
367
|
+
while lines.compact.any?
|
368
|
+
min = keys.compact.sort.first
|
369
|
+
str = []
|
370
|
+
keys.each_with_index do |key,i|
|
371
|
+
case key
|
372
|
+
when min
|
373
|
+
str << [parts[i] * sep]
|
374
|
+
line = lines[i] = streams[i].gets
|
375
|
+
if line.nil?
|
376
|
+
keys[i] = nil
|
377
|
+
parts[i] = nil
|
387
378
|
else
|
388
|
-
|
379
|
+
k, *p = line.strip.split(sep, -1)
|
380
|
+
keys[i] = k
|
381
|
+
parts[i] = p
|
389
382
|
end
|
383
|
+
else
|
384
|
+
str << [sep * (sizes[i]-1)] if sizes[i] > 0
|
390
385
|
end
|
391
|
-
|
392
|
-
sin.puts [min, str*sep] * sep
|
393
|
-
end
|
394
|
-
streams.each do |stream|
|
395
|
-
stream.join if stream.respond_to? :join
|
396
386
|
end
|
397
|
-
|
398
|
-
|
399
|
-
streams.each do |stream|
|
400
|
-
stream.abort if stream.respond_to? :abort
|
401
|
-
end
|
402
|
-
raise $!
|
387
|
+
|
388
|
+
output.puts [min, str*sep] * sep
|
403
389
|
end
|
390
|
+
streams.each do |stream|
|
391
|
+
stream.join if stream.respond_to? :join
|
392
|
+
end
|
393
|
+
rescue
|
394
|
+
Log.exception $!
|
395
|
+
streams.each do |stream|
|
396
|
+
stream.abort if stream.respond_to? :abort
|
397
|
+
end
|
398
|
+
raise $!
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def self.paste_streams(streams, lines = nil, sep = "\t", header = nil)
|
403
|
+
sep ||= "\t"
|
404
|
+
num_streams = streams.length
|
405
|
+
Misc.open_pipe do |sin|
|
406
|
+
self._paste_streams(streams, sin, lines, sep, header)
|
404
407
|
end
|
405
408
|
end
|
406
409
|
|