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
@@ -12,6 +12,7 @@ module HTS
12
12
  class BaseMod
13
13
  include Enumerable
14
14
 
15
+ class Error < HTS::Error; end
15
16
  class NotParsedError < StandardError; end
16
17
 
17
18
  attr_reader :record
@@ -133,11 +134,10 @@ module HTS
133
134
  # @param auto_parse [Boolean] If true, parse MM/ML lazily on first access
134
135
  def initialize(record, auto_parse: true)
135
136
  @record = record
136
- @state = LibHTS.hts_base_mod_state_alloc
137
+ @state = Native::BaseModHandle.open(record.__send__(:native_handle))
137
138
  @closed = false
138
139
  @auto_parse = !!auto_parse
139
140
  @parsed = false
140
- raise Error, "Failed to allocate hts_base_mod_state" if @state.null?
141
141
  end
142
142
 
143
143
  # Explicitly free the state
@@ -145,8 +145,7 @@ module HTS
145
145
  def close
146
146
  return if @closed
147
147
 
148
- # With HtsBaseModState as an AutoPointer, releasing the Ruby object
149
- # is sufficient. Avoid manual free to prevent double-free.
148
+ @state.close
150
149
  @state = nil
151
150
  @closed = true
152
151
  end
@@ -173,7 +172,7 @@ module HTS
173
172
  # @return [Integer] Number of modification types found, or -1 on error
174
173
  # @raise [Error] If parsing fails
175
174
  def parse(flags = 0)
176
- ret = LibHTS.bam_parse_basemod2(@record.struct, @state, flags)
175
+ ret = @state.parse(flags)
177
176
  raise Error, "Failed to parse base modifications" if ret < 0
178
177
 
179
178
  @parsed = true
@@ -188,13 +187,10 @@ module HTS
188
187
  # Reset state to ensure deterministic results even after prior iteration
189
188
  parsed? ? parse : ensure_parsed!
190
189
 
191
- mods_ptr = FFI::MemoryPointer.new(LibHTS::HtsBaseMod, max_mods)
190
+ values = @state.at(position, max_mods)
191
+ return nil unless values
192
192
 
193
- ret = LibHTS.bam_mods_at_qpos(@record.struct, position, @state,
194
- mods_ptr, max_mods)
195
- return nil if ret <= 0
196
-
197
- build_position(position, mods_ptr, [ret, max_mods].min)
193
+ build_position(position, values)
198
194
  end
199
195
 
200
196
  # Array-style access to modifications at a position
@@ -211,36 +207,41 @@ module HTS
211
207
  def each_position(max_mods: 10)
212
208
  return enum_for(__method__, max_mods: max_mods) unless block_given?
213
209
 
214
- # Reset state at the start of iteration to allow repeated enumerations
215
- parsed? ? parse : ensure_parsed!
216
-
217
- pos_ptr = FFI::MemoryPointer.new(:int)
218
- mods_ptr = FFI::MemoryPointer.new(LibHTS::HtsBaseMod, max_mods)
219
-
220
- loop do
221
- ret = LibHTS.bam_next_basemod(@record.struct, @state,
222
- mods_ptr, max_mods, pos_ptr)
223
- break if ret <= 0
224
-
225
- position = pos_ptr.read_int
226
- yield build_position(position, mods_ptr, [ret, max_mods].min)
210
+ current_position = nil
211
+ modifications = []
212
+ each_raw(max_mods: max_mods) do |position, canonical, modified, strand, qual|
213
+ if current_position && position != current_position
214
+ yield Position.new(current_position, modifications)
215
+ modifications = []
216
+ end
217
+ current_position = position
218
+ modifications << Modification.new(
219
+ modified_base: modified, canonical_base: canonical,
220
+ strand: strand, qual: qual
221
+ )
227
222
  end
223
+ yield Position.new(current_position, modifications) if current_position
224
+ self
228
225
  end
229
226
 
230
227
  alias each each_position
231
228
 
229
+ # Iterate primitive modification values without Position/Modification
230
+ # object allocation.
231
+ def each_raw(max_mods: 10)
232
+ return enum_for(__method__, max_mods: max_mods) unless block_given?
233
+
234
+ parsed? ? parse : ensure_parsed!
235
+ @state.each_raw(max_mods) { |*values| yield(*values) }
236
+ self
237
+ end
238
+
232
239
  # Get list of modification types present in this record
233
240
  # @return [Array<Integer>] Array of modification codes (char code or -ChEBI)
234
241
  def modification_types
235
242
  ensure_parsed!
