├── .gitignore ├── .travis.yml ├── LICENSE.html ├── README.md ├── project.clj ├── src └── clout │ ├── core.cljc │ └── internal │ └── regex.cljs └── test └── clout ├── core_test.cljc └── test_runner.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | script: 3 | - jdk_switcher use openjdk6 4 | - lein with-profile dev:dev,1.7:dev,1.8:dev,1.9 test 5 | - lein with-profile 1.7:1.8 doo node once 6 | - jdk_switcher use openjdk7 7 | - lein with-profile dev:dev,1.7:dev,1.8:dev,1.9 test 8 | - lein with-profile 1.7:1.8 doo node once 9 | - jdk_switcher use openjdk8 10 | - lein with-profile dev:dev,1.7:dev,1.8:dev,1.9 test 11 | - lein with-profile 1.7:1.8:1.9 doo node once 12 | -------------------------------------------------------------------------------- /LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 |Eclipse Public License - v 1.0
31 | 32 |THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.
36 | 37 |1. DEFINITIONS
38 | 39 |"Contribution" means:
40 | 41 |a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and
43 |b) in the case of each subsequent Contributor:
44 |i) changes to the Program, and
45 |ii) additions to the Program;
46 |where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.
54 | 55 |"Contributor" means any person or entity that distributes 56 | the Program.
57 | 58 |"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.
61 | 62 |"Program" means the Contributions distributed in accordance 63 | with this Agreement.
64 | 65 |"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.
67 | 68 |2. GRANT OF RIGHTS
69 | 70 |a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.
76 | 77 |b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.
88 | 89 |c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.
101 | 102 |d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.
105 | 106 |3. REQUIREMENTS
107 | 108 |A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:
110 | 111 |a) it complies with the terms and conditions of this 112 | Agreement; and
113 | 114 |b) its license agreement:
115 | 116 |i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;
120 | 121 |ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;
124 | 125 |iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and
128 | 129 |iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.
133 | 134 |When the Program is made available in source code form:
135 | 136 |a) it must be made available under this Agreement; and
137 | 138 |b) a copy of this Agreement must be included with each 139 | copy of the Program.
140 | 141 |Contributors may not remove or alter any copyright notices contained 142 | within the Program.
143 | 144 |Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.
147 | 148 |4. COMMERCIAL DISTRIBUTION
149 | 150 |Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.
172 | 173 |For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.
183 | 184 |5. NO WARRANTY
185 | 186 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.
197 | 198 |6. DISCLAIMER OF LIABILITY
199 | 200 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
208 | 209 |7. GENERAL
210 | 211 |If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.
216 | 217 |If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.
223 | 224 |All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.
232 | 233 |Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.
252 | 253 |This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.
258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clout 2 | 3 | [](https://travis-ci.org/weavejester/clout) 4 | 5 | Clout is a library for matching [Ring][1] HTTP requests. It uses the same 6 | routing syntax as used by popular Ruby web frameworks like Ruby on Rails and 7 | Sinatra. 8 | 9 | [1]: https://github.com/ring-clojure/ring 10 | 11 | ## Installation 12 | 13 | Add the following to your project.clj dependencies: 14 | 15 | ```clj 16 | [clout "2.2.1"] 17 | ``` 18 | 19 | ## Usage 20 | 21 | Require Clout in the normal way: 22 | 23 | ```clj 24 | (require '[clout.core :as clout]) 25 | ``` 26 | 27 | These following examples also make use of the [Ring-Mock][2] library 28 | to generate Ring request maps: 29 | 30 | [2]: https://github.com/ring-clojure/ring-mock 31 | 32 | ```clj 33 | (require '[ring.mock.request :as mock]) 34 | ``` 35 | 36 | Routes can match by keyword: 37 | 38 | ```clj 39 | (clout/route-matches 40 | "/article/:title" 41 | (mock/request :get "/article/clojure")) 42 | 43 | => {:title "clojure"} 44 | ``` 45 | 46 | Or with wildcards: 47 | 48 | ```clj 49 | (clout/route-matches 50 | "/public/*" 51 | (mock/request :get "/public/style/screen.css")) 52 | 53 | => {:* "style/screen.css"} 54 | ``` 55 | 56 | Clout can also match absolute routes: 57 | 58 | ```clj 59 | (clout/route-matches 60 | "http://subdomain.example.com/" 61 | (mock/request :get "http://subdomain.example.com/")) 62 | 63 | => {} 64 | ``` 65 | And scheme-relative routes: 66 | 67 | ```clj 68 | (clout/route-matches 69 | "//subdomain.example.com/" 70 | (mock/request :get "http://subdomain.example.com/")) 71 | 72 | => {} 73 | 74 | (clout/route-matches 75 | "//subdomain.example.com/" 76 | (mock/request :get "https://subdomain.example.com/")) 77 | 78 | => {} 79 | ``` 80 | 81 | Clout supports both keywords and wildcards. Keywords (like ":title") will 82 | match any character but the following: `/ . , ; ?`. Wildcards (*) will match 83 | anything. 84 | 85 | If a route does not match, nil is returned: 86 | 87 | ```clj 88 | (clout/route-matches "/products" (mock/request :get "/articles")) 89 | 90 | => nil 91 | ``` 92 | 93 | For additional performance, you can choose to pre-compile a route: 94 | 95 | ```clj 96 | (def user-route 97 | (clout/route-compile "/user/:id")) 98 | 99 | (clout/route-matches user-route (mock/request :get "/user/10")) 100 | 101 | => {:id "10"} 102 | ``` 103 | 104 | When compiling a route, you can specify a map of regular expressions to use 105 | for different keywords. This allows more specific routing: 106 | 107 | ```clj 108 | (def user-route 109 | (clout/route-compile "/user/:id" {:id #"\d+"})) 110 | 111 | (clout/route-matches user-route (mock/request :get "/user/10")) 112 | 113 | => {:user "10"} 114 | 115 | (clout/route-matches user-route (mock/request :get "/user/jsmith")) 116 | 117 | => nil 118 | ``` 119 | 120 | You can also specify regular expressions inline in braces after the 121 | keyword: 122 | 123 | ```clj 124 | (def user-route 125 | (clout/route-compile "/user/:id{\\d+}")) 126 | ``` 127 | 128 | Note that regular expression escape sequences (like `\d`) need to be 129 | double-escaped when placed inline in a string. 130 | 131 | ## License 132 | 133 | Copyright © 2018 James Reeves 134 | 135 | Distributed under the Eclipse Public License either version 1.0 or (at 136 | your option) any later version. 137 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clout "2.2.1" 2 | :description "A HTTP route matching library" 3 | :url "https://github.com/weavejester/clout" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.7.0"] 7 | [org.clojure/clojurescript "1.7.228" :scope "provided"] 8 | [instaparse "1.4.8" :exclusions [org.clojure/clojure]]] 9 | :plugins [[lein-doo "0.1.7"] 10 | [lein-cljsbuild "1.1.4"]] 11 | :cljsbuild {:builds 12 | {:test 13 | {:source-paths ["src" "test"] 14 | :compiler {:main clout.test-runner 15 | :output-dir "target/out" 16 | :output-to "target/test/advanced.js" 17 | :target :nodejs 18 | :optimizations :advanced}}}} 19 | :doo {:build "test"} 20 | :profiles 21 | {:dev {:jvm-opts ^:replace [] 22 | :dependencies [[ring/ring-mock "0.2.0"] 23 | [criterium "0.4.2"]]} 24 | :1.7 {:dependencies [[org.clojure/clojure "1.7.0"] 25 | [org.clojure/clojurescript "1.7.228"]]} 26 | :1.8 {:dependencies [[org.clojure/clojure "1.8.0"] 27 | [org.clojure/clojurescript "1.8.51"]]} 28 | :1.9 {:dependencies [[org.clojure/clojure "1.9.0"] 29 | [org.clojure/clojurescript "1.9.946"]]}}) 30 | -------------------------------------------------------------------------------- /src/clout/core.cljc: -------------------------------------------------------------------------------- 1 | (ns clout.core 2 | "A small language for routing." 3 | (:require [clojure.string :as string] 4 | [clojure.set :as set] 5 | [instaparse.core :as insta] 6 | #?(:cljs [clout.internal.regex :refer [re-unicode-letter]]))) 7 | 8 | (def ^:private re-chars (set "\\.*+|?()[]{}$^")) 9 | #?(:cljs (def ^:private re-char-escapes 10 | (into {} (for [c re-chars] [c (str "\\" c)])))) 11 | 12 | (defn- re-escape [s] 13 | (string/escape s #?(:clj #(if (re-chars %) (str \\ %)) :cljs re-char-escapes))) 14 | 15 | (defn- re-match-groups [re s] 16 | #?(:clj 17 | (let [matcher (re-matcher re s)] 18 | (when (.matches matcher) 19 | (for [i (range (.groupCount matcher))] 20 | (.group matcher (int (inc i)))))) 21 | :cljs 22 | (if (string? s) 23 | (let [matches (.exec re s)] 24 | (when (= (first matches) s) 25 | (rest matches))) 26 | (throw (js/TypeError. "re-match-groups must match against string."))))) 27 | 28 | (defn- assoc-conj [m k v] 29 | (assoc m k 30 | (if-let [cur (get m k)] 31 | (if (vector? cur) 32 | (conj cur v) 33 | [cur v]) 34 | v))) 35 | 36 | (defn- assoc-keys-with-groups [groups keys] 37 | (reduce (fn [m [k v]] (assoc-conj m k v)) 38 | {} 39 | (map vector keys groups))) 40 | 41 | (defn- request-url [request] 42 | (str (name (:scheme request)) 43 | "://" 44 | (get-in request [:headers "host"]) 45 | (:uri request))) 46 | 47 | (defn- path-info [request] 48 | (or (:path-info request) 49 | (:uri request))) 50 | 51 | (defprotocol Route 52 | (route-matches [route request] 53 | "If the route matches the supplied request, the matched keywords are 54 | returned as a map. Otherwise, nil is returned.")) 55 | 56 | (defrecord CompiledRoute [source re keys absolute?] 57 | Route 58 | (route-matches [_ request] 59 | (let [path-info (if absolute? 60 | (request-url request) 61 | (path-info request))] 62 | (let [groups (re-match-groups re path-info)] 63 | (when groups 64 | (assoc-keys-with-groups groups keys))))) 65 | Object 66 | (toString [_] source)) 67 | 68 | (def ^:private route-parser 69 | (insta/parser 70 | (let [letter #?(:clj "\\p{L}" :cljs re-unicode-letter)] 71 | (str 72 | "route = (scheme / part) part* 73 | scheme = #'(https?:)?//' 74 |