htslib 0.4.1 → 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 -123
  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 -109
  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 -79
  47. data/lib/hts/libhts/tbx_funcs.rb +0 -32
  48. data/lib/hts/libhts/thread_pool.rb +0 -139
  49. data/lib/hts/libhts/vcf.rb +0 -557
  50. data/lib/hts/libhts/vcf_funcs.rb +0 -353
  51. data/lib/hts/libhts.rb +0 -47
data/lib/hts/bcf/info.rb CHANGED
@@ -2,385 +2,137 @@
2
2
 
3
3
  module HTS
4
4
  class Bcf < Hts
5
- # Info field
6
5
  class Info
7
- def initialize(record)
8
- @record = record
9
- end
10
-
11
- # @note Specify the type. If you don't specify a type, it will still work, but it will be slower.
12
- # @note: Why is this method named "get" instead of "fetch"?
13
- # This is for compatibility with the Crystal language
14
- # which provides methods like `get_int`, `get_float`, etc.
15
- # I think they are better than `fetch_int`` and `fetch_float`.
16
- def get(key, type = nil)
17
- n = FFI::MemoryPointer.new(:int)
18
- p1 = FFI::MemoryPointer.new(:pointer)
19
- p1.write_pointer(FFI::Pointer::NULL)
20
- h = @record.header.struct
21
- r = @record.struct
22
-
23
- info_values = proc do |typ, reader|
24
- ret = LibHTS.bcf_get_info_values(h, r, key, p1, n, typ)
25
- return nil if ret < 0 # return from method.
6
+ TYPE_CODES = {
7
+ flag: Native::BCF_HT_FLAG, bool: Native::BCF_HT_FLAG,
8
+ int: Native::BCF_HT_INT, int32: Native::BCF_HT_INT,
9
+ int64: Native::BCF_HT_LONG, long: Native::BCF_HT_LONG,
10
+ float: Native::BCF_HT_REAL, real: Native::BCF_HT_REAL,
11
+ string: Native::BCF_HT_STR, str: Native::BCF_HT_STR
12
+ }.freeze
26
13
 
27
- dst = p1.read_pointer
28
- begin
29
- reader.call(dst, n.read_int)
30
- ensure
31
- LibHTS.hts_free(dst) unless dst.null?
32
- p1.write_pointer(FFI::Pointer::NULL)
33
- end
34
- end
35
-
36
- actual_type = ht_type_to_sym(get_info_type(key))
37
- if type && actual_type && !info_type_compatible?(actual_type, type.to_sym)
38
- raise InfoTypeError, "Tag #{key} is not #{type_label(type)} INFO field"
39
- end
14
+ def initialize(record) = @record = record
40
15
 
41
- type ||= actual_type
42
- return nil if actual_type && !key?(key)
16
+ def get(key, type = nil)
17
+ schema = header.schema("INFO", key.to_s)
18
+ return nil unless schema
43
19
 
44
- case type&.to_sym
45
- when :int, :int32
46
- info_values.call(LibHTS::BCF_HT_INT, ->(dst, len) { dst.read_array_of_int32(len) })
47
- when :int64, :long
48
- info_values.call(LibHTS::BCF_HT_LONG, ->(dst, len) { dst.read_array_of_int64(len) })
49
- when :float, :real
50
- info_values.call(LibHTS::BCF_HT_REAL, ->(dst, len) { dst.read_array_of_float(len) })
51
- when :flag, :bool
52
- begin
53
- case ret = LibHTS.bcf_get_info_flag(h, r, key, p1, n)
54
- when 1 then true
55
- when 0 then false
56
- when -1 then nil
57
- else
58
- raise InfoReadError, "Unknown return value from bcf_get_info_flag: #{ret}"
59
- end
60
- ensure
61
- dst = p1.read_pointer
62
- LibHTS.hts_free(dst) unless dst.null?
63
- p1.write_pointer(FFI::Pointer::NULL)
64
- end
65
- when :string, :str
66
- info_values.call(LibHTS::BCF_HT_STR, ->(dst, _len) { dst.read_string })
20
+ actual_type = schema.first
21
+ requested = type&.to_sym
22
+ if requested && !type_compatible?(actual_type, requested)
23
+ raise InfoTypeError, "Tag #{key} is not #{type_label(requested)} INFO field"
67
24
  end
68
- end
69
-
70
- # For compatibility with HTS.cr.
71
- def get_int(key)
72
- get(key, :int)
73
- end
74
-
75
- # For compatibility with HTS.cr.
76
- def get_float(key)
77
- get(key, :float)
78
- end
79
-
80
- # For compatibility with HTS.cr.
81
- def get_int64(key)
82
- get(key, :int64)
83
- end
84
-
85
- # For compatibility with HTS.cr.
86
- def get_string(key)
87
- get(key, :string)
88
- end
89
25
 
90
- # For compatibility with HTS.cr.
91
- def get_flag(key)
92
- get(key, :flag)
26
+ code = TYPE_CODES.fetch(requested || actual_type)
27
+ native.info_get(header_native, key.to_s, code)
93
28
  end
94
29
 
95
- def [](key)
96
- get(key)
97
- end
30
+ def get_int(key) = get(key, :int)
31
+ def get_float(key) = get(key, :float)
32
+ def get_int64(key) = get(key, :int64)
33
+ def get_string(key) = get(key, :string)
34
+ def get_flag(key) = get(key, :flag)
35
+ def [](key) = get(key)
98
36
 
99
- # Set INFO field value with automatic type detection.
100
- # @param key [String] INFO tag name
101
- # @param value [Integer, Float, String, Array, true, false, nil] value to set
102
- # - Integer or Array<Integer> -> update_int
103
- # - Float or Array<Float,Integer> -> update_float
104
- # - String -> update_string
105
- # - true/false -> update_flag
106
- # - nil -> delete the INFO field
107
37
  def []=(key, value)
108
38
  case value
109
- when nil
110
- delete(key)
111
- when true, false
112
- update_flag(key, value)
39
+ when nil then delete(key)
40
+ when true, false then update_flag(key, value)
113
41
  when Integer
114
- unless int32_range?(value)
42
+ unless int32?(value)
115
43
  raise RangeError,
116
44
  "Integer out of int32 range for []=. Current htslib backend does not support int64 INFO update."
117
45
  end
118
46
 
119
47
  update_int(key, [value])
120
- when Float
121
- update_float(key, [value])
122
- when String
123
- update_string(key, value)
48
+ when Float then update_float(key, [value])
49
+ when String then update_string(key, value)
124
50
  when Array
125
- if value.empty?
126
- raise ArgumentError, "Cannot set INFO field to empty array. Use nil to delete."
127
- elsif value.all? { |v| v.is_a?(Integer) }
128
- unless value.all? { |v| int32_range?(v) }
51
+ raise ArgumentError, "Cannot set INFO field to empty array. Use nil to delete." if value.empty?
52
+
53
+ if value.all? { |item| item.is_a?(Integer) }
54
+ unless value.all? do |item|
55
+ int32?(item)
56
+ end
129
57
  raise RangeError,
130
58
  "Integer array contains out-of-int32 values for []=. Current htslib backend does not support int64 INFO update."
131
59
  end
132
60
 
133
61
  update_int(key, value)
134
- elsif value.all? { |v| v.is_a?(Numeric) }
62
+ elsif value.all? { |item| item.is_a?(Numeric) }
135
63
  update_float(key, value)
136
64
  else
137
65
  raise ArgumentError, "INFO array must contain only integers or floats, got: #{value.map(&:class).uniq}"
138
66
  end
139
- else
140
- raise ArgumentError, "Unsupported INFO value type: #{value.class}"
67
+ else raise ArgumentError, "Unsupported INFO value type: #{value.class}"
141
68
  end
142
69
  end
143
70
 
144
- # Update INFO field with integer value(s).
145
- # For compatibility with HTS.cr.
146
- # @param key [String] INFO tag name
147
- # @param values [Array<Integer>] integer values (use single-element array for scalar)
148
- def update_int(key, values)
149
- values = Array(values)
150
- ptr = FFI::MemoryPointer.new(:int32, values.size)
151
- ptr.write_array_of_int32(values)
152
- ret = LibHTS.bcf_update_info(
153
- @record.header.struct,
154
- @record.struct,
155
- key,
156
- ptr,
157
- values.size,
158
- LibHTS::BCF_HT_INT
159
- )
160
- raise InfoUpdateError, "Failed to update INFO int field '#{key}': #{ret}" if ret < 0
161
-
162
- ret
163
- end
164
-
165
- # Update INFO field with int64 value(s).
166
- # @note int64 INFO values are primarily relevant for VCF output.
167
- # @param key [String] INFO tag name
168
- # @param values [Array<Integer>] integer values (use single-element array for scalar)
169
- def update_int64(_key, _values)
170
- raise UnsupportedInfoOperationError, "htslib backend does not implement int64 INFO update (BCF_HT_LONG)"
171
- end
172
-
173
- # Update INFO field with float value(s).
174
- # For compatibility with HTS.cr.
175
- # @param key [String] INFO tag name
176
- # @param values [Array<Float>] float values (use single-element array for scalar)
177
- def update_float(key, values)
178
- values = Array(values).map(&:to_f)
179
- ptr = FFI::MemoryPointer.new(:float, values.size)
180
- ptr.write_array_of_float(values)
181
- ret = LibHTS.bcf_update_info(
182
- @record.header.struct,
183
- @record.struct,
184
- key,
185
- ptr,
186
- values.size,
187
- LibHTS::BCF_HT_REAL
188
- )
189
- raise InfoUpdateError, "Failed to update INFO float field '#{key}': #{ret}" if ret < 0
190
-
191
- ret
192
- end
193
-
194
- # Update INFO field with string value.
195
- # For compatibility with HTS.cr.
196
- # @param key [String] INFO tag name
197
- # @param value [String] string value
198
- def update_string(key, value)
199
- ret = LibHTS.bcf_update_info(
200
- @record.header.struct,
201
- @record.struct,
202
- key,
203
- value.to_s,
204
- 1,
205
- LibHTS::BCF_HT_STR
206
- )
207
- raise InfoUpdateError, "Failed to update INFO string field '#{key}': #{ret}" if ret < 0
208
-
209
- ret
210
- end
71
+ def update_int(key, values) = update(key, Native::BCF_HT_INT, Array(values).map { |value| Integer(value) }, "int")
72
+ def update_float(key, values) = update(key, Native::BCF_HT_REAL, Array(values).map(&:to_f), "float")
73
+ def update_string(key, value) = update(key, Native::BCF_HT_STR, value.to_s, "string")
211
74
 
212
- # Update INFO flag field.
213
- # For compatibility with HTS.cr.
214
- # @param key [String] INFO tag name
215
- # @param present [Boolean] true to set flag, false to remove it
216
- def update_flag(key, present = true)
217
- ret = if present
218
- LibHTS.bcf_update_info(
219
- @record.header.struct,
220
- @record.struct,
221
- key,
222
- FFI::Pointer::NULL,
223
- 1,
224
- LibHTS::BCF_HT_FLAG
225
- )
226
- else
227
- # Remove flag by setting n=0
228
- LibHTS.bcf_update_info(
229
- @record.header.struct,
230
- @record.struct,
231
- key,
232
- FFI::Pointer::NULL,
233
- 0,
234
- LibHTS::BCF_HT_FLAG
235
- )
236
- end
237
- raise InfoUpdateError, "Failed to update INFO flag field '#{key}': #{ret}" if ret < 0
75
+ def update_int64(_key,
76
+ _values) = raise(UnsupportedInfoOperationError,
77
+ "htslib backend does not implement int64 INFO update (BCF_HT_LONG)")
238
78
 
239
- ret
240
- end
79
+ def update_flag(key, present = true) = update(key, Native::BCF_HT_FLAG, !!present, "flag")
241
80
 
242
- # Delete an INFO field.
243
- # @param key [String] INFO tag name
244
- # @return [Boolean] true if field was deleted, false if it didn't exist
245
81
  def delete(key)
246
- type = get_info_type(key)
247
- return false if type.nil?
248
- return false unless key?(key)
249
-
250
- # Delete by setting n=0
251
- ret = LibHTS.bcf_update_info(
252
- @record.header.struct,
253
- @record.struct,
254
- key,
255
- FFI::Pointer::NULL,
256
- 0,
257
- type
258
- )
259
- return false if ret < 0
82
+ schema = header.schema("INFO", key.to_s)
83
+ return false unless schema && key?(key)
260
84
 