236
243
 
237
- ntype_ptr = FFI::MemoryPointer.new(:int)
238
- codes_ptr = LibHTS.bam_mods_recorded(@state, ntype_ptr)
239
-
240
- ntype = ntype_ptr.read_int
241
- return [] if ntype <= 0 || codes_ptr.null?
242
-
243
- codes_ptr.read_array_of_int(ntype)
244
+ @state.types
244
245
  end
245
246
 
246
247
  alias recorded_types modification_types
@@ -253,19 +254,7 @@ module HTS
253
254
 
254
255
  code = code.ord if code.is_a?(String)
255
256
 
256
- strand_ptr = FFI::MemoryPointer.new(:int)
257
- implicit_ptr = FFI::MemoryPointer.new(:int)
258
- canonical_ptr = FFI::MemoryPointer.new(:char, 1)
259
-
260
- ret = LibHTS.bam_mods_query_type(@state, code, strand_ptr,
261
- implicit_ptr, canonical_ptr)
262
- return nil if ret < 0
263
-
264
- {
265
- canonical: canonical_ptr.read_char.chr,
266
- strand: strand_ptr.read_int,
267
- implicit: implicit_ptr.read_int != 0
268
- }
257
+ @state.query(code)
269
258
  end
270
259
 
271
260
  # Query information about i-th modification type
@@ -274,21 +263,7 @@ module HTS
274
263
  def query_type_at(index)
275
264
  ensure_parsed!
276
265
 
277
- strand_ptr = FFI::MemoryPointer.new(:int)
278
- implicit_ptr = FFI::MemoryPointer.new(:int)
279
- canonical_ptr = FFI::MemoryPointer.new(:char, 1)
280
-
281
- ret = LibHTS.bam_mods_queryi(@state, index, strand_ptr,
282
- implicit_ptr, canonical_ptr)
283
- return nil if ret < 0
284
-
285
- types = modification_types
286
- {
287
- code: types[index],
288
- canonical: canonical_ptr.read_char.chr,
289
- strand: strand_ptr.read_int,
290
- implicit: implicit_ptr.read_int != 0
291
- }
266
+ @state.query_at(index)
292
267
  end
293
268
 
294
269
  # Get all modifications as an array
@@ -319,21 +294,11 @@ module HTS
319
294
 
320
295
  # Build Position object from hts_base_mod array
321
296
  # @param position [Integer] Query position
322
- # @param mods_ptr [FFI::Pointer] Pointer to array of HtsBaseMod structures
323
- # @param n_mods [Integer] Number of modifications
297
+ # @param values [Array<Array>] Native modification values
324
298
  # @return [Position] Position object
325
- def build_position(position, mods_ptr, n_mods)
326
- modifications = []
327
-
328
- n_mods.times do |i|
329
- mod_struct = LibHTS::HtsBaseMod.new(mods_ptr + i * LibHTS::HtsBaseMod.size)
330
-
331
- modifications << Modification.new(
332
- modified_base: mod_struct[:modified_base],
333
- canonical_base: mod_struct[:canonical_base],
334
- strand: mod_struct[:strand],
335
- qual: mod_struct[:qual]
336
- )
299
+ def build_position(position, values)
300
+ modifications = values.map do |canonical, modified, strand, qual|
301
+ Modification.new(modified_base: modified, canonical_base: canonical, strand:, qual:)
337
302
  end
338
303
 
339
304
  Position.new(position, modifications)
data/lib/hts/bam/cigar.rb CHANGED
@@ -2,78 +2,32 @@
2
2
 
3
3
  module HTS
4
4
  class Bam < Hts
5
- # CIGAR string
6
5
  class Cigar
7
6
  include Enumerable
8
7
 
9
- # a uint32_t array (with 32 bits for every CIGAR op: length<<4|operation)
8
+ OP_CHARS = "MIDNSHP=XB"
10
9
  attr_accessor :array
11
10
 
12
- # Create a new Cigar object from a string.
13
- # @param [String] cigar_str
14
- # The CIGAR string is converted to a uint32_t array in htslib.
15
11
  def self.parse(str)
