From 17da9b9ce4a3e5354a6a8689c1e2ee4466b76e39 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Thu, 13 Feb 2014 23:36:36 +1100 Subject: Rename project to 'abra', add 'add-middleware' function to maintain reverse-routing through middleware stack --- project.clj | 6 +- src/abra/core.clj | 94 +++++++++++++++++++++ src/reverse_routing/core.clj | 85 ------------------- test/abra/core_test.clj | 168 +++++++++++++++++++++++++++++++++++++ test/reverse_routing/core_test.clj | 168 ------------------------------------- 5 files changed, 265 insertions(+), 256 deletions(-) create mode 100644 src/abra/core.clj delete mode 100644 src/reverse_routing/core.clj create mode 100644 test/abra/core_test.clj delete mode 100644 test/reverse_routing/core_test.clj diff --git a/project.clj b/project.clj index 431b954..4c96e79 100644 --- a/project.clj +++ b/project.clj @@ -1,6 +1,6 @@ -(defproject reverse-routing "0.1.0-SNAPSHOT" - :description "FIXME: write description" - :url "http://example.com/FIXME" +(defproject abra "0.1.0-SNAPSHOT" + :description "A small library for reverse-routing in compojure applications." + :url "http://bitbucket.org/czan/abra" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.5.1"] diff --git a/src/abra/core.clj b/src/abra/core.clj new file mode 100644 index 0000000..3d373eb --- /dev/null +++ b/src/abra/core.clj @@ -0,0 +1,94 @@ +(ns abra.core + (:require clout.core + compojure.core + [clojure.string :as string])) + +(def ^:private ^:dynamic *lookup-route* nil) +(def ^:private ^:dynamic *root* nil) + +(defn ^:private deref-if-var [arg] + (if (var? arg) + (deref arg) + arg)) + +(defn wrap-reverse-routing [handler & {:keys [root] :or {:root ""}}] + (fn [request] + (binding [*lookup-route* (->> handler deref-if-var meta ::lookup) + *root* root] + (handler request)))) + +(defn ^:private lookup-route [route & handlers] + (->> handlers + (map (comp ::lookup meta deref-if-var)) + (some #(if % (% route))))) + +(defn ^:private routing [request & handlers] + (some #(% request) handlers)) + +(defn routes [& handlers] + (vary-meta #(apply routing % handlers) + assoc ::lookup #(apply lookup-route % handlers))) + +(defmacro let-routes [bindings & handlers] + `(let ~bindings (routes ~@handlers))) + +(defmacro when-routes [cond & handlers] + `(if ~cond (routes ~@handlers) (routes))) + +(defmacro defroutes [name & handlers] + `(def ~name (routes ~@handlers))) + +(defmacro context [path args & routes] + (let [string-path (if (vector? path) (first path) path) + path-keys (vec (:keys (clout.core/route-compile string-path))) + keylen (count path-keys) + lookup-fn `(fn [[route-name# args#]] + (if (>= (count args#) ~keylen) + (let [~args args# + r# (try (#'lookup-route [route-name# (vec (drop ~keylen args#))] + ~@routes) + (catch Exception _#)) + {uri# :uri, args# :args} r#] + (if r# + (assoc r# + :uri (str ~string-path uri#) + :args (vec (concat ~path-keys args#)))))))] + `(vary-meta (compojure.core/context ~path ~args + (routes ~@routes)) + assoc ::lookup ~lookup-fn))) + +(defmacro register-route [route-name [type path args & body :as route]] + (let [string-path (if (vector? path) (first path) path) + route-args (:keys (clout.core/route-compile string-path)) + routes-map {:uri string-path + :type (keyword (string/lower-case (name type))) + :args (vec route-args)}] + `(vary-meta ~route + assoc ::lookup (fn [[name# args#]] + (if (and (= name# ~route-name) + (= (count args#) (count ~(vec route-args)))) + ~routes-map))))) + +(defn add-middleware [handler middleware & args] + (let [result (apply middleware handler args)] + (vary-meta result + assoc ::lookup (fn [[name args]] + (let [lookup-fn (-> (deref-if-var handler) + meta + ::lookup)] + (lookup-fn [name args])))))) + +(defmacro with-url-fn [f & body] + `(binding [*lookup-route* (fn [x#] (apply ~f x#)) + *root* ""] + ~@body)) + +(defn url-for [route & arg-values] + (let [spec (*lookup-route* [route arg-values]) + {:keys [uri type args]} spec + root-path *root*] + (if spec + (str root-path + (reduce (fn [string [name val]] + (clojure.string/replace string (str name) (str val))) + uri (map vector args arg-values)))))) diff --git a/src/reverse_routing/core.clj b/src/reverse_routing/core.clj deleted file mode 100644 index edc1f57..0000000 --- a/src/reverse_routing/core.clj +++ /dev/null @@ -1,85 +0,0 @@ -(ns reverse-routing.core - (:require clout.core - compojure.core - [clojure.string :as string])) - -(def ^:private ^:dynamic *lookup-route* nil) -(def ^:private ^:dynamic *root* nil) - -(defn ^:private deref-if-var [arg] - (if (var? arg) - (deref arg) - arg)) - -(defn wrap-reverse-routing [handler & {:keys [root] :or {:root ""}}] - (fn [request] - (binding [*lookup-route* (->> handler deref-if-var meta ::lookup) - *root* root] - (handler request)))) - -(defn ^:private lookup-route [route & handlers] - (->> handlers - (map (comp ::lookup meta deref-if-var)) - (some #(if % (% route))))) - -(defn ^:private routing [request & handlers] - (some #(% request) handlers)) - -(defn routes [& handlers] - (vary-meta #(apply routing % handlers) - assoc ::lookup #(apply lookup-route % handlers))) - -(defmacro let-routes [bindings & handlers] - `(let ~bindings (routes ~@handlers))) - -(defmacro when-routes [cond & handlers] - `(if ~cond (routes ~@handlers) (routes))) - -(defmacro defroutes [name & handlers] - `(def ~name (routes ~@handlers))) - -(defmacro context [path args & routes] - (let [string-path (if (vector? path) (first path) path) - path-keys (vec (:keys (clout.core/route-compile string-path))) - keylen (count path-keys) - lookup-fn `(fn [[route-name# args#]] - (if (>= (count args#) ~keylen) - (let [~args args# - r# (try (#'lookup-route [route-name# (vec (drop ~keylen args#))] - ~@routes) - (catch Exception _#)) - {uri# :uri, args# :args} r#] - (if r# - (assoc r# - :uri (str ~string-path uri#) - :args (vec (concat ~path-keys args#)))))))] - `(vary-meta (compojure.core/context ~path ~args - (routes ~@routes)) - assoc ::lookup ~lookup-fn))) - -(defmacro register-route [route-name [type path args & body :as route]] - (let [string-path (if (vector? path) (first path) path) - route-args (:keys (clout.core/route-compile string-path)) - routes-map {:uri string-path - :type (keyword (string/lower-case (name type))) - :args (vec route-args)}] - `(vary-meta ~route - assoc ::lookup (fn [[name# args#]] - (if (and (= name# ~route-name) - (= (count args#) (count ~(vec route-args)))) - ~routes-map))))) - -(defmacro with-url-fn [f & body] - `(binding [*lookup-route* (fn [x#] (apply ~f x#)) - *root* ""] - ~@body)) - -(defn url-for [route & arg-values] - (let [spec (*lookup-route* [route arg-values]) - {:keys [uri type args]} spec - root-path *root*] - (if spec - (str root-path - (reduce (fn [string [name val]] - (clojure.string/replace string (str name) (str val))) - uri (map vector args arg-values)))))) diff --git a/test/abra/core_test.clj b/test/abra/core_test.clj new file mode 100644 index 0000000..b829cec --- /dev/null +++ b/test/abra/core_test.clj @@ -0,0 +1,168 @@ +(ns abra.core-test + (:use clojure.test + abra.core + [compojure.core :only [GET]])) + +(defn make-request [handler uri] + ;; Technically most of this map is required by the ring spec + ;; I'm fairly sure we could get away with not having most of it + (-> {:request-method :get + :scheme :http + :uri uri + :remote-addr "127.0.0.1" + :server-port 8080 + :server-name "something"} + handler + :body)) + +(deftest test-basic-route + (let [handler (-> (register-route :user + (GET "/succeed" [id] (url-for :user 10))) + wrap-reverse-routing)] + (is (= (make-request handler "/succeed") "/user/10")))) + +(-> (routes + (register-route :user + (GET "/user/:id" [id] (str "user " id))) + (GET "/succeed" [] (url-for :user 10)) + (GET "/fail1" [] (url-for :user)) + (GET "/fail2" [] (url-for :user 10 20))) + wrap-reverse-routing + (make-request "/succeed")) + +(deftest test-basic-route + (let [handler (-> (routes + (register-route :user + (GET "/user/:id" [id] (str "user " id))) + (GET "/succeed" [] (url-for :user 10)) + (GET "/fail1" [] (url-for :user)) + (GET "/fail2" [] (url-for :user 10 20))) + wrap-reverse-routing)] + (is (= (make-request handler "/succeed") "/user/10")) + (is (nil? (make-request handler "/fail1"))) + (is (nil? (make-request handler "/fail2"))))) + +(-> (routes + (register-route :user + (GET "/user/" [id] (str "user list"))) + (context "/user/:id" [] + (register-route :user + (GET "/" [id] (str "user " id))) + (register-route :edit-user + (GET "/edit" [id] (str "edit user " id)))) + (GET "/succeed1" [] (url-for :user)) + (GET "/succeed2" [] (url-for :user 10)) + (GET "/succeed3" [] (url-for :edit-user 10)) + (GET "/fail1" [] (url-for :user 10 20)) + (GET "/fail2" [] (url-for :edit-user)) + (GET "/fail3" [] (url-for :edit-user 10 20))) + wrap-reverse-routing + (make-request "/fail2")) + +(deftest test-context-routes + (let [handler (-> (routes + (register-route :user + (GET "/user/" [id] (str "user list"))) + (context "/user/:id" [] + (register-route :user + (GET "/" [id] (str "user " id))) + (register-route :edit-user + (GET "/edit" [id] (str "edit user " id)))) + (GET "/succeed1" [] (url-for :user)) + (GET "/succeed2" [] (url-for :user 10)) + (GET "/succeed3" [] (url-for :edit-user 10)) + (GET "/fail1" [] (url-for :user 10 20)) + (GET "/fail2" [] (url-for :edit-user)) + (GET "/fail3" [] (url-for :edit-user 10 20))) + wrap-reverse-routing)] + (is (= (make-request handler "/succeed1") "/user/")) + (is (= (make-request handler "/succeed2") "/user/10/")) + (is (= (make-request handler "/succeed3") "/user/10/edit")) + + (is (nil? (make-request handler "/fail1"))) + (is (nil? (make-request handler "/fail2"))) + (is (nil? (make-request handler "/fail3"))))) + +(deftest test-context-in-context-routes + (let [handler (-> (routes + (context "/user" [] + (register-route :user + (GET "/" [id] (str "user list"))) + (context "/:id" [id] + (register-route :user + (GET "/" [] (str "user " id))) + (register-route :edit-user + (GET "/edit" [] (str "edit user " id))))) + (GET "/succeed1" [] (url-for :user)) + (GET "/succeed2" [] (url-for :user 10)) + (GET "/succeed3" [] (url-for :edit-user 10)) + (GET "/fail1" [] (url-for :user 10 20)) + (GET "/fail2" [] (url-for :edit-user)) + (GET "/fail3" [] (url-for :edit-user 10 20))) + wrap-reverse-routing)] + (is (= (make-request handler "/succeed1") "/user/")) + (is (= (make-request handler "/succeed2") "/user/10/")) + (is (= (make-request handler "/succeed3") "/user/10/edit")) + + (is (nil? (make-request handler "/fail1"))) + (is (nil? (make-request handler "/fail2"))) + (is (nil? (make-request handler "/fail3"))))) + + +(deftest test-with-rebinding-vars + (with-local-vars [subroutes (register-route :user + (GET "/user" [] (str "user")))] + (let [handler (-> (routes + subroutes + (GET "/succeed" [] (url-for :user)) + (GET "/fail" [] (url-for :user 10))) + wrap-reverse-routing)] + (is (= (make-request handler "/succeed") "/user")) + (is (nil? (make-request handler "/fail"))) + + (var-set subroutes (register-route :user + (GET "/not-user" [] (str "user")))) + (is (= (make-request handler "/succeed") "/not-user")) + (is (nil? (make-request handler "/fail")))))) + +(deftest top-level-is-a-var + (with-local-vars [bare-handler (routes + (register-route :user + (GET "/user/:id" [id] (str "user " id))) + (GET "/succeed" [] (url-for :user 10)) + (GET "/fail1" [] (url-for :user)) + (GET "/fail2" [] (url-for :user 10 20)))] + (let [handler (wrap-reverse-routing bare-handler)] + (is (= (make-request handler "/succeed") "/user/10")) + (is (nil? (make-request handler "/fail1"))) + (is (nil? (make-request handler "/fail2")))))) + + +(deftest context-requiring-not-nil-value + (let [handler (-> (routes + (context "/:type" [type] + (if (not= type nil) + (register-route :get-object + (GET "/:id" [id] + (str [type id]))))) + (GET "/succeed" [] (url-for :get-object "user" 10)) + (GET "/fail" [] (url-for :get-object nil 10))) + wrap-reverse-routing)] + (is (= (make-request handler "/succeed"), "/user/10")) + (is (nil? (make-request handler "/fail"))))) + +(deftest context-with-limited-options + (let [valid-type? #{"user"} + handler (-> (routes + (context "/:type" [type] + (when-routes (valid-type? type) + (register-route :get-object + (GET "/:id" [id] + (str [type id]))))) + (GET "/succeed" [] (url-for :get-object "user" 10)) + (GET "/fail1" [] (url-for :get-object nil 10)) + (GET "/fail2" [] (url-for :get-object "person" 10))) + wrap-reverse-routing)] + (is (= (make-request handler "/succeed"), "/user/10")) + (is (nil? (make-request handler "/fail1"))) + (is (nil? (make-request handler "/fail2"))))) diff --git a/test/reverse_routing/core_test.clj b/test/reverse_routing/core_test.clj deleted file mode 100644 index fb37c94..0000000 --- a/test/reverse_routing/core_test.clj +++ /dev/null @@ -1,168 +0,0 @@ -(ns reverse-routing.core-test - (:use clojure.test - reverse-routing.core - [compojure.core :only [GET]])) - -(defn make-request [handler uri] - ;; Technically most of this map is required by the ring spec - ;; I'm fairly sure we could get away with not having most of it - (-> {:request-method :get - :scheme :http - :uri uri - :remote-addr "127.0.0.1" - :server-port 8080 - :server-name "something"} - handler - :body)) - -(deftest test-basic-route - (let [handler (-> (register-route :user - (GET "/succeed" [id] (url-for :user 10))) - wrap-reverse-routing)] - (is (= (make-request handler "/succeed") "/user/10")))) - -(-> (routes - (register-route :user - (GET "/user/:id" [id] (str "user " id))) - (GET "/succeed" [] (url-for :user 10)) - (GET "/fail1" [] (url-for :user)) - (GET "/fail2" [] (url-for :user 10 20))) - wrap-reverse-routing - (make-request "/succeed")) - -(deftest test-basic-route - (let [handler (-> (routes - (register-route :user - (GET "/user/:id" [id] (str "user " id))) - (GET "/succeed" [] (url-for :user 10)) - (GET "/fail1" [] (url-for :user)) - (GET "/fail2" [] (url-for :user 10 20))) - wrap-reverse-routing)] - (is (= (make-request handler "/succeed") "/user/10")) - (is (nil? (make-request handler "/fail1"))) - (is (nil? (make-request handler "/fail2"))))) - -(-> (routes - (register-route :user - (GET "/user/" [id] (str "user list"))) - (context "/user/:id" [] - (register-route :user - (GET "/" [id] (str "user " id))) - (register-route :edit-user - (GET "/edit" [id] (str "edit user " id)))) - (GET "/succeed1" [] (url-for :user)) - (GET "/succeed2" [] (url-for :user 10)) - (GET "/succeed3" [] (url-for :edit-user 10)) - (GET "/fail1" [] (url-for :user 10 20)) - (GET "/fail2" [] (url-for :edit-user)) - (GET "/fail3" [] (url-for :edit-user 10 20))) - wrap-reverse-routing - (make-request "/fail2")) - -(deftest test-context-routes - (let [handler (-> (routes - (register-route :user - (GET "/user/" [id] (str "user list"))) - (context "/user/:id" [] - (register-route :user - (GET "/" [id] (str "user " id))) - (register-route :edit-user - (GET "/edit" [id] (str "edit user " id)))) - (GET "/succeed1" [] (url-for :user)) - (GET "/succeed2" [] (url-for :user 10)) - (GET "/succeed3" [] (url-for :edit-user 10)) - (GET "/fail1" [] (url-for :user 10 20)) - (GET "/fail2" [] (url-for :edit-user)) - (GET "/fail3" [] (url-for :edit-user 10 20))) - wrap-reverse-routing)] - (is (= (make-request handler "/succeed1") "/user/")) - (is (= (make-request handler "/succeed2") "/user/10/")) - (is (= (make-request handler "/succeed3") "/user/10/edit")) - - (is (nil? (make-request handler "/fail1"))) - (is (nil? (make-request handler "/fail2"))) - (is (nil? (make-request handler "/fail3"))))) - -(deftest test-context-in-context-routes - (let [handler (-> (routes - (context "/user" [] - (register-route :user - (GET "/" [id] (str "user list"))) - (context "/:id" [id] - (register-route :user - (GET "/" [] (str "user " id))) - (register-route :edit-user - (GET "/edit" [] (str "edit user " id))))) - (GET "/succeed1" [] (url-for :user)) - (GET "/succeed2" [] (url-for :user 10)) - (GET "/succeed3" [] (url-for :edit-user 10)) - (GET "/fail1" [] (url-for :user 10 20)) - (GET "/fail2" [] (url-for :edit-user)) - (GET "/fail3" [] (url-for :edit-user 10 20))) - wrap-reverse-routing)] - (is (= (make-request handler "/succeed1") "/user/")) - (is (= (make-request handler "/succeed2") "/user/10/")) - (is (= (make-request handler "/succeed3") "/user/10/edit")) - - (is (nil? (make-request handler "/fail1"))) - (is (nil? (make-request handler "/fail2"))) - (is (nil? (make-request handler "/fail3"))))) - - -(deftest test-with-rebinding-vars - (with-local-vars [subroutes (register-route :user - (GET "/user" [] (str "user")))] - (let [handler (-> (routes - subroutes - (GET "/succeed" [] (url-for :user)) - (GET "/fail" [] (url-for :user 10))) - wrap-reverse-routing)] - (is (= (make-request handler "/succeed") "/user")) - (is (nil? (make-request handler "/fail"))) - - (var-set subroutes (register-route :user - (GET "/not-user" [] (str "user")))) - (is (= (make-request handler "/succeed") "/not-user")) - (is (nil? (make-request handler "/fail")))))) - -(deftest top-level-is-a-var - (with-local-vars [bare-handler (routes - (register-route :user - (GET "/user/:id" [id] (str "user " id))) - (GET "/succeed" [] (url-for :user 10)) - (GET "/fail1" [] (url-for :user)) - (GET "/fail2" [] (url-for :user 10 20)))] - (let [handler (wrap-reverse-routing bare-handler)] - (is (= (make-request handler "/succeed") "/user/10")) - (is (nil? (make-request handler "/fail1"))) - (is (nil? (make-request handler "/fail2")))))) - - -(deftest context-requiring-not-nil-value - (let [handler (-> (routes - (context "/:type" [type] - (if (not= type nil) - (register-route :get-object - (GET "/:id" [id] - (str [type id]))))) - (GET "/succeed" [] (url-for :get-object "user" 10)) - (GET "/fail" [] (url-for :get-object nil 10))) - wrap-reverse-routing)] - (is (= (make-request handler "/succeed"), "/user/10")) - (is (nil? (make-request handler "/fail"))))) - -(deftest context-with-limited-options - (let [valid-type? #{"user"} - handler (-> (routes - (context "/:type" [type] - (when-routes (valid-type? type) - (register-route :get-object - (GET "/:id" [id] - (str [type id]))))) - (GET "/succeed" [] (url-for :get-object "user" 10)) - (GET "/fail1" [] (url-for :get-object nil 10)) - (GET "/fail2" [] (url-for :get-object "person" 10))) - wrap-reverse-routing)] - (is (= (make-request handler "/succeed"), "/user/10")) - (is (nil? (make-request handler "/fail1"))) - (is (nil? (make-request handler "/fail2"))))) -- cgit v1.2.3