htslib 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +83 -51
  3. data/TUTORIAL.md +11 -22
  4. data/ext/htslib_native/extconf.rb +41 -0
  5. data/ext/htslib_native/htslib_native.h +11 -0
  6. data/ext/htslib_native/htslib_native_ext.c +48 -0
  7. data/ext/htslib_native/native_bam.c +1167 -0
  8. data/ext/htslib_native/native_bcf.c +447 -0
  9. data/ext/htslib_native/native_faidx.c +164 -0
  10. data/ext/htslib_native/native_tabix.c +255 -0
  11. data/lib/hts/bam/auxi.rb +87 -342
  12. data/lib/hts/bam/base_mod.rb +38 -73
  13. data/lib/hts/bam/cigar.rb +9 -55
  14. data/lib/hts/bam/flag.rb +24 -30
  15. data/lib/hts/bam/header.rb +29 -56
  16. data/lib/hts/bam/mpileup.rb +158 -132
  17. data/lib/hts/bam/pileup.rb +120 -145
  18. data/lib/hts/bam/record.rb +233 -270
  19. data/lib/hts/bam.rb +154 -45
  20. data/lib/hts/bcf/errors.rb +4 -0
  21. data/lib/hts/bcf/format.rb +283 -377
  22. data/lib/hts/bcf/header.rb +40 -122
  23. data/lib/hts/bcf/header_record.rb +9 -35
  24. data/lib/hts/bcf/info.rb +73 -320
  25. data/lib/hts/bcf/record.rb +73 -101
  26. data/lib/hts/bcf.rb +178 -321
  27. data/lib/hts/faidx.rb +30 -87
  28. data/lib/hts/hts.rb +6 -94
  29. data/lib/hts/native.rb +13 -0
  30. data/lib/hts/tabix.rb +109 -51
  31. data/lib/hts/version.rb +1 -1
  32. data/lib/htslib.rb +6 -52
  33. metadata +13 -64
  34. data/lib/hts/ffi_ext/README.md +0 -8
  35. data/lib/hts/ffi_ext/pointer.rb +0 -18
  36. data/lib/hts/ffi_ext/struct.rb +0 -45
  37. data/lib/hts/libhts/bgzf.rb +0 -199
  38. data/lib/hts/libhts/constants.rb +0 -658
  39. data/lib/hts/libhts/cram.rb +0 -471
  40. data/lib/hts/libhts/fai.rb +0 -146
  41. data/lib/hts/libhts/hfile.rb +0 -121
  42. data/lib/hts/libhts/hts.rb +0 -477
  43. data/lib/hts/libhts/kfunc.rb +0 -38
  44. data/lib/hts/libhts/sam.rb +0 -760
  45. data/lib/hts/libhts/sam_funcs.rb +0 -155
  46. data/lib/hts/libhts/tbx.rb +0 -94
  47. data/lib/hts/libhts/tbx_funcs.rb +0 -36
  48. data/lib/hts/libhts/thread_pool.rb +0 -139
  49. data/lib/hts/libhts/vcf.rb +0 -567
  50. data/lib/hts/libhts/vcf_funcs.rb +0 -366
  51. data/lib/hts/libhts.rb +0 -47
data/lib/hts/faidx.rb CHANGED
@@ -1,21 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../htslib"
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:) # do not yield
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
- case LibHTS.fai_build3(file_name, fai_path, gzi_path)
31
- when 0
32
- else raise HTS::Error, "Failed to build faidx index for #{file_name}"
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
- @fai = load_handle(@file_name, @format, auto_build)
42
-
43
- raise Errno::ENOENT, "Failed to open #{@file_name}" if @fai.null?
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 = key.to_s
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 = LibHTS.faidx_seq_len64(@fai, chrom)
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
- fetch_seq(name, 0, len - 1)
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
- check_closed
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
- fetch_qual(name, 0, len - 1)
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 check_closed
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(ptr, len, kind, name, start, stop)
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
- raise HTS::Error, "Error fetching #{kind}: #{name}:#{start}-#{stop}" if ptr.null?
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
- # A base class for hts files.
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
- ary = map(&name)
16
+ values = map(&name)
16
17
  seek(position) if position
17
- ary
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 do |record|
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
- file
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
- @hts_file = LibHTS.hts_open(@file_name, @mode)
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
- # build_index(index) if build_index
44
- @idx = load_index(index)
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 LibHTS.tbx_index_build2(@file_name, index_name, min_shift, LibHTS.tbx_conf_vcf)
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 LibHTS.tbx_index_build(@file_name, min_shift, LibHTS.tbx_conf_vcf)
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
- if index_name
73
- LibHTS.tbx_index_load2(@file_name, index_name)
74
- else
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
- !@idx.null?
94
+ @native.index_loaded?
82
95
  end
83
96
 
84
97
  def name2id(name)
85
98
  check_closed
86
- LibHTS.tbx_name2id(@idx, name)
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
- nseq = FFI::MemoryPointer.new(:int)
92
- pts = LibHTS.tbx_seqnames(@idx, nseq)
93
- begin
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 index_loaded?
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
- querys(region, &block)
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
- @idx.close if @idx && !@idx.null?
115
- @idx = nil
116
- super
155
+ @native.close
117
156
  end
118
157
 
119
158
  def closed?
120
- @hts_file.nil? || @hts_file.null?
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 queryi(id, start, end_, &block)
126
- return to_enum(__method__, id, start, end_) unless block_given?
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
- qiter = LibHTS.tbx_itr_queryi(@idx, id, start, end_)
129
- raise "Failed to query region: #{id}:#{start}-#{end_}" if qiter.null?
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
- query_yield(qiter, &block)
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
- qiter = LibHTS.tbx_itr_querys(@idx, region)
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 query_yield(qiter)
146
- r = LibHTS::KString.new
147
- begin
148
- while (slen = LibHTS.tbx_itr_next(@hts_file, @idx, qiter, r)) >= 0
149
- yield r.read_string_copy.split("\t")
150
- end
151
- raise if slen < -1
152
- ensure
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTS
4
- VERSION = "0.4.2"
4
+ VERSION = "0.6.0"
5
5
  end
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
- class << self
11
- attr_accessor :lib_path
12
-
13
- def search_htslib(name = nil)
14
- name ||= "libhts.#{FFI::Platform::LIBSUFFIX}"
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