Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encoders for CSV #29

Merged
merged 9 commits into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions csv/generic/src/fs2/data/csv/generic/DerivedCellDecoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,30 @@ object DerivedCellDecoder extends DerivedCellDecoderInstances0 {

// Unary Products

final implicit def unaryProductDecoder[A <: Product, L <: HList, H](implicit
gen: Generic.Aux[A, L],
ev: =:=[H :: HNil, L],
cc: CellDecoder[H]
): DerivedCellDecoder[A] = s => cc(s).map(v => gen.from(v :: HNil))
final implicit def unaryProductDecoder[A <: Product, L <: HList, H](
implicit
gen: Generic.Aux[A, L],
ev: =:=[H :: HNil, L],
cc: CellDecoder[H]): DerivedCellDecoder[A] =
s => cc(s).map(v => gen.from(v :: HNil))

// Coproducts

final implicit def coproductDecoder[T, Repr <: Coproduct](implicit
gen: LabelledGeneric.Aux[T, Repr],
cc: Lazy[DerivedCellDecoder[Repr]]): DerivedCellDecoder[T] =
final implicit def coproductDecoder[T, Repr <: Coproduct](
implicit
gen: LabelledGeneric.Aux[T, Repr],
cc: Lazy[DerivedCellDecoder[Repr]]): DerivedCellDecoder[T] =
s => cc.value(s).map(gen.from(_))

final implicit val decodeCNil: DerivedCellDecoder[CNil] = (_: String) => Left(new DecoderError("CNil"))
final implicit val decodeCNil: DerivedCellDecoder[CNil] = (_: String) =>
Left(new DecoderError("CNil"))

final implicit def decodeCCons[K <: Symbol, L, R <: Coproduct](
implicit
witK: Witness.Aux[K],
decodeL: CellDecoder[L],
decodeR: Lazy[DerivedCellDecoder[R]]): DerivedCellDecoder[FieldType[K, L] :+: R] =
decodeR: Lazy[DerivedCellDecoder[R]])
: DerivedCellDecoder[FieldType[K, L] :+: R] =
s =>
decodeL(s)
.map[FieldType[K, L] :+: R](v => Inl(field[K](v)))
Expand All @@ -62,7 +66,8 @@ trait DerivedCellDecoderInstances0 extends DerivedCellDecoderInstances1 {
witL: Witness.Aux[L],
annotation: Annotation[CsvValue, L],
gen: Generic.Aux[L, HNil],
decodeR: Lazy[DerivedCellDecoder[R]]): DerivedCellDecoder[FieldType[K, L] :+: R] =
decodeR: Lazy[DerivedCellDecoder[R]])
: DerivedCellDecoder[FieldType[K, L] :+: R] =
s =>
if (annotation().value == s) Inl(field[K](witL.value)).asRight
else decodeR.value(s).map(Inr(_))
Expand All @@ -74,7 +79,8 @@ trait DerivedCellDecoderInstances1 {
witK: Witness.Aux[K],
witL: Witness.Aux[L],
gen: Generic.Aux[L, HNil],
decodeR: Lazy[DerivedCellDecoder[R]]): DerivedCellDecoder[FieldType[K, L] :+: R] =
decodeR: Lazy[DerivedCellDecoder[R]])
: DerivedCellDecoder[FieldType[K, L] :+: R] =
s =>
if (witK.value.name == s) Inl(field[K](witL.value)).asRight
else decodeR.value(s).map(Inr(_))
Expand Down
79 changes: 79 additions & 0 deletions csv/generic/src/fs2/data/csv/generic/DerivedCellEncoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2019 Lucas Satabin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fs2.data.csv
package generic

import cats.implicits._
import shapeless._
import shapeless.labelled._
import shapeless.ops.hlist.IsHCons

trait DerivedCellEncoder[T] extends CellEncoder[T]

