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 /// Explicit postblit to avoid 287 /// https://github.com/blachlylab/dhtslib/issues/122 288 this(this) 289 { 290 this.hdr = hdr; 291 } 292 293 /// copy this header 294 auto dup(){ 295 return VCFHeader(bcf_hdr_dup(this.hdr)); 296 } 297 298 /// List of contigs in the header 299 @property string[] sequences() 300 { 301 import core.stdc.stdlib : free; 302 int nseqs; 303 304 /** Creates a list of sequence names. It is up to the caller to free the list (but not the sequence names) */ 305 //const(char) **bcf_hdr_seqnames(const(bcf_hdr_t) *h, int *nseqs); 306 const(char*)*ary = bcf_hdr_seqnames(this.hdr, &nseqs); 307 if (!nseqs) return []; 308 309 string[] ret; 310 ret.reserve(nseqs); 311 312 for(int i; i < nseqs; i++) { 313 ret ~= fromStringz(ary[i]).idup; 314 } 315 316 free(cast(void*)ary); 317 return ret; 318 } 319 320 /// Number of samples in the header 321 pragma(inline, true) 322 @property int nsamples() { return bcf_hdr_nsamples(this.hdr); } 323 324 /// get int index of sample name 325 int getSampleId(string sam){ 326 auto ret = bcf_hdr_id2int(this.hdr, HeaderDictTypes.Sample, toUTFz!(char *)(sam)); 327 if(ret == -1) hts_log_error(__FUNCTION__, "Couldn't find sample in header: " ~ sam); 328 return ret; 329 } 330 331 /// get sample list 332 string[] getSamples(){ 333 auto samples = this.hdr.samples[0..this.nsamples]; 334 return samples.map!(x => fromStringz(x).idup).array; 335 } 336 337 // TODO 338 /// copy header lines from a template without overwiting existing lines 339 void copyHeaderLines(bcf_hdr_t *other) 340 { 341 assert(this.hdr != null); 342 assert(0); 343 // bcf_hdr_t *bcf_hdr_merge(bcf_hdr_t *dst, const(bcf_hdr_t) *src); 344 } 345 346 /// Add sample to this VCF 347 /// * int bcf_hdr_add_sample(bcf_hdr_t *hdr, const(char) *sample); 348 int addSample(string name) 349 in { assert(name != ""); } 350 do 351 { 352 assert(this.hdr != null); 353 354 bcf_hdr_add_sample(this.hdr, toStringz(name)); 355 356 // AARRRRGGGHHHH 357 // https://github.com/samtools/htslib/issues/767 358 bcf_hdr_sync(this.hdr); 359 360 return 0; 361 } 362 363 /** VCF version, e.g. VCFv4.2 */ 364 @property string vcfVersion() { return fromStringz( bcf_hdr_get_version(this.hdr) ).idup; } 365 366 /// Add a new header line 367 int addHeaderLineKV(string key, string value) 368 { 369 // TODO check that key is not Info, FILTER, FORMAT (or contig?) 370 string line = format("##%s=%s", key, value); 371 372 auto ret = bcf_hdr_append(this.hdr, toStringz(line)); 373 if(ret < 0) 374 hts_log_error(__FUNCTION__, "Couldn't add header line with key=%s and value =%s".format(key, value)); 375 auto notAdded = bcf_hdr_sync(this.hdr); 376 if(notAdded < 0) 377 hts_log_error(__FUNCTION__, "Couldn't add header line with key=%s and value =%s".format(key, value)); 378 return ret; 379 } 380 381 /// Add a new header line -- must be formatted ##key=value 382 int addHeaderLineRaw(string line) 383 { 384 assert(this.hdr != null); 385 // int bcf_hdr_append(bcf_hdr_t *h, const(char) *line); 386 const auto ret = bcf_hdr_append(this.hdr, toStringz(line)); 387 bcf_hdr_sync(this.hdr); 388 return ret; 389 } 390 391 /// Add a new header line using HeaderRecord 392 int addHeaderRecord(HeaderRecord rec) 393 { 394 assert(this.hdr != null); 395 auto ret = bcf_hdr_add_hrec(this.hdr, rec.convert(this.hdr)); 396 if(ret < 0) 397 hts_log_error(__FUNCTION__, "Couldn't add HeaderRecord"); 398 auto notAdded = bcf_hdr_sync(this.hdr); 399 if(notAdded != 0) 400 hts_log_error(__FUNCTION__, "Couldn't add HeaderRecord"); 401 return ret; 402 } 403 404 /// Remove all header lines of a particular type 405 void removeHeaderLines(HeaderRecordType linetype) 406 { 407 bcf_hdr_remove(this.hdr, linetype, null); 408 bcf_hdr_sync(this.hdr); 409 } 410 411 /// Remove a header line of a particular type with the key 412 void removeHeaderLines(HeaderRecordType linetype, string key) 413 { 414 bcf_hdr_remove(this.hdr, linetype, toStringz(key)); 415 bcf_hdr_sync(this.hdr); 416 } 417 418 /// get a header record via ID field 419 HeaderRecord getHeaderRecord(HeaderRecordType linetype, string id) 420 { 421 return this.getHeaderRecord(linetype, "ID", id); 422 } 423 424 /// get a header record via a string value pair 425 HeaderRecord getHeaderRecord(HeaderRecordType linetype, string key, string value) 426 { 427 auto rec = bcf_hdr_get_hrec(this.hdr, linetype, toUTFz!(const(char) *)(key),toUTFz!(const(char) *)(value), null); 428 if(!rec) throw new Exception("Record could not be found"); 429 auto ret = HeaderRecord(rec); 430 // bcf_hrec_destroy(rec); 431 return ret; 432 } 433 434 /// Add a filedate= headerline, which is not called out specifically in the spec, 435 /// but appears in the spec's example files. We could consider allowing a param here. 436 int addFiledate() 437 { 438 return addHeaderLineKV("filedate", (cast(Date) Clock.currTime()).toISOString ); 439 } 440 441 /** Add INFO (§1.2.2) or FORMAT (§1.2.4) tag 442 443 The INFO tag describes row-specific keys used in the INFO column; 444 The FORMAT tag describes sample-specific keys used in the last, and optional, genotype column. 445 446 Template parameter: string; must be INFO or FORMAT 447 448 The first four parameters are required; NUMBER and TYPE have specific allowable values. 449 source and version are optional, but recommended (for INFO only). 450 451 * id: ID tag 452 * number: NUMBER tag; here a string because it can also take special values {A,R,G,.} (see §1.2.2) 453 * type: Integer, Float, Flag, Character, and String 454 * description: Text description; will be double quoted 455 * source: Annotation source (eg dbSNP) 456 * version: Annotation version (eg 142) 457 */ 458 459 void addHeaderLine(HeaderRecordType lineType, T)(string id, T number, HeaderTypes type, 460 string description="", 461 string source="", 462 string _version="") 463 if((isIntegral!T || is(T == HeaderLengths)) && lineType != HeaderRecordType.None ) 464 { 465 HeaderRecord rec; 466 rec.setHeaderRecordType = lineType; 467 rec.setID(id); 468 rec.setLength(number); 469 rec.setValueType(type); 470 static if(lineType == HeaderRecordType.Info || lineType == HeaderRecordType.Filter || lineType == HeaderRecordType.FORMAT){ 471 if(description == ""){ 472 throw new Exception("description cannot be empty for " ~ HeaderRecordTypeStrings[lineType]); 473 } 474 } 475 rec.setDescription(description); 476 if(source != "") 477 rec["source"] = "\"%s\"".format(source); 478 if(_version != "") 479 rec["version"] = "\"%s\"".format(_version); 480 481 this.addHeaderRecord(rec); 482 } 483 484 /** Add FILTER tag (§1.2.3) */ 485 void addHeaderLine(HeaderRecordType lineType)(string id, string description) 486 if(lineType == HeaderRecordType.Filter) 487 { 488 HeaderRecord rec; 489 rec.setHeaderRecordType = lineType; 490 rec.setID(id); 491 rec.setDescription("\"%s\"".format(description)); 492 493 this.addHeaderRecord(rec); 494 } 495 496 /** Add FILTER tag (§1.2.3) */ 497 deprecated void addFilter(string id, string description) 498 { 499 addHeaderLine!(HeaderRecordType.Filter)(id, description); 500 } 501 502 /// string representation of header 503 string toString(){ 504 import htslib.kstring; 505 kstring_t s; 506 507 const int ret = bcf_hdr_format(this.hdr, 0, &s); 508 if (ret) 509 { 510 hts_log_error(__FUNCTION__, 511 format("bcf_hdr_format returned nonzero (%d) (likely EINVAL, invalid bcf_hdr_t struct?)", ret)); 512 return "[VCFHeader bcf_hdr_format parse_error]"; 513 } 514 515 return cast(string) s.s[0 .. s.l]; 516 } 517 } 518 519 /// 520 debug(dhtslib_unittest) 521 unittest 522 { 523 import std.exception: assertThrown; 524 import std.stdio: writeln, writefln; 525 526 hts_set_log_level(htsLogLevel.HTS_LOG_TRACE); 527 528 529 auto hdr = VCFHeader(bcf_hdr_init("w\0"c.ptr)); 530 531 hdr.addHeaderLineRaw("##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">"); 532 hdr.addHeaderLineKV("INFO", "<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">"); 533 // ##INFO=<ID=AF,Number=A,Type=Float,Description="Allele Frequency"> 534 hdr.addHeaderLine!(HeaderRecordType.Info)("AF", HeaderLengths.OnePerAltAllele, HeaderTypes.Integer, "Number of Samples With Data"); 535 hdr.addHeaderLineRaw("##contig=<ID=20,length=62435964,assembly=B36,md5=f126cdf8a6e0c7f379d618ff66beb2da,species=\"Homo sapiens\",taxonomy=x>"); // @suppress(dscanner.style.long_line) 536 hdr.addHeaderLineRaw("##FILTER=<ID=q10,Description=\"Quality below 10\">"); 537 538 539 // Exercise header 540 assert(hdr.nsamples == 0); 541 hdr.addSample("NA12878"); 542 assert(hdr.nsamples == 1); 543 assert(hdr.vcfVersion == "VCFv4.2"); 544 } 545 546 /// 547 debug(dhtslib_unittest) 548 unittest 549 { 550 import std.exception: assertThrown; 551 import std.stdio: writeln, writefln; 552 553 hts_set_log_level(htsLogLevel.HTS_LOG_TRACE); 554 555 556 auto hdr = VCFHeader(bcf_hdr_init("w\0"c.ptr)); 557 558 hdr.addHeaderLineRaw("##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">"); 559 hdr.addHeaderLineKV("INFO", "<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">"); 560 561 auto rec = hdr.getHeaderRecord(HeaderRecordType.Info,"ID","NS"); 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 569 assert(rec.idx == 1); 570 571 writeln(rec.toString); 572 573 574 rec = HeaderRecord(rec.convert(hdr.hdr)); 575 576 assert(rec.recType == HeaderRecordType.Info); 577 assert(rec.key == "INFO"); 578 assert(rec.nkeys == 4); 579 assert(rec.keys == ["ID", "Number", "Type", "Description"]); 580 assert(rec.vals == ["NS", "1", "Integer", "\"Number of Samples With Data\""]); 581 assert(rec["ID"] == "NS"); 582 // assert(rec["IDX"] == "1"); 583 // assert(rec.idx == 1); 584 585 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"ID","NS"); 586 587 assert(rec.recType == HeaderRecordType.Info); 588 assert(rec.getLength == "1"); 589 assert(rec.getValueType == HeaderTypes.Integer); 590 591 rec.idx = -1; 592 593 rec["ID"] = "NS2"; 594 595 hdr.addHeaderRecord(rec); 596 auto hdr2 = hdr.dup; 597 // writeln(hdr2.toString); 598 599 rec = hdr2.getHeaderRecord(HeaderRecordType.Info,"ID","NS2"); 600 assert(rec.recType == HeaderRecordType.Info); 601 assert(rec.key == "INFO"); 602 assert(rec.nkeys == 4); 603 assert(rec.keys == ["ID", "Number", "Type", "Description"]); 604 assert(rec.vals == ["NS2", "1", "Integer", "\"Number of Samples With Data\""]); 605 assert(rec["ID"] == "NS2"); 606 607 assert(rec.idx == 3); 608 609 rec = HeaderRecord.init; 610 rec.setHeaderRecordType(HeaderRecordType.Generic); 611 rec.key = "source"; 612 rec.value = "hello"; 613 hdr.addHeaderRecord(rec); 614 615 rec = hdr.getHeaderRecord(HeaderRecordType.Generic,"source","hello"); 616 assert(rec.recType == HeaderRecordType.Generic); 617 assert(rec.key == "source"); 618 assert(rec.value == "hello"); 619 assert(rec.nkeys == 0); 620 621 hdr.addHeaderLine!(HeaderRecordType.Filter)("nonsense","filter"); 622 623 rec = hdr.getHeaderRecord(HeaderRecordType.Filter,"ID","nonsense"); 624 assert(rec.recType == HeaderRecordType.Filter); 625 assert(rec.key == "FILTER"); 626 assert(rec.value == ""); 627 assert(rec.getID == "nonsense"); 628 assert(rec.idx == 4); 629 630 hdr.removeHeaderLines(HeaderRecordType.Filter); 631 632 auto expected = "##fileformat=VCFv4.2\n" ~ 633 "##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 634 "##INFO=<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">\n"~ 635 "##INFO=<ID=NS2,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 636 "##source=hello\n"~ 637 "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\n"; 638 assert(hdr.toString == expected); 639 640 rec = rec.init; 641 rec.setHeaderRecordType(HeaderRecordType.Contig); 642 rec.setID("test"); 643 rec["length"] = "5"; 644 645 hdr.addHeaderRecord(rec); 646 647 assert(hdr.sequences == ["test"]); 648 hdr.removeHeaderLines(HeaderRecordType.Generic, "source"); 649 hdr.addFilter("test","test"); 650 expected = "##fileformat=VCFv4.2\n" ~ 651 "##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 652 "##INFO=<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">\n"~ 653 "##INFO=<ID=NS2,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 654 "##contig=<ID=test,length=5>\n"~ 655 "##FILTER=<ID=test,Description=\"test\">\n"~ 656 "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\n"; 657 assert(hdr.toString == expected); 658 rec = hdr.getHeaderRecord(HeaderRecordType.Filter,"test"); 659 assert(rec.getDescription() == "\"test\""); 660 661 rec = HeaderRecord.init; 662 rec.setHeaderRecordType(HeaderRecordType.Info); 663 rec.setID("test"); 664 rec.setLength(HeaderLengths.OnePerGenotype); 665 rec.setValueType(HeaderTypes.Integer); 666 hdr.addHeaderRecord(rec); 667 668 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test"); 669 assert(rec.recType == HeaderRecordType.Info); 670 assert(rec.getLength == "G"); 671 assert(rec.getID == "test"); 672 assert(rec.getValueType == HeaderTypes.Integer); 673 674 rec = HeaderRecord.init; 675 rec.setHeaderRecordType(HeaderRecordType.Info); 676 rec.setID("test2"); 677 rec.setLength(HeaderLengths.OnePerAllele); 678 rec.setValueType(HeaderTypes.Integer); 679 hdr.addHeaderRecord(rec); 680 681 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test2"); 682 assert(rec.recType == HeaderRecordType.Info); 683 assert(rec.getLength == "R"); 684 assert(rec.getID == "test2"); 685 assert(rec.getValueType == HeaderTypes.Integer); 686 687 rec = HeaderRecord.init; 688 rec.setHeaderRecordType(HeaderRecordType.Info); 689 rec.setID("test3"); 690 rec.setLength(HeaderLengths.Variable); 691 rec.setValueType(HeaderTypes.Integer); 692 hdr.addHeaderRecord(rec); 693 694 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test3"); 695 assert(rec.recType == HeaderRecordType.Info); 696 assert(rec.getLength == "."); 697 assert(rec.getID == "test3"); 698 assert(rec.getValueType == HeaderTypes.Integer); 699 700 rec = HeaderRecord.init; 701 rec.setHeaderRecordType(HeaderRecordType.Info); 702 rec.setID("test4"); 703 rec.setLength(1); 704 rec.setValueType(HeaderTypes.Flag); 705 hdr.addHeaderRecord(rec); 706 707 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test4"); 708 assert(rec.recType == HeaderRecordType.Info); 709 assert(rec.getID == "test4"); 710 assert(rec.getValueType == HeaderTypes.Flag); 711 712 rec = HeaderRecord.init; 713 rec.setHeaderRecordType(HeaderRecordType.Info); 714 rec.setID("test5"); 715 rec.setLength(1); 716 rec.setValueType(HeaderTypes.Character); 717 hdr.addHeaderRecord(rec); 718 719 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test5"); 720 assert(rec.recType == HeaderRecordType.Info); 721 assert(rec.getLength == "1"); 722 assert(rec.getID == "test5"); 723 assert(rec.getValueType == HeaderTypes.Character); 724 725 rec = HeaderRecord.init; 726 rec.setHeaderRecordType(HeaderRecordType.Info); 727 rec.setID("test6"); 728 rec.setLength(HeaderLengths.Variable); 729 rec.setValueType(HeaderTypes.String); 730 hdr.addHeaderRecord(rec); 731 732 rec = hdr.getHeaderRecord(HeaderRecordType.Info,"test6"); 733 assert(rec.recType == HeaderRecordType.Info); 734 assert(rec.getLength == "."); 735 assert(rec.getID == "test6"); 736 assert(rec.getValueType == HeaderTypes.String); 737 738 expected = "##fileformat=VCFv4.2\n" ~ 739 "##INFO=<ID=NS,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 740 "##INFO=<ID=DP,Number=1,Type=Integer,Description=\"Total Depth\">\n"~ 741 "##INFO=<ID=NS2,Number=1,Type=Integer,Description=\"Number of Samples With Data\">\n"~ 742 "##contig=<ID=test,length=5>\n"~ 743 "##FILTER=<ID=test,Description=\"test\">\n"~ 744 "##INFO=<ID=test,Number=G,Type=Integer>\n"~ 745 "##INFO=<ID=test2,Number=R,Type=Integer>\n"~ 746 "##INFO=<ID=test3,Number=.,Type=Integer>\n"~ 747 "##INFO=<ID=test4,Number=1,Type=Flag>\n"~ 748 "##INFO=<ID=test5,Number=1,Type=Character>\n"~ 749 "##INFO=<ID=test6,Number=.,Type=String>\n"~ 750 "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\n"; 751 writeln(hdr.toString); 752 assert(hdr.toString == expected); 753 754 }