htslib 0.4.2 → 0.5.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 +79 -49
  3. data/TUTORIAL.md +9 -20
  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 +1108 -0
  8. data/ext/htslib_native/native_bcf.c +369 -0
  9. data/ext/htslib_native/native_faidx.c +155 -0
  10. data/ext/htslib_native/native_tabix.c +246 -0
  11. data/lib/hts/bam/auxi.rb +87 -342
  12. data/lib/hts/bam/base_mod.rb +37 -73
  13. data/lib/hts/bam/cigar.rb +9 -55
  14. data/lib/hts/bam/flag.rb +19 -27
  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 +102 -42
  20. data/lib/hts/bcf/errors.rb +1 -0
  21. data/lib/hts/bcf/format.rb +278 -379
  22. data/lib/hts/bcf/header.rb +38 -122
  23. data/lib/hts/bcf/header_record.rb +9 -35
  24. data/lib/hts/bcf/info.rb +72 -320
  25. data/lib/hts/bcf/record.rb +61 -102
  26. data/lib/hts/bcf.rb +149 -323
  27. data/lib/hts/faidx.rb +28 -87
  28. data/lib/hts/hts.rb +6 -94
  29. data/lib/hts/native.rb +13 -0
  30. data/lib/hts/tabix.rb +93 -48
  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,14 +10,22 @@ 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
@@ -29,16 +37,9 @@ module HTS
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,96 @@ 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
+ return if mode.start_with?("w")
68
71
 
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)
72
+ @read_header = Header.new(@native.read_header)
74
73
  @header = subset ? @read_header.subset(subset) : @read_header
75
- build_index(index) if build_index
76
- @idx = load_index(index)
74
+ if build_index
75
+ build_index(index)
76
+ load_index(index)
77
+ elsif index
78
+ load_index(index)
79
+ end
77
80
  @start_position = tell
81
+ rescue Errno::ENOENT
82
+ raise OpenError, "Failed to open #{@file_name}"
78
83
  end
79
84
 
80
85
  def build_index(index_name = nil, min_shift: 14, verbose: true)
81
86
  check_closed
82
-
83
87
  self.class.build_index(@file_name, index_name, min_shift, @nthreads || 0, verbose)
84
- self # for method chaining
88
+ @index_name = index_name
89
+ @index_load_attempted = false
90
+ self
85
91
  end
86
92
 
87
93
  def load_index(index_name = nil)
88
94
  check_closed
89
-
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
95
+ @index_name = index_name
96
+ @index_load_attempted = true
97
+ @native.load_index(index_name)
104
98
  end
105
99
 
106
100
  def index_loaded?
107
101
  check_closed
102
+ @native.index_loaded?
103
+ end
104
+
105
+ def close = @native&.close
106
+ def closed? = @native.nil? || @native.closed?
107
+ def file_format = @native.file_format
108
+ def file_format_version = @native.file_format_version
109
+ def seek(offset) = @native.seek(offset)
110
+ def tell = @native.tell
108
111
 
109
- !@idx.null?
112
+ def rewind
113
+ raise "Cannot rewind: no start position" unless @start_position
114
+
115
+ result = seek(@start_position)
116
+ raise "Failed to rewind: #{result}" if result.negative?
117
+
118
+ tell
110
119
  end
111
120
 
112
- 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
121
+ def set_threads(count = nil)
122
+ if count.nil?
123
+ require "etc"
124
+ count = [Etc.nprocessors - 1, 1].max
120
125
  end
121
- @idx = nil
122
- super
126
+ raise TypeError unless count.is_a?(Integer)
127
+ raise ArgumentError, "Number of threads must be positive" if count < 1
128
+ raise "Failed to set number of threads: #{count}" if @native.set_threads(count).negative?
129
+
130
+ @nthreads = count
131
+ self
123
132
  end
124
133
 
125
134
  def write_header(header)
126
135
  check_closed
127
-
128
136
  @header = header.dup
129
- LibHTS.bcf_hdr_write(@hts_file, header)
137
+ result = @native.write_header(header.__send__(:native_handle))
138
+ raise HeaderError, "Failed to write BCF header" if result.negative?
139
+
140
+ result
130
141
  end
131
142
 
132
143
  def header=(header)