16
- c = FFI::MemoryPointer.new(:pointer)
17
- m = FFI::MemoryPointer.new(:size_t)
18
- c.write_pointer(FFI::Pointer::NULL)
19
- m.write(:size_t, 0)
20
- ptr = nil
21
- n_cigar = LibHTS.sam_parse_cigar(str, FFI::Pointer::NULL, c, m)
22
- raise "sam_parse_cigar failed: #{n_cigar}" if n_cigar.negative?
23
-
24
- ptr = c.read_pointer
25
- cigar_array = ptr.null? ? [] : ptr.read_array_of_uint32(n_cigar)
26
- obj = new
27
- obj.array = cigar_array
28
- obj
29
- ensure
30
- LibHTS.hts_free(ptr) if ptr && !ptr.null?
12
+ new.tap { |cigar| cigar.array = Native.cigar_parse(str.to_s) }
31
13
  end
32
14
 
33
15
  def initialize(record = nil)
34
- if record
35
- # The record is used at initialization and is not retained after that.
36
- bam1 = record.struct
37
- n_cigar = bam1[:core][:n_cigar]
38
- @array = LibHTS.bam_get_cigar(bam1).read_array_of_uint32(n_cigar)
39
- else
40
- @array = []
41
- end
16
+ @array = record ? record.__send__(:native_handle).cigar_values : []
42
17
  end
43
18
 
44
- def to_s
45
- map { |op, len| "#{len}#{op}" }.join
46
- end
19
+ def to_s = map { |op, len| "#{len}#{op}" }.join
47
20
 
48
21
  def each
49
22
  return to_enum(__method__) unless block_given?
50
23
 
51
- @array.each do |c|
52
- op = LibHTS.bam_cigar_opchr(c)
53
- len = LibHTS.bam_cigar_oplen(c)
54
- yield [op, len]
55
- end
24
+ @array.each { |encoded| yield OP_CHARS[encoded & 15], encoded >> 4 }
56
25
  end
57
26
 
58
- def qlen
59
- a = FFI::MemoryPointer.new(:uint32, @array.size)
60
- a.write_array_of_uint32(@array)
61
- LibHTS.bam_cigar2qlen(@array.size, a)
62
- end
63
-
64
- def rlen
65
- a = FFI::MemoryPointer.new(:uint32, @array.size)
66
- a.write_array_of_uint32(@array)
67
- LibHTS.bam_cigar2rlen(@array.size, a)
68
- end
69
-
70
- def ==(other)
71
- other.is_a?(Cigar) && (@array == other.array)
72
- end
73
-
74
- def eql?(other)
75
- other.is_a?(Cigar) && @array.eql?(other.array)
76
- end
27
+ def qlen = Native.cigar_qlen(@array)
28
+ def rlen = Native.cigar_rlen(@array)
29
+ def ==(other) = other.is_a?(Cigar) && @array == other.array
30
+ def eql?(other) = other.is_a?(Cigar) && @array.eql?(other.array)
77
31
  end
78
32
  end
79
33
  end
data/lib/hts/bam/flag.rb CHANGED
@@ -15,31 +15,28 @@ module HTS
15
15
 
16
16
  attr_accessor :value
17
17
 
18
- # BAM_FPAIRED = 1
19
- # BAM_FPROPER_PAIR = 2
20
- # BAM_FUNMAP = 4
21
- # BAM_FMUNMAP = 8
22
- # BAM_FREVERSE = 16
23
- # BAM_FMREVERSE = 32
24
- # BAM_FREAD1 = 64
25
- # BAM_FREAD2 = 128
26
- # BAM_FSECONDARY = 256
27
- # BAM_FQCFAIL = 512
28
- # BAM_FDUP = 1024
29
- # BAM_FSUPPLEMENTARY = 2048
30
-
31
- TABLE = { paired?: LibHTS::BAM_FPAIRED,
32
- proper_pair?: LibHTS::BAM_FPROPER_PAIR,
33
- unmapped?: LibHTS::BAM_FUNMAP,
34
- mate_unmapped?: LibHTS::BAM_FMUNMAP,
35
- reverse?: LibHTS::BAM_FREVERSE,
36
- mate_reverse?: LibHTS::BAM_FMREVERSE,
37
- read1?: LibHTS::BAM_FREAD1,
38
- read2?: LibHTS::BAM_FREAD2,
39
- secondary?: LibHTS::BAM_FSECONDARY,
40
- qcfail?: LibHTS::BAM_FQCFAIL,
41
- duplicate?: LibHTS::BAM_FDUP,
42
- supplementary?: LibHTS::BAM_FSUPPLEMENTARY }.freeze
18
+ PAIRED = 1
19
+ PROPER_PAIR = 2
20
+ UNMAPPED = 4
21
+ MATE_UNMAPPED = 8
22
+ REVERSE = 16
23
+ MATE_REVERSE = 32
24
+ READ1 = 64
25
+ READ2 = 128
26
+ SECONDARY = 256
27
+ QCFAIL = 512
28
+ DUPLICATE = 1024
29
+ SUPPLEMENTARY = 2048
30
+
31
+ UNMAP = UNMAPPED
32
+ MUNMAP = MATE_UNMAPPED
33
+ DUP = DUPLICATE
34
+
35
+ TABLE = { paired?: PAIRED, proper_pair?: PROPER_PAIR, unmapped?: UNMAPPED,
36
+ mate_unmapped?: MATE_UNMAPPED, reverse?: REVERSE,
37
+ mate_reverse?: MATE_REVERSE, read1?: READ1, read2?: READ2,
38
+ secondary?: SECONDARY, qcfail?: QCFAIL, duplicate?: DUPLICATE,
39
+ supplementary?: SUPPLEMENTARY }.freeze
43
40
 
