Add 'std.format.read.formattedRead' overloads to return a Tuple with values read (#8647)

* refactor(formattedRead): add not is type validation to template argument 'fmt'

Signed-off-by: João Lourenço <jlourenco5691@gmail.com>

* feat(formattedRead): add overloads to return a tuple with the read values

Signed-off-by: João Lourenço <jlourenco5691@gmail.com>

* test(formattedRead): add unnittests for the tuple return type overloads

Signed-off-by: João Lourenço <jlourenco5691@gmail.com>

* chore(changelog): add a changelog formattedRead entry

Signed-off-by: João Lourenço <jlourenco5691@gmail.com>

---------

Signed-off-by: João Lourenço <jlourenco5691@gmail.com>
This commit is contained in:
João Lourenço 2024-11-26 04:43:04 +00:00 committed by GitHub
parent 5d16dafff0
commit 6b666971bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 143 additions and 2 deletions

View file

@ -0,0 +1,30 @@
Extend the functionality of formattedRead to permit a std.file.slurp like execution.
Template argument types can now be passed to formattedRead along with a
format string to parse and read the input range as a Tuple of those arguments.
All arguments must be read successfully, otherwise, and unlike std.file.slurp
which has non exhaustive option for partial reads, it'll throw a std.format.FormatException.
---
import std.exception : assertThrown;
import std.format : FormatException;
import std.typecons : tuple;
@safe pure unittest
{
auto complete = "hello!34.5:124".formattedRead!(string, double, int)("%s!%s:%s");
assert(complete == tuple("hello", 34.5, 124));
assertThrown!FormatException("hello!34.5:".formattedRead!(string, double, int)("%s!%s:%s"));
}
/// The format string can be checked at compile-time:
@safe pure unittest
{
auto expected = tuple("hello", 124, 34.5);
auto result = "hello!124:34.5".formattedRead!("%s!%s:%s", string, int, double);
assert(result == expected);
assertThrown!FormatException("hello!34.5:".formattedRead!("%s!%s:%s", string, double, int));
}
---

View file

@ -198,7 +198,8 @@ module std.format.read;
import std.format.spec : FormatSpec;
import std.format.internal.read;
import std.traits : isSomeString;
import std.meta : allSatisfy;
import std.traits : isSomeString, isType;
/**
Reads an input range according to a format string and stores the read
@ -300,7 +301,7 @@ uint formattedRead(Range, Char, Args...)(auto ref Range r, const(Char)[] fmt, au
/// ditto
uint formattedRead(alias fmt, Range, Args...)(auto ref Range r, auto ref Args args)
if (isSomeString!(typeof(fmt)))
if (!isType!fmt && isSomeString!(typeof(fmt)))
{
import std.format : checkFormatException;
import std.meta : staticMap;
@ -692,6 +693,116 @@ if (isSomeString!(typeof(fmt)))
assert(aa2 == ["hello":1, "world":2]);
}
/**
Reads an input range according to a format string and returns a tuple of Args
with the read values.
Format specifiers with format character $(B 'd'), $(B 'u') and $(B
'c') can take a $(B '*') parameter for skipping values.
The second version of `formattedRead` takes the format string as
template argument. In this case, it is checked for consistency at
compile-time.
Params:
Args = a variadic list of types of the arguments
*/
template formattedRead(Args...)
if (Args.length && allSatisfy!(isType, Args))
{
import std.typecons : Tuple;
/**
Params:
r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives),
where the formatted input is read from
fmt = a $(MREF_ALTTEXT format string, std,format)
Range = the type of the input range `r`
Char = the character type used for `fmt`
Returns:
A Tuple!Args with the elements filled.
Throws:
A $(REF_ALTTEXT FormatException, FormatException, std, format)
if reading did not succeed.
*/
Tuple!Args formattedRead(Range, Char)(auto ref Range r, const(Char)[] fmt)
{
import core.lifetime : forward;
import std.format : enforceFmt;
Tuple!Args args;
const numArgsFilled = .formattedRead(forward!r, fmt, args.expand);
enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments");
return args;
}
}
///
@safe pure unittest
{
import std.exception : assertThrown;
import std.format : FormatException;
import std.typecons : tuple;
auto complete = "hello!34.5:124".formattedRead!(string, double, int)("%s!%s:%s");
assert(complete == tuple("hello", 34.5, 124));
// reading ends early
assertThrown!FormatException("hello!34.5:".formattedRead!(string, double, int)("%s!%s:%s"));
}
/// Skipping values
@safe pure unittest
{
import std.format : FormatException;
import std.typecons : tuple;
auto result = "orange: (12%) 15.25".formattedRead!(string, double)("%s: (%*d%%) %f");
assert(result == tuple("orange", 15.25));
}
/// ditto
template formattedRead(alias fmt, Args...)
if (!isType!fmt && isSomeString!(typeof(fmt)) && Args.length && allSatisfy!(isType, Args))
{
import std.typecons : Flag, Tuple, Yes;
Tuple!Args formattedRead(Range)(auto ref Range r)
{
import core.lifetime : forward;
import std.format : enforceFmt;
Tuple!Args args;
const numArgsFilled = .formattedRead!fmt(forward!r, args.expand);
enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments");
return args;
}
}
/// The format string can be checked at compile-time
@safe pure unittest
{
import std.exception : assertThrown;
import std.format : FormatException;
import std.typecons : tuple;
auto expected = tuple("hello", 124, 34.5);
auto result = "hello!124:34.5".formattedRead!("%s!%s:%s", string, int, double);
assert(result == expected);
assertThrown!FormatException("hello!34.5:".formattedRead!("%s!%s:%s", string, double, int));
}
/// Compile-time consistency check
@safe pure unittest
{
import std.format : FormatException;
import std.typecons : tuple;
static assert(!__traits(compiles, "orange: (12%) 15.25".formattedRead!("%s: (%*d%%) %f", string, double)));
}
/**
Reads a value from the given _input range and converts it according to a
format specifier.