@@ -135,32 +146,23 @@ module HTS
135
146
 
136
147
  def write(record)
137
148
  check_closed
149
+ result = @native.write(header.__send__(:native_handle), record.__send__(:native_handle))
150
+ raise "Failed to write record" if result.negative?
138
151
 
139
- # record = record.dup
140
- r = LibHTS.bcf_write(@hts_file, header, record)
141
- raise "Failed to write record" if r < 0
142
- end
143
-
144
- def <<(var)
145
- write(var)
152
+ result
146
153
  end
154
+ alias << write
147
155
 
148
156
  def nsamples
149
157
  check_closed
150
-
151
158
  header.nsamples
152
159
  end
153
160
 
154
161
  def samples
155
162
  check_closed
156
-
157
163
  header.samples
158
164
  end
159
165
 
160
- # @!macro [attach] define_getter
161
- # @method $1
162
- # Get $1 array
163
- # @return [Array] the $1 array
164
166
  define_getter :chrom
165
167
  define_getter :pos
166
168
  define_getter :endpos
@@ -172,35 +174,24 @@ module HTS
172
174
 
173
175
  def info(key = nil)
174
176
  check_closed
175
- position = tell
176
177
  raise NotImplementedError unless key
177
178
 
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
179
+ position = tell
180
+ map { |record| record.info(key) }.tap { seek(position) if position }
185
181
  end
182
+ alias info_array info
186
183
 
187
184
  def format(key = nil)
188
185
  check_closed
189
- position = tell
190
186
  raise NotImplementedError unless key
191
187
 
192
- ary = map { |r| r.format(key) }
193
-
194
- # ary = each_copy.map { |r| r.format }
195
- # ary = map { |r| r.format.clone }
196
-
197
- seek(position)
198
- ary
188
+ position = tell
189
+ map { |record| record.format(key) }.tap { seek(position) if position }
199
190
  end
191
+ alias format_array format
192
+
193
+ def collect_records = each(copy: true).to_a
200
194
 
201
- # @!macro [attach] define_iterator
202
- # @method each_$1
203
- # Get $1 iterator
204
195
  define_iterator :chrom
205
196
  define_iterator :pos
206
197
  define_iterator :endpos
@@ -211,47 +202,40 @@ module HTS
211
202
  define_iterator :filter
212
203
 
213
204
  def each_info(key)
214
- check_closed
215
205
  return to_enum(__method__, key) unless block_given?
216
206
 
217
- each do |r|
218
- yield r.info(key)
219
- end
207
+ each { |record| yield record.info(key) }
208
+ self
220
209
  end
221
210
 
222
211
  def each_format(key)
223
- check_closed
224
212
  return to_enum(__method__, key) unless block_given?
225
213
 
226
- each do |r|
227
- yield r.format(key)
228
- end
214
+ each { |record| yield record.format(key) }
215
+ self
229
216
  end
230
217
 
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
218
+ def each(copy: false, &block) = copy ? each_record_copy(&block) : each_record_reuse(&block)
238
219
 
239
220
  def query(region, beg = nil, end_ = nil, copy: false, &block)
240
221
  check_closed
241
-
242
- raise MissingIndexError, "Index file is required to call the query method for #{@file_name}" unless index_loaded?
222
+ unless ensure_index_loaded
223
+ raise MissingIndexError, "Index file is required to call the query method for #{@file_name}"
224
+ end
243
225
 
244
226
  case region
245
227
  when Array
246
228
  raise ArgumentError, "beg and end must not be specified when region is an Array" unless beg.nil? && end_.nil?
229
+ return to_enum(__method__, region, copy:) unless block
247
230
 
248
- query_regions(region, copy:, &block)
231
+ region.each { |item| query(item, copy:, &block) }
232
+ self
249
233
  else
250
234
  if beg && end_
251
- tid = header.name2id(region)
252
- queryi(tid, beg, end_, copy:, &block)
235
+ iterate_query(@native.query_interval(read_header_native, header.name2id(region), beg, end_), copy, region,
236
+ &block)
253
237
  elsif beg.nil? && end_.nil?
254
- querys(region, copy:, &block)
238
+ iterate_query(@native.query_region(read_header_native, region), copy, region, &block)
255
239
  else
