Skip to content

Instantly share code, notes, and snippets.

@oyvindberg
Last active October 13, 2015 22:58
Show Gist options
  • Save oyvindberg/3a04677f107dee9c1152 to your computer and use it in GitHub Desktop.
Save oyvindberg/3a04677f107dee9c1152 to your computer and use it in GitHub Desktop.
linx med shapeless
package linx
import com.olvind.stringifiers.Stringifier
import shapeless._
import scala.language.implicitConversions
object Root extends StaticLinx(Vector.empty)
sealed trait Part
case class Literal(name: String) extends Part
case class Var[C](name: String, S: Stringifier[C]) extends Part
object Linx {
val ::: = scala.collection.immutable.::
class VarOps[A <: HList, X](l: Linx[A, Option[X]]) {
def apply(a: A) = l.links(a).head
}
implicit def VarOps[A <: HList, X](l: Linx[A, Option[X]]): VarOps[A, X] =
new VarOps[A, X](l)
class NoVarOps[X](l: Linx[HNil, X]) {
def apply(): String = l.links(HNil).head
}
implicit def NoVarOps[X](l: Linx[HNil, X]): NoVarOps[X] =
new NoVarOps[X](l)
}
import Linx.:::
sealed trait Linx[A <: HList, X] {
def split(s: String): List[String] =
s.split('/').toList match {
case "" ::: t => t
case t => t
}
def links(a: A): Stream[String] =
elements(a).map(_.mkString("/", "/", ""))
def /(s: Symbol): Linx[String :: A, Option[String :: A]] =
new VariableLinx[String, A](this, Vector.empty, s)
def /-[C: Stringifier](s: Symbol): Linx[C :: A, Option[C :: A]] =
new VariableLinx[C, A](this, Vector.empty, s)
def |(or: Linx[A, X])(implicit matcher: UnapplyMatch[X]): Linx[A, X] =
new UnionLinx(this, or, matcher)
def /(name: String): Linx[A, X]
def elements(a: A): Stream[Vector[String]]
def extract(seq: List[String]): Stream[(A, List[String])]
def unapply(s: String): X
def parts: Stream[Vector[Part]]
def templates(render: String => String): Stream[String] =
parts.map(_.map {
case Literal(l) => l
case Var(v, _) => render(v)
}.mkString("/", "/", ""))
def template(render: String => String): String =
templates(render).head
// rfc6570 uri template
override def toString = template("{" + _ + "}")
}
class StaticLinx(val static: Vector[String]) extends Linx[HNil, Boolean] {
override def unapply(s: String): Boolean =
extract(split(s)).exists(_._2.isEmpty)
override def /(name: String): StaticLinx =
new StaticLinx(static :+ name)
override def elements(a: HNil): Stream[Vector[String]] =
Stream(static)
override def extract(seq: List[String]): Stream[(HNil, List[String])] =
if (seq.startsWith(static)) Stream((HNil, seq.drop(static.size))) else Stream.empty
override def parts: Stream[Vector[Literal]] =
Stream(static.map(Literal))
}
class VariableLinx[C, P <: HList](
parent: Linx[P, _],
static: Vector[String],
symbol: Symbol)
(implicit C: Stringifier[C]) extends Linx[C :: P, Option[C :: P]] {
override def /(name: String): VariableLinx[C, P] =
new VariableLinx(parent, static :+ name, symbol)
override def elements(a: C :: P): Stream[Vector[String]] = {
val (part, p) = a.split[Nat._1]
parent.elements(p).map(_ ++ (C.encode(part.head) +: static))
}
override def extract(seq: List[String]): Stream[(C :: P, List[String])] =
for {
(p, head ::: tail) <- parent.extract(seq) if tail.startsWith(static)
parsedHead <- C.decode(head).right.toOption
} yield (parsedHead :: p, tail.drop(static.size))
override def unapply(s: String): Option[C :: P] =
(for {(a, Nil) <- extract(split(s))} yield a).headOption
override def parts: Stream[Vector[Part]] =
parent.parts.map(_ ++ (Var(symbol.name, C) +: static.map(Literal)))
}
class UnionLinx[A <: HList, X](
first: Linx[A, X],
next: Linx[A, X],
matcher: UnapplyMatch[X]) extends Linx[A, X] {
override def /(name: String): UnionLinx[A, X] =
new UnionLinx(first / name, next / name, matcher)
override def elements(a: A): Stream[Vector[String]] =
first.elements(a) #::: next.elements(a)
override def extract(seq: List[String]): Stream[(A, List[String])] =
first.extract(seq) #::: next.extract(seq)
override def unapply(s: String): X = {
val firstX = first.unapply(s)
if (matcher.is(firstX)) firstX else next.unapply(s)
}
override def parts: Stream[Vector[Part]] =
first.parts #::: next.parts
}
sealed case class UnapplyMatch[X] private(is: X => Boolean)
object UnapplyMatch {
implicit val boolean = UnapplyMatch[Boolean](identity)
implicit def option[A] = UnapplyMatch[Option[A]](_.isDefined)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment