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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -49
  3. data/TUTORIAL.md +9 -20
  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 +1108 -0
  8. data/ext/htslib_native/native_bcf.c +369 -0
  9. data/ext/htslib_native/native_faidx.c +155 -0
  10. data/ext/htslib_native/native_tabix.c +246 -0
  11. data/lib/hts/bam/auxi.rb +87 -342
  12. data/lib/hts/bam/base_mod.rb +37 -73
  13. data/lib/hts/bam/cigar.rb +9 -55
  14. data/lib/hts/bam/flag.rb +19 -27
  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 +102 -42
  20. data/lib/hts/bcf/errors.rb +1 -0
  21. data/lib/hts/bcf/format.rb +278 -379
  22. data/lib/hts/bcf/header.rb +38 -122
  23. data/lib/hts/bcf/header_record.rb +9 -35
  24. data/lib/hts/bcf/info.rb +72 -320
  25. data/lib/hts/bcf/record.rb +61 -102
  26. data/lib/hts/bcf.rb +149 -323
  27. data/lib/hts/faidx.rb +28 -87
  28. data/lib/hts/hts.rb +6 -94
  29. data/lib/hts/native.rb +13 -0
  30. data/lib/hts/tabix.rb +93 -48
  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
@@ -0,0 +1,1108 @@
1
+ #include "htslib_native.h"
2
+ #include <ruby/thread.h>
3
+
4
+ #include <htslib/bgzf.h>
5
+ #include <htslib/cram.h>
6
+ #include <htslib/hfile.h>
7
+ #include <htslib/hts.h>
8
+ #include <htslib/kstring.h>
9
+ #include <htslib/sam.h>
10
+ #include <errno.h>
11
+ #include <stdint.h>
12
+ #include <stdlib.h>
13
+ #include <string.h>
14
+
15
+ typedef struct { sam_hdr_t *pointer; } ruby_sam_header_t;
16
+ typedef struct { bam1_t *pointer; } ruby_bam_record_t;
17
+ typedef struct {
18
+ htsFile *file;
19
+ hts_idx_t *index;
20
+ VALUE path;
21
+ int active_io;
22
+ } ruby_bam_file_t;
23
+ typedef struct {
24
+ hts_itr_t *iterator;
25
+ VALUE file;
26
+ } ruby_bam_iterator_t;
27
+ typedef struct {
28
+ hts_base_mod_state *state;
29
+ VALUE record;
30
+ } ruby_base_mod_t;
31
+ typedef struct {
32
+ bam_plp_t pileup;
33
+ hts_itr_t *iterator;
34
+ VALUE file;
35
+ VALUE header;
36
+ } ruby_pileup_t;
37
+ typedef struct {
38
+ ruby_bam_file_t *file;
39
+ sam_hdr_t *header;
40
+ hts_itr_t *iterator;
41
+ } ruby_mplp_input_t;
42
+ typedef struct {
43
+ bam_mplp_t pileup;
44
+ int count;
45
+ ruby_mplp_input_t *inputs;
46
+ void **input_data;
47
+ int *depths;
48
+ const bam_pileup1_t **entries;
49
+ VALUE files;
50
+ VALUE headers;
51
+ } ruby_mpileup_t;
52
+
53
+ static VALUE cNativeSamHeader, cNativeBamRecord, cNativeBamFile, cNativeBamIterator, cNativeBaseMod, cNativePileup, cNativeMpileup;
54
+
55
+ static void sam_header_free(void *data) {
56
+ ruby_sam_header_t *value = data;
57
+ if (value->pointer) sam_hdr_destroy(value->pointer);
58
+ xfree(value);
59
+ }
60
+ static size_t sam_header_size(const void *data) { return data ? sizeof(ruby_sam_header_t) : 0; }
61
+ static const rb_data_type_t sam_header_type = {
62
+ .wrap_struct_name = "HTS::Native::SamHeaderHandle",
63
+ .function = { .dfree = sam_header_free, .dsize = sam_header_size },
64
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
65
+ };
66
+ static ruby_sam_header_t *get_header(VALUE self) {
67
+ ruby_sam_header_t *value;
68
+ TypedData_Get_Struct(self, ruby_sam_header_t, &sam_header_type, value);
69
+ if (!value->pointer) rb_raise(rb_eIOError, "closed BAM header");
70
+ return value;
71
+ }
72
+ static VALUE wrap_header(sam_hdr_t *header) {
73
+ ruby_sam_header_t *value;
74
+ VALUE object;
75
+ if (!header) rb_raise(rb_eRuntimeError, "failed to create BAM header");
76
+ object = TypedData_Make_Struct(cNativeSamHeader, ruby_sam_header_t, &sam_header_type, value);
77
+ value->pointer = header;
78
+ return object;
79
+ }
80
+
81
+ static void bam_record_free(void *data) {
82
+ ruby_bam_record_t *value = data;
83
+ if (value->pointer) bam_destroy1(value->pointer);
84
+ xfree(value);
85
+ }
86
+ static size_t bam_record_size(const void *data) { return data ? sizeof(ruby_bam_record_t) : 0; }
87
+ static const rb_data_type_t bam_record_type = {
88
+ .wrap_struct_name = "HTS::Native::BamRecordHandle",
89
+ .function = { .dfree = bam_record_free, .dsize = bam_record_size },
90
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
91
+ };
92
+ static ruby_bam_record_t *get_record(VALUE self) {
93
+ ruby_bam_record_t *value;
94
+ TypedData_Get_Struct(self, ruby_bam_record_t, &bam_record_type, value);
95
+ if (!value->pointer) rb_raise(rb_eIOError, "closed BAM record");
96
+ return value;
97
+ }
98
+ static VALUE wrap_record(bam1_t *record) {
99
+ ruby_bam_record_t *value;
100
+ VALUE object;
101
+ if (!record) rb_raise(rb_eNoMemError, "bam_init1 failed");
102
+ object = TypedData_Make_Struct(cNativeBamRecord, ruby_bam_record_t, &bam_record_type, value);
103
+ value->pointer = record;
104
+ return object;
105
+ }
106
+
107
+ static void bam_file_mark(void *data) { rb_gc_mark_movable(((ruby_bam_file_t *)data)->path); }
108
+ static void bam_file_compact(void *data) {
109
+ ruby_bam_file_t *value = data;
110
+ value->path = rb_gc_location(value->path);
111
+ }
112
+ static void bam_file_free(void *data) {
113
+ ruby_bam_file_t *value = data;
114
+ if (value->index) hts_idx_destroy(value->index);
115
+ if (value->file) hts_close(value->file);
116
+ xfree(value);
117
+ }
118
+ static size_t bam_file_size(const void *data) { return data ? sizeof(ruby_bam_file_t) : 0; }
119
+ static const rb_data_type_t bam_file_type = {
120
+ .wrap_struct_name = "HTS::Native::BamFileHandle",
121
+ .function = { .dmark = bam_file_mark, .dfree = bam_file_free, .dsize = bam_file_size, .dcompact = bam_file_compact },
122
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
123
+ };
124
+ static ruby_bam_file_t *get_file(VALUE self, int allow_closed) {
125
+ ruby_bam_file_t *value;
126
+ TypedData_Get_Struct(self, ruby_bam_file_t, &bam_file_type, value);
127
+ if (!allow_closed && !value->file) rb_raise(rb_eIOError, "closed stream");
128
+ return value;
129
+ }
130
+
131
+ static void bam_iterator_mark(void *data) { rb_gc_mark_movable(((ruby_bam_iterator_t *)data)->file); }
132
+ static void bam_iterator_compact(void *data) {
133
+ ruby_bam_iterator_t *value = data;
134
+ value->file = rb_gc_location(value->file);
135
+ }
136
+ static void bam_iterator_free(void *data) {
137
+ ruby_bam_iterator_t *value = data;
138
+ if (value->iterator) hts_itr_destroy(value->iterator);
139
+ xfree(value);
140
+ }
141
+ static size_t bam_iterator_size(const void *data) { return data ? sizeof(ruby_bam_iterator_t) : 0; }
142
+ static const rb_data_type_t bam_iterator_type = {
143
+ .wrap_struct_name = "HTS::Native::BamIteratorHandle",
144
+ .function = { .dmark = bam_iterator_mark, .dfree = bam_iterator_free, .dsize = bam_iterator_size, .dcompact = bam_iterator_compact },
145
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
146
+ };
147
+ static ruby_bam_iterator_t *get_iterator(VALUE self) {
148
+ ruby_bam_iterator_t *value;
149
+ TypedData_Get_Struct(self, ruby_bam_iterator_t, &bam_iterator_type, value);
150
+ if (!value->iterator) rb_raise(rb_eIOError, "closed BAM iterator");
151
+ return value;
152
+ }
153
+ static VALUE wrap_iterator(VALUE file_object, hts_itr_t *iterator) {
154
+ ruby_bam_iterator_t *value;
155
+ VALUE object;
156
+ if (!iterator) return Qnil;
157
+ object = TypedData_Make_Struct(cNativeBamIterator, ruby_bam_iterator_t, &bam_iterator_type, value);
158
+ value->iterator = iterator;
159
+ value->file = file_object;
160
+ RB_OBJ_WRITE(object, &value->file, file_object);
161
+ return object;
162
+ }
163
+
164
+ /* Header */
165
+ static VALUE native_header_create(VALUE klass) { return wrap_header(sam_hdr_init()); }
166
+ static VALUE native_header_parse(VALUE klass, VALUE text) {
167
+ StringValue(text);
168
+ return wrap_header(sam_hdr_parse(RSTRING_LEN(text), RSTRING_PTR(text)));
169
+ }
170
+ static VALUE native_header_duplicate(VALUE self) { return wrap_header(sam_hdr_dup(get_header(self)->pointer)); }
171
+ static VALUE native_header_target_count(VALUE self) { return INT2NUM(sam_hdr_nref(get_header(self)->pointer)); }
172
+ static VALUE native_header_target_name(VALUE self, VALUE tid) {
173
+ const char *name = sam_hdr_tid2name(get_header(self)->pointer, NUM2INT(tid));
174
+ return name ? rb_str_new_cstr(name) : Qnil;
175
+ }
176
+ static VALUE native_header_target_length(VALUE self, VALUE tid) {
177
+ return LL2NUM(sam_hdr_tid2len(get_header(self)->pointer, NUM2INT(tid)));
178
+ }
179
+ static VALUE native_header_name2tid(VALUE self, VALUE name) {
180
+ return INT2NUM(sam_hdr_name2tid(get_header(self)->pointer, StringValueCStr(name)));
181
+ }
182
+ static VALUE native_header_to_s(VALUE self) {
183
+ const char *text = sam_hdr_str(get_header(self)->pointer);
184
+ return text ? rb_str_new_cstr(text) : rb_str_new("", 0);
185
+ }
186
+ static VALUE native_header_add_lines(VALUE self, VALUE text) {
187
+ StringValue(text);
188
+ return INT2NUM(sam_hdr_add_lines(get_header(self)->pointer, RSTRING_PTR(text), RSTRING_LEN(text)));
189
+ }
190
+ static VALUE header_kstring_result(int code, kstring_t *string) {
191
+ VALUE result = code == 0 ? rb_str_new(string->s, string->l) : Qnil;
192
+ free(string->s);
193
+ return result;
194
+ }
195
+ static VALUE native_header_find_line(VALUE self, VALUE type, VALUE key, VALUE value) {
196
+ kstring_t str = KS_INITIALIZE;
197
+ int code = sam_hdr_find_line_id(get_header(self)->pointer, StringValueCStr(type),
198
+ NIL_P(key) ? NULL : StringValueCStr(key),
199
+ NIL_P(value) ? NULL : StringValueCStr(value), &str);
200
+ return header_kstring_result(code, &str);
201
+ }
202
+ static VALUE native_header_find_tag(VALUE self, VALUE type, VALUE id_key, VALUE id_value, VALUE key) {
203
+ kstring_t str = KS_INITIALIZE;
204
+ int code = sam_hdr_find_tag_id(get_header(self)->pointer, StringValueCStr(type), StringValueCStr(id_key),
205
+ StringValueCStr(id_value), StringValueCStr(key), &str);
206
+ return header_kstring_result(code, &str);
207
+ }
208
+ static VALUE native_header_find_line_at(VALUE self, VALUE type, VALUE position) {
209
+ kstring_t str = KS_INITIALIZE;
210
+ int code = sam_hdr_find_line_pos(get_header(self)->pointer, StringValueCStr(type), NUM2INT(position), &str);
211
+ return header_kstring_result(code, &str);
212
+ }
213
+ static VALUE native_header_remove_line(VALUE self, VALUE type, VALUE key, VALUE value) {
214
+ return INT2NUM(sam_hdr_remove_line_id(get_header(self)->pointer, StringValueCStr(type),
215
+ NIL_P(key) ? NULL : StringValueCStr(key),
216
+ NIL_P(value) ? NULL : StringValueCStr(value)));
217
+ }
218
+ static VALUE native_header_remove_line_at(VALUE self, VALUE type, VALUE position) {
219
+ return INT2NUM(sam_hdr_remove_line_pos(get_header(self)->pointer, StringValueCStr(type), NUM2INT(position)));
220
+ }
221
+ static VALUE native_header_remove_tag(VALUE self, VALUE type, VALUE id_key, VALUE id_value, VALUE key) {
222
+ return INT2NUM(sam_hdr_remove_tag_id(get_header(self)->pointer, StringValueCStr(type), StringValueCStr(id_key),
223
+ StringValueCStr(id_value), StringValueCStr(key)));
224
+ }
225
+ static VALUE native_header_count_lines(VALUE self, VALUE type) {
226
+ return INT2NUM(sam_hdr_count_lines(get_header(self)->pointer, StringValueCStr(type)));
227
+ }
228
+ static VALUE native_header_line_index(VALUE self, VALUE type, VALUE key) {
229
+ return INT2NUM(sam_hdr_line_index(get_header(self)->pointer, StringValueCStr(type), StringValueCStr(key)));
230
+ }
231
+ static VALUE native_header_line_name(VALUE self, VALUE type, VALUE pos) {
232
+ const char *name = sam_hdr_line_name(get_header(self)->pointer, StringValueCStr(type), NUM2INT(pos));
233
+ return name ? rb_str_new_cstr(name) : Qnil;
234
+ }
235
+
236
+ /* Record */
237
+ static VALUE native_record_create(VALUE klass) { return wrap_record(bam_init1()); }
238
+ static VALUE native_record_duplicate(VALUE self) { return wrap_record(bam_dup1(get_record(self)->pointer)); }
239
+ static VALUE native_record_replace(VALUE self, VALUE qname, VALUE flag, VALUE tid, VALUE pos,
240
+ VALUE mapq, VALUE cigar_value, VALUE mtid, VALUE mpos,
241
+ VALUE isize, VALUE sequence, VALUE qualities) {
242
+ bam1_t *record = get_record(self)->pointer;
243
+ long cigar_count, i;
244
+ uint32_t *cigar = NULL;
245
+ const char *quality_data = NULL;
246
+ int result;
247
+
248
+ StringValue(qname);
249
+ StringValue(sequence);
250
+ Check_Type(cigar_value, T_ARRAY);
251
+ cigar_count = RARRAY_LEN(cigar_value);
252
+ if (cigar_count > 0) {
253
+ cigar = ALLOC_N(uint32_t, cigar_count);
254
+ for (i = 0; i < cigar_count; i++) cigar[i] = NUM2UINT(rb_ary_entry(cigar_value, i));
255
+ }
256
+ if (!NIL_P(qualities)) {
257
+ StringValue(qualities);
258
+ if (RSTRING_LEN(qualities) != RSTRING_LEN(sequence)) {
259
+ if (cigar) xfree(cigar);
260
+ rb_raise(rb_eArgError, "qualities length must match sequence length");
261
+ }
262
+ quality_data = RSTRING_PTR(qualities);
263
+ }
264
+
265
+ errno = 0;
266
+ result = bam_set1(record,
267
+ (size_t)RSTRING_LEN(qname) + 1, RSTRING_PTR(qname),
268
+ NUM2UINT(flag), NUM2INT(tid), NUM2LL(pos), NUM2UINT(mapq),
269
+ (size_t)cigar_count, cigar,
270
+ NUM2INT(mtid), NUM2LL(mpos), NUM2LL(isize),
271
+ (size_t)RSTRING_LEN(sequence), RSTRING_PTR(sequence), quality_data, 0);
272
+ if (cigar) xfree(cigar);
273
+ if (result < 0) {
274
+ if (errno) rb_sys_fail("bam_set1");
275
+ rb_raise(rb_eRuntimeError, "bam_set1 failed: %d", result);
276
+ }
277
+ return self;
278
+ }
279
+ static VALUE native_record_qname(VALUE self) { return rb_str_new_cstr(bam_get_qname(get_record(self)->pointer)); }
280
+ static VALUE native_record_set_qname(VALUE self, VALUE name) {
281
+ int result = bam_set_qname(get_record(self)->pointer, StringValueCStr(name));
282
+ if (result < 0) rb_raise(rb_eRuntimeError, "bam_set_qname failed: %d", result);
283
+ return name;
284
+ }
285
+ static VALUE native_record_core_get(VALUE self, VALUE field) {
286
+ bam1_core_t *core = &get_record(self)->pointer->core;
287
+ ID id = SYM2ID(field);
288
+ if (id == rb_intern("tid")) return INT2NUM(core->tid);
289
+ if (id == rb_intern("mtid")) return INT2NUM(core->mtid);
290
+ if (id == rb_intern("pos")) return LL2NUM(core->pos);
291
+ if (id == rb_intern("mpos")) return LL2NUM(core->mpos);
292
+ if (id == rb_intern("bin")) return UINT2NUM(core->bin);
293
+ if (id == rb_intern("isize")) return LL2NUM(core->isize);
294
+ if (id == rb_intern("mapq")) return UINT2NUM(core->qual);
295
+ if (id == rb_intern("flag")) return UINT2NUM(core->flag);
296
+ if (id == rb_intern("length")) return LL2NUM(core->l_qseq);
297
+ rb_raise(rb_eArgError, "unknown BAM core field");
298
+ }
299
+ static VALUE native_record_core_set(VALUE self, VALUE field, VALUE new_value) {
300
+ bam1_core_t *core = &get_record(self)->pointer->core;
301
+ ID id = SYM2ID(field);
302
+ if (id == rb_intern("tid")) core->tid = NUM2INT(new_value);
303
+ else if (id == rb_intern("mtid")) core->mtid = NUM2INT(new_value);
304
+ else if (id == rb_intern("pos")) core->pos = NUM2LL(new_value);
305
+ else if (id == rb_intern("mpos")) core->mpos = NUM2LL(new_value);
306
+ else if (id == rb_intern("bin")) core->bin = NUM2UINT(new_value);
307
+ else if (id == rb_intern("isize")) core->isize = NUM2LL(new_value);
308
+ else if (id == rb_intern("mapq")) core->qual = NUM2UINT(new_value);
309
+ else if (id == rb_intern("flag")) core->flag = NUM2UINT(new_value);
310
+ else rb_raise(rb_eArgError, "unknown or read-only BAM core field");
311
+ return new_value;
312
+ }
313
+ static VALUE native_record_endpos(VALUE self) { return LL2NUM(bam_endpos(get_record(self)->pointer)); }
314
+ static VALUE native_record_cigar_values(VALUE self) {
315
+ bam1_t *record = get_record(self)->pointer;
316
+ uint32_t *cigar = bam_get_cigar(record);
317
+ VALUE result = rb_ary_new_capa(record->core.n_cigar);
318
+ uint32_t i;
319
+ for (i = 0; i < record->core.n_cigar; i++) rb_ary_push(result, UINT2NUM(cigar[i]));
320
+ return result;
321
+ }
322
+ static VALUE native_record_set_cigar(VALUE self, VALUE text) {
323
+ int result = bam_parse_cigar(StringValueCStr(text), NULL, get_record(self)->pointer);
324
+ if (result < 0) rb_raise(rb_eRuntimeError, "bam_parse_cigar failed: %d", result);
325
+ return text;
326
+ }
327
+ static VALUE native_record_qlen(VALUE self) {
328
+ bam1_t *record = get_record(self)->pointer;
329
+ return LL2NUM(bam_cigar2qlen(record->core.n_cigar, bam_get_cigar(record)));
330
+ }
331
+ static VALUE native_record_rlen(VALUE self) {
332
+ bam1_t *record = get_record(self)->pointer;
333
+ return LL2NUM(bam_cigar2rlen(record->core.n_cigar, bam_get_cigar(record)));
334
+ }
335
+ static VALUE native_record_sequence(VALUE self) {
336
+ static const char table[] = "=ACMGRSVTWYHKDBN";
337
+ bam1_t *record = get_record(self)->pointer;
338
+ uint8_t *packed = bam_get_seq(record);
339
+ hts_pos_t i, length = record->core.l_qseq;
340
+ VALUE result = rb_str_new(NULL, length);
341
+ char *out = RSTRING_PTR(result);
342
+ for (i = 0; i < length; i++) out[i] = table[bam_seqi(packed, i)];
343
+ return result;
344
+ }
345
+ static VALUE native_record_sequence_codes(VALUE self) {
346
+ bam1_t *record = get_record(self)->pointer;
347
+ uint8_t *packed = bam_get_seq(record);
348
+ hts_pos_t i, length = record->core.l_qseq;
349
+ VALUE result = rb_ary_new_capa(length);
350
+ for (i = 0; i < length; i++) rb_ary_push(result, INT2NUM(bam_seqi(packed, i)));
351
+ return result;
352
+ }
353
+ static VALUE native_record_qualities(VALUE self) {
354
+ bam1_t *record = get_record(self)->pointer;
355
+ uint8_t *qualities = bam_get_qual(record);
356
+ hts_pos_t i, length = record->core.l_qseq;
357
+ VALUE result = rb_ary_new_capa(length);
358
+ for (i = 0; i < length; i++) rb_ary_push(result, UINT2NUM(qualities[i]));
359
+ return result;
360
+ }
361
+ static VALUE native_record_quality_string(VALUE self) {
362
+ bam1_t *record = get_record(self)->pointer;
363
+ uint8_t *qualities = bam_get_qual(record);
364
+ hts_pos_t i, length = record->core.l_qseq;
365
+ VALUE result;
366
+ if (length == 0) return rb_str_new("", 0);
367
+ if (qualities[0] == 255) return rb_str_new("*", 1);
368
+ result = rb_str_new(NULL, length);
369
+ for (i = 0; i < length; i++) RSTRING_PTR(result)[i] = (char)(qualities[i] + 33);
370
+ return result;
371
+ }
372
+ static VALUE native_record_base_code(VALUE self, VALUE index) {
373
+ bam1_t *record = get_record(self)->pointer;
374
+ long i = NUM2LONG(index);
375
+ if (i < 0 || i >= record->core.l_qseq) return Qnil;
376
+ return INT2NUM(bam_seqi(bam_get_seq(record), i));
377
+ }
378
+ static VALUE native_record_quality_at(VALUE self, VALUE index) {
379
+ bam1_t *record = get_record(self)->pointer;
380
+ long i = NUM2LONG(index);
381
+ if (i < 0 || i >= record->core.l_qseq) return Qnil;
382
+ return UINT2NUM(bam_get_qual(record)[i]);
383
+ }
384
+ static VALUE native_record_to_s(VALUE self, VALUE header_value) {
385
+ kstring_t string = KS_INITIALIZE;
386
+ int result = sam_format1(get_header(header_value)->pointer, get_record(self)->pointer, &string);
387
+ VALUE value;
388
+ if (result < 0) { free(string.s); rb_raise(rb_eRuntimeError, "failed to format BAM record"); }
389
+ value = rb_str_new(string.s, string.l);
390
+ free(string.s);
391
+ return value;
392
+ }
393
+ static VALUE native_cigar_parse(VALUE klass, VALUE text) {
394
+ uint32_t *cigar = NULL;
395
+ size_t capacity = 0;
396
+ long result = sam_parse_cigar(StringValueCStr(text), NULL, &cigar, &capacity), i;
397
+ VALUE array;
398
+ if (result < 0) { free(cigar); rb_raise(rb_eRuntimeError, "sam_parse_cigar failed: %ld", result); }
399
+ array = rb_ary_new_capa(result);
400
+ for (i = 0; i < result; i++) rb_ary_push(array, UINT2NUM(cigar[i]));
401
+ free(cigar);
402
+ return array;
403
+ }
404
+ static VALUE native_cigar_qlen(VALUE klass, VALUE array) {
405
+ long count = RARRAY_LEN(array), i;
406
+ uint32_t *values = ALLOC_N(uint32_t, count ? count : 1);
407
+ for (i = 0; i < count; i++) values[i] = NUM2UINT(rb_ary_entry(array, i));
408
+ hts_pos_t result = bam_cigar2qlen(count, values);
409
+ xfree(values);
410
+ return LL2NUM(result);
411
+ }
412
+ static VALUE native_cigar_rlen(VALUE klass, VALUE array) {
413
+ long count = RARRAY_LEN(array), i;
414
+ uint32_t *values = ALLOC_N(uint32_t, count ? count : 1);
415
+ for (i = 0; i < count; i++) values[i] = NUM2UINT(rb_ary_entry(array, i));
416
+ hts_pos_t result = bam_cigar2rlen(count, values);
417
+ xfree(values);
418
+ return LL2NUM(result);
419
+ }
420
+
421
+ static VALUE aux_decode(uint8_t *aux, VALUE requested) {
422
+ char actual = (char)aux[0], wanted = NIL_P(requested) ? actual : StringValueCStr(requested)[0];
423
+ VALUE value, pair;
424
+ int compatible = (wanted == actual) ||
425
+ (strchr("iIcCsS", wanted) && strchr("iIcCsS", actual)) ||
426
+ (strchr("fd", wanted) && strchr("fd", actual)) ||
427
+ (strchr("ZH", wanted) && strchr("ZH", actual));
428
+ if (!compatible) rb_raise(rb_eTypeError, "AUX type mismatch: requested %c, actual %c", wanted, actual);
429
+ switch (wanted) {
430
+ case 'i': case 'I': case 'c': case 'C': case 's': case 'S': value = LL2NUM(bam_aux2i(aux)); break;
431
+ case 'f': case 'd': value = DBL2NUM(bam_aux2f(aux)); break;
432
+ case 'Z': case 'H': { char *str = bam_aux2Z(aux); value = str ? rb_str_new_cstr(str) : Qnil; break; }
433
+ case 'A': value = rb_str_new((char[]){bam_aux2A(aux)}, 1); break;
434
+ case 'B': {
435
+ uint32_t count = bam_auxB_len(aux), i;
436
+ char subtype = (char)aux[1];
437
+ value = rb_ary_new_capa(count);
438
+ for (i = 0; i < count; i++) {
439
+ if (subtype == 'f') rb_ary_push(value, DBL2NUM(bam_auxB2f(aux, i)));
440
+ else rb_ary_push(value, LL2NUM(bam_auxB2i(aux, i)));
441
+ }
442
+ break;
443
+ }
444
+ default: rb_raise(rb_eNotImpError, "unsupported AUX type: %c", wanted);
445
+ }
446
+ pair = rb_ary_new_capa(2);
447
+ if (actual == 'B') rb_ary_push(pair, rb_sprintf("B:%c", aux[1]));
448
+ else rb_ary_push(pair, rb_str_new(&actual, 1));
449
+ rb_ary_push(pair, value);
450
+ return pair;
451
+ }
452
+ static VALUE native_record_aux_get(VALUE self, VALUE key, VALUE requested) {
453
+ uint8_t *aux = bam_aux_get(get_record(self)->pointer, StringValueCStr(key));
454
+ return aux ? aux_decode(aux, requested) : Qnil;
455
+ }
456
+ static VALUE native_record_aux_entries(VALUE self) {
457
+ bam1_t *record = get_record(self)->pointer;
458
+ uint8_t *aux = bam_aux_first(record);
459
+ VALUE result = rb_ary_new();
460
+ while (aux) {
461
+ VALUE entry = aux_decode(aux, Qnil);
462
+ rb_ary_unshift(entry, rb_str_new((char *)aux - 2, 2));
463
+ rb_ary_push(result, entry);
464
+ aux = bam_aux_next(record, aux);
465
+ }
466
+ return result;
467
+ }
468
+ static VALUE native_record_aux_update_int(VALUE self, VALUE key, VALUE value) {
469
+ return INT2NUM(bam_aux_update_int(get_record(self)->pointer, StringValueCStr(key), NUM2LL(value)));
470
+ }
471
+ static VALUE native_record_aux_update_float(VALUE self, VALUE key, VALUE value) {
472
+ return INT2NUM(bam_aux_update_float(get_record(self)->pointer, StringValueCStr(key), NUM2DBL(value)));
473
+ }
474
+ static VALUE native_record_aux_update_string(VALUE self, VALUE key, VALUE value) {
475
+ return INT2NUM(bam_aux_update_str(get_record(self)->pointer, StringValueCStr(key), -1, StringValueCStr(value)));
476
+ }
477
+ static VALUE native_record_aux_append(VALUE self, VALUE key, VALUE type, VALUE payload) {
478
+ StringValue(payload);
479
+ return INT2NUM(bam_aux_append(get_record(self)->pointer, StringValueCStr(key), StringValueCStr(type)[0],
480
+ RSTRING_LEN(payload), (uint8_t *)RSTRING_PTR(payload)));
481
+ }
482
+ static VALUE native_record_aux_update_array(VALUE self, VALUE key, VALUE type_value, VALUE array) {
483
+ char type = StringValueCStr(type_value)[0];
484
+ long count = RARRAY_LEN(array), i;
485
+ size_t width;
486
+ uint8_t *buffer;
487
+ int result;
488
+ switch (type) { case 'c': case 'C': width = 1; break; case 's': case 'S': width = 2; break; default: width = 4; }
489
+ buffer = ALLOC_N(uint8_t, count * width + 1);
490
+ for (i = 0; i < count; i++) {
491
+ VALUE item = rb_ary_entry(array, i);
492
+ if (type == 'c') { int8_t v = NUM2INT(item); memcpy(buffer + i, &v, 1); }
493
+ else if (type == 'C') { uint8_t v = NUM2UINT(item); memcpy(buffer + i, &v, 1); }
494
+ else if (type == 's') { int16_t v = NUM2INT(item); memcpy(buffer + i * 2, &v, 2); }
495
+ else if (type == 'S') { uint16_t v = NUM2UINT(item); memcpy(buffer + i * 2, &v, 2); }
496
+ else if (type == 'i') { int32_t v = NUM2INT(item); memcpy(buffer + i * 4, &v, 4); }
497
+ else if (type == 'I') { uint32_t v = NUM2UINT(item); memcpy(buffer + i * 4, &v, 4); }
498
+ else if (type == 'f') { float v = (float)NUM2DBL(item); memcpy(buffer + i * 4, &v, 4); }
499
+ else { xfree(buffer); rb_raise(rb_eArgError, "invalid AUX array type"); }
500
+ }
501
+ result = bam_aux_update_array(get_record(self)->pointer, StringValueCStr(key), type, count, buffer);
502
+ xfree(buffer);
503
+ return INT2NUM(result);
504
+ }
505
+ static VALUE native_record_aux_delete(VALUE self, VALUE key) {
506
+ bam1_t *record = get_record(self)->pointer;
507
+ uint8_t *aux = bam_aux_get(record, StringValueCStr(key));
508
+ if (!aux) return Qfalse;
509
+ if (bam_aux_del(record, aux) < 0) rb_raise(rb_eRuntimeError, "failed to delete AUX tag");
510
+ return Qtrue;
511
+ }
512
+ static VALUE native_record_aux_key(VALUE self, VALUE key) {
513
+ return bam_aux_get(get_record(self)->pointer, StringValueCStr(key)) ? Qtrue : Qfalse;
514
+ }
515
+
516
+ /* File and iterator */
517
+ static VALUE native_bam_open(VALUE klass, VALUE path_value, VALUE mode_value) {
518
+ ruby_bam_file_t *value;
519
+ VALUE object = TypedData_Make_Struct(klass, ruby_bam_file_t, &bam_file_type, value);
520
+ value->index = NULL;
521
+ value->active_io = 0;
522
+ value->path = rb_str_dup(StringValue(path_value));
523
+ RB_OBJ_WRITE(object, &value->path, value->path);
524
+ value->file = hts_open(StringValueCStr(value->path), StringValueCStr(mode_value));
525
+ if (!value->file) rb_syserr_fail_str(ENOENT, path_value);
526
+ return object;
527
+ }
528
+ static VALUE native_bam_close(VALUE self) {
529
+ ruby_bam_file_t *value = get_file(self, 1);
530
+ if (value->active_io) rb_raise(rb_eIOError, "cannot close BAM during active I/O");
531
+ if (value->index) { hts_idx_destroy(value->index); value->index = NULL; }
532
+ if (value->file) { hts_close(value->file); value->file = NULL; }
533
+ return Qnil;
534
+ }
535
+ static VALUE native_bam_closed(VALUE self) { return get_file(self, 1)->file ? Qfalse : Qtrue; }
536
+ typedef struct { htsFile *file; sam_hdr_t *header; bam1_t *record; hts_itr_t *iterator; int result; } bam_io_args_t;
537
+ typedef struct { htsFile *file; sam_hdr_t *result; } bam_header_read_args_t;
538
+ static void *bam_read_header_without_gvl(void *data) { bam_header_read_args_t *args=data; args->result=sam_hdr_read(args->file); return NULL; }
539
+ static void *bam_write_header_without_gvl(void *data) { bam_io_args_t *args=data; args->result=sam_hdr_write(args->file,args->header); return NULL; }
540
+ static void *bam_read_without_gvl(void *data) { bam_io_args_t *args=data; args->result=sam_read1(args->file,args->header,args->record); return NULL; }
541
+ static void *bam_write_without_gvl(void *data) { bam_io_args_t *args=data; args->result=sam_write1(args->file,args->header,args->record); return NULL; }
542
+ static void *bam_iterator_without_gvl(void *data) { bam_io_args_t *args=data; args->result=sam_itr_next(args->file,args->iterator,args->record); return NULL; }
543
+ static void bam_io_begin(ruby_bam_file_t *file) { if(file->active_io)rb_raise(rb_eIOError,"concurrent BAM I/O is not supported"); file->active_io=1; }
544
+ static VALUE native_bam_read_header(VALUE self) {
545
+ ruby_bam_file_t *file=get_file(self,0); bam_header_read_args_t args={file->file,NULL};
546
+ bam_io_begin(file); rb_thread_call_without_gvl(bam_read_header_without_gvl,&args,RUBY_UBF_IO,NULL); file->active_io=0;
547
+ return wrap_header(args.result);
548
+ }
549
+ static VALUE native_bam_write_header(VALUE self, VALUE header) {
550
+ ruby_bam_file_t *file=get_file(self,0); bam_io_args_t args={file->file,get_header(header)->pointer,NULL,NULL,0};
551
+ bam_io_begin(file); rb_thread_call_without_gvl(bam_write_header_without_gvl,&args,RUBY_UBF_IO,NULL); file->active_io=0; return INT2NUM(args.result);
552
+ }
553
+ static VALUE native_bam_read(VALUE self, VALUE header, VALUE record) {
554
+ ruby_bam_file_t *file=get_file(self,0); bam_io_args_t args={file->file,get_header(header)->pointer,get_record(record)->pointer,NULL,0};
555
+ bam_io_begin(file); rb_thread_call_without_gvl(bam_read_without_gvl,&args,RUBY_UBF_IO,NULL); file->active_io=0; return INT2NUM(args.result);
556
+ }
557
+ static VALUE native_bam_write(VALUE self, VALUE header, VALUE record) {
558
+ ruby_bam_file_t *file=get_file(self,0); bam_io_args_t args={file->file,get_header(header)->pointer,get_record(record)->pointer,NULL,0};
559
+ bam_io_begin(file); rb_thread_call_without_gvl(bam_write_without_gvl,&args,RUBY_UBF_IO,NULL); file->active_io=0; return INT2NUM(args.result);
560
+ }
561
+ static VALUE native_bam_set_fai(VALUE self, VALUE path) {
562
+ return INT2NUM(hts_set_fai_filename(get_file(self, 0)->file, StringValueCStr(path)));
563
+ }
564
+ static VALUE native_bam_set_threads(VALUE self, VALUE count) {
565
+ return INT2NUM(hts_set_threads(get_file(self, 0)->file, NUM2INT(count)));
566
+ }
567
+ static VALUE native_bam_format(VALUE self) {
568
+ const htsFormat *format = hts_get_format(get_file(self, 0)->file);
569
+ if (!format) return Qnil;
570
+ switch (format->format) {
571
+ case sam: return rb_str_new_cstr("sam");
572
+ case bam: return rb_str_new_cstr("bam");
573
+ case cram: return rb_str_new_cstr("cram");
574
+ case vcf: return rb_str_new_cstr("vcf");
575
+ case bcf: return rb_str_new_cstr("bcf");
576
+ default: return rb_str_new_cstr("unknown_format");
577
+ }
578
+ }
579
+ static VALUE native_bam_format_version(VALUE self) {
580
+ const htsFormat *format = hts_get_format(get_file(self, 0)->file);
581
+ if (!format) return Qnil;
582
+ if (format->version.minor == -1) return rb_sprintf("%d", format->version.major);
583
+ return rb_sprintf("%d.%d", format->version.major, format->version.minor);
584
+ }
585
+ static VALUE native_bam_seek(VALUE self, VALUE offset) {
586
+ htsFile *file = get_file(self, 0)->file;
587
+ int64_t position = NUM2LL(offset);
588
+ if (file->is_cram) return LL2NUM(cram_seek(file->fp.cram, position, SEEK_SET));
589
+ if (file->is_bgzf) return LL2NUM(bgzf_seek(file->fp.bgzf, position, SEEK_SET));
590
+ return LL2NUM(hseek(file->fp.hfile, position, SEEK_SET));
591
+ }
592
+ static VALUE native_bam_tell(VALUE self) {
593
+ htsFile *file = get_file(self, 0)->file;
594
+ if (file->is_cram) return Qnil;
595
+ if (file->is_bgzf) return LL2NUM(bgzf_tell(file->fp.bgzf));
596
+ return LL2NUM(htell(file->fp.hfile));
597
+ }
598
+ static VALUE native_bam_load_index(VALUE self, VALUE index_value) {
599
+ ruby_bam_file_t *value = get_file(self, 0);
600
+ if (value->index) { hts_idx_destroy(value->index); value->index = NULL; }
601
+ value->index = NIL_P(index_value)
602
+ ? sam_index_load3(value->file, StringValueCStr(value->path), NULL, HTS_IDX_SAVE_REMOTE)
603
+ : sam_index_load2(value->file, StringValueCStr(value->path), StringValueCStr(index_value));
604
+ return value->index ? Qtrue : Qfalse;
605
+ }
606
+ static VALUE native_bam_index_loaded(VALUE self) { return get_file(self, 0)->index ? Qtrue : Qfalse; }
607
+ static VALUE native_bam_build_index(VALUE klass, VALUE path, VALUE index, VALUE shift, VALUE threads) {
608
+ return INT2NUM(sam_index_build3(StringValueCStr(path), NIL_P(index) ? NULL : StringValueCStr(index),
609
+ NUM2INT(shift), NUM2INT(threads)));
610
+ }
611
+ static VALUE native_bam_query_interval(VALUE self, VALUE tid, VALUE beg, VALUE end) {
612
+ ruby_bam_file_t *file = get_file(self, 0);
613
+ if (!file->index) rb_raise(rb_eRuntimeError, "index file is required");
614
+ return wrap_iterator(self, sam_itr_queryi(file->index, NUM2INT(tid), NUM2LL(beg), NUM2LL(end)));
615
+ }
616
+ static VALUE native_bam_query_region(VALUE self, VALUE header, VALUE region) {
617
+ ruby_bam_file_t *file = get_file(self, 0);
618
+ if (!file->index) rb_raise(rb_eRuntimeError, "index file is required");
619
+ return wrap_iterator(self, sam_itr_querys(file->index, get_header(header)->pointer, StringValueCStr(region)));
620
+ }
621
+ static VALUE native_bam_iterator_next(VALUE self, VALUE record) {
622
+ ruby_bam_iterator_t *iterator = get_iterator(self);
623
+ ruby_bam_file_t *file = get_file(iterator->file, 0);
624
+ bam_io_args_t args={file->file,NULL,get_record(record)->pointer,iterator->iterator,0};
625
+ bam_io_begin(file);
626
+ rb_thread_call_without_gvl(bam_iterator_without_gvl,&args,RUBY_UBF_IO,NULL);
627
+ file->active_io=0;
628
+ return INT2NUM(args.result);
629
+ }
630
+ static VALUE native_bam_iterator_close(VALUE self) {
631
+ ruby_bam_iterator_t *value;
632
+ TypedData_Get_Struct(self, ruby_bam_iterator_t, &bam_iterator_type, value);
633
+ if (value->iterator) { hts_itr_destroy(value->iterator); value->iterator = NULL; }
634
+ return Qnil;
635
+ }
636
+
637
+ static VALUE native_bam_flag_string(VALUE native, VALUE flag) {
638
+ char *value = bam_flag2str(NUM2INT(flag));
639
+ VALUE result = value ? rb_str_new_cstr(value) : Qnil;
640
+ free(value);
641
+ return result;
642
+ }
643
+
644
+ static void base_mod_mark(void *data) { rb_gc_mark_movable(((ruby_base_mod_t *)data)->record); }
645
+ static void base_mod_compact(void *data) {
646
+ ruby_base_mod_t *value = data;
647
+ value->record = rb_gc_location(value->record);
648
+ }
649
+ static void base_mod_free(void *data) {
650
+ ruby_base_mod_t *value = data;
651
+ if (value->state) hts_base_mod_state_free(value->state);
652
+ xfree(value);
653
+ }
654
+ static size_t base_mod_size(const void *data) { return data ? sizeof(ruby_base_mod_t) : 0; }
655
+ static const rb_data_type_t base_mod_type = {
656
+ .wrap_struct_name = "HTS::Native::BaseModHandle",
657
+ .function = { .dmark = base_mod_mark, .dfree = base_mod_free, .dsize = base_mod_size, .dcompact = base_mod_compact },
658
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
659
+ };
660
+ static ruby_base_mod_t *get_base_mod(VALUE self) {
661
+ ruby_base_mod_t *value;
662
+ TypedData_Get_Struct(self, ruby_base_mod_t, &base_mod_type, value);
663
+ if (!value->state) rb_raise(rb_eIOError, "closed base modification state");
664
+ return value;
665
+ }
666
+ static VALUE native_base_mod_open(VALUE klass, VALUE record) {
667
+ ruby_base_mod_t *value;
668
+ VALUE object;
669
+ get_record(record);
670
+ object = TypedData_Make_Struct(klass, ruby_base_mod_t, &base_mod_type, value);
671
+ value->state = hts_base_mod_state_alloc();
672
+ if (!value->state) rb_raise(rb_eNoMemError, "failed to allocate base modification state");
673
+ value->record = record;
674
+ RB_OBJ_WRITE(object, &value->record, record);
675
+ return object;
676
+ }
677
+ static VALUE native_base_mod_close(VALUE self) {
678
+ ruby_base_mod_t *value;
679
+ TypedData_Get_Struct(self, ruby_base_mod_t, &base_mod_type, value);
680
+ if (value->state) { hts_base_mod_state_free(value->state); value->state = NULL; }
681
+ return Qnil;
682
+ }
683
+ static VALUE native_base_mod_parse(VALUE self, VALUE flags) {
684
+ ruby_base_mod_t *value = get_base_mod(self);
685
+ return INT2NUM(bam_parse_basemod2(get_record(value->record)->pointer, value->state, NUM2INT(flags)));
686
+ }
687
+ static VALUE base_mod_array(const hts_base_mod *mods, int count) {
688
+ int index;
689
+ VALUE result = rb_ary_new_capa(count);
690
+ for (index = 0; index < count; index++) {
691
+ VALUE item = rb_ary_new_capa(4);
692
+ rb_ary_push(item, INT2NUM(mods[index].canonical_base));
693
+ rb_ary_push(item, INT2NUM(mods[index].modified_base));
694
+ rb_ary_push(item, INT2NUM(mods[index].strand));
695
+ rb_ary_push(item, INT2NUM(mods[index].qual));
696
+ rb_ary_push(result, item);
697
+ }
698
+ return result;
699
+ }
700
+ static VALUE native_base_mod_at(VALUE self, VALUE position, VALUE max_value) {
701
+ ruby_base_mod_t *value = get_base_mod(self);
702
+ int max = NUM2INT(max_value), count;
703
+ hts_base_mod *mods;
704
+ if (max <= 0) rb_raise(rb_eArgError, "max_mods must be positive");
705
+ mods = ALLOC_N(hts_base_mod, max);
706
+ count = bam_mods_at_qpos(get_record(value->record)->pointer, NUM2INT(position), value->state, mods, max);
707
+ VALUE result = count > 0 ? base_mod_array(mods, count < max ? count : max) : Qnil;
708
+ xfree(mods);
709
+ return result;
710
+ }
711
+ static VALUE native_base_mod_each_raw(VALUE self, VALUE max_value) {
712
+ ruby_base_mod_t *value = get_base_mod(self);
713
+ int max = NUM2INT(max_value), count, position, index;
714
+ hts_base_mod *mods;
715
+ if (!rb_block_given_p()) rb_raise(rb_eArgError, "block is required");
716
+ if (max <= 0) rb_raise(rb_eArgError, "max_mods must be positive");
717
+ mods = ALLOC_N(hts_base_mod, max);
718
+ while ((count = bam_next_basemod(get_record(value->record)->pointer, value->state, mods, max, &position)) > 0) {
719
+ if (count > max) count = max;
720
+ for (index = 0; index < count; index++) {
721
+ rb_yield_values(5, INT2NUM(position), INT2NUM(mods[index].canonical_base),
722
+ INT2NUM(mods[index].modified_base), INT2NUM(mods[index].strand),
723
+ INT2NUM(mods[index].qual));
724
+ }
725
+ }
726
+ xfree(mods);
727
+ return self;
728
+ }
729
+ static VALUE native_base_mod_types(VALUE self) {
730
+ ruby_base_mod_t *value = get_base_mod(self);
731
+ int count = 0, index, *codes = bam_mods_recorded(value->state, &count);
732
+ VALUE result = rb_ary_new_capa(count);
733
+ for (index = 0; index < count; index++) rb_ary_push(result, INT2NUM(codes[index]));
734
+ return result;
735
+ }
736
+ static VALUE base_mod_info(int code, int result, int strand, int implicit, char canonical, int include_code) {
737
+ VALUE hash;
738
+ if (result < 0) return Qnil;
739
+ hash = rb_hash_new();
740
+ if (include_code) rb_hash_aset(hash, ID2SYM(rb_intern("code")), INT2NUM(code));
741
+ rb_hash_aset(hash, ID2SYM(rb_intern("canonical")), rb_str_new(&canonical, 1));
742
+ rb_hash_aset(hash, ID2SYM(rb_intern("strand")), INT2NUM(strand));
743
+ rb_hash_aset(hash, ID2SYM(rb_intern("implicit")), implicit ? Qtrue : Qfalse);
744
+ return hash;
745
+ }
746
+ static VALUE native_base_mod_query(VALUE self, VALUE code_value) {
747
+ ruby_base_mod_t *value = get_base_mod(self);
748
+ int code = NUM2INT(code_value), strand, implicit;
749
+ char canonical;
750
+ int result = bam_mods_query_type(value->state, code, &strand, &implicit, &canonical);
751
+ return base_mod_info(code, result, strand, implicit, canonical, 0);
752
+ }
753
+ static VALUE native_base_mod_query_at(VALUE self, VALUE index_value) {
754
+ ruby_base_mod_t *value = get_base_mod(self);
755
+ int index = NUM2INT(index_value), strand, implicit, count = 0;
756
+ char canonical;
757
+ int result = bam_mods_queryi(value->state, index, &strand, &implicit, &canonical);
758
+ int *codes = bam_mods_recorded(value->state, &count);
759
+ int code = index >= 0 && index < count ? codes[index] : 0;
760
+ return base_mod_info(code, result, strand, implicit, canonical, 1);
761
+ }
762
+
763
+ static void pileup_mark(void *data) {
764
+ ruby_pileup_t *value = data;
765
+ rb_gc_mark_movable(value->file);
766
+ rb_gc_mark_movable(value->header);
767
+ }
768
+ static void pileup_compact(void *data) {
769
+ ruby_pileup_t *value = data;
770
+ value->file = rb_gc_location(value->file);
771
+ value->header = rb_gc_location(value->header);
772
+ }
773
+ static void pileup_free(void *data) {
774
+ ruby_pileup_t *value = data;
775
+ if (value->pileup) bam_plp_destroy(value->pileup);
776
+ if (value->iterator) hts_itr_destroy(value->iterator);
777
+ xfree(value);
778
+ }
779
+ static size_t pileup_size(const void *data) { return data ? sizeof(ruby_pileup_t) : 0; }
780
+ static const rb_data_type_t pileup_type = {
781
+ .wrap_struct_name = "HTS::Native::PileupHandle",
782
+ .function = { .dmark = pileup_mark, .dfree = pileup_free, .dsize = pileup_size, .dcompact = pileup_compact },
783
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
784
+ };
785
+ static ruby_pileup_t *get_pileup(VALUE self, int allow_closed) {
786
+ ruby_pileup_t *value;
787
+ TypedData_Get_Struct(self, ruby_pileup_t, &pileup_type, value);
788
+ if (!allow_closed && !value->pileup) rb_raise(rb_eIOError, "closed pileup");
789
+ return value;
790
+ }
791
+ static int native_pileup_read(void *data, bam1_t *record) {
792
+ ruby_pileup_t *value = data;
793
+ ruby_bam_file_t *file = get_file(value->file, 0);
794
+ if (value->iterator) return sam_itr_next(file->file, value->iterator, record);
795
+ return sam_read1(file->file, get_header(value->header)->pointer, record);
796
+ }
797
+ static VALUE native_pileup_open(VALUE klass, VALUE file_value, VALUE header_value,
798
+ VALUE region_value, VALUE beg_value, VALUE end_value, VALUE max_value) {
799
+ ruby_pileup_t *value;
800
+ ruby_bam_file_t *file = get_file(file_value, 0);
801
+ VALUE object;
802
+ get_header(header_value);
803
+ object = TypedData_Make_Struct(klass, ruby_pileup_t, &pileup_type, value);
804
+ value->pileup = NULL;
805
+ value->iterator = NULL;
806
+ value->file = file_value;
807
+ value->header = header_value;
808
+ RB_OBJ_WRITE(object, &value->file, file_value);
809
+ RB_OBJ_WRITE(object, &value->header, header_value);
810
+ if (!NIL_P(region_value)) {
811
+ if (!file->index) rb_raise(rb_eRuntimeError, "index file is required to use region pileup");
812
+ if (NIL_P(beg_value) && NIL_P(end_value))
813
+ value->iterator = sam_itr_querys(file->index, get_header(header_value)->pointer, StringValueCStr(region_value));
814
+ else
815
+ value->iterator = sam_itr_queryi(file->index,
816
+ sam_hdr_name2tid(get_header(header_value)->pointer, StringValueCStr(region_value)),
817
+ NUM2LL(beg_value), NUM2LL(end_value));
818
+ if (!value->iterator) rb_raise(rb_eRuntimeError, "failed to query pileup region");
819
+ }
820
+ value->pileup = bam_plp_init(native_pileup_read, value);
821
+ if (!value->pileup) rb_raise(rb_eNoMemError, "bam_plp_init failed");
822
+ if (!NIL_P(max_value)) bam_plp_set_maxcnt(value->pileup, NUM2INT(max_value));
823
+ return object;
824
+ }
825
+ static VALUE pileup_rows(const bam_pileup1_t *entries, int count) {
826
+ int index;
827
+ VALUE rows;
828
+ rows = rb_ary_new_capa(count);
829
+ for (index = 0; index < count; index++) {
830
+ const bam_pileup1_t *entry = &entries[index];
831
+ const bam1_t *record = entry->b;
832
+ int qpos = entry->qpos;
833
+ VALUE row = rb_ary_new_capa(11);
834
+ VALUE base = Qnil, quality = Qnil;
835
+ if (!entry->is_del && !entry->is_refskip && qpos >= 0) {
836
+ base = INT2NUM(bam_seqi(bam_get_seq(record), qpos));
837
+ quality = UINT2NUM(bam_get_qual(record)[qpos]);
838
+ }
839
+ rb_ary_push(row, wrap_record(bam_dup1(record)));
840
+ rb_ary_push(row, INT2NUM(qpos));
841
+ rb_ary_push(row, INT2NUM(entry->indel));
842
+ rb_ary_push(row, entry->is_del ? Qtrue : Qfalse);
843
+ rb_ary_push(row, entry->is_head ? Qtrue : Qfalse);
844
+ rb_ary_push(row, entry->is_tail ? Qtrue : Qfalse);
845
+ rb_ary_push(row, entry->is_refskip ? Qtrue : Qfalse);
846
+ rb_ary_push(row, UINT2NUM(record->core.flag));
847
+ rb_ary_push(row, base);
848
+ rb_ary_push(row, quality);
849
+ rb_ary_push(row, UINT2NUM(record->core.qual));
850
+ rb_ary_push(rows, row);
851
+ }
852
+ return rows;
853
+ }
854
+ static VALUE native_pileup_next(VALUE self) {
855
+ ruby_pileup_t *value = get_pileup(self, 0);
856
+ int tid = -1, count = 0;
857
+ hts_pos_t position = -1;
858
+ const bam_pileup1_t *entries = bam_plp64_auto(value->pileup, &tid, &position, &count);
859
+ VALUE result;
860
+ if (!entries) {
861
+ if (count < 0) rb_raise(rb_eRuntimeError, "HTSlib pileup error");
862
+ return Qnil;
863
+ }
864
+ result = rb_ary_new_capa(3);
865
+ rb_ary_push(result, INT2NUM(tid));
866
+ rb_ary_push(result, LL2NUM(position));
867
+ rb_ary_push(result, pileup_rows(entries, count));
868
+ return result;
869
+ }
870
+ static VALUE native_pileup_reset(VALUE self) {
871
+ bam_plp_reset(get_pileup(self, 0)->pileup);
872
+ return Qnil;
873
+ }
874
+ static VALUE native_pileup_close(VALUE self) {
875
+ ruby_pileup_t *value = get_pileup(self, 1);
876
+ if (value->pileup) { bam_plp_destroy(value->pileup); value->pileup = NULL; }
877
+ if (value->iterator) { hts_itr_destroy(value->iterator); value->iterator = NULL; }
878
+ return Qnil;
879
+ }
880
+
881
+ static void mpileup_mark(void *data) {
882
+ ruby_mpileup_t *value = data;
883
+ rb_gc_mark_movable(value->files);
884
+ rb_gc_mark_movable(value->headers);
885
+ }
886
+ static void mpileup_compact(void *data) {
887
+ ruby_mpileup_t *value = data;
888
+ value->files = rb_gc_location(value->files);
889
+ value->headers = rb_gc_location(value->headers);
890
+ }
891
+ static void mpileup_release(ruby_mpileup_t *value) {
892
+ int index;
893
+ if (value->pileup) { bam_mplp_destroy(value->pileup); value->pileup = NULL; }
894
+ if (value->inputs) {
895
+ for (index = 0; index < value->count; index++) {
896
+ if (value->inputs[index].iterator) hts_itr_destroy(value->inputs[index].iterator);
897
+ }
898
+ xfree(value->inputs);
899
+ value->inputs = NULL;
900
+ }
901
+ xfree(value->input_data); value->input_data = NULL;
902
+ xfree(value->depths); value->depths = NULL;
903
+ xfree(value->entries); value->entries = NULL;
904
+ }
905
+ static void mpileup_free(void *data) {
906
+ ruby_mpileup_t *value = data;
907
+ mpileup_release(value);
908
+ xfree(value);
909
+ }
910
+ static size_t mpileup_size(const void *data) {
911
+ const ruby_mpileup_t *value = data;
912
+ return value ? sizeof(*value) + value->count * (sizeof(ruby_mplp_input_t) + sizeof(void *) + sizeof(int) + sizeof(bam_pileup1_t *)) : 0;
913
+ }
914
+ static const rb_data_type_t mpileup_type = {
915
+ .wrap_struct_name = "HTS::Native::MpileupHandle",
916
+ .function = { .dmark = mpileup_mark, .dfree = mpileup_free, .dsize = mpileup_size, .dcompact = mpileup_compact },
917
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
918
+ };
919
+ static ruby_mpileup_t *get_mpileup(VALUE self, int allow_closed) {
920
+ ruby_mpileup_t *value;
921
+ TypedData_Get_Struct(self, ruby_mpileup_t, &mpileup_type, value);
922
+ if (!allow_closed && !value->pileup) rb_raise(rb_eIOError, "closed mpileup");
923
+ return value;
924
+ }
925
+ static int native_mpileup_read(void *data, bam1_t *record) {
926
+ ruby_mplp_input_t *input = data;
927
+ if (!input->file->file) return -2;
928
+ if (input->iterator) return sam_itr_next(input->file->file, input->iterator, record);
929
+ return sam_read1(input->file->file, input->header, record);
930
+ }
931
+ static VALUE native_mpileup_open(VALUE klass, VALUE files, VALUE headers, VALUE region,
932
+ VALUE beg, VALUE end, VALUE maxcnt, VALUE overlaps) {
933
+ ruby_mpileup_t *value;
934
+ VALUE object;
935
+ int index, count;
936
+ Check_Type(files, T_ARRAY);
937
+ Check_Type(headers, T_ARRAY);
938
+ count = RARRAY_LEN(files);
939
+ if (count <= 0 || RARRAY_LEN(headers) != count) rb_raise(rb_eArgError, "invalid mpileup inputs");
940
+ object = TypedData_Make_Struct(klass, ruby_mpileup_t, &mpileup_type, value);
941
+ memset(value, 0, sizeof(*value));
942
+ value->count = count;
943
+ value->files = files;
944
+ value->headers = headers;
945
+ RB_OBJ_WRITE(object, &value->files, files);
946
+ RB_OBJ_WRITE(object, &value->headers, headers);
947
+ value->inputs = ALLOC_N(ruby_mplp_input_t, count);
948
+ value->input_data = ALLOC_N(void *, count);
949
+ value->depths = ALLOC_N(int, count);
950
+ value->entries = ALLOC_N(const bam_pileup1_t *, count);
951
+ memset(value->inputs, 0, sizeof(ruby_mplp_input_t) * count);
952
+ for (index = 0; index < count; index++) {
953
+ ruby_bam_file_t *file = get_file(rb_ary_entry(files, index), 0);
954
+ sam_hdr_t *header = get_header(rb_ary_entry(headers, index))->pointer;
955
+ value->inputs[index].file = file;
956
+ value->inputs[index].header = header;
957
+ if (!NIL_P(region)) {
958
+ if (!file->index) { mpileup_release(value); rb_raise(rb_eRuntimeError, "index file is required to use region mpileup"); }
959
+ if (NIL_P(beg) && NIL_P(end))
960
+ value->inputs[index].iterator = sam_itr_querys(file->index, header, StringValueCStr(region));
961
+ else
962
+ value->inputs[index].iterator = sam_itr_queryi(file->index, sam_hdr_name2tid(header, StringValueCStr(region)), NUM2LL(beg), NUM2LL(end));
963
+ if (!value->inputs[index].iterator) { mpileup_release(value); rb_raise(rb_eRuntimeError, "failed to query mpileup region"); }
964
+ }
965
+ value->input_data[index] = &value->inputs[index];
966
+ }
967
+ value->pileup = bam_mplp_init(count, native_mpileup_read, value->input_data);
968
+ if (!value->pileup) { mpileup_release(value); rb_raise(rb_eNoMemError, "bam_mplp_init failed"); }
969
+ if (!NIL_P(maxcnt)) bam_mplp_set_maxcnt(value->pileup, NUM2INT(maxcnt));
970
+ if (RTEST(overlaps) && bam_mplp_init_overlaps(value->pileup) < 0) {
971
+ mpileup_release(value);
972
+ rb_raise(rb_eRuntimeError, "bam_mplp_init_overlaps failed");
973
+ }
974
+ return object;
975
+ }
976
+ static VALUE native_mpileup_next(VALUE self) {
977
+ ruby_mpileup_t *value = get_mpileup(self, 0);
978
+ int tid = -1, result, index;
979
+ hts_pos_t position = -1;
980
+ VALUE rows, depths, output;
981
+ result = bam_mplp64_auto(value->pileup, &tid, &position, value->depths, value->entries);
982
+ if (result == 0) return Qnil;
983
+ if (result < 0) rb_raise(rb_eRuntimeError, "HTSlib mpileup error");
984
+ rows = rb_ary_new_capa(value->count);
985
+ depths = rb_ary_new_capa(value->count);
986
+ for (index = 0; index < value->count; index++) {
987
+ rb_ary_push(depths, INT2NUM(value->depths[index]));
988
+ rb_ary_push(rows, pileup_rows(value->entries[index], value->depths[index]));
989
+ }
990
+ output = rb_ary_new_capa(4);
991
+ rb_ary_push(output, INT2NUM(tid));
992
+ rb_ary_push(output, LL2NUM(position));
993
+ rb_ary_push(output, depths);
994
+ rb_ary_push(output, rows);
995
+ return output;
996
+ }
997
+ static VALUE native_mpileup_reset(VALUE self) { bam_mplp_reset(get_mpileup(self, 0)->pileup); return Qnil; }
998
+ static VALUE native_mpileup_close(VALUE self) { mpileup_release(get_mpileup(self, 1)); return Qnil; }
999
+
1000
+ void Init_htslib_native_bam(VALUE native) {
1001
+ cNativeSamHeader = rb_define_class_under(native, "SamHeaderHandle", rb_cObject);
1002
+ rb_undef_alloc_func(cNativeSamHeader);
1003
+ rb_define_singleton_method(cNativeSamHeader, "create", native_header_create, 0);
1004
+ rb_define_singleton_method(cNativeSamHeader, "parse", native_header_parse, 1);
1005
+ rb_define_method(cNativeSamHeader, "duplicate", native_header_duplicate, 0);
1006
+ rb_define_method(cNativeSamHeader, "target_count", native_header_target_count, 0);
1007
+ rb_define_method(cNativeSamHeader, "target_name", native_header_target_name, 1);
1008
+ rb_define_method(cNativeSamHeader, "target_length", native_header_target_length, 1);
1009
+ rb_define_method(cNativeSamHeader, "name2tid", native_header_name2tid, 1);
1010
+ rb_define_method(cNativeSamHeader, "to_s", native_header_to_s, 0);
1011
+ rb_define_method(cNativeSamHeader, "add_lines", native_header_add_lines, 1);
1012
+ rb_define_method(cNativeSamHeader, "find_line", native_header_find_line, 3);
1013
+ rb_define_method(cNativeSamHeader, "find_tag", native_header_find_tag, 4);
1014
+ rb_define_method(cNativeSamHeader, "find_line_at", native_header_find_line_at, 2);
1015
+ rb_define_method(cNativeSamHeader, "remove_line", native_header_remove_line, 3);
1016
+ rb_define_method(cNativeSamHeader, "remove_line_at", native_header_remove_line_at, 2);
1017
+ rb_define_method(cNativeSamHeader, "remove_tag", native_header_remove_tag, 4);
1018
+ rb_define_method(cNativeSamHeader, "count_lines", native_header_count_lines, 1);
1019
+ rb_define_method(cNativeSamHeader, "line_index", native_header_line_index, 2);
1020
+ rb_define_method(cNativeSamHeader, "line_name", native_header_line_name, 2);
1021
+
1022
+ cNativeBamRecord = rb_define_class_under(native, "BamRecordHandle", rb_cObject);
1023
+ rb_undef_alloc_func(cNativeBamRecord);
1024
+ rb_define_singleton_method(cNativeBamRecord, "create", native_record_create, 0);
1025
+ rb_define_method(cNativeBamRecord, "duplicate", native_record_duplicate, 0);
1026
+ rb_define_method(cNativeBamRecord, "replace", native_record_replace, 11);
1027
+ rb_define_method(cNativeBamRecord, "qname", native_record_qname, 0);
1028
+ rb_define_method(cNativeBamRecord, "qname=", native_record_set_qname, 1);
1029
+ rb_define_method(cNativeBamRecord, "core_get", native_record_core_get, 1);
1030
+ rb_define_method(cNativeBamRecord, "core_set", native_record_core_set, 2);
1031
+ rb_define_method(cNativeBamRecord, "endpos", native_record_endpos, 0);
1032
+ rb_define_method(cNativeBamRecord, "cigar_values", native_record_cigar_values, 0);
1033
+ rb_define_method(cNativeBamRecord, "cigar=", native_record_set_cigar, 1);
1034
+ rb_define_method(cNativeBamRecord, "qlen", native_record_qlen, 0);
1035
+ rb_define_method(cNativeBamRecord, "rlen", native_record_rlen, 0);
1036
+ rb_define_method(cNativeBamRecord, "sequence", native_record_sequence, 0);
1037
+ rb_define_method(cNativeBamRecord, "sequence_codes", native_record_sequence_codes, 0);
1038
+ rb_define_method(cNativeBamRecord, "qualities", native_record_qualities, 0);
1039
+ rb_define_method(cNativeBamRecord, "quality_string", native_record_quality_string, 0);
1040
+ rb_define_method(cNativeBamRecord, "base_code", native_record_base_code, 1);
1041
+ rb_define_method(cNativeBamRecord, "quality_at", native_record_quality_at, 1);
1042
+ rb_define_method(cNativeBamRecord, "format", native_record_to_s, 1);
1043
+ rb_define_method(cNativeBamRecord, "aux_get", native_record_aux_get, 2);
1044
+ rb_define_method(cNativeBamRecord, "aux_entries", native_record_aux_entries, 0);
1045
+ rb_define_method(cNativeBamRecord, "aux_update_int", native_record_aux_update_int, 2);
1046
+ rb_define_method(cNativeBamRecord, "aux_update_float", native_record_aux_update_float, 2);
1047
+ rb_define_method(cNativeBamRecord, "aux_update_string", native_record_aux_update_string, 2);
1048
+ rb_define_method(cNativeBamRecord, "aux_append", native_record_aux_append, 3);
1049
+ rb_define_method(cNativeBamRecord, "aux_update_array", native_record_aux_update_array, 3);
1050
+ rb_define_method(cNativeBamRecord, "aux_delete", native_record_aux_delete, 1);
1051
+ rb_define_method(cNativeBamRecord, "aux_key?", native_record_aux_key, 1);
1052
+
1053
+ cNativeBamFile = rb_define_class_under(native, "BamFileHandle", rb_cObject);
1054
+ rb_undef_alloc_func(cNativeBamFile);
1055
+ rb_define_singleton_method(cNativeBamFile, "open", native_bam_open, 2);
1056
+ rb_define_singleton_method(cNativeBamFile, "build_index", native_bam_build_index, 4);
1057
+ rb_define_method(cNativeBamFile, "close", native_bam_close, 0);
1058
+ rb_define_method(cNativeBamFile, "closed?", native_bam_closed, 0);
1059
+ rb_define_method(cNativeBamFile, "read_header", native_bam_read_header, 0);
1060
+ rb_define_method(cNativeBamFile, "write_header", native_bam_write_header, 1);
1061
+ rb_define_method(cNativeBamFile, "read", native_bam_read, 2);
1062
+ rb_define_method(cNativeBamFile, "write", native_bam_write, 2);
1063
+ rb_define_method(cNativeBamFile, "set_fai", native_bam_set_fai, 1);
1064
+ rb_define_method(cNativeBamFile, "set_threads", native_bam_set_threads, 1);
1065
+ rb_define_method(cNativeBamFile, "file_format", native_bam_format, 0);
1066
+ rb_define_method(cNativeBamFile, "file_format_version", native_bam_format_version, 0);
1067
+ rb_define_method(cNativeBamFile, "seek", native_bam_seek, 1);
1068
+ rb_define_method(cNativeBamFile, "tell", native_bam_tell, 0);
1069
+ rb_define_method(cNativeBamFile, "load_index", native_bam_load_index, 1);
1070
+ rb_define_method(cNativeBamFile, "index_loaded?", native_bam_index_loaded, 0);
1071
+ rb_define_method(cNativeBamFile, "query_interval", native_bam_query_interval, 3);
1072
+ rb_define_method(cNativeBamFile, "query_region", native_bam_query_region, 2);
1073
+
1074
+ cNativeBamIterator = rb_define_class_under(native, "BamIteratorHandle", rb_cObject);
1075
+ rb_undef_alloc_func(cNativeBamIterator);
1076
+ rb_define_method(cNativeBamIterator, "next", native_bam_iterator_next, 1);
1077
+ rb_define_method(cNativeBamIterator, "close", native_bam_iterator_close, 0);
1078
+
1079
+ rb_define_singleton_method(native, "cigar_parse", native_cigar_parse, 1);
1080
+ rb_define_singleton_method(native, "cigar_qlen", native_cigar_qlen, 1);
1081
+ rb_define_singleton_method(native, "cigar_rlen", native_cigar_rlen, 1);
1082
+ rb_define_singleton_method(native, "bam_flag_string", native_bam_flag_string, 1);
1083
+
1084
+ cNativeBaseMod = rb_define_class_under(native, "BaseModHandle", rb_cObject);
1085
+ rb_undef_alloc_func(cNativeBaseMod);
1086
+ rb_define_singleton_method(cNativeBaseMod, "open", native_base_mod_open, 1);
1087
+ rb_define_method(cNativeBaseMod, "close", native_base_mod_close, 0);
1088
+ rb_define_method(cNativeBaseMod, "parse", native_base_mod_parse, 1);
1089
+ rb_define_method(cNativeBaseMod, "at", native_base_mod_at, 2);
1090
+ rb_define_method(cNativeBaseMod, "each_raw", native_base_mod_each_raw, 1);
1091
+ rb_define_method(cNativeBaseMod, "types", native_base_mod_types, 0);
1092
+ rb_define_method(cNativeBaseMod, "query", native_base_mod_query, 1);
1093
+ rb_define_method(cNativeBaseMod, "query_at", native_base_mod_query_at, 1);
1094
+
1095
+ cNativePileup = rb_define_class_under(native, "PileupHandle", rb_cObject);
1096
+ rb_undef_alloc_func(cNativePileup);
1097
+ rb_define_singleton_method(cNativePileup, "open", native_pileup_open, 6);
1098
+ rb_define_method(cNativePileup, "next", native_pileup_next, 0);
1099
+ rb_define_method(cNativePileup, "reset", native_pileup_reset, 0);
1100
+ rb_define_method(cNativePileup, "close", native_pileup_close, 0);
1101
+
1102
+ cNativeMpileup = rb_define_class_under(native, "MpileupHandle", rb_cObject);
1103
+ rb_undef_alloc_func(cNativeMpileup);
1104
+ rb_define_singleton_method(cNativeMpileup, "open", native_mpileup_open, 7);
1105
+ rb_define_method(cNativeMpileup, "next", native_mpileup_next, 0);
1106
+ rb_define_method(cNativeMpileup, "reset", native_mpileup_reset, 0);
1107
+ rb_define_method(cNativeMpileup, "close", native_mpileup_close, 0);
1108
+ }