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 @safe pure unittest 381 { 382 string hello; 383 string world; 384 385 assert("hello ignore world".formattedRead("%s %*s %s", hello, world) == 2); 386 assert(hello == "hello"); 387 assert(world == "world"); 388 } 389 390 // https://issues.dlang.org/show_bug.cgi?id=23600 391 @safe pure unittest 392 { 393 import std.typecons : Tuple, tuple; 394 395 string h, w; 396 Tuple!(int, float) t; 397 398 assert("hello 1 2.34 world".formattedRead!"%s %d %f %s"(h, t, w) == 3); 399 assert(h == "hello"); 400 assert(t == tuple(1, 2.34f)); 401 assert(w == "world"); 402 } 403 404 @safe unittest 405 { 406 import std.math.operations : isClose; 407 import std.math.traits : isNaN; 408 import std.range.primitives : empty; 409 410 string s = " 1.2 3.4 "; 411 double x, y, z; 412 assert(formattedRead(s, " %s %s %s ", x, y, z) == 2); 413 assert(s.empty); 414 assert(isClose(x, 1.2)); 415 assert(isClose(y, 3.4)); 416 assert(isNaN(z)); 417 } 418 419 // for backwards compatibility 420 @safe pure unittest 421 { 422 string s = "hello!124:34.5"; 423 string a; 424 int b; 425 double c; 426 formattedRead(s, "%s!%s:%s", &a, &b, &c); 427 assert(a == "hello" && b == 124 && c == 34.5); 428 429 // mix pointers and auto-ref 430 s = "world!200:42.25"; 431 formattedRead(s, "%s!%s:%s", a, &b, &c); 432 assert(a == "world" && b == 200 && c == 42.25); 433 434 s = "world1!201:42.5"; 435 formattedRead(s, "%s!%s:%s", &a, &b, c); 436 assert(a == "world1" && b == 201 && c == 42.5); 437 438 s = "world2!202:42.75"; 439 formattedRead(s, "%s!%s:%s", a, b, &c); 440 assert(a == "world2" && b == 202 && c == 42.75); 441 } 442 443 // for backwards compatibility 444 @safe pure unittest 445 { 446 import std.math.operations : isClose; 447 import std.math.traits : isNaN; 448 import std.range.primitives : empty; 449 450 string s = " 1.2 3.4 "; 451 double x, y, z; 452 assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2); 453 assert(s.empty); 454 assert(isClose(x, 1.2)); 455 assert(isClose(y, 3.4)); 456 assert(isNaN(z)); 457 } 458 459 @safe unittest 460 { 461 string s = "hello!124:34.5"; 462 string a; 463 int b; 464 double c; 465 formattedRead(s, "%s!%s:%s", &a, &b, &c); 466 assert(a == "hello" && b == 124 && c == 34.5); 467 } 468 469 @safe pure unittest 470 { 471 string line; 472 473 bool f1; 474 475 line = "true"; 476 formattedRead(line, "%s", &f1); 477 assert(f1); 478 479 line = "TrUE"; 480 formattedRead(line, "%s", &f1); 481 assert(f1); 482 483 line = "false"; 484 formattedRead(line, "%s", &f1); 485 assert(!f1); 486 487 line = "fALsE"; 488 formattedRead(line, "%s", &f1); 489 assert(!f1); 490 491 line = "1"; 492 formattedRead(line, "%d", &f1); 493 assert(f1); 494 495 line = "-1"; 496 formattedRead(line, "%d", &f1); 497 assert(f1); 498 499 line = "0"; 500 formattedRead(line, "%d", &f1); 501 assert(!f1); 502 503 line = "-0"; 504 formattedRead(line, "%d", &f1); 505 assert(!f1); 506 } 507 508 @safe pure unittest 509 { 510 union B 511 { 512 char[int.sizeof] untyped; 513 int typed; 514 } 515 516 B b; 517 b.typed = 5; 518 char[] input = b.untyped[]; 519 int witness; 520 formattedRead(input, "%r", &witness); 521 assert(witness == b.typed); 522 } 523 524 @safe pure unittest 525 { 526 union A 527 { 528 char[float.sizeof] untyped; 529 float typed; 530 } 531 532 A a; 533 a.typed = 5.5; 534 char[] input = a.untyped[]; 535 float witness; 536 formattedRead(input, "%r", &witness); 537 assert(witness == a.typed); 538 } 539 540 @safe pure unittest 541 { 542 import std.typecons : Tuple; 543 544 char[] line = "1 2".dup; 545 int a, b; 546 formattedRead(line, "%s %s", &a, &b); 547 assert(a == 1 && b == 2); 548 549 line = "10 2 3".dup; 550 formattedRead(line, "%d ", &a); 551 assert(a == 10); 552 assert(line == "2 3"); 553 554 Tuple!(int, float) t; 555 line = "1 2.125".dup; 556 formattedRead(line, "%d %g", &t); 557 assert(t[0] == 1 && t[1] == 2.125); 558 559 line = "1 7643 2.125".dup; 560 formattedRead(line, "%s %*u %s", &t); 561 assert(t[0] == 1 && t[1] == 2.125); 562 } 563 564 @safe pure unittest 565 { 566 string line; 567 568 char c1, c2; 569 570 line = "abc"; 571 formattedRead(line, "%s%c", &c1, &c2); 572 assert(c1 == 'a' && c2 == 'b'); 573 assert(line == "c"); 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 586 @safe pure unittest 587 { 588 string line; 589 590 line = "[1,2,3]"; 591 int[] s1; 592 formattedRead(line, "[%(%s,%)]", &s1); 593 assert(s1 == [1,2,3]); 594 595 line = `["hello", "world"]`; 596 string[] s2; 597 formattedRead(line, "[%(%s, %)]", &s2); 598 assert(s2 == ["hello", "world"]); 599 600 line = "123 456"; 601 int[] s3; 602 formattedRead(line, "%(%s %)", &s3); 603 assert(s3 == [123, 456]); 604 605 line = "h,e,l,l,o; w,o,r,l,d"; 606 string[] s4; 607 formattedRead(line, "%(%(%c,%); %)", &s4); 608 assert(s4 == ["hello", "world"]); 609 } 610 611 @safe pure unittest 612 { 613 import std.exception : assertThrown; 614 615 string line; 616 617 int[4] sa1; 618 line = `[1,2,3,4]`; 619 formattedRead(line, "%s", &sa1); 620 assert(sa1 == [1,2,3,4]); 621 622 int[4] sa2; 623 line = `[1,2,3]`; 624 assertThrown(formattedRead(line, "%s", &sa2)); 625 626 int[4] sa3; 627 line = `[1,2,3,4,5]`; 628 assertThrown(formattedRead(line, "%s", &sa3)); 629 } 630 631 @safe pure unittest 632 { 633 import std.exception : assertThrown; 634 import std.format : FormatException; 635 636 string input; 637 638 int[4] sa1; 639 input = `[1,2,3,4]`; 640 formattedRead(input, "[%(%s,%)]", &sa1); 641 assert(sa1 == [1,2,3,4]); 642 643 int[4] sa2; 644 input = `[1,2,3]`; 645 assertThrown!FormatException(formattedRead(input, "[%(%s,%)]", &sa2)); 646 } 647 648 @safe pure unittest 649 { 650 string line; 651 652 string s1, s2; 653 654 line = "hello, world"; 655 formattedRead(line, "%s", &s1); 656 assert(s1 == "hello, world", s1); 657 658 line = "hello, world;yah"; 659 formattedRead(line, "%s;%s", &s1, &s2); 660 assert(s1 == "hello, world", s1); 661 assert(s2 == "yah", s2); 662 663 line = `['h','e','l','l','o']`; 664 string s3; 665 formattedRead(line, "[%(%s,%)]", &s3); 666 assert(s3 == "hello"); 667 668 line = `"hello"`; 669 string s4; 670 formattedRead(line, "\"%(%c%)\"", &s4); 671 assert(s4 == "hello"); 672 } 673 674 @safe pure unittest 675 { 676 string line; 677 678 string[int] aa1; 679 line = `[1:"hello", 2:"world"]`; 680 formattedRead(line, "%s", &aa1); 681 assert(aa1 == [1:"hello", 2:"world"]); 682 683 int[string] aa2; 684 line = `{"hello"=1; "world"=2}`; 685 formattedRead(line, "{%(%s=%s; %)}", &aa2); 686 assert(aa2 == ["hello":1, "world":2]); 687 688 int[string] aa3; 689 line = `{[hello=1]; [world=2]}`; 690 formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3); 691 assert(aa3 == ["hello":1, "world":2]); 692 } 693 694 // test rvalue using 695 @safe pure unittest 696 { 697 string[int] aa1; 698 formattedRead!("%s")(`[1:"hello", 2:"world"]`, aa1); 699 assert(aa1 == [1:"hello", 2:"world"]); 700 701 int[string] aa2; 702 formattedRead(`{"hello"=1; "world"=2}`, "{%(%s=%s; %)}", aa2); 703 assert(aa2 == ["hello":1, "world":2]); 704 } 705 706 /** 707 Reads an input range according to a format string and returns a tuple of Args 708 with the read values. 709 710 Format specifiers with format character $(B 'd'), $(B 'u') and $(B 711 'c') can take a $(B '*') parameter for skipping values. 712 713 The second version of `formattedRead` takes the format string as 714 template argument. In this case, it is checked for consistency at 715 compile-time. 716 717 Params: 718 Args = a variadic list of types of the arguments 719 */ 720 template formattedRead(Args...) 721 if (Args.length && allSatisfy!(isType, Args)) 722 { 723 import std.typecons : Tuple; 724 725 /** 726 Params: 727 r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives), 728 where the formatted input is read from 729 fmt = a $(MREF_ALTTEXT format string, std,format) 730 Range = the type of the input range `r` 731 Char = the character type used for `fmt` 732 733 Returns: 734 A Tuple!Args with the elements filled. 735 736 Throws: 737 A $(REF_ALTTEXT FormatException, FormatException, std, format) 738 if reading did not succeed. 739 */ 740 Tuple!Args formattedRead(Range, Char)(auto ref Range r, const(Char)[] fmt) 741 { 742 import core.lifetime : forward; 743 import std.format : enforceFmt; 744 745 Tuple!Args args; 746 const numArgsFilled = .formattedRead(forward!r, fmt, args.expand); 747 enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments"); 748 return args; 749 } 750 } 751 752 /// 753 @safe pure unittest 754 { 755 import std.exception : assertThrown; 756 import std.format : FormatException; 757 import std.typecons : tuple; 758 759 auto complete = "hello!34.5:124".formattedRead!(string, double, int)("%s!%s:%s"); 760 assert(complete == tuple("hello", 34.5, 124)); 761 762 // reading ends early 763 assertThrown!FormatException("hello!34.5:".formattedRead!(string, double, int)("%s!%s:%s")); 764 } 765 766 /// Skipping values 767 @safe pure unittest 768 { 769 import std.format : FormatException; 770 import std.typecons : tuple; 771 772 auto result = "orange: (12%) 15.25".formattedRead!(string, double)("%s: (%*d%%) %f"); 773 assert(result == tuple("orange", 15.25)); 774 } 775 776 /// ditto 777 template formattedRead(alias fmt, Args...) 778 if (!isType!fmt && isSomeString!(typeof(fmt)) && Args.length && allSatisfy!(isType, Args)) 779 { 780 import std.typecons : Flag, Tuple, Yes; 781 Tuple!Args formattedRead(Range)(auto ref Range r) 782 { 783 import core.lifetime : forward; 784 import std.format : enforceFmt; 785 786 Tuple!Args args; 787 const numArgsFilled = .formattedRead!fmt(forward!r, args.expand); 788 enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments"); 789 return args; 790 } 791 } 792 793 /// The format string can be checked at compile-time 794 @safe pure unittest 795 { 796 import std.exception : assertThrown; 797 import std.format : FormatException; 798 import std.typecons : tuple; 799 800 auto expected = tuple("hello", 124, 34.5); 801 auto result = "hello!124:34.5".formattedRead!("%s!%s:%s", string, int, double); 802 assert(result == expected); 803 804 assertThrown!FormatException("hello!34.5:".formattedRead!("%s!%s:%s", string, double, int)); 805 } 806 807 /// Compile-time consistency check 808 @safe pure unittest 809 { 810 import std.format : FormatException; 811 import std.typecons : tuple; 812 813 static assert(!__traits(compiles, "orange: (12%) 15.25".formattedRead!("%s: (%*d%%) %f", string, double))); 814 } 815 816 /** 817 Reads a value from the given _input range and converts it according to a 818 format specifier. 819 820 Params: 821 input = the $(REF_ALTTEXT input range, isInputRange, std, range, primitives), 822 to read from 823 spec = a $(MREF_ALTTEXT format string, std,format) 824 T = type to return 825 Range = the type of the input range `input` 826 Char = the character type used for `spec` 827 828 Returns: 829 A value from `input` of type `T`. 830 831 Throws: 832 A $(REF_ALTTEXT FormatException, FormatException, std, format) 833 if reading did not succeed. 834 835 See_Also: 836 $(REF parse, std, conv) and $(REF to, std, conv) 837 */ 838 T unformatValue(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 839 { 840 return unformatValueImpl!T(input, spec); 841 } 842 843 /// 844 @safe pure unittest 845 { 846 import std.format.spec : singleSpec; 847 848 string s = "42"; 849 auto spec = singleSpec("%s"); 850 assert(unformatValue!int(s, spec) == 42); 851 } 852 853 // https://issues.dlang.org/show_bug.cgi?id=7241 854 @safe pure unittest 855 { 856 string input = "a"; 857 auto spec = FormatSpec!char("%s"); 858 spec.readUpToNextSpec(input); 859 auto result = unformatValue!(dchar[1])(input, spec); 860 assert(result[0] == 'a'); 861 } 862 863 // https://issues.dlang.org/show_bug.cgi?id=20393 864 @safe pure unittest 865 { 866 import std.exception : assertThrown; 867 string str = "foo 12a-buzz"; 868 string a, c; 869 int b; 870 assertThrown(formattedRead(str, "%s %d-%s", &a, &b, &c)); 871 } 872 873 // https://issues.dlang.org/show_bug.cgi?id=18051 874 @safe pure unittest 875 { 876 import std.format : format; 877 878 enum Op { lt, gt, eq } 879 880 auto s = format!"%s"(Op.lt); 881 Op op; 882 assert(formattedRead!"%s"(s, op) == 1); 883 assert(op == Op.lt); 884 }