Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian Forsey committed Nov 17, 2012
0 parents commit 34ca453
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
target/
*.iml
*.ipr
*.iws
.idea/

.project

# OS X
Icon
Thumbs.db
.DS_Store

.history
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Scala scala-uri

`scala-uri` is a small self contained Scala library that helps you work with URIs. It can be used outside a servlet environment as it has zero dependencies on the servlet spec or existing web frameworks.

## Including in your project

TODO: Put in repo. Add SBT config

## Building URIs with the DSL

import com.github.theon.uri.Uri._
val uri = "http://theon.github.com/scala-uri" ? ("param1" -> "1") & ("param2" -> "2")

## Parsing URIs

import com.github.theon.uri.Uri._
val uri = parseUri("http://theon.github.com/scala-uri?param1=1&param2=2")
15 changes: 15 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
organization := "com.github.theon"

version := "0.1-SNAPSHOT"

scalaVersion := "2.9.2"

scalacOptions := Seq("-Ydependent-method-types", "-unchecked", "-deprecation", "-encoding", "utf8")

resolvers ++= Seq(
"Sonatype OSS" at "http://oss.sonatype.org/content/groups/scala-tools"
)

libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "1.8" % "test"
)
121 changes: 121 additions & 0 deletions src/main/scala/com/github/theon/uri/Uri.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.github.theon.uri

import java.net.URI
import com.github.theon.uri.Uri._

case class Uri(protocol:Option[String], host:Option[String], path:String, query:Querystring = Querystring()) {

def param(kv:(String, Any)) = {
val (k,v) = kv
v match {
case valueOpt:Option[_] =>
copy(query = query & (k, valueOpt))
case _ =>
copy(query = query & (k, Some(v)))
}
}

def ?(kv:(String, Any)) = param(kv)
def &(kv:(String, Any)) = param(kv)

def replace(k:String, v:String) = {
copy(query = query.replace(k, v))
}

override def toString() = toString(true)
def toStringRaw() = toString(false)

def toString(enc:Boolean) = {
val encPath = if(enc) uriEncode(path) else path

protocol.map(_ + "://").getOrElse("") +
host.getOrElse("") +
encPath +
query.toString("?", enc)
}
}

case class Querystring(params:Map[String,List[String]] = Map()) {

def replace(k:String, v:String) = {
copy(params = params + (k -> List(v)))
}

def &(kv:(String, Option[Any])) = {
val (k,vOpt) = kv
vOpt match {
case Some(v) => {
val values = params.getOrElse(k, List())
copy(params = params + (k -> (v.toString :: values)))
}
case _ => this
}
}

override def toString() = toString(true)
def toStringRaw() = toString(false)

def toString(prefix:String, enc:Boolean):String = {
if(params.isEmpty) {
""
} else {
prefix + toString(enc)
}
}

def toString(enc:Boolean) = {
params.flatMap(kv => {
val (k,v) = kv
if(enc) {
v.map(uriEncode(k) + "=" + uriEncode(_))
} else {
v.map(k + "=" + _)
}
}).mkString("&")
}
}

object Uri {
implicit def stringToUri(s:String) = parseUri(s)
implicit def uriToString(uri:Uri):String = uri.toString

def parseUri(s:String) = {
val uri = new URI(s)
val q = parseQuery(Option(uri.getQuery))
Uri(Option(uri.getScheme), Option(uri.getAuthority), uri.getPath, q)
}

def parseQuery(qsOpt:Option[String]) = {
qsOpt match {
case None => Querystring()
case Some(qs) => {
val tuples = qs.split("&").map(pairStr => {
val pair = pairStr.split("=")
(pair(0), pair(1))
}).toList

val map = tuples.groupBy(_._1).map(kv => {
val (k,v) = kv
(k,v.map(_._2))
})

Querystring(map)
}
}
}

def uriEncode(s:String) = new URI(null, s, null).toASCIIString
def uriDecode(s:String) = URI.create(s).getPath

def apply(protocol:String, host:String, path:String):Uri =
Uri(Some(protocol), Some(host), path)

def apply(protocol:String, host:String, path:String, query:Querystring):Uri =
Uri(Some(protocol), Some(host), path, query)

def apply(path:String):Uri =
Uri(None, None, path)

def apply(path:String, query:Querystring):Uri =
Uri(None, None, path, query)
}
34 changes: 34 additions & 0 deletions src/test/scala/com/github/theon/urlutils/DslTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.github.theon.urlutils

