htslib 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -3
- data/TUTORIAL.md +2 -2
- data/ext/htslib_native/native_bam.c +92 -33
- data/ext/htslib_native/native_bcf.c +133 -55
- data/ext/htslib_native/native_faidx.c +10 -1
- data/ext/htslib_native/native_tabix.c +10 -1
- data/lib/hts/bam/base_mod.rb +1 -0
- data/lib/hts/bam/flag.rb +5 -3
- data/lib/hts/bam.rb +55 -6
- data/lib/hts/bcf/errors.rb +3 -0
- data/lib/hts/bcf/format.rb +8 -1
- data/lib/hts/bcf/header.rb +2 -0
- data/lib/hts/bcf/info.rb +3 -2
- data/lib/hts/bcf/record.rb +22 -9
- data/lib/hts/bcf.rb +38 -7
- data/lib/hts/faidx.rb +2 -0
- data/lib/hts/tabix.rb +17 -4
- data/lib/hts/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb6716391f6e8aa27cb4859140deb80073a7b02e149f17704a4b08e125567cb9
|
|
4
|
+
data.tar.gz: 20ec9fc316dd378bd869d590360fc8d50a9b2be075871e76a1b4148292614cf6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed55104f781970f32e5918d15fbe2d0cf5c08e01524c29cf95656b6da902a1528f7630152ae8eb9adee815442230e74868707d348bc7947a871b9576d44bcf0d
|
|
7
|
+
data.tar.gz: c21d82a89b123e25e135486844d09ba83734fb1149269a158b1f2b81be94708ba3e55f95ee94d3a54e78b40a40db7ebcc71fdce969ecc16950802f84bee79461
|
data/README.md
CHANGED
|
@@ -122,12 +122,12 @@ bcf = HTS::Bcf.open("test/fixtures/test.bcf")
|
|
|
122
122
|
|
|
123
123
|
bcf.each do |r|
|
|
124
124
|
p chrom: r.chrom,
|
|
125
|
-
pos: r.pos,
|
|
125
|
+
pos: r.pos + 1, # VCF POS is 1-based; Record#pos is 0-based.
|
|
126
126
|
id: r.id,
|
|
127
127
|
qual: r.qual.round(2),
|
|
128
128
|
ref: r.ref,
|
|
129
129
|
alt: r.alt,
|
|
130
|
-
|
|
130
|
+
filters: r.filter,
|
|
131
131
|
info: r.info.to_h,
|
|
132
132
|
format: r.format.to_h
|
|
133
133
|
end
|
|
@@ -157,6 +157,7 @@ fa.close
|
|
|
157
157
|
|
|
158
158
|
```ruby
|
|
159
159
|
tb = HTS::Tabix.open("test/fixtures/test.vcf.gz")
|
|
160
|
+
# Numeric coordinates are 0-based, half-open: [2000, 3000).
|
|
160
161
|
tb.query("poo", 2000, 3000) do |line|
|
|
161
162
|
puts line.join("\t")
|
|
162
163
|
end
|
|
@@ -191,7 +192,8 @@ silently returning overwritten data. `genotype_strings`, `get`, and `[]` remain
|
|
|
191
192
|
the allocating convenience APIs. Use `unpack: :site_only` when FORMAT columns
|
|
192
193
|
are not needed.
|
|
193
194
|
|
|
194
|
-
Tabix likewise
|
|
195
|
+
Tabix likewise lets callers avoid splitting unused columns. All yielded Ruby
|
|
196
|
+
strings and arrays below are owning values:
|
|
195
197
|
|
|
196
198
|
```ruby
|
|
197
199
|
tb.each_line("chr1:1-1000") { |line| }
|
data/TUTORIAL.md
CHANGED
|
@@ -331,12 +331,12 @@ bcf = HTS::Bcf.open("b.bcf")
|
|
|
331
331
|
|
|
332
332
|
bcf.each do |r|
|
|
333
333
|
p chrom: r.chrom,
|
|
334
|
-
pos: r.pos,
|
|
334
|
+
pos: r.pos + 1, # VCF POS is 1-based; Record#pos is 0-based.
|
|
335
335
|
id: r.id,
|
|
336
336
|
qual: r.qual.round(2),
|
|
337
337
|
ref: r.ref,
|
|
338
338
|
alt: r.alt,
|
|
339
|
-
|
|
339
|
+
filters: r.filter,
|
|
340
340
|
info: r.info.to_h,
|
|
341
341
|
format: r.format.to_h
|
|
342
342
|
end
|
|
@@ -242,21 +242,25 @@ static VALUE native_record_replace(VALUE self, VALUE qname, VALUE flag, VALUE ti
|
|
|
242
242
|
bam1_t *record = get_record(self)->pointer;
|
|
243
243
|
long cigar_count, i;
|
|
244
244
|
uint32_t *cigar = NULL;
|
|
245
|
+
VALUE cigar_storage = 0;
|
|
245
246
|
const char *quality_data = NULL;
|
|
246
247
|
int result;
|
|
247
248
|
|
|
248
249
|
StringValue(qname);
|
|
249
250
|
StringValue(sequence);
|
|
251
|
+
if (memchr(RSTRING_PTR(qname), '\0', RSTRING_LEN(qname))) {
|
|
252
|
+
rb_raise(rb_eArgError, "qname must not contain NUL bytes");
|
|
253
|
+
}
|
|
250
254
|
Check_Type(cigar_value, T_ARRAY);
|
|
251
255
|
cigar_count = RARRAY_LEN(cigar_value);
|
|
252
256
|
if (cigar_count > 0) {
|
|
253
|
-
cigar =
|
|
257
|
+
cigar = ALLOCV_N(uint32_t, cigar_storage, cigar_count);
|
|
254
258
|
for (i = 0; i < cigar_count; i++) cigar[i] = NUM2UINT(rb_ary_entry(cigar_value, i));
|
|
255
259
|
}
|
|
256
260
|
if (!NIL_P(qualities)) {
|
|
257
261
|
StringValue(qualities);
|
|
258
262
|
if (RSTRING_LEN(qualities) != RSTRING_LEN(sequence)) {
|
|
259
|
-
|
|
263
|
+
ALLOCV_END(cigar_storage);
|
|
260
264
|
rb_raise(rb_eArgError, "qualities length must match sequence length");
|
|
261
265
|
}
|
|
262
266
|
quality_data = RSTRING_PTR(qualities);
|
|
@@ -264,12 +268,12 @@ static VALUE native_record_replace(VALUE self, VALUE qname, VALUE flag, VALUE ti
|
|
|
264
268
|
|
|
265
269
|
errno = 0;
|
|
266
270
|
result = bam_set1(record,
|
|
267
|
-
(size_t)RSTRING_LEN(qname)
|
|
271
|
+
(size_t)RSTRING_LEN(qname), RSTRING_PTR(qname),
|
|
268
272
|
NUM2UINT(flag), NUM2INT(tid), NUM2LL(pos), NUM2UINT(mapq),
|
|
269
273
|
(size_t)cigar_count, cigar,
|
|
270
274
|
NUM2INT(mtid), NUM2LL(mpos), NUM2LL(isize),
|
|
271
275
|
(size_t)RSTRING_LEN(sequence), RSTRING_PTR(sequence), quality_data, 0);
|
|
272
|
-
|
|
276
|
+
ALLOCV_END(cigar_storage);
|
|
273
277
|
if (result < 0) {
|
|
274
278
|
if (errno) rb_sys_fail("bam_set1");
|
|
275
279
|
rb_raise(rb_eRuntimeError, "bam_set1 failed: %d", result);
|
|
@@ -282,6 +286,9 @@ static VALUE native_record_set_qname(VALUE self, VALUE name) {
|
|
|
282
286
|
if (result < 0) rb_raise(rb_eRuntimeError, "bam_set_qname failed: %d", result);
|
|
283
287
|
return name;
|
|
284
288
|
}
|
|
289
|
+
static void native_record_update_bin(bam1_t *record) {
|
|
290
|
+
record->core.bin = hts_reg2bin(record->core.pos, bam_endpos(record), 14, 5);
|
|
291
|
+
}
|
|
285
292
|
static VALUE native_record_core_get(VALUE self, VALUE field) {
|
|
286
293
|
bam1_core_t *core = &get_record(self)->pointer->core;
|
|
287
294
|
ID id = SYM2ID(field);
|
|
@@ -297,7 +304,8 @@ static VALUE native_record_core_get(VALUE self, VALUE field) {
|
|
|
297
304
|
rb_raise(rb_eArgError, "unknown BAM core field");
|
|
298
305
|
}
|
|
299
306
|
static VALUE native_record_core_set(VALUE self, VALUE field, VALUE new_value) {
|
|
300
|
-
|
|
307
|
+
bam1_t *record = get_record(self)->pointer;
|
|
308
|
+
bam1_core_t *core = &record->core;
|
|
301
309
|
ID id = SYM2ID(field);
|
|
302
310
|
if (id == rb_intern("tid")) core->tid = NUM2INT(new_value);
|
|
303
311
|
else if (id == rb_intern("mtid")) core->mtid = NUM2INT(new_value);
|
|
@@ -308,6 +316,7 @@ static VALUE native_record_core_set(VALUE self, VALUE field, VALUE new_value) {
|
|
|
308
316
|
else if (id == rb_intern("mapq")) core->qual = NUM2UINT(new_value);
|
|
309
317
|
else if (id == rb_intern("flag")) core->flag = NUM2UINT(new_value);
|
|
310
318
|
else rb_raise(rb_eArgError, "unknown or read-only BAM core field");
|
|
319
|
+
if (id == rb_intern("pos") || id == rb_intern("flag")) native_record_update_bin(record);
|
|
311
320
|
return new_value;
|
|
312
321
|
}
|
|
313
322
|
static VALUE native_record_endpos(VALUE self) { return LL2NUM(bam_endpos(get_record(self)->pointer)); }
|
|
@@ -320,8 +329,10 @@ static VALUE native_record_cigar_values(VALUE self) {
|
|
|
320
329
|
return result;
|
|
321
330
|
}
|
|
322
331
|
static VALUE native_record_set_cigar(VALUE self, VALUE text) {
|
|
323
|
-
|
|
332
|
+
bam1_t *record = get_record(self)->pointer;
|
|
333
|
+
int result = bam_parse_cigar(StringValueCStr(text), NULL, record);
|
|
324
334
|
if (result < 0) rb_raise(rb_eRuntimeError, "bam_parse_cigar failed: %d", result);
|
|
335
|
+
native_record_update_bin(record);
|
|
325
336
|
return text;
|
|
326
337
|
}
|
|
327
338
|
static VALUE native_record_qlen(VALUE self) {
|
|
@@ -390,31 +401,47 @@ static VALUE native_record_to_s(VALUE self, VALUE header_value) {
|
|
|
390
401
|
free(string.s);
|
|
391
402
|
return value;
|
|
392
403
|
}
|
|
404
|
+
typedef struct { uint32_t *cigar; long count; } cigar_result_t;
|
|
405
|
+
static VALUE native_cigar_result_to_array(VALUE data) {
|
|
406
|
+
cigar_result_t *result = (cigar_result_t *)(uintptr_t)data;
|
|
407
|
+
VALUE array = rb_ary_new_capa(result->count);
|
|
408
|
+
long i;
|
|
409
|
+
for (i = 0; i < result->count; i++) rb_ary_push(array, UINT2NUM(result->cigar[i]));
|
|
410
|
+
return array;
|
|
411
|
+
}
|
|
412
|
+
static VALUE native_cigar_result_free(VALUE data) {
|
|
413
|
+
cigar_result_t *result = (cigar_result_t *)(uintptr_t)data;
|
|
414
|
+
free(result->cigar);
|
|
415
|
+
result->cigar = NULL;
|
|
416
|
+
return Qnil;
|
|
417
|
+
}
|
|
393
418
|
static VALUE native_cigar_parse(VALUE klass, VALUE text) {
|
|
394
419
|
uint32_t *cigar = NULL;
|
|
395
420
|
size_t capacity = 0;
|
|
396
|
-
long result = sam_parse_cigar(StringValueCStr(text), NULL, &cigar, &capacity)
|
|
397
|
-
|
|
421
|
+
long result = sam_parse_cigar(StringValueCStr(text), NULL, &cigar, &capacity);
|
|
422
|
+
cigar_result_t parsed;
|
|
398
423
|
if (result < 0) { free(cigar); rb_raise(rb_eRuntimeError, "sam_parse_cigar failed: %ld", result); }
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
424
|
+
parsed.cigar = cigar;
|
|
425
|
+
parsed.count = result;
|
|
426
|
+
return rb_ensure(native_cigar_result_to_array, (VALUE)(uintptr_t)&parsed,
|
|
427
|
+
native_cigar_result_free, (VALUE)(uintptr_t)&parsed);
|
|
403
428
|
}
|
|
404
429
|
static VALUE native_cigar_qlen(VALUE klass, VALUE array) {
|
|
405
430
|
long count = RARRAY_LEN(array), i;
|
|
406
|
-
|
|
431
|
+
VALUE storage = 0;
|
|
432
|
+
uint32_t *values = ALLOCV_N(uint32_t, storage, count ? count : 1);
|
|
407
433
|
for (i = 0; i < count; i++) values[i] = NUM2UINT(rb_ary_entry(array, i));
|
|
408
434
|
hts_pos_t result = bam_cigar2qlen(count, values);
|
|
409
|
-
|
|
435
|
+
ALLOCV_END(storage);
|
|
410
436
|
return LL2NUM(result);
|
|
411
437
|
}
|
|
412
438
|
static VALUE native_cigar_rlen(VALUE klass, VALUE array) {
|
|
413
439
|
long count = RARRAY_LEN(array), i;
|
|
414
|
-
|
|
440
|
+
VALUE storage = 0;
|
|
441
|
+
uint32_t *values = ALLOCV_N(uint32_t, storage, count ? count : 1);
|
|
415
442
|
for (i = 0; i < count; i++) values[i] = NUM2UINT(rb_ary_entry(array, i));
|
|
416
443
|
hts_pos_t result = bam_cigar2rlen(count, values);
|
|
417
|
-
|
|
444
|
+
ALLOCV_END(storage);
|
|
418
445
|
return LL2NUM(result);
|
|
419
446
|
}
|
|
420
447
|
|
|
@@ -484,9 +511,10 @@ static VALUE native_record_aux_update_array(VALUE self, VALUE key, VALUE type_va
|
|
|
484
511
|
long count = RARRAY_LEN(array), i;
|
|
485
512
|
size_t width;
|
|
486
513
|
uint8_t *buffer;
|
|
514
|
+
VALUE buffer_storage = 0;
|
|
487
515
|
int result;
|
|
488
516
|
switch (type) { case 'c': case 'C': width = 1; break; case 's': case 'S': width = 2; break; default: width = 4; }
|
|
489
|
-
buffer =
|
|
517
|
+
buffer = ALLOCV_N(uint8_t, buffer_storage, count * width + 1);
|
|
490
518
|
for (i = 0; i < count; i++) {
|
|
491
519
|
VALUE item = rb_ary_entry(array, i);
|
|
492
520
|
if (type == 'c') { int8_t v = NUM2INT(item); memcpy(buffer + i, &v, 1); }
|
|
@@ -496,10 +524,10 @@ static VALUE native_record_aux_update_array(VALUE self, VALUE key, VALUE type_va
|
|
|
496
524
|
else if (type == 'i') { int32_t v = NUM2INT(item); memcpy(buffer + i * 4, &v, 4); }
|
|
497
525
|
else if (type == 'I') { uint32_t v = NUM2UINT(item); memcpy(buffer + i * 4, &v, 4); }
|
|
498
526
|
else if (type == 'f') { float v = (float)NUM2DBL(item); memcpy(buffer + i * 4, &v, 4); }
|
|
499
|
-
else {
|
|
527
|
+
else { ALLOCV_END(buffer_storage); rb_raise(rb_eArgError, "invalid AUX array type"); }
|
|
500
528
|
}
|
|
501
529
|
result = bam_aux_update_array(get_record(self)->pointer, StringValueCStr(key), type, count, buffer);
|
|
502
|
-
|
|
530
|
+
ALLOCV_END(buffer_storage);
|
|
503
531
|
return INT2NUM(result);
|
|
504
532
|
}
|
|
505
533
|
static VALUE native_record_aux_delete(VALUE self, VALUE key) {
|
|
@@ -514,6 +542,13 @@ static VALUE native_record_aux_key(VALUE self, VALUE key) {
|
|
|
514
542
|
}
|
|
515
543
|
|
|
516
544
|
/* File and iterator */
|
|
545
|
+
static void raise_bam_open_error(VALUE path_value, int error) {
|
|
546
|
+
if (error) rb_syserr_fail_str(error, path_value);
|
|
547
|
+
VALUE hts=rb_const_get(rb_cObject,rb_intern("HTS"));
|
|
548
|
+
VALUE bam=rb_const_get(hts,rb_intern("Bam"));
|
|
549
|
+
VALUE klass=rb_const_get(bam,rb_intern("OpenError"));
|
|
550
|
+
rb_raise(klass,"Failed to open %"PRIsVALUE": HTSlib could not recognize or open the input",path_value);
|
|
551
|
+
}
|
|
517
552
|
static VALUE native_bam_open(VALUE klass, VALUE path_value, VALUE mode_value) {
|
|
518
553
|
ruby_bam_file_t *value;
|
|
519
554
|
VALUE object = TypedData_Make_Struct(klass, ruby_bam_file_t, &bam_file_type, value);
|
|
@@ -521,16 +556,18 @@ static VALUE native_bam_open(VALUE klass, VALUE path_value, VALUE mode_value) {
|
|
|
521
556
|
value->active_io = 0;
|
|
522
557
|
value->path = rb_str_dup(StringValue(path_value));
|
|
523
558
|
RB_OBJ_WRITE(object, &value->path, value->path);
|
|
559
|
+
errno = 0;
|
|
524
560
|
value->file = hts_open(StringValueCStr(value->path), StringValueCStr(mode_value));
|
|
525
|
-
if (!value->file)
|
|
561
|
+
if (!value->file) raise_bam_open_error(path_value, errno);
|
|
526
562
|
return object;
|
|
527
563
|
}
|
|
528
564
|
static VALUE native_bam_close(VALUE self) {
|
|
529
565
|
ruby_bam_file_t *value = get_file(self, 1);
|
|
566
|
+
int result = 0;
|
|
530
567
|
if (value->active_io) rb_raise(rb_eIOError, "cannot close BAM during active I/O");
|
|
531
568
|
if (value->index) { hts_idx_destroy(value->index); value->index = NULL; }
|
|
532
|
-
if (value->file) { hts_close(value->file); value->file = NULL; }
|
|
533
|
-
return
|
|
569
|
+
if (value->file) { result = hts_close(value->file); value->file = NULL; }
|
|
570
|
+
return INT2NUM(result);
|
|
534
571
|
}
|
|
535
572
|
static VALUE native_bam_closed(VALUE self) { return get_file(self, 1)->file ? Qfalse : Qtrue; }
|
|
536
573
|
typedef struct { htsFile *file; sam_hdr_t *header; bam1_t *record; hts_itr_t *iterator; int result; } bam_io_args_t;
|
|
@@ -541,22 +578,29 @@ static void *bam_read_without_gvl(void *data) { bam_io_args_t *args=data; args->
|
|
|
541
578
|
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
579
|
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
580
|
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; }
|
|
581
|
+
typedef struct { ruby_bam_file_t *file; void *(*operation)(void *); void *arguments; } bam_io_call_t;
|
|
582
|
+
static VALUE bam_io_call_without_gvl(VALUE data) { bam_io_call_t *call=(bam_io_call_t *)(uintptr_t)data;rb_thread_call_without_gvl(call->operation,call->arguments,RUBY_UBF_IO,NULL);return Qnil; }
|
|
583
|
+
static VALUE bam_io_end(VALUE data) { bam_io_call_t *call=(bam_io_call_t *)(uintptr_t)data;call->file->active_io=0;return Qnil; }
|
|
584
|
+
/* Async Ruby exceptions are delivered after the native operation regains the GVL.
|
|
585
|
+
* rb_ensure therefore keeps active_io and the stack-backed arguments valid until
|
|
586
|
+
* the HTSlib call has returned or its RUBY_UBF_IO cancellation has completed. */
|
|
587
|
+
static void bam_io_run(ruby_bam_file_t *file,void *(*operation)(void *),void *arguments) { bam_io_call_t call={file,operation,arguments};bam_io_begin(file);rb_ensure(bam_io_call_without_gvl,(VALUE)(uintptr_t)&call,bam_io_end,(VALUE)(uintptr_t)&call); }
|
|
544
588
|
static VALUE native_bam_read_header(VALUE self) {
|
|
545
589
|
ruby_bam_file_t *file=get_file(self,0); bam_header_read_args_t args={file->file,NULL};
|
|
546
|
-
|
|
590
|
+
bam_io_run(file,bam_read_header_without_gvl,&args);
|
|
547
591
|
return wrap_header(args.result);
|
|
548
592
|
}
|
|
549
593
|
static VALUE native_bam_write_header(VALUE self, VALUE header) {
|
|
550
594
|
ruby_bam_file_t *file=get_file(self,0); bam_io_args_t args={file->file,get_header(header)->pointer,NULL,NULL,0};
|
|
551
|
-
|
|
595
|
+
bam_io_run(file,bam_write_header_without_gvl,&args); return INT2NUM(args.result);
|
|
552
596
|
}
|
|
553
597
|
static VALUE native_bam_read(VALUE self, VALUE header, VALUE record) {
|
|
554
598
|
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
|
-
|
|
599
|
+
bam_io_run(file,bam_read_without_gvl,&args); return INT2NUM(args.result);
|
|
556
600
|
}
|
|
557
601
|
static VALUE native_bam_write(VALUE self, VALUE header, VALUE record) {
|
|
558
602
|
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
|
-
|
|
603
|
+
bam_io_run(file,bam_write_without_gvl,&args); return INT2NUM(args.result);
|
|
560
604
|
}
|
|
561
605
|
static VALUE native_bam_set_fai(VALUE self, VALUE path) {
|
|
562
606
|
return INT2NUM(hts_set_fai_filename(get_file(self, 0)->file, StringValueCStr(path)));
|
|
@@ -622,9 +666,7 @@ static VALUE native_bam_iterator_next(VALUE self, VALUE record) {
|
|
|
622
666
|
ruby_bam_iterator_t *iterator = get_iterator(self);
|
|
623
667
|
ruby_bam_file_t *file = get_file(iterator->file, 0);
|
|
624
668
|
bam_io_args_t args={file->file,NULL,get_record(record)->pointer,iterator->iterator,0};
|
|
625
|
-
|
|
626
|
-
rb_thread_call_without_gvl(bam_iterator_without_gvl,&args,RUBY_UBF_IO,NULL);
|
|
627
|
-
file->active_io=0;
|
|
669
|
+
bam_io_run(file,bam_iterator_without_gvl,&args);
|
|
628
670
|
return INT2NUM(args.result);
|
|
629
671
|
}
|
|
630
672
|
static VALUE native_bam_iterator_close(VALUE self) {
|
|
@@ -697,24 +739,37 @@ static VALUE base_mod_array(const hts_base_mod *mods, int count) {
|
|
|
697
739
|
}
|
|
698
740
|
return result;
|
|
699
741
|
}
|
|
742
|
+
static void raise_base_mod_error(const char *operation) {
|
|
743
|
+
VALUE hts = rb_const_get(rb_cObject, rb_intern("HTS"));
|
|
744
|
+
VALUE bam = rb_const_get(hts, rb_intern("Bam"));
|
|
745
|
+
VALUE base_mod = rb_const_get(bam, rb_intern("BaseMod"));
|
|
746
|
+
VALUE error = rb_const_get(base_mod, rb_intern("Error"));
|
|
747
|
+
rb_raise(error, "%s failed", operation);
|
|
748
|
+
}
|
|
700
749
|
static VALUE native_base_mod_at(VALUE self, VALUE position, VALUE max_value) {
|
|
701
750
|
ruby_base_mod_t *value = get_base_mod(self);
|
|
702
751
|
int max = NUM2INT(max_value), count;
|
|
703
752
|
hts_base_mod *mods;
|
|
753
|
+
VALUE mods_storage = 0;
|
|
704
754
|
if (max <= 0) rb_raise(rb_eArgError, "max_mods must be positive");
|
|
705
|
-
mods =
|
|
755
|
+
mods = ALLOCV_N(hts_base_mod, mods_storage, max);
|
|
706
756
|
count = bam_mods_at_qpos(get_record(value->record)->pointer, NUM2INT(position), value->state, mods, max);
|
|
757
|
+
if (count < 0) {
|
|
758
|
+
ALLOCV_END(mods_storage);
|
|
759
|
+
raise_base_mod_error("bam_mods_at_qpos");
|
|
760
|
+
}
|
|
707
761
|
VALUE result = count > 0 ? base_mod_array(mods, count < max ? count : max) : Qnil;
|
|
708
|
-
|
|
762
|
+
ALLOCV_END(mods_storage);
|
|
709
763
|
return result;
|
|
710
764
|
}
|
|
711
765
|
static VALUE native_base_mod_each_raw(VALUE self, VALUE max_value) {
|
|
712
766
|
ruby_base_mod_t *value = get_base_mod(self);
|
|
713
767
|
int max = NUM2INT(max_value), count, position, index;
|
|
714
768
|
hts_base_mod *mods;
|
|
769
|
+
VALUE mods_storage = 0;
|
|
715
770
|
if (!rb_block_given_p()) rb_raise(rb_eArgError, "block is required");
|
|
716
771
|
if (max <= 0) rb_raise(rb_eArgError, "max_mods must be positive");
|
|
717
|
-
mods =
|
|
772
|
+
mods = ALLOCV_N(hts_base_mod, mods_storage, max);
|
|
718
773
|
while ((count = bam_next_basemod(get_record(value->record)->pointer, value->state, mods, max, &position)) > 0) {
|
|
719
774
|
if (count > max) count = max;
|
|
720
775
|
for (index = 0; index < count; index++) {
|
|
@@ -723,7 +778,11 @@ static VALUE native_base_mod_each_raw(VALUE self, VALUE max_value) {
|
|
|
723
778
|
INT2NUM(mods[index].qual));
|
|
724
779
|
}
|
|
725
780
|
}
|
|
726
|
-
|
|
781
|
+
if (count < 0) {
|
|
782
|
+
ALLOCV_END(mods_storage);
|
|
783
|
+
raise_base_mod_error("bam_next_basemod");
|
|
784
|
+
}
|
|
785
|
+
ALLOCV_END(mods_storage);
|
|
727
786
|
return self;
|
|
728
787
|
}
|
|
729
788
|
static VALUE native_base_mod_types(VALUE self) {
|
|
@@ -128,7 +128,25 @@ static VALUE wrap_bcf_iterator(VALUE file,VALUE header,hts_itr_t *iterator,int t
|
|
|
128
128
|
static VALUE native_bcf_header_create(VALUE klass) { return wrap_bcf_header(bcf_hdr_init("w")); }
|
|
129
129
|
static VALUE native_bcf_header_duplicate(VALUE self) { return wrap_bcf_header(bcf_hdr_dup(get_bcf_header(self)->pointer)); }
|
|
130
130
|
static VALUE native_bcf_header_version(VALUE self) { const char *v=bcf_hdr_get_version(get_bcf_header(self)->pointer); return v?rb_str_new_cstr(v):Qnil; }
|
|
131
|
-
static VALUE native_bcf_header_set_version(VALUE self,VALUE value) {
|
|
131
|
+
static VALUE native_bcf_header_set_version(VALUE self,VALUE value) {
|
|
132
|
+
bcf_hdr_t *header=get_bcf_header(self)->pointer;
|
|
133
|
+
const char *version=StringValueCStr(value);
|
|
134
|
+
#if !defined(HTS_VERSION) || HTS_VERSION < 102200
|
|
135
|
+
/* HTSlib before 1.22 failed to restore the generic-header hash value in
|
|
136
|
+
bcf_hdr_update_hrec(), so calling bcf_hdr_set_version() twice aborted.
|
|
137
|
+
Updating the public header record directly avoids that upstream bug. */
|
|
138
|
+
bcf_hrec_t *record=bcf_hdr_get_hrec(header,BCF_HL_GEN,"fileformat",NULL,NULL);
|
|
139
|
+
if(record) {
|
|
140
|
+
char *copy=strdup(version);
|
|
141
|
+
if(!copy) rb_memerror();
|
|
142
|
+
free(record->value);
|
|
143
|
+
record->value=copy;
|
|
144
|
+
header->dirty=1;
|
|
145
|
+
return INT2NUM(0);
|
|
146
|
+
}
|
|
147
|
+
#endif
|
|
148
|
+
return INT2NUM(bcf_hdr_set_version(header,version));
|
|
149
|
+
}
|
|
132
150
|
static VALUE native_bcf_header_read_file(VALUE self,VALUE path) { return INT2NUM(bcf_hdr_set(get_bcf_header(self)->pointer,StringValueCStr(path))); }
|
|
133
151
|
static VALUE native_bcf_header_nsamples(VALUE self) { return INT2NUM(bcf_hdr_nsamples(get_bcf_header(self)->pointer)); }
|
|
134
152
|
static VALUE native_bcf_header_samples(VALUE self) {
|
|
@@ -189,11 +207,12 @@ static VALUE native_bcf_header_schema(VALUE self,VALUE kind_value,VALUE key_valu
|
|
|
189
207
|
rb_ary_push(result,INT2NUM(bcf_hdr_id2number(h,kind,id))); rb_ary_push(result,INT2NUM(id)); return result;
|
|
190
208
|
}
|
|
191
209
|
static VALUE native_bcf_header_subset(VALUE self,VALUE samples) {
|
|
192
|
-
bcf_hdr_t *h=get_bcf_header(self)->pointer,*subset; int count=RARRAY_LEN(samples),i; char **names=NULL; int *imap=NULL; VALUE map;
|
|
193
|
-
if(count>0) { names=
|
|
194
|
-
subset=bcf_hdr_subset(h,count,names,imap);
|
|
195
|
-
|
|
196
|
-
|
|
210
|
+
bcf_hdr_t *h=get_bcf_header(self)->pointer,*subset; int count=RARRAY_LEN(samples),i; char **names=NULL; int *imap=NULL; VALUE map,names_storage=0,imap_storage=0;
|
|
211
|
+
if(count>0) { names=ALLOCV_N(char*,names_storage,count); imap=ALLOCV_N(int,imap_storage,count); for(i=0;i<count;i++) { VALUE sample=rb_ary_entry(samples,i); names[i]=StringValueCStr(sample); } }
|
|
212
|
+
subset=bcf_hdr_subset(h,count,names,imap); ALLOCV_END(names_storage); if(!subset){ALLOCV_END(imap_storage);return Qnil;}
|
|
213
|
+
VALUE subset_value=wrap_bcf_header(subset);
|
|
214
|
+
map=rb_ary_new_capa(count); for(i=0;i<count;i++)rb_ary_push(map,INT2NUM(imap[i])); ALLOCV_END(imap_storage);
|
|
215
|
+
return rb_ary_new_from_args(2,subset_value,map);
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
/* Header record */
|
|
@@ -204,6 +223,20 @@ static VALUE native_bcf_hrec_find_key(VALUE self,VALUE key) { return INT2NUM(bcf
|
|
|
204
223
|
static VALUE native_bcf_hrec_to_s(VALUE self) { kstring_t s=KS_INITIALIZE; VALUE r; bcf_hrec_format(get_bcf_hrec(self)->pointer,&s); r=rb_str_new(s.s,s.l); free(s.s); return r; }
|
|
205
224
|
|
|
206
225
|
/* Record core */
|
|
226
|
+
static void raise_bcf_record_error(const char *message) {
|
|
227
|
+
VALUE hts=rb_const_get(rb_cObject,rb_intern("HTS"));
|
|
228
|
+
VALUE bcf=rb_const_get(hts,rb_intern("Bcf"));
|
|
229
|
+
VALUE error=rb_const_get(bcf,rb_intern("RecordError"));
|
|
230
|
+
rb_raise(error,"%s",message);
|
|
231
|
+
}
|
|
232
|
+
static void check_bcf_unpack(bcf1_t *record,int fields) {
|
|
233
|
+
if(bcf_unpack(record,fields)<0)raise_bcf_record_error("Failed to unpack BCF record");
|
|
234
|
+
}
|
|
235
|
+
static const char *checked_bcf_id(bcf_hdr_t *header,int id) {
|
|
236
|
+
const char *name=(id>=0 && id<header->n[BCF_DT_ID] && header->id[BCF_DT_ID])?bcf_hdr_int2id(header,BCF_DT_ID,id):NULL;
|
|
237
|
+
if(!name)raise_bcf_record_error("BCF record contains an ID that is absent from the supplied header");
|
|
238
|
+
return name;
|
|
239
|
+
}
|
|
207
240
|
static VALUE native_bcf_record_create(VALUE klass) { return wrap_bcf_record(bcf_init()); }
|
|
208
241
|
static VALUE native_bcf_record_duplicate(VALUE self) { return wrap_bcf_record(bcf_dup(get_bcf_record(self)->pointer)); }
|
|
209
242
|
static VALUE native_bcf_record_core_get(VALUE self,VALUE field) {
|
|
@@ -221,21 +254,21 @@ static VALUE native_bcf_record_core_set(VALUE self,VALUE field,VALUE value) {
|
|
|
221
254
|
else rb_raise(rb_eArgError,"unknown BCF field");
|
|
222
255
|
return value;
|
|
223
256
|
}
|
|
224
|
-
static VALUE native_bcf_record_id(VALUE self) { bcf1_t *r=get_bcf_record(self)->pointer;
|
|
257
|
+
static VALUE native_bcf_record_id(VALUE self) { bcf1_t *r=get_bcf_record(self)->pointer; check_bcf_unpack(r,BCF_UN_STR); if(!r->d.id)raise_bcf_record_error("BCF record ID is missing after unpack"); return rb_str_new_cstr(r->d.id); }
|
|
225
258
|
static VALUE native_bcf_record_set_id(VALUE self,VALUE header,VALUE id) { return INT2NUM(bcf_update_id(get_bcf_header(header)->pointer,get_bcf_record(self)->pointer,StringValueCStr(id))); }
|
|
226
|
-
static VALUE native_bcf_record_alleles(VALUE self) { bcf1_t *r=get_bcf_record(self)->pointer; int i; VALUE a;
|
|
259
|
+
static VALUE native_bcf_record_alleles(VALUE self) { bcf1_t *r=get_bcf_record(self)->pointer; int i; VALUE a; check_bcf_unpack(r,BCF_UN_STR); a=rb_ary_new_capa(r->n_allele); for(i=0;i<r->n_allele;i++){if(!r->d.allele[i])raise_bcf_record_error("BCF allele is missing after unpack");rb_ary_push(a,rb_str_new_cstr(r->d.allele[i]));} return a; }
|
|
227
260
|
static VALUE native_bcf_record_set_alleles(VALUE self,VALUE header,VALUE value) { return INT2NUM(bcf_update_alleles_str(get_bcf_header(header)->pointer,get_bcf_record(self)->pointer,StringValueCStr(value))); }
|
|
228
|
-
static VALUE native_bcf_record_filter_ids(VALUE self) { bcf1_t *r=get_bcf_record(self)->pointer; int i; VALUE a;
|
|
229
|
-
static VALUE native_bcf_record_filter_names(VALUE self,VALUE header) { bcf1_t *r=get_bcf_record(self)->pointer; bcf_hdr_t *h=get_bcf_header(header)->pointer; int i; VALUE a;
|
|
261
|
+
static VALUE native_bcf_record_filter_ids(VALUE self) { bcf1_t *r=get_bcf_record(self)->pointer; int i; VALUE a; check_bcf_unpack(r,BCF_UN_FLT); a=rb_ary_new_capa(r->d.n_flt); for(i=0;i<r->d.n_flt;i++)rb_ary_push(a,INT2NUM(r->d.flt[i])); return a; }
|
|
262
|
+
static VALUE native_bcf_record_filter_names(VALUE self,VALUE header) { bcf1_t *r=get_bcf_record(self)->pointer; bcf_hdr_t *h=get_bcf_header(header)->pointer; int i; VALUE a; check_bcf_unpack(r,BCF_UN_FLT); a=rb_ary_new_capa(r->d.n_flt); for(i=0;i<r->d.n_flt;i++)rb_ary_push(a,rb_str_new_cstr(checked_bcf_id(h,r->d.flt[i]))); return a; }
|
|
230
263
|
static VALUE native_bcf_record_to_s(VALUE self,VALUE header) { kstring_t s=KS_INITIALIZE; VALUE v; if(vcf_format(get_bcf_header(header)->pointer,get_bcf_record(self)->pointer,&s)<0){free(s.s);rb_raise(rb_eRuntimeError,"failed to format BCF record");}v=rb_str_new(s.s,s.l);free(s.s);return v; }
|
|
231
264
|
static VALUE native_bcf_record_set_unpack(VALUE self,VALUE level){get_bcf_record(self)->pointer->max_unpack=NUM2INT(level);return level;}
|
|
232
|
-
static VALUE native_bcf_record_subset(VALUE self,VALUE header,VALUE map){int count=RARRAY_LEN(map),i,*imap=
|
|
265
|
+
static VALUE native_bcf_record_subset(VALUE self,VALUE header,VALUE map){VALUE storage=0;int count=RARRAY_LEN(map),i,*imap=ALLOCV_N(int,storage,count?count:1);for(i=0;i<count;i++)imap[i]=NUM2INT(rb_ary_entry(map,i));int result=bcf_subset(get_bcf_header(header)->pointer,get_bcf_record(self)->pointer,count,imap);ALLOCV_END(storage);return INT2NUM(result);}
|
|
233
266
|
|
|
234
267
|
static VALUE field_rows(bcf_hdr_t *h,bcf1_t *r,int format) {
|
|
235
|
-
int i,count; VALUE rows; if(format){
|
|
268
|
+
int i,count; VALUE rows; if(format){check_bcf_unpack(r,BCF_UN_FMT);count=r->n_fmt;}else{check_bcf_unpack(r,BCF_UN_INFO);count=r->n_info;}
|
|
236
269
|
rows=rb_ary_new_capa(count);
|
|
237
270
|
for(i=0;i<count;i++) { int id=format?r->d.fmt[i].id:r->d.info[i].key, kind=format?BCF_HL_FMT:BCF_HL_INFO; VALUE row=rb_hash_new();
|
|
238
|
-
rb_hash_aset(row,ID2SYM(rb_intern("name")),rb_str_new_cstr(
|
|
271
|
+
rb_hash_aset(row,ID2SYM(rb_intern("name")),rb_str_new_cstr(checked_bcf_id(h,id)));
|
|
239
272
|
rb_hash_aset(row,ID2SYM(rb_intern("n")),INT2NUM(bcf_hdr_id2number(h,kind,id)));
|
|
240
273
|
rb_hash_aset(row,ID2SYM(rb_intern("type")),type_symbol(bcf_hdr_id2type(h,kind,id)));
|
|
241
274
|
rb_hash_aset(row,ID2SYM(rb_intern(format?"id":"key")),INT2NUM(id)); rb_ary_push(rows,row); }
|
|
@@ -244,82 +277,120 @@ static VALUE field_rows(bcf_hdr_t *h,bcf1_t *r,int format) {
|
|
|
244
277
|
static VALUE native_bcf_record_info_fields(VALUE self,VALUE header) { return field_rows(get_bcf_header(header)->pointer,get_bcf_record(self)->pointer,0); }
|
|
245
278
|
static VALUE native_bcf_record_format_fields(VALUE self,VALUE header) { return field_rows(get_bcf_header(header)->pointer,get_bcf_record(self)->pointer,1); }
|
|
246
279
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
280
|
+
typedef struct { void *dst; int count; int type; } bcf_value_result_t;
|
|
281
|
+
static void raise_bcf_field_read_error(const char *kind,const char *key,int code) {
|
|
282
|
+
VALUE hts=rb_const_get(rb_cObject,rb_intern("HTS"));
|
|
283
|
+
VALUE bcf=rb_const_get(hts,rb_intern("Bcf"));
|
|
284
|
+
VALUE error=rb_const_get(bcf,rb_intern(kind));
|
|
285
|
+
const char *reason;
|
|
286
|
+
switch(code){case -1:reason="tag is not defined in the header";break;case -2:reason="stored type does not match the requested type";break;case -4:reason="native allocation failed";break;default:reason="native read failed";break;}
|
|
287
|
+
rb_raise(error,"Failed to read %s (HTSlib error %d: %s)",key,code,reason);
|
|
288
|
+
}
|
|
289
|
+
static VALUE bcf_value_result_free(VALUE data) {
|
|
290
|
+
bcf_value_result_t *result=(bcf_value_result_t *)(uintptr_t)data;
|
|
291
|
+
free(result->dst); result->dst=NULL; return Qnil;
|
|
292
|
+
}
|
|
293
|
+
static VALUE bcf_info_result_to_ruby(VALUE data) {
|
|
294
|
+
bcf_value_result_t *result=(bcf_value_result_t *)(uintptr_t)data; int i; VALUE value;
|
|
295
|
+
if(result->type==BCF_HT_STR)return rb_str_new_cstr((char*)result->dst);
|
|
296
|
+
value=rb_ary_new_capa(result->count); for(i=0;i<result->count;i++) {
|
|
297
|
+
if(result->type==BCF_HT_INT){
|
|
298
|
+
int32_t item=((int32_t*)result->dst)[i];
|
|
256
299
|
if(item==bcf_int32_vector_end)break;
|
|
257
300
|
rb_ary_push(value,item==bcf_int32_missing?Qnil:INT2NUM(item));
|
|
258
301
|
}
|
|
259
|
-
else if(type==BCF_HT_LONG){
|
|
260
|
-
int64_t item=((int64_t*)dst)[i];
|
|
302
|
+
else if(result->type==BCF_HT_LONG){
|
|
303
|
+
int64_t item=((int64_t*)result->dst)[i];
|
|
261
304
|
if(item==bcf_int64_vector_end)break;
|
|
262
305
|
rb_ary_push(value,item==bcf_int64_missing?Qnil:LL2NUM(item));
|
|
263
306
|
}
|
|
264
307
|
else {
|
|
265
|
-
float item=((float*)dst)[i];
|
|
308
|
+
float item=((float*)result->dst)[i];
|
|
266
309
|
if(bcf_float_is_vector_end(item))break;
|
|
267
310
|
rb_ary_push(value,bcf_float_is_missing(item)?Qnil:DBL2NUM(item));
|
|
268
|
-
}
|
|
269
|
-
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return value;
|
|
314
|
+
}
|
|
315
|
+
static VALUE info_get(VALUE self,VALUE header_value,VALUE key_value,VALUE type_value) {
|
|
316
|
+
bcf_hdr_t *h=get_bcf_header(header_value)->pointer; bcf1_t *r=get_bcf_record(self)->pointer; int type=NUM2INT(type_value),cap=0,count; void *dst=NULL; bcf_value_result_t result;
|
|
317
|
+
count=bcf_get_info_values(h,r,StringValueCStr(key_value),&dst,&cap,type);
|
|
318
|
+
if(count<0){const char *key=StringValueCStr(key_value);free(dst);if(count==-3)return Qnil;raise_bcf_field_read_error("InfoReadError",key,count);}
|
|
319
|
+
if(type==BCF_HT_FLAG){free(dst);return count==1?Qtrue:Qnil;}
|
|
320
|
+
result.dst=dst;result.count=count;result.type=type;
|
|
321
|
+
return rb_ensure(bcf_info_result_to_ruby,(VALUE)(uintptr_t)&result,
|
|
322
|
+
bcf_value_result_free,(VALUE)(uintptr_t)&result);
|
|
270
323
|
}
|
|
271
324
|
static VALUE native_bcf_info_get(VALUE self,VALUE header,VALUE key,VALUE type) { return info_get(self,header,key,type); }
|
|
272
325
|
static VALUE native_bcf_info_present(VALUE self,VALUE header,VALUE key,VALUE type) { return NIL_P(info_get(self,header,key,type))?Qfalse:Qtrue; }
|
|
273
|
-
static int fill_i32(VALUE array,int32_t **out) { int n=RARRAY_LEN(array),i; *out=ALLOC_N(int32_t,n?n:1);for(i=0;i<n;i++)(*out)[i]=NUM2INT(rb_ary_entry(array,i));return n; }
|
|
274
|
-
static int fill_float(VALUE array,float **out) { int n=RARRAY_LEN(array),i; *out=ALLOC_N(float,n?n:1);for(i=0;i<n;i++)(*out)[i]=(float)NUM2DBL(rb_ary_entry(array,i));return n; }
|
|
275
326
|
static VALUE native_bcf_info_update(VALUE self,VALUE header,VALUE key,VALUE type_value,VALUE values) {
|
|
276
|
-
bcf_hdr_t*h=get_bcf_header(header)->pointer;bcf1_t*r=get_bcf_record(self)->pointer;int type=NUM2INT(type_value),n,result;void*data=NULL;
|
|
327
|
+
bcf_hdr_t*h=get_bcf_header(header)->pointer;bcf1_t*r=get_bcf_record(self)->pointer;int type=NUM2INT(type_value),n,i,result;void*data=NULL;VALUE storage=0;
|
|
277
328
|
if(type!=BCF_HT_FLAG && (NIL_P(values) || values==Qfalse)){
|
|
278
329
|
return INT2NUM(bcf_update_info(h,r,StringValueCStr(key),NULL,0,type));
|
|
279
330
|
}
|
|
280
|
-
if(type==BCF_HT_INT){int32_t*p;n=
|
|
281
|
-
else if(type==BCF_HT_REAL){float*p;n=
|
|
331
|
+
if(type==BCF_HT_INT){int32_t*p;n=RARRAY_LEN(values);p=ALLOCV_N(int32_t,storage,n?n:1);for(i=0;i<n;i++)p[i]=NUM2INT(rb_ary_entry(values,i));data=p;result=bcf_update_info(h,r,StringValueCStr(key),data,n,type);ALLOCV_END(storage);}
|
|
332
|
+
else if(type==BCF_HT_REAL){float*p;n=RARRAY_LEN(values);p=ALLOCV_N(float,storage,n?n:1);for(i=0;i<n;i++)p[i]=(float)NUM2DBL(rb_ary_entry(values,i));data=p;result=bcf_update_info(h,r,StringValueCStr(key),data,n,type);ALLOCV_END(storage);}
|
|
282
333
|
else if(type==BCF_HT_STR){result=bcf_update_info(h,r,StringValueCStr(key),StringValueCStr(values),1,type);}
|
|
283
334
|
else { n=RTEST(values)?1:0; result=bcf_update_info(h,r,StringValueCStr(key),NULL,n,type); }
|
|
284
335
|
return INT2NUM(result);
|
|
285
336
|
}
|
|
286
337
|
|
|
338
|
+
typedef struct { char **strings; int sample_count; } bcf_string_result_t;
|
|
339
|
+
static VALUE bcf_string_result_free(VALUE data) {
|
|
340
|
+
bcf_string_result_t *result=(bcf_string_result_t *)(uintptr_t)data;
|
|
341
|
+
if(result->strings){if(result->sample_count>0)free(result->strings[0]);free(result->strings);result->strings=NULL;}
|
|
342
|
+
return Qnil;
|
|
343
|
+
}
|
|
344
|
+
static VALUE bcf_string_result_to_ruby(VALUE data) {
|
|
345
|
+
bcf_string_result_t *result=(bcf_string_result_t *)(uintptr_t)data; int i;
|
|
346
|
+
VALUE value=rb_ary_new_capa(result->sample_count);
|
|
347
|
+
for(i=0;i<result->sample_count;i++)rb_ary_push(value,rb_str_new_cstr(result->strings[i]));
|
|
348
|
+
return value;
|
|
349
|
+
}
|
|
350
|
+
typedef struct { void *dst; int count; int type; VALUE raw; } bcf_format_result_t;
|
|
351
|
+
static VALUE bcf_format_result_to_ruby(VALUE data) {
|
|
352
|
+
bcf_format_result_t *result=(bcf_format_result_t *)(uintptr_t)data; int i; VALUE value=rb_ary_new_capa(result->count);
|
|
353
|
+
for(i=0;i<result->count;i++){if(result->type==BCF_HT_INT)rb_ary_push(value,INT2NUM(((int32_t*)result->dst)[i]));else {float f=((float*)result->dst)[i];uint32_t word;memcpy(&word,&f,4);if(RTEST(result->raw))rb_ary_push(value,UINT2NUM(word));else if(bcf_float_is_missing(f)||bcf_float_is_vector_end(f))rb_ary_push(value,Qnil);else rb_ary_push(value,DBL2NUM(f));}}
|
|
354
|
+
return value;
|
|
355
|
+
}
|
|
287
356
|
static VALUE native_bcf_format_get(VALUE self,VALUE header_value,VALUE key_value,VALUE type_value,VALUE raw_value) {
|
|
288
|
-
bcf_hdr_t*h=get_bcf_header(header_value)->pointer;bcf1_t*r=get_bcf_record(self)->pointer;int type=NUM2INT(type_value),cap=0,count
|
|
357
|
+
bcf_hdr_t*h=get_bcf_header(header_value)->pointer;bcf1_t*r=get_bcf_record(self)->pointer;int type=NUM2INT(type_value),cap=0,count;void*dst=NULL;
|
|
289
358
|
if(type==BCF_HT_STR){
|
|
290
359
|
char **strings=NULL;
|
|
291
360
|
int sample_count=bcf_hdr_nsamples(h);
|
|
361
|
+
bcf_string_result_t result;
|
|
292
362
|
count=bcf_get_format_string(h,r,StringValueCStr(key_value),&strings,&cap);
|
|
293
|
-
if(count<0){free(strings);return Qnil;}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return value;
|
|
363
|
+
if(count<0){const char *key=StringValueCStr(key_value);free(strings);if(count==-3)return Qnil;raise_bcf_field_read_error("FormatReadError",key,count);}
|
|
364
|
+
result.strings=strings;result.sample_count=sample_count;
|
|
365
|
+
return rb_ensure(bcf_string_result_to_ruby,(VALUE)(uintptr_t)&result,
|
|
366
|
+
bcf_string_result_free,(VALUE)(uintptr_t)&result);
|
|
298
367
|
}
|
|
299
|
-
count=bcf_get_format_values(h,r,StringValueCStr(key_value),&dst,&cap,type);if(count<0){free(dst);return Qnil;
|
|
300
|
-
|
|
301
|
-
|
|
368
|
+
count=bcf_get_format_values(h,r,StringValueCStr(key_value),&dst,&cap,type);if(count<0){const char *key=StringValueCStr(key_value);free(dst);if(count==-3)return Qnil;raise_bcf_field_read_error("FormatReadError",key,count);}
|
|
369
|
+
bcf_format_result_t result={dst,count,type,raw_value};
|
|
370
|
+
return rb_ensure(bcf_format_result_to_ruby,(VALUE)(uintptr_t)&result,
|
|
371
|
+
bcf_value_result_free,(VALUE)(uintptr_t)&result);
|
|
302
372
|
}
|
|
303
373
|
static VALUE native_bcf_format_update(VALUE self,VALUE header,VALUE key,VALUE type_value,VALUE values) {
|
|
304
|
-
bcf_hdr_t*h=get_bcf_header(header)->pointer;bcf1_t*r=get_bcf_record(self)->pointer;int type=NUM2INT(type_value),n,result;
|
|
305
|
-
if(type==BCF_HT_INT){int32_t*p;n=
|
|
306
|
-
else if(type==BCF_HT_REAL){float*p;n=
|
|
307
|
-
else {int i;char**strings;n=RARRAY_LEN(values);strings=
|
|
374
|
+
bcf_hdr_t*h=get_bcf_header(header)->pointer;bcf1_t*r=get_bcf_record(self)->pointer;int type=NUM2INT(type_value),n,i,result;VALUE storage=0;
|
|
375
|
+
if(type==BCF_HT_INT){int32_t*p;n=RARRAY_LEN(values);p=ALLOCV_N(int32_t,storage,n?n:1);for(i=0;i<n;i++)p[i]=NUM2INT(rb_ary_entry(values,i));result=bcf_update_format_int32(h,r,StringValueCStr(key),p,n);ALLOCV_END(storage);}
|
|
376
|
+
else if(type==BCF_HT_REAL){float*p;n=RARRAY_LEN(values);p=ALLOCV_N(float,storage,n?n:1);for(i=0;i<n;i++)p[i]=(float)NUM2DBL(rb_ary_entry(values,i));result=bcf_update_format_float(h,r,StringValueCStr(key),p,n);ALLOCV_END(storage);}
|
|
377
|
+
else {int i;char**strings;n=RARRAY_LEN(values);strings=ALLOCV_N(char*,storage,n?n:1);for(i=0;i<n;i++){VALUE string=rb_ary_entry(values,i);strings[i]=StringValueCStr(string);}result=bcf_update_format_string(h,r,StringValueCStr(key),(const char**)strings,n);ALLOCV_END(storage);}return INT2NUM(result);
|
|
308
378
|
}
|
|
309
379
|
static VALUE native_bcf_format_update_float_words(VALUE self,VALUE header,VALUE key,VALUE values) {
|
|
310
380
|
bcf_hdr_t *h=get_bcf_header(header)->pointer;
|
|
311
381
|
bcf1_t *r=get_bcf_record(self)->pointer;
|
|
312
382
|
int n=RARRAY_LEN(values),i,result;
|
|
313
|
-
|
|
383
|
+
VALUE storage=0;
|
|
384
|
+
float *floats=ALLOCV_N(float,storage,n?n:1);
|
|
314
385
|
for(i=0;i<n;i++){
|
|
315
386
|
uint32_t word=NUM2UINT(rb_ary_entry(values,i));
|
|
316
387
|
memcpy(&floats[i],&word,sizeof(word));
|
|
317
388
|
}
|
|
318
389
|
result=bcf_update_format_float(h,r,StringValueCStr(key),floats,n);
|
|
319
|
-
|
|
390
|
+
ALLOCV_END(storage);
|
|
320
391
|
return INT2NUM(result);
|
|
321
392
|
}
|
|
322
|
-
static VALUE native_bcf_genotype_update(VALUE self,VALUE header,VALUE values) {int32_t*p;int n=
|
|
393
|
+
static VALUE native_bcf_genotype_update(VALUE self,VALUE header,VALUE values) {VALUE storage=0;int32_t*p;int n=RARRAY_LEN(values),i;p=ALLOCV_N(int32_t,storage,n?n:1);for(i=0;i<n;i++)p[i]=NUM2INT(rb_ary_entry(values,i));int result=bcf_update_genotypes(get_bcf_header(header)->pointer,get_bcf_record(self)->pointer,p,n);ALLOCV_END(storage);return INT2NUM(result);}
|
|
323
394
|
static VALUE native_bcf_format_delete(VALUE self,VALUE header,VALUE key,VALUE type) {return INT2NUM(bcf_update_format(get_bcf_header(header)->pointer,get_bcf_record(self)->pointer,StringValueCStr(key),NULL,0,NUM2INT(type)));}
|
|
324
395
|
|
|
325
396
|
/* File */
|
|
@@ -331,13 +402,20 @@ static void *bcf_write_header_without_gvl(void*data){bcf_io_args_t*a=data;a->res
|
|
|
331
402
|
static void *bcf_write_without_gvl(void*data){bcf_io_args_t*a=data;a->result=bcf_write(a->file,a->header,a->record);return NULL;}
|
|
332
403
|
static void *bcf_iterator_without_gvl(void*data){bcf_io_args_t*a=data;if(a->tabix){int n=tbx_itr_next(a->file,a->tabix,a->iterator,a->line);a->result=n<0?n:vcf_parse(a->line,a->header,a->record);}else a->result=bcf_itr_next(a->file,a->iterator,a->record);return NULL;}
|
|
333
404
|
static void bcf_io_begin(ruby_bcf_file_t*v){if(v->active_io)rb_raise(rb_eIOError,"concurrent BCF I/O is not supported");v->active_io=1;}
|
|
334
|
-
|
|
335
|
-
static VALUE
|
|
405
|
+
typedef struct {ruby_bcf_file_t*file;void*(*operation)(void*);void*arguments;} bcf_io_call_t;
|
|
406
|
+
static VALUE bcf_io_call_without_gvl(VALUE data){bcf_io_call_t*call=(bcf_io_call_t*)(uintptr_t)data;rb_thread_call_without_gvl(call->operation,call->arguments,RUBY_UBF_IO,NULL);return Qnil;}
|
|
407
|
+
static VALUE bcf_io_end(VALUE data){bcf_io_call_t*call=(bcf_io_call_t*)(uintptr_t)data;call->file->active_io=0;return Qnil;}
|
|
408
|
+
/* Async exceptions run only after the no-GVL operation (and any UBF cancellation)
|
|
409
|
+
* returns; rb_ensure resets the guard while the call arguments are still alive. */
|
|
410
|
+
static void bcf_io_run(ruby_bcf_file_t*file,void*(*operation)(void*),void*arguments){bcf_io_call_t call={file,operation,arguments};bcf_io_begin(file);rb_ensure(bcf_io_call_without_gvl,(VALUE)(uintptr_t)&call,bcf_io_end,(VALUE)(uintptr_t)&call);}
|
|
411
|
+
static void raise_bcf_open_error(VALUE path,int error){if(error)rb_syserr_fail_str(error,path);VALUE hts=rb_const_get(rb_cObject,rb_intern("HTS"));VALUE bcf=rb_const_get(hts,rb_intern("Bcf"));VALUE klass=rb_const_get(bcf,rb_intern("OpenError"));rb_raise(klass,"Failed to open %"PRIsVALUE": HTSlib could not recognize or open the input",path);}
|
|
412
|
+
static VALUE native_bcf_file_open(VALUE klass,VALUE path,VALUE mode) {ruby_bcf_file_t*v;VALUE o=TypedData_Make_Struct(klass,ruby_bcf_file_t,&bcf_file_type,v);v->index=NULL;v->tabix=NULL;v->active_io=0;v->path=rb_str_dup(StringValue(path));RB_OBJ_WRITE(o,&v->path,v->path);errno=0;v->file=hts_open(StringValueCStr(v->path),StringValueCStr(mode));if(!v->file)raise_bcf_open_error(path,errno);return o;}
|
|
413
|
+
static VALUE native_bcf_file_close(VALUE self){ruby_bcf_file_t*v=get_bcf_file(self,1);int result=0;if(v->active_io)rb_raise(rb_eIOError,"cannot close BCF during active I/O");if(v->index){hts_idx_destroy(v->index);v->index=NULL;}if(v->tabix){tbx_destroy(v->tabix);v->tabix=NULL;}if(v->file){result=hts_close(v->file);v->file=NULL;}return INT2NUM(result);}
|
|
336
414
|
static VALUE native_bcf_file_closed(VALUE self){return get_bcf_file(self,1)->file?Qfalse:Qtrue;}
|
|
337
|
-
static VALUE native_bcf_file_read_header(VALUE self){ruby_bcf_file_t*v=get_bcf_file(self,0);bcf_header_read_args_t a={v->file,NULL};
|
|
338
|
-
static VALUE native_bcf_file_read(VALUE self,VALUE header,VALUE record){ruby_bcf_file_t*v=get_bcf_file(self,0);bcf_io_args_t a={v->file,get_bcf_header(header)->pointer,get_bcf_record(record)->pointer,NULL,NULL,NULL,0};
|
|
339
|
-
static VALUE native_bcf_file_write_header(VALUE self,VALUE header){ruby_bcf_file_t*v=get_bcf_file(self,0);bcf_io_args_t a={v->file,get_bcf_header(header)->pointer,NULL,NULL,NULL,NULL,0};
|
|
340
|
-
static VALUE native_bcf_file_write(VALUE self,VALUE header,VALUE record){ruby_bcf_file_t*v=get_bcf_file(self,0);bcf_io_args_t a={v->file,get_bcf_header(header)->pointer,get_bcf_record(record)->pointer,NULL,NULL,NULL,0};
|
|
415
|
+
static VALUE native_bcf_file_read_header(VALUE self){ruby_bcf_file_t*v=get_bcf_file(self,0);bcf_header_read_args_t a={v->file,NULL};bcf_io_run(v,bcf_read_header_without_gvl,&a);return wrap_bcf_header(a.result);}
|
|
416
|
+
static VALUE native_bcf_file_read(VALUE self,VALUE header,VALUE record){ruby_bcf_file_t*v=get_bcf_file(self,0);bcf_io_args_t a={v->file,get_bcf_header(header)->pointer,get_bcf_record(record)->pointer,NULL,NULL,NULL,0};bcf_io_run(v,bcf_read_without_gvl,&a);return INT2NUM(a.result);}
|
|
417
|
+
static VALUE native_bcf_file_write_header(VALUE self,VALUE header){ruby_bcf_file_t*v=get_bcf_file(self,0);bcf_io_args_t a={v->file,get_bcf_header(header)->pointer,NULL,NULL,NULL,NULL,0};bcf_io_run(v,bcf_write_header_without_gvl,&a);return INT2NUM(a.result);}
|
|
418
|
+
static VALUE native_bcf_file_write(VALUE self,VALUE header,VALUE record){ruby_bcf_file_t*v=get_bcf_file(self,0);bcf_io_args_t a={v->file,get_bcf_header(header)->pointer,get_bcf_record(record)->pointer,NULL,NULL,NULL,0};bcf_io_run(v,bcf_write_without_gvl,&a);return INT2NUM(a.result);}
|
|
341
419
|
static VALUE native_bcf_file_set_threads(VALUE self,VALUE n){return INT2NUM(hts_set_threads(get_bcf_file(self,0)->file,NUM2INT(n)));}
|
|
342
420
|
static VALUE native_bcf_file_format(VALUE self){const htsFormat*f=hts_get_format(get_bcf_file(self,0)->file);if(!f)return Qnil;switch(f->format){case vcf:return rb_str_new_cstr("vcf");case bcf:return rb_str_new_cstr("bcf");default:return rb_str_new_cstr("unknown_format");}}
|
|
343
421
|
static VALUE native_bcf_file_version(VALUE self){const htsFormat*f=hts_get_format(get_bcf_file(self,0)->file);if(!f)return Qnil;if(f->version.minor==-1)return rb_sprintf("%d",f->version.major);return rb_sprintf("%d.%d",f->version.major,f->version.minor);}
|
|
@@ -348,7 +426,7 @@ static VALUE native_bcf_file_index_loaded(VALUE self){ruby_bcf_file_t*v=get_bcf_
|
|
|
348
426
|
static VALUE native_bcf_file_build_index(VALUE klass,VALUE path,VALUE index,VALUE shift,VALUE threads){return INT2NUM(bcf_index_build3(StringValueCStr(path),NIL_P(index)?NULL:StringValueCStr(index),NUM2INT(shift),NUM2INT(threads)));}
|
|
349
427
|
static VALUE native_bcf_file_query_region(VALUE self,VALUE header,VALUE region){ruby_bcf_file_t*v=get_bcf_file(self,0);hts_itr_t*i;if(v->tabix)i=tbx_itr_querys(v->tabix,StringValueCStr(region));else i=bcf_itr_querys(v->index,get_bcf_header(header)->pointer,StringValueCStr(region));return wrap_bcf_iterator(self,header,i,v->tabix!=NULL);}
|
|
350
428
|
static VALUE native_bcf_file_query_interval(VALUE self,VALUE header,VALUE rid,VALUE beg,VALUE end){ruby_bcf_file_t*v=get_bcf_file(self,0);hts_itr_t*i=v->tabix?tbx_itr_queryi(v->tabix,NUM2INT(rid),NUM2LL(beg),NUM2LL(end)):bcf_itr_queryi(v->index,NUM2INT(rid),NUM2LL(beg),NUM2LL(end));return wrap_bcf_iterator(self,header,i,v->tabix!=NULL);}
|
|
351
|
-
static VALUE native_bcf_iterator_next(VALUE self,VALUE record){ruby_bcf_iterator_t*i=get_bcf_iterator(self);ruby_bcf_file_t*f=get_bcf_file(i->file,0);bcf_io_args_t a={f->file,get_bcf_header(i->header)->pointer,get_bcf_record(record)->pointer,i->iterator,i->tabix?f->tabix:NULL,&i->line,0};
|
|
429
|
+
static VALUE native_bcf_iterator_next(VALUE self,VALUE record){ruby_bcf_iterator_t*i=get_bcf_iterator(self);ruby_bcf_file_t*f=get_bcf_file(i->file,0);bcf_io_args_t a={f->file,get_bcf_header(i->header)->pointer,get_bcf_record(record)->pointer,i->iterator,i->tabix?f->tabix:NULL,&i->line,0};bcf_io_run(f,bcf_iterator_without_gvl,&a);return INT2NUM(a.result);}
|
|
352
430
|
static VALUE native_bcf_iterator_close(VALUE self){ruby_bcf_iterator_t*v;TypedData_Get_Struct(self,ruby_bcf_iterator_t,&bcf_iterator_type,v);if(v->iterator){hts_itr_destroy(v->iterator);v->iterator=NULL;}return Qnil;}
|
|
353
431
|
|
|
354
432
|
void Init_htslib_native_bcf(VALUE native) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "htslib_native.h"
|
|
2
2
|
|
|
3
3
|
#include <htslib/faidx.h>
|
|
4
|
+
#include <errno.h>
|
|
4
5
|
#include <stdlib.h>
|
|
5
6
|
|
|
6
7
|
typedef struct {
|
|
@@ -47,10 +48,18 @@ static VALUE native_faidx_open(VALUE klass, VALUE path_value, VALUE format_value
|
|
|
47
48
|
enum fai_format_options format = NUM2INT(format_value) == 1 ? FAI_FASTQ : FAI_FASTA;
|
|
48
49
|
|
|
49
50
|
object = TypedData_Make_Struct(klass, ruby_faidx_t, &ruby_faidx_type, handle);
|
|
51
|
+
errno = 0;
|
|
50
52
|
handle->fai = RTEST(auto_build_value)
|
|
51
53
|
? fai_load_format(path, format)
|
|
52
54
|
: fai_load3_format(path, NULL, NULL, 0, format);
|
|
53
|
-
if (handle->fai == NULL)
|
|
55
|
+
if (handle->fai == NULL) {
|
|
56
|
+
int error = errno;
|
|
57
|
+
if (error) rb_syserr_fail_str(error, path_value);
|
|
58
|
+
VALUE hts = rb_const_get(rb_cObject, rb_intern("HTS"));
|
|
59
|
+
VALUE faidx = rb_const_get(hts, rb_intern("Faidx"));
|
|
60
|
+
VALUE open_error = rb_const_get(faidx, rb_intern("OpenError"));
|
|
61
|
+
rb_raise(open_error, "Failed to open %"PRIsVALUE": HTSlib could not load the index", path_value);
|
|
62
|
+
}
|
|
54
63
|
return object;
|
|
55
64
|
}
|
|
56
65
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include <htslib/hts.h>
|
|
4
4
|
#include <htslib/kstring.h>
|
|
5
5
|
#include <htslib/tbx.h>
|
|
6
|
+
#include <errno.h>
|
|
6
7
|
#include <stdlib.h>
|
|
7
8
|
|
|
8
9
|
typedef struct {
|
|
@@ -72,8 +73,16 @@ static VALUE native_tabix_open(VALUE klass, VALUE path_value)
|
|
|
72
73
|
handle->active_queries = 0;
|
|
73
74
|
handle->path = rb_str_dup(StringValue(path_value));
|
|
74
75
|
RB_OBJ_WRITE(object, &handle->path, handle->path);
|
|
76
|
+
errno = 0;
|
|
75
77
|
handle->file = hts_open(StringValueCStr(handle->path), "r");
|
|
76
|
-
if (handle->file == NULL)
|
|
78
|
+
if (handle->file == NULL) {
|
|
79
|
+
int error = errno;
|
|
80
|
+
if (error) rb_syserr_fail_str(error, path_value);
|
|
81
|
+
VALUE hts = rb_const_get(rb_cObject, rb_intern("HTS"));
|
|
82
|
+
VALUE tabix = rb_const_get(hts, rb_intern("Tabix"));
|
|
83
|
+
VALUE open_error = rb_const_get(tabix, rb_intern("OpenError"));
|
|
84
|
+
rb_raise(open_error, "Failed to open %"PRIsVALUE": HTSlib could not recognize or open the input", path_value);
|
|
85
|
+
}
|
|
77
86
|
return object;
|
|
78
87
|
}
|
|
79
88
|
|
data/lib/hts/bam/base_mod.rb
CHANGED
data/lib/hts/bam/flag.rb
CHANGED
|
@@ -28,6 +28,10 @@ module HTS
|
|
|
28
28
|
DUPLICATE = 1024
|
|
29
29
|
SUPPLEMENTARY = 2048
|
|
30
30
|
|
|
31
|
+
UNMAP = UNMAPPED
|
|
32
|
+
MUNMAP = MATE_UNMAPPED
|
|
33
|
+
DUP = DUPLICATE
|
|
34
|
+
|
|
31
35
|
TABLE = { paired?: PAIRED, proper_pair?: PROPER_PAIR, unmapped?: UNMAPPED,
|
|
32
36
|
mate_unmapped?: MATE_UNMAPPED, reverse?: REVERSE,
|
|
33
37
|
mate_reverse?: MATE_REVERSE, read1?: READ1, read2?: READ2,
|
|
@@ -74,9 +78,7 @@ module HTS
|
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
def ~
|
|
77
|
-
|
|
78
|
-
# The result is different from the Crystal version.
|
|
79
|
-
self.class.new(~@value)
|
|
81
|
+
self.class.new((~@value) & 0x0fff)
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def <<(f)
|
data/lib/hts/bam.rb
CHANGED
|
@@ -17,6 +17,11 @@ module HTS
|
|
|
17
17
|
class Bam
|
|
18
18
|
include Enumerable
|
|
19
19
|
|
|
20
|
+
class ReadError < HTS::Error; end
|
|
21
|
+
class WriteError < HTS::Error; end
|
|
22
|
+
class OpenError < HTS::Error; end
|
|
23
|
+
class MissingIndexError < HTS::Error; end
|
|
24
|
+
|
|
20
25
|
# Filter an owning batch of records in one native pass when available.
|
|
21
26
|
def self.filter_records(records, required_flags: 0, excluded_flags: 0,
|
|
22
27
|
min_mapq: 0, tid: nil, beg: nil, end_: nil)
|
|
@@ -38,11 +43,11 @@ module HTS
|
|
|
38
43
|
return file unless block_given?
|
|
39
44
|
|
|
40
45
|
begin
|
|
41
|
-
yield file
|
|
46
|
+
result = yield file
|
|
42
47
|
ensure
|
|
43
48
|
file.close
|
|
44
49
|
end
|
|
45
|
-
|
|
50
|
+
result
|
|
46
51
|
end
|
|
47
52
|
|
|
48
53
|
def self.build_index(file_name, index_name = nil, min_shift = 0, threads = 0, verbose = true)
|
|
@@ -98,7 +103,11 @@ module HTS
|
|
|
98
103
|
|
|
99
104
|
set_threads(threads) if threads
|
|
100
105
|
|
|
101
|
-
|
|
106
|
+
if writing?
|
|
107
|
+
@auto_index_on_close = build_index
|
|
108
|
+
@index_name_on_close = index
|
|
109
|
+
return
|
|
110
|
+
end
|
|
102
111
|
|
|
103
112
|
@header = Bam::Header.new(@native.read_header)
|
|
104
113
|
if build_index
|
|
@@ -120,6 +129,12 @@ module HTS
|
|
|
120
129
|
end
|
|
121
130
|
|
|
122
131
|
def load_index(index_name = nil)
|
|
132
|
+
return self if try_load_index(index_name)
|
|
133
|
+
|
|
134
|
+
raise MissingIndexError, "Failed to load index #{index_name || "for #{@file_name}"}"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def try_load_index(index_name = nil)
|
|
123
138
|
check_closed
|
|
124
139
|
|
|
125
140
|
@index_name = index_name
|
|
@@ -134,7 +149,16 @@ module HTS
|
|
|
134
149
|
end
|
|
135
150
|
|
|
136
151
|
def close
|
|
137
|
-
|
|
152
|
+
was_closed = closed?
|
|
153
|
+
result = @native&.close
|
|
154
|
+
raise WriteError, "Failed to close #{@file_name}: buffered output may be incomplete" if writing? && result&.negative?
|
|
155
|
+
|
|
156
|
+
if writing? && @auto_index_on_close && !was_closed
|
|
157
|
+
@auto_index_on_close = false
|
|
158
|
+
self.class.build_index(@file_name, @index_name_on_close, 0, @nthreads || 0, false)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
nil
|
|
138
162
|
end
|
|
139
163
|
|
|
140
164
|
def closed? = @native.nil? || @native.closed?
|
|
@@ -168,6 +192,8 @@ module HTS
|
|
|
168
192
|
|
|
169
193
|
private
|
|
170
194
|
|
|
195
|
+
def writing? = @mode&.start_with?("w", "a")
|
|
196
|
+
|
|
171
197
|
def native_handle = @native
|
|
172
198
|
|
|
173
199
|
public
|
|
@@ -177,6 +203,7 @@ module HTS
|
|
|
177
203
|
|
|
178
204
|
@header = header.dup
|
|
179
205
|
@native.write_header(header.__send__(:native_handle))
|
|
206
|
+
nil
|
|
180
207
|
end
|
|
181
208
|
|
|
182
209
|
def header=(header)
|
|
@@ -188,10 +215,13 @@ module HTS
|
|
|
188
215
|
|
|
189
216
|
r = @native.write(header.__send__(:native_handle), record.__send__(:native_handle))
|
|
190
217
|
raise "Failed to write record" if r < 0
|
|
218
|
+
|
|
219
|
+
nil
|
|
191
220
|
end
|
|
192
221
|
|
|
193
222
|
def <<(record)
|
|
194
223
|
write(record)
|
|
224
|
+
self
|
|
195
225
|
end
|
|
196
226
|
|
|
197
227
|
# @!macro [attach] define_getter
|
|
@@ -230,6 +260,13 @@ module HTS
|
|
|
230
260
|
each(copy: true).to_a
|
|
231
261
|
end
|
|
232
262
|
|
|
263
|
+
# Materialize independent, owning records. Enumerable#to_a is unsafe for
|
|
264
|
+
# the default reused-record iterator because every array element would
|
|
265
|
+
# otherwise refer to the same native bam1_t buffer.
|
|
266
|
+
def to_a
|
|
267
|
+
collect_records
|
|
268
|
+
end
|
|
269
|
+
|
|
233
270
|
# @!macro [attach] define_iterator
|
|
234
271
|
# @method each_$1
|
|
235
272
|
# Get $1 iterator
|
|
@@ -378,7 +415,13 @@ module HTS
|
|
|
378
415
|
return to_enum(__method__) unless block_given?
|
|
379
416
|
|
|
380
417
|
record = Record.new(header)
|
|
381
|
-
|
|
418
|
+
loop do
|
|
419
|
+
result = @native.read(header.__send__(:native_handle), record.__send__(:native_handle))
|
|
420
|
+
break if result == -1
|
|
421
|
+
raise ReadError, "Failed to read BAM/SAM record (HTSlib error #{result})" if result < -1
|
|
422
|
+
|
|
423
|
+
yield record
|
|
424
|
+
end
|
|
382
425
|
self
|
|
383
426
|
end
|
|
384
427
|
|
|
@@ -388,7 +431,13 @@ module HTS
|
|
|
388
431
|
return to_enum(__method__) unless block_given?
|
|
389
432
|
|
|
390
433
|
record = Record.new(header)
|
|
391
|
-
|
|
434
|
+
loop do
|
|
435
|
+
result = @native.read(header.__send__(:native_handle), record.__send__(:native_handle))
|
|
436
|
+
break if result == -1
|
|
437
|
+
raise ReadError, "Failed to read BAM/SAM record (HTSlib error #{result})" if result < -1
|
|
438
|
+
|
|
439
|
+
yield record.dup
|
|
440
|
+
end
|
|
392
441
|
self
|
|
393
442
|
end
|
|
394
443
|
|
data/lib/hts/bcf/errors.rb
CHANGED
|
@@ -5,14 +5,17 @@ module HTS
|
|
|
5
5
|
class Error < HTS::Error; end
|
|
6
6
|
|
|
7
7
|
class OpenError < Error; end
|
|
8
|
+
class WriteError < Error; end
|
|
8
9
|
class IndexError < Error; end
|
|
9
10
|
class MissingIndexError < IndexError; end
|
|
10
11
|
class QueryError < Error; end
|
|
12
|
+
class RecordError < Error; end
|
|
11
13
|
class HeaderError < Error; end
|
|
12
14
|
class SubsetError < HeaderError; end
|
|
13
15
|
class UnknownSampleError < SubsetError; end
|
|
14
16
|
class FieldError < Error; end
|
|
15
17
|
class InfoError < FieldError; end
|
|
18
|
+
class InfoDefinitionError < InfoError; end
|
|
16
19
|
class InfoTypeError < InfoError; end
|
|
17
20
|
class InfoReadError < InfoError; end
|
|
18
21
|
class InfoUpdateError < InfoError; end
|
data/lib/hts/bcf/format.rb
CHANGED
|
@@ -141,7 +141,14 @@ module HTS
|
|
|
141
141
|
native.format_get(header_native, key, code, raw_float)
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
-
def
|
|
144
|
+
def get_int_raw(key) = get_raw(key, :int)
|
|
145
|
+
|
|
146
|
+
def get_int(key)
|
|
147
|
+
values = get_int_raw(key)
|
|
148
|
+
values&.each_with_object([]) do |value, decoded|
|
|
149
|
+
decoded << missing_int(value) unless value == Native::BCF_INT32_VECTOR_END
|
|
150
|
+
end
|
|
151
|
+
end
|
|
145
152
|
|
|
146
153
|
def get_float(key)
|
|
147
154
|
words = get_raw(key, :float)
|
data/lib/hts/bcf/header.rb
CHANGED
|
@@ -44,6 +44,7 @@ module HTS
|
|
|
44
44
|
def get_version
|
|
45
45
|
@native.version
|
|
46
46
|
end
|
|
47
|
+
alias version get_version
|
|
47
48
|
|
|
48
49
|
def set_version(version)
|
|
49
50
|
rc = @native.set_version(version)
|
|
@@ -53,6 +54,7 @@ module HTS
|
|
|
53
54
|
sync_if_needed!
|
|
54
55
|
self
|
|
55
56
|
end
|
|
57
|
+
alias version= set_version
|
|
56
58
|
|
|
57
59
|
def nsamples
|
|
58
60
|
@native.nsamples
|
data/lib/hts/bcf/info.rb
CHANGED
|
@@ -15,7 +15,7 @@ module HTS
|
|
|
15
15
|
|
|
16
16
|
def get(key, type = nil)
|
|
17
17
|
schema = header.schema("INFO", key.to_s)
|
|
18
|
-
|
|
18
|
+
raise InfoDefinitionError, "INFO tag #{key} not defined in header" unless schema
|
|
19
19
|
|
|
20
20
|
actual_type = schema.first
|
|
21
21
|
requested = type&.to_sym
|
|
@@ -24,7 +24,8 @@ module HTS
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
code = TYPE_CODES.fetch(requested || actual_type)
|
|
27
|
-
native.info_get(header_native, key.to_s, code)
|
|
27
|
+
value = native.info_get(header_native, key.to_s, code)
|
|
28
|
+
code == Native::BCF_HT_FLAG && value.nil? ? false : value
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def get_int(key) = get(key, :int)
|
data/lib/hts/bcf/record.rb
CHANGED
|
@@ -27,11 +27,12 @@ module HTS
|
|
|
27
27
|
def id = @native.id
|
|
28
28
|
|
|
29
29
|
def id=(value)
|
|
30
|
-
@native.set_id(@header.__send__(:native_handle), value)
|
|
30
|
+
check_update_rc!(@native.set_id(@header.__send__(:native_handle), value), "ID", value)
|
|
31
|
+
value
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def clear_id
|
|
34
|
-
@native.set_id(@header.__send__(:native_handle), ".")
|
|
35
|
+
check_update_rc!(@native.set_id(@header.__send__(:native_handle), "."), "ID", ".")
|
|
35
36
|
nil
|
|
36
37
|
end
|
|
37
38
|
|
|
@@ -44,7 +45,8 @@ module HTS
|
|
|
44
45
|
values = Array(values).map(&:to_s)
|
|
45
46
|
raise ArgumentError, "at least one allele is required" if values.empty?
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
encoded = values.join(",")
|
|
49
|
+
check_update_rc!(@native.set_alleles(@header.__send__(:native_handle), encoded), "alleles", encoded)
|
|
48
50
|
values
|
|
49
51
|
end
|
|
50
52
|
|
|
@@ -62,15 +64,20 @@ module HTS
|
|
|
62
64
|
@native.core_set(:qual, value)
|
|
63
65
|
end
|
|
64
66
|
|
|
65
|
-
def
|
|
67
|
+
def filters
|
|
66
68
|
names = @native.filter_names(@header.__send__(:native_handle))
|
|
67
|
-
|
|
68
|
-
when 0 then "PASS"
|
|
69
|
-
when 1 then names.first
|
|
70
|
-
else names
|
|
71
|
-
end
|
|
69
|
+
names
|
|
72
70
|
end
|
|
73
71
|
|
|
72
|
+
|
|
73
|
+
# VCF FILTER is a list even when it contains a single value. Keeping a
|
|
74
|
+
# stable return type avoids substring matching for single-filter records.
|
|
75
|
+
alias filter filters
|
|
76
|
+
|
|
77
|
+
def passed? = filters == ["PASS"]
|
|
78
|
+
def filter_missing? = filters.empty?
|
|
79
|
+
def filtered? = !passed? && !filter_missing?
|
|
80
|
+
|
|
74
81
|
def each_filter_id(&block)
|
|
75
82
|
return to_enum(__method__) unless block_given?
|
|
76
83
|
|
|
@@ -95,6 +102,12 @@ module HTS
|
|
|
95
102
|
|
|
96
103
|
private
|
|
97
104
|
|
|
105
|
+
def check_update_rc!(result, field, value)
|
|
106
|
+
return result unless result.negative?
|
|
107
|
+
|
|
108
|
+
raise RecordError, "Failed to update #{field} to #{value.inspect} (HTSlib error #{result})"
|
|
109
|
+
end
|
|
110
|
+
|
|
98
111
|
def native_handle = @native
|
|
99
112
|
|
|
100
113
|
def initialize_copy(original)
|
data/lib/hts/bcf.rb
CHANGED
|
@@ -29,11 +29,11 @@ module HTS
|
|
|
29
29
|
return file unless block_given?
|
|
30
30
|
|
|
31
31
|
begin
|
|
32
|
-
yield file
|
|
32
|
+
result = yield file
|
|
33
33
|
ensure
|
|
34
34
|
file.close
|
|
35
35
|
end
|
|
36
|
-
|
|
36
|
+
result
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def self.build_index(file_name, index_name = nil, min_shift = 14, threads = 0, verbose = true)
|
|
@@ -67,7 +67,11 @@ module HTS
|
|
|
67
67
|
raise SubsetError,
|
|
68
68
|
"Sample subsetting is only available when reading BCF/VCF files"
|
|
69
69
|
end
|
|
70
|
-
|
|
70
|
+
if writing?
|
|
71
|
+
@auto_index_on_close = build_index
|
|
72
|
+
@index_name_on_close = index
|
|
73
|
+
return
|
|
74
|
+
end
|
|
71
75
|
|
|
72
76
|
@read_header = Header.new(@native.read_header)
|
|
73
77
|
@header = subset ? @read_header.subset(subset) : @read_header
|
|
@@ -91,6 +95,12 @@ module HTS
|
|
|
91
95
|
end
|
|
92
96
|
|
|
93
97
|
def load_index(index_name = nil)
|
|
98
|
+
return self if try_load_index(index_name)
|
|
99
|
+
|
|
100
|
+
raise MissingIndexError, "Failed to load index #{index_name || "for #{@file_name}"}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def try_load_index(index_name = nil)
|
|
94
104
|
check_closed
|
|
95
105
|
@index_name = index_name
|
|
96
106
|
@index_load_attempted = true
|
|
@@ -102,7 +112,18 @@ module HTS
|
|
|
102
112
|
@native.index_loaded?
|
|
103
113
|
end
|
|
104
114
|
|
|
105
|
-
def close
|
|
115
|
+
def close
|
|
116
|
+
was_closed = closed?
|
|
117
|
+
result = @native&.close
|
|
118
|
+
raise WriteError, "Failed to close #{@file_name}: buffered output may be incomplete" if writing? && result&.negative?
|
|
119
|
+
|
|
120
|
+
if writing? && @auto_index_on_close && !was_closed
|
|
121
|
+
@auto_index_on_close = false
|
|
122
|
+
self.class.build_index(@file_name, @index_name_on_close, 14, @nthreads || 0, false)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
106
127
|
def closed? = @native.nil? || @native.closed?
|
|
107
128
|
def file_format = @native.file_format
|
|
108
129
|
def file_format_version = @native.file_format_version
|
|
@@ -137,7 +158,7 @@ module HTS
|
|
|
137
158
|
result = @native.write_header(header.__send__(:native_handle))
|
|
138
159
|
raise HeaderError, "Failed to write BCF header" if result.negative?
|
|
139
160
|
|
|
140
|
-
|
|
161
|
+
nil
|
|
141
162
|
end
|
|
142
163
|
|
|
143
164
|
def header=(header)
|
|
@@ -149,9 +170,13 @@ module HTS
|
|
|
149
170
|
result = @native.write(header.__send__(:native_handle), record.__send__(:native_handle))
|
|
150
171
|
raise "Failed to write record" if result.negative?
|
|
151
172
|
|
|
152
|
-
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def <<(record)
|
|
177
|
+
write(record)
|
|
178
|
+
self
|
|
153
179
|
end
|
|
154
|
-
alias << write
|
|
155
180
|
|
|
156
181
|
def nsamples
|
|
157
182
|
check_closed
|
|
@@ -192,6 +217,10 @@ module HTS
|
|
|
192
217
|
|
|
193
218
|
def collect_records = each(copy: true).to_a
|
|
194
219
|
|
|
220
|
+
# Materialize independent, owning records rather than retaining the
|
|
221
|
+
# reused Record yielded by the allocation-conscious default iterator.
|
|
222
|
+
def to_a = collect_records
|
|
223
|
+
|
|
195
224
|
define_iterator :chrom
|
|
196
225
|
define_iterator :pos
|
|
197
226
|
define_iterator :endpos
|
|
@@ -244,6 +273,8 @@ module HTS
|
|
|
244
273
|
|
|
245
274
|
private
|
|
246
275
|
|
|
276
|
+
def writing? = @mode&.start_with?("w", "a")
|
|
277
|
+
|
|
247
278
|
def ensure_index_loaded
|
|
248
279
|
return true if index_loaded?
|
|
249
280
|
return false if @index_load_attempted
|
data/lib/hts/faidx.rb
CHANGED
data/lib/hts/tabix.rb
CHANGED
|
@@ -9,6 +9,9 @@ module HTS
|
|
|
9
9
|
class Tabix < Hts
|
|
10
10
|
include Enumerable
|
|
11
11
|
|
|
12
|
+
class OpenError < HTS::Error; end
|
|
13
|
+
class MissingIndexError < HTS::Error; end
|
|
14
|
+
|
|
12
15
|
attr_reader :file_name, :index_name, :mode, :nthreads
|
|
13
16
|
|
|
14
17
|
def self.open(*args, **kw)
|
|
@@ -16,11 +19,11 @@ module HTS
|
|
|
16
19
|
return file unless block_given?
|
|
17
20
|
|
|
18
21
|
begin
|
|
19
|
-
yield file
|
|
22
|
+
result = yield file
|
|
20
23
|
ensure
|
|
21
24
|
file.close
|
|
22
25
|
end
|
|
23
|
-
|
|
26
|
+
result
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
def initialize(file_name, index: nil, threads: nil, build_index: false)
|
|
@@ -40,8 +43,12 @@ module HTS
|
|
|
40
43
|
|
|
41
44
|
set_threads(threads) if threads
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
if build_index
|
|
47
|
+
build_index(index)
|
|
48
|
+
load_index(index)
|
|
49
|
+
elsif index
|
|
50
|
+
load_index(index)
|
|
51
|
+
end
|
|
45
52
|
end
|
|
46
53
|
|
|
47
54
|
def build_index(index_name = nil, min_shift: 0)
|
|
@@ -70,6 +77,12 @@ module HTS
|
|
|
70
77
|
end
|
|
71
78
|
|
|
72
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)
|
|
73
86
|
check_closed
|
|
74
87
|
@index_name = index_name
|
|
75
88
|
@index_load_attempted = true
|
data/lib/hts/version.rb
CHANGED