From 0168c5cf833c4a465b6a8be73c7f1d802285416e Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 8 Sep 2023 16:06:37 +0200 Subject: [PATCH 01/10] Update examples.md Case classes to CSV --- docs/examples.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/examples.md b/docs/examples.md index 427772f..9ce6468 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -420,6 +420,27 @@ object PerftConverter extends IOApp.Simple { ``` @:@ +## A helper utility to write out an `Seq[CaseClass]` to CSV + +In the presence of a CsvRowEncoder + +``` +import fs2.* +import fs2.data.csv.* +import fs2.io.file.Path + +given val pEncoder: CsvRowEncoder[CaseClass, String] = deriveCsvRowEncoder + +def writeCaseClassIterableToFile[A](data: Seq[A], file : Path)(using CsvRowEncoder[A, String]) = + fs2.Stream.emits[IO, A](data) + .through(encodeUsingFirstHeaders(fullRows = true)) + .through(text.utf8Encode) + .through( Files[IO].writeAll(file) ) + .compile + .drain +``` + + [fs2]: https://fs2.io/#/ [fs2-data-csv]: https://fs2-data.gnieh.org/documentation/csv/ [decline]: https://ben.kirw.in/decline/ From 6b75cc1b2fbcbe74a8d672f402cfaf7072886c7c Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Tue, 12 Sep 2023 10:23:30 +0200 Subject: [PATCH 02/10] Improve CSV examples as per feedback --- docs/examples.md | 70 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 9ce6468..d48f986 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -166,7 +166,7 @@ object Main extends CommandIOApp("mkString", "Concatenates strings from stdin") ## Parsing and transforming a CSV file -Here, [fs2-data-csv] is used to read and parse a comma separated file. +Here, [fs2-data-csv] is used to read and parse a comma separated file. Manual encoders and decoders are defined for our `Passenger`s to show you how to do everything from scratch. Let's start with a CSV file that has records of fictious passengers registered for a flight: @@ -233,7 +233,7 @@ val input = Files[IO] object CSVPrinter extends IOApp.Simple: - /** First we'll do some logging for each row, + /** First we'll do some logging for each row, * and then calculate and print the mean age */ val run = input @@ -310,7 +310,7 @@ object CSVPrinter extends IOApp.Simple { .through(decodeUsingHeaders[Passenger]()) - /** First we'll do some logging for each row, + /** First we'll do some logging for each row, * and then calculate and print the mean age */ val run = input @@ -420,26 +420,66 @@ object PerftConverter extends IOApp.Simple { ``` @:@ -## A helper utility to write out an `Seq[CaseClass]` to CSV +## Writing data to a CSV file -In the presence of a CsvRowEncoder +If you want to save a list of a case class into a CSV file the below helper method may aid you: -``` -import fs2.* +@:select(scala-version) + +@:choice(scala-3) +```scala mdoc:reset:silent +import fs2.io.file.Files import fs2.data.csv.* +import fs2.data.csv.generic.semiauto.* import fs2.io.file.Path +import cats.effect.IO +import fs2.Pipe -given val pEncoder: CsvRowEncoder[CaseClass, String] = deriveCsvRowEncoder +case class YourCaseClass(n: String, i: Int) + +given CsvRowEncoder[YourCaseClass, String] = deriveCsvRowEncoder + +def writeCaseClassToCsv[A] + (path: Path) + (using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = + _.through(encodeUsingFirstHeaders(fullRows = true)) + .through(fs2.text.utf8.encode) + .through(Files[IO].writeAll(path)) + .drain + +// Useage +fs2.Stream.emits(Seq(YourCaseClass("s", 1))).through(writeCaseClassToCsv(Path("temp.csv"))) -def writeCaseClassIterableToFile[A](data: Seq[A], file : Path)(using CsvRowEncoder[A, String]) = - fs2.Stream.emits[IO, A](data) - .through(encodeUsingFirstHeaders(fullRows = true)) - .through(text.utf8Encode) - .through( Files[IO].writeAll(file) ) - .compile - .drain ``` +@:choice(scala-2) + +```scala mdoc:reset:silent +import fs2.io.file.Files +import fs2.data.csv._ +import fs2.data.csv.generic.semiauto._ +import fs2.io.file.Path +import cats.effect.IO +import fs2.Pipe + +case class YourCaseClass(n: String, i: Int) + +implicit val csvRowEncoder: CsvRowEncoder[YourCaseClass, String] = deriveCsvRowEncoder + +def writeCaseClassToCsv[A](path: Path)(using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = + _.through(encodeUsingFirstHeaders(fullRows = true)) + .through(fs2.text.utf8.encode) + .through(Files[IO].writeAll(path)) + .drain + +// Useage +fs2.Stream.emits(Seq(YourCaseClass("s", 1))).through(writeCaseClassToCsv(Path("temp.csv"))) + +``` + +@:@ + + [fs2]: https://fs2.io/#/ [fs2-data-csv]: https://fs2-data.gnieh.org/documentation/csv/ From c4a4a17aad678508f110935715b37dca55b1d52d Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Tue, 12 Sep 2023 11:36:16 +0200 Subject: [PATCH 03/10] Update docs/examples.md agreed Co-authored-by: zetashift --- docs/examples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples.md b/docs/examples.md index d48f986..4817076 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -447,7 +447,7 @@ def writeCaseClassToCsv[A] .through(Files[IO].writeAll(path)) .drain -// Useage +// Usage fs2.Stream.emits(Seq(YourCaseClass("s", 1))).through(writeCaseClassToCsv(Path("temp.csv"))) ``` From 508ac9bcbdc7ac074aa4e96042aceb88e4b6be8f Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Tue, 12 Sep 2023 11:36:33 +0200 Subject: [PATCH 04/10] Update docs/examples.md Co-authored-by: zetashift --- docs/examples.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 4817076..06618cb 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -439,9 +439,7 @@ case class YourCaseClass(n: String, i: Int) given CsvRowEncoder[YourCaseClass, String] = deriveCsvRowEncoder -def writeCaseClassToCsv[A] - (path: Path) - (using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = +def writeCaseClassToCsv[A](path: Path)(using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = _.through(encodeUsingFirstHeaders(fullRows = true)) .through(fs2.text.utf8.encode) .through(Files[IO].writeAll(path)) From 712c167a23cfa1bc4ac134e5225187f90f3558ca Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Thu, 21 Sep 2023 09:47:03 +0200 Subject: [PATCH 05/10] Update docs/examples.md Co-authored-by: zetashift --- docs/examples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples.md b/docs/examples.md index 06618cb..87e7e74 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -422,7 +422,7 @@ object PerftConverter extends IOApp.Simple { ## Writing data to a CSV file -If you want to save a list of a case class into a CSV file the below helper method may aid you: +If you want to save a list of a case class into a CSV file this helper method may aid you: @:select(scala-version) From d00af42027da3234043edacb073de510f7246bcb Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Thu, 21 Sep 2023 09:47:20 +0200 Subject: [PATCH 06/10] Update docs/examples.md Co-authored-by: zetashift --- docs/examples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples.md b/docs/examples.md index 87e7e74..cc20910 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -470,7 +470,7 @@ def writeCaseClassToCsv[A](path: Path)(using CsvRowEncoder[A, String]): Pipe[IO, .through(Files[IO].writeAll(path)) .drain -// Useage +// Usage fs2.Stream.emits(Seq(YourCaseClass("s", 1))).through(writeCaseClassToCsv(Path("temp.csv"))) ``` From e9e95d5d09178484d4e9411258fb129230ee2348 Mon Sep 17 00:00:00 2001 From: zetashift Date: Sun, 29 Oct 2023 12:58:19 +0100 Subject: [PATCH 07/10] Add runnable example for CSV writing example. --- docs/examples.md | 95 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 27 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index cc20910..6a1def6 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -428,51 +428,92 @@ If you want to save a list of a case class into a CSV file this helper method ma @:choice(scala-3) ```scala mdoc:reset:silent +//> using lib "org.typelevel::toolkit::@VERSION@" + import fs2.io.file.Files import fs2.data.csv.* import fs2.data.csv.generic.semiauto.* import fs2.io.file.Path -import cats.effect.IO -import fs2.Pipe +import cats.effect.{IO, IOApp} +import fs2.{Pipe, Stream} +/** Define your case class and derive an encoder for it */ case class YourCaseClass(n: String, i: Int) - given CsvRowEncoder[YourCaseClass, String] = deriveCsvRowEncoder -def writeCaseClassToCsv[A](path: Path)(using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = - _.through(encodeUsingFirstHeaders(fullRows = true)) - .through(fs2.text.utf8.encode) - .through(Files[IO].writeAll(path)) - .drain - -// Usage -fs2.Stream.emits(Seq(YourCaseClass("s", 1))).through(writeCaseClassToCsv(Path("temp.csv"))) - -``` +/** This is our helper function that writes a case class as a csv given a path. */ +def writeCaseClassToCsv[A]( + path: Path +)(using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = + _.through(encodeUsingFirstHeaders(fullRows = true)) + .through(fs2.text.utf8.encode) + .through(Files[IO].writeAll(path)) + .drain + +/** Let's imagine we have a `Book` case class we would like to write to + * a .csv file. + */ +object WriteBooksToCsv extends IOApp.Simple: + case class Book(id: Long, name: String, isbn: String) + given CsvRowEncoder[Book, String] = deriveCsvRowEncoder + + val input = Seq( + Book(1, "Programming in Scala", "9780997148008"), + Book(2, "Hands-on Scala Programming", "9798387677205"), + Book(3, "Functional Programming in Scala", "9781617299582") + ) + + def run: IO[Unit] = + Stream + .emits(input) + .through(writeCaseClassToCsv(Path("books.csv"))) + .compile + .drain *> IO.println("Finished writing books to books.csv.")``` @:choice(scala-2) ```scala mdoc:reset:silent +//> using lib "org.typelevel::toolkit::@VERSION@" + import fs2.io.file.Files -import fs2.data.csv._ -import fs2.data.csv.generic.semiauto._ +import fs2.data.csv.* +import fs2.data.csv.generic.semiauto.* import fs2.io.file.Path -import cats.effect.IO -import fs2.Pipe +import cats.effect.{IO, IOApp} +import fs2.{Pipe, Stream} +/** Define your case class and derive an encoder for it */ case class YourCaseClass(n: String, i: Int) - implicit val csvRowEncoder: CsvRowEncoder[YourCaseClass, String] = deriveCsvRowEncoder -def writeCaseClassToCsv[A](path: Path)(using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = - _.through(encodeUsingFirstHeaders(fullRows = true)) - .through(fs2.text.utf8.encode) - .through(Files[IO].writeAll(path)) - .drain - -// Usage -fs2.Stream.emits(Seq(YourCaseClass("s", 1))).through(writeCaseClassToCsv(Path("temp.csv"))) - +/** This is our helper function that writes a case class as a csv given a path. */ +def writeCaseClassToCsv[A]( + path: Path +)(using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = + _.through(encodeUsingFirstHeaders(fullRows = true)) + .through(fs2.text.utf8.encode) + .through(Files[IO].writeAll(path)) + .drain + +/** Let's imagine we have a `Book` case class we would like to write to + * a .csv file. + */ +object WriteBooksToCsv extends IOApp.Simple: + case class Book(id: Long, name: String, isbn: String) + implicit val csvRowEncoder: [Book, String] = deriveCsvRowEncoder + + val input = Seq( + Book(1, "Programming in Scala", "9780997148008"), + Book(2, "Hands-on Scala Programming", "9798387677205"), + Book(3, "Functional Programming in Scala", "9781617299582") + ) + + def run: IO[Unit] = + Stream + .emits(input) + .through(writeCaseClassToCsv(Path("books.csv"))) + .compile + .drain *> IO.println("Finished writing books to books.csv.") ``` @:@ From be34afb46d9bbb1583f7de25a49516541e0538b5 Mon Sep 17 00:00:00 2001 From: zetashift Date: Sun, 29 Oct 2023 18:21:05 +0100 Subject: [PATCH 08/10] Fix CSV writing example --- docs/examples.md | 51 ++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 6a1def6..1cbcab0 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -430,10 +430,9 @@ If you want to save a list of a case class into a CSV file this helper method ma ```scala mdoc:reset:silent //> using lib "org.typelevel::toolkit::@VERSION@" -import fs2.io.file.Files import fs2.data.csv.* import fs2.data.csv.generic.semiauto.* -import fs2.io.file.Path +import fs2.io.file.{Files, Path} import cats.effect.{IO, IOApp} import fs2.{Pipe, Stream} @@ -468,39 +467,48 @@ object WriteBooksToCsv extends IOApp.Simple: .emits(input) .through(writeCaseClassToCsv(Path("books.csv"))) .compile - .drain *> IO.println("Finished writing books to books.csv.")``` + .drain *> IO.println("Finished writing books to books.csv.") +``` @:choice(scala-2) ```scala mdoc:reset:silent //> using lib "org.typelevel::toolkit::@VERSION@" -import fs2.io.file.Files -import fs2.data.csv.* -import fs2.data.csv.generic.semiauto.* -import fs2.io.file.Path +import fs2.data.csv._ +import fs2.data.csv.generic.semiauto._ +import fs2.io.file.{Files, Path} import cats.effect.{IO, IOApp} import fs2.{Pipe, Stream} /** Define your case class and derive an encoder for it */ case class YourCaseClass(n: String, i: Int) -implicit val csvRowEncoder: CsvRowEncoder[YourCaseClass, String] = deriveCsvRowEncoder +object YourCaseClass { + implicit val csvRowEncoder: CsvRowEncoder[YourCaseClass, String] = + deriveCsvRowEncoder +} -/** This is our helper function that writes a case class as a csv given a path. */ -def writeCaseClassToCsv[A]( - path: Path -)(using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = - _.through(encodeUsingFirstHeaders(fullRows = true)) - .through(fs2.text.utf8.encode) - .through(Files[IO].writeAll(path)) - .drain +object Helpers { -/** Let's imagine we have a `Book` case class we would like to write to - * a .csv file. + /** This is our helper function that writes a case class as a csv given a + * path. + */ + def writeCaseClassToCsv[A]( + path: Path + )(implicit encoder: CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = + _.through(encodeUsingFirstHeaders(fullRows = true)) + .through(fs2.text.utf8.encode) + .through(Files[IO].writeAll(path)) + .drain + +} + +/** Let's imagine we have a `Book` case class we would like to write to a .csv + * file. */ -object WriteBooksToCsv extends IOApp.Simple: +object WriteBooksToCsv extends IOApp.Simple { case class Book(id: Long, name: String, isbn: String) - implicit val csvRowEncoder: [Book, String] = deriveCsvRowEncoder + implicit val csvRowEncoder: CsvRowEncoder[Book, String] = deriveCsvRowEncoder val input = Seq( Book(1, "Programming in Scala", "9780997148008"), @@ -511,9 +519,10 @@ object WriteBooksToCsv extends IOApp.Simple: def run: IO[Unit] = Stream .emits(input) - .through(writeCaseClassToCsv(Path("books.csv"))) + .through(Helpers.writeCaseClassToCsv(Path("books.csv"))) .compile .drain *> IO.println("Finished writing books to books.csv.") +} ``` @:@ From b2065d2c46c64a01ac8bad3b3073484810409a18 Mon Sep 17 00:00:00 2001 From: zetashift Date: Tue, 31 Oct 2023 22:50:27 +0100 Subject: [PATCH 09/10] Remove redundant drain from CSV writing example --- docs/examples.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 1cbcab0..8f755ae 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -447,7 +447,6 @@ def writeCaseClassToCsv[A]( _.through(encodeUsingFirstHeaders(fullRows = true)) .through(fs2.text.utf8.encode) .through(Files[IO].writeAll(path)) - .drain /** Let's imagine we have a `Book` case class we would like to write to * a .csv file. @@ -499,7 +498,6 @@ object Helpers { _.through(encodeUsingFirstHeaders(fullRows = true)) .through(fs2.text.utf8.encode) .through(Files[IO].writeAll(path)) - .drain } From e173a24fdf116e69ee1e43b07b7425aa36c74e19 Mon Sep 17 00:00:00 2001 From: zetashift Date: Tue, 31 Oct 2023 23:15:11 +0100 Subject: [PATCH 10/10] Fixup code explanation for CSV writing. --- docs/examples.md | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 8f755ae..90a4c5e 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -422,7 +422,23 @@ object PerftConverter extends IOApp.Simple { ## Writing data to a CSV file -If you want to save a list of a case class into a CSV file this helper method may aid you: +If you want to save a list of a case class into a CSV file this utility may aid you: + +```scala +// Define your case class and derive an encoder for it +case class YourCaseClass(n: String, i: Int) +given CsvRowEncoder[YourCaseClass, String] = deriveCsvRowEncoder + +// Writes a case class as a csv given a path. +def writeCaseClassToCsv[A]( + path: Path +)(using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = + _.through(encodeUsingFirstHeaders(fullRows = true)) + .through(fs2.text.utf8.encode) + .through(Files[IO].writeAll(path)) +``` + +As an example, let's imagine we have a `Book` class we would like to write to a `.csv` file. @:select(scala-version) @@ -436,11 +452,6 @@ import fs2.io.file.{Files, Path} import cats.effect.{IO, IOApp} import fs2.{Pipe, Stream} -/** Define your case class and derive an encoder for it */ -case class YourCaseClass(n: String, i: Int) -given CsvRowEncoder[YourCaseClass, String] = deriveCsvRowEncoder - -/** This is our helper function that writes a case class as a csv given a path. */ def writeCaseClassToCsv[A]( path: Path )(using CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = @@ -448,9 +459,7 @@ def writeCaseClassToCsv[A]( .through(fs2.text.utf8.encode) .through(Files[IO].writeAll(path)) -/** Let's imagine we have a `Book` case class we would like to write to - * a .csv file. - */ + object WriteBooksToCsv extends IOApp.Simple: case class Book(id: Long, name: String, isbn: String) given CsvRowEncoder[Book, String] = deriveCsvRowEncoder @@ -480,30 +489,15 @@ import fs2.io.file.{Files, Path} import cats.effect.{IO, IOApp} import fs2.{Pipe, Stream} -/** Define your case class and derive an encoder for it */ -case class YourCaseClass(n: String, i: Int) -object YourCaseClass { - implicit val csvRowEncoder: CsvRowEncoder[YourCaseClass, String] = - deriveCsvRowEncoder -} - object Helpers { - - /** This is our helper function that writes a case class as a csv given a - * path. - */ def writeCaseClassToCsv[A]( path: Path )(implicit encoder: CsvRowEncoder[A, String]): Pipe[IO, A, Nothing] = _.through(encodeUsingFirstHeaders(fullRows = true)) .through(fs2.text.utf8.encode) .through(Files[IO].writeAll(path)) - } -/** Let's imagine we have a `Book` case class we would like to write to a .csv - * file. - */ object WriteBooksToCsv extends IOApp.Simple { case class Book(id: Long, name: String, isbn: String) implicit val csvRowEncoder: CsvRowEncoder[Book, String] = deriveCsvRowEncoder