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/faidx.rb
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
4
|
-
|
|
5
|
-
module HTS
|
|
6
|
-
module LibC
|
|
7
|
-
extend FFI::Library
|
|
8
|
-
ffi_lib FFI::Library::LIBC
|
|
9
|
-
attach_function :free, [:pointer], :void
|
|
10
|
-
end
|
|
11
|
-
end
|
|
3
|
+
require_relative "native"
|
|
12
4
|
|
|
13
5
|
module HTS
|
|
14
6
|
class Faidx
|
|
15
7
|
attr_reader :file_name, :format
|
|
16
8
|
|
|
17
9
|
def self.open(file_name, format: :auto, auto_build: true)
|
|
18
|
-
file = new(file_name, format:, auto_build:)
|
|
10
|
+
file = new(file_name, format:, auto_build:)
|
|
19
11
|
return file unless block_given?
|
|
20
12
|
|
|
21
13
|
begin
|
|
@@ -27,10 +19,9 @@ module HTS
|
|
|
27
19
|
end
|
|
28
20
|
|
|
29
21
|
def self.build_index(file_name, fai_path = nil, gzi_path = nil)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
end
|
|
22
|
+
return if Native::FaidxHandle.build(file_name, fai_path, gzi_path).zero?
|
|
23
|
+
|
|
24
|
+
raise HTS::Error, "Failed to build faidx index for #{file_name}"
|
|
34
25
|
end
|
|
35
26
|
|
|
36
27
|
def initialize(file_name, format: :auto, auto_build: true)
|
|
@@ -38,95 +29,62 @@ module HTS
|
|
|
38
29
|
|
|
39
30
|
@file_name = file_name.freeze
|
|
40
31
|
@format = resolve_format(@file_name, format)
|
|
41
|
-
@
|
|
42
|
-
|
|
43
|
-
raise Errno::ENOENT, "Failed to open #{@file_name}"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def struct
|
|
47
|
-
@fai
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def close
|
|
51
|
-
return if closed?
|
|
52
|
-
|
|
53
|
-
LibHTS.fai_destroy(@fai)
|
|
54
|
-
@fai = nil
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def closed?
|
|
58
|
-
@fai.nil? || @fai.null?
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def size
|
|
62
|
-
check_closed
|
|
63
|
-
LibHTS.faidx_nseq(@fai)
|
|
32
|
+
@native = Native::FaidxHandle.open(@file_name, @format == :fastq ? 1 : 0, auto_build)
|
|
33
|
+
rescue Errno::ENOENT
|
|
34
|
+
raise Errno::ENOENT, "Failed to open #{@file_name}"
|
|
64
35
|
end
|
|
65
36
|
|
|
37
|
+
def close = @native&.close
|
|
38
|
+
def closed? = @native.nil? || @native.closed?
|
|
39
|
+
def size = native.size
|
|
66
40
|
alias length size
|
|
67
|
-
|
|
68
|
-
def names
|
|
69
|
-
check_closed
|
|
70
|
-
Array.new(length) { |i| LibHTS.faidx_iseq(@fai, i) }
|
|
71
|
-
end
|
|
41
|
+
def names = native.names
|
|
72
42
|
|
|
73
43
|
def has_seq?(key)
|
|
74
|
-
check_closed
|
|
75
44
|
raise ArgumentError, "Expect chrom to be String or Symbol" unless key.is_a?(String) || key.is_a?(Symbol)
|
|
76
45
|
|
|
77
|
-
key
|
|
78
|
-
case LibHTS.faidx_has_seq(@fai, key)
|
|
79
|
-
when 1 then true
|
|
80
|
-
when 0 then false
|
|
81
|
-
else raise HTS::Error, "Unexpected return value from faidx_has_seq"
|
|
82
|
-
end
|
|
46
|
+
native.has_seq?(key.to_s)
|
|
83
47
|
end
|
|
84
48
|
|
|
85
49
|
def seq_len(chrom)
|
|
86
|
-
check_closed
|
|
87
50
|
raise ArgumentError, "Expect chrom to be String or Symbol" unless chrom.is_a?(String) || chrom.is_a?(Symbol)
|
|
88
51
|
|
|
89
52
|
chrom = chrom.to_s
|
|
90
|
-
result =
|
|
53
|
+
result = native.seq_len(chrom)
|
|
91
54
|
raise ArgumentError, "Sequence not found: #{chrom}" if result == -1
|
|
92
55
|
|
|
93
56
|
result
|
|
94
57
|
end
|
|
95
58
|
|
|
96
59
|
def fetch_seq(name, start = nil, stop = nil)
|
|
97
|
-
check_closed
|
|
98
60
|
name = name.to_s
|
|
99
|
-
|
|
100
61
|
if start.nil? && stop.nil?
|
|
101
62
|
len = seq_len(name)
|
|
102
63
|
return "" if len.zero?
|
|
103
64
|
|
|
104
|
-
|
|
65
|
+
start = 0
|
|
66
|
+
stop = len - 1
|
|
105
67
|
else
|
|
106
68
|
validate_range!(name, start, stop)
|
|
107
|
-
rlen = FFI::MemoryPointer.new(:int64)
|
|
108
|
-
result = LibHTS.faidx_fetch_seq64(@fai, name, start, stop, rlen)
|
|
109
|
-
fetch_result(result, rlen.read_int64, "sequence", name, start, stop)
|
|
110
69
|
end
|
|
70
|
+
fetch_result(native.fetch_seq(name, start, stop), "sequence", name, start, stop)
|
|
111
71
|
end
|
|
112
72
|
|
|
113
73
|
def fetch_qual(name, start = nil, stop = nil)
|
|
114
|
-
|
|
74
|
+
native
|
|
115
75
|
raise HTS::Error, "Quality is only available for FASTQ indexes" unless format == :fastq
|
|
116
76
|
|
|
117
77
|
name = name.to_s
|
|
118
|
-
|
|
119
78
|
if start.nil? && stop.nil?
|
|
120
79
|
len = seq_len(name)
|
|
121
80
|
return "" if len.zero?
|
|
122
81
|
|
|
123
|
-
|
|
82
|
+
start = 0
|
|
83
|
+
stop = len - 1
|
|
124
84
|
else
|
|
125
85
|
validate_range!(name, start, stop)
|
|
126
|
-
rlen = FFI::MemoryPointer.new(:int64)
|
|
127
|
-
result = LibHTS.faidx_fetch_qual64(@fai, name, start, stop, rlen)
|
|
128
|
-
fetch_result(result, rlen.read_int64, "quality", name, start, stop)
|
|
129
86
|
end
|
|
87
|
+
fetch_result(native.fetch_qual(name, start, stop), "quality", name, start, stop)
|
|
130
88
|
end
|
|
131
89
|
|
|
132
90
|
def build_index(fai_path = nil, gzi_path = nil)
|
|
@@ -136,8 +94,10 @@ module HTS
|
|
|
136
94
|
|
|
137
95
|
private
|
|
138
96
|
|
|
139
|
-
def
|
|
97
|
+
def native
|
|
140
98
|
raise IOError, "closed Faidx" if closed?
|
|
99
|
+
|
|
100
|
+
@native
|
|
141
101
|
end
|
|
142
102
|
|
|
143
103
|
def validate_range!(name, start, stop)
|
|
@@ -149,34 +109,15 @@ module HTS
|
|
|
149
109
|
raise ArgumentError, "Expect stop to be < seq_len (#{len})" if stop >= len
|
|
150
110
|
end
|
|
151
111
|
|
|
152
|
-
def fetch_result(
|
|
112
|
+
def fetch_result(result, kind, name, start, stop)
|
|
113
|
+
len, string = result
|
|
153
114
|
case len
|
|
154
115
|
when -2 then raise ArgumentError, "Sequence not found: #{name}"
|
|
155
116
|
when -1 then raise HTS::Error, "Error fetching #{kind}: #{name}:#{start}-#{stop}"
|
|
156
117
|
end
|
|
118
|
+
raise HTS::Error, "Error fetching #{kind}: #{name}:#{start}-#{stop}" unless string
|
|
157
119
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
begin
|
|
161
|
-
ptr.read_string_length(len)
|
|
162
|
-
ensure
|
|
163
|
-
HTS::LibC.free(ptr)
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def load_handle(file_name, format, auto_build)
|
|
168
|
-
case [format, auto_build]
|
|
169
|
-
when [:fasta, true]
|
|
170
|
-
LibHTS.fai_load_format(file_name, :FAI_FASTA)
|
|
171
|
-
when [:fastq, true]
|
|
172
|
-
LibHTS.fai_load_format(file_name, :FAI_FASTQ)
|
|
173
|
-
when [:fasta, false]
|
|
174
|
-
LibHTS.fai_load3_format(file_name, nil, nil, 0, :FAI_FASTA)
|
|
175
|
-
when [:fastq, false]
|
|
176
|
-
LibHTS.fai_load3_format(file_name, nil, nil, 0, :FAI_FASTQ)
|
|
177
|
-
else
|
|
178
|
-
raise ArgumentError, "Unsupported format: #{format}"
|
|
179
|
-
end
|
|
120
|
+
string
|
|
180
121
|
end
|
|
181
122
|
|
|
182
123
|
def resolve_format(file_name, format)
|
data/lib/hts/hts.rb
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
require_relative "../htslib"
|
|
4
4
|
|
|
5
5
|
module HTS
|
|
6
|
-
#
|
|
6
|
+
# Shared behavior for HTS-backed file classes. Native handles remain private
|
|
7
|
+
# implementation details of the concrete classes.
|
|
7
8
|
class Hts
|
|
8
9
|
class << self
|
|
9
10
|
private
|
|
@@ -12,10 +13,11 @@ module HTS
|
|
|
12
13
|
define_method(name) do
|
|
13
14
|
check_closed
|
|
14
15
|
position = tell
|
|
15
|
-
|
|
16
|
+
values = map(&name)
|
|
16
17
|
seek(position) if position
|
|
17
|
-
|
|
18
|
+
values
|
|
18
19
|
end
|
|
20
|
+
alias_method "#{name}_array", name
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def define_iterator(name)
|
|
@@ -23,9 +25,7 @@ module HTS
|
|
|
23
25
|
check_closed
|
|
24
26
|
return to_enum(__method__) unless block
|
|
25
27
|
|
|
26
|
-
each
|
|
27
|
-
block.call(record.public_send(name))
|
|
28
|
-
end
|
|
28
|
+
each { |record| block.call(record.public_send(name)) }
|
|
29
29
|
self
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -35,94 +35,6 @@ module HTS
|
|
|
35
35
|
raise TypeError, "Can't make instance of HTS abstract class"
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
def struct
|
|
39
|
-
@hts_file
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def to_ptr
|
|
43
|
-
@hts_file.to_ptr
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def file_format
|
|
47
|
-
LibHTS.hts_get_format(@hts_file)[:format].to_s
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def file_format_version
|
|
51
|
-
v = LibHTS.hts_get_format(@hts_file)[:version]
|
|
52
|
-
major = v[:major]
|
|
53
|
-
minor = v[:minor]
|
|
54
|
-
if minor == -1
|
|
55
|
-
major.to_s
|
|
56
|
-
else
|
|
57
|
-
"#{major}.#{minor}"
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def close
|
|
62
|
-
return if closed?
|
|
63
|
-
|
|
64
|
-
LibHTS.hts_close(@hts_file)
|
|
65
|
-
@hts_file = nil
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def closed?
|
|
69
|
-
@hts_file.nil? || @hts_file.null?
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def fai=(fai)
|
|
73
|
-
check_closed
|
|
74
|
-
r = LibHTS.hts_set_fai_filename(@hts_file, fai)
|
|
75
|
-
raise "Failed to load fasta index: #{fai}" if r.negative?
|
|
76
|
-
|
|
77
|
-
self
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def set_threads(n = nil)
|
|
81
|
-
if n.nil?
|
|
82
|
-
require "etc"
|
|
83
|
-
n = [Etc.nprocessors - 1, 1].max
|
|
84
|
-
end
|
|
85
|
-
raise TypeError unless n.is_a?(Integer)
|
|
86
|
-
raise ArgumentError, "Number of threads must be positive" if n < 1
|
|
87
|
-
|
|
88
|
-
r = LibHTS.hts_set_threads(@hts_file, n)
|
|
89
|
-
raise "Failed to set number of threads: #{threads}" if r < 0
|
|
90
|
-
|
|
91
|
-
@nthreads = n
|
|
92
|
-
self
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def seek(offset)
|
|
96
|
-
if @hts_file[:is_cram] == 1
|
|
97
|
-
LibHTS.cram_seek(@hts_file[:fp][:cram], offset, IO::SEEK_SET)
|
|
98
|
-
elsif @hts_file[:is_bgzf] == 1
|
|
99
|
-
LibHTS.bgzf_seek(@hts_file[:fp][:bgzf], offset, IO::SEEK_SET)
|
|
100
|
-
else
|
|
101
|
-
LibHTS.hseek(@hts_file[:fp][:hfile], offset, IO::SEEK_SET)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def tell
|
|
106
|
-
if @hts_file[:is_cram] == 1
|
|
107
|
-
# LibHTS.cram_tell(@hts_file[:fp][:cram])
|
|
108
|
-
# warn 'cram_tell is not implemented in c htslib'
|
|
109
|
-
nil
|
|
110
|
-
elsif @hts_file[:is_bgzf] == 1
|
|
111
|
-
LibHTS.bgzf_tell(@hts_file[:fp][:bgzf])
|
|
112
|
-
else
|
|
113
|
-
LibHTS.htell(@hts_file[:fp][:hfile])
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def rewind
|
|
118
|
-
raise "Cannot rewind: no start position" unless @start_position
|
|
119
|
-
|
|
120
|
-
r = seek(@start_position)
|
|
121
|
-
raise "Failed to rewind: #{r}" if r < 0
|
|
122
|
-
|
|
123
|
-
tell
|
|
124
|
-
end
|
|
125
|
-
|
|
126
38
|
private
|
|
127
39
|
|
|
128
40
|
def check_closed
|
data/lib/hts/native.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "htslib_native_ext"
|
|
5
|
+
rescue LoadError => e
|
|
6
|
+
raise LoadError, <<~MESSAGE
|
|
7
|
+
#{e.message}
|
|
8
|
+
ruby-htslib requires its native extension linked against HTSlib.
|
|
9
|
+
Install the HTSlib development package and reinstall the gem. If HTSlib is
|
|
10
|
+
in a non-standard prefix, use --with-htslib-dir or set HTSLIBDIR while
|
|
11
|
+
building the gem.
|
|
12
|
+
MESSAGE
|
|
13
|
+
end
|
data/lib/hts/tabix.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "../htslib"
|
|
4
4
|
|
|
5
5
|
require_relative "hts"
|
|
6
|
+
require_relative "native"
|
|
6
7
|
|
|
7
8
|
module HTS
|
|
8
9
|
class Tabix < Hts
|
|
@@ -34,14 +35,13 @@ module HTS
|
|
|
34
35
|
@index_name = index
|
|
35
36
|
@mode = "r"
|
|
36
37
|
@nthreads = threads
|
|
37
|
-
@
|
|
38
|
-
|
|
39
|
-
raise Errno::ENOENT, "Failed to open #{@file_name}" if @hts_file.null?
|
|
38
|
+
@index_load_attempted = false
|
|
39
|
+
@native = Native::TabixHandle.open(@file_name)
|
|
40
40
|
|
|
41
41
|
set_threads(threads) if threads
|
|
42
42
|
|
|
43
43
|
# build_index(index) if build_index
|
|
44
|
-
|
|
44
|
+
load_index(index) if index
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def build_index(index_name = nil, min_shift: 0)
|
|
@@ -49,7 +49,7 @@ module HTS
|
|
|
49
49
|
|
|
50
50
|
if index_name
|
|
51
51
|
warn "Create index for #{@file_name} to #{index_name}"
|
|
52
|
-
case
|
|
52
|
+
case Native::TabixHandle.build(@file_name, index_name, min_shift)
|
|
53
53
|
when 0 # successful
|
|
54
54
|
when -1 then raise "general failure"
|
|
55
55
|
when -2 then raise "compression not BGZF"
|
|
@@ -57,104 +57,149 @@ module HTS
|
|
|
57
57
|
end
|
|
58
58
|
else
|
|
59
59
|
warn "Create index for #{@file_name}"
|
|
60
|
-
case
|
|
60
|
+
case Native::TabixHandle.build(@file_name, nil, min_shift)
|
|
61
61
|
when 0 # successful
|
|
62
62
|
when -1 then raise "general failure"
|
|
63
63
|
when -2 then raise "compression not BGZF"
|
|
64
64
|
else raise "unknown error"
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
|
+
@index_name = index_name
|
|
68
|
+
@index_load_attempted = false
|
|
67
69
|
self # for method chaining
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
def load_index(index_name = nil)
|
|
71
73
|
check_closed
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
LibHTS.tbx_index_load3(@file_name, nil, 2)
|
|
76
|
-
end
|
|
74
|
+
@index_name = index_name
|
|
75
|
+
@index_load_attempted = true
|
|
76
|
+
@native.load_index(index_name)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def index_loaded?
|
|
80
80
|
check_closed
|
|
81
|
-
|
|
81
|
+
@native.index_loaded?
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
def name2id(name)
|
|
85
85
|
check_closed
|
|
86
|
-
|
|
86
|
+
raise "Index file is required to call the name2id method." unless ensure_index_loaded
|
|
87
|
+
|
|
88
|
+
@native.name2id(name)
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
def seqnames
|
|
90
92
|
check_closed
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
pts.read_array_of_pointer(nseq.read_int).map(&:read_string)
|
|
95
|
-
ensure
|
|
96
|
-
LibHTS.hts_free(pts) unless pts.null?
|
|
97
|
-
end
|
|
93
|
+
raise "Index file is required to call the seqnames method." unless ensure_index_loaded
|
|
94
|
+
|
|
95
|
+
@native.seqnames
|
|
98
96
|
end
|
|
99
97
|
|
|
100
98
|
def query(region, start = nil, end_ = nil, &block)
|
|
99
|
+
query_with_mode(region, start, end_, :fields, nil, &block)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Explicit name for the legacy split-fields API.
|
|
103
|
+
def each_fields(region, start = nil, end_ = nil, &block)
|
|
104
|
+
query_with_mode(region, start, end_, :fields, nil, &block)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Iterate raw rows, allocating one String per row and no field Array.
|
|
108
|
+
def each_line(region, start = nil, end_ = nil, &block)
|
|
109
|
+
query_with_mode(region, start, end_, :line, nil, &block)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Extract only requested zero-based columns without materializing unused
|
|
113
|
+
# field strings. This Ruby implementation scans tab delimiters once.
|
|
114
|
+
def each_selected_fields(region, *columns, &block)
|
|
115
|
+
raise ArgumentError, "at least one column index is required" if columns.empty?
|
|
116
|
+
|
|
117
|
+
normalized = columns.map do |column|
|
|
118
|
+
index = Integer(column)
|
|
119
|
+
raise ArgumentError, "column indices must be non-negative" if index.negative?
|
|
120
|
+
|
|
121
|
+
index
|
|
122
|
+
end
|
|
123
|
+
query_with_mode(region, nil, nil, :selected, normalized, &block)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def query_with_mode(region, start, end_, mode, columns, &block)
|
|
101
127
|
check_closed
|
|
102
|
-
raise "Index file is required to call the query method." unless
|
|
128
|
+
raise "Index file is required to call the query method." unless ensure_index_loaded
|
|
103
129
|
|
|
104
130
|
if start && end_
|
|
105
|
-
queryi(name2id(region), start, end_, &block)
|
|
131
|
+
queryi(name2id(region), start, end_, mode, columns, &block)
|
|
132
|
+
elsif start.nil? && end_.nil?
|
|
133
|
+
querys(region, mode, columns, &block)
|
|
106
134
|
else
|
|
107
|
-
|
|
135
|
+
raise ArgumentError, "start and end must be specified together"
|
|
108
136
|
end
|
|
109
137
|
end
|
|
110
138
|
|
|
111
139
|
def close
|
|
112
140
|
return if closed?
|
|
113
141
|
|
|
114
|
-
@
|
|
115
|
-
@idx = nil
|
|
116
|
-
super
|
|
142
|
+
@native.close
|
|
117
143
|
end
|
|
118
144
|
|
|
119
145
|
def closed?
|
|
120
|
-
@
|
|
146
|
+
@native.nil? || @native.closed?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def file_format = @native.file_format
|
|
150
|
+
def file_format_version = @native.file_format_version
|
|
151
|
+
|
|
152
|
+
def set_threads(n = nil)
|
|
153
|
+
if n.nil?
|
|
154
|
+
require "etc"
|
|
155
|
+
n = [Etc.nprocessors - 1, 1].max
|
|
156
|
+
end
|
|
157
|
+
raise TypeError unless n.is_a?(Integer)
|
|
158
|
+
raise ArgumentError, "Number of threads must be positive" if n < 1
|
|
159
|
+
raise "Failed to set number of threads: #{n}" if @native.set_threads(n).negative?
|
|
160
|
+
|
|
161
|
+
@nthreads = n
|
|
162
|
+
self
|
|
121
163
|
end
|
|
122
164
|
|
|
123
165
|
private
|
|
124
166
|
|
|
125
|
-
def
|
|
126
|
-
return
|
|
167
|
+
def ensure_index_loaded
|
|
168
|
+
return true if index_loaded?
|
|
169
|
+
return false if @index_load_attempted
|
|
127
170
|
|
|
128
|
-
|
|
129
|
-
|
|
171
|
+
load_index(@index_name)
|
|
172
|
+
end
|
|
130
173
|
|
|
131
|
-
|
|
174
|
+
def queryi(id, start, end_, mode = :fields, columns = nil, &block)
|
|
175
|
+
return to_enum(__method__, id, start, end_, mode, columns) unless block_given?
|
|
176
|
+
|
|
177
|
+
@native.query_interval(id, start, end_) { |line| yield_line(line, mode, columns, &block) }
|
|
132
178
|
self
|
|
133
179
|
end
|
|
134
180
|
|
|
135
|
-
def querys(region, &block)
|
|
136
|
-
return to_enum(__method__, region) unless block_given?
|
|
137
|
-
|
|
138
|
-
qiter = LibHTS.tbx_itr_querys(@idx, region)
|
|
139
|
-
raise "Failed to query region: #{region}" if qiter.null?
|
|
181
|
+
def querys(region, mode = :fields, columns = nil, &block)
|
|
182
|
+
return to_enum(__method__, region, mode, columns) unless block_given?
|
|
140
183
|
|
|
141
|
-
|
|
184
|
+
@native.query_region(region) { |line| yield_line(line, mode, columns, &block) }
|
|
142
185
|
self
|
|
143
186
|
end
|
|
144
187
|
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
r.free_buffer
|
|
154
|
-
LibHTS.hts_itr_destroy(qiter)
|
|
188
|
+
def yield_line(line, mode, columns)
|
|
189
|
+
case mode
|
|
190
|
+
when :line
|
|
191
|
+
yield line
|
|
192
|
+
when :selected
|
|
193
|
+
yield selected_fields(line, columns)
|
|
194
|
+
else
|
|
195
|
+
yield line.split("\t")
|
|
155
196
|
end
|
|
156
197
|
end
|
|
157
198
|
|
|
199
|
+
def selected_fields(line, columns)
|
|
200
|
+
HTS::Native.selected_fields(line, columns)
|
|
201
|
+
end
|
|
202
|
+
|
|
158
203
|
def check_closed
|
|
159
204
|
raise IOError, "closed Tabix" if closed?
|
|
160
205
|
end
|
data/lib/hts/version.rb
CHANGED
data/lib/htslib.rb
CHANGED
|
@@ -1,60 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "ffi"
|
|
4
|
-
|
|
5
3
|
require "hts/version"
|
|
4
|
+
require "hts/native"
|
|
6
5
|
|
|
7
6
|
module HTS
|
|
8
7
|
class Error < StandardError; end
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
lib_path = if ENV["HTSLIBDIR"]
|
|
16
|
-
File.expand_path(name, ENV["HTSLIBDIR"])
|
|
17
|
-
else
|
|
18
|
-
File.expand_path("../vendor/#{name}", __dir__)
|
|
19
|
-
end
|
|
20
|
-
return lib_path if File.exist?(lib_path)
|
|
21
|
-
|
|
22
|
-
begin
|
|
23
|
-
require "pkg-config"
|
|
24
|
-
lib_dir = PKGConfig.variable("htslib", "libdir")
|
|
25
|
-
lib_path = File.expand_path(name, lib_dir)
|
|
26
|
-
rescue PackageConfig::NotFoundError
|
|
27
|
-
warn "htslib.pc was not found in the pkg-config search path."
|
|
28
|
-
end
|
|
29
|
-
return lib_path if File.exist?(lib_path)
|
|
30
|
-
|
|
31
|
-
warn "htslib shared library '#{name}' not found."
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def search_htslib_windows
|
|
35
|
-
ENV["HTSLIBDIR"] ||= [
|
|
36
|
-
RubyInstaller::Runtime.msys2_installation.msys_path,
|
|
37
|
-
RubyInstaller::Runtime.msys2_installation.mingwarch
|
|
38
|
-
].join(File::ALT_SEPARATOR)
|
|
39
|
-
path = File.expand_path("bin/hts-3.dll", ENV["HTSLIBDIR"])
|
|
40
|
-
RubyInstaller::Runtime.add_dll_directory(File.dirname(path))
|
|
41
|
-
path
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
self.lib_path = if Object.const_defined?(:RubyInstaller)
|
|
46
|
-
search_htslib_windows
|
|
47
|
-
else
|
|
48
|
-
search_htslib
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# You can change the path of the shared library with `HTS.lib_path=`
|
|
52
|
-
# before calling the LibHTS module.
|
|
53
|
-
autoload :LibHTS, "hts/libhts"
|
|
54
|
-
|
|
55
|
-
autoload :Hts, "hts/hts"
|
|
56
|
-
autoload :Bam, "hts/bam"
|
|
57
|
-
autoload :Bcf, "hts/bcf"
|
|
58
|
-
autoload :Faidx, "hts/faidx"
|
|
59
|
-
autoload :Tabix, "hts/tabix"
|
|
9
|
+
autoload :Hts, "hts/hts"
|
|
10
|
+
autoload :Bam, "hts/bam"
|
|
11
|
+
autoload :Bcf, "hts/bcf"
|
|
12
|
+
autoload :Faidx, "hts/faidx"
|
|
13
|
+
autoload :Tabix, "hts/tabix"
|
|
60
14
|
end
|