htslib 0.3.1 → 0.3.2
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/TUTORIAL.md +45 -0
- data/lib/hts/bam/auxi.rb +118 -0
- data/lib/hts/bcf/info.rb +158 -0
- data/lib/hts/faidx/sequence.rb +1 -1
- data/lib/hts/faidx.rb +82 -28
- data/lib/hts/libhts/constants.rb +7 -1
- data/lib/hts/tabix.rb +22 -0
- data/lib/hts/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ed0d57a77d113e37ce3a9c8bf75ad8e35640a82eb51d7af09f39409c26a04ae
|
|
4
|
+
data.tar.gz: a5a4092321c2245fd4416ffe2b6b50014b5a6af1a40f20a4ca55a1296b96a2ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: abd2f5234927ee1ba553c40194865ccfca47e24338c0859b46c370f248d9f7e602068500d6a49bd80b8cb4a12ef3f3e78efb49ff3d30b628bc1fea519d3d3fd6
|
|
7
|
+
data.tar.gz: af733241e107742ddc702619ac8ab226daa72bdc515c9f591bb3317be1d3f6556070df8afa664066aca6b38466a84cf9c45c4b76e32c68d607ada759818f7f1c
|
data/TUTORIAL.md
CHANGED
|
@@ -254,6 +254,51 @@ in.close
|
|
|
254
254
|
out.close
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
+
Writing and modifying auxiliary tags
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
# Reading auxiliary tags
|
|
261
|
+
bam = HTS::Bam.open("input.bam")
|
|
262
|
+
record = bam.first
|
|
263
|
+
aux = record.aux
|
|
264
|
+
|
|
265
|
+
# Read tags
|
|
266
|
+
alignment_score = aux["AS"] # Auto-detect type
|
|
267
|
+
mc_cigar = aux.get_string("MC") # Type-specific getter
|
|
268
|
+
edit_distance = aux.get_int("NM") # Type-specific getter
|
|
269
|
+
|
|
270
|
+
# Writing/updating auxiliary tags
|
|
271
|
+
in_bam = HTS::Bam.open("input.bam")
|
|
272
|
+
out_bam = HTS::Bam.open("output.bam", "wb")
|
|
273
|
+
out_bam.write_header(in_bam.header)
|
|
274
|
+
|
|
275
|
+
in_bam.each do |record|
|
|
276
|
+
aux = record.aux
|
|
277
|
+
|
|
278
|
+
# Update or add tags using type-specific methods
|
|
279
|
+
aux.update_int("AS", 100) # Integer tag
|
|
280
|
+
aux.update_float("ZQ", 0.95) # Float tag
|
|
281
|
+
aux.update_string("RG", "sample1") # String tag
|
|
282
|
+
aux.update_array("BC", [25, 30, 28, 32]) # Array tag
|
|
283
|
+
|
|
284
|
+
# Or use the []= operator (auto-detects type)
|
|
285
|
+
aux["NM"] = 2 # Integer
|
|
286
|
+
aux["ZS"] = "modified" # String
|
|
287
|
+
aux["ZF"] = 3.14 # Float
|
|
288
|
+
aux["ZA"] = [1, 2, 3, 4] # Array
|
|
289
|
+
|
|
290
|
+
# Check if tag exists
|
|
291
|
+
if aux.key?("XS")
|
|
292
|
+
aux.delete("XS") # Delete tag
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
out_bam.write(record)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
in_bam.close
|
|
299
|
+
out_bam.close
|
|
300
|
+
```
|
|
301
|
+
|
|
257
302
|
Create index
|
|
258
303
|
|
|
259
304
|
```ruby
|
data/lib/hts/bam/auxi.rb
CHANGED
|
@@ -55,6 +55,124 @@ module HTS
|
|
|
55
55
|
get(key)
|
|
56
56
|
end
|
|
57
57
|
|
|
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
|
+
def []=(key, value)
|
|
63
|
+
case value
|
|
64
|
+
when Integer
|
|
65
|
+
update_int(key, value)
|
|
66
|
+
when Float
|
|
67
|
+
update_float(key, value)
|
|
68
|
+
when String
|
|
69
|
+
update_string(key, value)
|
|
70
|
+
when Array
|
|
71
|
+
update_array(key, value)
|
|
72
|
+
else
|
|
73
|
+
raise ArgumentError, "Unsupported type: #{value.class}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
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
|
+
def update_int(key, value)
|
|
82
|
+
ret = LibHTS.bam_aux_update_int(@record.struct, key, value.to_i)
|
|
83
|
+
raise "Failed to update integer tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
84
|
+
|
|
85
|
+
value
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Update or add a floating-point tag
|
|
89
|
+
# For compatibility with HTS.cr.
|
|
90
|
+
# @param key [String] tag name (2 characters)
|
|
91
|
+
# @param value [Float] floating-point value
|
|
92
|
+
def update_float(key, value)
|
|
93
|
+
ret = LibHTS.bam_aux_update_float(@record.struct, key, value.to_f)
|
|
94
|
+
raise "Failed to update float tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
95
|
+
|
|
96
|
+
value
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Update or add a string tag
|
|
100
|
+
# For compatibility with HTS.cr.
|
|
101
|
+
# @param key [String] tag name (2 characters)
|
|
102
|
+
# @param value [String] string value
|
|
103
|
+
def update_string(key, value)
|
|
104
|
+
ret = LibHTS.bam_aux_update_str(@record.struct, key, -1, value.to_s)
|
|
105
|
+
raise "Failed to update string tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
106
|
+
|
|
107
|
+
value
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Update or add an array tag
|
|
111
|
+
# For compatibility with HTS.cr.
|
|
112
|
+
# @param key [String] tag name (2 characters)
|
|
113
|
+
# @param value [Array] array of integers or floats
|
|
114
|
+
# @param type [String, nil] element type ('c', 'C', 's', 'S', 'i', 'I', 'f'). Auto-detected if nil.
|
|
115
|
+
def update_array(key, value, type: nil)
|
|
116
|
+
raise ArgumentError, "Array cannot be empty" if value.empty?
|
|
117
|
+
|
|
118
|
+
# Auto-detect type if not specified
|
|
119
|
+
if type.nil?
|
|
120
|
+
if value.all? { |v| v.is_a?(Integer) }
|
|
121
|
+
# Use 'i' for signed 32-bit integers by default
|
|
122
|
+
type = "i"
|
|
123
|
+
elsif value.all? { |v| v.is_a?(Float) || v.is_a?(Integer) }
|
|
124
|
+
type = "f"
|
|
125
|
+
else
|
|
126
|
+
raise ArgumentError, "Array must contain only integers or floats"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Convert array to appropriate C type
|
|
131
|
+
case type
|
|
132
|
+
when "c", "C", "s", "S", "i", "I"
|
|
133
|
+
# Integer types
|
|
134
|
+
ptr = FFI::MemoryPointer.new(:int32, value.size)
|
|
135
|
+
ptr.write_array_of_int32(value.map(&:to_i))
|
|
136
|
+
ret = LibHTS.bam_aux_update_array(@record.struct, key, type.ord, value.size, ptr)
|
|
137
|
+
when "f"
|
|
138
|
+
# Float type
|
|
139
|
+
ptr = FFI::MemoryPointer.new(:float, value.size)
|
|
140
|
+
ptr.write_array_of_float(value.map(&:to_f))
|
|
141
|
+
ret = LibHTS.bam_aux_update_array(@record.struct, key, type.ord, value.size, ptr)
|
|
142
|
+
else
|
|
143
|
+
raise ArgumentError, "Invalid array type: #{type}"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
raise "Failed to update array tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
147
|
+
|
|
148
|
+
value
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Delete an auxiliary tag
|
|
152
|
+
# For compatibility with HTS.cr.
|
|
153
|
+
# @param key [String] tag name (2 characters)
|
|
154
|
+
# @return [Boolean] true if tag was deleted, false if tag was not found
|
|
155
|
+
def delete(key)
|
|
156
|
+
aux_ptr = LibHTS.bam_aux_get(@record.struct, key)
|
|
157
|
+
return false if aux_ptr.null?
|
|
158
|
+
|
|
159
|
+
ret = LibHTS.bam_aux_del(@record.struct, aux_ptr)
|
|
160
|
+
raise "Failed to delete tag '#{key}': errno #{FFI.errno}" if ret < 0
|
|
161
|
+
|
|
162
|
+
true
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Check if a tag exists
|
|
166
|
+
# For compatibility with HTS.cr.
|
|
167
|
+
# @param key [String] tag name (2 characters)
|
|
168
|
+
# @return [Boolean] true if tag exists
|
|
169
|
+
def key?(key)
|
|
170
|
+
aux_ptr = LibHTS.bam_aux_get(@record.struct, key)
|
|
171
|
+
!aux_ptr.null?
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
alias include? key?
|
|
175
|
+
|
|
58
176
|
def first
|
|
59
177
|
aux_ptr = first_pointer
|
|
60
178
|
return nil if aux_ptr.null?
|
data/lib/hts/bcf/info.rb
CHANGED
|
@@ -74,6 +74,164 @@ module HTS
|
|
|
74
74
|
get(key)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# Set INFO field value with automatic type detection.
|
|
78
|
+
# @param key [String] INFO tag name
|
|
79
|
+
# @param value [Integer, Float, String, Array, true, false, nil] value to set
|
|
80
|
+
# - Integer or Array<Integer> -> update_int
|
|
81
|
+
# - Float or Array<Float,Integer> -> update_float
|
|
82
|
+
# - String -> update_string
|
|
83
|
+
# - true/false -> update_flag
|
|
84
|
+
# - nil -> delete the INFO field
|
|
85
|
+
def []=(key, value)
|
|
86
|
+
case value
|
|
87
|
+
when nil
|
|
88
|
+
delete(key)
|
|
89
|
+
when true, false
|
|
90
|
+
update_flag(key, value)
|
|
91
|
+
when Integer
|
|
92
|
+
update_int(key, [value])
|
|
93
|
+
when Float
|
|
94
|
+
update_float(key, [value])
|
|
95
|
+
when String
|
|
96
|
+
update_string(key, value)
|
|
97
|
+
when Array
|
|
98
|
+
if value.empty?
|
|
99
|
+
raise ArgumentError, "Cannot set INFO field to empty array. Use nil to delete."
|
|
100
|
+
elsif value.all? { |v| v.is_a?(Integer) }
|
|
101
|
+
update_int(key, value)
|
|
102
|
+
elsif value.all? { |v| v.is_a?(Numeric) }
|
|
103
|
+
update_float(key, value)
|
|
104
|
+
else
|
|
105
|
+
raise ArgumentError, "INFO array must contain only integers or floats, got: #{value.map(&:class).uniq}"
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
raise ArgumentError, "Unsupported INFO value type: #{value.class}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Update INFO field with integer value(s).
|
|
113
|
+
# For compatibility with HTS.cr.
|
|
114
|
+
# @param key [String] INFO tag name
|
|
115
|
+
# @param values [Array<Integer>] integer values (use single-element array for scalar)
|
|
116
|
+
def update_int(key, values)
|
|
117
|
+
values = Array(values)
|
|
118
|
+
ptr = FFI::MemoryPointer.new(:int32, values.size)
|
|
119
|
+
ptr.write_array_of_int32(values)
|
|
120
|
+
ret = LibHTS.bcf_update_info(
|
|
121
|
+
@record.header.struct,
|
|
122
|
+
@record.struct,
|
|
123
|
+
key,
|
|
124
|
+
ptr,
|
|
125
|
+
values.size,
|
|
126
|
+
LibHTS::BCF_HT_INT
|
|
127
|
+
)
|
|
128
|
+
raise "Failed to update INFO int field '#{key}': #{ret}" if ret < 0
|
|
129
|
+
|
|
130
|
+
ret
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Update INFO field with float value(s).
|
|
134
|
+
# For compatibility with HTS.cr.
|
|
135
|
+
# @param key [String] INFO tag name
|
|
136
|
+
# @param values [Array<Float>] float values (use single-element array for scalar)
|
|
137
|
+
def update_float(key, values)
|
|
138
|
+
values = Array(values).map(&:to_f)
|
|
139
|
+
ptr = FFI::MemoryPointer.new(:float, values.size)
|
|
140
|
+
ptr.write_array_of_float(values)
|
|
141
|
+
ret = LibHTS.bcf_update_info(
|
|
142
|
+
@record.header.struct,
|
|
143
|
+
@record.struct,
|
|
144
|
+
key,
|
|
145
|
+
ptr,
|
|
146
|
+
values.size,
|
|
147
|
+
LibHTS::BCF_HT_REAL
|
|
148
|
+
)
|
|
149
|
+
raise "Failed to update INFO float field '#{key}': #{ret}" if ret < 0
|
|
150
|
+
|
|
151
|
+
ret
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Update INFO field with string value.
|
|
155
|
+
# For compatibility with HTS.cr.
|
|
156
|
+
# @param key [String] INFO tag name
|
|
157
|
+
# @param value [String] string value
|
|
158
|
+
def update_string(key, value)
|
|
159
|
+
ret = LibHTS.bcf_update_info(
|
|
160
|
+
@record.header.struct,
|
|
161
|
+
@record.struct,
|
|
162
|
+
key,
|
|
163
|
+
value.to_s,
|
|
164
|
+
1,
|
|
165
|
+
LibHTS::BCF_HT_STR
|
|
166
|
+
)
|
|
167
|
+
raise "Failed to update INFO string field '#{key}': #{ret}" if ret < 0
|
|
168
|
+
|
|
169
|
+
ret
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Update INFO flag field.
|
|
173
|
+
# For compatibility with HTS.cr.
|
|
174
|
+
# @param key [String] INFO tag name
|
|
175
|
+
# @param present [Boolean] true to set flag, false to remove it
|
|
176
|
+
def update_flag(key, present = true)
|
|
177
|
+
ret = if present
|
|
178
|
+
LibHTS.bcf_update_info(
|
|
179
|
+
@record.header.struct,
|
|
180
|
+
@record.struct,
|
|
181
|
+
key,
|
|
182
|
+
FFI::Pointer::NULL,
|
|
183
|
+
1,
|
|
184
|
+
LibHTS::BCF_HT_FLAG
|
|
185
|
+
)
|
|
186
|
+
else
|
|
187
|
+
# Remove flag by setting n=0
|
|
188
|
+
LibHTS.bcf_update_info(
|
|
189
|
+
@record.header.struct,
|
|
190
|
+
@record.struct,
|
|
191
|
+
key,
|
|
192
|
+
FFI::Pointer::NULL,
|
|
193
|
+
0,
|
|
194
|
+
LibHTS::BCF_HT_FLAG
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
raise "Failed to update INFO flag field '#{key}': #{ret}" if ret < 0
|
|
198
|
+
|
|
199
|
+
ret
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Delete an INFO field.
|
|
203
|
+
# @param key [String] INFO tag name
|
|
204
|
+
# @return [Boolean] true if field was deleted, false if it didn't exist
|
|
205
|
+
def delete(key)
|
|
206
|
+
# Try to get current type to check existence
|
|
207
|
+
type = get_info_type(key)
|
|
208
|
+
return false if type.nil?
|
|
209
|
+
|
|
210
|
+
# Delete by setting n=0
|
|
211
|
+
ret = LibHTS.bcf_update_info(
|
|
212
|
+
@record.header.struct,
|
|
213
|
+
@record.struct,
|
|
214
|
+
key,
|
|
215
|
+
FFI::Pointer::NULL,
|
|
216
|
+
0,
|
|
217
|
+
type
|
|
218
|
+
)
|
|
219
|
+
return false if ret < 0
|
|
220
|
+
|
|
221
|
+
true
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Check if an INFO field exists.
|
|
225
|
+
# @param key [String] INFO tag name
|
|
226
|
+
# @return [Boolean] true if the field exists
|
|
227
|
+
def key?(key)
|
|
228
|
+
# Use get() to check if value is actually present
|
|
229
|
+
# (get_info_type only checks header, not actual value)
|
|
230
|
+
!get(key).nil?
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
alias include? key?
|
|
234
|
+
|
|
77
235
|
# FIXME: naming? room for improvement.
|
|
78
236
|
def fields
|
|
79
237
|
keys.map do |key|
|
data/lib/hts/faidx/sequence.rb
CHANGED
data/lib/hts/faidx.rb
CHANGED
|
@@ -5,6 +5,8 @@ require_relative "faidx/sequence"
|
|
|
5
5
|
|
|
6
6
|
module HTS
|
|
7
7
|
class Faidx
|
|
8
|
+
include Enumerable
|
|
9
|
+
|
|
8
10
|
attr_reader :file_name
|
|
9
11
|
|
|
10
12
|
def self.open(*args, **kw)
|
|
@@ -20,12 +22,9 @@ module HTS
|
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def initialize(file_name)
|
|
23
|
-
if block_given?
|
|
24
|
-
message = "HTS::Faidx.new() does not take block; Please use HTS::Faidx.open() instead"
|
|
25
|
-
raise message
|
|
26
|
-
end
|
|
25
|
+
raise ArgumentError, "HTS::Faidx.new() does not take block; Please use HTS::Faidx.open() instead" if block_given?
|
|
27
26
|
|
|
28
|
-
@file_name = file_name
|
|
27
|
+
@file_name = file_name.freeze
|
|
29
28
|
@fai = case File.extname(@file_name)
|
|
30
29
|
when ".fq", ".fastq"
|
|
31
30
|
LibHTS.fai_load_format(@file_name, 2)
|
|
@@ -52,75 +51,102 @@ module HTS
|
|
|
52
51
|
end
|
|
53
52
|
|
|
54
53
|
def file_format
|
|
54
|
+
check_closed
|
|
55
55
|
@fai[:format]
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# Iterate over each sequence in the index.
|
|
59
|
+
# @yield [Sequence] each sequence object
|
|
60
|
+
# @return [Enumerator] if no block given
|
|
61
|
+
def each
|
|
62
|
+
return to_enum(__method__) unless block_given?
|
|
63
|
+
|
|
64
|
+
check_closed
|
|
65
|
+
names.each { |name| yield self[name] }
|
|
66
|
+
end
|
|
67
|
+
|
|
58
68
|
# the number of sequences in the index.
|
|
69
|
+
# @return [Integer] the number of sequences
|
|
59
70
|
def length
|
|
71
|
+
check_closed
|
|
60
72
|
LibHTS.faidx_nseq(@fai)
|
|
61
73
|
end
|
|
62
74
|
alias size length
|
|
63
75
|
|
|
64
|
-
#
|
|
76
|
+
# Return the list of sequence names in the index.
|
|
77
|
+
# @return [Array<String>] sequence names
|
|
65
78
|
def names
|
|
79
|
+
check_closed
|
|
66
80
|
Array.new(length) { |i| LibHTS.faidx_iseq(@fai, i) }
|
|
67
81
|
end
|
|
68
82
|
|
|
69
83
|
alias keys names
|
|
70
84
|
|
|
85
|
+
# Check if a sequence exists in the index.
|
|
86
|
+
# @param key [String, Symbol] sequence name
|
|
87
|
+
# @return [Boolean] true if the sequence exists
|
|
71
88
|
def has_key?(key)
|
|
89
|
+
check_closed
|
|
72
90
|
raise ArgumentError, "Expect chrom to be String or Symbol" unless key.is_a?(String) || key.is_a?(Symbol)
|
|
73
91
|
|
|
74
92
|
key = key.to_s
|
|
75
93
|
case LibHTS.faidx_has_seq(@fai, key)
|
|
76
94
|
when 1 then true
|
|
77
95
|
when 0 then false
|
|
78
|
-
else raise
|
|
96
|
+
else raise HTS::Error, "Unexpected return value from faidx_has_seq"
|
|
79
97
|
end
|
|
80
98
|
end
|
|
81
99
|
|
|
100
|
+
# Get a Sequence object by name or index.
|
|
101
|
+
# @param name [String, Symbol, Integer] sequence name or index
|
|
102
|
+
# @return [Sequence] the sequence object
|
|
103
|
+
# @raise [ArgumentError] if the sequence does not exist
|
|
82
104
|
def [](name)
|
|
105
|
+
check_closed
|
|
83
106
|
name = LibHTS.faidx_iseq(@fai, name) if name.is_a?(Integer)
|
|
84
107
|
Sequence.new(self, name)
|
|
85
108
|
end
|
|
86
109
|
|
|
87
|
-
#
|
|
110
|
+
# Return the length of the requested chromosome.
|
|
111
|
+
# @param chrom [String, Symbol] chromosome name
|
|
112
|
+
# @return [Integer] sequence length
|
|
113
|
+
# @raise [ArgumentError] if the sequence does not exist
|
|
88
114
|
def seq_len(chrom)
|
|
115
|
+
check_closed
|
|
89
116
|
raise ArgumentError, "Expect chrom to be String or Symbol" unless chrom.is_a?(String) || chrom.is_a?(Symbol)
|
|
90
117
|
|
|
91
118
|
chrom = chrom.to_s
|
|
92
119
|
result = LibHTS.faidx_seq_len(@fai, chrom)
|
|
93
|
-
|
|
120
|
+
raise ArgumentError, "Sequence not found: #{chrom}" if result == -1
|
|
121
|
+
|
|
122
|
+
result
|
|
94
123
|
end
|
|
95
124
|
|
|
96
|
-
# @overload
|
|
125
|
+
# @overload fetch_seq(name)
|
|
97
126
|
# Fetch the sequence as a String.
|
|
98
|
-
# @param name [String] chr1:0-10
|
|
99
|
-
#
|
|
127
|
+
# @param name [String, Symbol] chr1:0-10
|
|
128
|
+
# @return [String] the sequence
|
|
129
|
+
# @overload fetch_seq(name, start, stop)
|
|
100
130
|
# Fetch the sequence as a String.
|
|
101
|
-
# @param name [String] the name of the chromosome
|
|
131
|
+
# @param name [String, Symbol] the name of the chromosome
|
|
102
132
|
# @param start [Integer] the start position of the sequence (0-based)
|
|
103
133
|
# @param stop [Integer] the end position of the sequence (0-based)
|
|
104
134
|
# @return [String] the sequence
|
|
105
|
-
|
|
106
135
|
def fetch_seq(name, start = nil, stop = nil)
|
|
136
|
+
check_closed
|
|
107
137
|
name = name.to_s
|
|
108
138
|
rlen = FFI::MemoryPointer.new(:int)
|
|
109
139
|
|
|
110
140
|
if start.nil? && stop.nil?
|
|
111
141
|
result = LibHTS.fai_fetch64(@fai, name, rlen)
|
|
112
142
|
else
|
|
113
|
-
|
|
114
|
-
stop < 0 && raise(ArgumentError, "Expect stop to be >= 0")
|
|
115
|
-
start > stop && raise(ArgumentError, "Expect start to be <= stop")
|
|
116
|
-
stop >= seq_len(name) && raise(ArgumentError, "Expect stop to be < seq_len")
|
|
117
|
-
|
|
143
|
+
validate_range!(name, start, stop)
|
|
118
144
|
result = LibHTS.faidx_fetch_seq64(@fai, name, start, stop, rlen)
|
|
119
145
|
end
|
|
120
146
|
|
|
121
147
|
case rlen.read_int
|
|
122
|
-
when -2 then raise "Invalid chromosome name: #{name}"
|
|
123
|
-
when -1 then raise "Error fetching sequence: #{name}:#{start}-#{stop}"
|
|
148
|
+
when -2 then raise ArgumentError, "Invalid chromosome name: #{name}"
|
|
149
|
+
when -1 then raise HTS::Error, "Error fetching sequence: #{name}:#{start}-#{stop}"
|
|
124
150
|
end
|
|
125
151
|
|
|
126
152
|
result
|
|
@@ -128,29 +154,57 @@ module HTS
|
|
|
128
154
|
|
|
129
155
|
alias seq fetch_seq
|
|
130
156
|
|
|
157
|
+
# @overload fetch_qual(name)
|
|
158
|
+
# Fetch the quality string.
|
|
159
|
+
# @param name [String, Symbol] sequence name
|
|
160
|
+
# @return [String] the quality string
|
|
161
|
+
# @overload fetch_qual(name, start, stop)
|
|
162
|
+
# Fetch the quality string.
|
|
163
|
+
# @param name [String, Symbol] the name of the chromosome
|
|
164
|
+
# @param start [Integer] the start position of the sequence (0-based)
|
|
165
|
+
# @param stop [Integer] the end position of the sequence (0-based)
|
|
166
|
+
# @return [String] the quality string
|
|
131
167
|
def fetch_qual(name, start = nil, stop = nil)
|
|
168
|
+
check_closed
|
|
132
169
|
name = name.to_s
|
|
133
170
|
rlen = FFI::MemoryPointer.new(:int)
|
|
134
171
|
|
|
135
172
|
if start.nil? && stop.nil?
|
|
136
173
|
result = LibHTS.fai_fetchqual64(@fai, name, rlen)
|
|
137
174
|
else
|
|
138
|
-
|
|
139
|
-
stop < 0 && raise(ArgumentError, "Expect stop to be >= 0")
|
|
140
|
-
start > stop && raise(ArgumentError, "Expect start to be <= stop")
|
|
141
|
-
stop >= seq_len(name) && raise(ArgumentError, "Expect stop to be < seq_len")
|
|
142
|
-
|
|
175
|
+
validate_range!(name, start, stop)
|
|
143
176
|
result = LibHTS.faidx_fetch_qual64(@fai, name, start, stop, rlen)
|
|
144
177
|
end
|
|
145
178
|
|
|
146
179
|
case rlen.read_int
|
|
147
|
-
when -2 then raise "Invalid chromosome name: #{name}"
|
|
148
|
-
when -1 then raise "Error fetching
|
|
180
|
+
when -2 then raise ArgumentError, "Invalid chromosome name: #{name}"
|
|
181
|
+
when -1 then raise HTS::Error, "Error fetching quality: #{name}:#{start}-#{stop}"
|
|
149
182
|
end
|
|
150
183
|
|
|
151
184
|
result
|
|
152
185
|
end
|
|
153
186
|
|
|
154
187
|
alias qual fetch_qual
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
def check_closed
|
|
192
|
+
raise IOError, "closed Faidx" if closed?
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Validate range parameters.
|
|
196
|
+
# @param name [String] sequence name
|
|
197
|
+
# @param start [Integer] start position (0-based)
|
|
198
|
+
# @param stop [Integer] stop position (0-based)
|
|
199
|
+
# @raise [ArgumentError] if range is invalid
|
|
200
|
+
def validate_range!(name, start, stop)
|
|
201
|
+
raise ArgumentError, "Expect start to be >= 0" if start < 0
|
|
202
|
+
raise ArgumentError, "Expect stop to be >= 0" if stop < 0
|
|
203
|
+
raise ArgumentError, "Expect start to be <= stop" if start > stop
|
|
204
|
+
|
|
205
|
+
len = seq_len(name)
|
|
206
|
+
raise ArgumentError, "Sequence not found: #{name}" if len.nil?
|
|
207
|
+
raise ArgumentError, "Expect stop to be < seq_len (#{len})" if stop >= len
|
|
208
|
+
end
|
|
155
209
|
end
|
|
156
210
|
end
|
data/lib/hts/libhts/constants.rb
CHANGED
|
@@ -439,7 +439,13 @@ module HTS
|
|
|
439
439
|
|
|
440
440
|
FaiFormatOptions = enum(:FAI_NONE, :FAI_FASTA, :FAI_FASTQ)
|
|
441
441
|
|
|
442
|
-
|
|
442
|
+
# Faidx represents a faidx_t handle which is treated as a
|
|
443
|
+
# file-level RAII object in HTS::Faidx. It is intentionally
|
|
444
|
+
# kept as a plain Struct and is destroyed explicitly via
|
|
445
|
+
# LibHTS.fai_destroy in HTS::Faidx#close. Do not convert this
|
|
446
|
+
# to ManagedStruct; that would interfere with the explicit
|
|
447
|
+
# lifetime managed by the Ruby wrapper.
|
|
448
|
+
class Faidx < FFI::Struct
|
|
443
449
|
layout :bgzf, BGZF.ptr,
|
|
444
450
|
:n, :int,
|
|
445
451
|
:m, :int,
|
data/lib/hts/tabix.rb
CHANGED
|
@@ -68,6 +68,7 @@ module HTS
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def load_index(index_name = nil)
|
|
71
|
+
check_closed
|
|
71
72
|
if index_name
|
|
72
73
|
LibHTS.tbx_index_load2(@file_name, index_name)
|
|
73
74
|
else
|
|
@@ -76,14 +77,17 @@ module HTS
|
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
def index_loaded?
|
|
80
|
+
check_closed
|
|
79
81
|
!@idx.null?
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def name2id(name)
|
|
85
|
+
check_closed
|
|
83
86
|
LibHTS.tbx_name2id(@idx, name)
|
|
84
87
|
end
|
|
85
88
|
|
|
86
89
|
def seqnames
|
|
90
|
+
check_closed
|
|
87
91
|
nseq = FFI::MemoryPointer.new(:int)
|
|
88
92
|
LibHTS.tbx_seqnames(@idx, nseq).then do |pts|
|
|
89
93
|
pts.read_array_of_pointer(nseq.read_int).map(&:read_string)
|
|
@@ -101,6 +105,20 @@ module HTS
|
|
|
101
105
|
end
|
|
102
106
|
end
|
|
103
107
|
|
|
108
|
+
def close
|
|
109
|
+
return if closed?
|
|
110
|
+
|
|
111
|
+
# @idx is an internal index (LibHTS::Tbx, a ManagedStruct).
|
|
112
|
+
# Do not call tbx_destroy here; the FFI finalizer will
|
|
113
|
+
# release the underlying C struct when @idx becomes unreachable.
|
|
114
|
+
@idx = nil
|
|
115
|
+
super
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def closed?
|
|
119
|
+
@hts_file.nil? || @hts_file.null?
|
|
120
|
+
end
|
|
121
|
+
|
|
104
122
|
private
|
|
105
123
|
|
|
106
124
|
def queryi(id, start, end_, &block)
|
|
@@ -131,5 +149,9 @@ module HTS
|
|
|
131
149
|
LibHTS.hts_itr_destroy(qiter)
|
|
132
150
|
end
|
|
133
151
|
end
|
|
152
|
+
|
|
153
|
+
def check_closed
|
|
154
|
+
raise IOError, "closed Tabix" if closed?
|
|
155
|
+
end
|
|
134
156
|
end
|
|
135
157
|
end
|
data/lib/hts/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: htslib
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kojix2
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2025-11-27 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ffi
|
|
@@ -119,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
119
119
|
- !ruby/object:Gem::Version
|
|
120
120
|
version: '0'
|
|
121
121
|
requirements: []
|
|
122
|
-
rubygems_version: 3.6.
|
|
122
|
+
rubygems_version: 3.6.2
|
|
123
123
|
specification_version: 4
|
|
124
124
|
summary: HTSlib bindings for Ruby
|
|
125
125
|
test_files: []
|