Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Refactored Clout to use protocols and types

  • Loading branch information...
commit 45af50aca00e3c52298bf748cb5113c2213cb375 1 parent d511730
James Reeves weavejester authored
Showing with 89 additions and 101 deletions.
  1. +89 −101 src/clout/core.clj
190 src/clout/core.clj
View
@@ -8,25 +8,21 @@
(ns clout.core
"Library for parsing the Rails routes syntax."
+ (:require [clojure.string :as string])
(:import java.util.Map
java.net.URLDecoder))
;; Regular expression utilties
-(defn- escape
- "Returns a string with each occurance of a character in
- chars escaped."
- [chars #^String string]
- (let [charset (set chars)]
- (apply str
- (mapcat
- #(if (contains? charset %) [\\ %] [%])
- string))))
+(def ^{:private true} re-chars
+ (set "\\.*+|?()[]{}$^"))
(defn- re-escape
- "Escape all special regex chars in string."
- [string]
- (escape "\\.*+|?()[]{}$^" string))
+ "Escape all special regex chars in a string."
+ [s]
+ (string/escape
+ s
+ #(if (re-chars %) (str \\ %))))
(defn re-groups*
"More consistant re-groups that always returns a vector of groups, even if
@@ -61,52 +57,21 @@
results
(recur results src clauses))))))
-;; Compile route syntax
-
-(defstruct route
- :absolute?
- :regex
- :keys)
-
-(defn- make-route
- "Construct a route structure."
- [absolute? re keys]
- (with-meta
- (struct route absolute? re keys)
- {:type ::compiled-route}))
+;; Route matching
-(defn- absolute-url?
- "True if the path contains an absolute URL."
- [path]
- (boolean (re-matches #"https?://.*" path)))
-
-(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 #(.group % 1)
- word-regex #(regexs (keyword (word-group %))
- "[^/.,;?]+")]
- (make-route
- (absolute-url? path)
- (re-pattern
- (apply str
- (lex path
- splat "(.*?)"
- word #(str "(" (word-regex %) ")")
- literal #(re-escape (.group %)))))
- (vec
- (remove nil?
- (lex path
- splat "*"
- word word-group
- literal nil)))))))
+(defn- urldecode
+ "Encode a urlencoded string using the default encoding."
+ [string]
+ (URLDecoder/decode string))
-;; Parse URI with compiled route
+(defn request-url
+ "Return the complete URL for the request."
+ [request]
+ (str
+ (name (:scheme request))
+ "://"
+ (get-in request [:headers "host"])
+ (:uri request)))
(defn- assoc-vec
"Associate a key with a value. If the key already exists in the map, create a
@@ -128,50 +93,73 @@
{}
(map vector keys groups)))
-(defn- urldecode
- "Encode a urlencoded string using the default encoding."
- [string]
- (URLDecoder/decode string))
+(defprotocol Request
+ (uri [request absolute?]
+ "Return the URI of the request. If absolute? is true, the URI returned
+ will be an absolte URL."))
+
+(extend-protocol Request
+ nil
+ (uri [_ _] (uri "/" false))
+ String
+ (uri [path _] path)
+ Map
+ (uri [request absolute?]
+ (if absolute?
+ (request-url request)
+ (:uri request))))
+
+(defprotocol Route
+ (route-matches [route request]
+ "If the route matches the supplied request, the matched keywords are
+ returned as a map. Otherwise, nil is returned.
+ e.g. (route-matches \"/product/:id\" \"/product/10\")
+ -> {:id 10}"))
+
+(declare route-compile)
+
+(extend-type String
+ Route
+ (route-matches [route request]
+ (route-matches (route-compile route) request)))
+
+(defrecord CompiledRoute [re keys absolute?]
+ Route
+ (route-matches [route request]
+ (let [matcher (re-matcher re (uri request absolute?))]
+ (if (.matches matcher)
+ (assoc-keys-with-groups
+ (map urldecode (re-groups* matcher))
+ keys)))))
+
+;; Compile routes
-(defn request-url
- "Return the complete URL for the request."
- [request]
- (str
- (name (:scheme request))
- "://"
- (get-in request [:headers "host"])
- (:uri request)))
+(defn- absolute-url?
+ "True if the path contains an absolute URL."
+ [path]
+ (boolean (re-matches #"https?://.*" path)))
-(derive Map ::request)
-(derive String ::request)
-
-(derive String ::route)
-(derive ::compiled-route ::route)
-
-(defmulti route-matches
- "Match a route against an object. Returns the matched keywords of the route.
- e.g. (route-matches \"/product/:id\" \"/product/10\")
- -> {:id 10}"
- (fn [route x] [(type route) (type x)]))
-
-(defmethod route-matches [::route nil]
- [route _]
- (route-matches route "/"))
-
-(defmethod route-matches [String ::request]
- [route request]
- (route-matches (route-compile route) request))
-
-(defmethod route-matches [::compiled-route Map]
- [route request]
- (route-matches route (if (:absolute? route)
- (request-url request)
- (:uri request))))
-
-(defmethod route-matches [::compiled-route String]
- [route uri]
- (let [matcher (re-matcher (route :regex) (or uri "/"))]
- (if (.matches matcher)
- (assoc-keys-with-groups
- (map urldecode (re-groups* matcher))
- (route :keys)))))
+(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 #(.group % 1)
+ word-regex #(regexs (keyword (word-group %))
+ "[^/.,;?]+")]
+ (CompiledRoute.
+ (re-pattern
+ (apply str
+ (lex path
+ splat "(.*?)"
+ word #(str "(" (word-regex %) ")")
+ literal #(re-escape (.group %)))))
+ (remove nil?
+ (lex path
+ splat "*"
+ word word-group
+ literal nil))
+ (absolute-url? path)))))
Please sign in to comment.
Something went wrong with that request. Please try again.