From dc6bcee1f18ab1137be288b3ebc57e63cca7a0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=A6=E5=A2=83=E8=BF=B7=E7=A6=BB?= Date: Sat, 14 May 2022 00:44:25 +0800 Subject: [PATCH] support read/write file --- .gitignore | 1 + build.sbt | 2 +- .../org/bitlap/csv/core/CsvableBuilder.scala | 11 +++ .../scala/org/bitlap/csv/core/FileUtils.scala | 82 +++++++++++++++++++ .../org/bitlap/csv/core/ScalableBuilder.scala | 12 ++- .../org/bitlap/csv/core/ScalableHelper.scala | 23 ++---- .../core/macros/DeriveCsvableBuilder.scala | 80 +++++++++++------- .../core/macros/DeriveScalableBuilder.scala | 70 +++++++++------- .../csv/core/macros/DeriveToCaseClass.scala | 8 +- .../csv/core/macros/DeriveToString.scala | 9 +- .../core/test/CsvableAndScalableTest.scala | 27 ++++++ 11 files changed, 238 insertions(+), 87 deletions(-) create mode 100644 smt-csv-core/src/main/scala/org/bitlap/csv/core/FileUtils.scala rename smt-csv-core/src/main/{scala-2.13 => scala}/org/bitlap/csv/core/ScalableHelper.scala (76%) diff --git a/.gitignore b/.gitignore index d0ac046..e5b7ecb 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ .idea/ *.iml .bsp +*.csv examples/scala2-11/target/ examples/scala2-12/target/ examples/scala2-13/target/ diff --git a/build.sbt b/build.sbt index 8367ff2..17f666f 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ ThisBuild / resolvers ++= Seq( lazy val scala212 = "2.12.14" lazy val scala211 = "2.11.12" lazy val scala213 = "2.13.8" -lazy val lastVersionForExamples = "0.4.2" +lazy val lastVersionForExamples = "0.5.2" lazy val scalatestVersion = "3.2.12" lazy val zioVersion = "1.0.14" diff --git a/smt-csv-core/src/main/scala/org/bitlap/csv/core/CsvableBuilder.scala b/smt-csv-core/src/main/scala/org/bitlap/csv/core/CsvableBuilder.scala index 1d421bf..cc2d88f 100644 --- a/smt-csv-core/src/main/scala/org/bitlap/csv/core/CsvableBuilder.scala +++ b/smt-csv-core/src/main/scala/org/bitlap/csv/core/CsvableBuilder.scala @@ -31,6 +31,8 @@ import org.bitlap.csv.core.macros.DeriveCsvableBuilder */ class CsvableBuilder[T] { + import java.io.File + /** * Convert this CSV column string to any Scala types. * @@ -70,6 +72,15 @@ class CsvableBuilder[T] { */ def convert(ts: List[T]): String = macro DeriveCsvableBuilder.convertDefaultImpl[T] + /** + * Convert the sequence of Scala case class to CSV string and write to file. + * + * @param ts The sequence of Scala case class. + * @param file File to save CSV string. + * @return + */ + def writeTo(ts: List[T], file: File): Boolean = macro DeriveCsvableBuilder.writeToFileImpl[T] + } object CsvableBuilder { diff --git a/smt-csv-core/src/main/scala/org/bitlap/csv/core/FileUtils.scala b/smt-csv-core/src/main/scala/org/bitlap/csv/core/FileUtils.scala new file mode 100644 index 0000000..a571de8 --- /dev/null +++ b/smt-csv-core/src/main/scala/org/bitlap/csv/core/FileUtils.scala @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 bitlap + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.bitlap.csv.core + +import java.io._ +import scala.io.Source +import scala.language.reflectiveCalls +import scala.util.control.Exception.ignoring + +import scala.collection.mutable.ListBuffer + +/** + * @author 梦境迷离 + * @version 1.0,5/13/22 + */ +object FileUtils { + + type Closable = { + def close(): Unit + } + + def using[R <: Closable, T](resource: => R)(f: R => T): T = + try f(resource) + finally ignoring(classOf[Throwable]) apply { + resource.close() + } + + def writer(file: File, lines: List[String]): Boolean = { + checkFile(file) + val bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file)) + try using(new PrintWriter(bufferedOutputStream, true)) { r => + lines.foreach(r.println) + } finally bufferedOutputStream.close() + true + } + + def reader(file: InputStream, charset: String = "UTF-8"): List[String] = + try using(Source.fromInputStream(new BufferedInputStream(file), charset)) { lines => + lines.getLines().toList + } finally file.close() + + def checkFile(file: File): Unit = { + if (file.isDirectory) { + throw new Exception(s"File path: $file is a directory.") + } + if (!file.exists()) { + file.createNewFile() + } + } + + def readFileFunc[T](reader: BufferedReader, func: String => Option[T]): List[Option[T]] = { + val ts = ListBuffer[Option[T]]() + var line: String = null + FileUtils.using(new BufferedReader(reader)) { input => + while ({ + line = input.readLine() + line != null + }) + ts.append(func(line)) + } + ts.result() + } +} diff --git a/smt-csv-core/src/main/scala/org/bitlap/csv/core/ScalableBuilder.scala b/smt-csv-core/src/main/scala/org/bitlap/csv/core/ScalableBuilder.scala index 2c7567c..9d3abca 100644 --- a/smt-csv-core/src/main/scala/org/bitlap/csv/core/ScalableBuilder.scala +++ b/smt-csv-core/src/main/scala/org/bitlap/csv/core/ScalableBuilder.scala @@ -22,6 +22,7 @@ package org.bitlap.csv.core import org.bitlap.csv.core.macros.DeriveScalableBuilder +import java.io.InputStream /** * Builder to create a custom Csv Decoder. @@ -59,7 +60,7 @@ class ScalableBuilder[T] { /** * Convert all CSV lines to the sequence of Scala case class. * - * @param lines All CSV lines. + * @param lines All CSV lines. * @param columnSeparator The separator for CSV column value. * @return */ @@ -70,6 +71,15 @@ class ScalableBuilder[T] { */ def convert(lines: List[String]): List[Option[T]] = macro DeriveScalableBuilder.convertDefaultImpl[T] + /** + * Read all CSV lines of the file and convert them to the sequence of Scala case class. + * + * @param file InputStream of the CSV file. + * @param charset String charset of the CSV file content. + * @return + */ + def readFrom(file: InputStream, charset: String): List[Option[T]] = macro DeriveScalableBuilder.readFromFileImpl[T] + } object ScalableBuilder { diff --git a/smt-csv-core/src/main/scala-2.13/org/bitlap/csv/core/ScalableHelper.scala b/smt-csv-core/src/main/scala/org/bitlap/csv/core/ScalableHelper.scala similarity index 76% rename from smt-csv-core/src/main/scala-2.13/org/bitlap/csv/core/ScalableHelper.scala rename to smt-csv-core/src/main/scala/org/bitlap/csv/core/ScalableHelper.scala index 12993bd..bffcbf3 100644 --- a/smt-csv-core/src/main/scala-2.13/org/bitlap/csv/core/ScalableHelper.scala +++ b/smt-csv-core/src/main/scala/org/bitlap/csv/core/ScalableHelper.scala @@ -21,32 +21,23 @@ package org.bitlap.csv.core -import java.io.{ BufferedReader, InputStreamReader } -import scala.collection.mutable.ListBuffer -import scala.util.Using +import java.io.{ BufferedReader, File, FileReader, InputStreamReader } /** * Tool class for parsing CSV files. * * @author 梦境迷离 - * @version 1.0,2022/5/2 + * @version 1.0,2022/5/13 */ object ScalableHelper { def readCsvFromClassPath[T <: Product](fileName: String)(func: String => Option[T]): List[Option[T]] = { - val ts = ListBuffer[Option[T]]() val reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(fileName)) - val bufferedReader = new BufferedReader(reader) - var line: String = null - Using.resource(bufferedReader) { input => - while ({ - line = input.readLine() - line != null - }) - ts.append(func(line)) - } - - ts.result() + FileUtils.readFileFunc[T](new BufferedReader(reader), func) } + def readCsvFromFile[T <: Product](file: File)(func: String => Option[T]): List[Option[T]] = { + val reader = new BufferedReader(new FileReader(file)) + FileUtils.readFileFunc[T](reader, func) + } } diff --git a/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveCsvableBuilder.scala b/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveCsvableBuilder.scala index 8c3f650..e42f1fa 100644 --- a/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveCsvableBuilder.scala +++ b/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveCsvableBuilder.scala @@ -25,6 +25,7 @@ import org.bitlap.csv.core.{ Csvable, CsvableBuilder } import scala.collection.mutable import scala.reflect.macros.whitebox +import java.io.File /** * @author 梦境迷离 @@ -44,10 +45,8 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac private val csvableImplClassNamePrefix = "_CsvAnno$" private val funcArgsTempTermName = TermName("temp") - def setFieldImpl[T: WeakTypeTag, SF: WeakTypeTag]( - scalaField: Expr[T => SF], - value: Expr[SF => String] - ): Expr[CsvableBuilder[T]] = { + // scalafmt: { maxColumn = 400 } + def setFieldImpl[T: WeakTypeTag, SF: WeakTypeTag](scalaField: Expr[T => SF], value: Expr[SF => String]): Expr[CsvableBuilder[T]] = { val Function(_, Select(_, termName)) = scalaField.tree val builderId = getBuilderId(annoBuilderPrefix) MacroCache.builderFunctionTrees.getOrElseUpdate(builderId, mutable.Map.empty).update(termName.toString, value) @@ -70,6 +69,9 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac def convertDefaultImpl[T: WeakTypeTag](ts: Expr[List[T]]): Expr[String] = deriveFullCsvableImpl[T](ts, c.Expr[Char](q"','")) + def writeToFileImpl[T: WeakTypeTag](ts: Expr[List[T]], file: Expr[File]): Expr[Boolean] = + deriveFullIntoFileCsvableImpl[T](ts, file, c.Expr[Char](q"','")) + private def deriveBuilderApplyImpl[T: WeakTypeTag]: Expr[CsvableBuilder[T]] = { val className = TypeName(annoBuilderPrefix + MacroCache.getBuilderId) val caseClazzName = TypeName(weakTypeOf[T].typeSymbol.name.decodedName.toString) @@ -93,29 +95,30 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac customTrees -> preTrees } - private def deriveFullCsvableImpl[T: WeakTypeTag]( - ts: Expr[List[T]], - columnSeparator: Expr[Char] - ): Expr[String] = { + // scalafmt: { maxColumn = 400 } + private def deriveFullIntoFileCsvableImpl[T: WeakTypeTag](ts: Expr[List[T]], file: Expr[File], columnSeparator: Expr[Char]): Expr[Boolean] = { + val clazzName = resolveClazzTypeName[T] + val (customTrees, preTrees) = getCustomPreTress + val tree = q""" + ..$preTrees + ..${getAnnoClassObject[T](customTrees, columnSeparator)} + $packageName.FileUtils.writer($file, $ts.map { ($innerTName: $clazzName) => + $csvableInstanceTermName.$innerTmpTermName = $innerTName + $csvableInstanceTermName.toCsvString + } + ) + """ + exprPrintTree[Boolean](force = false, tree) + } + + // scalafmt: { maxColumn = 400 } + private def deriveFullCsvableImpl[T: WeakTypeTag](ts: Expr[List[T]], columnSeparator: Expr[Char]): Expr[String] = { val clazzName = resolveClazzTypeName[T] val (customTrees, preTrees) = getCustomPreTress - val annoClassName = TermName(csvableImplClassNamePrefix + MacroCache.getIdentityId) - val separator = q"$columnSeparator" val tree = q""" ..$preTrees - object $annoClassName extends $packageName.Csvable[$clazzName] { - var $innerTmpTermName: $clazzName = _ - - lazy private val toCsv = ($funcArgsTempTermName: $clazzName) => { - val fields = ${clazzName.toTermName}.unapply($funcArgsTempTermName).orNull - if (null == fields) "" else ${fieldsToString[T](funcArgsTempTermName, customTrees)}.mkString($separator.toString) - } - override def toCsvString: String = toCsv($annoClassName.$innerTmpTermName) - } - - final lazy private val $csvableInstanceTermName = $annoClassName - + ..${getAnnoClassObject[T](customTrees, columnSeparator)} $ts.map { ($innerTName: $clazzName) => $csvableInstanceTermName.$innerTmpTermName = $innerTName $csvableInstanceTermName.toCsvString @@ -124,6 +127,25 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac exprPrintTree[String](force = false, tree) } + private def getAnnoClassObject[T: WeakTypeTag](customTrees: mutable.Map[String, Any], columnSeparator: Expr[Char]): Tree = { + val clazzName = resolveClazzTypeName[T] + val annoClassName = TermName(csvableImplClassNamePrefix + MacroCache.getIdentityId) + val separator = q"$columnSeparator" + q""" + object $annoClassName extends $packageName.Csvable[$clazzName] { + var $innerTmpTermName: $clazzName = _ + + lazy private val toCsv = ($funcArgsTempTermName: $clazzName) => { + val fields = ${clazzName.toTermName}.unapply($funcArgsTempTermName).orNull + if (null == fields) "" else ${fieldsToString[T](funcArgsTempTermName, customTrees)}.mkString($separator.toString) + } + override def toCsvString: String = toCsv($annoClassName.$innerTmpTermName) + } + + final lazy private val $csvableInstanceTermName = $annoClassName + """ + } + private def deriveCsvableImpl[T: WeakTypeTag](t: Expr[T], columnSeparator: Expr[Char]): Expr[Csvable[T]] = { val clazzName = resolveClazzTypeName[T] val (customTrees, preTrees) = getCustomPreTress @@ -146,16 +168,13 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac exprPrintTree[Csvable[T]](force = false, tree) } - private def fieldsToString[T: WeakTypeTag]( - innerVarTermName: TermName, - customTrees: mutable.Map[String, Any] - ): List[Tree] = { + // scalafmt: { maxColumn = 400 } + private def fieldsToString[T: WeakTypeTag](innerVarTermName: TermName, customTrees: mutable.Map[String, Any]): List[Tree] = { val clazzName = resolveClazzTypeName[T] val (fieldNames, indexTypes) = checkCaseClassZip val indexByName = (i: Int) => TermName(fieldNames(i)) indexTypes.map { idxType => - val customFunction = () => - q"${TermName(builderFunctionPrefix + fieldNames(idxType._1))}.apply($innerVarTermName.${indexByName(idxType._1)})" + val customFunction = () => q"${TermName(builderFunctionPrefix + fieldNames(idxType._1))}.apply($innerVarTermName.${indexByName(idxType._1)})" idxType._2 match { case t if t <:< typeOf[List[_]] => if (customTrees.contains(fieldNames(idxType._1))) { @@ -180,11 +199,10 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac if (customTrees.contains(fieldNames(idxType._1))) { customFunction() } else { + // scalafmt: { maxColumn = 400 } q""" $packageName.Csvable[${genericType.typeSymbol.name.toTypeName}]._toCsvString { - if ($innerVarTermName.${indexByName(idxType._1)}.isEmpty) "" else $innerVarTermName.${indexByName( - idxType._1 - )}.get + if ($innerVarTermName.${indexByName(idxType._1)}.isEmpty) "" else $innerVarTermName.${indexByName(idxType._1)}.get } """ } diff --git a/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveScalableBuilder.scala b/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveScalableBuilder.scala index d3efe51..cb88a95 100644 --- a/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveScalableBuilder.scala +++ b/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveScalableBuilder.scala @@ -23,6 +23,7 @@ package org.bitlap.csv.core.macros import org.bitlap.csv.core.{ Scalable, ScalableBuilder } +import java.io.InputStream import scala.collection.mutable import scala.reflect.macros.whitebox @@ -39,16 +40,13 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa private val builderFunctionPrefix = "_ScalableBuilderFunction$" private val innerColumnFuncTermName = TermName("_columns") - private val innerLName = q"_l" private val innerTempTermName = TermName("_line") private val scalableInstanceTermName = TermName("_scalableInstance") private val scalableImplClassNamePrefix = "_ScalaAnno$" - def setFieldImpl[T: WeakTypeTag, SF: WeakTypeTag]( - scalaField: Expr[T => SF], - value: Expr[String => SF] - ): Expr[ScalableBuilder[T]] = { + // scalafmt: { maxColumn = 400 } + def setFieldImpl[T: WeakTypeTag, SF: WeakTypeTag](scalaField: Expr[T => SF], value: Expr[String => SF]): Expr[ScalableBuilder[T]] = { val Function(_, Select(_, termName)) = scalaField.tree val builderId = getBuilderId(annoBuilderPrefix) MacroCache.builderFunctionTrees.getOrElseUpdate(builderId, mutable.Map.empty).update(termName.toString, value) @@ -79,6 +77,11 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa deriveScalableImpl[T](clazzName, line, c.Expr[Char](q"','")) } + def readFromFileImpl[T: WeakTypeTag](file: Expr[InputStream], charset: Expr[String]): Expr[List[Option[T]]] = { + val clazzName = resolveClazzTypeName[T] + deriveFullFromFileScalableImpl[T](clazzName, file, charset, c.Expr[Char](q"','")) + } + private def deriveBuilderApplyImpl[T: WeakTypeTag]: Expr[ScalableBuilder[T]] = { val className = TypeName(annoBuilderPrefix + MacroCache.getBuilderId) val caseClazzName = TypeName(weakTypeOf[T].typeSymbol.name.decodedName.toString) @@ -102,24 +105,28 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa preTrees } - private def deriveFullScalableImpl[T: WeakTypeTag]( - clazzName: TypeName, - lines: Expr[List[String]], - columnSeparator: Expr[Char] - ): Expr[List[Option[T]]] = { - val annoClassName = TermName(scalableImplClassNamePrefix + MacroCache.getIdentityId) + // scalafmt: { maxColumn = 400 } + private def deriveFullFromFileScalableImpl[T: WeakTypeTag](clazzName: TypeName, file: Expr[InputStream], charset: Expr[String], columnSeparator: Expr[Char]): Expr[List[Option[T]]] = { // NOTE: preTrees must be at the same level as Scalable val tree = q""" ..$getPreTree - - object $annoClassName extends $packageName.Scalable[$clazzName] { - var $innerTempTermName: String = _ - private val $innerColumnFuncTermName = () => _root_.org.bitlap.csv.core.StringUtils.splitColumns(${annoClassName.toTermName}.$innerTempTermName, $columnSeparator) - ..${scalableBody[T](clazzName, innerColumnFuncTermName)} + ..${getAnnoClassObject[T](clazzName, columnSeparator)} + $packageName.FileUtils.reader($file, $charset).map { ($innerLName: String) => + $scalableInstanceTermName.$innerTempTermName = ${TermName(innerLName.toString())} + $scalableInstanceTermName.toScala } - private final lazy val $scalableInstanceTermName = $annoClassName - + """ + exprPrintTree[List[Option[T]]](force = false, tree) + } + + // scalafmt: { maxColumn = 400 } + private def deriveFullScalableImpl[T: WeakTypeTag](clazzName: TypeName, lines: Expr[List[String]], columnSeparator: Expr[Char]): Expr[List[Option[T]]] = { + // NOTE: preTrees must be at the same level as Scalable + val tree = + q""" + ..$getPreTree + ..${getAnnoClassObject[T](clazzName, columnSeparator)} $lines.map { ($innerLName: String) => $scalableInstanceTermName.$innerTempTermName = ${TermName(innerLName.toString())} $scalableInstanceTermName.toScala @@ -128,18 +135,27 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa exprPrintTree[List[Option[T]]](force = false, tree) } - private def deriveScalableImpl[T: WeakTypeTag]( - clazzName: TypeName, - line: Expr[String], - columnSeparator: Expr[Char] - ): Expr[Scalable[T]] = { + private def getAnnoClassObject[T: WeakTypeTag](clazzName: TypeName, columnSeparator: Expr[Char]): Tree = { + val annoClassName = TermName(scalableImplClassNamePrefix + MacroCache.getIdentityId) + q""" + object $annoClassName extends $packageName.Scalable[$clazzName] { + var $innerTempTermName: String = _ + private val $innerColumnFuncTermName = () => $packageName.StringUtils.splitColumns(${annoClassName.toTermName}.$innerTempTermName, $columnSeparator) + ..${scalableBody[T](clazzName, innerColumnFuncTermName)} + } + private final lazy val $scalableInstanceTermName = $annoClassName + """ + } + + // scalafmt: { maxColumn = 400 } + private def deriveScalableImpl[T: WeakTypeTag](clazzName: TypeName, line: Expr[String], columnSeparator: Expr[Char]): Expr[Scalable[T]] = { val annoClassName = TermName(scalableImplClassNamePrefix + MacroCache.getIdentityId) // NOTE: preTrees must be at the same level as Scalable val tree = q""" ..$getPreTree object $annoClassName extends $packageName.Scalable[$clazzName] { - final lazy private val $innerColumnFuncTermName = () => _root_.org.bitlap.csv.core.StringUtils.splitColumns($line, $columnSeparator) + final lazy private val $innerColumnFuncTermName = () => $packageName.StringUtils.splitColumns($line, $columnSeparator) ..${scalableBody[T](clazzName, innerColumnFuncTermName)} } $annoClassName @@ -147,10 +163,8 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa exprPrintTree[Scalable[T]](force = false, tree) } - private def scalableBody[T: WeakTypeTag]( - clazzName: TypeName, - innerFuncTermName: TermName - ): Tree = { + // scalafmt: { maxColumn = 400 } + private def scalableBody[T: WeakTypeTag](clazzName: TypeName, innerFuncTermName: TermName): Tree = { val customTrees = MacroCache.builderFunctionTrees.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty) val params = getCaseClassParams[T]() val fieldNames = params.map(_.name.decodedName.toString) diff --git a/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveToCaseClass.scala b/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveToCaseClass.scala index 379e2af..2138214 100644 --- a/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveToCaseClass.scala +++ b/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveToCaseClass.scala @@ -34,10 +34,8 @@ object DeriveToCaseClass { class Macro(override val c: blackbox.Context) extends AbstractMacroProcessor(c) { import c.universe._ - def macroImpl[T <: Product: c.WeakTypeTag]( - line: c.Expr[String], - columnSeparator: c.Expr[Char] - ): c.Expr[Option[T]] = { + // scalafmt: { maxColumn = 400 } + def macroImpl[T <: Product: c.WeakTypeTag](line: c.Expr[String], columnSeparator: c.Expr[Char]): c.Expr[Option[T]] = { val clazzName = c.weakTypeOf[T].typeSymbol.name val innerFuncTermName = TermName("_columns") val fields = (columnsFunc: TermName) => @@ -72,7 +70,7 @@ object DeriveToCaseClass { } val tree = q""" - lazy val $innerFuncTermName = () => _root_.org.bitlap.csv.core.StringUtils.splitColumns($line, $columnSeparator) + lazy val $innerFuncTermName = () => $packageName.StringUtils.splitColumns($line, $columnSeparator) Option(${TermName(clazzName.decodedName.toString)}(..${fields(innerFuncTermName)})) """ exprPrintTree[T](force = false, tree) diff --git a/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveToString.scala b/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveToString.scala index 547f67a..c90c03a 100644 --- a/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveToString.scala +++ b/smt-csv-core/src/main/scala/org/bitlap/csv/core/macros/DeriveToString.scala @@ -43,12 +43,11 @@ object DeriveToString { val fieldsToString = indexTypes.map { idxType => if (idxType._2 <:< typeOf[Option[_]]) { val genericType = c.typecheck(q"${idxType._2}", c.TYPEmode).tpe.typeArgs.head + // scalafmt: { maxColumn = 400 } q"""$packageName.Converter[${genericType.typeSymbol.name.toTypeName}].toCsvString { - if ($innerVarTermName.${indexByName(idxType._1)}.isEmpty) "" else $innerVarTermName.${indexByName( - idxType._1 - )}.get - } - """ + if ($innerVarTermName.${indexByName(idxType._1)}.isEmpty) "" else $innerVarTermName.${indexByName(idxType._1)}.get + } + """ } else { q"$packageName.Converter[${TypeName(idxType._2.typeSymbol.name.decodedName.toString)}].toCsvString($innerVarTermName.${indexByName(idxType._1)})" } diff --git a/smt-csv-core/src/test/scala/org/bitlap/csv/core/test/CsvableAndScalableTest.scala b/smt-csv-core/src/test/scala/org/bitlap/csv/core/test/CsvableAndScalableTest.scala index 71c60dc..0f25ea5 100644 --- a/smt-csv-core/src/test/scala/org/bitlap/csv/core/test/CsvableAndScalableTest.scala +++ b/smt-csv-core/src/test/scala/org/bitlap/csv/core/test/CsvableAndScalableTest.scala @@ -28,6 +28,7 @@ import org.scalatest.matchers.should.Matchers import org.bitlap.csv.core.ScalableBuilder import org.bitlap.csv.core.CsvableBuilder import org.bitlap.csv.core.ScalableHelper +import java.io.File /** * Complex use of common tests @@ -212,6 +213,7 @@ class CsvableAndScalableTest extends AnyFlatSpec with Matchers { object _ScalaAnno$1 extends _root_.org.bitlap.csv.core.Scalable[Metric2] { var _line: String = _; private val _columns = (() => _root_.org.bitlap.csv.core.StringUtils.splitColumns(_ScalaAnno$1._line, ',')); + override def toScala: Option[Metric2] = Option( Metric2( _root_.org.bitlap.csv.core.Scalable[Long]._toScala(_columns()(0)).getOrElse(0L), @@ -266,6 +268,7 @@ class CsvableAndScalableTest extends AnyFlatSpec with Matchers { ) .mkString(','.toString) }); + override def toCsvString: String = toCsv(_CsvAnno$2._tt) }; lazy val _csvableInstance = _CsvAnno$2; @@ -280,4 +283,28 @@ class CsvableAndScalableTest extends AnyFlatSpec with Matchers { println(csv) } + + "CsvableAndScalable8" should "ok when reading from file" in { + val metrics = + ScalableBuilder[Metric2] + .setField[Seq[Dimension3]]( + _.dimensions, + dims => StringUtils.extractJsonValues[Dimension3](dims)((k, v) => Dimension3(k, v)) + ) + .readFrom(ClassLoader.getSystemResourceAsStream("simple_data.csv"), "utf-8") + + println(metrics) + assert(metrics.nonEmpty) + + val file = new File("./simple_data.csv") + CsvableBuilder[Metric2] + .setField[Seq[Dimension3]]( + _.dimensions, + ds => s"""\"{${ds.map(kv => s"""\"\"${kv.key}\"\":\"\"${kv.value}\"\"""").mkString(",")}}\"""" + ) + .writeTo(metrics.filter(_.isDefined).map(_.get), file) + + file.delete() + } + } -- GitLab