Last active
October 13, 2015 22:58
-
-
Save oyvindberg/3a04677f107dee9c1152 to your computer and use it in GitHub Desktop.
linx med shapeless
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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