(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))))))