Skip to content

Instantly share code, notes, and snippets.

@guersam
Last active July 2, 2016 10:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guersam/2c72fa36977e494ea1db to your computer and use it in GitHub Desktop.
Save guersam/2c72fa36977e494ea1db to your computer and use it in GitHub Desktop.
Generate a bidimensional table from a sequence of arbitrary case class
import cats.Show
import shapeless._
import shapeless.ops.record._
import shapeless.ops.hlist._
import scala.collection.immutable
trait Tablifier[A] {
def head: List[String]
def rows(objs: immutable.Seq[A]): List[List[String]]
}
object Tablifier extends TablifierDerivation {
implicit def apply[A](implicit t: Tablifier[A]): Tablifier[A] = t
}
trait TablifierDerivation {
implicit def derive[
A,
GenRepr <: HList,
KeysRepr <: HList,
ValuesRepr <: HList,
StrRepr <: HList
] (
implicit
gen: LabelledGeneric.Aux[A, GenRepr],
keys: Keys.Aux[GenRepr, KeysRepr],
values: Values.Aux[GenRepr, ValuesRepr],
keysMapper: Mapper.Aux[toColumnTitle.type, KeysRepr, StrRepr],
valuesMapper: Mapper.Aux[toColumnValue.type, ValuesRepr, StrRepr],
toStrList: ToList[StrRepr, String]
): Tablifier[A] =
new Tablifier[A] {
override val head: List[String] = keys.apply.map(toColumnTitle).toList
override def rows(objs: immutable.Seq[A]): List[List[String]] =
objs
.iterator
.map { obj =>
val repr = gen.to(obj)
values.apply(repr).map(toColumnValue).toList
}
.toList
}
}
object toColumnTitle extends Poly1 {
implicit def keyToName[A] = at[Symbol with A](_.name)
}
object toColumnValue extends Poly1 with ToColumnValue0 {
implicit def booleanCase = at[Boolean](bool => if (bool) "O" else "X")
implicit def bigDecimalCase = at[BigDecimal](x => x formatted (if (x.isValidLong) "%.0f" else "%.4f"))
implicit def fractionalCase[A: Fractional] = at[A](_ formatted "%.4f")
implicit def optionCase[A](implicit c: Case.Aux[A, String]) = at[Option[A]](_.fold("-")(c(_)))
}
trait ToColumnValue1 { this: Poly1 =>
implicit def default[A] = at[A](_.toString)
}
trait ToColumnValue0 extends ToColumnValue1 { this: Poly1 =>
implicit def showCase[A](implicit s: Show[A]) = at[A](s.show)
}
object TablifierDemo extends App {
sealed trait Perk
object Perk {
case object Polymorph extends Perk
implicit val perkShow: Show[Perk] =
new Show[Perk] {
override def show(p: Perk): String = s"[$p]"
}
}
case class Row(id: Int, name: String, isHuman: Boolean, weight: BigDecimal, perk: Option[Perk])
val rows = List(
Row(1, "Finn", true, BigDecimal("12.34567"), None),
Row(2, "Jake", false, BigDecimal("1234567"), Some(Perk.Polymorph))
)
val tablifier = Tablifier[Row]
assert(tablifier.head == List("id", "name", "isHuman", "weight", "perk"))
assert(
tablifier.rows(rows) == List(
List("1", "Finn", "O", "12.3457", "-"),
List("2", "Jake", "X", "1234567", "[Polymorph]")
)
)
}
@genTable[A](title: String, level: Int, objs: immutable.Seq[A], drop: Int = 0)(implicit tablifier: Tablifier[A]) = {
@if(objs.nonEmpty) {
<h@level>@title</h@level>
<table class="table table-bordered table-hover">
<thead>
<tr>
@for(key <- tablifier.head.drop(drop)) {
<th>@key</th>
}
</tr>
</thead>
<tbody>
@for(row <- tablifier.rows(objs)) {
<tr>
@for(col <- row.drop(drop)) {
<td>@col</td>
}
</tr>
}
</tbody>
</table>
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment