diff --git a/.gitignore b/.gitignore index d15c3d0..084d85e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ lib clout*.jar pom.xml .lein-failures +.lein-* +node_modules/ +target/ +resources/js/clout_*.js \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..193482d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: + - node_js + - clojure +lein: lein2 +jdk: + - oraclejdk7 +node_js: + - "0.8" +before_script: + - "export DISPLAY=:99:0" + - "echo 'Compile to javascript'" + - "lein2 cljsbuild once" + - "echo 'Starting buster server'" + - "./node_modules/buster/bin/buster-server &" + - "sleep 3" + - "echo 'Done.'" + - "echo 'Starting phantomjs server'" + - "phantomjs ./node_modules/buster/script/phantom.js &" + - "sleep 3" + - "echo 'Done.'" +script: + - lein2 all test && npm test \ No newline at end of file diff --git a/README.md b/README.md index 273fd87..2391ec8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Clout +Clout [![Build Status](https://travis-ci.org/BirdseyeSoftware/clout.png?branch=cljs)](https://travis-ci.org/BirdseyeSoftware/clout) ===== Clout is a library for matching [Ring][1] HTTP requests. It uses the same @@ -56,7 +56,7 @@ For additional performance, you can choose to pre-compile a route: user=> (def user-route (route-compile "/user/:id")) #'user/user-route user=> (route-matches user-route (request :get "/user/10")) - {:user "10"} + {:id "10"} When compiling a route, you can specify a map of regular expressions to use for different keywords. This allows more specific routing: @@ -64,7 +64,7 @@ for different keywords. This allows more specific routing: user=> (def user-route (route-compile "/user/:id" {:id #"\d+"})) #'user/user-route user=> (route-matches user-route (request :get "/user/10")) - {:user "10"} + {:id "10"} user=> (route-matches user-route (request :get "/user/jsmith")) nil diff --git a/buster.js b/buster.js new file mode 100644 index 0000000..72444e3 --- /dev/null +++ b/buster.js @@ -0,0 +1,14 @@ +var config = exports; + +config['Browser Tests'] = { + environment: 'browser', + sources: [], + tests: [ "resources/js/clout_browser_test.js" + , "resources/js/clout_browser_advanced_test.js"] +}; + + config['Server Tests'] = { + environment: 'node', + sources: [], + tests: ["resources/js/clout_node_test.js"] + }; diff --git a/dalap_rules.clj b/dalap_rules.clj new file mode 100644 index 0000000..0681bea --- /dev/null +++ b/dalap_rules.clj @@ -0,0 +1,7 @@ +{ + ["src/clj/clout/core.clj" "src/cljs/clout/core.cljs"] + [] + + ["test/clj/clout/test/core.clj" "test/cljs/clout/test/core.cljs"] + [] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..482ee85 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "clout", + "description": "", + "version": "1.1.0", + "author": "", + "dependencies" : { "buster" : "0.6.11" }, + "scripts": { "test": "buster-test -e node && buster-test -e browser" } +} diff --git a/project.clj b/project.clj index e86be63..bb85b00 100644 --- a/project.clj +++ b/project.clj @@ -1,6 +1,51 @@ -(defproject clout "1.1.0" +(defproject clout "1.1.1-SNAPSHOT" :description "A HTTP route matching library" :url "http://github.com/weavejester/clout" - :dependencies [[org.clojure/clojure "1.2.1"]] + :dependencies [[org.clojure/clojure "1.5.1"] + [com.birdseye-sw/buster-cljs "0.1.0"]] + :plugins [[lein-cljsbuild "0.3.0"] + [com.birdseye-sw/lein-dalap "0.1.0"]] + :hooks [leiningen.dalap] :profiles - {:dev {:dependencies [[ring-mock "0.1.1"]]}}) + {:1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]} + :dev {:dependencies [[org.clojure/tools.namespace "0.2.1"] + [ring-mock "0.1.1"]]}} + + :aliases { "all" ["with-profile" "dev:dev,1.4"]} + :source-paths ["src/clj"] + :test-paths ["test/clj"] + + :cljsbuild + {:builds + [{:id "dev" + :source-path "src/cljs" + :compiler + {:optimizations :simple + :pretty-print true + :output-to "resources/js/clout_dev.js"}} + ;; + {:id "browser-test" + :source-path "test/cljs" + :compiler + {:optimizations :simple + :pretty-print true + :externs ["externs/buster.js"] + :libraries ["resources/js/clout_dev.js"] + :output-to "resources/js/clout_browser_test.js"}} + {:id "browser-advanced-test" + :source-path "test/cljs" + :compiler + {:optimizations :advanced + :externs ["externs/buster.js"] + :libraries ["resources/js/clout_dev.js"] + :output-to "resources/js/clout_browser_advanced_test.js"}} + ;; + {:id "node-test" + :source-path "test/cljs" + :compiler + {:optimizations :simple + :pretty-print true + :target :node + :externs ["externs/buster.js"] + :libraries ["resources/js/clout_dev.js"] + :output-to "resources/js/clout_node_test.js"}}]}) diff --git a/src/clout/core.clj b/src/clj/clout/core.clj similarity index 53% rename from src/clout/core.clj rename to src/clj/clout/core.clj index 3b3c534..1ee4a0a 100644 --- a/src/clout/core.clj +++ b/src/clj/clout/core.clj @@ -1,10 +1,51 @@ +^{:cljs + '(ns clout.core + "Library for parsing the Rails routes syntax." + (:require [goog.string :as gstring] + [clojure.string :as string] + [goog.Uri :as uri]))} (ns clout.core "Library for parsing the Rails routes syntax." (:require [clojure.string :as string]) - (:import java.util.Map - java.util.regex.Matcher + (:import java.util.regex.Matcher [java.net URLDecoder URLEncoder])) +#_(:cljs + (defn re-matcher [re s] + (let [re (js* "new RegExp(re.source, 'g')") + match (.exec re s) + matcher (js-obj)] + ;; (log "* " re " " s) + ;; NOTE: we avoid setting this attributes + ;; through js-obj given that it doesn't work + ;; on advanced compilation mode + (set! (.-lookingAt matcher) + (fn [] + (and match + (= (.-index match) 0)))) + (set! (.-matches matcher) + (fn [] + (and match + (= s (aget match 0))))) + (set! (.-start matcher) + (fn [] + (and match + (.-index match)))) + (set! (.-end matcher) + (fn [] + (if match + (.-lastIndex re) + (throw (js/Error. "invalid state"))))) + (set! (.-groupCount matcher) + (fn [] + (or (and match (.-length match)) + 0))) + (set! (.-group matcher) + (fn [& [i]] + (and match + (aget match (or i 0))))) + matcher))) + ;; Regular expression utilties (def ^{:private true} re-chars @@ -15,14 +56,16 @@ [s] (string/escape s - #(if (re-chars %) (str \\ %)))) + (reduce (fn [m c] (assoc m c (str \\ c))) {} re-chars))) (defn re-groups* "More consistant re-groups that always returns a vector of groups, even if there is only one group." [^Matcher matcher] (for [i (range (.groupCount matcher))] - (.group matcher (int (inc i))))) + (do + ;; (log ".group - " (.group matcher (int (inc i)))) + (.group matcher (int (inc i)))))) ;; Route matching @@ -31,7 +74,10 @@ ([path] (path-decode path "UTF-8")) ([path encoding] - (-> (string/replace path "+" (URLEncoder/encode "+" encoding)) + (-> (string/replace path "+" + ^{:cljs '(gstring/urlEncode "+")} + (URLEncoder/encode "+" encoding)) + ^{:cljs '(gstring/urlDecode)} (URLDecoder/decode encoding)))) (defn- assoc-vec @@ -43,6 +89,7 @@ (if (vector? cur) (conj cur v) [cur v]) + v))) (defn- assoc-keys-with-groups @@ -80,8 +127,10 @@ (request-url request) (path-info request)) matcher (re-matcher re path-info)] + ;; (log ".matches - " (.matches matcher)) (if (.matches matcher) (assoc-keys-with-groups + ^{:cljs '(re-groups* matcher)} (map path-decode (re-groups* matcher)) keys))))) @@ -94,8 +143,11 @@ (fn [[re action]] (let [matcher (re-matcher re src)] (if (.lookingAt matcher) - [(if (fn? action) (action matcher) action) - (subs src (.end matcher))]))) + (do + ;; (log ".lookingAt - " (.lookingAt matcher)) + ;; (log ".end - " (.end matcher)) + [(if (fn? action) (action matcher) action) + (subs src (.end matcher))])))) (partition 2 clauses))) (defn- lex @@ -116,24 +168,40 @@ [path] (boolean (re-matches #"(https?:)?//.*" path))) +^{:cljs '(def -word-regexp #":([a-zA-Z_][\w\-]*)")} +(def -word-regexp #":([\p{L}_][\p{L}_0-9-]*)") + +^{:cljs '(def -literal-regexp #"(:[^a-zA-Z_*]|[^:*])+")} +(def -literal-regexp #"(:[^\p{L}_*]|[^:*])+") + (defn route-compile "Compile a path string using the routes syntax into a uri-matcher struct." ([path] (route-compile path {})) ([path regexs] (let [splat #"\*" - word #":([\p{L}_][\p{L}_0-9-]*)" - literal #"(:[^\p{L}_*]|[^:*])+" - word-group #(keyword (.group ^Matcher % 1)) + word -word-regexp + literal -literal-regexp + word-group #(do + ;; (log ".group - " (.group ^Matcher % 1)) + (keyword (.group ^Matcher % 1))) word-regex #(regexs (word-group %) "[^/,;?]+")] (CompiledRoute. (re-pattern (apply str (lex path - splat "(.*?)" + splat "(.*)" #"^//" "https?://" - word #(str "(" (word-regex %) ")") - literal #(re-escape (.group ^Matcher %))))) + word ^{:cljs + '(fn [w] + (let [w (word-regex w)] + (str "(" + (if (regexp? w) (.-source w) w) + ")")))} + (fn [w] (str "(" (word-regex w) ")")) + literal #(do + ;; (log ".group - " (.group ^Matcher %)) + (re-escape (.group ^Matcher %)))))) (remove nil? (lex path splat :* @@ -141,8 +209,7 @@ literal nil)) (absolute-url? path))))) -(extend-type String +(extend-type java.lang.String Route (route-matches [route request] (route-matches (route-compile route) request))) - diff --git a/src/cljs/clout/core.cljs b/src/cljs/clout/core.cljs new file mode 100644 index 0000000..94322fe --- /dev/null +++ b/src/cljs/clout/core.cljs @@ -0,0 +1,23 @@ +;; This file was generated with lein-dalap from +;; +;; src/clj/clout/core.clj @ Wed Mar 13 05:25:02 PDT 2013 +;; +(ns clout.core "Library for parsing the Rails routes syntax." (:require [goog.string :as gstring] [clojure.string :as string] [goog.Uri :as uri])) +(do (defn re-matcher [re s] (let [re (js* "new RegExp(re.source, 'g')") match (.exec re s) matcher (js-obj)] (set! (.-lookingAt matcher) (fn [] (and match (= (.-index match) 0)))) (set! (.-matches matcher) (fn [] (and match (= s (aget match 0))))) (set! (.-start matcher) (fn [] (and match (.-index match)))) (set! (.-end matcher) (fn [] (if match (.-lastIndex re) (throw (js/Error. "invalid state"))))) (set! (.-groupCount matcher) (fn [] (or (and match (.-length match)) 0))) (set! (.-group matcher) (fn [& [i]] (and match (aget match (or i 0))))) matcher))) +(def re-chars (set "\\.*+|?()[]{}$^")) +(defn- re-escape "Escape all special regex chars in a string." [s] (string/escape s (reduce (fn [m c] (assoc m c (str \\ c))) {} re-chars))) +(defn re-groups* "More consistant re-groups that always returns a vector of groups, even if\n there is only one group." [matcher] (for [i (range (.groupCount matcher))] (do (.group matcher (int (inc i)))))) +(defn path-decode "Decode a path segment in a URI. Defaults to using UTF-8 encoding." ([path] (path-decode path "UTF-8")) ([path encoding] (-> (string/replace path "+" (gstring/urlEncode "+")) (gstring/urlDecode)))) +(defn- assoc-vec "Associate a key with a value. If the key already exists in the map, create a\n vector of values." [m k v] (assoc m k (if-let [cur (m k)] (if (vector? cur) (conj cur v) [cur v]) v))) +(defn- assoc-keys-with-groups "Create a hash-map from a series of regex match groups and a collection of\n keywords." [groups keys] (reduce (fn [m [k v]] (assoc-vec m k v)) {} (map vector keys groups))) +(defn- request-url "Return the complete URL for the request." [request] (str (name (:scheme request)) "://" (get-in request [:headers "host"]) (:uri request))) +(defn- path-info "Return the path info for the request." [request] (or (:path-info request) (:uri request))) +(defprotocol Route (route-matches [route request] "If the route matches the supplied request, the matched keywords are\n returned as a map. Otherwise, nil is returned.")) +(defrecord CompiledRoute [re keys absolute?] Route (route-matches [route request] (let [path-info (if absolute? (request-url request) (path-info request)) matcher (re-matcher re path-info)] (if (.matches matcher) (assoc-keys-with-groups (re-groups* matcher) keys))))) +(defn- lex-1 "Lex one symbol from a string, and return the symbol and trailing source." [src clauses] (some (fn [[re action]] (let [matcher (re-matcher re src)] (if (.lookingAt matcher) (do [(if (fn? action) (action matcher) action) (subs src (.end matcher))])))) (partition 2 clauses))) +(defn- lex "Lex a string into tokens by matching against regexs and evaluating\n the matching associated function." [src & clauses] (loop [results [] src src clauses clauses] (if-let [[result src] (lex-1 src clauses)] (let [results (conj results result)] (if (= src "") results (recur results src clauses)))))) +(defn- absolute-url? "True if the path contains an absolute or scheme-relative URL." [path] (boolean (re-matches #"(https?:)?//.*" path))) +(def -word-regexp #":([a-zA-Z_][\w\-]*)") +(def -literal-regexp #"(:[^a-zA-Z_*]|[^:*])+") +(defn route-compile "Compile a path string using the routes syntax into a uri-matcher struct." ([path] (route-compile path {})) ([path regexs] (let [splat #"\*" word -word-regexp literal -literal-regexp word-group (fn* [p1__1197#] (do (keyword (.group p1__1197# 1)))) word-regex (fn* [p1__1198#] (regexs (word-group p1__1198#) "[^/,;?]+"))] (CompiledRoute. (re-pattern (apply str (lex path splat "(.*)" #"^//" "https?://" word (fn [w] (let [w (word-regex w)] (str "(" (if (regexp? w) (.-source w) w) ")"))) literal (fn* [p1__1199#] (do (re-escape (.group p1__1199#))))))) (remove nil? (lex path splat :* word word-group literal nil)) (absolute-url? path))))) +(extend-type string Route (route-matches [route request] (route-matches (route-compile route) request))) \ No newline at end of file diff --git a/test/clj/clout/test/core.clj b/test/clj/clout/test/core.clj new file mode 100644 index 0000000..76566fd --- /dev/null +++ b/test/clj/clout/test/core.clj @@ -0,0 +1,147 @@ +^{:cljs + '(ns clout.test.core + (:require [clout.core :refer [route-matches route-compile]] + [goog.Uri :as uri]) + (:require-macros [buster-cljs.macros + :refer [initialize-buster deftest it is are]]))} +(ns clout.test.core + (:require [buster-cljs.clojure :refer [deftest describe it is are]] + [ring.mock.request :refer [request]] + [clout.core :refer [route-matches route-compile]])) + +#_(:cljs + (initialize-buster) + + (defn request [method uri] + (let [uri (goog.Uri. uri)] + {:uri (.getPath uri) + :scheme (.getScheme uri) + :headers + {"host" (str (.getDomain uri) + (when (.hasPort uri) + (str + ":" + (.getPort uri)))) + } + :request-method method}))) + +(deftest fixed-path + (it "" + (are [path] (route-matches path (request :get path)) + "/" + "/foo" + "/foo/bar" + "/foo/bar.html" + ))) + +(deftest keyword-paths + (it "" + (are [path uri params] (= (route-matches path (request :get uri)) params) + "/:x" "/foo" {:x "foo"} + "/foo/:x" "/foo/bar" {:x "bar"} + "/a/b/:c" "/a/b/c" {:c "c"} + "/:a/b/:c" "/a/b/c" {:a "a", :c "c"} + ))) + +(deftest keywords-match-extensions + (it "" + (are [path uri params] (= (route-matches path (request :get uri)) params) + "/foo.:ext" "/foo.txt" {:ext "txt"} + "/:x.:y" "/foo.txt" {:x "foo", :y "txt"}))) + +(deftest hyphen-keywords + (it "" + (are [path uri params] (= (route-matches path (request :get uri)) params) + "/:foo-bar" "/baz" {:foo-bar "baz"} + "/:foo-" "/baz" {:foo- "baz"}))) + +(deftest underscore-keywords + (it "" + (are [path uri params] (= (route-matches path (request :get uri)) params) + "/:foo_bar" "/baz" {:foo_bar "baz"} + "/:_foo" "/baz" {:_foo "baz"}))) + +(deftest urlencoded-keywords + (it "" + (are [path uri params] (= (route-matches path (request :get uri)) params) + "/:x" "/foo%20bar" {:x "foo bar"} + "/:x" "/foo+bar" {:x "foo+bar"} + "/:x" "/foo%5Cbar" {:x "foo\\bar"}))) + +(deftest same-keyword-many-times + (it "" + (are [path uri params] (= (route-matches path (request :get uri)) params) + "/:x/:x/:x" "/a/b/c" {:x ["a" "b" "c"]} + "/:x/b/:x" "/a/b/c" {:x ["a" "c"]}))) + +;; clojure supported only +^:clj +(deftest non-ascii-keywords + (it "" + (are [path uri params] (= (route-matches path (request :get uri)) params) + "/:äñßOÔ" "/abc" {:äñßOÔ "abc"} + "/:ÁäñßOÔ" "/abc" {:ÁäñßOÔ "abc"} + "/:ä/:ش" "/foo/bar" {:ä "foo" :ش "bar"} + "/:ä/:ä" "/foo/bar" {:ä ["foo" "bar"]} + "/:Ä-ü" "/baz" {:Ä-ü "baz"} + "/:Ä_ü" "/baz" {:Ä_ü "baz"}))) + +(deftest utf8-routes + (it "" + (is (= (route-matches "/:x" (request :get "/gro%C3%9Fp%C3%B6sna")) + {:x "großpösna"})))) + +(deftest compiled-routes + (it "" + (is (= (route-matches (route-compile "/foo/:id") + (request :get "/foo/bar")) + {:id "bar"})))) + +(deftest url-paths + (it "" + (is (route-matches + "http://localhost/" + {:scheme :http + :headers {"host" "localhost"} + :uri "/"})) + (is (route-matches + "//localhost/" + {:scheme :http + :headers {"host" "localhost"} + :uri "/"})) + (is (route-matches + "//localhost/" + {:scheme :https + :headers {"host" "localhost"} + :uri "/"})))) + +(deftest unmatched-paths + (it "" + (is (nil? (route-matches "/foo" (request :get "/bar")))))) + +(deftest path-info-matches + (it "" + (is (route-matches "/bar" (-> (request :get "/foo/bar") + (assoc :path-info "/bar")))))) + +(deftest url-port-paths + (it "" + (let [req (request :get "http://localhost:8080/")] + (is (route-matches "http://localhost:8080/" req)) + (is (not (route-matches "http://localhost:7070/" req)))))) + +(deftest wildcard-paths + (it "" + (are [path uri params] (= (route-matches path (request :get uri)) params) + "/*" "/foo" {:* "foo"} + "/*" "/foo.txt" {:* "foo.txt"} + "/*" "/foo/bar" {:* "foo/bar"} + "/foo/*" "/foo/bar/baz" {:* "bar/baz"} + "/a/*/d" "/a/b/c/d" {:* "b/c"}))) + +(deftest custom-matches + (it "" + (let [route (route-compile "/foo/:bar" {:bar #"\d+"})] + (is (not (route-matches route (request :get "/foo/bar")))) + (is (not (route-matches route (request :get "/foo/1x")))) + (is (route-matches route (request :get "/foo/10")))))) diff --git a/test/cljs/clout/test/core.cljs b/test/cljs/clout/test/core.cljs new file mode 100644 index 0000000..cbe51c6 --- /dev/null +++ b/test/cljs/clout/test/core.cljs @@ -0,0 +1,21 @@ +;; This file was generated with lein-dalap from +;; +;; test/clj/clout/test/core.clj @ Wed Dec 05 18:41:17 UTC 2012 +;; +(ns clout.test.core (:require [clout.core :refer [route-matches route-compile]] [goog.Uri :as uri]) (:require-macros [buster-cljs.macros :refer [initialize-buster deftest it is are]])) +(do (initialize-buster) (defn request [method uri] (let [uri (goog.Uri. uri)] {:uri (.getPath uri), :scheme (.getScheme uri), :headers {"host" (str (.getDomain uri) (when (.hasPort uri) (str ":" (.getPort uri))))}, :request-method method}))) +(deftest fixed-path (it "" (are [path] (route-matches path (request :get path)) "/" "/foo" "/foo/bar" "/foo/bar.html"))) +(deftest keyword-paths (it "" (are [path uri params] (= (route-matches path (request :get uri)) params) "/:x" "/foo" {:x "foo"} "/foo/:x" "/foo/bar" {:x "bar"} "/a/b/:c" "/a/b/c" {:c "c"} "/:a/b/:c" "/a/b/c" {:a "a", :c "c"}))) +(deftest keywords-match-extensions (it "" (are [path uri params] (= (route-matches path (request :get uri)) params) "/foo.:ext" "/foo.txt" {:ext "txt"} "/:x.:y" "/foo.txt" {:x "foo", :y "txt"}))) +(deftest hyphen-keywords (it "" (are [path uri params] (= (route-matches path (request :get uri)) params) "/:foo-bar" "/baz" {:foo-bar "baz"} "/:foo-" "/baz" {:foo- "baz"}))) +(deftest underscore-keywords (it "" (are [path uri params] (= (route-matches path (request :get uri)) params) "/:foo_bar" "/baz" {:foo_bar "baz"} "/:_foo" "/baz" {:_foo "baz"}))) +(deftest urlencoded-keywords (it "" (are [path uri params] (= (route-matches path (request :get uri)) params) "/:x" "/foo%20bar" {:x "foo bar"} "/:x" "/foo+bar" {:x "foo+bar"} "/:x" "/foo%5Cbar" {:x "foo\\bar"}))) +(deftest same-keyword-many-times (it "" (are [path uri params] (= (route-matches path (request :get uri)) params) "/:x/:x/:x" "/a/b/c" {:x ["a" "b" "c"]} "/:x/b/:x" "/a/b/c" {:x ["a" "c"]}))) +(deftest utf8-routes (it "" (is (= (route-matches "/:x" (request :get "/gro%C3%9Fp%C3%B6sna")) {:x "großpösna"})))) +(deftest compiled-routes (it "" (is (= (route-matches (route-compile "/foo/:id") (request :get "/foo/bar")) {:id "bar"})))) +(deftest url-paths (it "" (is (route-matches "http://localhost/" {:scheme :http, :headers {"host" "localhost"}, :uri "/"})) (is (route-matches "//localhost/" {:scheme :http, :headers {"host" "localhost"}, :uri "/"})) (is (route-matches "//localhost/" {:scheme :https, :headers {"host" "localhost"}, :uri "/"})))) +(deftest unmatched-paths (it "" (is (nil? (route-matches "/foo" (request :get "/bar")))))) +(deftest path-info-matches (it "" (is (route-matches "/bar" (-> (request :get "/foo/bar") (assoc :path-info "/bar")))))) +(deftest url-port-paths (it "" (let [req (request :get "http://localhost:8080/")] (is (route-matches "http://localhost:8080/" req)) (is (not (route-matches "http://localhost:7070/" req)))))) +(deftest wildcard-paths (it "" (are [path uri params] (= (route-matches path (request :get uri)) params) "/*" "/foo" {:* "foo"} "/*" "/foo.txt" {:* "foo.txt"} "/*" "/foo/bar" {:* "foo/bar"} "/foo/*" "/foo/bar/baz" {:* "bar/baz"} "/a/*/d" "/a/b/c/d" {:* "b/c"}))) +(deftest custom-matches (it "" (let [route (route-compile "/foo/:bar" {:bar #"\d+"})] (is (not (route-matches route (request :get "/foo/bar")))) (is (not (route-matches route (request :get "/foo/1x")))) (is (route-matches route (request :get "/foo/10")))))) \ No newline at end of file diff --git a/test/clout/test/core.clj b/test/clout/test/core.clj deleted file mode 100644 index feaf454..0000000 --- a/test/clout/test/core.clj +++ /dev/null @@ -1,105 +0,0 @@ -(ns clout.test.core - (:use clojure.test - ring.mock.request - clout.core)) - -(deftest fixed-path - (are [path] (route-matches path (request :get path)) - "/" - "/foo" - "/foo/bar" - "/foo/bar.html")) - -(deftest keyword-paths - (are [path uri params] (= (route-matches path (request :get uri)) params) - "/:x" "/foo" {:x "foo"} - "/foo/:x" "/foo/bar" {:x "bar"} - "/a/b/:c" "/a/b/c" {:c "c"} - "/:a/b/:c" "/a/b/c" {:a "a", :c "c"})) - -(deftest keywords-match-extensions - (are [path uri params] (= (route-matches path (request :get uri)) params) - "/foo.:ext" "/foo.txt" {:ext "txt"} - "/:x.:y" "/foo.txt" {:x "foo", :y "txt"})) - -(deftest hyphen-keywords - (are [path uri params] (= (route-matches path (request :get uri)) params) - "/:foo-bar" "/baz" {:foo-bar "baz"} - "/:foo-" "/baz" {:foo- "baz"})) - -(deftest underscore-keywords - (are [path uri params] (= (route-matches path (request :get uri)) params) - "/:foo_bar" "/baz" {:foo_bar "baz"} - "/:_foo" "/baz" {:_foo "baz"})) - -(deftest urlencoded-keywords - (are [path uri params] (= (route-matches path (request :get uri)) params) - "/:x" "/foo%20bar" {:x "foo bar"} - "/:x" "/foo+bar" {:x "foo+bar"} - "/:x" "/foo%5Cbar" {:x "foo\\bar"})) - -(deftest same-keyword-many-times - (are [path uri params] (= (route-matches path (request :get uri)) params) - "/:x/:x/:x" "/a/b/c" {:x ["a" "b" "c"]} - "/:x/b/:x" "/a/b/c" {:x ["a" "c"]})) - -(deftest non-ascii-keywords - (are [path uri params] (= (route-matches path (request :get uri)) params) - "/:äñßOÔ" "/abc" {:äñßOÔ "abc"} - "/:ÁäñßOÔ" "/abc" {:ÁäñßOÔ "abc"} - "/:ä/:ش" "/foo/bar" {:ä "foo" :ش "bar"} - "/:ä/:ä" "/foo/bar" {:ä ["foo" "bar"]} - "/:Ä-ü" "/baz" {:Ä-ü "baz"} - "/:Ä_ü" "/baz" {:Ä_ü "baz"})) - -(deftest utf8-routes - (is (= (route-matches "/:x" (request :get "/gro%C3%9Fp%C3%B6sna")) - {:x "großpösna"}))) - -(deftest wildcard-paths - (are [path uri params] (= (route-matches path (request :get uri)) params) - "/*" "/foo" {:* "foo"} - "/*" "/foo.txt" {:* "foo.txt"} - "/*" "/foo/bar" {:* "foo/bar"} - "/foo/*" "/foo/bar/baz" {:* "bar/baz"} - "/a/*/d" "/a/b/c/d" {:* "b/c"})) - -(deftest compiled-routes - (is (= (route-matches (route-compile "/foo/:id") - (request :get "/foo/bar")) - {:id "bar"}))) - -(deftest url-paths - (is (route-matches - "http://localhost/" - {:scheme :http - :headers {"host" "localhost"} - :uri "/"})) - (is (route-matches - "//localhost/" - {:scheme :http - :headers {"host" "localhost"} - :uri "/"})) - (is (route-matches - "//localhost/" - {:scheme :https - :headers {"host" "localhost"} - :uri "/"}))) - -(deftest url-port-paths - (let [req (request :get "http://localhost:8080/")] - (is (route-matches "http://localhost:8080/" req)) - (is (not (route-matches "http://localhost:7070/" req))))) - -(deftest unmatched-paths - (is (nil? (route-matches "/foo" (request :get "/bar"))))) - -(deftest path-info-matches - (is (route-matches "/bar" (-> (request :get "/foo/bar") - (assoc :path-info "/bar"))))) - -(deftest custom-matches - (let [route (route-compile "/foo/:bar" {:bar #"\d+"})] - (is (not (route-matches route (request :get "/foo/bar")))) - (is (not (route-matches route (request :get "/foo/1x")))) - (is (route-matches route (request :get "/foo/10")))))