scout-gear 7.1.0 → 7.3.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/.vimproject +65 -2
- data/VERSION +1 -1
- data/bin/scout +5 -1
- data/lib/rbbt-scout.rb +5 -0
- data/lib/scout/concurrent_stream.rb +13 -8
- data/lib/scout/config.rb +168 -0
- data/lib/scout/exceptions.rb +5 -3
- data/lib/scout/indiferent_hash/options.rb +1 -0
- data/lib/scout/indiferent_hash.rb +4 -2
- data/lib/scout/log/color.rb +3 -2
- data/lib/scout/log/progress/report.rb +1 -0
- data/lib/scout/log/progress/util.rb +66 -1
- data/lib/scout/log/progress.rb +5 -3
- data/lib/scout/log.rb +3 -2
- data/lib/scout/misc/helper.rb +31 -0
- data/lib/scout/misc/monitor.rb +4 -1
- data/lib/scout/misc/system.rb +15 -0
- data/lib/scout/misc.rb +2 -0
- data/lib/scout/named_array.rb +68 -0
- data/lib/scout/open/stream.rb +58 -33
- data/lib/scout/path/find.rb +27 -3
- data/lib/scout/path/util.rb +7 -4
- data/lib/scout/persist/serialize.rb +7 -14
- data/lib/scout/persist.rb +46 -12
- data/lib/scout/resource/produce.rb +7 -94
- data/lib/scout/resource/software.rb +176 -0
- data/lib/scout/semaphore.rb +8 -1
- data/lib/scout/tsv/dumper.rb +112 -0
- data/lib/scout/tsv/index.rb +161 -0
- data/lib/scout/tsv/open.rb +128 -0
- data/lib/scout/tsv/parser.rb +230 -30
- data/lib/scout/tsv/path.rb +13 -0
- data/lib/scout/tsv/persist/adapter.rb +367 -0
- data/lib/scout/tsv/persist/fix_width_table.rb +324 -0
- data/lib/scout/tsv/persist/serialize.rb +117 -0
- data/lib/scout/tsv/persist/tokyocabinet.rb +113 -0
- data/lib/scout/tsv/persist.rb +13 -0
- data/lib/scout/tsv/traverse.rb +143 -0
- data/lib/scout/tsv/util/filter.rb +303 -0
- data/lib/scout/tsv/util/process.rb +73 -0
- data/lib/scout/tsv/util/select.rb +220 -0
- data/lib/scout/tsv/util.rb +82 -0
- data/lib/scout/tsv.rb +16 -3
- data/lib/scout/work_queue/worker.rb +4 -4
- data/lib/scout/work_queue.rb +22 -7
- data/lib/scout/workflow/definition.rb +101 -4
- data/lib/scout/workflow/step/config.rb +18 -0
- data/lib/scout/workflow/step/dependencies.rb +40 -0
- data/lib/scout/workflow/step/file.rb +15 -0
- data/lib/scout/workflow/step/info.rb +35 -4
- data/lib/scout/workflow/step/progress.rb +14 -0
- data/lib/scout/workflow/step/provenance.rb +148 -0
- data/lib/scout/workflow/step.rb +71 -17
- data/lib/scout/workflow/task.rb +10 -5
- data/lib/scout/workflow/usage.rb +3 -1
- data/lib/scout/workflow.rb +11 -3
- data/lib/scout-gear.rb +1 -0
- data/lib/scout.rb +1 -0
- data/scout-gear.gemspec +64 -10
- data/scout_commands/find +1 -1
- data/scout_commands/workflow/task +16 -9
- data/scout_commands/workflow/task_old +2 -2
- data/share/software/install_helpers +523 -0
- data/test/scout/log/test_progress.rb +0 -2
- data/test/scout/misc/test_system.rb +21 -0
- data/test/scout/open/test_stream.rb +160 -1
- data/test/scout/path/test_find.rb +14 -7
- data/test/scout/resource/test_software.rb +24 -0
- data/test/scout/test_config.rb +66 -0
- data/test/scout/test_meta_extension.rb +10 -0
- data/test/scout/test_named_array.rb +19 -0
- data/test/scout/test_persist.rb +96 -0
- data/test/scout/test_tmpfile.rb +1 -1
- data/test/scout/test_tsv.rb +50 -1
- data/test/scout/test_work_queue.rb +41 -13
- data/test/scout/tsv/persist/test_adapter.rb +44 -0
- data/test/scout/tsv/persist/test_fix_width_table.rb +134 -0
- data/test/scout/tsv/persist/test_tokyocabinet.rb +92 -0
- data/test/scout/tsv/test_dumper.rb +44 -0
- data/test/scout/tsv/test_index.rb +156 -0
- data/test/scout/tsv/test_open.rb +9 -0
- data/test/scout/tsv/test_parser.rb +114 -3
- data/test/scout/tsv/test_persist.rb +43 -0
- data/test/scout/tsv/test_traverse.rb +116 -0
- data/test/scout/tsv/test_util.rb +23 -0
- data/test/scout/tsv/util/test_filter.rb +188 -0
- data/test/scout/tsv/util/test_process.rb +47 -0
- data/test/scout/tsv/util/test_select.rb +44 -0
- data/test/scout/work_queue/test_worker.rb +66 -9
- data/test/scout/workflow/step/test_dependencies.rb +25 -0
- data/test/scout/workflow/step/test_info.rb +15 -17
- data/test/scout/workflow/step/test_load.rb +19 -21
- data/test/scout/workflow/step/test_provenance.rb +25 -0
- data/test/scout/workflow/test_step.rb +206 -10
- data/test/scout/workflow/test_task.rb +0 -3
- data/test/test_helper.rb +9 -1
- metadata +50 -6
data/lib/scout/semaphore.rb
CHANGED
@@ -37,7 +37,13 @@ if continue
|
|
37
37
|
int ret;
|
38
38
|
sem_t* sem;
|
39
39
|
sem = sem_open(name, 0);
|
40
|
+
if (sem == SEM_FAILED){
|
41
|
+
return(errno);
|
42
|
+
}
|
40
43
|
ret = sem_wait(sem);
|
44
|
+
if (ret == -1){
|
45
|
+
return(errno);
|
46
|
+
}
|
41
47
|
sem_close(sem);
|
42
48
|
return(ret);
|
43
49
|
}
|
@@ -51,6 +57,7 @@ if continue
|
|
51
57
|
sem_close(sem);
|
52
58
|
}
|
53
59
|
EOF
|
60
|
+
|
54
61
|
end
|
55
62
|
|
56
63
|
SEM_MUTEX = Mutex.new
|
@@ -66,7 +73,7 @@ if continue
|
|
66
73
|
|
67
74
|
def self.with_semaphore(size, file = nil)
|
68
75
|
if file.nil?
|
69
|
-
file = "/" << Misc.digest(rand(
|
76
|
+
file = "/scout-" << Misc.digest(rand(100000000000).to_s)[0..10] if file.nil?
|
70
77
|
else
|
71
78
|
file = file.gsub('/', '_') if file
|
72
79
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module TSV
|
2
|
+
class Dumper
|
3
|
+
def self.header_lines(key_field, fields, entry_hash = nil)
|
4
|
+
if Hash === entry_hash
|
5
|
+
sep = entry_hash[:sep] ? entry_hash[:sep] : "\t"
|
6
|
+
preamble = entry_hash[:preamble]
|
7
|
+
header_hash = entry_hash[:header_hash]
|
8
|
+
end
|
9
|
+
|
10
|
+
header_hash = "#" if header_hash.nil?
|
11
|
+
|
12
|
+
preamble = "#: " << Misc.hash2string(entry_hash.merge(:key_field => nil, :fields => nil)) << "\n" if preamble.nil? and entry_hash and entry_hash.values.compact.any?
|
13
|
+
|
14
|
+
str = ""
|
15
|
+
str << preamble.strip << "\n" if preamble and not preamble.empty?
|
16
|
+
if fields
|
17
|
+
if fields.empty?
|
18
|
+
str << header_hash << (key_field || "ID").to_s << "\n"
|
19
|
+
else
|
20
|
+
str << header_hash << (key_field || "ID").to_s << sep << (fields * sep) << "\n"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
str
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.header(options={})
|
28
|
+
key_field, fields, sep, header_hash, preamble = IndiferentHash.process_options options,
|
29
|
+
:key_field, :fields, :sep, :header_hash, :preamble,
|
30
|
+
:sep => "\t", :header_hash => "#", :preamble => true
|
31
|
+
|
32
|
+
if fields.nil? || key_field.nil?
|
33
|
+
fields_str = nil
|
34
|
+
else
|
35
|
+
fields_str = "#{header_hash}#{key_field}#{sep}#{fields*sep}"
|
36
|
+
end
|
37
|
+
|
38
|
+
if preamble && options.values.compact.any?
|
39
|
+
preamble_str = "#: " << IndiferentHash.hash2string(options)
|
40
|
+
else
|
41
|
+
preamble_str = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
[preamble_str, fields_str].compact * "\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
attr_accessor :options
|
49
|
+
def initialize(options = {})
|
50
|
+
@sep, @type = IndiferentHash.process_options options,
|
51
|
+
:sep, :type,
|
52
|
+
:sep => "\t", :type => :double
|
53
|
+
@options = options
|
54
|
+
@sout, @sin = Open.pipe
|
55
|
+
ConcurrentStream.setup(@sin, pair: @sout)
|
56
|
+
ConcurrentStream.setup(@sout, pair: @sin)
|
57
|
+
end
|
58
|
+
|
59
|
+
def init
|
60
|
+
header = Dumper.header(@options.merge(:type => @type, :sep => @sep))
|
61
|
+
@sin.puts header if header and ! header.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def add(key, value)
|
65
|
+
|
66
|
+
case @type
|
67
|
+
when :single
|
68
|
+
@sin.puts key + @sep + value
|
69
|
+
when :list, :flat
|
70
|
+
@sin.puts key + @sep + value * @sep
|
71
|
+
when :double
|
72
|
+
@sin.puts key + @sep + value.collect{|v| v * "|" } * @sep
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def close
|
77
|
+
@sin.close
|
78
|
+
@sin.join
|
79
|
+
end
|
80
|
+
|
81
|
+
def stream
|
82
|
+
@sout
|
83
|
+
end
|
84
|
+
|
85
|
+
def abort(exception=nil)
|
86
|
+
@sin.abort(exception)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def stream
|
91
|
+
dumper = TSV::Dumper.new self.extension_attr_hash
|
92
|
+
dumper.init
|
93
|
+
t = Thread.new do
|
94
|
+
begin
|
95
|
+
Thread.current.report_on_exception = true
|
96
|
+
Thread.current["name"] = "Dumper thread"
|
97
|
+
self.each do |k,v|
|
98
|
+
dumper.add k, v
|
99
|
+
end
|
100
|
+
dumper.close
|
101
|
+
rescue
|
102
|
+
dumper.abort($!)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
Thread.pass until t["name"]
|
106
|
+
dumper.stream
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
stream.read
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require_relative 'parser'
|
2
|
+
require_relative 'persist/fix_width_table'
|
3
|
+
module TSV
|
4
|
+
def self.index(tsv_file, target: 0, fields: nil, order: true, **kwargs)
|
5
|
+
persist, type = IndiferentHash.process_options kwargs,
|
6
|
+
:persist, :persist_type,
|
7
|
+
:persist => false, :persist_type => "HDB"
|
8
|
+
kwargs.delete :type
|
9
|
+
|
10
|
+
Persist.persist(tsv_file, type, kwargs.merge(:persist => persist, :persist_prefix => "Index")) do |filename|
|
11
|
+
if filename
|
12
|
+
index = ScoutCabinet.open(filename, true, type)
|
13
|
+
TSV.setup(index, :type => :single)
|
14
|
+
index.extend TSVAdapter
|
15
|
+
else
|
16
|
+
index = TSV.setup({}, :type => :single)
|
17
|
+
end
|
18
|
+
|
19
|
+
dummy_data = TSV.setup({}, :key_field => "Key", :fields => ["Target"])
|
20
|
+
if order
|
21
|
+
tmp_index = {}
|
22
|
+
key_field, field_names = TSV.traverse tsv_file, key_field: target, fields: fields, type: :double, into: dummy_data, unnamed: true, **kwargs do |k,values|
|
23
|
+
values.each_with_index do |list,i|
|
24
|
+
list.each do |e|
|
25
|
+
tmp_index[e] ||= []
|
26
|
+
tmp_index[e][i] ||= []
|
27
|
+
tmp_index[e][i] << k
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
tmp_index.each do |e,list|
|
32
|
+
index[e] = list.flatten.compact.uniq.first
|
33
|
+
end
|
34
|
+
else
|
35
|
+
key_field, field_names = TSV.traverse tsv_file, key_field: target, fields: fields, type: :flat, into: dummy_data, unnamed: true, **kwargs do |k,values|
|
36
|
+
values.each do |e|
|
37
|
+
index[e] = k unless index.include?(e)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
index.key_field = dummy_data.fields * ", "
|
43
|
+
index.fields = [dummy_data.key_field]
|
44
|
+
index
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def index(*args, **kwargs, &block)
|
49
|
+
TSV.index(self, *args, **kwargs, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.range_index(tsv_file, start_field = nil, end_field = nil, key_field: :key, **kwargs)
|
53
|
+
persist, type = IndiferentHash.process_options kwargs,
|
54
|
+
:persist, :persist_type,
|
55
|
+
:persist => false, :persist_type => :fwt
|
56
|
+
kwargs.delete :type
|
57
|
+
|
58
|
+
Persist.persist(tsv_file, type, kwargs.merge(:persist => persist, :persist_prefix => "Index")) do |filename|
|
59
|
+
|
60
|
+
max_key_size = 0
|
61
|
+
index_data = []
|
62
|
+
TSV.traverse tsv_file, key_field: key_field, fields: [start_field, end_field] do |key, values|
|
63
|
+
key_size = key.length
|
64
|
+
max_key_size = key_size if key_size > max_key_size
|
65
|
+
|
66
|
+
start_pos, end_pos = values
|
67
|
+
if Array === start_pos
|
68
|
+
start_pos.zip(end_pos).each do |s,e|
|
69
|
+
index_data << [key, [s.to_i, e.to_i]]
|
70
|
+
end
|
71
|
+
else
|
72
|
+
index_data << [key, [start_pos.to_i, end_pos.to_i]]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
filename = :memory if filename.nil?
|
77
|
+
index = FixWidthTable.get(filename, max_key_size, true)
|
78
|
+
index.add_range index_data
|
79
|
+
index.read
|
80
|
+
index
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def range_index(*args, **kwargs, &block)
|
85
|
+
TSV.range_index(self, *args, **kwargs, &block)
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
#def range_index(start_field = nil, end_field = nil, options = {})
|
90
|
+
# start_field ||= "Start"
|
91
|
+
# end_field ||= "End"
|
92
|
+
|
93
|
+
# options = Misc.add_defaults options,
|
94
|
+
# :persist => false, :persist_file => nil, :persist_update => false
|
95
|
+
|
96
|
+
# persist_options = Misc.pull_keys options, :persist
|
97
|
+
# persist_options[:prefix] ||= "RangeIndex[#{start_field}-#{end_field}]"
|
98
|
+
|
99
|
+
# Persist.persist(filename || self.object_id.to_s, :fwt, persist_options) do
|
100
|
+
# max_key_size = 0
|
101
|
+
# index_data = []
|
102
|
+
# with_unnamed do
|
103
|
+
# with_monitor :desc => "Creating Index Data", :step => 10000 do
|
104
|
+
# through :key, [start_field, end_field] do |key, values|
|
105
|
+
# key_size = key.length
|
106
|
+
# max_key_size = key_size if key_size > max_key_size
|
107
|
+
|
108
|
+
# start_pos, end_pos = values
|
109
|
+
# if Array === start_pos
|
110
|
+
# start_pos.zip(end_pos).each do |s,e|
|
111
|
+
# index_data << [key, [s.to_i, e.to_i]]
|
112
|
+
# end
|
113
|
+
# else
|
114
|
+
# index_data << [key, [start_pos.to_i, end_pos.to_i]]
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
|
120
|
+
# index = FixWidthTable.get(:memory, max_key_size, true)
|
121
|
+
# index.add_range index_data
|
122
|
+
# index.read
|
123
|
+
# index
|
124
|
+
# end
|
125
|
+
#end
|
126
|
+
|
127
|
+
#def self.range_index(file, start_field = nil, end_field = nil, options = {})
|
128
|
+
# start_field ||= "Start"
|
129
|
+
# end_field ||= "End"
|
130
|
+
|
131
|
+
# data_options = Misc.pull_keys options, :data
|
132
|
+
# filename = case
|
133
|
+
# when (String === file or Path === file)
|
134
|
+
# file
|
135
|
+
# when file.respond_to?(:filename)
|
136
|
+
# file.filename
|
137
|
+
# else
|
138
|
+
# file.object_id.to_s
|
139
|
+
# end
|
140
|
+
# persist_options = Misc.pull_keys options, :persist
|
141
|
+
# persist_options[:prefix] ||= "StaticRangeIndex[#{start_field}-#{end_field}]"
|
142
|
+
|
143
|
+
# filters = Misc.process_options options, :filters
|
144
|
+
|
145
|
+
# if filters
|
146
|
+
# filename += ":Filtered[#{filters.collect{|f| f * "="} * ", "}]"
|
147
|
+
# end
|
148
|
+
|
149
|
+
# Persist.persist(filename, :fwt, persist_options) do
|
150
|
+
# tsv = TSV.open(file, data_options)
|
151
|
+
# if filters
|
152
|
+
# tsv.filter
|
153
|
+
# filters.each do |match, value|
|
154
|
+
# tsv.add_filter match, value
|
155
|
+
# end
|
156
|
+
# end
|
157
|
+
|
158
|
+
# tsv.range_index(start_field, end_field, options)
|
159
|
+
# end
|
160
|
+
#end
|
161
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require_relative '../open'
|
2
|
+
module Open
|
3
|
+
def self.traverse_add(into, res)
|
4
|
+
case into
|
5
|
+
when TSV::Dumper
|
6
|
+
into.add *res
|
7
|
+
when TSV, Hash
|
8
|
+
key, value = res
|
9
|
+
into[key] = value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
#def self.traverse(obj, into: nil, cpus: nil, bar: nil, **options, &block)
|
14
|
+
# case obj
|
15
|
+
# when TSV
|
16
|
+
# obj.traverse options[:key_field], options[:fields], **options do |k,v|
|
17
|
+
# res = yield k, v
|
18
|
+
# end
|
19
|
+
# when String
|
20
|
+
# f = Open.open(obj)
|
21
|
+
# self.traverse(f, into: into, cpus: cpus, bar: bar, **options, &block)
|
22
|
+
# when Step
|
23
|
+
# self.traverse(obj.stream, into: into, cpus: cpus, bar: bar, **options, &block)
|
24
|
+
# when IO
|
25
|
+
# if into && (IO === into || into.respond_to?(:stream) )
|
26
|
+
# into_thread = Thread.new do
|
27
|
+
# Thread.current.report_on_exception = false
|
28
|
+
# Thread.current["name"] = "Traverse into"
|
29
|
+
# TSV.parse obj, **options do |k,v|
|
30
|
+
# begin
|
31
|
+
# res = block.call k, v
|
32
|
+
# traverse_add into, res
|
33
|
+
# rescue
|
34
|
+
# into.abort $!
|
35
|
+
# end
|
36
|
+
# nil
|
37
|
+
# end
|
38
|
+
# into.close if into.respond_to?(:close)
|
39
|
+
# end
|
40
|
+
# Thread.pass until into_thread
|
41
|
+
# into
|
42
|
+
# else
|
43
|
+
# TSV.parse obj, **options do |k,v|
|
44
|
+
# block.call k, v
|
45
|
+
# nil
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#end
|
50
|
+
|
51
|
+
def self.traverse(obj, into: nil, cpus: nil, bar: nil, callback: nil, unnamed: true, **options, &block)
|
52
|
+
|
53
|
+
if into || bar
|
54
|
+
orig_callback = callback if callback
|
55
|
+
bar = Log::ProgressBar.get_obj_bar(bar, obj)
|
56
|
+
callback = proc do |res|
|
57
|
+
bar.tick if bar
|
58
|
+
traverse_add into, res if into
|
59
|
+
orig_callback.call res if orig_callback
|
60
|
+
end
|
61
|
+
|
62
|
+
if into.respond_to?(:close)
|
63
|
+
into_thread = Thread.new do
|
64
|
+
Thread.current.report_on_exception = false
|
65
|
+
Thread.current["name"] = "Traverse into"
|
66
|
+
error = false
|
67
|
+
begin
|
68
|
+
self.traverse(obj, callback: callback, **options, &block)
|
69
|
+
into.close if into.respond_to?(:close)
|
70
|
+
bar.remove if bar
|
71
|
+
rescue Exception
|
72
|
+
into.abort($!) if into.respond_to?(:abort)
|
73
|
+
bar.remove($!) if bar
|
74
|
+
end
|
75
|
+
end
|
76
|
+
Thread.pass until into_thread
|
77
|
+
return into
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
begin
|
82
|
+
case obj
|
83
|
+
when TSV
|
84
|
+
obj.traverse options[:key_field], options[:fields], unnamed: unnamed, **options do |k,v|
|
85
|
+
res = block.call(k, v)
|
86
|
+
callback.call res if callback
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
when Array
|
90
|
+
obj.each do |line|
|
91
|
+
res = block.call(line)
|
92
|
+
callback.call res if callback
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
when String
|
96
|
+
f = Open.open(obj)
|
97
|
+
self.traverse(f, cpus: cpus, callback: callback, **options, &block)
|
98
|
+
when Step
|
99
|
+
raise obj.exception if obj.error?
|
100
|
+
self.traverse(obj.stream, cpus: cpus, callback: callback, **options, &block)
|
101
|
+
when IO
|
102
|
+
TSV.parse obj, **options do |k,v|
|
103
|
+
res = block.call k, v
|
104
|
+
callback.call res if callback
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
else
|
108
|
+
TSV.parse obj, **options do |k,v|
|
109
|
+
res = block.call k, v
|
110
|
+
callback.call res if callback
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
bar.remove if bar
|
115
|
+
rescue
|
116
|
+
bar.abort($!) if bar
|
117
|
+
raise $!
|
118
|
+
end
|
119
|
+
|
120
|
+
into
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
module TSV
|
125
|
+
def self.traverse(*args, **kwargs, &block)
|
126
|
+
Open.traverse(*args, **kwargs, &block)
|
127
|
+
end
|
128
|
+
end
|