htslib 0.4.2 → 0.6.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +83 -51
  3. data/TUTORIAL.md +11 -22
  4. data/ext/htslib_native/extconf.rb +41 -0
  5. data/ext/htslib_native/htslib_native.h +11 -0
  6. data/ext/htslib_native/htslib_native_ext.c +48 -0
  7. data/ext/htslib_native/native_bam.c +1167 -0
  8. data/ext/htslib_native/native_bcf.c +447 -0
  9. data/ext/htslib_native/native_faidx.c +164 -0
  10. data/ext/htslib_native/native_tabix.c +255 -0
  11. data/lib/hts/bam/auxi.rb +87 -342
  12. data/lib/hts/bam/base_mod.rb +38 -73
  13. data/lib/hts/bam/cigar.rb +9 -55
  14. data/lib/hts/bam/flag.rb +24 -30
  15. data/lib/hts/bam/header.rb +29 -56
  16. data/lib/hts/bam/mpileup.rb +158 -132
  17. data/lib/hts/bam/pileup.rb +120 -145
  18. data/lib/hts/bam/record.rb +233 -270
  19. data/lib/hts/bam.rb +154 -45
  20. data/lib/hts/bcf/errors.rb +4 -0
  21. data/lib/hts/bcf/format.rb +283 -377
  22. data/lib/hts/bcf/header.rb +40 -122
  23. data/lib/hts/bcf/header_record.rb +9 -35
  24. data/lib/hts/bcf/info.rb +73 -320
  25. data/lib/hts/bcf/record.rb +73 -101
  26. data/lib/hts/bcf.rb +178 -321
  27. data/lib/hts/faidx.rb +30 -87
  28. data/lib/hts/hts.rb +6 -94
  29. data/lib/hts/native.rb +13 -0
  30. data/lib/hts/tabix.rb +109 -51
  31. data/lib/hts/version.rb +1 -1
  32. data/lib/htslib.rb +6 -52
  33. metadata +13 -64
  34. data/lib/hts/ffi_ext/README.md +0 -8
  35. data/lib/hts/ffi_ext/pointer.rb +0 -18
  36. data/lib/hts/ffi_ext/struct.rb +0 -45
  37. data/lib/hts/libhts/bgzf.rb +0 -199
  38. data/lib/hts/libhts/constants.rb +0 -658
  39. data/lib/hts/libhts/cram.rb +0 -471
  40. data/lib/hts/libhts/fai.rb +0 -146
  41. data/lib/hts/libhts/hfile.rb +0 -121
  42. data/lib/hts/libhts/hts.rb +0 -477
  43. data/lib/hts/libhts/kfunc.rb +0 -38
  44. data/lib/hts/libhts/sam.rb +0 -760
  45. data/lib/hts/libhts/sam_funcs.rb +0 -155
  46. data/lib/hts/libhts/tbx.rb +0 -94
  47. data/lib/hts/libhts/tbx_funcs.rb +0 -36
  48. data/lib/hts/libhts/thread_pool.rb +0 -139
  49. data/lib/hts/libhts/vcf.rb +0 -567
  50. data/lib/hts/libhts/vcf_funcs.rb +0 -366
  51. data/lib/hts/libhts.rb +0 -47
data/lib/hts/bcf.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../htslib"
4
-
5
4
  require_relative "hts"
5
+ require_relative "native"
6
6
  require_relative "bcf/errors"
7
7
  require_relative "bcf/header"
8
8
  require_relative "bcf/info"
@@ -10,35 +10,36 @@ require_relative "bcf/format"
10
10
  require_relative "bcf/record"
11
11
 
12
12
  module HTS
13
- # A class for working with VCF, BCF files.
14
13
  class Bcf < Hts
15
14
  include Enumerable
16
15
 
