Class: HTS::Bcf::Info

Inherits:
Object
  • Object
show all
Defined in:
lib/hts/bcf/info.rb

Overview

Info field

Instance Method Summary collapse

Constructor Details

#initialize(record) ⇒ Info

Returns a new instance of Info.



7
8
9
10
# File 'lib/hts/bcf/info.rb', line 7

def initialize(record)
  @record = record
  @p1 = FFI::MemoryPointer.new(:pointer) # FIXME: naming
end

Instance Method Details

#[](key) ⇒ Object



73
74
75
# File 'lib/hts/bcf/info.rb', line 73

def [](key)
  get(key)
end

#[]=(key, value) ⇒ Object

Set INFO field value with automatic type detection.

Parameters:

  • key (String)

    INFO tag name

  • value (Integer, Float, String, Array, true, false, nil)

    value to set

    • Integer or Array<Integer> -> update_int

    • Float or Array<Float,Integer> -> update_float

    • String -> update_string

    • true/false -> update_flag

    • nil -> delete the INFO field



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/hts/bcf/info.rb', line 85

def []=(key, value)
  case value
  when nil
    delete(key)
  when true, false
    update_flag(key, value)
  when Integer
    update_int(key, [value])
  when Float
    update_float(key, [value])
  when String
    update_string(key, value)
  when Array
    if value.empty?
      raise ArgumentError, "Cannot set INFO field to empty array. Use nil to delete."
    elsif value.all? { |v| v.is_a?(Integer) }
      update_int(key, value)
    elsif value.all? { |v| v.is_a?(Numeric) }
      update_float(key, value)
    else
      raise ArgumentError, "INFO array must contain only integers or floats, got: #{value.map(&:class).uniq}"
    end
  else
    raise ArgumentError, "Unsupported INFO value type: #{value.class}"
  end
end

#delete(key) ⇒ Boolean

Delete an INFO field.

Parameters:

  • key (String)

    INFO tag name

Returns:

  • (Boolean)

    true if field was deleted, false if it didn’t exist



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/hts/bcf/info.rb', line 205

def delete(key)
  # Try to get current type to check existence
  type = get_info_type(key)
  return false if type.nil?

  # Delete by setting n=0
  ret = LibHTS.bcf_update_info(
    @record.header.struct,
    @record.struct,
    key,
    FFI::Pointer::NULL,
    0,
    type
  )
  return false if ret < 0

  true
end

#fieldsObject

FIXME: naming? room for improvement.



236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/hts/bcf/info.rb', line 236

def fields
  keys.map do |key|
    name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, key)
    num  = LibHTS.bcf_hdr_id2number(@record.header.struct, LibHTS::BCF_HL_INFO, key)
    type = LibHTS.bcf_hdr_id2type(@record.header.struct, LibHTS::BCF_HL_INFO, key)
    {
      name:,
      n: num,
      type: ht_type_to_sym(type),
      key:
    }
  end
end

#get(key, type = nil) ⇒ Object

Note:

Specify the type. If you don’t specify a type, it will still work, but it will be slower.

@note: Why is this method named “get” instead of “fetch”? This is for compatibility with the Crystal language which provides methods like ‘get_int`, `get_float`, etc. I think they are better than `fetch_int“ and `fetch_float`.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/hts/bcf/info.rb', line 17

def get(key, type = nil)
  n = FFI::MemoryPointer.new(:int)
  p1 = @p1
  h = @record.header.struct
  r = @record.struct

  info_values = proc do |typ|
    ret = LibHTS.bcf_get_info_values(h, r, key, p1, n, typ)
    return nil if ret < 0 # return from method.

    p1.read_pointer
  end

  type ||= ht_type_to_sym(get_info_type(key))

  case type&.to_sym
  when :int, :int32
    info_values.call(LibHTS::BCF_HT_INT)
               .read_array_of_int32(n.read_int)
  when :float, :real
    info_values.call(LibHTS::BCF_HT_REAL)
               .read_array_of_float(n.read_int)
  when :flag, :bool
    case ret = LibHTS.bcf_get_info_flag(h, r, key, p1, n)
    when 1 then true
    when 0 then false
    when -1 then nil
    else
      raise "Unknown return value from bcf_get_info_flag: #{ret}"
    end
  when :string, :str
    info_values.call(LibHTS::BCF_HT_STR)
               .read_string
  end
end

#get_flag(key) ⇒ Object

For compatibility with HTS.cr.



69
70
71
# File 'lib/hts/bcf/info.rb', line 69