85
+ native.info_update(header_native, key.to_s, TYPE_CODES.fetch(schema.first), false)
261
86
  true
262
87
  end
263
88
 
264
- # Check if an INFO field exists.
265
- # @param key [String] INFO tag name
266
- # @return [Boolean] true if the field exists
267
89
  def key?(key)
268
- type = header_info_type(key)
269
- return false if type.nil?
270
-
271
- ndst = FFI::MemoryPointer.new(:int)
272
- ndst.write_int(0)
273
- dst_ptr = FFI::MemoryPointer.new(:pointer)
274
- dst_ptr.write_pointer(FFI::Pointer::NULL)
275
-
276
- ret = LibHTS.bcf_get_info_values(@record.header.struct, @record.struct, key, dst_ptr, ndst, type)
277
- type == LibHTS::BCF_HT_FLAG ? ret == 1 : ret >= 0
278
- ensure
279
- if dst_ptr
280
- dst = dst_ptr.read_pointer
281
- LibHTS.hts_free(dst) unless dst.null?
282
- end
90
+ schema = header.schema("INFO", key.to_s)
91
+ schema ? native.info_present?(header_native, key.to_s, TYPE_CODES.fetch(schema.first)) : false
283
92
  end
284
-
285
93
  alias include? key?
286
94
 
287
- # FIXME: naming? room for improvement.
288
- def fields
289
- keys.map do |key|
290
- name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, key)
291
- num = LibHTS.bcf_hdr_id2number(@record.header.struct, LibHTS::BCF_HL_INFO, key)
292
- type = LibHTS.bcf_hdr_id2type(@record.header.struct, LibHTS::BCF_HL_INFO, key)
293
- {
294
- name:,
295
- n: num,
296
- type: ht_type_to_sym(type),
297
- key:
298
- }
299
- end
300
- end
301
-
302
- def length
303
- @record.struct[:n_info]
304
- end
305
-
306
- def size
307
- length
308
- end
309
-
310
- def to_h
311
- ret = {}
312
- keys.each do |key|
313
- name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, key)
314
- ret[name] = get(name)
315
- end
316
- ret
317
- end
95
+ def fields = native.info_fields(header_native)
96
+ def keys = fields.map { |field| field[:key] }
97
+ def length = fields.length
98
+ alias size length
99
+ def to_h = fields.to_h { |field| [field[:name], get(field[:name])] }
318
100
 
319
101
  private
320
102
 
321
- def info_ptr
322
- @record.struct[:d][:info].to_ptr
323
- end
103
+ def native = @record.__send__(:native_handle)
104
+ def header = @record.header.__send__(:native_handle)
105
+ alias header_native header
324
106
 
325
- def keys
326
- info_ptr.read_array_of_struct(LibHTS::BcfInfo, length).map do |info|
327
- info[:key]
328
- end
329
- end
107
+ def update(key, type, value, label)
108
+ result = native.info_update(header_native, key.to_s, type, value)
109
+ raise InfoUpdateError, "Failed to update INFO #{label} field '#{key}': #{result}" if result.negative?
330
110
 
331
- def get_info_type(key)
332
- header_info_type(key)
111
+ result
333
112
  end
334
113
 
335
- def header_info_type(key)
336
- id = LibHTS.bcf_hdr_id2int(@record.header.struct, LibHTS::BCF_DT_ID, key)
337
- return nil if id.negative?
338
- return nil unless LibHTS.bcf_hdr_idinfo_exists(@record.header.struct, LibHTS::BCF_HL_INFO, id)
339
-
340
- LibHTS.bcf_hdr_id2type(@record.header.struct, LibHTS::BCF_HL_INFO, id)
341
- end
342
-
343
- def ht_type_to_sym(t)
344
- case t
345
- when LibHTS::BCF_HT_FLAG then :flag
346
- when LibHTS::BCF_HT_INT then :int
347
- when LibHTS::BCF_HT_REAL then :float
348
- when LibHTS::BCF_HT_STR then :string
349
- when LibHTS::BCF_HT_LONG then :int64
350
- end
351
- end
352
-
353
- def int32_range?(value)
354
- value >= -2_147_483_648 && value <= 2_147_483_647
355
- end
356
-
357
- def info_type_compatible?(actual_type, requested_type)
358
- case requested_type
359
- when :int, :int32
360
- actual_type == :int
361
- when :int64, :long
362
- %i[int int64].include?(actual_type)
363
- when :float, :real
364
- actual_type == :float
365
- when :flag, :bool
366
- actual_type == :flag
367
- when :string, :str
368
- actual_type == :string
369
- else
370
- actual_type == requested_type
114
+ def type_compatible?(actual, requested)
115
+ case requested
116
+ when :int, :int32 then actual == :int
117
+ when :int64, :long then %i[int int64].include?(actual)
118
+ when :float, :real then actual == :float
119
+ when :flag, :bool then actual == :flag
120
+ when :string, :str then actual == :string
121
+ else actual == requested
371
122
  end
