(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 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 deref-if-var [arg] (if (var? arg) (deref arg) arg)) (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] `(when ~cond (routes ~@handlers))) (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-count#]] (let [~args (repeat nil) ;; hacky, but necessary - provide nil values for args r# (#'lookup-route [route-name# (- args-count# ~keylen)] ~@routes) {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)) route-id [route-name (count route-args)] routes-map {:uri string-path :type (keyword (string/lower-case (name type))) :args (vec route-args)}] `(vary-meta ~route assoc ::lookup (fn [signature#] (if (= signature# ~route-id) ~routes-map))))) (defn url-for [route & arg-values] (let [{:keys [uri type args]} (*lookup-route* [route (count arg-values)]) root-path *root*] (assert uri) (str root-path (reduce (fn [string [name val]] (clojure.string/replace string (str name) (str val))) uri (map vector args arg-values)))))