From ef20502683968b0b56cbe7875df17f91caccde8e Mon Sep 17 00:00:00 2001 From: partens Date: Mon, 11 Aug 2025 14:37:07 +0200 Subject: [PATCH 1/7] experimental tprint --- build.mill | 5 +- scautable/src/tprint.scala | 18 +++++++ scautable/test/src/tprintTest.scala | 76 +++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 scautable/src/tprint.scala create mode 100644 scautable/test/src/tprintTest.scala diff --git a/build.mill b/build.mill index b5478e005..8b3a780e8 100644 --- a/build.mill +++ b/build.mill @@ -13,7 +13,7 @@ object Config { val oslib = ivy"com.lihaoyi::os-lib:0.11.4" val scalatags = ivy"com.lihaoyi::scalatags::0.13.1" val fansi = ivy"com.lihaoyi::fansi::0.5.0" - + val pprint = ivy"com.lihaoyi::pprint::0.9.3" val laminar = ivy"com.raquo::laminar::17.2.1" @@ -29,7 +29,8 @@ trait Common extends ScalaModule { override def ivyDeps = super.ivyDeps() ++ Agg( Config.scalatags, Config.oslib, - Config.fansi + Config.fansi, + Config.pprint ) override def scalacOptions: T[Seq[String]] = super.scalacOptions() ++ Seq("-Xmax-inlines", "128") } diff --git a/scautable/src/tprint.scala b/scautable/src/tprint.scala new file mode 100644 index 000000000..23acef4ea --- /dev/null +++ b/scautable/src/tprint.scala @@ -0,0 +1,18 @@ +package io.github.quafadas.scautable + +import pprint.TPrint +import pprint.TPrintColors + +object CsvIteratorTPrint: + given csvIterTPrint[K <: Tuple]: TPrint[CsvIterator[K]] = new TPrint[CsvIterator[K]]: + def render(implicit tpc: TPrintColors): fansi.Str = + fansi.Str("CsvIterator[K <: Tuple]") + + extension [K <: Tuple](csvIterator: CsvIterator[K]) + def prettyPrint(implicit tpc: TPrintColors): fansi.Str = + val columnNames = csvIterator.headers + val colTypes = List.fill(columnNames.size)("String") + val coltypes = columnNames.zip(colTypes).map { case (name, colType) => + fansi.Str(s"$name: $colType,") + }.mkString("[\n\t", "\n\t", "\n]") + fansi.Str("CsvIterator") ++ fansi.Str(coltypes) \ No newline at end of file diff --git a/scautable/test/src/tprintTest.scala b/scautable/test/src/tprintTest.scala new file mode 100644 index 000000000..7c3c27316 --- /dev/null +++ b/scautable/test/src/tprintTest.scala @@ -0,0 +1,76 @@ +package io.github.quafadas.scautable + +import pprint.TPrint +import pprint.TPrintColors + +class TPrintSuite extends munit.FunSuite: + + test("CsvIterator TPrint should render type info") { + val csv: CsvIterator[("col1", "col2", "col3")] = CSV.resource("simple.csv") + val tprint = summon[TPrint[CsvIterator[("col1", "col2", "col3")]]] + implicit val colors: TPrintColors = TPrintColors.BlackWhite + val rendered = tprint.render.toString() + // Just verify the output contains CsvIterator - the exact format doesn't matter as much + assert(rendered.contains("CsvIterator"), s"Expected 'CsvIterator' in '$rendered'") + } + + test("CsvIterator prettyPrint extension should show column details") { + import io.github.quafadas.scautable.CsvIteratorTPrint.* + + // Create a CSV iterator with known columns + val csv: CsvIterator[("col1", "col2", "col3")] = CSV.resource("simple.csv") + + implicit val colors: TPrintColors = TPrintColors.BlackWhite + + // Use the extension method directly + val prettyOutput = prettyPrint(csv).plainText + + println(prettyOutput) + + // Verify the output contains the expected column information + assert(prettyOutput.contains("CsvIterator"), s"Expected 'CsvIterator' in '$prettyOutput'") + assert(prettyOutput.contains("\tcol1: String,"), s"Expected 'col1: String' in '$prettyOutput'") + assert(prettyOutput.contains("\tcol2: String,"), s"Expected 'col2: String' in '$prettyOutput'") + assert(prettyOutput.contains("col3: String"), s"Expected 'col3: String' in '$prettyOutput'") + + // The prettyPrint output looks good from the debug output + assert(prettyOutput.startsWith("CsvIterator[")) + assert(prettyOutput.endsWith("]")) + } + + test("CsvIterator prettyPrint should work with different column names") { + import io.github.quafadas.scautable.CsvIteratorTPrint.* + + // Test with different column names + val csv: CsvIterator[("name", "age")] = CSV.fromString("name,age\nAlice,25\nBob,30") + + implicit val colors: TPrintColors = TPrintColors.BlackWhite + + val prettyOutput = prettyPrint(csv).plainText + println(prettyOutput) + + assert(prettyOutput.contains("CsvIterator"), s"Expected 'CsvIterator' in '$prettyOutput'") + assert(prettyOutput.contains("\tname: String,"), s"Expected 'name: String' in '$prettyOutput'") + assert(prettyOutput.contains("age: String,"), s"Expected 'age: String' in '$prettyOutput'") + + assert(prettyOutput.startsWith("CsvIterator[")) + assert(prettyOutput.endsWith("]")) + } + + test("CsvIterator prettyPrint should handle single column") { + import io.github.quafadas.scautable.CsvIteratorTPrint.* + + // Test with a single column CSV from string + val csv = CSV.fromString("singleCol\nvalue1\nvalue2") + + implicit val colors: TPrintColors = TPrintColors.BlackWhite + + val prettyOutput = prettyPrint(csv).plainText + println(prettyOutput) + + assert(prettyOutput.contains("CsvIterator"), s"Expected 'CsvIterator' in '$prettyOutput'") + assert(prettyOutput.contains("singleCol: String"), s"Expected 'singleCol: String' in '$prettyOutput'") + + assert(prettyOutput.startsWith("CsvIterator[")) + assert(prettyOutput.endsWith("]")) + } From a80d7ca11ac9c6eb553d37ea318b7986499b7b6a Mon Sep 17 00:00:00 2001 From: partens Date: Mon, 11 Aug 2025 14:41:54 +0200 Subject: [PATCH 2/7] . --- scautable/test/{src => src-jvm}/tprintTest.scala | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scautable/test/{src => src-jvm}/tprintTest.scala (100%) diff --git a/scautable/test/src/tprintTest.scala b/scautable/test/src-jvm/tprintTest.scala similarity index 100% rename from scautable/test/src/tprintTest.scala rename to scautable/test/src-jvm/tprintTest.scala From 23e58852f37498f7df3e1f7e20c4b427ad81d445 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Mon, 11 Aug 2025 14:51:23 +0200 Subject: [PATCH 3/7] Update scautable/src/tprint.scala Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scautable/src/tprint.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scautable/src/tprint.scala b/scautable/src/tprint.scala index 23acef4ea..283152284 100644 --- a/scautable/src/tprint.scala +++ b/scautable/src/tprint.scala @@ -12,7 +12,7 @@ object CsvIteratorTPrint: def prettyPrint(implicit tpc: TPrintColors): fansi.Str = val columnNames = csvIterator.headers val colTypes = List.fill(columnNames.size)("String") - val coltypes = columnNames.zip(colTypes).map { case (name, colType) => - fansi.Str(s"$name: $colType,") - }.mkString("[\n\t", "\n\t", "\n]") + val coltypes = columnNames.zip(colTypes) + .map { case (name, colType) => fansi.Str(s"$name: $colType") } + .mkString("[\n\t", ",\n\t", "\n]") fansi.Str("CsvIterator") ++ fansi.Str(coltypes) \ No newline at end of file From 20fa06513a652f2e97cfdd045df64a099bc825eb Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Mon, 11 Aug 2025 15:20:04 +0200 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scautable/test/src-jvm/tprintTest.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/scautable/test/src-jvm/tprintTest.scala b/scautable/test/src-jvm/tprintTest.scala index 7c3c27316..1288c81c9 100644 --- a/scautable/test/src-jvm/tprintTest.scala +++ b/scautable/test/src-jvm/tprintTest.scala @@ -25,7 +25,6 @@ class TPrintSuite extends munit.FunSuite: // Use the extension method directly val prettyOutput = prettyPrint(csv).plainText - println(prettyOutput) // Verify the output contains the expected column information assert(prettyOutput.contains("CsvIterator"), s"Expected 'CsvIterator' in '$prettyOutput'") @@ -47,7 +46,6 @@ class TPrintSuite extends munit.FunSuite: implicit val colors: TPrintColors = TPrintColors.BlackWhite val prettyOutput = prettyPrint(csv).plainText - println(prettyOutput) assert(prettyOutput.contains("CsvIterator"), s"Expected 'CsvIterator' in '$prettyOutput'") assert(prettyOutput.contains("\tname: String,"), s"Expected 'name: String' in '$prettyOutput'") @@ -66,7 +64,6 @@ class TPrintSuite extends munit.FunSuite: implicit val colors: TPrintColors = TPrintColors.BlackWhite val prettyOutput = prettyPrint(csv).plainText - println(prettyOutput) assert(prettyOutput.contains("CsvIterator"), s"Expected 'CsvIterator' in '$prettyOutput'") assert(prettyOutput.contains("singleCol: String"), s"Expected 'singleCol: String' in '$prettyOutput'") From 68d212f9b8e0764a989fe3a9130fac068c046ba3 Mon Sep 17 00:00:00 2001 From: partens Date: Mon, 11 Aug 2025 15:24:12 +0200 Subject: [PATCH 5/7] . --- scautable/test/src-jvm/tprintTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scautable/test/src-jvm/tprintTest.scala b/scautable/test/src-jvm/tprintTest.scala index 1288c81c9..9f70f9f8a 100644 --- a/scautable/test/src-jvm/tprintTest.scala +++ b/scautable/test/src-jvm/tprintTest.scala @@ -49,7 +49,7 @@ class TPrintSuite extends munit.FunSuite: assert(prettyOutput.contains("CsvIterator"), s"Expected 'CsvIterator' in '$prettyOutput'") assert(prettyOutput.contains("\tname: String,"), s"Expected 'name: String' in '$prettyOutput'") - assert(prettyOutput.contains("age: String,"), s"Expected 'age: String' in '$prettyOutput'") + assert(prettyOutput.contains("\tage: String"), s"Expected 'age: String' in '$prettyOutput'") assert(prettyOutput.startsWith("CsvIterator[")) assert(prettyOutput.endsWith("]")) From ca6b32e03e8cd7fb62459a63e5b42b2863c08857 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 15 Aug 2025 12:59:49 +0200 Subject: [PATCH 6/7] . --- scautable/src/tprint.scala | 25 ++++++++++++++++++------- scautable/test/src-jvm/tprintTest.scala | 8 ++++---- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/scautable/src/tprint.scala b/scautable/src/tprint.scala index 283152284..db0a08d91 100644 --- a/scautable/src/tprint.scala +++ b/scautable/src/tprint.scala @@ -4,15 +4,26 @@ import pprint.TPrint import pprint.TPrintColors object CsvIteratorTPrint: - given csvIterTPrint[K <: Tuple]: TPrint[CsvIterator[K]] = new TPrint[CsvIterator[K]]: + given csvIterTPrint[K <: Tuple, V <: Tuple]: TPrint[CsvIterator[K, V]] = new TPrint[CsvIterator[K, V]]: def render(implicit tpc: TPrintColors): fansi.Str = - fansi.Str("CsvIterator[K <: Tuple]") - - extension [K <: Tuple](csvIterator: CsvIterator[K]) - def prettyPrint(implicit tpc: TPrintColors): fansi.Str = + fansi.Str("CsvIterator[K <: Tuple, V <: Tuple]") + + import scala.compiletime.{erasedValue, summonInline} + import scala.deriving.Mirror + import scala.annotation.tailrec + + + inline def getTypeNames[T <: Tuple](implicit tpc: TPrintColors): List[String] = + inline erasedValue[T] match + case _: EmptyTuple => Nil + case _: (h *: t) => summonInline[TPrint[h]].render(tpc).toString :: getTypeNames[t] + + extension [K <: Tuple, V <: Tuple](csvIterator: CsvIterator[K, V]) + inline def prettyPrint(implicit tpc: TPrintColors): fansi.Str = val columnNames = csvIterator.headers - val colTypes = List.fill(columnNames.size)("String") - val coltypes = columnNames.zip(colTypes) + + val colTypes = getTypeNames[V] + val coltypes = columnNames.zipAll(colTypes, "", "") .map { case (name, colType) => fansi.Str(s"$name: $colType") } .mkString("[\n\t", ",\n\t", "\n]") fansi.Str("CsvIterator") ++ fansi.Str(coltypes) \ No newline at end of file diff --git a/scautable/test/src-jvm/tprintTest.scala b/scautable/test/src-jvm/tprintTest.scala index 9f70f9f8a..d42105fe9 100644 --- a/scautable/test/src-jvm/tprintTest.scala +++ b/scautable/test/src-jvm/tprintTest.scala @@ -6,8 +6,8 @@ import pprint.TPrintColors class TPrintSuite extends munit.FunSuite: test("CsvIterator TPrint should render type info") { - val csv: CsvIterator[("col1", "col2", "col3")] = CSV.resource("simple.csv") - val tprint = summon[TPrint[CsvIterator[("col1", "col2", "col3")]]] + val csv: CsvIterator[("col1", "col2", "col3"), (String, String, String)] = CSV.resource("simple.csv") + val tprint = summon[TPrint[CsvIterator[("col1", "col2", "col3"), (String, String, String)]]] implicit val colors: TPrintColors = TPrintColors.BlackWhite val rendered = tprint.render.toString() // Just verify the output contains CsvIterator - the exact format doesn't matter as much @@ -18,7 +18,7 @@ class TPrintSuite extends munit.FunSuite: import io.github.quafadas.scautable.CsvIteratorTPrint.* // Create a CSV iterator with known columns - val csv: CsvIterator[("col1", "col2", "col3")] = CSV.resource("simple.csv") + val csv: CsvIterator[("col1", "col2", "col3"), (String, String, String)] = CSV.resource("simple.csv") implicit val colors: TPrintColors = TPrintColors.BlackWhite @@ -41,7 +41,7 @@ class TPrintSuite extends munit.FunSuite: import io.github.quafadas.scautable.CsvIteratorTPrint.* // Test with different column names - val csv: CsvIterator[("name", "age")] = CSV.fromString("name,age\nAlice,25\nBob,30") + val csv: CsvIterator[("name", "age"), (String, String)] = CSV.fromString("name,age\nAlice,25\nBob,30") implicit val colors: TPrintColors = TPrintColors.BlackWhite From cf1b6bddf3098394d606932013f943a26b5fd04c Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Fri, 15 Aug 2025 13:45:42 +0200 Subject: [PATCH 7/7] . --- examples/src/titanic.scala | 19 +++++++++++-------- scautable/src/columnExtensions.scala | 10 ++++++++++ scautable/src/tprint.scala | 4 ++-- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/examples/src/titanic.scala b/examples/src/titanic.scala index f3be0245f..b347cff13 100644 --- a/examples/src/titanic.scala +++ b/examples/src/titanic.scala @@ -17,6 +17,8 @@ import io.github.quafadas.scautable.ColumnTyped.IsNumeric import io.github.quafadas.scautable.ColumnTyped.GetTypeAtName import io.github.quafadas.scautable.ColumnTyped.AllAreColumns import scala.concurrent.Future +import io.github.quafadas.scautable.CsvIteratorTPrint +import scala.util.Try enum Gender: case Male, Female, Unknown @@ -46,15 +48,16 @@ end Gender */ @main def titanic = - val titanic = CSV.resource("titanic.csv") + val titanic = CSV.resource("titanic.csv", TypeInferrer.Auto) - val data = titanic.toSeq - .mapColumn["Sex", Gender]((x: String) => Gender.valueOf(x.capitalize)) - .dropColumn["PassengerId"] - .mapColumn["Age", Option[Double]](_.toDoubleOption) - .mapColumn["Survived", Boolean](_ == "1") - .mapColumn["Fare", Double](_.toDouble) - .addColumn["AgeIsDefined", Boolean](_.Age.isDefined) + val data = LazyList.from( + titanic + .mapColumn["Sex", Gender]((x: String) => Gender.valueOf(x.capitalize)) + .dropColumn["PassengerId"] + .mapColumn["Age", Option[Double]](a =>Try(a.toDouble).toOption) + .mapColumn["Survived", Boolean](_ == 1) + .addColumn["AgeIsDefined", Boolean](_.Age.isDefined) + ) val surived: (survivied: Int, total: Int, pct: Double) = data .column["Survived"] diff --git a/scautable/src/columnExtensions.scala b/scautable/src/columnExtensions.scala index 932b8ac6c..31b2f000e 100644 --- a/scautable/src/columnExtensions.scala +++ b/scautable/src/columnExtensions.scala @@ -22,6 +22,16 @@ object NamedTupleIteratorExtensions: extension [K <: Tuple, V <: Tuple](itr: Iterator[NamedTuple[K, V]]) + inline def info: fansi.Str = + val headers = constValueTuple[K].toList.map(_.toString()) + + val colTypes = CsvIteratorTPrint.getTypeNames[V] + val coltypes = headers.zipAll(colTypes, "", "") + .map { case (name, colType) => fansi.Color.Red(name) ++ fansi.Str(": ") ++ fansi.Color.Green(colType) } + .mkString("[\n\t", ",\n\t", "\n]") + fansi.Str(coltypes) + + inline def numericTypeTest: (List[ConversionAcc], Long) = val headers = constValueTuple[K].toList.map(_.toString()) val headerAcc = headers.map(_ => ConversionAcc(0, 0, 0)) diff --git a/scautable/src/tprint.scala b/scautable/src/tprint.scala index db0a08d91..f546559fc 100644 --- a/scautable/src/tprint.scala +++ b/scautable/src/tprint.scala @@ -13,13 +13,13 @@ object CsvIteratorTPrint: import scala.annotation.tailrec - inline def getTypeNames[T <: Tuple](implicit tpc: TPrintColors): List[String] = + inline def getTypeNames[T <: Tuple](using tpc: TPrintColors): List[String] = inline erasedValue[T] match case _: EmptyTuple => Nil case _: (h *: t) => summonInline[TPrint[h]].render(tpc).toString :: getTypeNames[t] extension [K <: Tuple, V <: Tuple](csvIterator: CsvIterator[K, V]) - inline def prettyPrint(implicit tpc: TPrintColors): fansi.Str = + inline def prettyPrint(using tpc: TPrintColors): fansi.Str = val columnNames = csvIterator.headers val colTypes = getTypeNames[V]