import org.scalatest._
import org.scalatest.matchers._
import com.github.theon.uri.Uri
import com.github.theon.uri.Uri._

class DslTests extends FlatSpec with ShouldMatchers {

"A simple absolute URI" should "render correctly" in {
val uri:Uri = "http://theon.github.com/uris-in-scala.html"
uri.toString should equal ("http://theon.github.com/uris-in-scala.html")
}

"A simple relative URI" should "render correctly" in {
val uri:Uri = "/uris-in-scala.html"
uri.toString should equal ("/uris-in-scala.html")
}

"An absolute URI with querystring params" should "render correctly" in {
val uri = "http://theon.github.com/uris-in-scala.html" ? ("testOne" -> "1") & ("testTwo" -> "2")
uri.toString should equal ("http://theon.github.com/uris-in-scala.html?testOne=1&testTwo=2")
}

"A relative URI with querystring params" should "render correctly" in {
val uri = "/uris-in-scala.html" ? ("testOne" -> "1") & ("testTwo" -> "2")
uri.toString should equal ("/uris-in-scala.html?testOne=1&testTwo=2")
}

"Multiple querystring params with the same key" should "render correctly" in {
val uri = "/uris-in-scala.html" ? ("testOne" -> "1") & ("testOne" -> "2")
uri.toString should equal ("/uris-in-scala.html?testOne=2&testOne=1")
}
}
19 changes: 19 additions & 0 deletions src/test/scala/com/github/theon/urlutils/EncodingTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.github.theon.urlutils

import org.scalatest._
import org.scalatest.matchers._
import com.github.theon.uri.Uri
import com.github.theon.uri.Uri._

class EncodingTests extends FlatSpec with ShouldMatchers {

"URI paths" should "be percent encoded" in {
val uri:Uri = "http://theon.github.com/üris-in-scàla.html"
uri.toString should equal ("http://theon.github.com/%C3%BCris-in-sc%C3%A0la.html")
}

"Querystring parameters" should "be percent encoded" in {
val uri = "http://theon.github.com/uris-in-scala.html" ? ("càsh" -> "£50") & ("©opyright" -> "false")
uri.toString should equal ("http://theon.github.com/uris-in-scala.html?c%C3%A0sh=%C2%A350&%C2%A9opyright=false")
}
}
33 changes: 33 additions & 0 deletions src/test/scala/com/github/theon/urlutils/ParsingTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.theon.urlutils

import org.scalatest._
import org.scalatest.matchers._
import com.github.theon.uri.Uri
import com.github.theon.uri.Uri._

class ParsingTests extends FlatSpec with ShouldMatchers {

"Parsing an absolute URI" should "result in a valid Uri object" in {
val uri = parseUri("http://theon.github.com/uris-in-scala.html")
uri.protocol should equal (Some("http"))
uri.host should equal (Some("theon.github.com"))
uri.path should equal ("/uris-in-scala.html")
}

"Parsing a relative URI" should "result in a valid Uri object" in {
val uri = parseUri("/uris-in-scala.html")
uri.protocol should equal (None)
uri.host should equal (None)
uri.path should equal ("/uris-in-scala.html")
}

"Parsing a URI with querystring paramteres" should "result in a valid Uri object" in {
val uri = parseUri("/uris-in-scala.html?query_param_one=hello&query_param_one=goodbye&query_param_two=false")
uri.query.params should equal (
Map(
("query_param_two" -> List("false")),
("query_param_one" -> List("hello", "goodbye"))
)
)
}
}

0 comments on commit 34ca453

Please sign in to comment.