commit e6beae52f0afb4181d2659006a7e05394d30993d Author: Tommi Reiman Date: Mon Aug 7 14:08:39 2017 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6a25983e --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/target +/classes +/checkouts +pom.xml +pom.xml.asc +*.jar +*.class +/.lein-* +/.nrepl-port +/.nrepl-history +/doc +/gh-pages diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..80c9f42c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +sudo: false +language: clojure +lein: 2.7.1 +script: + - ./scripts/test.sh $TEST + - ./scripts/submit-to-coveralls.sh $TEST +env: + matrix: + - TEST=clj + - TEST=cljs +jdk: + - oraclejdk8 +node_js: + - "6.1" +cache: + directories: + - "$HOME/.m2" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..705149ef --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## UNRELEASED + +* Initial release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..88fd2906 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +How to contribute + +Contributions are welcome. + +Please file bug reports and feature requests to https://github.com/metosin/reitit/issues. + +## Making changes + +* Fork the repository on Github +* Create a topic branch from where you want to base your work (usually the master branch) +* Check the formatting rules from existing code (no trailing whitepace, mostly default indentation) +* Ensure any new code is well-tested, and if possible, any issue fixed is covered by one or more new tests +* Verify that all tests pass using ```lein test``` +* Push your code to your fork of the repository +* Make a Pull Request + +## Commit messages + +1. Separate subject from body with a blank line +2. Limit the subject line to 50 characters +3. Capitalize the subject line +4. Do not end the subject line with a period +5. Use the imperative mood in the subject line + - "Add x", "Fix y", "Support z", "Remove x" +6. Wrap the body at 72 characters +7. Use the body to explain what and why vs. how diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f735bee0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. diff --git a/README.md b/README.md new file mode 100644 index 00000000..99f61d07 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# reitit [![Build Status](https://travis-ci.org/metosin/reitit.svg?branch=master)](https://travis-ci.org/metosin/reitit) [![Dependencies Status](https://jarkeeper.com/metosin/reitit/status.svg)](https://jarkeeper.com/metosin/reitit) + +Snappy data-driven router for Clojure(Script). + +## Latest version + +[![Clojars Project](http://clojars.org/metosin/reitit/latest-version.svg)](http://clojars.org/metosin/reitit) + +## Usage + +TODO + +## License + +Copyright © 2016-2017 [Metosin Oy](http://www.metosin.fi) + +Distributed under the Eclipse Public License, the same as Clojure. diff --git a/project.clj b/project.clj new file mode 100644 index 00000000..2c1133ca --- /dev/null +++ b/project.clj @@ -0,0 +1,54 @@ +(defproject metosin/reitit "0.1.0-SNAPSHOT" + :description "Snappy data-driven router for Clojure(Script)" + :url "https://github.com/metosin/reitit" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html" + :distribution :repo + :comments "same as Clojure"} + :test-paths ["test/clj" "test/cljc"] + :deploy-repositories [["releases" :clojars]] + :codox {:output-path "doc" + :source-uri "https://github.com/metosin/reitit/{version}/{filepath}#L{line}" + :metadata {:doc/format :markdown}} + + :profiles {:dev {:plugins [[jonase/eastwood "0.2.3"] + [lein-tach "0.3.0"] + [lein-doo "0.1.7"] + [lein-cljsbuild "1.1.6"] + [lein-cloverage "1.0.9"] + [lein-codox "0.10.3"]] + :jvm-opts ^:replace ["-server"] + :dependencies [[org.clojure/clojure "1.9.0-alpha17"] + [org.clojure/clojurescript "1.9.660"] + [criterium "0.4.4"] + [org.clojure/test.check "0.9.0"] + [org.clojure/tools.namespace "0.2.11"] + [com.gfredericks/test.chuck "0.2.7"]]} + :perf {:jvm-opts ^:replace ["-server"]}} + :aliases {"all" ["with-profile" "dev"] + "perf" ["with-profile" "default,dev,perf"] + "test-clj" ["all" "do" ["test"] ["check"]] + "test-phantom" ["doo" "phantom" "test"] + "test-advanced" ["doo" "phantom" "advanced-test"] + "test-node" ["doo" "node" "node-test"]} + :cljsbuild {:builds [{:id "test" + :source-paths ["src" "test/cljc" "test/cljs"] + :compiler {:output-to "target/out/test.js" + :output-dir "target/out" + :main reitit.doo-runner + :optimizations :none}} + {:id "advanced-test" + :source-paths ["src" "test/cljc" "test/cljs"] + :compiler {:output-to "target/advanced_out/test.js" + :output-dir "target/advanced_out" + :main reitit.doo-runner + :optimizations :advanced}} + ;; Node.js requires :target :nodejs, hence the separate + ;; build configuration. + {:id "node-test" + :source-paths ["src" "test/cljc" "test/cljs"] + :compiler {:output-to "target/node_out/test.js" + :output-dir "target/node_out" + :main reitit.doo-runner + :optimizations :none + :target :nodejs}}]}) diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh new file mode 100755 index 00000000..ae5dd632 --- /dev/null +++ b/scripts/build-docs.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +set -euo pipefail + +rev=$(git rev-parse HEAD) +remoteurl=$(git ls-remote --get-url origin) +repodir=gh-pages +tag=$(git tag --points-at HEAD) +name=$tag +if [[ -z $name ]]; then + name=master +fi +target=$repodir/$name + +git fetch +if [[ -z $(git branch -r --list origin/gh-pages) ]]; then + ( + mkdir "$repodir" + cd "$repodir" + git init + git remote add origin "${remoteurl}" + git checkout -b gh-pages + git commit --allow-empty -m "Init" + git push -u origin gh-pages + ) +elif [[ ! -d "$repodir" ]]; then + git clone --branch gh-pages "${remoteurl}" "$repodir" +else + ( + cd "$repodir" + git pull + ) +fi + +rm -fr doc +lein codox + +# replace docs for current version with new docs +rm -fr "$target" +cp -r doc "$target" + +cd "$repodir" +git add --all +git commit -m "Build docs from ${rev}." +git push origin gh-pages diff --git a/scripts/build.clj b/scripts/build.clj new file mode 100644 index 00000000..764cf9fa --- /dev/null +++ b/scripts/build.clj @@ -0,0 +1,13 @@ +(require 'cljs.closure) + +(cljs.closure/build + ; Includes :source-paths and :test-paths already + "test" + {:main "reitit.runner" + :output-to "target/generated/js/out/tests.js" + :source-map true + :output-dir "target/generated/js/out" + :optimizations :none + :target :nodejs}) + +(shutdown-agents) diff --git a/scripts/repl.clj b/scripts/repl.clj new file mode 100644 index 00000000..fcedfb9b --- /dev/null +++ b/scripts/repl.clj @@ -0,0 +1,9 @@ +(require + '[cljs.repl :as repl] + '[cljs.repl.node :as node]) + +(repl/repl* (node/repl-env) + {:output-dir "target/generated/js/out" + :optimizations :none + :cache-analysis true + :source-map true}) diff --git a/scripts/repl.sh b/scripts/repl.sh new file mode 100755 index 00000000..27afc98f --- /dev/null +++ b/scripts/repl.sh @@ -0,0 +1,2 @@ +#!/bin/sh +rlwrap lein trampoline run -m clojure.main scripts/repl.clj diff --git a/scripts/submit-to-coveralls.sh b/scripts/submit-to-coveralls.sh new file mode 100755 index 00000000..725d4ff7 --- /dev/null +++ b/scripts/submit-to-coveralls.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -euo pipefail + +case $1 in + cljs) + echo "No support for ClojureScript coverage." + ;; + clj) + COVERALLS_URL="https://coveralls.io/api/v1/jobs" + lein cloverage --coveralls + curl -F "json_file=@target/coverage/coveralls.json" "$COVERALLS_URL" + ;; + *) + echo "Please select [clj|cljs]" + exit 1 + ;; +esac + diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 00000000..aef6088f --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e +case $1 in + cljs) + lein "do" test-phantom once, test-node once, test-advanced once + ;; + clj) + lein test-clj + ;; + *) + echo "Please select [clj|cljs]" + exit 1 + ;; +esac diff --git a/src/reitit/core.cljc b/src/reitit/core.cljc new file mode 100644 index 00000000..681d22ba --- /dev/null +++ b/src/reitit/core.cljc @@ -0,0 +1,76 @@ +(ns reitit.core) + +(defn- deep-merge [& values] + (let [[values strategy] (if (keyword? (first values)) + [(rest values) (first values)] + [values :replace])] + (cond + (every? map? values) + (apply merge-with (partial deep-merge strategy) values) + + (and (= strategy :into) (every? coll? values)) + (reduce into values) + + :else + (last values)))) + +(defprotocol ExpandArgs + (expand [this])) + +(extend-protocol ExpandArgs + + #?(:clj clojure.lang.Keyword + :cljs cljs.core.Keyword) + (expand [this] {:handler this}) + + #?(:clj clojure.lang.PersistentArrayMap + :cljs cljs.core.PersistentArrayMap) + (expand [this] this) + + #?(:clj clojure.lang.PersistentHashMap + :cljs cljs.core.PersistentHashMap) + (expand [this] this) + + nil + (expand [_])) + +(defn walk + ([routes] + (walk ["" []] routes)) + ([[pacc macc] routes] + (let [subwalk (fn [path meta routes] + (reduce + (fn [acc route] + (into acc (walk [path meta] route))) + [] + routes))] + (if (vector? (first routes)) + (subwalk pacc macc routes) + (let [[path & [maybe-meta :as args]] routes] + (let [[meta childs] (if-not (vector? maybe-meta) + [maybe-meta (rest args)] + [{} args]) + macc (into macc (expand meta))] + (if (seq childs) + (subwalk (str pacc path) macc childs) + [[(str pacc path) macc]]))))))) + +(defn map-meta [f routes] + (mapv #(update % 1 f) routes)) + +(defn merge-meta + ([x] + (merge-meta (constantly :into) x)) + ([key-strategy x] + (reduce + (fn [acc [k v]] + (let [strategy (or (key-strategy k) :replace)] + (deep-merge strategy acc {k v}))) + {} + x))) + +(defn resolve-routes + ([x] + (resolve-routes (constantly :replace) x)) + ([key-strategy x] + (->> x (walk) (map-meta (partial merge-meta key-strategy))))) diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc new file mode 100644 index 00000000..e790e853 --- /dev/null +++ b/test/cljc/reitit/core_test.cljc @@ -0,0 +1,38 @@ +(ns reitit.core-test + (:require [clojure.test :refer [deftest testing is are]] + [reitit.core :as reitit])) + +(def routes2 + ["/api" {:mw [:api]} + ["/ping" :kikka] + ["/user/:id" {:parameters {:id String}} + ["/:sub-id" {:parameters {:sub-id String}}]] + ["/pong"] + ["/admin" {:mw [:admin] :roles #{:admin}} + ["/user" {:roles #{:user}}] + ["/db" {:mw [:db]}]]]) + +(deftest reitit-test + (testing "bide sample" + (let [routes [["/auth/login" :auth/login] + ["/auth/recovery/token/:token" :auth/recovery] + ["/workspace/:project-uuid/:page-uuid" :workspace/page]]] + (is (= [["/auth/login" {:handler :auth/login}] + ["/auth/recovery/token/:token" {:handler :auth/recovery}] + ["/workspace/:project-uuid/:page-uuid" {:handler :workspace/page}]] + (reitit/resolve-routes routes))))) + (testing "ring sample" + (let [routes ["/api" {:mw [:api]} + ["/ping" :kikka] + ["/user/:id" {:parameters {:id String}} + ["/:sub-id" {:parameters {:sub-id String}}]] + ["/pong"] + ["/admin" {:mw [:admin] :roles #{:admin}} + ["/user" {:roles #{:user}}] + ["/db" {:mw [:db]}]]]] + (is (= [["/api/ping" {:mw [:api], :handler :kikka}] + ["/api/user/:id/:sub-id" {:mw [:api], :parameters {:id String, :sub-id String}}] + ["/api/pong" {:mw [:api]}] + ["/api/admin/user" {:mw [:api :admin], :roles #{:user}}] + ["/api/admin/db" {:mw [:api :admin :db], :roles #{:admin}}]] + (reitit/resolve-routes {:mw :into} routes)))))) \ No newline at end of file diff --git a/test/cljs/reitit/doo_runner.cljs b/test/cljs/reitit/doo_runner.cljs new file mode 100644 index 00000000..cc33a68e --- /dev/null +++ b/test/cljs/reitit/doo_runner.cljs @@ -0,0 +1,7 @@ +(ns reitit.doo-runner + (:require [doo.runner :refer-macros [doo-tests]] + reitit.core-test)) + +(enable-console-print!) + +(doo-tests 'reitit.core-test)