372
123
  end
373
124
 
374
125
  def type_label(type)
375
- case type.to_sym
376
- when :int, :int32 then "integer"
377
- when :int64, :long then "integer"
126
+ case type
127
+ when :int, :int32, :int64, :long then "integer"
378
128
  when :float, :real then "float"
379
129
  when :flag, :bool then "flag"
380
130
  when :string, :str then "string"
381
131
  else type.to_s
382
132
  end
383
133
  end
134
+
135
+ def int32?(value) = value.between?(-2_147_483_648, 2_147_483_647)
384
136
  end
385
137
  end
386
138
  end
@@ -2,148 +2,107 @@
2
2
 
3
3
  module HTS
4
4
  class Bcf < Hts
5
- # A class for working with VCF records.
6
5
  class Record
7
- def initialize(header, bcf_t = nil)
8
- @bcf1 = bcf_t || LibHTS.bcf_init
6
+ attr_reader :header
7
+
8
+ def initialize(header, native_record = nil)
9
+ @native = native_record || Native::BcfRecordHandle.create
9
10
  @header = header
10
11
  end
11
12
 
12
- attr_reader :header
13
+ def rid = @native.core_get(:rid)
13
14
 
14
- def struct
15
- @bcf1
15
+ def rid=(value)
16
+ @native.core_set(:rid, value)
16
17
  end
17
18
 
18
- def to_ptr
19
- @bcf1.to_ptr
20
- end
19
+ def chrom = @header.id2name(rid)
20
+ def pos = @native.core_get(:pos)
21
21
 
22
- # Get the reference id of the record.
23
- def rid
24
- @bcf1[:rid]
22
+ def pos=(value)
23
+ @native.core_set(:pos, value)
25
24
  end
26
25
 
27
- def rid=(rid)
28
- @bcf1[:rid] = rid
29
- end
26
+ def endpos = pos + @native.core_get(:rlen)
27
+ def id = @native.id
30
28
 
31
- # Get the chromosome of variant.
32
- def chrom
33
- LibHTS.bcf_hdr_id2name(@header.struct, rid)
29
+ def id=(value)
30
+ @native.set_id(@header.__send__(:native_handle), value)
34
31
  end
35
32
 
36
- # Return 0-based position.
37
- def pos
38
- @bcf1[:pos]
33
+ def clear_id
34
+ @native.set_id(@header.__send__(:native_handle), ".")
35
+ nil
39
36
  end
40
37
 
41
- def pos=(pos)
42
- @bcf1[:pos] = pos
43
- end
38
+ def alleles = @native.alleles
39
+ def ref = alleles.first
40
+ def alt = alleles.drop(1)
41
+ def allele_count = alleles.length
44
42
 
45
- # Return the 0-based, exclusive end position
46
- def endpos
47
- pos + @bcf1[:rlen]
48
- end
43
+ def alleles=(values)
44
+ values = Array(values).map(&:to_s)
45
+ raise ArgumentError, "at least one allele is required" if values.empty?
49
46
 
50
- # Return the value of the ID column.
51
- def id
52
- LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_INFO)
53
- @bcf1[:d][:id]
47
+ @native.set_alleles(@header.__send__(:native_handle), values.join(","))
48
+ values
54
49
  end
55
50
 
