1 /** 2 3 This module provides structs that encapsulate VCFHeader and HeaderRecord 4 5 `VCFHeader` encapsulates and owns a `bcf_hdr_t*`, 6 and provides convenience functions to read and write header records. 7 8 `HeaderRecord` provides an easy way to coonstruct new header records and 9 convert them to bcf_hrec_t * for use by the htslib API. 10 11 */ 12 13 module dhtslib.vcf.header; 14 15 import std.datetime; 16 import std.string: fromStringz, toStringz; 17 import std.format: format; 18 import std.traits : isArray, isIntegral, isSomeString; 19 import std.conv: to, ConvException; 20 import std.algorithm : map; 21 import std.array : array; 22 import std.utf : toUTFz; 23 24 import dhtslib.memory; 25 import dhtslib.vcf; 26 import htslib.vcf; 27 import htslib.hts_log; 28 29 30 /// Struct for easy setting and getting of bcf_hrec_t values for VCFheader 31 struct HeaderRecord 32 { 33 /// HeaderRecordType type i.e INFO, contig, FORMAT 34 HeaderRecordType recType = HeaderRecordType.None; 35 36 /// string of HeaderRecordType type i.e INFO, contig, FORMAT ? 37 /// or could be ##source=program 38 /// ====== 39 string key; 40 41 /// mostly empty except for 42 /// this ##source=program 43 /// ======= 44 string value; 45 46 /// number kv pairs 47 int nkeys; 48 49 /// kv pair keys 50 string[] keys; 51 52 /// kv pair values 53 string[] vals; 54 55 /// HDR IDX value 56 int idx = -1; 57 58 /// HDR Length value A, R, G, ., FIXED 59 HeaderLengths lenthType = HeaderLengths.None; 60 61 /// if HDR Length value is FIXED 62 /// this is the number 63 int length = -1; 64 65 /// HDR Length value INT, FLOAT, STRING 66 HeaderTypes valueType = HeaderTypes.None; 67 68 invariant 69 { 70 assert(this.keys.length == this.vals.length); 71 } 72 /// ctor from a bcf_hrec_t 73 this(bcf_hrec_t * rec){ 74 75 /// Set the easy stuff 76 this.recType = cast(HeaderRecordType) rec.type; 77 this.key = fromStringz(rec.key).dup; 78 this.value = fromStringz(rec.value).dup; 79 this.nkeys = rec.nkeys; 80 81 /// get the kv pairs 82 /// special logic for Number and Type 83 for(auto i=0; i < rec.nkeys; i++){ 84 keys ~= fromStringz(rec.keys[i]).dup; 85 vals ~= fromStringz(rec.vals[i]).dup; 86 if(keys[i] == "Number") 87 { 88 switch(vals[i]){ 89 case "A": 90 this.lenthType = HeaderLengths.OnePerAltAllele; 91 break; 92 case "G": 93 this.lenthType = HeaderLengths.OnePerGenotype; 94 break; 95 case "R": 96 this.lenthType = HeaderLengths.OnePerAllele; 97 break; 98 case ".": 99 this.lenthType = HeaderLengths.Variable; 100 break; 101 default: 102 this.lenthType = HeaderLengths.Fixed; 103 this.length = vals[i].to!int; 104 break; 105 } 106 } 107 if(keys[i] == "Type") 108 { 109 switch(vals[i]){ 110 case "Flag": 111 this.valueType = HeaderTypes.Flag; 112 break; 113 case "Integer": 114 this.valueType = HeaderTypes.Integer; 115 break; 116 case "Float": 117 this.valueType = HeaderTypes.Float; 118 break; 119 case "Character": 120 this.valueType = HeaderTypes.Character; 121 break; 122 case "String": 123 this.valueType = HeaderTypes.String; 124 break; 125 default: 126 throw new Exception(vals[i]~" is not a know BCF Header Type"); 127 } 128 } 129 if(keys[i] == "IDX"){ 130 this.nkeys--; 131 this.idx = this.vals[$-1].to!int; 132 this.keys = this.keys[0..$-1]; 133 this.vals = this.vals[0..$-1]; 134 } 135 136 } 137 } 138 139 /// set Record Type i.e INFO, FORMAT ... 140 void setHeaderRecordType(HeaderRecordType line) 141 { 142 this.recType = line; 143 this.key = HeaderRecordTypeStrings[line]; 144 } 145 146 /// get Record Type i.e INFO, FORMAT ... 147 HeaderRecordType getHeaderRecordType() 148 { 149 return this.recType; 150 } 151 152 /// set Value Type length with integer 153 void setLength(T)(T number) 154 if(isIntegral!T) 155 { 156 this.lenthType = HeaderLengths.Fixed; 157 this["Number"] = number.to!string; 158 } 159 160 /// set Value Type length i.e A, R, G, . 161 void setLength(HeaderLengths number) 162 { 163 this.lenthType = number; 164 this["Number"] = HeaderLengthsStrings[number]; 165 } 166 167 /// get Value Type length 168 string getLength() 169 { 170 return this["Number"]; 171 } 172 173 /// set Value Type i.e Integer, String, Float 174 void setValueType(HeaderTypes type) 175 { 176 this.valueType = type; 177 this["Type"] = HeaderTypesStrings[type]; 178 } 179 180 /// get Value Type i.e Integer, String, Float 181 HeaderTypes getValueType() 182 { 183 return this.valueType; 184 } 185 186 /// set ID field 187 void setID(string id) 188 { 189 this["ID"] = id; 190 } 191 192 /// get ID field 193 string getID() 194 { 195 return this["ID"]; 196 } 197 198 /// set Description field 199 void setDescription(string des) 200 { 201 this["Description"] = des; 202 } 203 204 /// get Description field 205 string getDescription() 206 { 207 return this["Description"]; 208 } 209 210 /// get a value from the KV pairs 211 /// if key isn't present thows exception 212 ref auto opIndex(string index) 213 { 214 foreach (i, string key; keys) 215 { 216 if(key == index){ 217 return vals[i]; 218 } 219 } 220 throw new Exception("Key " ~ index ~" not found"); 221 } 222 223 /// set a value from the KV pairs 224 /// if key isn't present a new KV pair is 225 /// added 226 void opIndexAssign(string value, string index) 227 { 228 foreach (i, string key; keys) 229 { 230 if(key == index){ 231 vals[i] = value; 232 return; 233 } 234 } 235 this.nkeys++; 236 keys~=index; 237 vals~=value; 238 } 239 240 /// convert to bcf_hrec_t for use with htslib functions 241 bcf_hrec_t * convert(bcf_hdr_t * hdr) 242 { 243 if(this.recType == HeaderRecordType.Info || this.recType == HeaderRecordType.Format){ 244 assert(this.valueType != HeaderTypes.None); 245 assert(this.lenthType != HeaderLengths.None); 246 } 247 248 auto str = this.toString; 249 int parsed; 250 auto rec = bcf_hdr_parse_line(hdr, toUTFz!(char *)(str), &parsed); 251 rec.type = this.recType; 252 return rec; 253 } 254 255 /// print a string representation of the header record 256 string toString() 257 { 258 string ret = "##" ~ this.key ~ "=" ~ this.value; 259 if(this.nkeys > 0){ 260 ret ~= "<"; 261 for(auto i =0; i < this.nkeys - 1; i++) 262 { 263 ret ~= this.keys[i] ~ "=" ~ this.vals[i] ~ ", "; 264 } 265 ret ~= this.keys[$-1] ~ "=" ~ this.vals[$-1] ~ ">"; 266 } 267 268 return ret; 269 } 270 } 271 272 /** VCFHeader encapsulates `bcf_hdr_t*` 273 and provides convenience wrappers to manipulate the header metadata/records. 274 */ 275 struct VCFHeader 276 { 277 /// Pointer to htslib BCF/VCF header struct; will be freed from VCFHeader dtor 278 BcfHdr hdr; 279 280 /// pointer ctor 281 this(bcf_hdr_t * h) 282 { 283 this.hdr = BcfHdr(h); 284 } 285 286 /// copy this header 287 auto dup(){ 288 return VCFHeader(bcf_hdr_dup(this.hdr)); 289 } 290 291 /// List of contigs in the header 292 @property string[] sequences() 293 { 294 import core.stdc.stdlib : free; 295 int nseqs; 296 297 /** Creates a list of sequence names. It is up to the caller to free the list (but not the sequence names) */ 298 //const(char) **bcf_hdr_seqnames(const(bcf_hdr_t) *h, int *nseqs); 299 const(char*)*ary = bcf_hdr_seqnames(this.hdr, &nseqs); 300 if (!nseqs) return []; 301 302 string[] ret; 303 ret.reserve(nseqs); 304 305 for(int i; i < nseqs; i++) { 306 ret ~= fromStringz(ary[i]).idup; 307 } 308 309 free(cast(void*)ary); 310 return ret; 311 } 312 313 /// Number of samples in the header 314 pragma(inline, true) 315 @property int nsamples() { return bcf_hdr_nsamples(this.hdr); } 316 317 int getSampleId(string sam){ 318 auto ret = bcf_hdr_id2int(this.hdr, HeaderDictTypes.Sample, toUTFz!(char *)(sam)); 319 if(ret == -1) hts_log_error(__FUNCTION__, "Couldn't find sample in header: " ~ sam); 320 return ret; 321 } 322 323 // TODO 324 /// copy header lines from a template without overwiting existing lines 325 void copyHeaderLines(bcf_hdr_t *other) 326 { 327 assert(this.hdr != null); 328 assert(0); 329 // bcf_hdr_t *bcf_hdr_merge(bcf_hdr_t *dst, const(bcf_hdr_t) *src); 330 } 331 332 /// Add sample to this VCF 333 /// * int bcf_hdr_add_sample(bcf_hdr_t *hdr, const(char) *sample); 334 int addSample(string name) 335 in { assert(name != ""); } 336 do 337 { 338 assert(this.hdr != null); 339 340 bcf_hdr_add_sample(this.hdr, toStringz(name)); 341 342 // AARRRRGGGHHHH 343 // https://github.com/samtools/htslib/issues/767 344 bcf_hdr_sync(this.hdr); 345 346 return 0; 347 } 348 349 /** VCF version, e.g. VCFv4.2 */ 350 @property string vcfVersion() { return fromStringz( bcf_hdr_get_version(this.hdr) ).idup; } 351 352 /// Add a new header line 353 int addHeaderLineKV(string key, string value) 354 { 355 // TODO check that key is not Info, FILTER, FORMAT (or contig?) 356 string line = format("##%s=%s", key, value); 357 358 auto ret = bcf_hdr_append(this.hdr, toStringz(line)); 359 if(ret < 0) 360 hts_log_error(__FUNCTION__, "Couldn't add header line with key=%s and value =%s".format(key, value)); 361 auto notAdded = bcf_hdr_sync(this.hdr); 362 if(notAdded < 0) 363 hts_log_error(__FUNCTION__, "Couldn't add header line with key=%s and value =%s".format(key, value)); 364 return ret; 365 } 366 367 /// Add a new header line -- must be formatted ##key=value 368 int addHeaderLineRaw(string line) 369 { 370 assert(this.hdr != null); 371 // int bcf_hdr_append(bcf_hdr_t *h, const(char) *line); 372 const auto ret = bcf_hdr_append(this.hdr, toStringz(line)); 373 bcf_hdr_sync(this.hdr); 374 return ret; 375 } 376 377 /// Add a new header line using HeaderRecord 378 int addHeaderRecord(HeaderRecord rec) 379 { 380 assert(this.hdr != null); 381 auto ret = bcf_hdr_add_hrec(this.hdr, rec.convert(this.hdr)); 382 if(ret < 0) 383 hts_log_error(__FUNCTION__, "Couldn't add HeaderRecord"); 384 auto notAdded = bcf_hdr_sync(this.hdr); 385 if(notAdded != 0) 386 hts_log_error(__FUNCTION__, "Couldn't add HeaderRecord"); 387 return ret; 388 } 389 390 /// Remove all header lines of a particular type 391 void removeHeaderLines(HeaderRecordType linetype) 392 { 393 bcf_hdr_remove(this.hdr, linetype, null); 394 bcf_hdr_sync(this.hdr); 395 } 396 397 /// Remove a header line of a particular type with the key 398 void removeHeaderLines(HeaderRecordType linetype, string key) 399 { 400 bcf_hdr_remove(this.hdr, linetype, toStringz(key)); 401 bcf_hdr_sync(this.hdr); 402 } 403 404 /// get a header record via ID field 405 HeaderRecord getHeaderRecord(HeaderRecordType linetype, string id) 406 { 407 return this.getHeaderRecord(linetype, "ID", id); 408 } 409 410 /// get a header record via a string value pair 411 HeaderRecord getHeaderRecord(HeaderRecordType linetype, string key, string value) 412 { 413 auto rec = bcf_hdr_get_hrec(this.hdr, linetype, toUTFz!(const(char) *)(key),toUTFz!(const(char) *)(value), null); 414 if(!rec) throw new Exception("Record could not be found"); 415 auto ret = HeaderRecord(rec); 416 // bcf_hrec_destroy(rec); 417 return ret; 418 } 419 420 /// Add a filedate= headerline, which is not called out specifically in the spec, 421 /// but appears in the spec's example files. We could consider allowing a param here. 422 int addFiledate() 423 { 424 return addHeaderLineKV("filedate", (cast(Date) Clock.currTime()).toISOString ); 425 } 426 427 /** Add INFO (§1.2.2) or FORMAT (§1.2.4) tag 428 429 The INFO tag describes row-specific keys used in the INFO column; 430 The FORMAT tag describes sample-specific keys used in the last, and optional, genotype column. 431 432 Template parameter: string; must be INFO or FORMAT 433 434 The first four parameters are required; NUMBER and TYPE have specific allowable values. 435 source and version are optional, but recommended (for INFO only). 436 437 * id: ID tag 438 * number: NUMBER tag; here a string because it can also take special values {A,R,G,.} (see §1.2.2) 439 * type: Integer, Float, Flag, Character, and String 440 * description: Text description; will be double quoted 441 * source: Annotation source (eg dbSNP) 442 * version: Annotation version (eg 142) 443 */ 444 445 void addHeaderLine(HeaderRecordType lineType, T)(string id, T number, HeaderTypes type, 446 string description="", 447 string source="", 448 string _version="") 449 if((isIntegral!T || is(T == HeaderLengths)) && lineType != HeaderRecordType.None ) 450 { 451 HeaderRecord rec; 452 rec.setHeaderRecordType = lineType; 453 rec.setID(id); 454 rec.setLength(number); 455 rec.setValueType(type); 456 static if(lineType == HeaderRecordType.Info || lineType == HeaderRecordType.Filter || lineType == HeaderRecordType.FORMAT){ 457 if(description == ""){ 458 throw new Exception("description cannot be empty for " ~ HeaderRecordTypeStrings[lineType]); 459 } 460 } 461 rec.setDescription(description); 462 if(source != "") 463 rec["source"] = "\"%s\"".format(source); 464 if(_version != "") 465 rec["version"] = "\"%s\"".format(_version); 466 467 this.addHeaderRecord(rec); 468 } 469 470 /** Add FILTER tag (§1.2.3) */ 471 void addHeaderLine(HeaderRecordType lineType)(string id, string description) 472 if(lineType == HeaderRecordType.Filter) 473 { 474 HeaderRecord rec; 475 rec.setHeaderRecordType = lineType; 476 rec.setID(id); 477 rec.setDescription("\"%s\"".format(description)); 478 479 this.addHeaderRecord(rec); 480 } 481 482 /** Add FILTER tag (§1.2.3) */ 483 deprecated void addFilter(string id, string description) 484 { 485 addHeaderLine!(HeaderRecordType.Filter)(id, description); 486 } 487 488 /// string representation of header 489 string toString(){ 490 import htslib.kstring; 491 kstring_t s; 492 493 const int ret = bcf_hdr_format(this.hdr, 0, &s); 494 if (ret) 495 { 496 hts_log_error(__FUNCTION__, 497 format("bcf_hdr_format returned nonzero (%d) (likely EINVAL, invalid bcf_hdr_t struct?)", ret)); 498 return "[VCFHeader bcf_hdr_format parse_error]"; 499 } 500 501 return cast(string) s.s[0 .. s.l]; 502 } 503 } 504 505 /// 506 debug(dhtslib_unittest) 507 unittest 508 { 509 import std.exception: assertThrown; 510 import std.stdio: writeln, writefln; 511 512 hts_set_log_level(htsLogLevel.HTS_LOG_TRACE); 513 514 515 auto hdr = VCFHeader(bcf_hdr_init("w\0"c.ptr)); 516 517 hdr.addHeaderLineRaw("##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">"); 518 hdr.addHeaderLineKV("INFO", "<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">"); 519 // ##INFO=<ID=AF,Number=A,Type=Float,Description="Allele Frequency"> 520 hdr.addHeaderLine!(HeaderRecordType.Info)("AF", HeaderLengths.OnePerAltAllele, HeaderTypes.Integer, "Number of Samples With Data"); 521 hdr.addHeaderLineRaw("##contig=<ID=20,length=62435964,assembly=B36,md5=f126cdf8a6e0c7f379d618ff66beb2da,species=\"Homo sapiens\",taxonomy=x>"); // @suppress(dscanner.style.long_line) 522 hdr.addHeaderLineRaw("##FILTER=<ID=q10,Description=\"Quality below 10\">"); 523 524 525 // Exercise header 526 assert(hdr.nsamples == 0); 527 hdr.addSample("NA12878"); 528 assert(hdr.nsamples == 1); 529 assert(hdr.vcfVersion == "VCFv4.2"); 530 } 531 532 /// 533 debug(dhtslib_unittest) 534 unittest 535 { 536 import std.exception: assertThrown; 537 import std.stdio: writeln, writefln; 538 539 hts_set_log_level(htsLogLevel.HTS_LOG_TRACE); 540 541 542 auto hdr = VCFHeader(bcf_hdr_init("w\0"c.ptr)); 543 544 hdr.addHeaderLineRaw("##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">"); 545 hdr.addHeaderLineKV("INFO", "<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">"); 546 547 auto rec = hdr.getHeaderRecord(HeaderRecordType.Info,"ID","NS"); 548 assert(rec.recType == HeaderRecordType.Info); 549 assert(rec.key == "INFO"); 550 assert(rec.nkeys == 4); 551 assert(rec.keys == ["ID", "Number", "Type", "Description"]); 552 assert(rec.vals == ["NS", "1", "Integer", "\"Number of Samples With Data\""]); 553 assert(rec["ID"] == "NS"); 554 555 assert(rec.idx == 1); 556 557 writeln(rec.toString); 558 559 560 rec = HeaderRecord(rec.convert(hdr.hdr)); 561 562 assert(rec.recType == HeaderRecordType.Info); 563 assert(rec.key == "INFO"); 564 assert(rec.nkeys == 4); 565 assert(rec.keys == ["ID", "Number", "Type", "Description"]); 566 assert(rec.vals == ["NS", "1", "Integer", "\"Number of Samples With Data\""]); 567 assert(rec["ID"] == "NS"); 568 // assert(rec["IDX"] == "1"); 569 // assert(rec.idx == 1); 570 571 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"ID","NS"); 572 573 assert(rec.recType == HeaderRecordType.Info); 574 assert(rec.getLength == "1"); 575 assert(rec.getValueType == HeaderTypes.Integer); 576 577 rec.idx = -1; 578 579 rec["ID"] = "NS2"; 580 581 hdr.addHeaderRecord(rec); 582 auto hdr2 = hdr.dup; 583 // writeln(hdr2.toString); 584 585 rec = hdr2.getHeaderRecord(HeaderRecordType.Info,"ID","NS2"); 586 assert(rec.recType == HeaderRecordType.Info); 587 assert(rec.key == "INFO"); 588 assert(rec.nkeys == 4); 589 assert(rec.keys == ["ID", "Number", "Type", "Description"]); 590 assert(rec.vals == ["NS2", "1", "Integer", "\"Number of Samples With Data\""]); 591 assert(rec["ID"] == "NS2"); 592 593 assert(rec.idx == 3); 594 595 rec = HeaderRecord.init; 596 rec.setHeaderRecordType(HeaderRecordType.Generic); 597 rec.key = "source"; 598 rec.value = "hello"; 599 hdr.addHeaderRecord(rec); 600 601 rec = hdr.getHeaderRecord(HeaderRecordType.Generic,"source","hello"); 602 assert(rec.recType == HeaderRecordType.Generic); 603 assert(rec.key == "source"); 604 assert(rec.value == "hello"); 605 assert(rec.nkeys == 0); 606 607 hdr.addHeaderLine!(HeaderRecordType.Filter)("nonsense","filter"); 608 609 rec = hdr.getHeaderRecord(HeaderRecordType.Filter,"ID","nonsense"); 610 assert(rec.recType == HeaderRecordType.Filter); 611 assert(rec.key == "FILTER"); 612 assert(rec.value == ""); 613 assert(rec.getID == "nonsense"); 614 assert(rec.idx == 4); 615 616 hdr.removeHeaderLines(HeaderRecordType.Filter); 617 618 auto expected = "##fileformat=VCFv4.2\n" ~ 619 "##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 620 "##INFO=<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">\n"~ 621 "##INFO=<ID=NS2,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 622 "##source=hello\n"~ 623 "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\n"; 624 assert(hdr.toString == expected); 625 626 rec = rec.init; 627 rec.setHeaderRecordType(HeaderRecordType.Contig); 628 rec.setID("test"); 629 rec["length"] = "5"; 630 631 hdr.addHeaderRecord(rec); 632 633 assert(hdr.sequences == ["test"]); 634 hdr.removeHeaderLines(HeaderRecordType.Generic, "source"); 635 hdr.addFilter("test","test"); 636 expected = "##fileformat=VCFv4.2\n" ~ 637 "##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 638 "##INFO=<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">\n"~ 639 "##INFO=<ID=NS2,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 640 "##contig=<ID=test,length=5>\n"~ 641 "##FILTER=<ID=test,Description=\"test\">\n"~ 642 "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\n"; 643 assert(hdr.toString == expected); 644 rec = hdr.getHeaderRecord(HeaderRecordType.Filter,"test"); 645 assert(rec.getDescription() == "\"test\""); 646 647 rec = HeaderRecord.init; 648 rec.setHeaderRecordType(HeaderRecordType.Info); 649 rec.setID("test"); 650 rec.setLength(HeaderLengths.OnePerGenotype); 651 rec.setValueType(HeaderTypes.Integer); 652 hdr.addHeaderRecord(rec); 653 654 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test"); 655 assert(rec.recType == HeaderRecordType.Info); 656 assert(rec.getLength == "G"); 657 assert(rec.getID == "test"); 658 assert(rec.getValueType == HeaderTypes.Integer); 659 660 rec = HeaderRecord.init; 661 rec.setHeaderRecordType(HeaderRecordType.Info); 662 rec.setID("test2"); 663 rec.setLength(HeaderLengths.OnePerAllele); 664 rec.setValueType(HeaderTypes.Integer); 665 hdr.addHeaderRecord(rec); 666 667 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test2"); 668 assert(rec.recType == HeaderRecordType.Info); 669 assert(rec.getLength == "R"); 670 assert(rec.getID == "test2"); 671 assert(rec.getValueType == HeaderTypes.Integer); 672 673 rec = HeaderRecord.init; 674 rec.setHeaderRecordType(HeaderRecordType.Info); 675 rec.setID("test3"); 676 rec.setLength(HeaderLengths.Variable); 677 rec.setValueType(HeaderTypes.Integer); 678 hdr.addHeaderRecord(rec); 679 680 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test3"); 681 assert(rec.recType == HeaderRecordType.Info); 682 assert(rec.getLength == "."); 683 assert(rec.getID == "test3"); 684 assert(rec.getValueType == HeaderTypes.Integer); 685 686 rec = HeaderRecord.init; 687 rec.setHeaderRecordType(HeaderRecordType.Info); 688 rec.setID("test4"); 689 rec.setLength(1); 690 rec.setValueType(HeaderTypes.Flag); 691 hdr.addHeaderRecord(rec); 692 693 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test4"); 694 assert(rec.recType == HeaderRecordType.Info); 695 assert(rec.getID == "test4"); 696 assert(rec.getValueType == HeaderTypes.Flag); 697 698 rec = HeaderRecord.init; 699 rec.setHeaderRecordType(HeaderRecordType.Info); 700 rec.setID("test5"); 701 rec.setLength(1); 702 rec.setValueType(HeaderTypes.Character); 703 hdr.addHeaderRecord(rec); 704 705 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test5"); 706 assert(rec.recType == HeaderRecordType.Info); 707 assert(rec.getLength == "1"); 708 assert(rec.getID == "test5"); 709 assert(rec.getValueType == HeaderTypes.Character); 710 711 rec = HeaderRecord.init; 712 rec.setHeaderRecordType(HeaderRecordType.Info); 713 rec.setID("test6"); 714 rec.setLength(HeaderLengths.Variable); 715 rec.setValueType(HeaderTypes.String); 716 hdr.addHeaderRecord(rec); 717 718 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test6"); 719 assert(rec.recType == HeaderRecordType.Info); 720 assert(rec.getLength == "."); 721 assert(rec.getID == "test6"); 722 assert(rec.getValueType == HeaderTypes.String); 723 724 expected = "##fileformat=VCFv4.2\n" ~ 725 "##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 726 "##INFO=<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">\n"~ 727 "##INFO=<ID=NS2,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 728 "##contig=<ID=test,length=5>\n"~ 729 "##FILTER=<ID=test,Description=\"test\">\n"~ 730 "##INFO=<ID=test,Number=G,Type=Integer>\n"~ 731 "##INFO=<ID=test2,Number=R,Type=Integer>\n"~ 732 "##INFO=<ID=test3,Number=.,Type=Integer>\n"~ 733 "##INFO=<ID=test4,Number=1,Type=Flag>\n"~ 734 "##INFO=<ID=test5,Number=1,Type=Character>\n"~ 735 "##INFO=<ID=test6,Number=.,Type=String>\n"~ 736 "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\n"; 737 writeln(hdr.toString); 738 assert(hdr.toString == expected); 739 740 }