44
41
  # @!macro [attach] generate_flag_methods
45
42
  # @!method $1
@@ -81,9 +78,7 @@ module HTS
81
78
  end
82
79
 
83
80
  def ~
84
- # FIXME: Only 12bits are used for flags
85
- # The result is different from the Crystal version.
86
- self.class.new(~@value)
81
+ self.class.new((~@value) & 0x0fff)
87
82
  end
88
83
 
89
84
  def <<(f)
@@ -99,8 +94,7 @@ module HTS
99
94
  end
100
95
 
101
96
  def to_s
102
- LibHTS.bam_flag2str(@value)
103
- # "0x#{format('%x', @value)}\t#{@value}\t#{LibHTS.bam_flag2str(@value)}"
97
+ Native.bam_flag_string(@value)
104
98
  end
105
99
  end
106
100
  end
@@ -40,17 +40,15 @@ module HTS
40
40
  }.freeze
41
41
 
42
42
  def self.parse(text)
43
- new(LibHTS.sam_hdr_parse(text.size, text))
43
+ new(Native::SamHeaderHandle.parse(text))
44
44
  end
45
45
 
46
46
  def initialize(arg = nil)
47
47
  case arg
48
- when LibHTS::HtsFile
49
- @sam_hdr = LibHTS.sam_hdr_read(arg)
50
- when LibHTS::SamHdr
51
- @sam_hdr = arg
48
+ when Native::SamHeaderHandle
49
+ @native = arg
52
50
  when nil
53
- @sam_hdr = LibHTS.sam_hdr_init
51
+ @native = Native::SamHeaderHandle.create
54
52
  else
55
53
  raise TypeError, "Invalid argument"
56
54
  end
@@ -58,25 +56,16 @@ module HTS
58
56
  yield self if block_given?
59
57
  end
60
58
 
61
- def struct
62
- @sam_hdr
63
- end
64
-
65
- def to_ptr
66
- @sam_hdr.to_ptr
67
- end
68
-
69
59
  def targets
70
60
  Array.new(target_count) do |i|
71
- name = LibHTS.sam_hdr_tid2name(@sam_hdr, i)
72
- len = LibHTS.sam_hdr_tid2len(@sam_hdr, i)
61
+ name = @native.target_name(i)
62
+ len = @native.target_length(i)
73
63
  { name:, len: }
74
64
  end
75
65
  end
76
66
 
77
67
  def target_count
78
- # FIXME: sam_hdr_nref
79
- @sam_hdr[:n_targets]
68
+ @native.target_count
80
69
  end
81
70
 
82
71
  def target_name(tid)
@@ -85,13 +74,13 @@ module HTS
85
74
 
86
75
  def target_names
87
76
  Array.new(target_count) do |i|
88
- LibHTS.sam_hdr_tid2name(@sam_hdr, i)
77
+ @native.target_name(i)
89
78
  end
90
79
  end
91
80
 
92
81
  def target_len
93
82
  Array.new(target_count) do |i|
94
- LibHTS.sam_hdr_tid2len(@sam_hdr, i)
83
+ @native.target_length(i)
95
84
  end
96
85
  end
97
86
 
@@ -118,68 +107,50 @@ module HTS
118
107
 
119
108
  # experimental
120
109
  def find_line(type, key, value)
121
- ks = LibHTS::KString.new
122
- begin
123
- r = LibHTS.sam_hdr_find_line_id(@sam_hdr, type, key, value, ks)
124
- r == 0 ? ks.read_string_copy : nil
125
- ensure
126
- ks.free_buffer
127
- end
110
+ @native.find_line(type, key, value)
128
111
  end
129
112
 
130
113
  def find_tag(type, id_key, id_value, key)