object DerivedCellEncoder extends DerivedCellEncoderInstances0 {

// Unary Products

final implicit def unaryProductEncoder[A <: Product, L <: HList, H](
implicit
gen: Generic.Aux[A, L],
ev: IsHCons.Aux[L, H, HNil],
cc: CellEncoder[H]): DerivedCellEncoder[A] =
a => cc.contramap[A](gen.to(_).head).apply(a)

// Coproducts

final implicit def coproductEncoder[T, Repr <: Coproduct](
implicit
gen: LabelledGeneric.Aux[T, Repr],
cc: Lazy[DerivedCellEncoder[Repr]]): DerivedCellEncoder[T] =
t => (cc.value: CellEncoder[Repr]).contramap(gen.to)(t)
ybasket marked this conversation as resolved.
Show resolved Hide resolved

final implicit val encodeCNil: DerivedCellEncoder[CNil] = (_: CNil) =>
sys.error("Can't happen")

final implicit def encodeCCons[K <: Symbol, L, R <: Coproduct](
implicit
encodeL: CellEncoder[L],
encodeR: Lazy[DerivedCellEncoder[R]])
: DerivedCellEncoder[FieldType[K, L] :+: R] = {
case Inl(head) => encodeL(head)
case Inr(tail) => encodeR.value(tail)
}

}

trait DerivedCellEncoderInstances0 extends DerivedCellEncoderInstances1 {
final implicit def encodeCConsObjAnnotated[K <: Symbol, L, R <: Coproduct](
implicit
annotation: Annotation[CsvValue, L],
encodeR: Lazy[DerivedCellEncoder[R]])
: DerivedCellEncoder[FieldType[K, L] :+: R] = {
case Inl(_) => annotation().value
case Inr(tail) => encodeR.value(tail)
}
}

trait DerivedCellEncoderInstances1 {
final implicit def encodeCConsObj[K <: Symbol, L, R <: Coproduct](
implicit
witK: Witness.Aux[K],
encodeR: Lazy[DerivedCellEncoder[R]])
: DerivedCellEncoder[FieldType[K, L] :+: R] = {
case Inl(_) => witK.value.name
case Inr(tail) => encodeR.value(tail)
}
}
34 changes: 34 additions & 0 deletions csv/generic/src/fs2/data/csv/generic/DerivedCsvRowEncoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2019 Lucas Satabin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fs2
package data
package csv
package generic

import shapeless._

trait DerivedCsvRowEncoder[T] extends CsvRowEncoder[T, String]

object DerivedCsvRowEncoder {

final implicit def productWriter[T, Repr <: HList, AnnoRepr <: HList](
implicit
gen: LabelledGeneric.Aux[T, Repr],
annotations: Annotations.Aux[CsvName, T, AnnoRepr],
cc: Lazy[MapShapedCsvRowEncoder.WithAnnotations[T, Repr, AnnoRepr]])
: DerivedCsvRowEncoder[T] = (elem: T) => cc.value.fromWithAnnotation(gen.to(elem), annotations())

}
33 changes: 33 additions & 0 deletions csv/generic/src/fs2/data/csv/generic/DerivedRowEncoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2019 Lucas Satabin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fs2
package data
package csv
package generic

import shapeless._

trait DerivedRowEncoder[T] extends RowEncoder[T]

object DerivedRowEncoder {

final implicit def productEncoder[T, Repr <: HList](
implicit
gen: Generic.Aux[T, Repr],
cc: Lazy[SeqShapedRowEncoder[Repr]]): DerivedRowEncoder[T] =
(elem: T) => cc.value(gen.to(elem))

}
80 changes: 66 additions & 14 deletions csv/generic/src/fs2/data/csv/generic/ExportMacros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fs2.data.csv.generic

import fs2.data.csv.{CellDecoder, CsvRowDecoder, Exported, RowDecoder}
package fs2.data.csv
package generic

import scala.reflect.macros.blackbox

Expand All @@ -25,28 +24,81 @@ import scala.reflect.macros.blackbox
class ExportMacros(val c: blackbox.Context) {
import c.universe._

final def exportRowDecoder[A](implicit a: c.WeakTypeTag[A]): c.Expr[Exported[RowDecoder[A]]] = {
c.typecheck(q"_root_.shapeless.lazily[_root_.fs2.data.csv.generic.DerivedRowDecoder[$a]]", silent = true) match {
case EmptyTree => c.abort(c.enclosingPosition, s"Unable to infer value of type $a")
final def exportRowDecoder[A](
implicit a: c.WeakTypeTag[A]): c.Expr[Exported[RowDecoder[A]]] = {
c.typecheck(
q"_root_.shapeless.lazily[_root_.fs2.data.csv.generic.DerivedRowDecoder[$a]]",
silent = true) match {
case EmptyTree =>
c.abort(c.enclosingPosition, s"Unable to infer value of type $a")
case t =>
c.Expr[Exported[RowDecoder[A]]](q"new _root_.fs2.data.csv.Exported($t: _root_.fs2.data.csv.RowDecoder[$a])")
c.Expr[Exported[RowDecoder[A]]](
q"new _root_.fs2.data.csv.Exported($t: _root_.fs2.data.csv.RowDecoder[$a])")
}
}

final def exportCsvRowDecoder[A](implicit a: c.WeakTypeTag[A]): c.Expr[Exported[CsvRowDecoder[A, String]]] = {
c.typecheck(q"_root_.shapeless.lazily[_root_.fs2.data.csv.generic.DerivedCsvRowDecoder[$a]]", silent = true) match {
case EmptyTree => c.abort(c.enclosingPosition, s"Unable to infer value of type $a")
final def exportRowEncoder[A](
implicit a: c.WeakTypeTag[A]): c.Expr[Exported[RowEncoder[A]]] = {
c.typecheck(
q"_root_.shapeless.lazily[_root_.fs2.data.csv.generic.DerivedRowEncoder[$a]]",
silent = true) match {
case EmptyTree =>
c.abort(c.enclosingPosition, s"Unable to infer value of type $a")
case t =>
c.Expr[Exported[RowEncoder[A]]](
q"new _root_.fs2.data.csv.Exported($t: _root_.fs2.data.csv.RowEncoder[$a])")
}
}

final def exportCsvRowDecoder[A](implicit a: c.WeakTypeTag[A])
: c.Expr[Exported[CsvRowDecoder[A, String]]] = {
c.typecheck(
q"_root_.shapeless.lazily[_root_.fs2.data.csv.generic.DerivedCsvRowDecoder[$a]]",
silent = true) match {
case EmptyTree =>
c.abort(c.enclosingPosition, s"Unable to infer value of type $a")
case t =>
c.Expr[Exported[CsvRowDecoder[A, String]]](
q"new _root_.fs2.data.csv.Exported($t: _root_.fs2.data.csv.CsvRowDecoder[$a, ${weakTypeTag[String]}])")
}
}

final def exportCellDecoder[A](implicit a: c.WeakTypeTag[A]): c.Expr[Exported[CellDecoder[A]]] = {
c.typecheck(q"_root_.shapeless.lazily[_root_.fs2.data.csv.generic.DerivedCellDecoder[$a]]", silent = true) match {
case EmptyTree => c.abort(c.enclosingPosition, s"Unable to infer value of type $a")
final def exportCsvRowEncoder[A](implicit a: c.WeakTypeTag[A])
: c.Expr[Exported[CsvRowEncoder[A, String]]] = {
c.typecheck(
q"_root_.shapeless.lazily[_root_.fs2.data.csv.generic.DerivedCsvRowEncoder[$a]]",
silent = true) match {
case EmptyTree =>
c.abort(c.enclosingPosition, s"Unable to infer value of type $a")
case t =>
c.Expr[Exported[CsvRowEncoder[A, String]]](
q"new _root_.fs2.data.csv.Exported($t: _root_.fs2.data.csv.CsvRowEncoder[$a, ${weakTypeTag[String]}])")
}
}

final def exportCellDecoder[A](
implicit a: c.WeakTypeTag[A]): c.Expr[Exported[CellDecoder[A]]] = {
c.typecheck(
q"_root_.shapeless.lazily[_root_.fs2.data.csv.generic.DerivedCellDecoder[$a]]",
silent = true) match {
case EmptyTree =>
c.abort(c.enclosingPosition, s"Unable to infer value of type $a")
case t =>
c.Expr[Exported[CellDecoder[A]]](
q"new _root_.fs2.data.csv.Exported($t: _root_.fs2.data.csv.CellDecoder[$a])")
}
}

final def exportCellEncoder[A](
implicit a: c.WeakTypeTag[A]): c.Expr[Exported[CellEncoder[A]]] = {
c.typecheck(
q"_root_.shapeless.lazily[_root_.fs2.data.csv.generic.DerivedCellEncoder[$a]]",
silent = true) match {
case EmptyTree =>
c.abort(c.enclosingPosition, s"Unable to infer value of type $a")
case t =>
c.Expr[Exported[CellDecoder[A]]](q"new _root_.fs2.data.csv.Exported($t: _root_.fs2.data.csv.CellDecoder[$a])")
c.Expr[Exported[CellEncoder[A]]](
q"new _root_.fs2.data.csv.Exported($t: _root_.fs2.data.csv.CellEncoder[$a])")
}
}
}
61 changes: 61 additions & 0 deletions csv/generic/src/fs2/data/csv/generic/MapShapedCsvRowEncoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2019 Lucas Satabin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fs2.data.csv.generic

import cats.data.NonEmptyList
import fs2.data.csv.{CellEncoder, CsvRow, CsvRowEncoder}
import shapeless._
import shapeless.labelled._

trait MapShapedCsvRowEncoder[Repr] extends CsvRowEncoder[Repr, String]

object MapShapedCsvRowEncoder extends LowPrioMapShapedCsvRowEncoderImplicits {

implicit def lastElemRowEncoder[Wrapped, Repr, Anno, Key <: Symbol](
implicit Last: CellEncoder[Repr],
ev: <:<[Anno, Option[CsvName]],
witness: Witness.Aux[Key])
: WithAnnotations[Wrapped, FieldType[Key, Repr] :: HNil, Anno :: HNil] =
(row: Repr :: HNil, annotation: Anno :: HNil) =>
CsvRow(NonEmptyList.one(Last(row.head)),
NonEmptyList.one(annotation.head.fold(witness.value.name)(_.name)))

}

trait LowPrioMapShapedCsvRowEncoderImplicits {
trait WithAnnotations[Wrapped, Repr, AnnoRepr] {
def fromWithAnnotation(row: Repr, annotation: AnnoRepr): CsvRow[String]
}

implicit def hconsRowEncoder[Wrapped,
Key <: Symbol,
Head,
Tail <: HList,
DefaultTail <: HList,
Anno,
AnnoTail <: HList](
implicit witness: Witness.Aux[Key],
Head: CellEncoder[Head],
ev: <:<[Anno, Option[CsvName]],
Tail: Lazy[WithAnnotations[Wrapped, Tail, AnnoTail]])
: WithAnnotations[Wrapped, FieldType[Key, Head] :: Tail, Anno :: AnnoTail] =
(row: FieldType[Key, Head] :: Tail, annotation: Anno :: AnnoTail) => {
val tailRow = Tail.value.fromWithAnnotation(row.tail, annotation.tail)
CsvRow(NonEmptyList(Head(row.head), tailRow.values.toList),
NonEmptyList(annotation.head.fold(witness.value.name)(_.name),
tailRow.headers.toList))
}
}
Loading