17
- attr_reader :file_name, :index_name, :mode, :header, :nthreads
16
+ def self.filter_records(records, rid: nil, beg: nil, end_: nil, min_qual: nil, filter_id: nil)
17
+ Array(records).select do |record|
18
+ (rid.nil? || record.rid == Integer(rid)) &&
19
+ (beg.nil? || record.endpos > Integer(beg)) && (end_.nil? || record.pos < Integer(end_)) &&
20
+ (min_qual.nil? || (!record.qual.nan? && record.qual >= Float(min_qual))) &&
21
+ (filter_id.nil? || record.filter_id?(filter_id))
22
+ end
23
+ end
18
24
 
19
- def self.open(*args, **kw)
20
- file = new(*args, **kw) # do not yield
25
+ attr_reader :file_name, :index_name, :mode, :header, :nthreads, :unpack
26
+
27
+ def self.open(*args, **keywords)
28
+ file = new(*args, **keywords)
21
29
  return file unless block_given?
22
30
 
23
31
  begin
24
- yield file
32
+ result = yield file
25
33
  ensure
26
34
  file.close
27
35
  end
28
- file
36
+ result
29
37
  end
30
38
 
31
39
  def self.build_index(file_name, index_name = nil, min_shift = 14, threads = 0, verbose = true)
32
- if verbose
33
- if index_name
34
- warn "Create index for #{file_name} to #{index_name}"
35
- else
36
- warn "Create index for #{file_name}"
37
- end
38
- end
39
-
40
- case LibHTS.bcf_index_build3(file_name, index_name, min_shift, threads)
41
- when 0 # successful
40
+ warn(index_name ? "Create index for #{file_name} to #{index_name}" : "Create index for #{file_name}") if verbose
41
+ case Native::BcfFileHandle.build_index(file_name, index_name, min_shift, threads)
42
+ when 0 then nil
42
43
  when -1 then raise IndexError, "Indexing failed for #{file_name}"
43
44
  when -2 then raise IndexError, "Opening #{file_name} failed while building the index"
44
45
  when -3 then raise IndexError, "#{file_name} is not in an indexable format"
@@ -47,86 +48,117 @@ module HTS
47
48
  end
48
49
  end
49
50
 
50
- def initialize(file_name, mode = "r", index: nil, threads: nil,
51
- build_index: false, subset: nil)
52
- if block_given?
53
- message = "HTS::Bcf.new() does not take block; Please use HTS::Bcf.open() instead"
54
- raise message
55
- end
56
-
57
- # NOTE: Do not check for the existence of local files, since file_names may be remote URIs.
51
+ def initialize(file_name, mode = "r", index: nil, threads: nil, build_index: false,
52
+ subset: nil, samples: nil, unpack: :all)
53
+ raise "HTS::Bcf.new() does not take block; Please use HTS::Bcf.open() instead" if block_given?
54
+ raise ArgumentError, "specify either samples: or subset:, not both" if samples && subset
58
55
 
59
- @file_name = file_name
56
+ subset = samples unless samples.nil?
57
+ @unpack = unpack.to_sym
58
+ @max_unpack = resolve_max_unpack(@unpack)
59
+ @file_name = file_name
60
60
  @index_name = index
61
- @mode = mode
62
- @nthreads = threads
63
- @hts_file = LibHTS.hts_open(@file_name, mode)
64
-
65
- raise OpenError, "Failed to open #{@file_name}" if @hts_file.null?
66
-
61
+ @mode = mode
62
+ @nthreads = threads
63
+ @index_load_attempted = false
64
+ @native = Native::BcfFileHandle.open(@file_name, mode)
67
65
  set_threads(threads) if threads
66
+ if subset && mode.start_with?("w")
67
+ raise SubsetError,
68
+ "Sample subsetting is only available when reading BCF/VCF files"
69
+ end
70
+ if writing?
71
+ @auto_index_on_close = build_index
72
+ @index_name_on_close = index
73
+ return
74
+ end
68
75
 
69
- raise SubsetError, "Sample subsetting is only available when reading BCF/VCF files" if subset && @mode[0] == "w"
70
-
71
- return if @mode[0] == "w"
72
-
73
- @read_header = Bcf::Header.new(@hts_file)
76
+ @read_header = Header.new(@native.read_header)
74
77
  @header = subset ? @read_header.subset(subset) : @read_header