def get_flag(key)
  get(key, :flag)
end

#get_float(key) ⇒ Object

For compatibility with HTS.cr.



59
60
61
# File 'lib/hts/bcf/info.rb', line 59

def get_float(key)
  get(key, :float)
end

#get_int(key) ⇒ Object

For compatibility with HTS.cr.



54
55
56
# File 'lib/hts/bcf/info.rb', line 54

def get_int(key)
  get(key, :int)
end

#get_string(key) ⇒ Object

For compatibility with HTS.cr.



64
65
66
# File 'lib/hts/bcf/info.rb', line 64

def get_string(key)
  get(key, :string)
end

#key?(key) ⇒ Boolean Also known as: include?

Check if an INFO field exists.

Parameters:

  • key (String)

    INFO tag name

Returns:

  • (Boolean)

    true if the field exists



227
228
229
230
231
# File 'lib/hts/bcf/info.rb', line 227

def key?(key)
  # Use get() to check if value is actually present
  # (get_info_type only checks header, not actual value)
  !get(key).nil?
end

#lengthObject



250
251
252
# File 'lib/hts/bcf/info.rb', line 250

def length
  @record.struct[:n_info]
end

#sizeObject



254
255
256
# File 'lib/hts/bcf/info.rb', line 254

def size
  length
end

#to_hObject



258
259
260
261
262
263
264
265
# File 'lib/hts/bcf/info.rb', line 258

def to_h
  ret = {}
  keys.each do |key|
    name = LibHTS.bcf_hdr_int2id(@record.header.struct, LibHTS::BCF_DT_ID, key)
    ret[name] = get(name)
  end
  ret
end

#update_flag(key, present = true) ⇒ Object

Update INFO flag field. For compatibility with HTS.cr.

Parameters:

  • key (String)

    INFO tag name

  • present (Boolean) (defaults to: true)

    true to set flag, false to remove it



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/hts/bcf/info.rb', line 176

def update_flag(key, present = true)
  ret = if present
          LibHTS.bcf_update_info(
            @record.header.struct,
            @record.struct,
            key,
            FFI::Pointer::NULL,
            1,
            LibHTS::BCF_HT_FLAG
          )
        else
          # Remove flag by setting n=0
          LibHTS.bcf_update_info(
            @record.header.struct,
            @record.struct,
            key,
            FFI::Pointer::NULL,
            0,
            LibHTS::BCF_HT_FLAG
          )
        end
  raise "Failed to update INFO flag field '#{key}': #{ret}" if ret < 0

  ret
end

#update_float(key, values) ⇒ Object

Update INFO field with float value(s). For compatibility with HTS.cr.

Parameters:

  • key (String)

    INFO tag name

  • values (Array<Float>)

    float values (use single-element array for scalar)



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/hts/bcf/info.rb', line 137

def update_float(key, values)
  values = Array(values).map(&:to_f)
  ptr = FFI::MemoryPointer.new(:float, values.size)
  ptr.write_array_of_float(values)
  ret = LibHTS.bcf_update_info(
    @record.header.struct,
    @record.struct,
    key,
    ptr,
    values.size,
    LibHTS::BCF_HT_REAL
  )
  raise "Failed to update INFO float field '#{key}': #{ret}" if ret < 0

  ret
end

#update_int(key, values) ⇒ Object

Update INFO field with integer value(s). For compatibility with HTS.cr.

Parameters:

  • key (String)

    INFO tag name

  • values (Array<Integer>)

    integer values (use single-element array for scalar)



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/hts/bcf/info.rb', line 116

def update_int(key, values)
  values = Array(values)
  ptr = FFI::MemoryPointer.new(:int32, values.size)
  ptr.write_array_of_int32(values)
  ret = LibHTS.bcf_update_info(
    @record.header.struct,
    @record.struct,
    key,
    ptr,
    values.size,
    LibHTS::BCF_HT_INT
  )
  raise "Failed to update INFO int field '#{key}': #{ret}" if ret < 0

  ret
end

#update_string(key, value) ⇒ Object

Update INFO field with string value. For compatibility with HTS.cr.

Parameters:

  • key (String)

    INFO tag name

  • value (String)

    string value



158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/hts/bcf/info.rb', line 158

def update_string(key, value)
  ret = LibHTS.bcf_update_info(
    @record.header.struct,
    @record.struct,
    key,
    value.to_s,
    1,
    LibHTS::BCF_HT_STR
  )
  raise "Failed to update INFO string field '#{key}': #{ret}" if ret < 0

  ret
end