提交 dc6bcee1 编写于 作者: 梦境迷离's avatar 梦境迷离

support read/write file

上级 c62edcd9
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
.idea/ .idea/
*.iml *.iml
.bsp .bsp
*.csv
examples/scala2-11/target/ examples/scala2-11/target/
examples/scala2-12/target/ examples/scala2-12/target/
examples/scala2-13/target/ examples/scala2-13/target/
......
...@@ -11,7 +11,7 @@ ThisBuild / resolvers ++= Seq( ...@@ -11,7 +11,7 @@ ThisBuild / resolvers ++= Seq(
lazy val scala212 = "2.12.14" lazy val scala212 = "2.12.14"
lazy val scala211 = "2.11.12" lazy val scala211 = "2.11.12"
lazy val scala213 = "2.13.8" 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 scalatestVersion = "3.2.12"
lazy val zioVersion = "1.0.14" lazy val zioVersion = "1.0.14"
......
...@@ -31,6 +31,8 @@ import org.bitlap.csv.core.macros.DeriveCsvableBuilder ...@@ -31,6 +31,8 @@ import org.bitlap.csv.core.macros.DeriveCsvableBuilder
*/ */
class CsvableBuilder[T] { class CsvableBuilder[T] {
import java.io.File
/** /**
* Convert this CSV column string to any Scala types. * Convert this CSV column string to any Scala types.
* *
...@@ -70,6 +72,15 @@ class CsvableBuilder[T] { ...@@ -70,6 +72,15 @@ class CsvableBuilder[T] {
*/ */
def convert(ts: List[T]): String = macro DeriveCsvableBuilder.convertDefaultImpl[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 { object CsvableBuilder {
......
/*
* 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()
}
}
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
package org.bitlap.csv.core package org.bitlap.csv.core
import org.bitlap.csv.core.macros.DeriveScalableBuilder import org.bitlap.csv.core.macros.DeriveScalableBuilder
import java.io.InputStream
/** /**
* Builder to create a custom Csv Decoder. * Builder to create a custom Csv Decoder.
...@@ -59,7 +60,7 @@ class ScalableBuilder[T] { ...@@ -59,7 +60,7 @@ class ScalableBuilder[T] {
/** /**
* Convert all CSV lines to the sequence of Scala case class. * 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. * @param columnSeparator The separator for CSV column value.
* @return * @return
*/ */
...@@ -70,6 +71,15 @@ class ScalableBuilder[T] { ...@@ -70,6 +71,15 @@ class ScalableBuilder[T] {
*/ */
def convert(lines: List[String]): List[Option[T]] = macro DeriveScalableBuilder.convertDefaultImpl[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 { object ScalableBuilder {
......
...@@ -21,32 +21,23 @@ ...@@ -21,32 +21,23 @@
package org.bitlap.csv.core package org.bitlap.csv.core
import java.io.{ BufferedReader, InputStreamReader } import java.io.{ BufferedReader, File, FileReader, InputStreamReader }
import scala.collection.mutable.ListBuffer
import scala.util.Using
/** /**
* Tool class for parsing CSV files. * Tool class for parsing CSV files.
* *
* @author 梦境迷离 * @author 梦境迷离
* @version 1.0,2022/5/2 * @version 1.0,2022/5/13
*/ */
object ScalableHelper { object ScalableHelper {
def readCsvFromClassPath[T <: Product](fileName: String)(func: String => Option[T]): List[Option[T]] = { 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 reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(fileName))
val bufferedReader = new BufferedReader(reader) FileUtils.readFileFunc[T](new BufferedReader(reader), func)
var line: String = null
Using.resource(bufferedReader) { input =>
while ({
line = input.readLine()
line != null
})
ts.append(func(line))
}
ts.result()
} }
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)
}
} }
...@@ -25,6 +25,7 @@ import org.bitlap.csv.core.{ Csvable, CsvableBuilder } ...@@ -25,6 +25,7 @@ import org.bitlap.csv.core.{ Csvable, CsvableBuilder }
import scala.collection.mutable import scala.collection.mutable
import scala.reflect.macros.whitebox import scala.reflect.macros.whitebox
import java.io.File
/** /**
* @author 梦境迷离 * @author 梦境迷离
...@@ -44,10 +45,8 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac ...@@ -44,10 +45,8 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac
private val csvableImplClassNamePrefix = "_CsvAnno$" private val csvableImplClassNamePrefix = "_CsvAnno$"
private val funcArgsTempTermName = TermName("temp") private val funcArgsTempTermName = TermName("temp")
def setFieldImpl[T: WeakTypeTag, SF: WeakTypeTag]( // scalafmt: { maxColumn = 400 }
scalaField: Expr[T => SF], def setFieldImpl[T: WeakTypeTag, SF: WeakTypeTag](scalaField: Expr[T => SF], value: Expr[SF => String]): Expr[CsvableBuilder[T]] = {
value: Expr[SF => String]
): Expr[CsvableBuilder[T]] = {
val Function(_, Select(_, termName)) = scalaField.tree val Function(_, Select(_, termName)) = scalaField.tree
val builderId = getBuilderId(annoBuilderPrefix) val builderId = getBuilderId(annoBuilderPrefix)
MacroCache.builderFunctionTrees.getOrElseUpdate(builderId, mutable.Map.empty).update(termName.toString, value) MacroCache.builderFunctionTrees.getOrElseUpdate(builderId, mutable.Map.empty).update(termName.toString, value)
...@@ -70,6 +69,9 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac ...@@ -70,6 +69,9 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac
def convertDefaultImpl[T: WeakTypeTag](ts: Expr[List[T]]): Expr[String] = def convertDefaultImpl[T: WeakTypeTag](ts: Expr[List[T]]): Expr[String] =
deriveFullCsvableImpl[T](ts, c.Expr[Char](q"','")) 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]] = { private def deriveBuilderApplyImpl[T: WeakTypeTag]: Expr[CsvableBuilder[T]] = {
val className = TypeName(annoBuilderPrefix + MacroCache.getBuilderId) val className = TypeName(annoBuilderPrefix + MacroCache.getBuilderId)
val caseClazzName = TypeName(weakTypeOf[T].typeSymbol.name.decodedName.toString) val caseClazzName = TypeName(weakTypeOf[T].typeSymbol.name.decodedName.toString)
...@@ -93,29 +95,30 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac ...@@ -93,29 +95,30 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac
customTrees -> preTrees customTrees -> preTrees
} }
private def deriveFullCsvableImpl[T: WeakTypeTag]( // scalafmt: { maxColumn = 400 }
ts: Expr[List[T]], private def deriveFullIntoFileCsvableImpl[T: WeakTypeTag](ts: Expr[List[T]], file: Expr[File], columnSeparator: Expr[Char]): Expr[Boolean] = {
columnSeparator: Expr[Char] val clazzName = resolveClazzTypeName[T]
): Expr[String] = { 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 clazzName = resolveClazzTypeName[T]
val (customTrees, preTrees) = getCustomPreTress val (customTrees, preTrees) = getCustomPreTress
val annoClassName = TermName(csvableImplClassNamePrefix + MacroCache.getIdentityId)
val separator = q"$columnSeparator"
val tree = val tree =
q""" q"""
..$preTrees ..$preTrees
object $annoClassName extends $packageName.Csvable[$clazzName] { ..${getAnnoClassObject[T](customTrees, columnSeparator)}
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
$ts.map { ($innerTName: $clazzName) => $ts.map { ($innerTName: $clazzName) =>
$csvableInstanceTermName.$innerTmpTermName = $innerTName $csvableInstanceTermName.$innerTmpTermName = $innerTName
$csvableInstanceTermName.toCsvString $csvableInstanceTermName.toCsvString
...@@ -124,6 +127,25 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac ...@@ -124,6 +127,25 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac
exprPrintTree[String](force = false, tree) 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]] = { private def deriveCsvableImpl[T: WeakTypeTag](t: Expr[T], columnSeparator: Expr[Char]): Expr[Csvable[T]] = {
val clazzName = resolveClazzTypeName[T] val clazzName = resolveClazzTypeName[T]
val (customTrees, preTrees) = getCustomPreTress val (customTrees, preTrees) = getCustomPreTress
...@@ -146,16 +168,13 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac ...@@ -146,16 +168,13 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac
exprPrintTree[Csvable[T]](force = false, tree) exprPrintTree[Csvable[T]](force = false, tree)
} }
private def fieldsToString[T: WeakTypeTag]( // scalafmt: { maxColumn = 400 }
innerVarTermName: TermName, private def fieldsToString[T: WeakTypeTag](innerVarTermName: TermName, customTrees: mutable.Map[String, Any]): List[Tree] = {
customTrees: mutable.Map[String, Any]
): List[Tree] = {
val clazzName = resolveClazzTypeName[T] val clazzName = resolveClazzTypeName[T]
val (fieldNames, indexTypes) = checkCaseClassZip val (fieldNames, indexTypes) = checkCaseClassZip
val indexByName = (i: Int) => TermName(fieldNames(i)) val indexByName = (i: Int) => TermName(fieldNames(i))
indexTypes.map { idxType => indexTypes.map { idxType =>
val customFunction = () => val customFunction = () => q"${TermName(builderFunctionPrefix + fieldNames(idxType._1))}.apply($innerVarTermName.${indexByName(idxType._1)})"
q"${TermName(builderFunctionPrefix + fieldNames(idxType._1))}.apply($innerVarTermName.${indexByName(idxType._1)})"
idxType._2 match { idxType._2 match {
case t if t <:< typeOf[List[_]] => case t if t <:< typeOf[List[_]] =>
if (customTrees.contains(fieldNames(idxType._1))) { if (customTrees.contains(fieldNames(idxType._1))) {
...@@ -180,11 +199,10 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac ...@@ -180,11 +199,10 @@ class DeriveCsvableBuilder(override val c: whitebox.Context) extends AbstractMac
if (customTrees.contains(fieldNames(idxType._1))) { if (customTrees.contains(fieldNames(idxType._1))) {
customFunction() customFunction()
} else { } else {
// scalafmt: { maxColumn = 400 }
q""" q"""
$packageName.Csvable[${genericType.typeSymbol.name.toTypeName}]._toCsvString { $packageName.Csvable[${genericType.typeSymbol.name.toTypeName}]._toCsvString {
if ($innerVarTermName.${indexByName(idxType._1)}.isEmpty) "" else $innerVarTermName.${indexByName( if ($innerVarTermName.${indexByName(idxType._1)}.isEmpty) "" else $innerVarTermName.${indexByName(idxType._1)}.get
idxType._1
)}.get
} }
""" """
} }
......
...@@ -23,6 +23,7 @@ package org.bitlap.csv.core.macros ...@@ -23,6 +23,7 @@ package org.bitlap.csv.core.macros
import org.bitlap.csv.core.{ Scalable, ScalableBuilder } import org.bitlap.csv.core.{ Scalable, ScalableBuilder }
import java.io.InputStream
import scala.collection.mutable import scala.collection.mutable
import scala.reflect.macros.whitebox import scala.reflect.macros.whitebox
...@@ -39,16 +40,13 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa ...@@ -39,16 +40,13 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa
private val builderFunctionPrefix = "_ScalableBuilderFunction$" private val builderFunctionPrefix = "_ScalableBuilderFunction$"
private val innerColumnFuncTermName = TermName("_columns") private val innerColumnFuncTermName = TermName("_columns")
private val innerLName = q"_l" private val innerLName = q"_l"
private val innerTempTermName = TermName("_line") private val innerTempTermName = TermName("_line")
private val scalableInstanceTermName = TermName("_scalableInstance") private val scalableInstanceTermName = TermName("_scalableInstance")
private val scalableImplClassNamePrefix = "_ScalaAnno$" private val scalableImplClassNamePrefix = "_ScalaAnno$"
def setFieldImpl[T: WeakTypeTag, SF: WeakTypeTag]( // scalafmt: { maxColumn = 400 }
scalaField: Expr[T => SF], def setFieldImpl[T: WeakTypeTag, SF: WeakTypeTag](scalaField: Expr[T => SF], value: Expr[String => SF]): Expr[ScalableBuilder[T]] = {
value: Expr[String => SF]
): Expr[ScalableBuilder[T]] = {
val Function(_, Select(_, termName)) = scalaField.tree val Function(_, Select(_, termName)) = scalaField.tree
val builderId = getBuilderId(annoBuilderPrefix) val builderId = getBuilderId(annoBuilderPrefix)
MacroCache.builderFunctionTrees.getOrElseUpdate(builderId, mutable.Map.empty).update(termName.toString, value) MacroCache.builderFunctionTrees.getOrElseUpdate(builderId, mutable.Map.empty).update(termName.toString, value)
...@@ -79,6 +77,11 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa ...@@ -79,6 +77,11 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa
deriveScalableImpl[T](clazzName, line, c.Expr[Char](q"','")) 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]] = { private def deriveBuilderApplyImpl[T: WeakTypeTag]: Expr[ScalableBuilder[T]] = {
val className = TypeName(annoBuilderPrefix + MacroCache.getBuilderId) val className = TypeName(annoBuilderPrefix + MacroCache.getBuilderId)
val caseClazzName = TypeName(weakTypeOf[T].typeSymbol.name.decodedName.toString) val caseClazzName = TypeName(weakTypeOf[T].typeSymbol.name.decodedName.toString)
...@@ -102,24 +105,28 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa ...@@ -102,24 +105,28 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa
preTrees preTrees
} }
private def deriveFullScalableImpl[T: WeakTypeTag]( // scalafmt: { maxColumn = 400 }
clazzName: TypeName, private def deriveFullFromFileScalableImpl[T: WeakTypeTag](clazzName: TypeName, file: Expr[InputStream], charset: Expr[String], columnSeparator: Expr[Char]): Expr[List[Option[T]]] = {
lines: Expr[List[String]],
columnSeparator: Expr[Char]
): Expr[List[Option[T]]] = {
val annoClassName = TermName(scalableImplClassNamePrefix + MacroCache.getIdentityId)
// NOTE: preTrees must be at the same level as Scalable // NOTE: preTrees must be at the same level as Scalable
val tree = val tree =
q""" q"""
..$getPreTree ..$getPreTree
..${getAnnoClassObject[T](clazzName, columnSeparator)}
object $annoClassName extends $packageName.Scalable[$clazzName] { $packageName.FileUtils.reader($file, $charset).map { ($innerLName: String) =>
var $innerTempTermName: String = _ $scalableInstanceTermName.$innerTempTermName = ${TermName(innerLName.toString())}
private val $innerColumnFuncTermName = () => _root_.org.bitlap.csv.core.StringUtils.splitColumns(${annoClassName.toTermName}.$innerTempTermName, $columnSeparator) $scalableInstanceTermName.toScala
..${scalableBody[T](clazzName, innerColumnFuncTermName)}
} }
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) => $lines.map { ($innerLName: String) =>
$scalableInstanceTermName.$innerTempTermName = ${TermName(innerLName.toString())} $scalableInstanceTermName.$innerTempTermName = ${TermName(innerLName.toString())}
$scalableInstanceTermName.toScala $scalableInstanceTermName.toScala
...@@ -128,18 +135,27 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa ...@@ -128,18 +135,27 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa
exprPrintTree[List[Option[T]]](force = false, tree) exprPrintTree[List[Option[T]]](force = false, tree)
} }
private def deriveScalableImpl[T: WeakTypeTag]( private def getAnnoClassObject[T: WeakTypeTag](clazzName: TypeName, columnSeparator: Expr[Char]): Tree = {
clazzName: TypeName, val annoClassName = TermName(scalableImplClassNamePrefix + MacroCache.getIdentityId)
line: Expr[String], q"""
columnSeparator: Expr[Char] object $annoClassName extends $packageName.Scalable[$clazzName] {
): Expr[Scalable[T]] = { 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) val annoClassName = TermName(scalableImplClassNamePrefix + MacroCache.getIdentityId)
// NOTE: preTrees must be at the same level as Scalable // NOTE: preTrees must be at the same level as Scalable
val tree = val tree =
q""" q"""
..$getPreTree ..$getPreTree
object $annoClassName extends $packageName.Scalable[$clazzName] { 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)} ..${scalableBody[T](clazzName, innerColumnFuncTermName)}
} }
$annoClassName $annoClassName
...@@ -147,10 +163,8 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa ...@@ -147,10 +163,8 @@ class DeriveScalableBuilder(override val c: whitebox.Context) extends AbstractMa
exprPrintTree[Scalable[T]](force = false, tree) exprPrintTree[Scalable[T]](force = false, tree)
} }
private def scalableBody[T: WeakTypeTag]( // scalafmt: { maxColumn = 400 }
clazzName: TypeName, private def scalableBody[T: WeakTypeTag](clazzName: TypeName, innerFuncTermName: TermName): Tree = {
innerFuncTermName: TermName
): Tree = {
val customTrees = MacroCache.builderFunctionTrees.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty) val customTrees = MacroCache.builderFunctionTrees.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
val params = getCaseClassParams[T]() val params = getCaseClassParams[T]()
val fieldNames = params.map(_.name.decodedName.toString) val fieldNames = params.map(_.name.decodedName.toString)
......
...@@ -34,10 +34,8 @@ object DeriveToCaseClass { ...@@ -34,10 +34,8 @@ object DeriveToCaseClass {
class Macro(override val c: blackbox.Context) extends AbstractMacroProcessor(c) { class Macro(override val c: blackbox.Context) extends AbstractMacroProcessor(c) {
import c.universe._ import c.universe._
def macroImpl[T <: Product: c.WeakTypeTag]( // scalafmt: { maxColumn = 400 }
line: c.Expr[String], def macroImpl[T <: Product: c.WeakTypeTag](line: c.Expr[String], columnSeparator: c.Expr[Char]): c.Expr[Option[T]] = {
columnSeparator: c.Expr[Char]
): c.Expr[Option[T]] = {
val clazzName = c.weakTypeOf[T].typeSymbol.name val clazzName = c.weakTypeOf[T].typeSymbol.name
val innerFuncTermName = TermName("_columns") val innerFuncTermName = TermName("_columns")
val fields = (columnsFunc: TermName) => val fields = (columnsFunc: TermName) =>
...@@ -72,7 +70,7 @@ object DeriveToCaseClass { ...@@ -72,7 +70,7 @@ object DeriveToCaseClass {
} }
val tree = val tree =
q""" 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)})) Option(${TermName(clazzName.decodedName.toString)}(..${fields(innerFuncTermName)}))
""" """
exprPrintTree[T](force = false, tree) exprPrintTree[T](force = false, tree)
......
...@@ -43,12 +43,11 @@ object DeriveToString { ...@@ -43,12 +43,11 @@ object DeriveToString {
val fieldsToString = indexTypes.map { idxType => val fieldsToString = indexTypes.map { idxType =>
if (idxType._2 <:< typeOf[Option[_]]) { if (idxType._2 <:< typeOf[Option[_]]) {
val genericType = c.typecheck(q"${idxType._2}", c.TYPEmode).tpe.typeArgs.head val genericType = c.typecheck(q"${idxType._2}", c.TYPEmode).tpe.typeArgs.head
// scalafmt: { maxColumn = 400 }
q"""$packageName.Converter[${genericType.typeSymbol.name.toTypeName}].toCsvString { q"""$packageName.Converter[${genericType.typeSymbol.name.toTypeName}].toCsvString {
if ($innerVarTermName.${indexByName(idxType._1)}.isEmpty) "" else $innerVarTermName.${indexByName( if ($innerVarTermName.${indexByName(idxType._1)}.isEmpty) "" else $innerVarTermName.${indexByName(idxType._1)}.get
idxType._1 }
)}.get """
}
"""
} else { } else {
q"$packageName.Converter[${TypeName(idxType._2.typeSymbol.name.decodedName.toString)}].toCsvString($innerVarTermName.${indexByName(idxType._1)})" q"$packageName.Converter[${TypeName(idxType._2.typeSymbol.name.decodedName.toString)}].toCsvString($innerVarTermName.${indexByName(idxType._1)})"
} }
......
...@@ -28,6 +28,7 @@ import org.scalatest.matchers.should.Matchers ...@@ -28,6 +28,7 @@ import org.scalatest.matchers.should.Matchers
import org.bitlap.csv.core.ScalableBuilder import org.bitlap.csv.core.ScalableBuilder
import org.bitlap.csv.core.CsvableBuilder import org.bitlap.csv.core.CsvableBuilder
import org.bitlap.csv.core.ScalableHelper import org.bitlap.csv.core.ScalableHelper
import java.io.File
/** /**
* Complex use of common tests * Complex use of common tests
...@@ -212,6 +213,7 @@ class CsvableAndScalableTest extends AnyFlatSpec with Matchers { ...@@ -212,6 +213,7 @@ class CsvableAndScalableTest extends AnyFlatSpec with Matchers {
object _ScalaAnno$1 extends _root_.org.bitlap.csv.core.Scalable[Metric2] { object _ScalaAnno$1 extends _root_.org.bitlap.csv.core.Scalable[Metric2] {
var _line: String = _; var _line: String = _;
private val _columns = (() => _root_.org.bitlap.csv.core.StringUtils.splitColumns(_ScalaAnno$1._line, ',')); private val _columns = (() => _root_.org.bitlap.csv.core.StringUtils.splitColumns(_ScalaAnno$1._line, ','));
override def toScala: Option[Metric2] = Option( override def toScala: Option[Metric2] = Option(
Metric2( Metric2(
_root_.org.bitlap.csv.core.Scalable[Long]._toScala(_columns()(0)).getOrElse(0L), _root_.org.bitlap.csv.core.Scalable[Long]._toScala(_columns()(0)).getOrElse(0L),
...@@ -266,6 +268,7 @@ class CsvableAndScalableTest extends AnyFlatSpec with Matchers { ...@@ -266,6 +268,7 @@ class CsvableAndScalableTest extends AnyFlatSpec with Matchers {
) )
.mkString(','.toString) .mkString(','.toString)
}); });
override def toCsvString: String = toCsv(_CsvAnno$2._tt) override def toCsvString: String = toCsv(_CsvAnno$2._tt)
}; };
lazy val _csvableInstance = _CsvAnno$2; lazy val _csvableInstance = _CsvAnno$2;
...@@ -280,4 +283,28 @@ class CsvableAndScalableTest extends AnyFlatSpec with Matchers { ...@@ -280,4 +283,28 @@ class CsvableAndScalableTest extends AnyFlatSpec with Matchers {
println(csv) 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()
}
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册