1 module dhtslib.file.file;
2 
3 import core.stdc.stdio : SEEK_SET;
4 
5 import std.stdio;
6 import std.string : toStringz, fromStringz;
7 import std.utf : toUTFz;
8 import std.traits : isSomeString;
9 import std.algorithm : map;
10 import std.array : array;
11 
12 import dhtslib.memory;
13 import dhtslib.file.iterator;
14 import dhtslib : initKstring;
15 import htslib;
16 
17 enum HtslibFileFormatMode
18 {
19     Binary = 'b',
20     Cram = 'c',
21     Gzip = 'g',
22     Uncompressed = 'u',
23     Bgzf = 'z',
24     ZlibCompress0 = '0',
25     ZlibCompress1 = '1',
26     ZlibCompress2 = '2',
27     ZlibCompress3 = '3',
28     ZlibCompress4 = '4',
29     ZlibCompress5 = '5',
30     ZlibCompress6 = '6',
31     ZlibCompress7 = '7',
32     ZlibCompress8 = '8',
33     ZlibCompress9 = '9',
34 }
35 
36 
37 /** 
38  * Some shortcut mode strings for different file types
39  * As a reminder:
40  *      b  binary format (BAM, BCF, etc) rather than text (SAM, VCF, etc)
41         c  CRAM format
42         g  gzip compressed
43         u  uncompressed
44         z  bgzf compressed
45         [0-9]  zlib compression level
46  */
47 enum HtslibFileWriteMode
48 {
49     Bam             = "wb",
50     Cram            = "wc",
51     Sam             = "w",
52     UncompressedBam = "wb0",
53     GzippedSam      = "wg",
54     BgzippedSam     = "wz",
55 
56     Bcf             = "wb",
57     UncompressedBcf = "wb0",
58     Vcf             = "w",
59     GzippedVcf      = "wg",
60     BgzippedVcf     = "wz",
61 
62     Text            = "w",
63     GzippedText     = "wg",
64     BgzippedText    = "wz",
65     
66 }
67 
68 /** 
69  * HtslibFile is an abstraction for htslib's htsFile using dhtslib.memory for reference counting.
70  * 
71  * Ideally this struct and its methods can replace file io across dhtslib through a 
72  * standard interface. It can also centralize the ability to duplicate a file so that 
73  * it can be iterated several times. It pairs with HtslibIterator.
74  */
75 struct HtslibFile
76 {
77     /// dhtslib.memory htsFile* rc wrapper
78     /// File pointer
79     HtsFile fp; 
80 
81     /// D File if used
82     File f;
83 
84     /// dhtslib.memory kstring_t rc wrapper
85     /// Since we need the filename as a null terminated string anyway
86     /// just use kstring
87     Kstring fn;
88 
89     char[] mode;
90 
91     /// SAM/BAM/CRAM index 
92     HtsIdx idx;
93 
94     /// SAM/BAM/CRAM header
95     BamHdr bamHdr;
96     /// BCF/VCF header
97     BcfHdr bcfHdr;
98     /// text header
99     Kstring textHdr;
100 
101     /// Tabix index 
102     Tbx tbx;
103 
104     /// is file end reached?
105     bool eof;
106 
107     /// offset of file after header
108     /// if it has been loaded 
109     off_t headerOffset = -1;
110 
111     /// allow HtslibFile to be used as 
112     /// underlying ptr type
113     alias getFilePtr this;
114 
115     /// get underlying file pointer wrapper
116     @property nothrow pure @nogc
117     ref inout(HtsFile) getFilePtr() inout return
118     {
119         return this.fp;
120     }
121 
122     /// File or string ctor
123     this(T)(T f)
124     if ((isSomeString!T || is(T == File)))
125     {
126         this(f, "r");
127     }
128 
129     /// File or string ctor with mode
130     this(T1, T2)(T1 f, T2 mode)
131     if ((isSomeString!T1 || is(T1 == File)) && (isSomeString!T2 || is(T2 == HtslibFileWriteMode)))
132     {
133         this.mode = mode.dup ~ '\0';
134         // open file
135         static if (isSomeString!T1)
136         {
137             this.fn = Kstring(initKstring());
138             ks_initialize(this.fn);
139             kputsn(f.ptr, f.length, this.fn);
140             this.fp = HtsFile(hts_open(this.fn.s, this.mode.ptr));
141         }
142         else static if (is(T1 == File))
143         {
144             this.fn = Kstring(initKstring());
145             ks_initialize(this.fn);
146             kputsn(f.name.ptr, f.name.length, this.fn);
147 
148             auto hf = hdopen(f.fileno, m.ptr);
149             this.fp = HtsFile(hts_hopen(hf, this.fn.s, m.ptr));
150         }
151         else static assert(0);
152     }
153 
154     /// Duplicate the file with the same offset
155     HtslibFile dup()
156     {
157         // open a new file
158         auto filename = fromStringz(this.fn.s).idup;
159         auto newFile = HtslibFile(filename, this.mode);
160 
161         // seek to the current offset
162         newFile.seek(this.tell);
163 
164         // copy internal fields
165         newFile.idx = this.idx;
166         newFile.bamHdr = this.bamHdr;
167         newFile.bcfHdr = this.bcfHdr;
168         newFile.textHdr = this.textHdr;
169         newFile.tbx = this.tbx;
170         newFile.eof = this.eof;
171         newFile.headerOffset = this.headerOffset;
172         return newFile;
173     }
174 
175     /// set extra multithreading
176     void setExtraThreads(int extra)
177     {
178         hts_set_threads(this.fp, extra);
179     }
180 
181     /// get file offset
182     off_t tell()
183     {
184         if(this.fp.is_bgzf) return bgzf_tell(this.fp.fp.bgzf);
185         else return htell(this.fp.fp.hfile);
186     }
187 
188     /// seek to offset in file
189     void seek(long loc)
190     {
191         long err;
192         if(this.fp.is_bgzf) err = bgzf_seek(this.fp.fp.bgzf, loc, SEEK_SET);
193         else err = hseek(this.fp.fp.hfile, loc, SEEK_SET);
194         if(err < 0) hts_log_error(__FUNCTION__, "Error seeking htsFile");
195     }
196 
197     /// reset file reading
198     void reset()
199     {
200         this.seek(0);
201     }
202 
203     /// reset file reading
204     void resetToFirstRecord()
205     {
206         if(headerOffset != -1)
207             this.seek(headerOffset);
208         else {
209             hts_log_error(__FUNCTION__, "Cannot resetToFirstRecord, header offset unknown");
210             hts_log_error(__FUNCTION__, "Has the header been loaded?");
211         }
212     }
213 
214     /// Read a SAM/BAM header, VCF/BCF header, or text header and internally store it
215     void loadHeader(char commentChar = '#')
216     {
217         switch(this.fp.format.format){
218             case htsExactFormat.sam:
219             case htsExactFormat.bam:
220             case htsExactFormat.cram:
221                 this.loadHeader!BamHdr(commentChar);
222                 break;
223             case htsExactFormat.vcf:
224             case htsExactFormat.bcf:
225                 this.loadHeader!BcfHdr(commentChar);    
226                 break;
227             default:
228                 this.loadHeader!Kstring(commentChar);
229         }
230     }
231 
232     /// Read a SAM/BAM header, VCF/BCF header, or text header and internally store it
233     void loadHeader(T)(char commentChar)
234     if(is(T == BamHdr) || is(T == BcfHdr) || is(T == Kstring))
235     {
236         static if(is(T == BamHdr)){
237             this.bamHdr = BamHdr(sam_hdr_read(this.fp));
238         }
239         else static if(is(T == BcfHdr)){
240             this.bcfHdr = BcfHdr(bcf_hdr_read(this.fp));
241         }
242         else static if(is(T == Kstring)){
243             // parse header from a text file
244             this.textHdr = Kstring(initKstring);
245             ks_initialize(this.textHdr);
246 
247             auto ks = Kstring(initKstring);
248             ks_initialize(ks);
249 
250             // peek at first char, if commentChar (#)
251             // save the line and repeat
252             if(this.fp.is_bgzf){
253                 
254                 while(true){
255                     if(cast(char) bgzf_peek(this.fp.fp.bgzf) == commentChar){
256                         hts_getline(this.fp, cast(int)'\n', ks);
257                         kputs(ks.s, this.textHdr);
258                         kputc(cast(int)'\n', this.textHdr);
259                     } else break;
260                 }
261             }else{
262                 while(true){
263                     char c;
264                     hpeek(this.fp.fp.hfile, &c, 1);
265                     if(c  == commentChar){
266                         hts_getline(this.fp, cast(int)'\n', ks);
267                         kputs(ks.s, this.textHdr);
268                         kputc(cast(int)'\n', this.textHdr);
269                     } else break;
270                 }
271                 this.textHdr.l--;
272                 kputc(cast(int)'\0', this.textHdr);
273             }
274         }
275         // set header offset
276         this.headerOffset = this.tell;
277     }
278 
279     /// set header for a HtlsibFile 
280     void setHeader(T)(T hdr)
281     if(is(T == BamHdr) || is(T == BcfHdr))
282     {
283         static if(is(T == BamHdr))
284             this.bamHdr = hdr;
285         else static if(is(T == BcfHdr))
286             this.bcfHdr = hdr;
287         else static assert(0);
288     }
289 
290     /// write internally stored header
291     void writeHeader()
292     {
293         assert(this.bamHdr.initialized || this.bcfHdr.initialized);
294         assert(!(this.bamHdr.initialized && this.bcfHdr.initialized));
295         assert((this.bamHdr.initialized && this.bamHdr.getRef != null) 
296             || (this.bcfHdr.initialized && this.bcfHdr.getRef != null));
297         int err;
298         if(this.bamHdr.initialized)
299             err = sam_hdr_write(this.fp, this.bamHdr);
300         else
301             err = bcf_hdr_write(this.fp, this.bcfHdr);
302         if(err < 0) hts_log_error(__FUNCTION__, "Error writing SAM/BAM/VCF/BCF header");
303     }
304 
305     /// load BAM/BCF index
306     HtsIdx loadHtsIndex()
307     {
308         if(this.fp.format.format == htsExactFormat.bam || this.fp.format.format == htsExactFormat.cram)
309             this.idx = HtsIdx(sam_index_load(this.fp, cast(const(char)*)this.fn.s));
310         else if(this.fp.format.format == htsExactFormat.bcf)
311             this.idx = HtsIdx(bcf_index_load(cast(const(char)*)this.fn.s));
312         return this.idx;
313     }
314 
315     /// load SAM/BAM index from filename
316     HtsIdx loadHtsIndex(string idxFile)
317     {
318         if(this.fp.format.format == htsExactFormat.bam || this.fp.format.format == htsExactFormat.cram)
319             this.idx = HtsIdx(sam_index_load2(this.fp, this.fn.s, toStringz(idxFile)));
320         else if(this.fp.format.format == htsExactFormat.bcf)
321             this.idx = HtsIdx(bcf_index_load2(this.fn.s, toStringz(idxFile)));
322         return this.idx;
323     }
324 
325     /// load tabix index
326     Tbx loadTabixIndex()
327     {
328         this.tbx = Tbx(tbx_index_load(this.fn.s));
329         return this.tbx;
330     }
331 
332     /// load tabix index from file
333     Tbx loadTabixIndex(T)(T idxFile)
334     if(isSomeString!T)
335     {
336         this.tbx = Tbx(tbx_index_load2(this.fn.s, toStringz(idxFile)));
337         return this.tbx;
338     }
339 
340     /// Query htsFile with tid, start, and end
341     /// returns an HtslibIterator that has type T
342     /// requires index be loaded first
343     /// can use Tabix index or BAI/CSI index
344     auto query(T)(int tid, long beg, long end)
345     if(is(T == Bam1) || is(T == Bcf1) || is(T == Kstring))
346     {
347         // if T == Kstring
348         // we assume using tabix index
349         static if(is(T == Kstring)){
350             if(this.tbx != null){
351                 auto itr = HtsItr(tbx_itr_queryi(this.tbx, tid, beg, end));
352                 return HtslibIterator!Kstring(this, itr);
353             }else{
354                 throw new Exception("No TABIX index is loaded");
355             }
356         }else{
357             // BAI/CSI Index
358             if(this.idx != null){
359                 static if(is(T == Bam1)){
360                     auto itr = HtsItr(sam_itr_queryi(this.idx, tid, beg, end));
361                     return HtslibIterator!Bam1(this, itr);
362                 }else static if(is(T == Bcf1)){
363                     auto itr = HtsItr(hts_itr_query(this.idx, tid, beg, end, &bcf_readrec));
364                     return HtslibIterator!Bcf1(this, itr);
365                 }
366             // Tabix Index
367             }else if(this.tbx != null){
368                 static if(is(T == Bam1)){
369                     auto itr = HtsItr(tbx_itr_queryi(this.tbx, tid, beg, end));
370                     return HtslibIterator!Bam1(this, itr);
371                 }else static if(is(T == Bcf1)){
372                     auto itr = HtsItr(tbx_itr_queryi(this.tbx, tid, beg, end));
373                     return HtslibIterator!Bcf1(this, itr);
374                 }
375             }else{
376                 throw new Exception("No TABIX/BAI/CSI index is loaded");
377             }
378         }
379         
380         
381     }
382 
383     /// Query htsFile with string region
384     /// returns an HtslibIterator that has type T
385     /// requires index and header be loaded first
386     /// can use Tabix index or BAI/CSI index
387     auto query(T)(string region)
388     if(is(T == Bam1) || is(T == Bcf1))
389     {
390         // BAI/CSI Index
391         if(this.idx != null){
392             static if(is(T == Bam1)){
393                 auto itr = HtsItr(sam_itr_querys(this.idx, this.bamHdr, toStringz(region)));
394                 return HtslibIterator!Bam1(this, itr);
395             }else static if(is(T == Bcf1)){
396                 auto itr = HtsItr(bcf_itr_querys(this.idx, this.bcfHdr, toStringz(region)));
397                 return HtslibIterator!Bcf1(this, itr);
398             }
399         // Tabix Index
400         }else if(this.tbx != null){
401             static if(is(T == Bam1)){
402                 auto itr = HtsItr(tbx_itr_querys(this.tbx, toStringz(region)));
403                 return HtslibIterator!Bam1(this, itr);
404             }else static if(is(T == Bcf1)){
405                 auto itr = HtsItr(tbx_itr_querys(this.tbx, toStringz(region)));
406                 return HtslibIterator!Bcf1(this, itr);
407             }else static if(is(T == Kstring)){
408                 auto itr = HtsItr(tbx_itr_querys(this.tbx, toStringz(region)));
409                 return HtslibIterator!Kstring(this, itr);
410             }
411         }else{
412             throw new Exception("No TABIX/BAI/CSI index is loaded");
413         }
414     }
415 
416     /// Query htsFile with array of regions
417     /// returns an HtslibIterator that has type Bam1
418     /// requires index and header be loaded first
419     auto query(string[] regions)
420     {
421         auto cQueries = regions.map!(toUTFz!(char *)).array;
422 
423         assert(this.idx.getRef != null);
424         auto itr = HtsItr(sam_itr_regarray(this.idx, this.bamHdr, cQueries.ptr, cast(int)regions.length));
425         return HtslibIterator!Bam1(this, itr);
426     }
427 
428     /// iterate over all records in a file
429     /// returns a HtslibIterator
430     auto byRecord(T)()
431     if(is(T == Bam1) || is(T == Bcf1) || is(T == Kstring))
432     {
433         return HtslibIterator!T(this);
434     }
435 
436     /// write SAM/BAM/VCF/BCF record, string, or ubyte data
437     /// requires the header be loaded if writing SAM/BAM/VCF/BCF record
438     void write(T)(T rec)
439     if(isSomeString!T || is(T == Bam1) || is(T == Bcf1) || is(T == Kstring) || is(T == ubyte[]))
440     {
441         long err;
442         static if(is(T == Bam1)){
443             err = sam_write1(this.fp, this.bamHdr, rec);
444         }else static if(is(T == Bcf1)){
445             err = bcf_write(this.fp, this.bcfHdr, rec);
446         }else static if(isSomeString!T || is(T == ubyte[])){
447             if(this.fp.is_bgzf) err = bgzf_write(this.fp.fp.bgzf, rec.ptr, rec.length);
448             else err = hwrite(this.fp.fp.hfile, rec.ptr, rec.length);
449         }else static if(is(T == Kstring)){
450             if(this.fp.is_bgzf) err = bgzf_write(this.fp.fp.bgzf, rec.s, rec.l);
451             else err = hwrite(this.fp.fp.hfile, rec.s, rec.l);
452         }else static assert(0);
453         if(err < 0) hts_log_error(__FUNCTION__, "Error writing SAM/BAM/VCF/BCF record, string, or ubyte data");
454     }
455 
456     /// write a string with a newline appended
457     void writeln(T)(T rec)
458     if(isSomeString!T)
459     {
460         rec = rec ~ '\n';
461         write(rec);
462     }
463 
464     /// read a BAM/SAM/BCF/VCF record
465     auto readRecord(T)()
466     if(is(T == Bam1) || is(T == Bcf1) || is(T == Kstring))
467     {
468         long err;
469         static if(is(T == Bam1)){
470             assert(this.bamHdr.initialized && this.bamHdr.getRef != null);
471             auto rec = bam_init1;
472             err = sam_read1(this.fp, this.bamHdr, rec);
473         }
474         else static if(is(T == Bcf1)){
475             assert(this.bcfHdr.initialized && this.bcfHdr.getRef != null);
476             auto rec = bcf_init;
477             err = bcf_read(this.fp, this.bcfHdr, rec);
478         }else static if(is(T == Kstring)){
479             auto rec = initKstring;
480             ks_initialize(rec);
481             err = hts_getline(this.fp, cast(int)'\n', rec);
482         }
483         if(err < -1) hts_log_error(__FUNCTION__, "Error reading Bam/Bcf record");
484         else if(err == -1) eof = true;
485         return T(rec);
486     }
487 
488 }
489 
490 debug(dhtslib_unittest) unittest
491 {
492     hts_log_info(__FUNCTION__, "Testing HtslibFile text reading and writing");
493     {
494         auto f = HtslibFile("/tmp/htslibfile.test.txt", HtslibFileWriteMode.Text);
495         f.writeln("hello");
496         f.writeln("test");
497 
498         f = HtslibFile("/tmp/htslibfile.test.txt.gz", HtslibFileWriteMode.GzippedText);
499         f.writeln("hello");
500         f.writeln("test");
501 
502         f = HtslibFile("/tmp/htslibfile.test.txt.bgz", HtslibFileWriteMode.BgzippedText);
503         f.writeln("hello");
504         f.writeln("test");
505 
506         f = HtslibFile("/tmp/htslibfile.test.txt.9gz", "w9");
507         f.writeln("hello");
508         f.writeln("test");
509     }
510     {
511         auto f = HtslibFile("/tmp/htslibfile.test.txt");
512         assert(fromStringz(f.readRecord!Kstring.s) == "hello");
513         assert(fromStringz(f.readRecord!Kstring.s) == "test");
514 
515         f = HtslibFile("/tmp/htslibfile.test.txt.gz");
516         assert(fromStringz(f.readRecord!Kstring.s) == "hello");
517         assert(fromStringz(f.readRecord!Kstring.s) == "test");
518 
519         f = HtslibFile("/tmp/htslibfile.test.txt.bgz");
520         assert(fromStringz(f.readRecord!Kstring.s) == "hello");
521         assert(fromStringz(f.readRecord!Kstring.s) == "test");
522 
523         f = HtslibFile("/tmp/htslibfile.test.txt.9gz");
524         assert(fromStringz(f.readRecord!Kstring.s) == "hello");
525         assert(fromStringz(f.readRecord!Kstring.s) == "test");
526 
527     }
528     {
529         auto f = HtslibFile("/tmp/htslibfile.test.txt.gz");
530         assert(fromStringz(f.readRecord!Kstring.s) == "hello");
531         assert(fromStringz(f.readRecord!Kstring.s) == "test");
532     }
533     {
534         auto f = HtslibFile("/tmp/htslibfile.test.txt");
535         assert(f.byRecord!Kstring.map!(x=> fromStringz(x.s).idup).array == ["hello", "test"] );
536     }
537 }
538 
539 debug(dhtslib_unittest) unittest
540 {
541     hts_log_info(__FUNCTION__, "Testing HtslibFile SAM/BAM reading and writing");
542     import std.path:buildPath,dirName;
543     auto fn = buildPath(dirName(dirName(dirName(dirName(__FILE__)))),"htslib","test","range.bam");
544     {
545         auto f = HtslibFile(fn);
546         f.loadHeader;
547         auto h = f.bamHdr;
548         auto read = f.readRecord!Bam1();
549 
550         f = HtslibFile("/tmp/htslibfile.test.sam", HtslibFileWriteMode.Sam);
551         f.setHeader(h);
552         f.writeHeader;
553         f.write(read);
554         
555         f = HtslibFile("/tmp/htslibfile.test.bam", HtslibFileWriteMode.Bam);
556         f.setHeader(h);
557         f.writeHeader;
558         f.write(read);
559 
560         f = HtslibFile("/tmp/htslibfile.test.ubam", HtslibFileWriteMode.UncompressedBam);
561         f.setHeader(h);
562         f.writeHeader;
563         f.write(read);
564 
565         f = HtslibFile("/tmp/htslibfile.test.sam.gz", HtslibFileWriteMode.GzippedSam);
566         f.setHeader(h);
567         f.writeHeader;
568         f.write(read);
569 
570         f = HtslibFile("/tmp/htslibfile.test.sam.bgz", HtslibFileWriteMode.BgzippedSam);
571         f.setHeader(h);
572         f.writeHeader;
573         f.write(read);
574 
575     }
576     {
577 
578         auto f = HtslibFile("/tmp/htslibfile.test.sam");
579         f.loadHeader;
580         auto h = f.bamHdr;
581         auto read = f.readRecord!Bam1();
582         assert(fromStringz(bam_get_qname(read)) == "HS18_09653:4:1315:19857:61712");
583         
584         f = HtslibFile("/tmp/htslibfile.test.bam");
585         f.loadHeader;
586         h = f.bamHdr;
587         read = f.readRecord!Bam1();
588         assert(fromStringz(bam_get_qname(read)) == "HS18_09653:4:1315:19857:61712");
589 
590         f = HtslibFile("/tmp/htslibfile.test.ubam");
591         f.loadHeader;
592         h = f.bamHdr;
593         read = f.readRecord!Bam1();
594         assert(fromStringz(bam_get_qname(read)) == "HS18_09653:4:1315:19857:61712");
595 
596         f = HtslibFile("/tmp/htslibfile.test.sam.gz");
597         f.loadHeader;
598         h = f.bamHdr;
599         read = f.readRecord!Bam1();
600         assert(fromStringz(bam_get_qname(read)) == "HS18_09653:4:1315:19857:61712");
601 
602         f = HtslibFile("/tmp/htslibfile.test.sam.bgz");
603         f.loadHeader;
604         h = f.bamHdr;
605         read = f.readRecord!Bam1();
606         assert(fromStringz(bam_get_qname(read)) == "HS18_09653:4:1315:19857:61712");
607     }
608 }
609 
610 debug(dhtslib_unittest) unittest
611 {
612     hts_log_info(__FUNCTION__, "Testing HtslibFile BCF reading and writing");
613     import std.path:buildPath,dirName;
614     auto fn = buildPath(dirName(dirName(dirName(dirName(__FILE__)))),"htslib","test","tabix","vcf_file.vcf.gz");
615     {
616         auto f = HtslibFile(fn);
617         f.loadHeader;
618         auto h = f.bcfHdr;
619         auto read = f.readRecord!Bcf1();
620 
621         f = HtslibFile("/tmp/htslibfile.test.vcf", HtslibFileWriteMode.Vcf);
622         f.setHeader(h);
623         f.writeHeader;
624         f.write(read);
625         
626         f = HtslibFile("/tmp/htslibfile.test.bcf", HtslibFileWriteMode.Bcf);
627         f.setHeader(h);
628         f.writeHeader;
629         f.write(read);
630 
631         f = HtslibFile("/tmp/htslibfile.test.ubcf", HtslibFileWriteMode.UncompressedBcf);
632         f.setHeader(h);
633         f.writeHeader;
634         f.write(read);
635 
636         f = HtslibFile("/tmp/htslibfile.test.vcf.gz", HtslibFileWriteMode.GzippedVcf);
637         f.setHeader(h);
638         f.writeHeader;
639         f.write(read);
640 
641         f = HtslibFile("/tmp/htslibfile.test.vcf.bgz", HtslibFileWriteMode.BgzippedVcf);
642         f.setHeader(h);
643         f.writeHeader;
644         f.write(read);
645 
646     }
647     {
648 
649         auto f = HtslibFile("/tmp/htslibfile.test.vcf");
650         f.loadHeader;
651         auto h = f.bcfHdr;
652         auto read = f.readRecord!Bcf1();
653         assert(read.pos == 3000149);
654         
655         f = HtslibFile("/tmp/htslibfile.test.bcf");
656         f.loadHeader;
657         h = f.bcfHdr;
658         read = f.readRecord!Bcf1();
659         assert(read.pos == 3000149);
660 
661         f = HtslibFile("/tmp/htslibfile.test.ubcf");
662         f.loadHeader;
663         h = f.bcfHdr;
664         read = f.readRecord!Bcf1();
665         assert(read.pos == 3000149);
666 
667         f = HtslibFile("/tmp/htslibfile.test.vcf.gz");
668         f.loadHeader;
669         h = f.bcfHdr;
670         read = f.readRecord!Bcf1();
671         assert(read.pos == 3000149);
672 
673         f = HtslibFile("/tmp/htslibfile.test.vcf.bgz");
674         f.loadHeader;
675         h = f.bcfHdr;
676         read = f.readRecord!Bcf1();
677         assert(read.pos == 3000149);
678     }
679 }