56
- def id=(id)
57
- LibHTS.bcf_update_id(@header.struct, @bcf1, id)
58
- end
51
+ # Native migration replacement for the former pointer-yielding API.
52
+ def each_allele_raw
53
+ return to_enum(__method__) unless block_given?
59
54
 
60
- def clear_id
61
- LibHTS.bcf_update_id(@header.struct, @bcf1, ".")
55
+ alleles.each { |allele| yield allele, allele.bytesize }
56
+ self
62
57
  end
63
58
 
64
- def ref
65
- LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_STR)
66
- @bcf1[:d][:allele].get_pointer(0).read_string
67
- end
59
+ def qual = @native.core_get(:qual)
68
60
 
69
- def alt
70
- LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_STR)
71
- @bcf1[:d][:allele].get_array_of_pointer(
72
- FFI::TYPE_POINTER.size, @bcf1[:n_allele] - 1
73
- ).map(&:read_string)
61
+ def qual=(value)
62
+ @native.core_set(:qual, value)
74
63
  end
75
64
 
76
- def alleles
77
- LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_STR)
78
- @bcf1[:d][:allele].get_array_of_pointer(
79
- 0, @bcf1[:n_allele]
80
- ).map(&:read_string)
65
+ def filter
66
+ names = @native.filter_names(@header.__send__(:native_handle))
67
+ case names.length
68
+ when 0 then "PASS"
69
+ when 1 then names.first
70
+ else names
71
+ end
81
72
  end
82
73
 
83
- # Get variant quality.
84
- def qual
85
- @bcf1[:qual]
86
- end
74
+ def each_filter_id(&block)
75
+ return to_enum(__method__) unless block_given?
87
76
 
88
- def qual=(qual)
89
- @bcf1[:qual] = qual
77
+ @native.filter_ids.each(&block)
78
+ self
90
79
  end
91
80
 
92
- def filter
93
- LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_FLT)
94
- d = @bcf1[:d]
95
- n_flt = d[:n_flt]
96
-
97
- case n_flt
98
- when 0
99
- "PASS"
100
- when 1
101
- id = d[:flt].read_int
102
- LibHTS.bcf_hdr_int2id(@header.struct, LibHTS::BCF_DT_ID, id)
103
- when 2..
104
- d[:flt].get_array_of_int(0, n_flt).map do |i|
105
- LibHTS.bcf_hdr_int2id(@header.struct, LibHTS::BCF_DT_ID, i)
106
- end
107
- else
108
- raise "Unexpected number of filters. n_flt: #{n_flt}"
109
- end
110
- end
81
+ def filter_ids = @native.filter_ids
82
+ def filter_id?(target_id) = filter_ids.include?(Integer(target_id))
111
83
 
112
84
  def info(key = nil)
113
- LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_SHR)
114
- info = Info.new(self)
115
- if key
116
- info.get(key)
117
- else
118
- info
119
- end
85
+ accessor = (@info_accessor ||= Info.new(self))
86
+ key ? accessor.get(key) : accessor
120
87
  end
121
88
 
122
89
  def format(key = nil)
123
- LibHTS.bcf_unpack(@bcf1, LibHTS::BCF_UN_FMT)
124
- if key
125
- Format.new(self).get(key)
126
- else
127
- Format.new(self)
128
- end
90
+ accessor = (@format_accessor ||= Format.new(self))
91
+ key ? accessor.get(key) : accessor
129
92
  end
130
93
 
131
- def to_s
132
- ksr = LibHTS::KString.new
133
- begin
134
- raise "Failed to format record" if LibHTS.vcf_format(@header.struct, @bcf1, ksr) == -1
135
-
136
- ksr.read_string_copy
137
- ensure
138
- ksr.free_buffer
139
- end
140
- end
94
+ def to_s = @native.format_record(@header.__send__(:native_handle))
141
95
 
142
96
  private
143
97
 
144
- def initialize_copy(orig)
145
- @header = orig.header
146
- @bcf1 = LibHTS.bcf_dup(orig.struct)
98
+ def native_handle = @native
99
+
100
+ def initialize_copy(original)
101
+ super
102
+ @header = original.header
103
+ @native = original.__send__(:native_handle).duplicate
104
+ @info_accessor = nil
105
+ @format_accessor = nil
147
106
  end
148
107
  end
149
108
  end