@@ -79,17 +79,49 @@ def []=(key, value)
7979 # @param key [String] tag name (2 characters)
8080 # @param value [Integer] integer value
8181 def update_int ( key , value )
82+ validate_tag! ( key )
8283 ret = LibHTS . bam_aux_update_int ( @record . struct , key , value . to_i )
8384 raise "Failed to update integer tag '#{ key } ': errno #{ FFI . errno } " if ret < 0
8485
8586 value
8687 end
8788
89+ # Update or add a signed 8-bit integer tag.
90+ def update_int8 ( key , value )
91+ update_exact_integer ( key , value , "c" , -128 , 127 )
92+ end
93+
94+ # Update or add an unsigned 8-bit integer tag.
95+ def update_uint8 ( key , value )
96+ update_exact_integer ( key , value , "C" , 0 , 255 )
97+ end
98+
99+ # Update or add a signed 16-bit integer tag.
100+ def update_int16 ( key , value )
101+ update_exact_integer ( key , value , "s" , -32_768 , 32_767 )
102+ end
103+
104+ # Update or add an unsigned 16-bit integer tag.
105+ def update_uint16 ( key , value )
106+ update_exact_integer ( key , value , "S" , 0 , 65_535 )
107+ end
108+
109+ # Update or add a signed 32-bit integer tag.
110+ def update_int32 ( key , value )
111+ update_exact_integer ( key , value , "i" , -2_147_483_648 , 2_147_483_647 )
112+ end
113+
114+ # Update or add an unsigned 32-bit integer tag.
115+ def update_uint32 ( key , value )
116+ update_exact_integer ( key , value , "I" , 0 , 4_294_967_295 )
117+ end
118+
88119 # Update or add a floating-point tag
89120 # For compatibility with HTS.cr.
90121 # @param key [String] tag name (2 characters)
91122 # @param value [Float] floating-point value
92123 def update_float ( key , value )
124+ validate_tag! ( key )
93125 ret = LibHTS . bam_aux_update_float ( @record . struct , key , value . to_f )
94126 raise "Failed to update float tag '#{ key } ': errno #{ FFI . errno } " if ret < 0
95127
@@ -101,18 +133,51 @@ def update_float(key, value)
101133 # @param key [String] tag name (2 characters)
102134 # @param value [String] string value
103135 def update_string ( key , value )
136+ validate_tag! ( key )
104137 ret = LibHTS . bam_aux_update_str ( @record . struct , key , -1 , value . to_s )
105138 raise "Failed to update string tag '#{ key } ': errno #{ FFI . errno } " if ret < 0
106139
107140 value
108141 end
109142
143+ # Update or add a character tag.
144+ def update_char ( key , value )
145+ validate_tag! ( key )
146+
147+ string = value . to_s
148+ raise ArgumentError , "Character AUX tags must be a single character" unless string . length == 1
149+
150+ replace_with_append ( key , "A" , string . b )
151+ string
152+ end
153+
154+ # Update or add a hexadecimal string tag.
155+ def update_hex ( key , value )
156+ validate_tag! ( key )
157+
158+ string = value . to_s
159+ raise ArgumentError , "Hex AUX tags must contain an even number of characters" if string . length . odd?
160+ raise ArgumentError , "Hex AUX tags must contain only hexadecimal characters" unless /\A [0-9A-Fa-f]*\z / . match? ( string )
161+
162+ replace_with_append ( key , "H" , string . b + "\0 " )
163+ string
164+ end
165+
166+ # Update or add a double-precision floating-point tag.
167+ def update_double ( key , value )
168+ validate_tag! ( key )
169+
170+ replace_with_append ( key , "d" , [ Float ( value ) ] . pack ( "E" ) )
171+ value . to_f
172+ end
173+
110174 # Update or add an array tag
111175 # For compatibility with HTS.cr.
112176 # @param key [String] tag name (2 characters)
113177 # @param value [Array] array of integers or floats
114178 # @param type [String, nil] element type ('c', 'C', 's', 'S', 'i', 'I', 'f'). Auto-detected if nil.
115179 def update_array ( key , value , type : nil )
180+ validate_tag! ( key )
116181 raise ArgumentError , "Array cannot be empty" if value . empty?
117182
118183 # Auto-detect type if not specified
@@ -127,21 +192,10 @@ def update_array(key, value, type: nil)
127192 end
128193 end
129194
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
195+ payload = pack_array_payload ( value , type )
196+ ptr = FFI ::MemoryPointer . new ( :uint8 , payload . bytesize )
197+ ptr . put_bytes ( 0 , payload )
198+ ret = LibHTS . bam_aux_update_array ( @record . struct , key , type . ord , value . size , ptr )
145199
146200 raise "Failed to update array tag '#{ key } ': errno #{ FFI . errno } " if ret < 0
147201
@@ -213,6 +267,86 @@ def first_pointer
213267 LibHTS . bam_aux_first ( @record . struct )
214268 end
215269
270+ def validate_tag! ( key )
271+ raise ArgumentError , "AUX tag must be a 2-character String" unless key . is_a? ( String ) && key . length == 2
272+ end
273+
274+ def update_exact_integer ( key , value , type , min , max )
275+ validate_tag! ( key )
276+
277+ integer = Integer ( value )
278+ raise RangeError , "Value #{ integer } is out of range for AUX type #{ type } " unless integer . between? ( min , max )
279+
280+ replace_with_append ( key , type , pack_scalar_payload ( integer , type ) )
281+ integer
282+ end
283+
284+ def replace_with_append ( key , type , payload )
285+ delete ( key ) if key? ( key )
286+
287+ ptr = FFI ::MemoryPointer . new ( :uint8 , payload . bytesize )
288+ ptr . put_bytes ( 0 , payload )
289+ ret = LibHTS . bam_aux_append ( @record . struct , key , type . ord , payload . bytesize , ptr )
290+ raise "Failed to update #{ type } tag '#{ key } ': errno #{ FFI . errno } " if ret < 0
291+
292+ true
293+ end
294+
295+ def pack_scalar_payload ( value , type )
296+ case type
297+ when "c"
298+ [ value ] . pack ( "c" )
299+ when "C"
300+ [ value ] . pack ( "C" )
301+ when "s"
302+ [ value ] . pack ( "s<" )
303+ when "S"
304+ [ value ] . pack ( "S<" )
305+ when "i"
306+ [ value ] . pack ( "l<" )
307+ when "I"
308+ [ value ] . pack ( "L<" )
309+ else
310+ raise ArgumentError , "Unsupported scalar AUX type: #{ type } "
311+ end
312+ end
313+
314+ def pack_array_payload ( value , type )
315+ case type
316+ when "c"
317+ validate_integer_array_range! ( value , -128 , 127 , type )
318+ value . pack ( "c*" )
319+ when "C"
320+ validate_integer_array_range! ( value , 0 , 255 , type )
321+ value . pack ( "C*" )
322+ when "s"
323+ validate_integer_array_range! ( value , -32_768 , 32_767 , type )
324+ value . pack ( "s<*" )
325+ when "S"
326+ validate_integer_array_range! ( value , 0 , 65_535 , type )
327+ value . pack ( "S<*" )
328+ when "i"
329+ validate_integer_array_range! ( value , -2_147_483_648 , 2_147_483_647 , type )
330+ value . pack ( "l<*" )
331+ when "I"
332+ validate_integer_array_range! ( value , 0 , 4_294_967_295 , type )
333+ value . pack ( "L<*" )
334+ when "f"
335+ value . map ( &:to_f ) . pack ( "e*" )
336+ else
337+ raise ArgumentError , "Invalid array type: #{ type } "
338+ end
339+ end
340+
341+ def validate_integer_array_range! ( value , min , max , type )
342+ value . each do |element |
343+ integer = Integer ( element )
344+ unless integer . between? ( min , max )
345+ raise RangeError , "Array element #{ integer } is out of range for AUX array type #{ type } "
346+ end
347+ end
348+ end
349+
216350 def get_ruby_aux ( aux_ptr , type = nil )
217351 type = type ? type . to_s : aux_ptr . read_string ( 1 )
218352
0 commit comments