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

add markdown-table module #21

Merged
merged 2 commits into from
Jul 2, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 20 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ default(
)

lazy val bytes = crossProject.settings(
v"1.2.1",
v"1.2.0",
dep(
case_app,
cats
Expand All @@ -17,7 +17,7 @@ lazy val `bytes.jvm` = bytes.jvm
lazy val `bytes-x` = parent(`bytes.js`, `bytes.jvm`)

lazy val channel = project.settings(
v"1.5.0",
v"1.5.1",
dep(
log4j tests,
math.utils % "2.2.0",
Expand All @@ -34,9 +34,9 @@ lazy val io = crossProject.settings(
dep(
case_app,
cats,
iterators % "2.1.0",
iterators % "2.2.0",
shapeless_utils % "1.3.0",
types % "1.1.0"
types % "1.2.0"
),
consoleImport(
"hammerlab.lines._",
Expand All @@ -52,9 +52,24 @@ lazy val `io.jvm` = io.jvm.settings(
)
lazy val `io-x` = parent(`io.js`, `io.jvm`)

lazy val markdown = crossProject.settings(
v"0.1.0",
dep(
hammerlab.math.format % "1.0.0",
shapeless
)
)
.dependsOn(
io
)
lazy val `markdown.js` = markdown.js
lazy val `markdown.jvm` = markdown.jvm
lazy val `markdown-x` = parent(`markdown.js`, `markdown.jvm`)

lazy val `io-utils` =
root(
channel,
`bytes-x`,
`io-x`
`io-x`,
`markdown-x`
)
10 changes: 10 additions & 0 deletions markdown/shared/src/main/scala/hammerlab/markdown.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package hammerlab

import org.hammerlab.markdown.table.HasTable

trait markdown {
trait table extends HasTable
object table extends table
}

object markdown extends markdown with HasTable
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.hammerlab.markdown.table

import hammerlab.lines._
import org.hammerlab.markdown.table.Header.Entry

trait HasTable {

object sep {
val left = "---"
val center = ":-:"
val right = "--:"
}

case class InvalidFieldOverrides(overrides: Iterable[String])
extends IllegalArgumentException(
s"Invalid field overrides: ${overrides.mkString(",")}"
)

def apply[T](
ts: Seq[T],
overrides: (Symbol, String)*
)(
implicit
toRow: ToRow[T],
header: Header[T]
):
Lines = {

val overrideMap =
overrides
.map {
case (n, display) ⇒
n.name → display
}
.toMap

val headers = header()
val headerMap =
headers
.map {
case Entry(name, display) ⇒
name → display
}
.toMap

overrideMap
.keys
.filter(!headerMap.contains(_)) match {
case missings
if missings.nonEmpty ⇒
throw InvalidFieldOverrides(missings)
case _ ⇒
}

val hdr =
headers
.map {
case Entry(name, display) ⇒
overrideMap.getOrElse(name, display)
}
val seps = Seq.fill(hdr.size)(sep.left)
val rows = ts.map(toRow(_)).toList
val lines =
hdr ::
seps ::
rows
Lines(
lines
.map(
_.mkString(
"| ",
" | ",
" |"
):
Lines
)
)
}

implicit class MarkdownTableOps[T](ts: Seq[T]) {
def mdTable(
overrides: (Symbol, String)*
)(
implicit
toRow: ToRow[T],
header: Header[T]
):
Lines =
apply(
ts,
overrides: _*
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.hammerlab.markdown.table

import hammerlab.iterator._
import hammerlab.lines._
import hammerlab.show._
import org.hammerlab.markdown.table.Header.Entry

trait Header[T] {
def apply(): Seq[Entry]
}
object Header {
case class Entry(name: String, display: String)
object Entry {
implicit val show: Show[Entry] = Show { _.display }
}

def apply[T](fn: () ⇒ Seq[Entry]): Header[T] =
new Header[T] {
def apply(): Seq[Entry] = fn()
}

val regex = "[A-Z]?[a-z]+".r
def display(name: String): String = {
regex
.findAllMatchIn(name)
.sliding2Opt
.flatMap {
case (cur, nextOpt) ⇒
val next = nextOpt.map(_.start).getOrElse(name.length)
val start = cur.start
val `match` = cur.matched
val first = `match`(0).toUpper + `match`.substring(1)
val end = start + first.length
if (end != next)
Seq(
first,
name.substring(end, next)
)
else
Seq(
first
)

}
.mkString(" ")
}

import shapeless._
import shapeless.ops.hlist.ToTraversable
import shapeless.ops.record._

implicit def default[T, L <: HList, Out <: HList](
implicit
g: LabelledGeneric.Aux[T, L],
keys: Keys.Aux[L, Out],
t: ToTraversable.Aux[Out, List, Symbol]
):
Header[T] =
Header {
() ⇒
keys
.apply
.toList
.map(_.name)
.map(n ⇒ Entry(n, display(n)))
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.hammerlab.markdown.table

import hammerlab.show._
import shapeless._

trait ToRow[T] {
def apply(t: T): Seq[String]
}

trait LowPriToRow {
implicit def fromShow[T](implicit show: Show[T]): ToRow[T] =
ToRow {
t ⇒ Seq(t.show)
}
}

object ToRow
extends LowPriToRow {

def apply[T](fn: T ⇒ Seq[String]): ToRow[T] =
new ToRow[T] {
def apply(t: T): Seq[String] = fn(t)
}

implicit def caseClass[T, L <: HList](
implicit
g: Generic.Aux[T, L],
r: Lazy[ToRow[L]]
):
ToRow[T] =
ToRow {
t ⇒
r.value(g.to(t))
}

implicit def cons[H, T <: HList](
implicit
rh: Lazy[ToRow[H]],
rt: Lazy[ToRow[T]]
):
ToRow[H :: T] =
ToRow {
case h :: t ⇒
(
rh.value(h) ++
rt.value(t)
)
.toList
}

implicit val hnil: ToRow[HNil] = ToRow { _ ⇒ Nil }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.hammerlab.markdown.table

import hammerlab.indent.spaces
import hammerlab.markdown._
import hammerlab.show._

class Test
extends hammerlab.Suite {

// Format [[Double]]s to 2 sig-figs
import hammerlab.math.sigfigs._
implicit val sigfigs: SigFigs = 2

val users =
Seq(
User("abc def", 123.4567),
User("ghi jkl mno", 2.34567)
)

test("simple") {
===(
users
.mdTable()
.showLines,
"""|| User Name | Balance |
|| --- | --- |
|| abc def | 123 |
|| ghi jkl mno | 2.3 |"""
.stripMargin
)
}

test("override fields") {
===(
users
.mdTable('balance → "Balance ($)")
.showLines,
"""|| User Name | Balance ($) |
|| --- | --- |
|| abc def | 123 |
|| ghi jkl mno | 2.3 |"""
.stripMargin
)
}

test("invalid override") {
intercept[InvalidFieldOverrides] {
users.mdTable('balanc → "Balance…")
}
}
}

case class User(userName: String, balance: Double)
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
addSbtPlugin("org.hammerlab.sbt" % "base" % "4.5.1")
addSbtPlugin("org.hammerlab.sbt" % "base" % "4.6.1")