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.
- checksums.yaml +4 -4
- data/README.md +79 -49
- data/TUTORIAL.md +9 -20
- data/ext/htslib_native/extconf.rb +41 -0
- data/ext/htslib_native/htslib_native.h +11 -0
- data/ext/htslib_native/htslib_native_ext.c +48 -0
- data/ext/htslib_native/native_bam.c +1108 -0
- data/ext/htslib_native/native_bcf.c +369 -0
- data/ext/htslib_native/native_faidx.c +155 -0
- data/ext/htslib_native/native_tabix.c +246 -0
- data/lib/hts/bam/auxi.rb +87 -342
- data/lib/hts/bam/base_mod.rb +37 -73
- data/lib/hts/bam/cigar.rb +9 -55
- data/lib/hts/bam/flag.rb +19 -27
- data/lib/hts/bam/header.rb +29 -56
- data/lib/hts/bam/mpileup.rb +158 -132
- data/lib/hts/bam/pileup.rb +120 -145
- data/lib/hts/bam/record.rb +233 -270
- data/lib/hts/bam.rb +102 -42
- data/lib/hts/bcf/errors.rb +1 -0
- data/lib/hts/bcf/format.rb +278 -379
- data/lib/hts/bcf/header.rb +38 -122
- data/lib/hts/bcf/header_record.rb +9 -35
- data/lib/hts/bcf/info.rb +72 -320
- data/lib/hts/bcf/record.rb +61 -102
- data/lib/hts/bcf.rb +149 -323
- data/lib/hts/faidx.rb +28 -87
- data/lib/hts/hts.rb +6 -94
- data/lib/hts/native.rb +13 -0
- data/lib/hts/tabix.rb +93 -48
- data/lib/hts/version.rb +1 -1
- data/lib/htslib.rb +6 -52
- metadata +13 -64
- data/lib/hts/ffi_ext/README.md +0 -8
- data/lib/hts/ffi_ext/pointer.rb +0 -18
- data/lib/hts/ffi_ext/struct.rb +0 -45
- data/lib/hts/libhts/bgzf.rb +0 -199
- data/lib/hts/libhts/constants.rb +0 -658
- data/lib/hts/libhts/cram.rb +0 -471
- data/lib/hts/libhts/fai.rb +0 -146
- data/lib/hts/libhts/hfile.rb +0 -121
- data/lib/hts/libhts/hts.rb +0 -477
- data/lib/hts/libhts/kfunc.rb +0 -38
- data/lib/hts/libhts/sam.rb +0 -760
- data/lib/hts/libhts/sam_funcs.rb +0 -155
- data/lib/hts/libhts/tbx.rb +0 -94
- data/lib/hts/libhts/tbx_funcs.rb +0 -36
- data/lib/hts/libhts/thread_pool.rb +0 -139
- data/lib/hts/libhts/vcf.rb +0 -567
- data/lib/hts/libhts/vcf_funcs.rb +0 -366
- data/lib/hts/libhts.rb +0 -47
data/lib/hts/bam/auxi.rb
CHANGED
|
@@ -1,318 +1,157 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Q. Why is the file name auxi.rb and not aux.rb?
|
|
4
|
-
#
|
|
5
|
-
# A. This is for compatibility with Windows.
|
|
6
|
-
#
|
|
7
|
-
# In Windows, aux is a reserved word
|
|
8
|
-
# You cannot create a file named aux.
|
|
9
|
-
#
|
|
10
|
-
# What?! That's crazy!
|
|
11
|
-
|
|
12
3
|
module HTS
|
|
13
4
|
class Bam < Hts
|
|
14
|
-
# Auxiliary record data
|
|
15
|
-
#
|
|
16
|
-
# @noge Aux is a View object.
|
|
17
|
-
# The result of the alignment is assigned to the bam1 structure.
|
|
18
|
-
# Ruby's Aux class references a part of it. There is no one-to-one
|
|
19
|
-
# correspondence between C structures and Ruby's Aux class.
|
|
20
5
|
class Aux
|
|
21
6
|
include Enumerable
|
|
22
7
|
attr_reader :record
|
|
23
8
|
|
|
24
|
-
def initialize(record)
|
|
25
|
-
@record = record
|
|
26
|
-
end
|
|
9
|
+
def initialize(record) = @record = record
|
|
27
10
|
|
|
28
|
-
# @note Why is this method named "get" instead of "fetch"?
|
|
29
|
-
# This is for compatibility with the Crystal language
|
|
30
|
-
# which provides methods like `get_int`, `get_float`, etc.
|
|
31
|
-
# I think they are better than `fetch_int`` and `fetch_float`.
|
|
32
11
|
def get(key, type = nil)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
get_ruby_aux(aux_ptr, type)
|
|
12
|
+
pair = native.aux_get(key, type&.to_s)
|
|
13
|
+
pair&.last
|
|
37
14
|
end
|
|
38
15
|
|
|
39
|
-
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
|
|
16
|
+
def get_int(key) = get(key, "i")
|
|
17
|
+
def get_float(key) = get(key, "f")
|
|
18
|
+
def get_string(key) = get(key, "Z")
|
|
19
|
+
def [](key) = get(key)
|
|
43
20
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
get(key, "f")
|
|
47
|
-
end
|
|
21
|
+
def each_array(key, &block)
|
|
22
|
+
return enum_for(__method__, key) unless block_given?
|
|
48
23
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
24
|
+
pair = native.aux_get(key, nil)
|
|
25
|
+
return nil unless pair
|
|
26
|
+
raise TypeError, "AUX tag #{key} is not a B array" unless pair.first.start_with?("B:")
|
|
53
27
|
|
|
54
|
-
|
|
55
|
-
|
|
28
|
+
pair.last.each(&block)
|
|
29
|
+
self
|
|
56
30
|
end
|
|
57
31
|
|
|
58
|
-
# Set auxiliary tag value (auto-detects type from value)
|
|
59
|
-
# For compatibility with HTS.cr.
|
|
60
|
-
# @param key [String] tag name (2 characters)
|
|
61
|
-
# @param value [Integer, Float, String, Array] tag value
|
|
62
32
|
def []=(key, value)
|
|
63
33
|
case value
|
|
64
|
-
when Integer
|
|
65
|
-
|
|
66
|
-
when
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
update_string(key, value)
|
|
70
|
-
when Array
|
|
71
|
-
update_array(key, value)
|
|
72
|
-
else
|
|
73
|
-
raise ArgumentError, "Unsupported type: #{value.class}"
|
|
34
|
+
when Integer then update_int(key, value)
|
|
35
|
+
when Float then update_float(key, value)
|
|
36
|
+
when String then update_string(key, value)
|
|
37
|
+
when Array then update_array(key, value)
|
|
38
|
+
else raise ArgumentError, "Unsupported type: #{value.class}"
|
|
74
39
|
end
|
|
75
40
|
end
|
|
76
41
|
|
|
77
|
-
# Update or add an integer tag
|
|
78
|
-
# For compatibility with HTS.cr.
|
|
79
|
-
# @param key [String] tag name (2 characters)
|
|
80
|
-
# @param value [Integer] integer value
|
|
81
42
|
def update_int(key, value)
|
|
82
43
|
validate_tag!(key)
|
|
83
|
-
|
|
84
|
-
raise "Failed to update integer tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
44
|
+
raise "Failed to update integer tag '#{key}'" if native.aux_update_int(key, value.to_i).negative?
|
|
85
45
|
|
|
86
46
|
value
|
|
87
47
|
end
|
|
88
48
|
|
|
89
|
-
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def update_uint8(key, value)
|
|
96
|
-
update_exact_integer(key, value, "C", 0, 255)
|
|
97
|
-
end
|
|
49
|
+
def update_int8(key, value) = update_exact_integer(key, value, "c", -128, 127)
|
|
50
|
+
def update_uint8(key, value) = update_exact_integer(key, value, "C", 0, 255)
|
|
51
|
+
def update_int16(key, value) = update_exact_integer(key, value, "s", -32_768, 32_767)
|
|
52
|
+
def update_uint16(key, value) = update_exact_integer(key, value, "S", 0, 65_535)
|
|
53
|
+
def update_int32(key, value) = update_exact_integer(key, value, "i", -2_147_483_648, 2_147_483_647)
|
|
54
|
+
def update_uint32(key, value) = update_exact_integer(key, value, "I", 0, 4_294_967_295)
|
|
98
55
|
|
|
99
|
-
# Update or add a signed 16-bit integer tag.
|
|
100
|
-
def update_int16(key, value)
|
|
101
|
-
update_exact_integer(key, value, "s", -32_768, 32_767)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Update or add an unsigned 16-bit integer tag.
|
|
105
|
-
def update_uint16(key, value)
|
|
106
|
-
update_exact_integer(key, value, "S", 0, 65_535)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Update or add a signed 32-bit integer tag.
|
|
110
|
-
def update_int32(key, value)
|
|
111
|
-
update_exact_integer(key, value, "i", -2_147_483_648, 2_147_483_647)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Update or add an unsigned 32-bit integer tag.
|
|
115
|
-
def update_uint32(key, value)
|
|
116
|
-
update_exact_integer(key, value, "I", 0, 4_294_967_295)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Update or add a floating-point tag
|
|
120
|
-
# For compatibility with HTS.cr.
|
|
121
|
-
# @param key [String] tag name (2 characters)
|
|
122
|
-
# @param value [Float] floating-point value
|
|
123
56
|
def update_float(key, value)
|
|
124
57
|
validate_tag!(key)
|
|
125
|
-
|
|
126
|
-
raise "Failed to update float tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
58
|
+
raise "Failed to update float tag '#{key}'" if native.aux_update_float(key, value.to_f).negative?
|
|
127
59
|
|
|
128
60
|
value
|
|
129
61
|
end
|
|
130
62
|
|
|
131
|
-
# Update or add a string tag
|
|
132
|
-
# For compatibility with HTS.cr.
|
|
133
|
-
# @param key [String] tag name (2 characters)
|
|
134
|
-
# @param value [String] string value
|
|
135
63
|
def update_string(key, value)
|
|
136
64
|
validate_tag!(key)
|
|
137
65
|
string = value.to_s
|
|
138
66
|
validate_string_value!(string)
|
|
139
|
-
|
|
140
|
-
raise "Failed to update string tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
67
|
+
raise "Failed to update string tag '#{key}'" if native.aux_update_string(key, string).negative?
|
|
141
68
|
|
|
142
69
|
string
|
|
143
70
|
end
|
|
144
71
|
|
|
145
|
-
# Update or add a character tag.
|
|
146
72
|
def update_char(key, value)
|
|
147
73
|
validate_tag!(key)
|
|
148
|
-
|
|
149
74
|
string = value.to_s
|
|
150
75
|
validate_char_value!(string)
|
|
151
|
-
|
|
152
76
|
replace_with_append(key, "A", string.b)
|
|
153
77
|
string
|
|
154
78
|
end
|
|
155
79
|
|
|
156
|
-
# Update or add a hexadecimal string tag.
|
|
157
80
|
def update_hex(key, value)
|
|
158
81
|
validate_tag!(key)
|
|
159
|
-
|
|
160
82
|
string = value.to_s
|
|
161
83
|
validate_hex_value!(string)
|
|
162
|
-
|
|
163
84
|
replace_with_append(key, "H", string.b + "\0")
|
|
164
85
|
string
|
|
165
86
|
end
|
|
166
87
|
|
|
167
|
-
# Update or add a double-precision floating-point tag.
|
|
168
88
|
def update_double(key, value)
|
|
169
89
|
validate_tag!(key)
|
|
170
|
-
|
|
171
90
|
replace_with_append(key, "d", [Float(value)].pack("E"))
|
|
172
91
|
value.to_f
|
|
173
92
|
end
|
|
174
93
|
|
|
175
|
-
# Update or add an array tag
|
|
176
|
-
# For compatibility with HTS.cr.
|
|
177
|
-
# @param key [String] tag name (2 characters)
|
|
178
|
-
# @param value [Array] array of integers or floats
|
|
179
|
-
# @param type [String, nil] element type ('c', 'C', 's', 'S', 'i', 'I', 'f'). Auto-detected if nil.
|
|
180
94
|
def update_array(key, value, type: nil)
|
|
181
95
|
validate_tag!(key)
|
|
182
96
|
raise ArgumentError, "Array cannot be empty" if value.empty?
|
|
183
97
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
payload = pack_array_payload(value, type)
|
|
197
|
-
ptr = FFI::MemoryPointer.new(:uint8, payload.bytesize)
|
|
198
|
-
ptr.put_bytes(0, payload)
|
|
199
|
-
ret = LibHTS.bam_aux_update_array(@record.struct, key, type.ord, value.size, ptr)
|
|
200
|
-
|
|
201
|
-
raise "Failed to update array tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
98
|
+
type ||= if value.all? { |item| item.is_a?(Integer) }
|
|
99
|
+
"i"
|
|
100
|
+
elsif value.all? { |item| item.is_a?(Numeric) }
|
|
101
|
+
"f"
|
|
102
|
+
else
|
|
103
|
+
raise ArgumentError, "Array must contain only integers or floats"
|
|
104
|
+
end
|
|
105
|
+
validate_array!(value, type)
|
|
106
|
+
raise "Failed to update array tag '#{key}'" if native.aux_update_array(key, type, value).negative?
|
|
202
107
|
|
|
203
108
|
value
|
|
204
109
|
end
|
|
205
110
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
# @param key [String] tag name (2 characters)
|
|
209
|
-
# @return [Boolean] true if tag was deleted, false if tag was not found
|
|
210
|
-
def delete(key)
|
|
211
|
-
aux_ptr = LibHTS.bam_aux_get(@record.struct, key)
|
|
212
|
-
return false if aux_ptr.null?
|
|
213
|
-
|
|
214
|
-
ret = LibHTS.bam_aux_del(@record.struct, aux_ptr)
|
|
215
|
-
raise "Failed to delete tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
216
|
-
|
|
217
|
-
true
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Check if a tag exists
|
|
221
|
-
# For compatibility with HTS.cr.
|
|
222
|
-
# @param key [String] tag name (2 characters)
|
|
223
|
-
# @return [Boolean] true if tag exists
|
|
224
|
-
def key?(key)
|
|
225
|
-
aux_ptr = LibHTS.bam_aux_get(@record.struct, key)
|
|
226
|
-
!aux_ptr.null?
|
|
227
|
-
end
|
|
228
|
-
|
|
111
|
+
def delete(key) = native.aux_delete(key)
|
|
112
|
+
def key?(key) = native.aux_key?(key)
|
|
229
113
|
alias include? key?
|
|
230
114
|
|
|
231
|
-
def first
|
|
232
|
-
aux_ptr = first_pointer
|
|
233
|
-
return nil if aux_ptr.null?
|
|
234
|
-
|
|
235
|
-
get_ruby_aux(aux_ptr)
|
|
236
|
-
end
|
|
115
|
+
def first = entries.first&.last
|
|
237
116
|
|
|
238
117
|
def each_value
|
|
239
118
|
return enum_for(__method__) unless block_given?
|
|
240
119
|
|
|
241
|
-
|
|
242
|
-
return nil if aux_ptr.null?
|
|
243
|
-
|
|
244
|
-
loop do
|
|
245
|
-
yield get_ruby_aux(aux_ptr)
|
|
246
|
-
aux_ptr = LibHTS.bam_aux_next(@record.struct, aux_ptr)
|
|
247
|
-
break if aux_ptr.null?
|
|
248
|
-
end
|
|
120
|
+
entries.each { |_, _, value| yield value }
|
|
249
121
|
end
|
|
250
122
|
|
|
251
|
-
# Iterate auxiliary tags as key-value pairs.
|
|
252
|
-
#
|
|
253
|
-
# @yieldparam tag [String] 2-byte AUX tag name
|
|
254
|
-
# @yieldparam value [Object] Ruby representation of the AUX value
|
|
255
123
|
def each
|
|
256
124
|
return enum_for(__method__) unless block_given?
|
|
257
125
|
|
|
258
|
-
|
|
259
|
-
return nil if aux_ptr.null?
|
|
260
|
-
|
|
261
|
-
loop do
|
|
262
|
-
tag = FFI::Pointer.new(aux_ptr.address - 2).read_string(2)
|
|
263
|
-
yield tag, get_ruby_aux(aux_ptr)
|
|
264
|
-
aux_ptr = LibHTS.bam_aux_next(@record.struct, aux_ptr)
|
|
265
|
-
break if aux_ptr.null?
|
|
266
|
-
end
|
|
126
|
+
entries.each { |tag, _, value| yield tag, value }
|
|
267
127
|
end
|
|
268
|
-
|
|
269
128
|
alias each_pair each
|
|
270
129
|
|
|
271
|
-
|
|
272
|
-
#
|
|
273
|
-
# @yieldparam tag [String] 2-byte AUX tag name
|
|
274
|
-
# @yieldparam type [String] AUX type, e.g. "i", "Z", or "B:C"
|
|
275
|
-
# @yieldparam value [Object] Ruby representation of the AUX value
|
|
276
|
-
def each_with_type
|
|
130
|
+
def each_tag_id
|
|
277
131
|
return enum_for(__method__) unless block_given?
|
|
278
132
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
loop do
|
|
283
|
-
tag = FFI::Pointer.new(aux_ptr.address - 2).read_string(2)
|
|
284
|
-
yield tag, aux_type(aux_ptr), get_ruby_aux(aux_ptr)
|
|
285
|
-
aux_ptr = LibHTS.bam_aux_next(@record.struct, aux_ptr)
|
|
286
|
-
break if aux_ptr.null?
|
|
287
|
-
end
|
|
133
|
+
entries.each { |tag, _, value| yield self.class.tag_id(tag), value }
|
|
134
|
+
self
|
|
288
135
|
end
|
|
289
136
|
|
|
290
|
-
def
|
|
291
|
-
|
|
292
|
-
aux_ptr = first_pointer
|
|
293
|
-
return h if aux_ptr.null?
|
|
137
|
+
def self.tag_id(tag)
|
|
138
|
+
raise ArgumentError, "AUX tag must be a 2-byte String" unless tag.is_a?(String) && tag.bytesize == 2
|
|
294
139
|
|
|
295
|
-
|
|
296
|
-
key = FFI::Pointer.new(aux_ptr.address - 2).read_string(2)
|
|
297
|
-
h[key] = get_ruby_aux(aux_ptr)
|
|
298
|
-
aux_ptr = LibHTS.bam_aux_next(@record.struct, aux_ptr)
|
|
299
|
-
break if aux_ptr.null?
|
|
300
|
-
end
|
|
301
|
-
h
|
|
140
|
+
tag.getbyte(0) | (tag.getbyte(1) << 8)
|
|
302
141
|
end
|
|
303
142
|
|
|
304
|
-
|
|
143
|
+
def each_with_type(&block)
|
|
144
|
+
return enum_for(__method__) unless block_given?
|
|
305
145
|
|
|
306
|
-
|
|
307
|
-
LibHTS.bam_aux_first(@record.struct)
|
|
146
|
+
entries.each(&block)
|
|
308
147
|
end
|
|
309
148
|
|
|
310
|
-
def
|
|
311
|
-
type = aux_ptr.read_string(1)
|
|
312
|
-
return type unless type == "B"
|
|
149
|
+
def to_h = entries.to_h { |tag, _, value| [tag, value] }
|
|
313
150
|
|
|
314
|
-
|
|
315
|
-
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def native = @record.__send__(:native_handle)
|
|
154
|
+
def entries = native.aux_entries
|
|
316
155
|
|
|
317
156
|
def validate_tag!(key)
|
|
318
157
|
return if key.is_a?(String) && key.bytesize == 2 && key.ascii_only?
|
|
@@ -321,9 +160,7 @@ module HTS
|
|
|
321
160
|
end
|
|
322
161
|
|
|
323
162
|
def validate_string_value!(string)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
raise ArgumentError, "String AUX tags must not contain NUL bytes"
|
|
163
|
+
raise ArgumentError, "String AUX tags must not contain NUL bytes" if string.include?("\0")
|
|
327
164
|
end
|
|
328
165
|
|
|
329
166
|
def validate_char_value!(string)
|
|
@@ -334,143 +171,51 @@ module HTS
|
|
|
334
171
|
|
|
335
172
|
def validate_hex_value!(string)
|
|
336
173
|
raise ArgumentError, "Hex AUX tags must contain an even number of characters" if string.bytesize.odd?
|
|
337
|
-
|
|
338
174
|
return if string.ascii_only? && /\A[0-9A-Fa-f]*\z/.match?(string)
|
|
339
175
|
|
|
340
|
-
raise ArgumentError,
|
|
341
|
-
"Hex AUX tags must contain only ASCII hexadecimal characters"
|
|
176
|
+
raise ArgumentError, "Hex AUX tags must contain only ASCII hexadecimal characters"
|
|
342
177
|
end
|
|
343
178
|
|
|
344
179
|
def update_exact_integer(key, value, type, min, max)
|
|
345
180
|
validate_tag!(key)
|
|
346
|
-
|
|
347
181
|
integer = Integer(value)
|
|
348
182
|
raise RangeError, "Value #{integer} is out of range for AUX type #{type}" unless integer.between?(min, max)
|
|
349
183
|
|
|
350
|
-
|
|
184
|
+
payload = case type
|
|
185
|
+
when "c" then [integer].pack("c")
|
|
186
|
+
when "C" then [integer].pack("C")
|
|
187
|
+
when "s" then [integer].pack("s<")
|
|
188
|
+
when "S" then [integer].pack("S<")
|
|
189
|
+
when "i" then [integer].pack("l<")
|
|
190
|
+
when "I" then [integer].pack("L<")
|
|
191
|
+
end
|
|
192
|
+
replace_with_append(key, type, payload)
|
|
351
193
|
integer
|
|
352
194
|
end
|
|
353
195
|
|
|
354
196
|
def replace_with_append(key, type, payload)
|
|
355
|
-
delete(key)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
[value].pack("s<")
|
|
373
|
-
when "S"
|
|
374
|
-
[value].pack("S<")
|
|
375
|
-
when "i"
|
|
376
|
-
[value].pack("l<")
|
|
377
|
-
when "I"
|
|
378
|
-
[value].pack("L<")
|
|
379
|
-
else
|
|
380
|
-
raise ArgumentError, "Unsupported scalar AUX type: #{type}"
|
|
381
|
-
end
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
def pack_array_payload(value, type)
|
|
385
|
-
case type
|
|
386
|
-
when "c"
|
|
387
|
-
validate_integer_array_range!(value, -128, 127, type)
|
|
388
|
-
value.pack("c*")
|
|
389
|
-
when "C"
|
|
390
|
-
validate_integer_array_range!(value, 0, 255, type)
|
|
391
|
-
value.pack("C*")
|
|
392
|
-
when "s"
|
|
393
|
-
validate_integer_array_range!(value, -32_768, 32_767, type)
|
|
394
|
-
value.pack("s<*")
|
|
395
|
-
when "S"
|
|
396
|
-
validate_integer_array_range!(value, 0, 65_535, type)
|
|
397
|
-
value.pack("S<*")
|
|
398
|
-
when "i"
|
|
399
|
-
validate_integer_array_range!(value, -2_147_483_648, 2_147_483_647, type)
|
|
400
|
-
value.pack("l<*")
|
|
401
|
-
when "I"
|
|
402
|
-
validate_integer_array_range!(value, 0, 4_294_967_295, type)
|
|
403
|
-
value.pack("L<*")
|
|
404
|
-
when "f"
|
|
405
|
-
value.map(&:to_f).pack("e*")
|
|
406
|
-
else
|
|
407
|
-
raise ArgumentError, "Invalid array type: #{type}"
|
|
408
|
-
end
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
def validate_integer_array_range!(value, min, max, type)
|
|
412
|
-
value.each do |element|
|
|
413
|
-
integer = Integer(element)
|
|
414
|
-
unless integer.between?(min, max)
|
|
415
|
-
raise RangeError, "Array element #{integer} is out of range for AUX array type #{type}"
|
|
416
|
-
end
|
|
417
|
-
end
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
def get_ruby_aux(aux_ptr, type = nil)
|
|
421
|
-
actual_type = aux_ptr.read_string(1)
|
|
422
|
-
type = type ? type.to_s : actual_type
|
|
423
|
-
validate_aux_type!(actual_type, type)
|
|
424
|
-
|
|
425
|
-
# A (character), B (general array),
|
|
426
|
-
# f (real number), H (hexadecimal array),
|
|
427
|
-
# i (integer), or Z (string).
|
|
428
|
-
|
|
429
|
-
case type
|
|
430
|
-
when "i", "I", "c", "C", "s", "S"
|
|
431
|
-
LibHTS.bam_aux2i(aux_ptr)
|
|
432
|
-
when "f", "d"
|
|
433
|
-
LibHTS.bam_aux2f(aux_ptr)
|
|
434
|
-
when "Z", "H"
|
|
435
|
-
LibHTS.bam_aux2Z(aux_ptr)
|
|
436
|
-
when "A" # char
|
|
437
|
-
LibHTS.bam_aux2A(aux_ptr).chr
|
|
438
|
-
when "B" # array
|
|
439
|
-
t2 = aux_ptr.read_string(2)[1] # just a little less efficient
|
|
440
|
-
l = LibHTS.bam_auxB_len(aux_ptr)
|
|
441
|
-
case t2
|
|
442
|
-
when "c", "C", "s", "S", "i", "I"
|
|
443
|
-
# FIXME : Not efficient.
|
|
444
|
-
Array.new(l) { |i| LibHTS.bam_auxB2i(aux_ptr, i) }
|
|
445
|
-
when "f", "d"
|
|
446
|
-
# FIXME : Not efficient.
|
|
447
|
-
Array.new(l) { |i| LibHTS.bam_auxB2f(aux_ptr, i) }
|
|
448
|
-
else
|
|
449
|
-
raise NotImplementedError, "type: #{type} #{t2}"
|
|
197
|
+
delete(key)
|
|
198
|
+
raise "Failed to update #{type} tag '#{key}'" if native.aux_append(key, type, payload).negative?
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def validate_array!(value, type)
|
|
202
|
+
ranges = {
|
|
203
|
+
"c" => (-128..127), "C" => (0..255), "s" => (-32_768..32_767),
|
|
204
|
+
"S" => (0..65_535), "i" => (-2_147_483_648..2_147_483_647),
|
|
205
|
+
"I" => (0..4_294_967_295)
|
|
206
|
+
}
|
|
207
|
+
if (range = ranges[type])
|
|
208
|
+
value.each do |element|
|
|
209
|
+
integer = Integer(element)
|
|
210
|
+
unless range.cover?(integer)
|
|
211
|
+
raise RangeError,
|
|
212
|
+
"Array element #{integer} is out of range for AUX array type #{type}"
|
|
213
|
+
end
|
|
450
214
|
end
|
|
215
|
+
elsif type == "f"
|
|
216
|
+
value.each { |element| Float(element) }
|
|
451
217
|
else
|
|
452
|
-
raise
|
|
453
|
-
end
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
def validate_aux_type!(actual_type, requested_type)
|
|
457
|
-
return if aux_type_compatible?(actual_type, requested_type)
|
|
458
|
-
|
|
459
|
-
raise TypeError, "AUX type mismatch: requested #{requested_type.inspect}, actual #{actual_type.inspect}"
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
def aux_type_compatible?(actual_type, requested_type)
|
|
463
|
-
case requested_type
|
|
464
|
-
when "i", "I", "c", "C", "s", "S"
|
|
465
|
-
%w[i I c C s S].include?(actual_type)
|
|
466
|
-
when "f", "d"
|
|
467
|
-
%w[f d].include?(actual_type)
|
|
468
|
-
when "Z", "H"
|
|
469
|
-
%w[Z H].include?(actual_type)
|
|
470
|
-
when "A", "B"
|
|
471
|
-
actual_type == requested_type
|
|
472
|
-
else
|
|
473
|
-
true
|
|
218
|
+
raise ArgumentError, "Invalid array type: #{type}"
|
|
474
219
|
end
|
|
475
220
|
end
|
|
476
221
|
end
|