diff --git a/build.mill b/build.mill index f17139767..1a6387905 100644 --- a/build.mill +++ b/build.mill @@ -13,7 +13,7 @@ object Config { val oslib = ivy"com.lihaoyi::os-lib:0.11.5" val scalatags = ivy"com.lihaoyi::scalatags::0.13.1" val fansi = ivy"com.lihaoyi::fansi::0.5.1" - + 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/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 new file mode 100644 index 000000000..f546559fc --- /dev/null +++ b/scautable/src/tprint.scala @@ -0,0 +1,29 @@ +package io.github.quafadas.scautable + +import pprint.TPrint +import pprint.TPrintColors + +object CsvIteratorTPrint: + 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, V <: Tuple]") + + import scala.compiletime.{erasedValue, summonInline} + import scala.deriving.Mirror + import scala.annotation.tailrec + + + 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(using tpc: TPrintColors): fansi.Str = + val columnNames = csvIterator.headers + + 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 new file mode 100644 index 000000000..d42105fe9 --- /dev/null +++ b/scautable/test/src-jvm/tprintTest.scala @@ -0,0 +1,73 @@ +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"), (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 + 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"), (String, String, String)] = CSV.resource("simple.csv") + + implicit val colors: TPrintColors = TPrintColors.BlackWhite + + // Use the extension method directly + val prettyOutput = prettyPrint(csv).plainText + + + // 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"), (String, String)] = CSV.fromString("name,age\nAlice,25\nBob,30") + + implicit val colors: TPrintColors = TPrintColors.BlackWhite + + val prettyOutput = prettyPrint(csv).plainText + + assert(prettyOutput.contains("CsvIterator"), s"Expected 'CsvIterator' in '$prettyOutput'") + assert(prettyOutput.contains("\tname: String,"), s"Expected 'name: String' in '$prettyOutput'") + assert(prettyOutput.contains("\tage: 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 + + 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("]")) + }