75
- build_index(index) if build_index
76
- @idx = load_index(index)
78
+ if build_index
79
+ build_index(index)
80
+ load_index(index)
81
+ elsif index
82
+ load_index(index)
83
+ end
77
84
  @start_position = tell
85
+ rescue Errno::ENOENT
86
+ raise OpenError, "Failed to open #{@file_name}"
78
87
  end
79
88
 
80
89
  def build_index(index_name = nil, min_shift: 14, verbose: true)
81
90
  check_closed
82
-
83
91
  self.class.build_index(@file_name, index_name, min_shift, @nthreads || 0, verbose)
84
- self # for method chaining
92
+ @index_name = index_name
93
+ @index_load_attempted = false
94
+ self
85
95
  end
86
96
 
87
97
  def load_index(index_name = nil)
88
- check_closed
98
+ return self if try_load_index(index_name)
89
99
 
90
- if file_format == "vcf"
91
- @index_format = :tabix
92
- if index_name
93
- LibHTS.tbx_index_load2(@file_name, index_name)
94
- else
95
- LibHTS.tbx_index_load3(@file_name, nil, 2)
96
- end
97
- elsif index_name
98
- @index_format = :bcf
99
- LibHTS.bcf_index_load2(@file_name, index_name)
100
- else
101
- @index_format = :bcf
102
- LibHTS.bcf_index_load3(@file_name, nil, 2)
103
- end
100
+ raise MissingIndexError, "Failed to load index #{index_name || "for #{@file_name}"}"
104
101
  end
105
102
 
106
- def index_loaded?
103
+ def try_load_index(index_name = nil)
107
104
  check_closed
105
+ @index_name = index_name
106
+ @index_load_attempted = true
107
+ @native.load_index(index_name)
108
+ end
108
109
 
109
- !@idx.null?
110
+ def index_loaded?
111
+ check_closed
112
+ @native.index_loaded?
110
113
  end
111
114
 
112
115
  def close
113
- if @idx && !@idx.null?
114
- case @index_format
115
- when :bcf
116
- LibHTS.hts_idx_destroy(@idx)
117
- when :tabix
118
- @idx.close
119
- end
116
+ was_closed = closed?
117
+ result = @native&.close
118
+ raise WriteError, "Failed to close #{@file_name}: buffered output may be incomplete" if writing? && result&.negative?
119
+
120
+ if writing? && @auto_index_on_close && !was_closed
121
+ @auto_index_on_close = false
122
+ self.class.build_index(@file_name, @index_name_on_close, 14, @nthreads || 0, false)
120
123
  end
121
- @idx = nil
122
- super
124
+
125
+ nil
126
+ end
127
+ def closed? = @native.nil? || @native.closed?
128
+ def file_format = @native.file_format
129
+ def file_format_version = @native.file_format_version
130
+ def seek(offset) = @native.seek(offset)
131
+ def tell = @native.tell
132
+
133
+ def rewind
134
+ raise "Cannot rewind: no start position" unless @start_position
135
+
136
+ result = seek(@start_position)
137
+ raise "Failed to rewind: #{result}" if result.negative?
138
+
139
+ tell
140
+ end
141
+
142
+ def set_threads(count = nil)
143
+ if count.nil?
144
+ require "etc"
145
+ count = [Etc.nprocessors - 1, 1].max
146
+ end
147
+ raise TypeError unless count.is_a?(Integer)
148
+ raise ArgumentError, "Number of threads must be positive" if count < 1
149
+ raise "Failed to set number of threads: #{count}" if @native.set_threads(count).negative?
150
+
151
+ @nthreads = count
152
+ self
123
153
  end
124
154
 
125
155
  def write_header(header)
126
156
  check_closed
127
-
128
157
  @header = header.dup
129
- LibHTS.bcf_hdr_write(@hts_file, header)
158
+ result = @native.write_header(header.__send__(:native_handle))
159
+ raise HeaderError, "Failed to write BCF header" if result.negative?
160
+
161
+ nil
130
162
  end
