(ns reverse-routing.core (:require clout.core compojure.core [clojure.string :as string])) (def ^:private ^:dynamic *reverse-routes* nil) (def ^:private ^:dynamic *root* nil) (defn wrap-reverse-routing [handler & {:keys [root] :or {:root ""}}] (fn [request] (binding [*reverse-routes* (->> handler meta ::routes) *root* root] (handler request)))) (defn ^:private routing [request & handlers] (some #(% request) handlers)) (defmacro routes [& handlers] (let [routes-map (->> handlers (map (comp ::routes meta macroexpand)) (into {}))] (vary-meta `(vary-meta (fn [request#] (#'routing request# ~@handlers)) assoc ::routes ~routes-map) assoc ::routes routes-map))) (defmacro defroutes [name & handlers] `(def ~name (routes ~@handlers))) (defmacro context [path args & routes] (let [string-path (if (vector? path) (first path) path) routes-map (let [path-keys (:keys (clout.core/route-compile string-path)) keylen (count path-keys)] (->> (for [route (map (comp ::routes meta macroexpand) routes) [[path num] {:keys [uri args type]}] route] [[path (+ num keylen)] {:uri (str string-path uri) :args (vec (concat path-keys args)) :type type}]) (into {})))] (vary-meta `(vary-meta (compojure.core/context ~path ~args ~@routes) assoc ::routes ~routes-map) assoc ::routes routes-map))) (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 {route-id {:uri string-path :type (keyword (string/lower-case (name type))) :args (vec route-args)}}] (vary-meta `(vary-meta ~route assoc ::routes ~routes-map) assoc ::routes routes-map))) (defn url-for [route & arg-values] (let [{:keys [uri type args]} (get *reverse-routes* [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)))))