256
240
  raise ArgumentError, "beg and end must be specified together"
257
241
  end
@@ -260,241 +244,83 @@ module HTS
260
244
 
261
245
  private
262
246
 
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?
247
+ def ensure_index_loaded
248
+ return true if index_loaded?
249
+ return false if @index_load_attempted
289
250
 
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
251
+ load_index(@index_name)
309
252
  end
310
253
 
311
- def query_regions_reuse(regions, &block)
312
- return to_enum(__method__, regions) unless block_given?
254
+ def native_handle = @native
255
+ def read_header_native = (@read_header || @header).__send__(:native_handle)
313
256
 
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?
351
-
352
- return querys_copy_vcf(region, &block) if tabix_index?
353
-
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
359
- end
360
-
361
- def query_regions_copy(regions, &block)
362
- return to_enum(__method__, regions) unless block_given?
363
-
364
- regions.each do |region|
365
- querys_copy(region, &block)
366
- end
367
- self
368
- end
257
+ def each_record_reuse
258
+ return to_enum(__method__) unless block_given?
369
259
 
370
- def query_copy_yield(qiter)
371
- bcf1 = LibHTS.bcf_init
372
- record = Record.new(header, bcf1)
260
+ record = Record.new(header)
261
+ prepare_record(record)
373
262
  loop do
374
- slen = LibHTS.bcf_itr_next(@hts_file, qiter, bcf1)
375
- break if slen == -1
376
- raise if slen < -1
263
+ result = @native.read(read_header_native, record.__send__(:native_handle))
264
+ break if result == -1
265
+ raise QueryError, "Failed to read variant record from #{@file_name}" if result < -1
377
266
 
378
267
  apply_subset!(record)
379
- yield record.dup
268
+ yield record
380
269
  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
270
  self
403
271
  end
404
272
 
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?
273
+ def each_record_copy
274
+ return to_enum(__method__) unless block_given?
427
275
 
428
- query_copy_yield_vcf(qiter, &block)
276
+ each_record_reuse { |record| yield record.dup }
429
277
  self
430
278
  end
431
279
 
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?
280
+ def iterate_query(iterator, copy, region)
281
+ return to_enum(__method__, iterator, copy, region) unless block_given?
282
+ raise QueryError, "Failed to query region #{region.inspect} in #{@file_name}" unless iterator
435
283
 
436
- query_copy_yield_vcf(qiter, &block)
437
- self
438
- end
439
-
440
- def query_copy_yield_vcf(qiter)
441
- line = LibHTS::KString.new
284
+ record = Record.new(header)
285
+ prepare_record(record)
442
286
  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
287
+ loop do
288
+ result = iterator.next(record.__send__(:native_handle))
289
+ break if result == -1
290
+ raise QueryError, "Failed to parse/query record in #{@file_name}" if result < -1
447
291
 
448
- record = Record.new(header, bcf1)
449
292
  apply_subset!(record)
450
- yield record
293
+ yield(copy ? record.dup : record)
451
294
  end
452
- raise if slen < -1
453
295
  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
296
+ iterator.close
469
297
  end
470
298
  self
471
299
  end
472
300
 
473
- def each_record_copy
474
- check_closed
301
+ def apply_subset!(record)
302
+ return unless header&.subset?
475
303
 
476
- return to_enum(__method__) unless block_given?
304
+ map = header.__send__(:subset_imap)
305
+ result = record.__send__(:native_handle).subset(header.__send__(:native_handle), map)
306
+ return unless result.negative?
477
307
 
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
308
+ raise SubsetError,
309
+ "Failed to subset samples #{header.subset_samples.inspect} while reading #{@file_name}"
485
310
  end
486
311
 
487
- def read_header
488
- @read_header || header
312
+ def resolve_max_unpack(level)
313
+ case level
314
+ when :all, :format then 15
315
+ when :site_only, :info then 4
316
+ when :filter then 2
317
+ when :string, :alleles then 1
318
+ else raise ArgumentError, "unknown unpack level: #{level.inspect}"
319
+ end
489
320
  end
490
321
 
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}"
322
+ def prepare_record(record)
323
+ record.__send__(:native_handle).max_unpack = @max_unpack
498
324
  end
499
325
  end
500
326
  end