Skip to content

Instantly share code, notes, and snippets.

@vaclavsvejcar
Created November 29, 2016 20:05
Show Gist options
  • Save vaclavsvejcar/a33711fa34cb71a6c7492075218c5ed4 to your computer and use it in GitHub Desktop.
Save vaclavsvejcar/a33711fa34cb71a6c7492075218c5ed4 to your computer and use it in GitHub Desktop.
package com.norcane.reelife.client.router
import scala.annotation.tailrec
import scala.language.implicitConversions
package object syntax {
type Out = String
def bind[R](routeDef: RouteDef[R]): R = routeDef.self
protected def splitPath(path: String): List[String] = path.split("/").dropWhile(_.isEmpty).toList
sealed trait PathElem
case class Static(name: String) extends PathElem {
override def toString: String = name
}
case object * extends PathElem
case object ** extends PathElem
sealed trait RouteDef[Self] {
def self: Self
def elems: List[PathElem]
}
implicit def stringToRouteDef0(name: String): RouteDef0 = RouteDef0(Static(name) :: Nil)
implicit def asterixToRoutePath1(ast: *.type): RouteDef1 = RouteDef1(ast :: Nil)
implicit def stringToStatic(name: String): Static = Static(name)
case class RouteDef0(elems: List[PathElem]) extends RouteDef[RouteDef0] {
def /(static: Static) = RouteDef0(elems :+ static)
def /(p: PathElem) = RouteDef1(elems :+ p)
def self = RouteDef0(elems)
def to(f0: () ⇒ Out) = Route0(this, f0)
}
case class RouteDef1(elems: List[PathElem]) extends RouteDef[RouteDef1] {
def /(static: Static) = RouteDef1(elems :+ static)
def /(p: PathElem) = RouteDef2(elems :+ p)
def self = RouteDef1(elems)
def to[A: PathParam : Manifest](f1: (A) ⇒ Out) = Route1(this, f1)
}
case class RouteDef2(elems: List[PathElem]) extends RouteDef[RouteDef2] {
def /(static: Static) = RouteDef2(elems :+ static)
def self = RouteDef2(elems)
def to[A: PathParam : Manifest, B: PathParam : Manifest](f2: (A, B) ⇒ Out) = Route2(this, f2)
}
sealed trait Route[RD] {
def routeDef: RouteDef[RD]
def unapply(path: String): Option[() => Out]
}
case class Route0(routeDef: RouteDef0, f0: () ⇒ Out) extends Route[RouteDef0] {
def apply(): String = PathMatcher0(routeDef.elems)()
override def unapply(path: String): Option[() => Out] =
PathMatcher0.unapply(routeDef.elems, splitPath(path), f0)
}
case class Route1[A: PathParam : Manifest](routeDef: RouteDef1, f1: (A) ⇒ Out) extends Route[RouteDef1] {
def apply(a: A): String = PathMatcher1(routeDef.elems)(a)
override def unapply(path: String): Option[() => Out] =
PathMatcher1.unapply(routeDef.elems, splitPath(path), f1)
}
case class Route2[A: PathParam : Manifest, B: PathParam : Manifest](routeDef: RouteDef2, f2: (A, B) ⇒ Out) extends Route[RouteDef2] {
def apply(a: A, b: B): String = PathMatcher2(routeDef.elems)(a, b)
override def unapply(path: String): Option[() => Out] =
PathMatcher2.unapply(routeDef.elems, splitPath(path), f2)
}
trait PathParam[T] {
def apply(t: T): String
def unapply(s: String): Option[T]
}
implicit val StringPathParam: PathParam[String] = new PathParam[String] {
def apply(s: String): String = s
def unapply(s: String): Option[String] = Some(s)
}
implicit val BooleanPathParam: PathParam[Boolean] = new PathParam[Boolean] {
def apply(b: Boolean): String = b.toString
def unapply(s: String): Option[Boolean] = s.toLowerCase match {
case "1" | "true" | "yes" ⇒ Some(true)
case "0" | "false" | "no" ⇒ Some(false)
case _ ⇒ None
}
}
object PathMatcher0 {
def apply(elems: List[PathElem])(): String = elems.mkString("/", "/", "")
def unapply(elems: List[PathElem], parts: List[String], handler: () => Out): Option[() => Out] =
(elems, parts) match {
case (Nil, Nil) => Some(handler)
case (Static(x) :: xs, y :: ys) if x == y => unapply(xs, ys, handler)
case _ => None
}
}
object PathMatcher1 {
def apply[A](elems: List[PathElem], prefix: List[PathElem] = Nil)(a: A)
(implicit ppa: PathParam[A]): String = elems match {
case Static(x) :: rest => apply(rest, prefix :+ Static(x))(a)
case (* | **) :: rest => PathMatcher0(prefix ::: Static(ppa(a)) :: rest)()
case _ => PathMatcher0(elems)()
}
@tailrec
def unapply[A](elems: List[PathElem], parts: List[String], handler: (A) => Out)
(implicit ppa: PathParam[A]): Option[() => Out] = (elems, parts) match {
case (Static(x) :: xs, y :: ys) if x == y => unapply(xs, ys, handler)
case (* :: xs, ppa(a) :: ys) => PathMatcher0.unapply(xs, ys, () => handler(a))
case (** :: xs, ys) => ppa.unapply(ys.mkString("/")).map { a => () => handler(a) }
case _ => None
}
}
object PathMatcher2 {
def apply[A, B](elems: List[PathElem], prefix: List[PathElem] = Nil)(a: A, b: B)
(implicit ppa: PathParam[A], ppb: PathParam[B]): String = elems match {
case Static(x) :: rest => apply(rest, prefix :+ Static(x))(a, b)
case (* | **) :: rest => PathMatcher1(prefix ::: Static(ppa(a)) :: rest)(b)
case _ => PathMatcher0(elems)()
}
@tailrec
def unapply[A, B](elems: List[PathElem], parts: List[String], handler: (A, B) => Out)
(implicit ppa: PathParam[A], ppb: PathParam[B]): Option[() => Out] =
(elems, parts) match {
case (Static(x) :: xs, y :: ys) if x == y => unapply(xs, ys, handler)
case (* :: xs, ppa(a) :: ys) => PathMatcher1.unapply(xs, ys, (b: B) => handler(a, b))
case _ => None
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment