From 6b666971bcbfe45d44a2ebb3d29b901f8bab4e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Louren=C3=A7o?= <32413766+iK4tsu@users.noreply.github.com> Date: Tue, 26 Nov 2024 04:43:04 +0000 Subject: [PATCH] Add 'std.format.read.formattedRead' overloads to return a Tuple with values read (#8647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(formattedRead): add not is type validation to template argument 'fmt' Signed-off-by: João Lourenço * feat(formattedRead): add overloads to return a tuple with the read values Signed-off-by: João Lourenço * test(formattedRead): add unnittests for the tuple return type overloads Signed-off-by: João Lourenço * chore(changelog): add a changelog formattedRead entry Signed-off-by: João Lourenço --------- Signed-off-by: João Lourenço --- changelog/formatted_read_tuple_return.dd | 30 ++++++ std/format/read.d | 115 ++++++++++++++++++++++- 2 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 changelog/formatted_read_tuple_return.dd diff --git a/changelog/formatted_read_tuple_return.dd b/changelog/formatted_read_tuple_return.dd new file mode 100644 index 000000000..03fb23781 --- /dev/null +++ b/changelog/formatted_read_tuple_return.dd @@ -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)); +} +--- diff --git a/std/format/read.d b/std/format/read.d index da9d0dc14..e2f9b944a 100644 --- a/std/format/read.d +++ b/std/format/read.d @@ -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.