summaryrefslogtreecommitdiffstats
path: root/src/main/scala/router.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/router.scala')
-rw-r--r--src/main/scala/router.scala136
1 files changed, 136 insertions, 0 deletions
diff --git a/src/main/scala/router.scala b/src/main/scala/router.scala
new file mode 100644
index 0000000..f1fbfa7
--- /dev/null
+++ b/src/main/scala/router.scala
@@ -0,0 +1,136 @@
+package router
+
+import scala.collection.mutable.ArrayBuffer
+import scala.util.matching.Regex
+
+class Router[T] {
+ val routes = new ArrayBuffer[Route[T]]()
+
+ def addRoute (
+ path: String,
+ target: T,
+ defaults: Map[String, String] = Map(),
+ validations: Map[String, Regex] = Map()
+ ) {
+ routes += new Route(path, defaults, validations, target)
+ }
+
+ def insertRoute (
+ path: String,
+ target: T,
+ defaults: Map[String, String] = Map(),
+ validations: Map[String, Regex] = Map(),
+ at: Int = 0
+ ) {
+ routes insert (
+ at min routes.length,
+ new Route(path, defaults, validations, target)
+ )
+ }
+
+ def route (path: String): Option[Match[T]] = {
+ def _route (
+ components: Seq[String],
+ routes: List[Route[T]]
+ ): Option[Match[T]] = routes match {
+ case r :: rs => r.route(components) match {
+ case Some(found) => Some(found)
+ case None => _route(components, rs)
+ }
+ case _ => None
+ }
+ _route(path.split("/"), routes.toList)
+ }
+
+ def uriFor (mapping: Map[String, String]): String = {
+ throw new Error("unimplemented")
+ }
+}
+
+class Route[T] (
+ val path: String,
+ val defaults: Map[String, String],
+ val validations: Map[String, Regex],
+ val target: T
+) {
+ def route(
+ parts: Seq[String],
+ components: Seq[String] = components,
+ mapping: Map[String, String] = defaults
+ ): Option[Match[T]] = {
+ if (components.length == 0 && parts.length == 0) {
+ Some(new Match[T](path, mapping, target))
+ }
+ else if (components.length == 0 || parts.length == 0) {
+ None
+ }
+ else {
+ components.head match {
+ case Optional(name) => {
+ throw new Error("unsupported")
+ }
+ case Variable(name) => {
+ if (validate(name, parts.head)) {
+ route(parts.tail, components.tail, mapping + (name -> parts.head))
+ }
+ else {
+ None
+ }
+ }
+ case literal => parts.head match {
+ case `literal` => route(parts.tail, components.tail, mapping)
+ case _ => None
+ }
+ }
+ }
+ }
+
+ override def toString = path
+
+ private lazy val components =
+ path.split("/").filter(_.length > 0)
+
+ private lazy val length =
+ components.length
+
+ private lazy val lengthWithoutOptionals =
+ components.filter(!isOptional(_)).length
+
+ private lazy val requiredVariableComponentNames =
+ for (c <- components if isVariable(c) && !isOptional(c))
+ yield getComponentName(c)
+
+ private lazy val optionalVariableComponentNames =
+ for (c <- components if isVariable(c) && isOptional(c))
+ yield getComponentName(c)
+
+ private val Optional = """^\?:(.*)$""".r
+ private val Variable = """^\??:(.*)$""".r
+
+ private def isOptional (component: String): Boolean = component match {
+ case Optional(_) => true
+ case _ => false
+ }
+
+ private def isVariable (component: String): Boolean = component match {
+ case Variable(_) => true
+ case _ => false
+ }
+
+ private def getComponentName (component: String) = component match {
+ case Variable(name) => Some(name)
+ case _ => None
+ }
+
+ private def validate (name: String, component: String): Boolean =
+ validations get name match {
+ case Some(rx) => rx.findFirstIn(component).nonEmpty
+ case None => true
+ }
+}
+
+class Match[T] (
+ val path: String,
+ val mapping: Map[String, String],
+ val target: T
+)