Skip to content

Commit

Permalink
Support formatters on a column by column basis
Browse files Browse the repository at this point in the history
  • Loading branch information
Astrac committed Oct 28, 2018
1 parent 04bcdaf commit 21e8a0b
Show file tree
Hide file tree
Showing 14 changed files with 89 additions and 65 deletions.
4 changes: 1 addition & 3 deletions src/main/scala/astrac/boxtables/Config.scala
@@ -1,8 +1,6 @@
package astrac.boxtables

case class RowsConfig[Primitive](theme: Theme[Primitive],
sizing: Sizing,
formatter: GenericFormatter[Primitive])
case class RowsConfig[Primitive](theme: Theme[Primitive], sizing: Sizing)

case class TableConfig[Primitive](
main: RowsConfig[Primitive],
Expand Down
4 changes: 4 additions & 0 deletions src/main/scala/astrac/boxtables/GenericAutoRow.scala
Expand Up @@ -57,4 +57,8 @@ object GenericAutoRow {

def derive[Primitive, Model](implicit r: GenericAutoRow[Primitive, Model])
: GenericRow[Primitive, Model] = apply

def formatted[Primitive, Model](f: GenericFormatter[Primitive])(
implicit r: GenericAutoRow[Primitive, Model])
: GenericRow[Primitive, Model] = derive[Primitive, Model].format(f)
}
12 changes: 9 additions & 3 deletions src/main/scala/astrac/boxtables/GenericCell.scala
Expand Up @@ -4,6 +4,10 @@ import cats.Contravariant

trait GenericCell[Primitive, Model] {
def content(a: Model): Primitive
def formatter: GenericFormatter[Primitive]

def format(f: GenericFormatter[Primitive]): GenericCell[Primitive, Model] =
GenericCell.instance(content, f)
}

object GenericCell {
Expand All @@ -12,16 +16,18 @@ object GenericCell {
: GenericCell[Primitive, Model] = c

def instance[Primitive, Model](
f: Model => Primitive): GenericCell[Primitive, Model] =
f: Model => Primitive,
fmt: GenericFormatter[Primitive]): GenericCell[Primitive, Model] =
new GenericCell[Primitive, Model] {
def content(a: Model) = f(a)
override def content(a: Model) = f(a)
override lazy val formatter = fmt
}

implicit def contravariant[Primitive]
: Contravariant[GenericCell[Primitive, ?]] =
new Contravariant[GenericCell[Primitive, ?]] {
override def contramap[A, B](fa: GenericCell[Primitive, A])(
f: B => A): GenericCell[Primitive, B] =
GenericCell.instance(b => fa.content(f(b)))
GenericCell.instance(b => fa.content(f(b)), fa.formatter)
}
}
1 change: 0 additions & 1 deletion src/main/scala/astrac/boxtables/GenericFormatter.scala
@@ -1,6 +1,5 @@
package astrac.boxtables

trait GenericFormatter[Primitive] {
def space: Primitive
def apply(s: Primitive)(w: Int): List[Primitive]
}
8 changes: 7 additions & 1 deletion src/main/scala/astrac/boxtables/GenericRow.scala
Expand Up @@ -5,8 +5,14 @@ import cats.syntax.contravariant._

sealed trait GenericRow[Primitive, Model] {
def cells: List[GenericCell[Primitive, Model]]
def contents(model: Model): List[Primitive] = cells.map(_.content(model))
lazy val columns: Int = cells.size

def format(f: GenericFormatter[Primitive]): GenericRow[Primitive, Model] =
GenericRow.instance(cells.map(_.format(f)))

def formatByIndex(
f: Int => GenericFormatter[Primitive]): GenericRow[Primitive, Model] =
GenericRow.instance(cells.zipWithIndex.map(ci => ci._1.format(f(ci._2))))
}

object GenericRow {
Expand Down
48 changes: 23 additions & 25 deletions src/main/scala/astrac/boxtables/algebra/Line.scala
Expand Up @@ -10,24 +10,25 @@ import cats.syntax.traverse._
import Sizing._

trait Line[Primitive, Model] {
implicit val PrimitiveSupport: PrimitiveSupport[Primitive]
implicit def Primitive: Monoid[Primitive]
implicit def Model: GenericRow[Primitive, Model]

val formatter: Rows[Primitive, GenericFormatter[Primitive]] = Rows.formatter
import PrimitiveSupport._

val sizing: Rows[Primitive, Sizing] = Rows.sizing
val theme: Rows[Primitive, Theme[Primitive]] = Rows.theme

def transpose(
ls: List[List[Primitive]]): Rows[Primitive, List[List[Primitive]]] =
formatter.flatMap { f =>
val w = ls.map(_.size).max
ls.zipWithIndex
.traverse {
case (l, i) =>
cellWidth(i).map(cw => l.padTo(w, Primitive.combineN(f.space, cw)))
}
.map(_.transpose)
}
ls: List[List[Primitive]]): Rows[Primitive, List[List[Primitive]]] = {
val w = ls.map(_.size).max
ls.zipWithIndex
.traverse {
case (l, i) =>
cellWidth(i).map(cw => l.padTo(w, Primitive.combineN(space, cw)))
}
.map(_.transpose)
}

def boundedSpace(w: Int): Rows[Primitive, Int] = theme.map { t =>
math.max(0,
Expand Down Expand Up @@ -118,29 +119,26 @@ trait Line[Primitive, Model] {
.flatMap(identity)

def contentLine(contents: List[Primitive]): Rows[Primitive, Primitive] =
for {
t <- theme
f <- formatter
l <- line(t.margins.fill.l,
t.borders.l,
t.padding.fill.l,
contents,
t.dividers.v.getOrElse(f.space),
t.padding.fill.r,
t.borders.r,
t.margins.fill.r)
} yield l
theme.flatMap { t =>
line(t.margins.fill.l,
t.borders.l,
t.padding.fill.l,
contents,
t.dividers.v.getOrElse(space),
t.padding.fill.r,
t.borders.r,
t.margins.fill.r)
}

def paddingLine(p: Primitive): Rows[Primitive, Primitive] =
for {
t <- theme
cs <- fillCells(p)
f <- formatter
l <- line(t.margins.fill.l,
t.borders.l,
p,
cs,
t.dividers.v.getOrElse(f.space),
t.dividers.v.getOrElse(space),
p,
t.borders.r,
t.margins.fill.r)
Expand Down
16 changes: 16 additions & 0 deletions src/main/scala/astrac/boxtables/algebra/PrimitiveSupport.scala
@@ -0,0 +1,16 @@
package astrac.boxtables
package algebra

trait PrimitiveSupport[Primitive] {
def space: Primitive
}

object PrimitiveSupport {
def instance[Primitive](s: Primitive): PrimitiveSupport[Primitive] =
new PrimitiveSupport[Primitive] {
override lazy val space = s
}

implicit val stringSupport: PrimitiveSupport[String] =
instance(" ")
}
3 changes: 0 additions & 3 deletions src/main/scala/astrac/boxtables/algebra/Rows.scala
Expand Up @@ -4,9 +4,6 @@ package algebra
import cats.data.Reader

object Rows {
def formatter[Primitive]: Rows[Primitive, GenericFormatter[Primitive]] =
Reader(_.formatter)

def sizing[Primitive]: Rows[Primitive, Sizing] =
Reader(_.sizing)

Expand Down
25 changes: 12 additions & 13 deletions src/main/scala/astrac/boxtables/algebra/Table.scala
Expand Up @@ -45,18 +45,15 @@ trait Table[Primitive, Model] extends Line[Primitive, Model] {
List.fill(t.padding.space.b)(paddingLine(t.padding.fill.b)).sequence
}

def row(model: Model): Rows[Primitive, List[Primitive]] = formatter.flatMap {
f =>
val contents = Model.contents(model)

val contentLines = contents.zipWithIndex
.traverse {
case (c, i) => cellWidth(i).map(f(c))
}
.flatMap(transpose)
.flatMap(_.traverse(contentLine))

paddingTop |+| contentLines |+| paddingBottom
def row(model: Model): Rows[Primitive, List[Primitive]] = {
val contentLines = Model.cells.zipWithIndex
.traverse {
case (c, i) => cellWidth(i).map(c.formatter(c.content(model)))
}
.flatMap(transpose)
.flatMap(_.traverse(contentLine))

paddingTop |+| contentLines |+| paddingBottom
}

lazy val tableStart = topMargin |+| topBorder
Expand All @@ -78,8 +75,10 @@ trait Table[Primitive, Model] extends Line[Primitive, Model] {
object Table {
implicit def instance[Primitive, Model](
implicit monoid: Monoid[Primitive],
modelRow: GenericRow[Primitive, Model]): Table[Primitive, Model] =
modelRow: GenericRow[Primitive, Model],
support: PrimitiveSupport[Primitive]): Table[Primitive, Model] =
new Table[Primitive, Model] {
implicit val PrimitiveSupport = support
implicit val Primitive = monoid
implicit val Model = modelRow
}
Expand Down
3 changes: 0 additions & 3 deletions src/main/scala/astrac/boxtables/string/Formatter.scala
Expand Up @@ -5,8 +5,6 @@ trait Formatter extends GenericFormatter[String]

object Formatter {
val withWordBoundaries: Formatter = new Formatter {
override val space = " "

private def breakLine(w: Int)(s: String) = {
val (lines, last) = s
.split(" ")
Expand Down Expand Up @@ -34,7 +32,6 @@ object Formatter {
}

val basic: Formatter = new Formatter {
override val space = " "
override def apply(s: String)(w: Int): List[String] =
if (w == 0) Nil
else
Expand Down
6 changes: 5 additions & 1 deletion src/main/scala/astrac/boxtables/string/Specialisations.scala
Expand Up @@ -3,12 +3,16 @@ package string

object Cell {
def apply[Model: Cell]: Cell[Model] = implicitly
def instance[Model](f: Model => String): Cell[Model] = GenericCell.instance(f)
def instance[Model](f: Model => String,
formatter: Formatter = Formatter.basic): Cell[Model] =
GenericCell.instance(f, formatter)
}

object AutoRow {
def apply[Model: AutoRow]: Row[Model] = implicitly[AutoRow[Model]].row
def derive[Model: AutoRow]: Row[Model] = AutoRow[Model]
def formatted[Model: AutoRow](f: Formatter): Row[Model] =
AutoRow[Model].format(f)
}

object Row {
Expand Down
12 changes: 5 additions & 7 deletions src/main/scala/astrac/boxtables/string/Tables.scala
Expand Up @@ -8,13 +8,12 @@ object Tables {

def simple[F[_]: Foldable, Model](data: F[Model],
sizing: Sizing,
theme: Theme[String],
formatter: Formatter = Formatter.basic)(
theme: Theme[String])(
implicit algebra: Table[String, Model]
): String =
Templates
.simple(data)
.run(TableConfig(RowsConfig(theme, sizing, formatter)))
.run(TableConfig(RowsConfig(theme, sizing)))
.mkString("\n")

def withHeader[F[_]: Foldable, Header, Model](
Expand Down Expand Up @@ -64,9 +63,8 @@ object Tables {
withHeader(
h,
as,
TableConfig[String](
main = RowsConfig(Themes.markdownMain, sizing, Formatter.basic),
header =
Some(RowsConfig(Themes.markdownHeader, sizing, Formatter.basic)))
TableConfig[String](main = RowsConfig(Themes.markdownMain, sizing),
header =
Some(RowsConfig(Themes.markdownHeader, sizing)))
)
}
4 changes: 3 additions & 1 deletion src/test/scala/astrac/boxtables/RowSpec.scala
Expand Up @@ -20,7 +20,9 @@ class RowSpec extends CatsSuite {
case Some(v) => v
}
.take(equalitySamples)
.forall(e => a.contents(e) == b.contents(e))
.forall(e =>
a.cells.map(_.content(e)) == b.cells.map(_.content(e)) &&
a.cells.map(_.formatter) == b.cells.map(_.formatter))
}

checkAll("Row.ControvariantMonoidalLaws",
Expand Down
8 changes: 4 additions & 4 deletions src/test/scala/astrac/boxtables/string/TablesSpec.scala
Expand Up @@ -140,20 +140,20 @@ class TablesSpec extends Properties("Tables") {
User("Billy Pilgrim", 20, false, Counters(5, 7)),
User("Mandarax", 3, true, Counters(10, 0))
))
.run(RowsConfig(testTheme, Sizing.Equal(w), Formatter.basic))
.run(RowsConfig(testTheme, Sizing.Equal(w)))

lines.forall(_.size == w)
}

property("TableWithWordBoundariesFormatter") = {
implicit val tupleRow: Row[(String, String, String)] = AutoRow.derive
implicit val tupleRow: Row[(String, String, String)] =
AutoRow.formatted(Formatter.withWordBoundaries)

val tbl = Tables.simple(
List.fill(2)(
(Examples.loremIpsumShort, Examples.loremIpsum, Examples.loremIpsum)),
Sizing.Weighted(60, List(2, 3, 4)),
Themes.singleLineAscii,
Formatter.withWordBoundaries
Themes.singleLineAscii
)

tbl == Examples.tableWordBoundariesFormatter
Expand Down

0 comments on commit 21e8a0b

Please sign in to comment.