131
163
 
132
164
  def header=(header)
@@ -135,32 +167,27 @@ module HTS
135
167
 
136
168
  def write(record)
137
169
  check_closed
170
+ result = @native.write(header.__send__(:native_handle), record.__send__(:native_handle))
171
+ raise "Failed to write record" if result.negative?
138
172
 
139
- # record = record.dup
140
- r = LibHTS.bcf_write(@hts_file, header, record)
141
- raise "Failed to write record" if r < 0
173
+ nil
142
174
  end
143
175
 
144
- def <<(var)
145
- write(var)
176
+ def <<(record)
177
+ write(record)
178
+ self
146
179
  end
147
180
 
148
181
  def nsamples
149
182
  check_closed
150
-
151
183
  header.nsamples
152
184
  end
153
185
 
154
186
  def samples
155
187
  check_closed
156
-
157
188
  header.samples
158
189
  end
159
190
 
160
- # @!macro [attach] define_getter
161
- # @method $1
162
- # Get $1 array
163
- # @return [Array] the $1 array
164
191
  define_getter :chrom
165
192
  define_getter :pos
166
193
  define_getter :endpos
@@ -172,35 +199,28 @@ module HTS
172
199
 
173
200
  def info(key = nil)
174
201
  check_closed
175
- position = tell
176
202
  raise NotImplementedError unless key
177
203
 
178
- ary = map { |r| r.info(key) }
179
-
180
- # ary = each_copy.map { |r| r.info }
181
- # ary = map { |r| r.info.clone }
182
-
183
- seek(position)
184
- ary
204
+ position = tell
205
+ map { |record| record.info(key) }.tap { seek(position) if position }
185
206
  end
207
+ alias info_array info
186
208
 
187
209
  def format(key = nil)
188
210
  check_closed
189
- position = tell
190
211
  raise NotImplementedError unless key
191
212
 
192
- ary = map { |r| r.format(key) }
213
+ position = tell
214
+ map { |record| record.format(key) }.tap { seek(position) if position }
215
+ end
216
+ alias format_array format
193
217
 
194
- # ary = each_copy.map { |r| r.format }
195
- # ary = map { |r| r.format.clone }
218
+ def collect_records = each(copy: true).to_a
196
219
 
197
- seek(position)
198
- ary
199
- end
220
+ # Materialize independent, owning records rather than retaining the
221
+ # reused Record yielded by the allocation-conscious default iterator.
222
+ def to_a = collect_records
200
223
 
201
- # @!macro [attach] define_iterator
202
- # @method each_$1
203
- # Get $1 iterator
204
224
  define_iterator :chrom
205
225
  define_iterator :pos
206
226
  define_iterator :endpos
@@ -211,47 +231,40 @@ module HTS
211
231
  define_iterator :filter
212
232
 
213
233
  def each_info(key)
214
- check_closed
215
234
  return to_enum(__method__, key) unless block_given?
216
235
 
217
- each do |r|
218
- yield r.info(key)
219
- end
236
+ each { |record| yield record.info(key) }
237
+ self
220
238
  end
221
239
 
222
240
  def each_format(key)
223
- check_closed
224
241
  return to_enum(__method__, key) unless block_given?
225
242
 
226
- each do |r|
227
- yield r.format(key)
228
- end
243
+ each { |record| yield record.format(key) }
244
+ self
229
245
  end
230
246
 
231
- def each(copy: false, &block)
232
- if copy
233
- each_record_copy(&block)
234
- else
235
- each_record_reuse(&block)
236
- end
237
- end
247
+ def each(copy: false, &block) = copy ? each_record_copy(&block) : each_record_reuse(&block)
238
248
 
239
249
  def query(region, beg = nil, end_ = nil, copy: false, &block)
240
250
  check_closed
241
-
242
- raise MissingIndexError, "Index file is required to call the query method for #{@file_name}" unless index_loaded?
251
+ unless ensure_index_loaded
252
+ raise MissingIndexError, "Index file is required to call the query method for #{@file_name}"
253
+ end
243
254
 
244
255
  case region
245
256
  when Array
246
257
  raise ArgumentError, "beg and end must not be specified when region is an Array" unless beg.nil? && end_.nil?
258
+ return to_enum(__method__, region, copy:) unless block
247
259
 
248
- query_regions(region, copy:, &block)
260
+ region.each { |item| query(item, copy:, &block) }
261
+ self
249
262
  else
250
263
  if beg && end_
251
- tid = header.name2id(region)
252
- queryi(tid, beg, end_, copy:, &block)
264
+ iterate_query(@native.query_interval(read_header_native, header.name2id(region), beg, end_), copy, region,
265
+ &block)
253
266
  elsif beg.nil? && end_.nil?
254
- querys(region, copy:, &block)
267
+ iterate_query(@native.query_region(read_header_native, region), copy, region, &block)
255
268
  else
256
269
  raise ArgumentError, "beg and end must be specified together"
257
270
  end
@@ -260,241 +273,85 @@ module HTS
260
273
 
261
274
  private
262
275
 
263
- def queryi(tid, beg, end_, copy: false, &block)
264
- if copy
265
- queryi_copy(tid, beg, end_, &block)
266
- else
267
- queryi_reuse(tid, beg, end_, &block)
268
- end
269
- end
270
-
271
- def querys(region, copy: false, &block)
272
- if copy
273
- querys_copy(region, &block)
274
- else
275
- querys_reuse(region, &block)
276
- end
277
- end
278
-
279
- def query_regions(regions, copy: false, &block)
280
- if copy
281
- query_regions_copy(regions, &block)
282
- else
283
- query_regions_reuse(regions, &block)
284
- end
285
- end
286
-
287
- def queryi_reuse(tid, beg, end_, &block)
288
- return to_enum(__method__, tid, beg, end_) unless block_given?
289
-
290
- return queryi_reuse_vcf(tid, beg, end_, &block) if tabix_index?
291
-
292
- qiter = LibHTS.bcf_itr_queryi(@idx, tid, beg, end_)
293
- raise QueryError, "Failed to query region #{tid}:#{beg}-#{end_} in #{@file_name}" if qiter.null?
294
-
295
- query_reuse_yield(qiter, &block)
296
- self
297
- end
298
-
299
- def querys_reuse(region, &block)
300
- return to_enum(__method__, region) unless block_given?
301
-
302
- return querys_reuse_vcf(region, &block) if tabix_index?
303
-
304
- qiter = LibHTS.bcf_itr_querys(@idx, read_header, region)
305
- raise QueryError, "Failed to query region #{region.inspect} in #{@file_name}" if qiter.null?
306
-
307
- query_reuse_yield(qiter, &block)
308
- self
309
- end
310
-
311
- def query_regions_reuse(regions, &block)
312
- return to_enum(__method__, regions) unless block_given?
313
-
314
- regions.each do |region|
315
- querys_reuse(region, &block)
316
- end
317
- self
318
- end
319
-
320
- def query_reuse_yield(qiter)
321
- bcf1 = LibHTS.bcf_init
322
- record = Record.new(header, bcf1)
323
- begin
324
- loop do
325
- slen = LibHTS.bcf_itr_next(@hts_file, qiter, bcf1)
326
- break if slen == -1
327
- raise if slen < -1
328
-
329
- apply_subset!(record)
330
- yield record
331
- end
332
- ensure
333
- LibHTS.bcf_itr_destroy(qiter)
334
- end
335
- end
336
-
337
- def queryi_copy(tid, beg, end_, &block)
338
- return to_enum(__method__, tid, beg, end_) unless block_given?
339
-
340
- return queryi_copy_vcf(tid, beg, end_, &block) if tabix_index?
341
-
342
- qiter = LibHTS.bcf_itr_queryi(@idx, tid, beg, end_)
343
- raise QueryError, "Failed to query region #{tid}:#{beg}-#{end_} in #{@file_name}" if qiter.null?
344
-
345
- query_copy_yield(qiter, &block)
346
- self
347
- end
348
-
349
- def querys_copy(region, &block)
350
- return to_enum(__method__, region) unless block_given?
276
+ def writing? = @mode&.start_with?("w", "a")
351
277
 
352
- return querys_copy_vcf(region, &block) if tabix_index?
278
+ def ensure_index_loaded
279
+ return true if index_loaded?
280
+ return false if @index_load_attempted
353
281
 
354
- qiter = LibHTS.bcf_itr_querys(@idx, read_header, region)
355
- raise QueryError, "Failed to query region #{region.inspect} in #{@file_name}" if qiter.null?
356
-
357
- query_copy_yield(qiter, &block)
358
- self
282
+ load_index(@index_name)
359
283
  end
360
284
 
361
- def query_regions_copy(regions, &block)
362
- return to_enum(__method__, regions) unless block_given?
285
+ def native_handle = @native
286
+ def read_header_native = (@read_header || @header).__send__(:native_handle)
363
287
 
364
- regions.each do |region|
365
- querys_copy(region, &block)
366
- end
367
- self
368
- end
288
+ def each_record_reuse
289
+ return to_enum(__method__) unless block_given?
369
290
 
370
- def query_copy_yield(qiter)
371
- bcf1 = LibHTS.bcf_init
372
- record = Record.new(header, bcf1)
291
+ record = Record.new(header)
292
+ prepare_record(record)
373
293
  loop do
374
- slen = LibHTS.bcf_itr_next(@hts_file, qiter, bcf1)
375
- break if slen == -1
376
- raise if slen < -1
294
+ result = @native.read(read_header_native, record.__send__(:native_handle))
295
+ break if result == -1
296
+ raise QueryError, "Failed to read variant record from #{@file_name}" if result < -1
377
297
 
378
298
  apply_subset!(record)
379
- yield record.dup
299
+ yield record
380
300
  end
381
- ensure
382
- LibHTS.bcf_itr_destroy(qiter)
383
- end
384
-
385
- def tabix_index?
386
- @index_format == :tabix
387
- end
388
-
389
- def queryi_reuse_vcf(tid, beg, end_, &block)
390
- qiter = LibHTS.tbx_itr_queryi(@idx, tid, beg, end_)
391
- raise QueryError, "Failed to query region #{tid}:#{beg}-#{end_} in #{@file_name}" if qiter.null?
392
-
393
- query_reuse_yield_vcf(qiter, &block)
394
- self
395
- end
396
-
397
- def querys_reuse_vcf(region, &block)
398
- qiter = LibHTS.tbx_itr_querys(@idx, region)
399
- raise QueryError, "Failed to query region #{region.inspect} in #{@file_name}" if qiter.null?
400
-
401
- query_reuse_yield_vcf(qiter, &block)
402
301
  self
403
302
  end
404
303
 
405
- def query_reuse_yield_vcf(qiter)
406
- line = LibHTS::KString.new
407
- bcf1 = LibHTS.bcf_init
408
- record = Record.new(header, bcf1)
409
- begin
410
- while (slen = LibHTS.tbx_itr_next(@hts_file, @idx, qiter, line)) >= 0
411
- raise QueryError, "Failed to parse VCF record in #{@file_name}" if LibHTS.vcf_parse(line, read_header,
412
- bcf1) < 0
413
-
414
- apply_subset!(record)
415
- yield record
416
- end
417
- raise if slen < -1
418
- ensure
419
- line.free_buffer
420
- LibHTS.hts_itr_destroy(qiter)
421
- end
422
- end
423
-
424
- def queryi_copy_vcf(tid, beg, end_, &block)
425
- qiter = LibHTS.tbx_itr_queryi(@idx, tid, beg, end_)
426
- raise QueryError, "Failed to query region #{tid}:#{beg}-#{end_} in #{@file_name}" if qiter.null?
304
+ def each_record_copy
305
+ return to_enum(__method__) unless block_given?
427
306
 