131
- ks = LibHTS::KString.new
132
- begin
133
- r = LibHTS.sam_hdr_find_tag_id(@sam_hdr, type, id_key, id_value, key, ks)
134
- r == 0 ? ks.read_string_copy : nil
135
- ensure
136
- ks.free_buffer
137
- end
114
+ @native.find_tag(type, id_key, id_value, key)
138
115
  end
139
116
 
140
117
  # experimental
141
118
  def find_line_at(type, pos)
142
- ks = LibHTS::KString.new
143
- begin
144
- r = LibHTS.sam_hdr_find_line_pos(@sam_hdr, type, pos, ks)
145
- r == 0 ? ks.read_string_copy : nil
146
- ensure
147
- ks.free_buffer
148
- end
119
+ @native.find_line_at(type, pos)
149
120
  end
150
121
 
151
122
  # experimental
152
123
  def remove_line(type, key, value)
153
- LibHTS.sam_hdr_remove_line_id(@sam_hdr, type, key, value)
124
+ @native.remove_line(type, key, value)
154
125
  end
155
126
 
156
127
  # experimental
157
128
  def remove_line_at(type, pos)
158
- LibHTS.sam_hdr_remove_line_pos(@sam_hdr, type, pos)
129
+ @native.remove_line_at(type, pos)
159
130
  end
160
131
 
161
132
  def delete_line(type, key = nil, value = nil)
162
- LibHTS.sam_hdr_remove_line_id(@sam_hdr, type, key, value).zero?
133
+ @native.remove_line(type, key, value).zero?
163
134
  end
164
135
 
165
136
  def delete_tag(type, id_key, id_value, key)
166
- LibHTS.sam_hdr_remove_tag_id(@sam_hdr, type, id_key, id_value, key) == 1
137
+ @native.remove_tag(type, id_key, id_value, key) == 1
167
138
  end
168
139
 
169
140
  def count_lines(type)
170
- LibHTS.sam_hdr_count_lines(@sam_hdr, type)
141
+ @native.count_lines(type)
171
142
  end
172
143
 
173
144
  def line_index(type, key)
174
- LibHTS.sam_hdr_line_index(@sam_hdr, type, key)
145
+ @native.line_index(type, key)
175
146
  end
176
147
 
177
148
  def line_name(type, pos)
178
- LibHTS.sam_hdr_line_name(@sam_hdr, type, pos)
149
+ @native.line_name(type, pos)
179
150
  end
180
151
 
181
152
  def to_s
182
- LibHTS.sam_hdr_str(@sam_hdr)
153
+ @native.to_s
183
154
  end
184
155
 
185
156
  # experimental
@@ -241,7 +212,7 @@ module HTS
241
212
  # header.add_pg("samtools", VN: "1.15", PP: "bwa")
242
213
  def add_pg(program_name, **options)
243
214
  line = build_pg_line(program_name.to_s, options)
244
- result = LibHTS.sam_hdr_add_lines(@sam_hdr, line, line.bytesize)
215
+ result = @native.add_lines(line)
245
216
  raise "Failed to add @PG line" if result < 0
246
217
 
247
218
  self
@@ -418,26 +389,28 @@ module HTS
418
389
  end
419
390
 
420
391
  def name2tid(name)
421
- LibHTS.sam_hdr_name2tid(@sam_hdr, name)
392
+ @native.name2tid(name)
422
393
  end
423
394
 
424
395
  def tid2name(tid)
425
- LibHTS.sam_hdr_tid2name(@sam_hdr, tid)
396
+ @native.target_name(tid)
426
397
  end
427
398
 
428
399
  def add_lines(str)
429
- LibHTS.sam_hdr_add_lines(@sam_hdr, str, 0)
400
+ @native.add_lines(str)
430
401
  end
431
402
 
432
403
  def add_line(*args)
433
404
  type = args.shift
434
- args = args.flat_map { |arg| [:string, arg] }
435
- LibHTS.sam_hdr_add_line(@sam_hdr, type, *args, :pointer, FFI::Pointer::NULL)
405
+ pairs = args.each_slice(2).map { |key, value| "#{key}:#{value}" }
406
+ @native.add_lines("@#{type}\t#{pairs.join("\t")}\n")
436
407
  end
437
408
 
438
409
  def initialize_copy(orig)
439
- @sam_hdr = LibHTS.sam_hdr_dup(orig.struct)
410
+ @native = orig.__send__(:native_handle).duplicate
440
411
  end
412
+
413
+ def native_handle = @native
441
414
  end
442
415
  end
443
416
  end