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.
- checksums.yaml +4 -4
- data/README.md +83 -51
- data/TUTORIAL.md +11 -22
- 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 +1167 -0
- data/ext/htslib_native/native_bcf.c +447 -0
- data/ext/htslib_native/native_faidx.c +164 -0
- data/ext/htslib_native/native_tabix.c +255 -0
- data/lib/hts/bam/auxi.rb +87 -342
- data/lib/hts/bam/base_mod.rb +38 -73
- data/lib/hts/bam/cigar.rb +9 -55
- data/lib/hts/bam/flag.rb +24 -30
- 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 +154 -45
- data/lib/hts/bcf/errors.rb +4 -0
- data/lib/hts/bcf/format.rb +283 -377
- data/lib/hts/bcf/header.rb +40 -122
- data/lib/hts/bcf/header_record.rb +9 -35
- data/lib/hts/bcf/info.rb +73 -320
- data/lib/hts/bcf/record.rb +73 -101
- data/lib/hts/bcf.rb +178 -321
- data/lib/hts/faidx.rb +30 -87
- data/lib/hts/hts.rb +6 -94
- data/lib/hts/native.rb +13 -0
- data/lib/hts/tabix.rb +109 -51
- 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,15 @@
|
|
|
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
|
|
7
|
+
class OpenError < HTS::Error; end
|
|
8
|
+
|
|
15
9
|
attr_reader :file_name, :format
|
|
16
10
|
|
|
17
11
|
def self.open(file_name, format: :auto, auto_build: true)
|
|
18
|
-
file = new(file_name, format:, auto_build:)
|
|
12
|
+
file = new(file_name, format:, auto_build:)
|
|
19
13
|
return file unless block_given?
|
|
20
14
|
|
|
21
15
|
begin
|
|
@@ -27,10 +21,9 @@ module HTS
|
|
|
27
21
|
end
|
|
28
22
|
|
|
29
23
|
def self.build_index(file_name, fai_path = nil, gzi_path = nil)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
end
|
|
24
|
+
return if Native::FaidxHandle.build(file_name, fai_path, gzi_path).zero?
|
|
25
|
+
|
|
26
|
+
raise HTS::Error, "Failed to build faidx index for #{file_name}"
|
|
34
27
|
end
|
|
35
28
|
|
|
36
29
|
def initialize(file_name, format: :auto, auto_build: true)
|
|
@@ -38,95 +31,62 @@ module HTS
|
|
|
38
31
|
|
|
39
32
|
@file_name = file_name.freeze
|
|
40
33
|
@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)
|
|
34
|
+
@native = Native::FaidxHandle.open(@file_name, @format == :fastq ? 1 : 0, auto_build)
|
|
35
|
+
rescue Errno::ENOENT
|
|
36
|
+
raise Errno::ENOENT, "Failed to open #{@file_name}"
|
|
64
37
|
end
|
|
65
38
|
|
|
39
|
+
def close = @native&.close
|
|
40
|
+
def closed? = @native.nil? || @native.closed?
|
|
41
|
+
def size = native.size
|
|
66
42
|
alias length size
|
|
67
|
-
|
|
68
|
-
def names
|
|
69
|
-
check_closed
|
|
70
|
-
Array.new(length) { |i| LibHTS.faidx_iseq(@fai, i) }
|
|
71
|
-
end
|
|
43
|
+
def names = native.names
|
|
72
44
|
|
|
73
45
|
def has_seq?(key)
|
|
74
|
-
check_closed
|
|
75
46
|
raise ArgumentError, "Expect chrom to be String or Symbol" unless key.is_a?(String) || key.is_a?(Symbol)
|
|
76
47
|
|
|
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
|
|
48
|
+
native.has_seq?(key.to_s)
|
|
83
49
|
end
|
|
84
50
|
|
|
85
51
|
def seq_len(chrom)
|
|
86
|
-
check_closed
|
|
87
52
|
raise ArgumentError, "Expect chrom to be String or Symbol" unless chrom.is_a?(String) || chrom.is_a?(Symbol)
|
|
88
53
|
|
|
89
54
|
chrom = chrom.to_s
|
|
90
|
-
result =
|
|
55
|
+
result = native.seq_len(chrom)
|
|
91
56
|
raise ArgumentError, "Sequence not found: #{chrom}" if result == -1
|
|
92
57
|
|
|
93
58
|
result
|
|
94
59
|
end
|
|
95
60
|
|
|
96
61
|
def fetch_seq(name, start = nil, stop = nil)
|
|
97
|
-
check_closed
|
|
98
62
|
name = name.to_s
|
|
99
|
-
|
|
100
63
|
if start.nil? && stop.nil?
|
|
101
64
|
len = seq_len(name)
|
|
102
65
|
return "" if len.zero?
|
|
103
66
|
|
|
104
|
-
|
|
67
|
+
start = 0
|
|
68
|
+
stop = len - 1
|
|
105
69
|
else
|
|
106
70
|
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
71
|
end
|
|
72
|
+
fetch_result(native.fetch_seq(name, start, stop), "sequence", name, start, stop)
|
|
111
73
|
end
|
|
112
74
|
|
|
113
75
|
def fetch_qual(name, start = nil, stop = nil)
|
|
114
|
-
|
|
76
|
+
native
|
|
115
77
|
raise HTS::Error, "Quality is only available for FASTQ indexes" unless format == :fastq
|
|
116
78
|
|
|
117
79
|
name = name.to_s
|
|
118
|
-
|
|
119
80
|
if start.nil? && stop.nil?
|
|
120
81
|
len = seq_len(name)
|
|
121
82
|
return "" if len.zero?
|
|
122
83
|
|
|
123
|
-
|
|
84
|
+
start = 0
|
|
85
|
+
stop = len - 1
|
|
124
86
|
else
|
|
125
87
|
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
88
|
end
|
|
89
|
+
fetch_result(native.fetch_qual(name, start, stop), "quality", name, start, stop)
|
|
130
90
|
end
|
|
131
91
|
|
|
132
92
|
def build_index(fai_path = nil, gzi_path = nil)
|
|
@@ -136,8 +96,10 @@ module HTS
|
|
|
136
96
|
|
|
137
97
|
private
|
|
138
98
|
|
|
139
|
-
def
|
|
99
|
+
def native
|
|
140
100
|
raise IOError, "closed Faidx" if closed?
|
|
101
|
+
|
|
102
|
+
@native
|
|
141
103
|
end
|
|
142
104
|
|
|
143
105
|
def validate_range!(name, start, stop)
|
|
@@ -149,34 +111,15 @@ module HTS
|
|
|
149
111
|
raise ArgumentError, "Expect stop to be < seq_len (#{len})" if stop >= len
|
|
150
112
|
end
|
|
151
113
|
|
|
152
|
-
def fetch_result(
|
|
114
|
+
def fetch_result(result, kind, name, start, stop)
|
|
115
|
+
len, string = result
|
|
153
116
|
case len
|
|
154
117
|
when -2 then raise ArgumentError, "Sequence not found: #{name}"
|
|
155
118
|
when -1 then raise HTS::Error, "Error fetching #{kind}: #{name}:#{start}-#{stop}"
|
|
156
119
|
end
|
|
120
|
+
raise HTS::Error, "Error fetching #{kind}: #{name}:#{start}-#{stop}" unless string
|
|
157
121
|
|
|
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
|
|
122
|
+
string
|
|
180
123
|
end
|
|
181
124
|
|
|
182
125
|
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,11 +3,15 @@
|
|
|
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
|
|
9
10
|
include Enumerable
|
|
10
11
|
|
|
12
|
+
class OpenError < HTS::Error; end
|
|
13
|
+
class MissingIndexError < HTS::Error; end
|
|
14
|
+
|
|
11
15
|
attr_reader :file_name, :index_name, :mode, :nthreads
|
|
12
16
|
|
|
13
17
|
def self.open(*args, **kw)
|
|
@@ -15,11 +19,11 @@ module HTS
|
|
|
15
19
|
return file unless block_given?
|
|
16
20
|
|
|
17
21
|
begin
|
|
18
|
-
yield file
|
|
22
|
+
result = yield file
|
|
19
23
|
ensure
|
|
20
24
|
file.close
|
|
21
25
|
end
|
|
22
|
-
|
|
26
|
+
result
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
def initialize(file_name, index: nil, threads: nil, build_index: false)
|
|
@@ -34,14 +38,17 @@ module HTS
|
|
|
34
38
|
@index_name = index
|
|
35
39
|
@mode = "r"
|
|
36
40
|
@nthreads = threads
|
|
37
|
-
@
|
|
38
|
-
|
|
39
|
-
raise Errno::ENOENT, "Failed to open #{@file_name}" if @hts_file.null?
|
|
41
|
+
@index_load_attempted = false
|
|
42
|
+
@native = Native::TabixHandle.open(@file_name)
|
|
40
43
|
|
|
41
44
|
set_threads(threads) if threads
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
if build_index
|
|
47
|
+
build_index(index)
|
|
48
|
+
load_index(index)
|
|
49
|
+
elsif index
|
|
50
|
+
load_index(index)
|
|
51
|
+
end
|
|
45
52
|
end
|
|
46
53
|
|
|
47
54
|
def build_index(index_name = nil, min_shift: 0)
|
|
@@ -49,7 +56,7 @@ module HTS
|
|
|
49
56
|
|
|
50
57
|
if index_name
|
|
51
58
|
warn "Create index for #{@file_name} to #{index_name}"
|
|
52
|
-
case
|
|
59
|
+
case Native::TabixHandle.build(@file_name, index_name, min_shift)
|
|
53
60
|
when 0 # successful
|
|
54
61
|
when -1 then raise "general failure"
|
|
55
62
|
when -2 then raise "compression not BGZF"
|
|
@@ -57,104 +64,155 @@ module HTS
|
|
|
57
64
|
end
|
|
58
65
|
else
|
|
59
66
|
warn "Create index for #{@file_name}"
|
|
60
|
-
case
|
|
67
|
+
case Native::TabixHandle.build(@file_name, nil, min_shift)
|
|
61
68
|
when 0 # successful
|
|
62
69
|
when -1 then raise "general failure"
|
|
63
70
|
when -2 then raise "compression not BGZF"
|
|
64
71
|
else raise "unknown error"
|
|
65
72
|
end
|
|
66
73
|
end
|
|
74
|
+
@index_name = index_name
|
|
75
|
+
@index_load_attempted = false
|
|
67
76
|
self # for method chaining
|
|
68
77
|
end
|
|
69
78
|
|
|
70
79
|
def load_index(index_name = nil)
|
|
80
|
+
return self if try_load_index(index_name)
|
|
81
|
+
|
|
82
|
+
raise MissingIndexError, "Failed to load index #{index_name || "for #{@file_name}"}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def try_load_index(index_name = nil)
|
|
71
86
|
check_closed
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
LibHTS.tbx_index_load3(@file_name, nil, 2)
|
|
76
|
-
end
|
|
87
|
+
@index_name = index_name
|
|
88
|
+
@index_load_attempted = true
|
|
89
|
+
@native.load_index(index_name)
|
|
77
90
|
end
|
|
78
91
|
|
|
79
92
|
def index_loaded?
|
|
80
93
|
check_closed
|
|
81
|
-
|
|
94
|
+
@native.index_loaded?
|
|
82
95
|
end
|
|
83
96
|
|
|
84
97
|
def name2id(name)
|
|
85
98
|
check_closed
|
|
86
|
-
|
|
99
|
+
raise "Index file is required to call the name2id method." unless ensure_index_loaded
|
|
100
|
+
|
|
101
|
+
@native.name2id(name)
|
|
87
102
|
end
|
|
88
103
|
|
|
89
104
|
def seqnames
|
|
90
105
|
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
|
|
106
|
+
raise "Index file is required to call the seqnames method." unless ensure_index_loaded
|
|
107
|
+
|
|
108
|
+
@native.seqnames
|
|
98
109
|
end
|
|
99
110
|
|
|
100
111
|
def query(region, start = nil, end_ = nil, &block)
|
|
112
|
+
query_with_mode(region, start, end_, :fields, nil, &block)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Explicit name for the legacy split-fields API.
|
|
116
|
+
def each_fields(region, start = nil, end_ = nil, &block)
|
|
117
|
+
query_with_mode(region, start, end_, :fields, nil, &block)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Iterate raw rows, allocating one String per row and no field Array.
|
|
121
|
+
def each_line(region, start = nil, end_ = nil, &block)
|
|
122
|
+
query_with_mode(region, start, end_, :line, nil, &block)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Extract only requested zero-based columns without materializing unused
|
|
126
|
+
# field strings. This Ruby implementation scans tab delimiters once.
|
|
127
|
+
def each_selected_fields(region, *columns, &block)
|
|
128
|
+
raise ArgumentError, "at least one column index is required" if columns.empty?
|
|
129
|
+
|
|
130
|
+
normalized = columns.map do |column|
|
|
131
|
+
index = Integer(column)
|
|
132
|
+
raise ArgumentError, "column indices must be non-negative" if index.negative?
|
|
133
|
+
|
|
134
|
+
index
|
|
135
|
+
end
|
|
136
|
+
query_with_mode(region, nil, nil, :selected, normalized, &block)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def query_with_mode(region, start, end_, mode, columns, &block)
|
|
101
140
|
check_closed
|
|
102
|
-
raise "Index file is required to call the query method." unless
|
|
141
|
+
raise "Index file is required to call the query method." unless ensure_index_loaded
|
|
103
142
|
|
|
104
143
|
if start && end_
|
|
105
|
-
queryi(name2id(region), start, end_, &block)
|
|
144
|
+
queryi(name2id(region), start, end_, mode, columns, &block)
|
|
145
|
+
elsif start.nil? && end_.nil?
|
|
146
|
+
querys(region, mode, columns, &block)
|
|
106
147
|
else
|
|
107
|
-
|
|
148
|
+
raise ArgumentError, "start and end must be specified together"
|
|
108
149
|
end
|
|
109
150
|
end
|
|
110
151
|
|
|
111
152
|
def close
|
|
112
153
|
return if closed?
|
|
113
154
|
|
|
114
|
-
@
|
|
115
|
-
@idx = nil
|
|
116
|
-
super
|
|
155
|
+
@native.close
|
|
117
156
|
end
|
|
118
157
|
|
|
119
158
|
def closed?
|
|
120
|
-
@
|
|
159
|
+
@native.nil? || @native.closed?
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def file_format = @native.file_format
|
|
163
|
+
def file_format_version = @native.file_format_version
|
|
164
|
+
|
|
165
|
+
def set_threads(n = nil)
|
|
166
|
+
if n.nil?
|
|
167
|
+
require "etc"
|
|
168
|
+
n = [Etc.nprocessors - 1, 1].max
|
|
169
|
+
end
|
|
170
|
+
raise TypeError unless n.is_a?(Integer)
|
|
171
|
+
raise ArgumentError, "Number of threads must be positive" if n < 1
|
|
172
|
+
raise "Failed to set number of threads: #{n}" if @native.set_threads(n).negative?
|
|
173
|
+
|
|
174
|
+
@nthreads = n
|
|
175
|
+
self
|
|
121
176
|
end
|
|
122
177
|
|
|
123
178
|
private
|
|
124
179
|
|
|
125
|
-
def
|
|
126
|
-
return
|
|
180
|
+
def ensure_index_loaded
|
|
181
|
+
return true if index_loaded?
|
|
182
|
+
return false if @index_load_attempted
|
|
183
|
+
|
|
184
|
+
load_index(@index_name)
|
|
185
|
+
end
|
|
127
186
|
|
|
128
|
-
|
|
129
|
-
|
|
187
|
+
def queryi(id, start, end_, mode = :fields, columns = nil, &block)
|
|
188
|
+
return to_enum(__method__, id, start, end_, mode, columns) unless block_given?
|
|
130
189
|
|
|
131
|
-
|
|
190
|
+
@native.query_interval(id, start, end_) { |line| yield_line(line, mode, columns, &block) }
|
|
132
191
|
self
|
|
133
192
|
end
|
|
134
193
|
|
|
135
|
-
def querys(region, &block)
|
|
136
|
-
return to_enum(__method__, region) unless block_given?
|
|
194
|
+
def querys(region, mode = :fields, columns = nil, &block)
|
|
195
|
+
return to_enum(__method__, region, mode, columns) unless block_given?
|
|
137
196
|
|
|
138
|
-
|
|
139
|
-
raise "Failed to query region: #{region}" if qiter.null?
|
|
140
|
-
|
|
141
|
-
query_yield(qiter, &block)
|
|
197
|
+
@native.query_region(region) { |line| yield_line(line, mode, columns, &block) }
|
|
142
198
|
self
|
|
143
199
|
end
|
|
144
200
|
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
r.free_buffer
|
|
154
|
-
LibHTS.hts_itr_destroy(qiter)
|
|
201
|
+
def yield_line(line, mode, columns)
|
|
202
|
+
case mode
|
|
203
|
+
when :line
|
|
204
|
+
yield line
|
|
205
|
+
when :selected
|
|
206
|
+
yield selected_fields(line, columns)
|
|
207
|
+
else
|
|
208
|
+
yield line.split("\t")
|
|
155
209
|
end
|
|
156
210
|
end
|
|
157
211
|
|
|
212
|
+
def selected_fields(line, columns)
|
|
213
|
+
HTS::Native.selected_fields(line, columns)
|
|
214
|
+
end
|
|
215
|
+
|
|
158
216
|
def check_closed
|
|
159
217
|
raise IOError, "closed Tabix" if closed?
|
|
160
218
|
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
|