From bd551a6d480f13e5f261baa1f083c771dad8ab6d Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sat, 18 Mar 2023 18:44:57 +0100 Subject: [PATCH 1/8] Added writeUtf8 and writeUtf8Lines --- .../src/main/scala/fs2/io/file/Files.scala | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/io/shared/src/main/scala/fs2/io/file/Files.scala b/io/shared/src/main/scala/fs2/io/file/Files.scala index 0b6f815e5f..262859dc62 100644 --- a/io/shared/src/main/scala/fs2/io/file/Files.scala +++ b/io/shared/src/main/scala/fs2/io/file/Files.scala @@ -409,6 +409,32 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { limit: Long, flags: Flags ): Pipe[F, Byte, Nothing] + + /** Writes to the specified file as an utf8 string. + * + * The file is created if it does not exist and is truncated. + * Use `writeUtf8(path, Flags.Append)` to append to the end of + * the file, or pass other flags to further customize behavior. + */ + def writeUtf8(path: Path): Pipe[F, String, Nothing] = writeUtf8(path, Flags.Write) + + /** Writes to the specified file as an utf8 string using + * the specified flags to open the file. + */ + def writeUtf8(path: Path, flags: Flags): Pipe[F, String, Nothing] + + /** Writes each string to the specified file as utf8 lines. + * + * The file is created if it does not exist and is truncated. + * Use `writeUtf8Lines(path, Flags.Append)` to append to the end + * of the file, or pass other flags to further customize behavior. + */ + def writeUtf8Lines(path: Path): Pipe[F, String, Nothing] = writeUtf8(path, Flags.Write) + + /** Writes each string to the specified file as utf8 lines + * using the specified flags to open the file. + */ + def writeUtf8Lines(path: Path, flags: Flags): Pipe[F, String, Nothing] } object Files extends FilesCompanionPlatform { @@ -573,6 +599,12 @@ object Files extends FilesCompanionPlatform { } } } + + def writeUtf8(path: Path, flags: Flags): Pipe[F, String, Nothing] = + _.through(text.utf8.encode).through(writeAll(path, flags)) + + def writeUtf8Lines(path: Path, flags: Flags): Pipe[F, String, Nothing] = + _.through(text.lines).through(writeUtf8(path, flags)) } def apply[F[_]](implicit F: Files[F]): Files[F] = F From 8325becb9cc03b5fd42e5332dcd54ad126884b20 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sun, 19 Mar 2023 10:17:42 +0100 Subject: [PATCH 2/8] Moved implementations in to trait --- .../src/main/scala/fs2/io/file/Files.scala | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/io/shared/src/main/scala/fs2/io/file/Files.scala b/io/shared/src/main/scala/fs2/io/file/Files.scala index 262859dc62..22934ab0d7 100644 --- a/io/shared/src/main/scala/fs2/io/file/Files.scala +++ b/io/shared/src/main/scala/fs2/io/file/Files.scala @@ -269,7 +269,10 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { def readAll(path: Path, chunkSize: Int, flags: Flags): Stream[F, Byte] /** Returns a `ReadCursor` for the specified path, using the supplied flags when opening the file. */ - def readCursor(path: Path, flags: Flags): Resource[F, ReadCursor[F]] + def readCursor(path: Path, flags: Flags): Resource[F, ReadCursor[F]] = + open(path, flags.addIfAbsent(Flag.Read)).map { fileHandle => + ReadCursor(fileHandle, 0L) + } /** Reads a range of data synchronously from the file at the specified path. * `start` is inclusive, `end` is exclusive, so when `start` is 0 and `end` is 2, @@ -278,10 +281,10 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { def readRange(path: Path, chunkSize: Int, start: Long, end: Long): Stream[F, Byte] /** Reads all bytes from the file specified and decodes them as a utf8 string. */ - def readUtf8(path: Path): Stream[F, String] + def readUtf8(path: Path): Stream[F, String] = readAll(path).through(text.utf8.decode) /** Reads all bytes from the file specified and decodes them as utf8 lines. */ - def readUtf8Lines(path: Path): Stream[F, String] + def readUtf8Lines(path: Path): Stream[F, String] = readUtf8(path).through(text.lines) /** Returns the real path i.e. the actual location of `path`. * The precise definition of this method is implementation dependent but in general @@ -421,7 +424,8 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { /** Writes to the specified file as an utf8 string using * the specified flags to open the file. */ - def writeUtf8(path: Path, flags: Flags): Pipe[F, String, Nothing] + def writeUtf8(path: Path, flags: Flags): Pipe[F, String, Nothing] = in => + in.through(text.utf8.encode).through(writeAll(path, flags)) /** Writes each string to the specified file as utf8 lines. * @@ -434,7 +438,8 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { /** Writes each string to the specified file as utf8 lines * using the specified flags to open the file. */ - def writeUtf8Lines(path: Path, flags: Flags): Pipe[F, String, Nothing] + def writeUtf8Lines(path: Path, flags: Flags): Pipe[F, String, Nothing] = in => + in.through(text.lines).through(writeUtf8(path, flags)) } object Files extends FilesCompanionPlatform { @@ -445,22 +450,11 @@ object Files extends FilesCompanionPlatform { cursor.readAll(chunkSize).void.stream } - def readCursor(path: Path, flags: Flags): Resource[F, ReadCursor[F]] = - open(path, flags.addIfAbsent(Flag.Read)).map { fileHandle => - ReadCursor(fileHandle, 0L) - } - def readRange(path: Path, chunkSize: Int, start: Long, end: Long): Stream[F, Byte] = Stream.resource(readCursor(path, Flags.Read)).flatMap { cursor => cursor.seek(start).readUntil(chunkSize, end).void.stream } - def readUtf8(path: Path): Stream[F, String] = - readAll(path).through(text.utf8.decode) - - def readUtf8Lines(path: Path): Stream[F, String] = - readUtf8(path).through(text.lines) - def tail( path: Path, chunkSize: Int, @@ -599,12 +593,6 @@ object Files extends FilesCompanionPlatform { } } } - - def writeUtf8(path: Path, flags: Flags): Pipe[F, String, Nothing] = - _.through(text.utf8.encode).through(writeAll(path, flags)) - - def writeUtf8Lines(path: Path, flags: Flags): Pipe[F, String, Nothing] = - _.through(text.lines).through(writeUtf8(path, flags)) } def apply[F[_]](implicit F: Files[F]): Files[F] = F From 45e22849eecbfb576ebbf1915208f0809c4c2b6a Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sun, 19 Mar 2023 11:27:25 +0100 Subject: [PATCH 3/8] Fixed writeUtf8Lines implementation --- .../src/main/scala/fs2/io/file/Files.scala | 4 +-- .../test/scala/fs2/io/file/FilesSuite.scala | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/io/shared/src/main/scala/fs2/io/file/Files.scala b/io/shared/src/main/scala/fs2/io/file/Files.scala index 22934ab0d7..1fee4d3dcc 100644 --- a/io/shared/src/main/scala/fs2/io/file/Files.scala +++ b/io/shared/src/main/scala/fs2/io/file/Files.scala @@ -433,13 +433,13 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { * Use `writeUtf8Lines(path, Flags.Append)` to append to the end * of the file, or pass other flags to further customize behavior. */ - def writeUtf8Lines(path: Path): Pipe[F, String, Nothing] = writeUtf8(path, Flags.Write) + def writeUtf8Lines(path: Path): Pipe[F, String, Nothing] = writeUtf8Lines(path, Flags.Write) /** Writes each string to the specified file as utf8 lines * using the specified flags to open the file. */ def writeUtf8Lines(path: Path, flags: Flags): Pipe[F, String, Nothing] = in => - in.through(text.lines).through(writeUtf8(path, flags)) + in.intersperse("\n").through(writeUtf8(path, flags)) } object Files extends FilesCompanionPlatform { diff --git a/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala b/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala index 6b98858447..7d137ed9d4 100644 --- a/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala +++ b/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala @@ -138,6 +138,36 @@ class FilesSuite extends Fs2IoSuite with BaseFileSuite { .compile .drain } + + test("writeUtf8") { + Stream + .resource(tempFile) + .flatMap { path => + Stream("Hello", " world!") + .covary[IO] + .through(Files[IO].writeUtf8(path)) ++ Files[IO] + .readAll(path) + .through(text.utf8.decode) + } + .compile + .foldMonoid + .assertEquals("Hello world!") + } + + test("writeUtf8Lines") { + Stream + .resource(tempFile) + .flatMap { path => + Stream("foo", "bar") + .covary[IO] + .through(Files[IO].writeUtf8Lines(path)) ++ Files[IO] + .readUtf8(path) + } + .compile + .foldMonoid + .assertEquals("""|foo + |bar""".stripMargin) + } } group("tail") { From 4e82a30875305be073f122e3b6a3cf6d52fefe9d Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sun, 19 Mar 2023 12:09:53 +0100 Subject: [PATCH 4/8] Added a os-aware line separator to Files --- io/js/src/main/scala/fs2/io/file/FilesPlatform.scala | 2 ++ io/js/src/main/scala/fs2/io/internal/facade/os.scala | 4 ++++ io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala | 2 ++ io/shared/src/main/scala/fs2/io/file/Files.scala | 5 ++++- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala b/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala index 7a514e4dfc..c537d29bc6 100644 --- a/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala +++ b/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala @@ -264,6 +264,8 @@ private[fs2] trait FilesCompanionPlatform { override def isSameFile(path1: Path, path2: Path): F[Boolean] = F.pure(path1.absolute == path2.absolute) + override val lineSeparator: String = facade.os.EOL() + override def list(path: Path): Stream[F, Path] = Stream .bracket(F.fromPromise(F.delay(facade.fs.promises.opendir(path.toString))))(dir => diff --git a/io/js/src/main/scala/fs2/io/internal/facade/os.scala b/io/js/src/main/scala/fs2/io/internal/facade/os.scala index 0c134a4ae4..c47ba186c5 100644 --- a/io/js/src/main/scala/fs2/io/internal/facade/os.scala +++ b/io/js/src/main/scala/fs2/io/internal/facade/os.scala @@ -42,6 +42,10 @@ package object os { @JSImport("os", "networkInterfaces") private[io] def networkInterfaces(): js.Dictionary[js.Array[NetworkInterfaceInfo]] = js.native + @js.native + @JSImport("os", "EOL") + private[io] def EOL(): String = js.native + } package os { diff --git a/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala index 34d7b08a47..6581a64994 100644 --- a/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala +++ b/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala @@ -291,6 +291,8 @@ private[file] trait FilesCompanionPlatform { def isSameFile(path1: Path, path2: Path): F[Boolean] = Sync[F].blocking(JFiles.isSameFile(path1.toNioPath, path2.toNioPath)) + val lineSeparator: String = System.lineSeparator() + def list(path: Path): Stream[F, Path] = _runJavaCollectionResource[JStream[JPath]]( Sync[F].blocking(JFiles.list(path.toNioPath)), diff --git a/io/shared/src/main/scala/fs2/io/file/Files.scala b/io/shared/src/main/scala/fs2/io/file/Files.scala index 1fee4d3dcc..7083eba5a3 100644 --- a/io/shared/src/main/scala/fs2/io/file/Files.scala +++ b/io/shared/src/main/scala/fs2/io/file/Files.scala @@ -240,6 +240,9 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { /** Returns true if the supplied paths reference the same file. */ def isSameFile(path1: Path, path2: Path): F[Boolean] + /** Returns the line separator for the specific OS */ + val lineSeparator: String + /** Gets the contents of the specified directory. */ def list(path: Path): Stream[F, Path] @@ -439,7 +442,7 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { * using the specified flags to open the file. */ def writeUtf8Lines(path: Path, flags: Flags): Pipe[F, String, Nothing] = in => - in.intersperse("\n").through(writeUtf8(path, flags)) + in.intersperse(lineSeparator).through(writeUtf8(path, flags)) } object Files extends FilesCompanionPlatform { From 865f17af8b3c37e8618f74bca415897a698476bf Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sun, 19 Mar 2023 12:15:04 +0100 Subject: [PATCH 5/8] Removed useless parenthesis --- io/js/src/main/scala/fs2/io/file/FilesPlatform.scala | 2 +- io/js/src/main/scala/fs2/io/internal/facade/os.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala b/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala index c537d29bc6..89a133f1a4 100644 --- a/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala +++ b/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala @@ -264,7 +264,7 @@ private[fs2] trait FilesCompanionPlatform { override def isSameFile(path1: Path, path2: Path): F[Boolean] = F.pure(path1.absolute == path2.absolute) - override val lineSeparator: String = facade.os.EOL() + override val lineSeparator: String = facade.os.EOL override def list(path: Path): Stream[F, Path] = Stream diff --git a/io/js/src/main/scala/fs2/io/internal/facade/os.scala b/io/js/src/main/scala/fs2/io/internal/facade/os.scala index c47ba186c5..a3da76d69e 100644 --- a/io/js/src/main/scala/fs2/io/internal/facade/os.scala +++ b/io/js/src/main/scala/fs2/io/internal/facade/os.scala @@ -44,7 +44,7 @@ package object os { @js.native @JSImport("os", "EOL") - private[io] def EOL(): String = js.native + private[io] def EOL: String = js.native } From 0042b11e34738df692a0c71a4b67fcd2f340ef9c Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sun, 19 Mar 2023 12:18:06 +0100 Subject: [PATCH 6/8] Changed lineSeparator from val to def --- io/js/src/main/scala/fs2/io/file/FilesPlatform.scala | 2 +- io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala | 2 +- io/shared/src/main/scala/fs2/io/file/Files.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala b/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala index 89a133f1a4..24e541c0d4 100644 --- a/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala +++ b/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala @@ -264,7 +264,7 @@ private[fs2] trait FilesCompanionPlatform { override def isSameFile(path1: Path, path2: Path): F[Boolean] = F.pure(path1.absolute == path2.absolute) - override val lineSeparator: String = facade.os.EOL + override def lineSeparator: String = facade.os.EOL override def list(path: Path): Stream[F, Path] = Stream diff --git a/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala index 6581a64994..9b4144c00e 100644 --- a/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala +++ b/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala @@ -291,7 +291,7 @@ private[file] trait FilesCompanionPlatform { def isSameFile(path1: Path, path2: Path): F[Boolean] = Sync[F].blocking(JFiles.isSameFile(path1.toNioPath, path2.toNioPath)) - val lineSeparator: String = System.lineSeparator() + def lineSeparator: String = System.lineSeparator() def list(path: Path): Stream[F, Path] = _runJavaCollectionResource[JStream[JPath]]( diff --git a/io/shared/src/main/scala/fs2/io/file/Files.scala b/io/shared/src/main/scala/fs2/io/file/Files.scala index 7083eba5a3..05b9ca5e38 100644 --- a/io/shared/src/main/scala/fs2/io/file/Files.scala +++ b/io/shared/src/main/scala/fs2/io/file/Files.scala @@ -241,7 +241,7 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { def isSameFile(path1: Path, path2: Path): F[Boolean] /** Returns the line separator for the specific OS */ - val lineSeparator: String + def lineSeparator: String /** Gets the contents of the specified directory. */ def list(path: Path): Stream[F, Path] From 5d4fd6929b487d425f90b47a474b73d1555ae1c9 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sun, 19 Mar 2023 12:56:38 +0100 Subject: [PATCH 7/8] Tests trigger From de9272077c30c3c3b6b47c1b60d5c7bf7ab7e864 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sun, 19 Mar 2023 19:15:25 +0100 Subject: [PATCH 8/8] Added newline after each string --- io/shared/src/main/scala/fs2/io/file/Files.scala | 2 +- io/shared/src/test/scala/fs2/io/file/FilesSuite.scala | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/io/shared/src/main/scala/fs2/io/file/Files.scala b/io/shared/src/main/scala/fs2/io/file/Files.scala index 05b9ca5e38..1a7e5ca599 100644 --- a/io/shared/src/main/scala/fs2/io/file/Files.scala +++ b/io/shared/src/main/scala/fs2/io/file/Files.scala @@ -442,7 +442,7 @@ sealed trait Files[F[_]] extends FilesPlatform[F] { * using the specified flags to open the file. */ def writeUtf8Lines(path: Path, flags: Flags): Pipe[F, String, Nothing] = in => - in.intersperse(lineSeparator).through(writeUtf8(path, flags)) + in.flatMap(s => Stream[F, String](s, lineSeparator)).through(writeUtf8(path, flags)) } object Files extends FilesCompanionPlatform { diff --git a/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala b/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala index 7d137ed9d4..07052788e5 100644 --- a/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala +++ b/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala @@ -166,7 +166,8 @@ class FilesSuite extends Fs2IoSuite with BaseFileSuite { .compile .foldMonoid .assertEquals("""|foo - |bar""".stripMargin) + |bar + |""".stripMargin) } }