428
- query_copy_yield_vcf(qiter, &block)
307
+ each_record_reuse { |record| yield record.dup }
429
308
  self
430
309
  end
431
310
 
432
- def querys_copy_vcf(region, &block)
433
- qiter = LibHTS.tbx_itr_querys(@idx, region)
434
- raise QueryError, "Failed to query region #{region.inspect} in #{@file_name}" if qiter.null?
311
+ def iterate_query(iterator, copy, region)
312
+ return to_enum(__method__, iterator, copy, region) unless block_given?
313
+ raise QueryError, "Failed to query region #{region.inspect} in #{@file_name}" unless iterator
435
314
 
436
- query_copy_yield_vcf(qiter, &block)
437
- self
438
- end
439
-
440
- def query_copy_yield_vcf(qiter)
441
- line = LibHTS::KString.new
315
+ record = Record.new(header)
316
+ prepare_record(record)
442
317
  begin
443
- while (slen = LibHTS.tbx_itr_next(@hts_file, @idx, qiter, line)) >= 0
444
- bcf1 = LibHTS.bcf_init
445
- raise QueryError, "Failed to parse VCF record in #{@file_name}" if LibHTS.vcf_parse(line, read_header,
446
- bcf1) < 0
318
+ loop do
319
+ result = iterator.next(record.__send__(:native_handle))
320
+ break if result == -1
321
+ raise QueryError, "Failed to parse/query record in #{@file_name}" if result < -1
447
322
 
448
- record = Record.new(header, bcf1)
449
323
  apply_subset!(record)
450
- yield record
324
+ yield(copy ? record.dup : record)
451
325
  end
452
- raise if slen < -1
453
326
  ensure
454
- line.free_buffer
455
- LibHTS.hts_itr_destroy(qiter)
456
- end
457
- end
458
-
459
- def each_record_reuse
460
- check_closed
461
-
462
- return to_enum(__method__) unless block_given?
463
-
464
- bcf1 = LibHTS.bcf_init
465
- record = Record.new(header, bcf1)
466
- while LibHTS.bcf_read(@hts_file, read_header, bcf1) != -1
467
- apply_subset!(record)
468
- yield record
327
+ iterator.close
469
328
  end
470
329
  self
471
330
  end
472
331
 
473
- def each_record_copy
474
- check_closed
332
+ def apply_subset!(record)
333
+ return unless header&.subset?
475
334
 
476
- return to_enum(__method__) unless block_given?
335
+ map = header.__send__(:subset_imap)
336
+ result = record.__send__(:native_handle).subset(header.__send__(:native_handle), map)
337
+ return unless result.negative?
477
338
 
478
- bcf1 = LibHTS.bcf_init
479
- record = Record.new(header, bcf1)
480
- while LibHTS.bcf_read(@hts_file, read_header, bcf1) != -1
481
- apply_subset!(record)
482
- yield record.dup
483
- end
484
- self
339
+ raise SubsetError,
340
+ "Failed to subset samples #{header.subset_samples.inspect} while reading #{@file_name}"
485
341
  end
486
342
 
487
- def read_header
488
- @read_header || header
343
+ def resolve_max_unpack(level)
344
+ case level
345
+ when :all, :format then 15
346
+ when :site_only, :info then 4
347
+ when :filter then 2
348
+ when :string, :alleles then 1
349
+ else raise ArgumentError, "unknown unpack level: #{level.inspect}"
350
+ end
489
351
  end
490
352
 
491
- def apply_subset!(record)
492
- return unless header.subset?
493
-
494
- rc = LibHTS.bcf_subset(header.struct, record.struct, header.subset_sample_count, header.subset_imap_pointer || ::FFI::Pointer::NULL)
495
- return if rc >= 0
496
-
497
- raise SubsetError, "Failed to subset samples #{header.subset_samples.inspect} while reading #{@file_name}"
353
+ def prepare_record(record)
354
+ record.__send__(:native_handle).max_unpack = @max_unpack
498
355
  end
499
356
  end
500
357
  end