1 // Written in the D programming language. 2 3 /** 4 This is a submodule of $(MREF std, format). 5 6 It provides two functions for reading formatted input: $(LREF 7 unformatValue) and $(LREF formattedRead). The former reads a single 8 value. The latter reads several values at once and matches the 9 characters found between format specifiers. 10 11 Parameters are ignored, except for the ones consisting of a single 12 $(B '*'). See $(LREF formattedRead) for more information. 13 14 A space outside of a format specifier has a special meaning: it 15 matches any sequence of whitespace characters, not just a single 16 space. 17 18 The following combinations of format characters and types are 19 available: 20 21 $(BOOKTABLE , 22 $(TR $(TH) $(TH s) $(TH c) $(TH d, u, b, o, x, X) $(TH e, E, f, g, G) $(TH r) $(TH compound)) 23 $(TR $(TD `bool`) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) 24 $(TR $(TD `null`) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) 25 $(TR $(TD $(I integer)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH))) 26 $(TR $(TD $(I floating point)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH))) 27 $(TR $(TD $(I character)) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) 28 $(TR $(TD $(I string)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) 29 $(TR $(TD $(I array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) 30 $(TR $(TD $(I associative array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) 31 ) 32 33 Below are highlighted examples on how these combinations are used 34 with $(LREF unformatValue), however, they apply for $(LREF 35 formattedRead) also 36 37 Copyright: Copyright The D Language Foundation 2000-2013. 38 39 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 40 41 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, 42 Andrei Alexandrescu), and Kenji Hara 43 44 Source: $(PHOBOSSRC std/format/read.d) 45 */ 46 module std.format.read; 47 48 /// Booleans 49 @safe pure unittest 50 { 51 import std.format.spec : singleSpec; 52 53 auto str = "false"; 54 auto spec = singleSpec("%s"); 55 assert(str.unformatValue!bool(spec) == false); 56 57 str = "1"; 58 spec = singleSpec("%d"); 59 assert(str.unformatValue!bool(spec) == true); 60 } 61 62 /// Null values 63 @safe pure unittest 64 { 65 import std.format.spec : singleSpec; 66 67 auto str = "null"; 68 auto spec = singleSpec("%s"); 69 assert(str.unformatValue!(typeof(null))(spec) == null); 70 } 71 72 /// Integrals 73 @safe pure unittest 74 { 75 import std.format.spec : singleSpec; 76 77 // signed decimal values 78 auto str = "123"; 79 auto spec = singleSpec("%s"); 80 assert(str.unformatValue!int(spec) == 123); 81 82 // hexadecimal values 83 str = "ABC"; 84 spec = singleSpec("%X"); 85 assert(str.unformatValue!int(spec) == 2748); 86 87 // octal values 88 str = "11610"; 89 spec = singleSpec("%o"); 90 assert(str.unformatValue!int(spec) == 5000); 91 92 // raw read, depends on endianess 93 str = "\x75\x01"; 94 spec = singleSpec("%r"); 95 auto result = str.unformatValue!short(spec); 96 assert(result == 373 /* little endian */ || result == 29953 /* big endian */ ); 97 } 98 99 /// Floating point numbers 100 @safe pure unittest 101 { 102 import std.format.spec : singleSpec; 103 import std.math.operations : isClose; 104 105 // natural notation 106 auto str = "123.456"; 107 auto spec = singleSpec("%s"); 108 assert(str.unformatValue!double(spec).isClose(123.456)); 109 110 // scientific notation 111 str = "1e17"; 112 spec = singleSpec("%e"); 113 assert(str.unformatValue!double(spec).isClose(1e17)); 114 115 // raw read, depends on endianess 116 str = "\x40\x00\x00\xBF"; 117 spec = singleSpec("%r"); 118 auto result = str.unformatValue!float(spec); 119 assert(isClose(result, -0.5) /* little endian */ || isClose(result, 2.0) /* big endian */ ); 120 } 121 122 /// Characters 123 @safe pure unittest 124 { 125 import std.format.spec : singleSpec; 126 127 // only the first character is read 128 auto str = "abc"; 129 auto spec = singleSpec("%s"); 130 assert(str.unformatValue!char(spec) == 'a'); 131 132 // using a numerical format character treats the read number as unicode code point 133 str = "65"; 134 spec = singleSpec("%d"); 135 assert(str.unformatValue!char(spec) == 'A'); 136 137 str = "41"; 138 spec = singleSpec("%x"); 139 assert(str.unformatValue!char(spec) == 'A'); 140 141 str = "10003"; 142 spec = singleSpec("%d"); 143 assert(str.unformatValue!dchar(spec) == '✓'); 144 } 145 146 /// Arrays 147 @safe pure unittest 148 { 149 import std.format.spec : singleSpec; 150 151 // string value 152 string str = "aaa"; 153 auto spec = singleSpec("%s"); 154 assert(str.unformatValue!(dchar[])(spec) == "aaa"d); 155 156 // fixed size array with characters 157 str = "aaa"; 158 spec = singleSpec("%s"); 159 dchar[3] ret = ['a', 'a', 'a']; 160 assert(str.unformatValue!(dchar[3])(spec) == ret); 161 162 // dynamic array 163 str = "[1, 2, 3, 4]"; 164 spec = singleSpec("%s"); 165 assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]); 166 167 // fixed size array with integers 168 str = "[1, 2, 3, 4]"; 169 spec = singleSpec("%s"); 170 int[4] ret2 = [1, 2, 3, 4]; 171 assert(str.unformatValue!(int[4])(spec) == ret2); 172 173 // compound specifiers can be used for more control 174 str = "1,2,3"; 175 spec = singleSpec("%(%s,%)"); 176 assert(str.unformatValue!(int[])(spec) == [1, 2, 3]); 177 178 str = "cool"; 179 spec = singleSpec("%(%c%)"); 180 assert(str.unformatValue!(char[])(spec) == ['c', 'o', 'o', 'l']); 181 } 182 183 /// Associative arrays 184 @safe pure unittest 185 { 186 import std.format.spec : singleSpec; 187 188 // as single value 189 auto str = `["one": 1, "two": 2]`; 190 auto spec = singleSpec("%s"); 191 assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]); 192 193 // with compound specifier for more control 194 str = "1/1, 2/4, 3/9"; 195 spec = singleSpec("%(%d/%d%|, %)"); 196 assert(str.unformatValue!(int[int])(spec) == [1: 1, 2: 4, 3: 9]); 197 } 198 199 import std.format.spec : FormatSpec; 200 import std.format.internal.read; 201 import std.meta : allSatisfy; 202 import std.traits : isSomeString, isType; 203 204 /** 205 Reads an input range according to a format string and stores the read 206 values into its arguments. 207 208 Format specifiers with format character $(B 'd'), $(B 'u') and $(B 209 'c') can take a $(B '*') parameter for skipping values. 210 211 The second version of `formattedRead` takes the format string as 212 template argument. In this case, it is checked for consistency at 213 compile-time. 214 215 Params: 216 r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives), 217 where the formatted input is read from 218 fmt = a $(MREF_ALTTEXT format string, std,format) 219 args = a variadic list of arguments where the read values are stored 220 Range = the type of the input range `r` 221 Char = the character type used for `fmt` 222 Args = a variadic list of types of the arguments 223 224 Returns: 225 The number of variables filled. If the input range `r` ends early, 226 this number will be less than the number of variables provided. 227 228 Throws: 229 A $(REF_ALTTEXT FormatException, FormatException, std, format) 230 if reading did not succeed. 231 232 Note: 233 For backward compatibility the arguments `args` can be given as pointers 234 to that variable, but it is not recommended to do so, because this 235 option might be removed in the future. 236 */ 237 uint formattedRead(Range, Char, Args...)(auto ref Range r, const(Char)[] fmt, auto ref Args args) 238 { 239 import std.format : enforceFmt; 240 import std.range.primitives : empty; 241 import std.traits : isPointer; 242 import std.typecons : isTuple; 243 244 auto spec = FormatSpec!Char(fmt); 245 static if (!Args.length) 246 { 247 spec.readUpToNextSpec(r); 248 enforceFmt(spec.trailing.empty, "Trailing characters in formattedRead format string"); 249 return 0; 250 } 251 else 252 { 253 enum hasPointer = isPointer!(typeof(args[0])); 254 255 // The function below accounts for '*' == fields meant to be 256 // read and skipped 257 void skipUnstoredFields() 258 { 259 for (;;) 260 { 261 spec.readUpToNextSpec(r); 262 if (spec.width != spec.DYNAMIC) break; 263 // must skip this field 264 skipData(r, spec); 265 } 266 } 267 268 skipUnstoredFields(); 269 if (r.empty) 270 { 271 // Input is empty, nothing to read 272 return 0; 273 } 274 275 static if (hasPointer) 276 alias A = typeof(*args[0]); 277 else 278 alias A = typeof(args[0]); 279 280 static if (isTuple!A) 281 { 282 foreach (i, T; A.Types) 283 { 284 static if (hasPointer) 285 (*args[0])[i] = unformatValue!(T)(r, spec); 286 else 287 args[0][i] = unformatValue!(T)(r, spec); 288 skipUnstoredFields(); 289 } 290 } 291 else 292 { 293 static if (hasPointer) 294 *args[0] = unformatValue!(A)(r, spec); 295 else 296 args[0] = unformatValue!(A)(r, spec); 297 } 298 return 1 + formattedRead(r, spec.trailing, args[1 .. $]); 299 } 300 } 301 302 /// ditto 303 uint formattedRead(alias fmt, Range, Args...)(auto ref Range r, auto ref Args args) 304 if (!isType!fmt && isSomeString!(typeof(fmt))) 305 { 306 import std.format : checkFormatException; 307 import std.meta : staticMap; 308 import std.typecons : Tuple; 309 310 311 // formattedRead supports std.typecons.Tuple 312 // however, checkFormatException does not 313 // this means that all std.typecons.Tuple's types in Args must be unwrapped 314 // and passed to checkFormatException 315 template Flatten(T) 316 { 317 static if (is(T : Tuple!Args, Args...)) 318 alias Flatten = Args; 319 else 320 alias Flatten = T; 321 } 322 323 alias e = checkFormatException!(fmt, staticMap!(Flatten, Args)); 324 static assert(!e, e); 325 return .formattedRead(r, fmt, args); 326 } 327 328 /// 329 @safe pure unittest 330 { 331 string object; 332 char cmp; 333 int value; 334 335 assert(formattedRead("angle < 36", "%s %c %d", object, cmp, value) == 3); 336 assert(object == "angle"); 337 assert(cmp == '<'); 338 assert(value == 36); 339 340 // reading may end early: 341 assert(formattedRead("length >", "%s %c %d", object, cmp, value) == 2); 342 assert(object == "length"); 343 assert(cmp == '>'); 344 // value is not changed: 345 assert(value == 36); 346 } 347 348 /// The format string can be checked at compile-time: 349 @safe pure unittest 350 { 351 string a; 352 int b; 353 double c; 354 355 assert("hello!124:34.5".formattedRead!"%s!%s:%s"(a, b, c) == 3); 356 assert(a == "hello"); 357 assert(b == 124); 358 assert(c == 34.5); 359 } 360 361 /// Skipping values 362 @safe pure unittest 363 { 364 string item; 365 double amount; 366 367 assert("orange: (12%) 15.25".formattedRead("%s: (%*d%%) %f", item, amount) == 2); 368 assert(item == "orange"); 369 assert(amount == 15.25); 370 371 // can also be used with tuples 372 import std.typecons : Tuple; 373 374 Tuple!(int, float) t; 375 char[] line = "1 7643 2.125".dup; 376 formattedRead(line, "%s %*u %s", t); 377 assert(t[0] == 1 && t[1] == 2.125); 378 } 379 380 // https://issues.dlang.org/show_bug.cgi?id=23600 381 @safe pure unittest 382 { 383 import std.typecons : Tuple, tuple; 384 385 string h, w; 386 Tuple!(int, float) t; 387 388 assert("hello 1 2.34 world".formattedRead!"%s %d %f %s"(h, t, w) == 3); 389 assert(h == "hello"); 390 assert(t == tuple(1, 2.34f)); 391 assert(w == "world"); 392 } 393 394 @safe unittest 395 { 396 import std.math.operations : isClose; 397 import std.math.traits : isNaN; 398 import std.range.primitives : empty; 399 400 string s = " 1.2 3.4 "; 401 double x, y, z; 402 assert(formattedRead(s, " %s %s %s ", x, y, z) == 2); 403 assert(s.empty); 404 assert(isClose(x, 1.2)); 405 assert(isClose(y, 3.4)); 406 assert(isNaN(z)); 407 } 408 409 // for backwards compatibility 410 @safe pure unittest 411 { 412 string s = "hello!124:34.5"; 413 string a; 414 int b; 415 double c; 416 formattedRead(s, "%s!%s:%s", &a, &b, &c); 417 assert(a == "hello" && b == 124 && c == 34.5); 418 419 // mix pointers and auto-ref 420 s = "world!200:42.25"; 421 formattedRead(s, "%s!%s:%s", a, &b, &c); 422 assert(a == "world" && b == 200 && c == 42.25); 423 424 s = "world1!201:42.5"; 425 formattedRead(s, "%s!%s:%s", &a, &b, c); 426 assert(a == "world1" && b == 201 && c == 42.5); 427 428 s = "world2!202:42.75"; 429 formattedRead(s, "%s!%s:%s", a, b, &c); 430 assert(a == "world2" && b == 202 && c == 42.75); 431 } 432 433 // for backwards compatibility 434 @safe pure unittest 435 { 436 import std.math.operations : isClose; 437 import std.math.traits : isNaN; 438 import std.range.primitives : empty; 439 440 string s = " 1.2 3.4 "; 441 double x, y, z; 442 assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2); 443 assert(s.empty); 444 assert(isClose(x, 1.2)); 445 assert(isClose(y, 3.4)); 446 assert(isNaN(z)); 447 } 448 449 @safe unittest 450 { 451 string s = "hello!124:34.5"; 452 string a; 453 int b; 454 double c; 455 formattedRead(s, "%s!%s:%s", &a, &b, &c); 456 assert(a == "hello" && b == 124 && c == 34.5); 457 } 458 459 @safe pure unittest 460 { 461 string line; 462 463 bool f1; 464 465 line = "true"; 466 formattedRead(line, "%s", &f1); 467 assert(f1); 468 469 line = "TrUE"; 470 formattedRead(line, "%s", &f1); 471 assert(f1); 472 473 line = "false"; 474 formattedRead(line, "%s", &f1); 475 assert(!f1); 476 477 line = "fALsE"; 478 formattedRead(line, "%s", &f1); 479 assert(!f1); 480 481 line = "1"; 482 formattedRead(line, "%d", &f1); 483 assert(f1); 484 485 line = "-1"; 486 formattedRead(line, "%d", &f1); 487 assert(f1); 488 489 line = "0"; 490 formattedRead(line, "%d", &f1); 491 assert(!f1); 492 493 line = "-0"; 494 formattedRead(line, "%d", &f1); 495 assert(!f1); 496 } 497 498 @safe pure unittest 499 { 500 union B 501 { 502 char[int.sizeof] untyped; 503 int typed; 504 } 505 506 B b; 507 b.typed = 5; 508 char[] input = b.untyped[]; 509 int witness; 510 formattedRead(input, "%r", &witness); 511 assert(witness == b.typed); 512 } 513 514 @safe pure unittest 515 { 516 union A 517 { 518 char[float.sizeof] untyped; 519 float typed; 520 } 521 522 A a; 523 a.typed = 5.5; 524 char[] input = a.untyped[]; 525 float witness; 526 formattedRead(input, "%r", &witness); 527 assert(witness == a.typed); 528 } 529 530 @safe pure unittest 531 { 532 import std.typecons : Tuple; 533 534 char[] line = "1 2".dup; 535 int a, b; 536 formattedRead(line, "%s %s", &a, &b); 537 assert(a == 1 && b == 2); 538 539 line = "10 2 3".dup; 540 formattedRead(line, "%d ", &a); 541 assert(a == 10); 542 assert(line == "2 3"); 543 544 Tuple!(int, float) t; 545 line = "1 2.125".dup; 546 formattedRead(line, "%d %g", &t); 547 assert(t[0] == 1 && t[1] == 2.125); 548 549 line = "1 7643 2.125".dup; 550 formattedRead(line, "%s %*u %s", &t); 551 assert(t[0] == 1 && t[1] == 2.125); 552 } 553 554 @safe pure unittest 555 { 556 string line; 557 558 char c1, c2; 559 560 line = "abc"; 561 formattedRead(line, "%s%c", &c1, &c2); 562 assert(c1 == 'a' && c2 == 'b'); 563 assert(line == "c"); 564 } 565 566 @safe pure unittest 567 { 568 string line; 569 570 line = "[1,2,3]"; 571 int[] s1; 572 formattedRead(line, "%s", &s1); 573 assert(s1 == [1,2,3]); 574 } 575 576 @safe pure unittest 577 { 578 string line; 579 580 line = "[1,2,3]"; 581 int[] s1; 582 formattedRead(line, "[%(%s,%)]", &s1); 583 assert(s1 == [1,2,3]); 584 585 line = `["hello", "world"]`; 586 string[] s2; 587 formattedRead(line, "[%(%s, %)]", &s2); 588 assert(s2 == ["hello", "world"]); 589 590 line = "123 456"; 591 int[] s3; 592 formattedRead(line, "%(%s %)", &s3); 593 assert(s3 == [123, 456]); 594 595 line = "h,e,l,l,o; w,o,r,l,d"; 596 string[] s4; 597 formattedRead(line, "%(%(%c,%); %)", &s4); 598 assert(s4 == ["hello", "world"]); 599 } 600 601 @safe pure unittest 602 { 603 import std.exception : assertThrown; 604 605 string line; 606 607 int[4] sa1; 608 line = `[1,2,3,4]`; 609 formattedRead(line, "%s", &sa1); 610 assert(sa1 == [1,2,3,4]); 611 612 int[4] sa2; 613 line = `[1,2,3]`; 614 assertThrown(formattedRead(line, "%s", &sa2)); 615 616 int[4] sa3; 617 line = `[1,2,3,4,5]`; 618 assertThrown(formattedRead(line, "%s", &sa3)); 619 } 620 621 @safe pure unittest 622 { 623 import std.exception : assertThrown; 624 import std.format : FormatException; 625 626 string input; 627 628 int[4] sa1; 629 input = `[1,2,3,4]`; 630 formattedRead(input, "[%(%s,%)]", &sa1); 631 assert(sa1 == [1,2,3,4]); 632 633 int[4] sa2; 634 input = `[1,2,3]`; 635 assertThrown!FormatException(formattedRead(input, "[%(%s,%)]", &sa2)); 636 } 637 638 @safe pure unittest 639 { 640 string line; 641 642 string s1, s2; 643 644 line = "hello, world"; 645 formattedRead(line, "%s", &s1); 646 assert(s1 == "hello, world", s1); 647 648 line = "hello, world;yah"; 649 formattedRead(line, "%s;%s", &s1, &s2); 650 assert(s1 == "hello, world", s1); 651 assert(s2 == "yah", s2); 652 653 line = `['h','e','l','l','o']`; 654 string s3; 655 formattedRead(line, "[%(%s,%)]", &s3); 656 assert(s3 == "hello"); 657 658 line = `"hello"`; 659 string s4; 660 formattedRead(line, "\"%(%c%)\"", &s4); 661 assert(s4 == "hello"); 662 } 663 664 @safe pure unittest 665 { 666 string line; 667 668 string[int] aa1; 669 line = `[1:"hello", 2:"world"]`; 670 formattedRead(line, "%s", &aa1); 671 assert(aa1 == [1:"hello", 2:"world"]); 672 673 int[string] aa2; 674 line = `{"hello"=1; "world"=2}`; 675 formattedRead(line, "{%(%s=%s; %)}", &aa2); 676 assert(aa2 == ["hello":1, "world":2]); 677 678 int[string] aa3; 679 line = `{[hello=1]; [world=2]}`; 680 formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3); 681 assert(aa3 == ["hello":1, "world":2]); 682 } 683 684 // test rvalue using 685 @safe pure unittest 686 { 687 string[int] aa1; 688 formattedRead!("%s")(`[1:"hello", 2:"world"]`, aa1); 689 assert(aa1 == [1:"hello", 2:"world"]); 690 691 int[string] aa2; 692 formattedRead(`{"hello"=1; "world"=2}`, "{%(%s=%s; %)}", aa2); 693 assert(aa2 == ["hello":1, "world":2]); 694 } 695 696 /** 697 Reads an input range according to a format string and returns a tuple of Args 698 with the read values. 699 700 Format specifiers with format character $(B 'd'), $(B 'u') and $(B 701 'c') can take a $(B '*') parameter for skipping values. 702 703 The second version of `formattedRead` takes the format string as 704 template argument. In this case, it is checked for consistency at 705 compile-time. 706 707 Params: 708 Args = a variadic list of types of the arguments 709 */ 710 template formattedRead(Args...) 711 if (Args.length && allSatisfy!(isType, Args)) 712 { 713 import std.typecons : Tuple; 714 715 /** 716 Params: 717 r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives), 718 where the formatted input is read from 719 fmt = a $(MREF_ALTTEXT format string, std,format) 720 Range = the type of the input range `r` 721 Char = the character type used for `fmt` 722 723 Returns: 724 A Tuple!Args with the elements filled. 725 726 Throws: 727 A $(REF_ALTTEXT FormatException, FormatException, std, format) 728 if reading did not succeed. 729 */ 730 Tuple!Args formattedRead(Range, Char)(auto ref Range r, const(Char)[] fmt) 731 { 732 import core.lifetime : forward; 733 import std.format : enforceFmt; 734 735 Tuple!Args args; 736 const numArgsFilled = .formattedRead(forward!r, fmt, args.expand); 737 enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments"); 738 return args; 739 } 740 } 741 742 /// 743 @safe pure unittest 744 { 745 import std.exception : assertThrown; 746 import std.format : FormatException; 747 import std.typecons : tuple; 748 749 auto complete = "hello!34.5:124".formattedRead!(string, double, int)("%s!%s:%s"); 750 assert(complete == tuple("hello", 34.5, 124)); 751 752 // reading ends early 753 assertThrown!FormatException("hello!34.5:".formattedRead!(string, double, int)("%s!%s:%s")); 754 } 755 756 /// Skipping values 757 @safe pure unittest 758 { 759 import std.format : FormatException; 760 import std.typecons : tuple; 761 762 auto result = "orange: (12%) 15.25".formattedRead!(string, double)("%s: (%*d%%) %f"); 763 assert(result == tuple("orange", 15.25)); 764 } 765 766 /// ditto 767 template formattedRead(alias fmt, Args...) 768 if (!isType!fmt && isSomeString!(typeof(fmt)) && Args.length && allSatisfy!(isType, Args)) 769 { 770 import std.typecons : Flag, Tuple, Yes; 771 Tuple!Args formattedRead(Range)(auto ref Range r) 772 { 773 import core.lifetime : forward; 774 import std.format : enforceFmt; 775 776 Tuple!Args args; 777 const numArgsFilled = .formattedRead!fmt(forward!r, args.expand); 778 enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments"); 779 return args; 780 } 781 } 782 783 /// The format string can be checked at compile-time 784 @safe pure unittest 785 { 786 import std.exception : assertThrown; 787 import std.format : FormatException; 788 import std.typecons : tuple; 789 790 auto expected = tuple("hello", 124, 34.5); 791 auto result = "hello!124:34.5".formattedRead!("%s!%s:%s", string, int, double); 792 assert(result == expected); 793 794 assertThrown!FormatException("hello!34.5:".formattedRead!("%s!%s:%s", string, double, int)); 795 } 796 797 /// Compile-time consistency check 798 @safe pure unittest 799 { 800 import std.format : FormatException; 801 import std.typecons : tuple; 802 803 static assert(!__traits(compiles, "orange: (12%) 15.25".formattedRead!("%s: (%*d%%) %f", string, double))); 804 } 805 806 /** 807 Reads a value from the given _input range and converts it according to a 808 format specifier. 809 810 Params: 811 input = the $(REF_ALTTEXT input range, isInputRange, std, range, primitives), 812 to read from 813 spec = a $(MREF_ALTTEXT format string, std,format) 814 T = type to return 815 Range = the type of the input range `input` 816 Char = the character type used for `spec` 817 818 Returns: 819 A value from `input` of type `T`. 820 821 Throws: 822 A $(REF_ALTTEXT FormatException, FormatException, std, format) 823 if reading did not succeed. 824 825 See_Also: 826 $(REF parse, std, conv) and $(REF to, std, conv) 827 */ 828 T unformatValue(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 829 { 830 return unformatValueImpl!T(input, spec); 831 } 832 833 /// 834 @safe pure unittest 835 { 836 import std.format.spec : singleSpec; 837 838 string s = "42"; 839 auto spec = singleSpec("%s"); 840 assert(unformatValue!int(s, spec) == 42); 841 } 842 843 // https://issues.dlang.org/show_bug.cgi?id=7241 844 @safe pure unittest 845 { 846 string input = "a"; 847 auto spec = FormatSpec!char("%s"); 848 spec.readUpToNextSpec(input); 849 auto result = unformatValue!(dchar[1])(input, spec); 850 assert(result[0] == 'a'); 851 } 852 853 // https://issues.dlang.org/show_bug.cgi?id=20393 854 @safe pure unittest 855 { 856 import std.exception : assertThrown; 857 string str = "foo 12a-buzz"; 858 string a, c; 859 int b; 860 assertThrown(formattedRead(str, "%s %d-%s", &a, &b, &c)); 861 } 862 863 // https://issues.dlang.org/show_bug.cgi?id=18051 864 @safe pure unittest 865 { 866 import std.format : format; 867 868 enum Op { lt, gt, eq } 869 870 auto s = format!"%s"(Op.lt); 871 Op op; 872 assert(formattedRead!"%s"(s, op) == 1); 873 assert(op == Op.lt); 874 }