├── .travis.yml ├── deps.edn ├── .gitignore ├── test └── medley │ ├── test_runner.cljs │ └── core_test.cljc ├── .github └── workflows │ └── test.yml ├── CONTRIBUTING.md ├── project.clj ├── README.md ├── LICENSE.txt └── src └── medley └── core.cljc /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | script: lein test-all 3 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.9.0"}}} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | /codox 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | /.cpcache 12 | /.clj-kondo 13 | -------------------------------------------------------------------------------- /test/medley/test_runner.cljs: -------------------------------------------------------------------------------- 1 | (ns medley.test-runner 2 | (:require [doo.runner :refer-macros [doo-tests]] 3 | [medley.core-test])) 4 | 5 | (doo-tests 'medley.core-test) 6 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v3 9 | 10 | - name: Prepare java 11 | uses: actions/setup-java@v3 12 | with: 13 | distribution: 'zulu' 14 | java-version: '8' 15 | 16 | - name: Install clojure tools 17 | uses: DeLaGuardo/setup-clojure@10.1 18 | with: 19 | lein: 2.9.10 20 | 21 | - name: Cache clojure dependencies 22 | uses: actions/cache@v3 23 | with: 24 | path: ~/.m2/repository 25 | key: cljdeps-${{ hashFiles('project.clj') }} 26 | restore-keys: cljdeps- 27 | 28 | - name: Run tests 29 | run: lein test-all 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | **Do** follow [the seven rules of a great Git commit message][1]. 4 | 5 | **Do** follow [the Clojure Style Guide][2]. 6 | 7 | **Do** include tests for your change when appropriate. 8 | 9 | **Do** ensure that the CI checks pass. 10 | 11 | **Do** squash the commits in your PR to remove corrections 12 | irrelevant to the code history, once the PR has been reviewed. 13 | 14 | **Do** feel free to pester the project maintainers about the PR if it 15 | hasn't been responded to. Sometimes notifications can be missed. 16 | 17 | **Don't** include more than one feature or fix in a single PR. 18 | 19 | **Don't** include changes unrelated to the purpose of the PR. This 20 | includes changing the project version number, adding lines to the 21 | `.gitignore` file, or changing the indentation or formatting. 22 | 23 | **Don't** open a new PR if changes are requested. Just push to the 24 | same branch and the PR will be updated. 25 | 26 | **Don't** overuse vertical whitespace; avoid multiple sequential blank 27 | lines. 28 | 29 | **Don't** docstring private vars or functions. 30 | 31 | [1]: https://chris.beams.io/posts/git-commit/#seven-rules 32 | [2]: https://github.com/bbatsov/clojure-style-guide 33 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject dev.weavejester/medley "1.9.0" 2 | :description "A lightweight library of useful, mostly pure functions" 3 | :url "https://github.com/weavejester/medley" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.9.0"]] 7 | :plugins [[lein-codox "0.10.8"] 8 | [lein-cljsbuild "1.1.7"] 9 | [lein-doo "0.1.10"]] 10 | :codox 11 | {:output-path "codox" 12 | :metadata {:doc/format :markdown} 13 | :source-uri "http://github.com/weavejester/medley/blob/{version}/{filepath}#L{line}"} 14 | :cljsbuild 15 | {:builds 16 | {:test 17 | {:source-paths ["src" "test"] 18 | :compiler {:output-to "target/main.js" 19 | :output-dir "target" 20 | :main medley.test-runner 21 | :optimizations :simple}}}} 22 | :doo {:paths {:rhino "lein run -m org.mozilla.javascript.tools.shell.Main"}} 23 | :aliases 24 | {"test-cljs" ["doo" "rhino" "test" "once"] 25 | "test-clj" ["with-profile" "default:+1.10:+1.11:+1.12" "test"] 26 | "test-all" ["do" ["test-clj"] ["test-cljs"]]} 27 | :profiles 28 | {:provided {:dependencies [[org.clojure/clojurescript "1.10.439"]]} 29 | :test {:dependencies [[org.mozilla/rhino "1.7.14"]]} 30 | :dev {:dependencies [[criterium "0.4.6"]] 31 | :jvm-opts ^:replace {}} 32 | :1.10 {:dependencies [[org.clojure/clojure "1.10.0"]]} 33 | :1.11 {:dependencies [[org.clojure/clojure "1.11.2"]]} 34 | :1.12 {:dependencies [[org.clojure/clojure "1.12.2"]]}}) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Medley [![Build Status](https://github.com/weavejester/medley/actions/workflows/test.yml/badge.svg)](https://github.com/weavejester/medley/actions/workflows/test.yml) 2 | 3 | Medley is a lightweight Clojure/ClojureScript library of useful, 4 | *mostly* pure functions that are "missing" from clojure.core. 5 | 6 | Medley has a tighter focus than [flatland/useful][] or [Plumbing][], 7 | and limits itself to a small set of general-purpose functions. 8 | 9 | [flatland/useful]: https://github.com/flatland/useful 10 | [plumbing]: https://github.com/plumatic/plumbing 11 | 12 | ## Installation 13 | 14 | Add the following dependency to your deps.edn file: 15 | 16 | dev.weavejester/medley {:mvn/version "1.9.0"} 17 | 18 | Or to your Leiningen project file: 19 | 20 | [dev.weavejester/medley "1.9.0"] 21 | 22 | ## Documentation 23 | 24 | * [API Docs](http://weavejester.github.io/medley/medley.core.html) 25 | 26 | ## Ports 27 | 28 | * [Medley for the CLR](https://github.com/E-A-Griffin/medley) is kindly 29 | maintained by [Emma Griffin](https://github.com/E-A-Griffin). 30 | 31 | ## Pre-1.0 Breaking Changes 32 | 33 | In 0.7.0 the minimum Clojure version was changed from 1.5.1 to 1.7.0 34 | to take advantage of [reader conditionals][]. The `update` function 35 | has also been removed, as it is now present in `clojure.core`. 36 | 37 | In 0.6.0 the type signature of `greatest` and `least` was changed to 38 | be more like `max` and `min` in Clojure core. If you're upgrading from 39 | a version prior to 0.6.0, please update your usage of `greatest` and 40 | `least`. 41 | 42 | [reader conditionals]: http://dev.clojure.org/display/design/Reader+Conditionals 43 | 44 | ## License 45 | 46 | Copyright © 2025 James Reeves 47 | 48 | Distributed under the Eclipse Public License either version 1.0 or (at 49 | your option) any later version. 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | a) in the case of the initial Contributor, the initial code and 11 | documentation distributed under this Agreement, and 12 | b) in the case of each subsequent Contributor: 13 | i) changes to the Program, and 14 | ii) additions to the Program; 15 | 16 | where such changes and/or additions to the Program originate from and are 17 | distributed by that particular Contributor. A Contribution 'originates' from a 18 | Contributor if it was added to the Program by such Contributor itself or 19 | anyone acting on such Contributor's behalf. Contributions do not include 20 | additions to the Program which: (i) are separate modules of software 21 | distributed in conjunction with the Program under their own license agreement, 22 | and (ii) are not derivative works of the Program. 23 | "Contributor" means any person or entity that distributes the Program. 24 | 25 | "Licensed Patents" mean patent claims licensable by a Contributor which are 26 | necessarily infringed by the use or sale of its Contribution alone or when 27 | combined with the Program. 28 | 29 | "Program" means the Contributions distributed in accordance with this 30 | Agreement. 31 | 32 | "Recipient" means anyone who receives the Program under this Agreement, 33 | including all Contributors. 34 | 35 | 2. GRANT OF RIGHTS 36 | 37 | a) Subject to the terms of this Agreement, each Contributor hereby grants 38 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 39 | reproduce, prepare derivative works of, publicly display, publicly 40 | perform, distribute and sublicense the Contribution of such Contributor, 41 | if any, and such derivative works, in source code and object code form. 42 | 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | 54 | c) Recipient understands that although each Contributor grants the 55 | licenses to its Contributions set forth herein, no assurances are 56 | provided by any Contributor that the Program does not infringe the patent 57 | or other intellectual property rights of any other entity. Each 58 | Contributor disclaims any liability to Recipient for claims brought by 59 | any other entity based on infringement of intellectual property rights or 60 | otherwise. As a condition to exercising the rights and licenses granted 61 | hereunder, each Recipient hereby assumes sole responsibility to secure 62 | any other intellectual property rights needed, if any. For example, if a 63 | third party patent license is required to allow Recipient to distribute 64 | the Program, it is Recipient's responsibility to acquire that license 65 | before distributing the Program. 66 | 67 | d) Each Contributor represents that to its knowledge it has sufficient 68 | copyright rights in its Contribution, if any, to grant the copyright 69 | license set forth in this Agreement. 70 | 71 | 3. REQUIREMENTS 72 | A Contributor may choose to distribute the Program in object code form under 73 | its own license agreement, provided that: 74 | 75 | a) it complies with the terms and conditions of this Agreement; and 76 | 77 | b) its license agreement: 78 | i) effectively disclaims on behalf of all Contributors all 79 | warranties and conditions, express and implied, including warranties 80 | or conditions of title and non-infringement, and implied warranties 81 | or conditions of merchantability and fitness for a particular 82 | purpose; 83 | ii) effectively excludes on behalf of all Contributors all liability 84 | for damages, including direct, indirect, special, incidental and 85 | consequential damages, such as lost profits; 86 | iii) states that any provisions which differ from this Agreement are 87 | offered by that Contributor alone and not by any other party; and 88 | iv) states that source code for the Program is available from such 89 | Contributor, and informs licensees how to obtain it in a reasonable 90 | manner on or through a medium customarily used for software 91 | exchange. 92 | 93 | When the Program is made available in source code form: 94 | 95 | a) it must be made available under this Agreement; and 96 | 97 | b) a copy of this Agreement must be included with each copy of the 98 | Program. 99 | Contributors may not remove or alter any copyright notices contained within 100 | the Program. 101 | 102 | Each Contributor must identify itself as the originator of its Contribution, 103 | if any, in a manner that reasonably allows subsequent Recipients to identify 104 | the originator of the Contribution. 105 | 106 | 4. COMMERCIAL DISTRIBUTION 107 | Commercial distributors of software may accept certain responsibilities with 108 | respect to end users, business partners and the like. While this license is 109 | intended to facilitate the commercial use of the Program, the Contributor who 110 | includes the Program in a commercial product offering should do so in a manner 111 | which does not create potential liability for other Contributors. Therefore, 112 | if a Contributor includes the Program in a commercial product offering, such 113 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 114 | every other Contributor ("Indemnified Contributor") against any losses, 115 | damages and costs (collectively "Losses") arising from claims, lawsuits and 116 | other legal actions brought by a third party against the Indemnified 117 | Contributor to the extent caused by the acts or omissions of such Commercial 118 | Contributor in connection with its distribution of the Program in a commercial 119 | product offering. The obligations in this section do not apply to any claims 120 | or Losses relating to any actual or alleged intellectual property 121 | infringement. In order to qualify, an Indemnified Contributor must: a) 122 | promptly notify the Commercial Contributor in writing of such claim, and b) 123 | allow the Commercial Contributor to control, and cooperate with the Commercial 124 | Contributor in, the defense and any related settlement negotiations. The 125 | Indemnified Contributor may participate in any such claim at its own expense. 126 | 127 | For example, a Contributor might include the Program in a commercial product 128 | offering, Product X. That Contributor is then a Commercial Contributor. If 129 | that Commercial Contributor then makes performance claims, or offers 130 | warranties related to Product X, those performance claims and warranties are 131 | such Commercial Contributor's responsibility alone. Under this section, the 132 | Commercial Contributor would have to defend claims against the other 133 | Contributors related to those performance claims and warranties, and if a 134 | court requires any other Contributor to pay any damages as a result, the 135 | Commercial Contributor must pay those damages. 136 | 137 | 5. NO WARRANTY 138 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 139 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 140 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 141 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 142 | Recipient is solely responsible for determining the appropriateness of using 143 | and distributing the Program and assumes all risks associated with its 144 | exercise of rights under this Agreement , including but not limited to the 145 | risks and costs of program errors, compliance with applicable laws, damage to 146 | or loss of data, programs or equipment, and unavailability or interruption of 147 | operations. 148 | 149 | 6. DISCLAIMER OF LIABILITY 150 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 151 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 152 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 153 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 154 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 155 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 156 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 157 | OF SUCH DAMAGES. 158 | 159 | 7. GENERAL 160 | 161 | If any provision of this Agreement is invalid or unenforceable under 162 | applicable law, it shall not affect the validity or enforceability of the 163 | remainder of the terms of this Agreement, and without further action by the 164 | parties hereto, such provision shall be reformed to the minimum extent 165 | necessary to make such provision valid and enforceable. 166 | 167 | If Recipient institutes patent litigation against any entity (including a 168 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 169 | (excluding combinations of the Program with other software or hardware) 170 | infringes such Recipient's patent(s), then such Recipient's rights granted 171 | under Section 2(b) shall terminate as of the date such litigation is filed. 172 | 173 | All Recipient's rights under this Agreement shall terminate if it fails to 174 | comply with any of the material terms or conditions of this Agreement and does 175 | not cure such failure in a reasonable period of time after becoming aware of 176 | such noncompliance. If all Recipient's rights under this Agreement terminate, 177 | Recipient agrees to cease use and distribution of the Program as soon as 178 | reasonably practicable. However, Recipient's obligations under this Agreement 179 | and any licenses granted by Recipient relating to the Program shall continue 180 | and survive. 181 | 182 | Everyone is permitted to copy and distribute copies of this Agreement, but in 183 | order to avoid inconsistency the Agreement is copyrighted and may only be 184 | modified in the following manner. The Agreement Steward reserves the right to 185 | publish new versions (including revisions) of this Agreement from time to 186 | time. No one other than the Agreement Steward has the right to modify this 187 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 188 | Eclipse Foundation may assign the responsibility to serve as the Agreement 189 | Steward to a suitable separate entity. Each new version of the Agreement will 190 | be given a distinguishing version number. The Program (including 191 | Contributions) may always be distributed subject to the version of the 192 | Agreement under which it was received. In addition, after a new version of the 193 | Agreement is published, Contributor may elect to distribute the Program 194 | (including its Contributions) under the new version. Except as expressly 195 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 196 | licenses to the intellectual property of any Contributor under this Agreement, 197 | whether expressly, by implication, estoppel or otherwise. All rights in the 198 | Program not expressly granted under this Agreement are reserved. 199 | 200 | This Agreement is governed by the laws of the State of New York and the 201 | intellectual property laws of the United States of America. No party to this 202 | Agreement will bring a legal action under this Agreement more than one year 203 | after the cause of action arose. Each party waives its rights to a jury trial 204 | in any resulting litigation. 205 | -------------------------------------------------------------------------------- /test/medley/core_test.cljc: -------------------------------------------------------------------------------- 1 | (ns medley.core-test 2 | #?(:clj (:import [clojure.lang ArityException])) 3 | (:require #?(:clj [clojure.test :refer :all] 4 | :cljs [cljs.test :refer-macros [deftest is testing]]) 5 | [medley.core :as m])) 6 | 7 | (deftest test-find-first 8 | (testing "sequences" 9 | (is (= (m/find-first even? [7 3 3 2 8]) 2)) 10 | (is (nil? (m/find-first even? [7 3 3 7 3])))) 11 | (testing "transducers" 12 | (is (= (transduce (m/find-first even?) + 0 [7 3 3 2 8]) 2)) 13 | (is (= (transduce (m/find-first even?) + 0 [7 3 3 7 3]) 0)))) 14 | 15 | (deftest test-dissoc-in 16 | (is (= (m/dissoc-in {:a {:b {:c 1 :d 2}}} [:a :b :c]) 17 | {:a {:b {:d 2}}})) 18 | (is (= (m/dissoc-in {:a {:b {:c 1}}} [:a :b :c]) 19 | {})) 20 | (is (= (m/dissoc-in {:a {:b {:c 1} :d 2}} [:a :b :c]) 21 | {:a {:d 2}})) 22 | (is (= (m/dissoc-in {:a {:b {:c 1} :d 2} :b {:c {:d 2 :e 3}}} [:a :b :c] [:b :c :d]) 23 | {:a {:d 2} :b {:c {:e 3}}})) 24 | (is (= (m/dissoc-in {:a 1} []) 25 | {:a 1}))) 26 | 27 | (deftest test-assoc-some 28 | (is (= (m/assoc-some {:a 1} :b 2) {:a 1 :b 2})) 29 | (is (= (m/assoc-some {:a 1} :b nil) {:a 1})) 30 | (is (= (m/assoc-some {:a 1} :b 2 :c nil :d 3) {:a 1 :b 2 :d 3})) 31 | (is (= (m/assoc-some nil :a 1) {:a 1})) 32 | (is (= (m/assoc-some nil :a 1 :b 2) {:a 1 :b 2})) 33 | (is (nil? (m/assoc-some nil :a nil))) 34 | (is (nil? (m/assoc-some nil :a nil :b nil))) 35 | (let [input (with-meta {:a 1} {:m 42})] 36 | (is (= {:m 42} (meta (m/assoc-some input :b 2 :c nil :d 3)))))) 37 | 38 | (deftest test-update-existing 39 | (is (= (m/update-existing {:a 1} :a inc) {:a 2})) 40 | (is (= (m/update-existing {:a 1 :b 2} :a inc) {:a 2 :b 2})) 41 | (is (= (m/update-existing {:b 2} :a inc) {:b 2})) 42 | (is (= (m/update-existing {:a nil} :a str) {:a ""})) 43 | (is (= (m/update-existing {} :a str) {}))) 44 | 45 | (deftest test-update-existing-in 46 | (is (= (m/update-existing-in {:a 1} [:a] inc) {:a 2})) 47 | (is (= (m/update-existing-in {:a 1 :b 2} [:a] inc) {:a 2 :b 2})) 48 | (is (= (m/update-existing-in {:b 2} [:a] inc) {:b 2})) 49 | (is (= (m/update-existing-in {:a nil} [:a] str) {:a ""})) 50 | (is (= (m/update-existing-in {} [:a] str) {})) 51 | (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] inc) 52 | {:a [:b {:c 43} :d]})) 53 | (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] + 7) 54 | {:a [:b {:c 49} :d]})) 55 | (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] + 3 4) 56 | {:a [:b {:c 49} :d]})) 57 | (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] + 3 3 1) 58 | {:a [:b {:c 49} :d]})) 59 | (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] vector 9 10 11 12 13 14) 60 | {:a [:b {:c [42 9 10 11 12 13 14]} :d]}))) 61 | 62 | (deftest test-map-entry 63 | (is (= (key (m/map-entry :a 1)) :a)) 64 | (is (= (val (m/map-entry :a 1)) 1)) 65 | (is (= (first (m/map-entry :a 1)) :a)) 66 | (is (= (second (m/map-entry :a 1)) 1)) 67 | (is (= (type (m/map-entry :a 1)) 68 | (type (first {:a 1}))))) 69 | 70 | (defrecord MyRecord [x]) 71 | 72 | (deftest test-map-kv 73 | (is (= (m/map-kv (fn [k v] [(name k) (inc v)]) {:a 1 :b 2}) 74 | {"a" 2 "b" 3})) 75 | (is (= (m/map-kv (fn [k v] [(name k) (inc v)]) (sorted-map :a 1 :b 2)) 76 | {"a" 2 "b" 3})) 77 | (is (= (m/map-kv (fn [k v] (m/map-entry (name k) (inc v))) {:a 1 :b 2}) 78 | {"a" 2 "b" 3})) 79 | (testing "map-kv with record" 80 | (is (= (m/map-kv (fn [k v] (m/map-entry (name k) (inc v))) (->MyRecord 1)) {"x" 2})))) 81 | 82 | (deftest test-map-keys 83 | (is (= (m/map-keys name {:a 1 :b 2}) 84 | {"a" 1 "b" 2})) 85 | (is (= (m/map-keys name (sorted-map :a 1 :b 2)) 86 | (sorted-map "a" 1 "b" 2))) 87 | (testing "map-keys with record" 88 | (is (= (m/map-keys name (->MyRecord 1)) {"x" 1})))) 89 | 90 | (deftest test-map-vals 91 | (is (= (m/map-vals inc {:a 1 :b 2}) 92 | {:a 2 :b 3})) 93 | (is (= (m/map-vals inc (sorted-map :a 1 :b 2)) 94 | (sorted-map :a 2 :b 3))) 95 | (testing "map-vals with record" 96 | (is (= (m/map-vals inc (->MyRecord 1)) {:x 2}))) 97 | (testing "multiple collections" 98 | (is (= (m/map-vals + {:a 1 :b 2 :c 3} {:a 4 :c 5 :d 6}) 99 | {:a 5, :c 8})) 100 | (is (= (m/map-vals min 101 | (sorted-map :z 10 :y 8 :x 4) 102 | {:x 7, :y 14, :z 13} 103 | {:x 11, :y 6, :z 9} 104 | {:x 19, :y 3, :z 2} 105 | {:x 4, :y 0, :z 16} 106 | {:x 17, :y 14, :z 13}) 107 | (sorted-map :x 4 :y 0 :z 2))) 108 | (is (= (m/map-vals #(%1 %2) {:a nil? :b some?} {:b nil}) 109 | {:b false})))) 110 | 111 | (deftest test-map-kv-keys 112 | (is (= (m/map-kv-keys + {1 2, 2 4}) 113 | {3 2, 6 4})) 114 | (is (= (m/map-kv-keys + (sorted-map 1 2, 2 4)) 115 | (sorted-map 3 2, 6 4))) 116 | (is (= (m/map-kv-keys str (->MyRecord 1)) 117 | {":x1" 1}))) 118 | 119 | (deftest test-map-kv-vals 120 | (is (= (m/map-kv-vals + {1 2, 2 4}) 121 | {1 3, 2 6})) 122 | (is (= (m/map-kv-vals + (sorted-map 1 2, 2 4)) 123 | (sorted-map 1 3, 2 6))) 124 | (is (= (m/map-kv-vals str (->MyRecord 1)) 125 | {:x ":x1"}))) 126 | 127 | (deftest test-filter-kv 128 | (is (= (m/filter-kv (fn [k v] (and (keyword? k) (number? v))) {"a" 1 :b 2 :c "d"}) 129 | {:b 2})) 130 | (is (= (m/filter-kv (fn [k v] (= v 2)) (sorted-map "a" 1 "b" 2)) 131 | (sorted-map "b" 2)))) 132 | 133 | (deftest test-filter-keys 134 | (is (= (m/filter-keys keyword? {"a" 1 :b 2}) 135 | {:b 2})) 136 | (is (= (m/filter-keys #(re-find #"^b" %) (sorted-map "a" 1 "b" 2)) 137 | (sorted-map "b" 2)))) 138 | 139 | (deftest test-filter-vals 140 | (is (= (m/filter-vals even? {:a 1 :b 2}) 141 | {:b 2})) 142 | (is (= (m/filter-vals even? (sorted-map :a 1 :b 2)) 143 | (sorted-map :b 2)))) 144 | 145 | (deftest test-remove-kv 146 | (is (= (m/remove-kv (fn [k v] (and (keyword? k) (number? v))) {"a" 1 :b 2 :c "d"}) 147 | {"a" 1 :c "d"})) 148 | (is (= (m/remove-kv (fn [k v] (= v 2)) (sorted-map "a" 1 "b" 2)) 149 | (sorted-map "a" 1)))) 150 | 151 | (deftest test-remove-keys 152 | (is (= (m/remove-keys keyword? {"a" 1 :b 2}) 153 | {"a" 1})) 154 | (is (= (m/remove-keys #(re-find #"^b" %) (sorted-map "a" 1 "b" 2)) 155 | {"a" 1}))) 156 | 157 | (deftest test-remove-vals 158 | (is (= (m/remove-vals even? {:a 1 :b 2}) 159 | {:a 1})) 160 | (is (= (m/remove-keys #(re-find #"^b" %) (sorted-map "a" 1 "b" 2)) 161 | {"a" 1}))) 162 | 163 | (deftest test-queue 164 | (testing "empty" 165 | #?(:clj (is (instance? clojure.lang.PersistentQueue (m/queue))) 166 | :cljs (is (instance? cljs.core.PersistentQueue (m/queue)))) 167 | (is (empty? (m/queue)))) 168 | (testing "not empty" 169 | #?(:clj (is (instance? clojure.lang.PersistentQueue (m/queue [1 2 3]))) 170 | :cljs (is (instance? cljs.core.PersistentQueue (m/queue [1 2 3])))) 171 | (is (= (first (m/queue [1 2 3])) 1)))) 172 | 173 | (deftest test-queue? 174 | #?(:clj (is (m/queue? clojure.lang.PersistentQueue/EMPTY)) 175 | :cljs (is (m/queue? cljs.core.PersistentQueue.EMPTY))) 176 | (is (not (m/queue? [])))) 177 | 178 | (deftest test-boolean? 179 | (is (m/boolean? true)) 180 | (is (m/boolean? false)) 181 | (is (not (m/boolean? nil))) 182 | (is (not (m/boolean? "foo"))) 183 | (is (not (m/boolean? 1)))) 184 | 185 | (deftest test-least 186 | (is (= (m/least) nil)) 187 | (is (= (m/least "a") "a")) 188 | (is (= (m/least "a" "b") "a")) 189 | (is (= (m/least 3 2 5 -1 0 2) -1))) 190 | 191 | (deftest test-least-by 192 | (is (= (m/least-by :a) nil)) 193 | (is (= (m/least-by :a {:a 42}) {:a 42})) 194 | (is (= (m/least-by :a {:a 3} {:a 1} {:a 2}) {:a 1})) 195 | (is (= (m/least-by :a {:a 3 :b 1} {:a 2 :b 2} {:a 2 :b 3}) {:a 2 :b 3})) 196 | (is (= (m/least-by last "foo" "baa" "baz" "qux") "baa"))) 197 | 198 | (deftest test-greatest 199 | (is (= (m/greatest) nil)) 200 | (is (= (m/greatest "a") "a")) 201 | (is (= (m/greatest "a" "b") "b")) 202 | (is (= (m/greatest 3 2 5 -1 0 2) 5))) 203 | 204 | (deftest test-greatest-by 205 | (is (= (m/greatest-by :a) nil)) 206 | (is (= (m/greatest-by :a {:a 42}) {:a 42})) 207 | (is (= (m/greatest-by :a {:a 3} {:a 1} {:a 2}) {:a 3})) 208 | (is (= (m/greatest-by :a {:a 2 :b 1} {:a 3 :b 2} {:a 3 :b 3}) {:a 3 :b 3})) 209 | (is (= (m/greatest-by last "foo" "baa" "baz" "qux") "baz"))) 210 | 211 | (deftest test-join 212 | (is (= (m/join [[1 2] [] [3] [4 5 6]]) [1 2 3 4 5 6])) 213 | (is (= (m/join (sorted-map :x 1, :y 2, :z 3)) [:x 1 :y 2 :z 3])) 214 | (let [a (atom 0) 215 | s (m/join (iterate #(do (swap! a inc) (range (inc (count %)))) ()))] 216 | (is (= (first s) 0)) 217 | (is (= @a 1)) 218 | (is (= (second s) 0)) 219 | (is (= @a 2)))) 220 | 221 | (deftest test-deep-merge 222 | (is (= (m/deep-merge) nil)) 223 | (is (= (m/deep-merge {:a 1}) {:a 1})) 224 | (is (= (m/deep-merge {:a 1} nil) {:a 1})) 225 | (is (= (m/deep-merge {:a 1} {:a 2 :b 3}) {:a 2 :b 3})) 226 | (is (= (m/deep-merge {:a {:b 1 :c 2}} {:a {:b 2 :d 3}}) {:a {:b 2 :c 2 :d 3}})) 227 | (is (= (m/deep-merge {:a {:b 1}} {:a 1}) {:a 1})) 228 | (is (= (m/deep-merge {:a 1} {:b 2} {:b 3 :c 4}) {:a 1 :b 3 :c 4})) 229 | (is (= (m/deep-merge {:a {:b {:c {:d 1}}}} {:a {:b {:c {:e 2}}}}) {:a {:b {:c {:d 1 :e 2}}}})) 230 | (is (= (m/deep-merge {:a {:b [1 2]}} {:a {:b [3 4]}}) {:a {:b [3 4]}})) 231 | (is (= (m/deep-merge (->MyRecord 1) {:x 2}) (->MyRecord 2))) 232 | (is (= (m/deep-merge {:a (->MyRecord 1)} {:a {:x 2 :y 3}}) {:a (map->MyRecord {:x 2 :y 3})}))) 233 | 234 | (deftest test-mapply 235 | (letfn [(foo [& {:keys [bar]}] bar)] 236 | (is (= (m/mapply foo {}) nil)) 237 | (is (= (m/mapply foo {:baz 1}) nil)) 238 | (is (= (m/mapply foo {:bar 1}) 1))) 239 | (letfn [(foo [bar & {:keys [baz]}] [bar baz])] 240 | (is (= (m/mapply foo 0 {}) [0 nil])) 241 | (is (= (m/mapply foo 0 {:baz 1}) [0 1])) 242 | (is (= (m/mapply foo 0 {:spam 1}) [0 nil])) 243 | (is (= (m/mapply foo 0 nil) [0 nil])) 244 | #?@(:clj [(is (thrown? ArityException (m/mapply foo {}))) 245 | (is (thrown? IllegalArgumentException (m/mapply foo 0)))] 246 | :cljs [(is (thrown? js/Error (m/mapply foo 0)))]))) 247 | 248 | (deftest test-collate-by 249 | (is (= (m/collate-by identity conj vector [1 2 2 3 3]) 250 | {1 [1], 2 [2 2], 3 [3 3]})) 251 | (is (= (m/collate-by identity conj hash-set [1 2 2 3 3]) 252 | {1 #{1}, 2 #{2}, 3 #{3}})) 253 | (is (= (m/collate-by first conj list ["foo" "bar" "baz"]) 254 | {\f '("foo"), \b '("baz" "bar")})) 255 | (is (= (m/collate-by first (fn [_ x] x) ["foo" "bar" "baz"]) 256 | {\f "foo", \b "baz"})) 257 | (is (= (m/collate-by even? + [1 2 3 4 5 6]) 258 | {true 12, false 9})) 259 | (is (= (m/collate-by first (fn [_ x] x) []) {})) 260 | (is (= (m/collate-by first conj vector []) {}))) 261 | 262 | (deftest test-index-by 263 | (is (= (m/index-by identity [1 2 3]) {1 1, 2 2, 3 3})) 264 | (is (= (m/index-by inc [1 2 3]) {2 1, 3 2, 4 3})) 265 | (is (= (m/index-by first ["foo" "bar" "baz"]) {\f "foo", \b "baz"})) 266 | (is (= (m/index-by first []) {}))) 267 | 268 | (deftest test-interleave-all 269 | (is (= (m/interleave-all []) [])) 270 | (is (= (m/interleave-all [1 2 3]) [1 2 3])) 271 | (is (= (m/interleave-all [1 2 3] [4 5 6]) [1 4 2 5 3 6])) 272 | (is (= (m/interleave-all [1 2 3] [4 5 6] [7 8 9]) [1 4 7 2 5 8 3 6 9])) 273 | (is (= (m/interleave-all [1 2] [3]) [1 3 2])) 274 | (is (= (m/interleave-all [1 2 3] [4 5]) [1 4 2 5 3])) 275 | (is (= (m/interleave-all [1] [2 3] [4 5 6]) [1 2 4 3 5 6]))) 276 | 277 | (deftest test-distinct-by 278 | (testing "sequences" 279 | (is (= (m/distinct-by count ["a" "ab" "c" "cd" "def"]) 280 | ["a" "ab" "def"])) 281 | (is (= (m/distinct-by count []) 282 | [])) 283 | (is (= (m/distinct-by first ["foo" "faa" "boom" "bar"]) 284 | ["foo" "boom"]))) 285 | 286 | (testing "transucers" 287 | (is (= (into [] (m/distinct-by count) ["a" "ab" "c" "cd" "def"]) 288 | ["a" "ab" "def"])) 289 | (is (= (into [] (m/distinct-by count) []) 290 | [])) 291 | (is (= (into [] (m/distinct-by first) ["foo" "faa" "boom" "bar"]) 292 | ["foo" "boom"])))) 293 | 294 | (deftest test-dedupe-by 295 | (testing "sequences" 296 | (is (= (m/dedupe-by count ["a" "b" "bc" "bcd" "cd"]) 297 | ["a" "bc" "bcd" "cd"])) 298 | (is (= (m/dedupe-by count []) 299 | [])) 300 | (is (= (m/dedupe-by first ["foo" "faa" "boom" "bar"]) 301 | ["foo" "boom"]))) 302 | 303 | (testing "transucers" 304 | (is (= (into [] (m/dedupe-by count) ["a" "b" "bc" "bcd" "cd"]) 305 | ["a" "bc" "bcd" "cd"])) 306 | (is (= (into [] (m/dedupe-by count) []) 307 | [])) 308 | (is (= (into [] (m/dedupe-by first) ["foo" "faa" "boom" "bar"]) 309 | ["foo" "boom"])))) 310 | 311 | (deftest test-take-upto 312 | (testing "sequences" 313 | (is (= (m/take-upto zero? [1 2 3 0 4 5 6]) [1 2 3 0])) 314 | (is (= (m/take-upto zero? [0 1 2 3 4 5 6]) [0])) 315 | (is (= (m/take-upto zero? [1 2 3 4 5 6 7]) [1 2 3 4 5 6 7]))) 316 | 317 | (testing "tranducers" 318 | (is (= (into [] (m/take-upto zero?) [1 2 3 0 4 5 6]) [1 2 3 0])) 319 | (is (= (into [] (m/take-upto zero?) [0 1 2 3 4 5 6]) [0])) 320 | (is (= (into [] (m/take-upto zero?) [1 2 3 4 5 6 7]) [1 2 3 4 5 6 7])) 321 | (is (= (transduce (m/take-upto zero?) 322 | (completing (fn [_ x] (reduced x))) 323 | nil 324 | [0 1 2]) 325 | 0)))) 326 | 327 | (deftest test-drop-upto 328 | (testing "sequences" 329 | (is (= (m/drop-upto zero? [1 2 3 0 4 5 6]) [4 5 6])) 330 | (is (= (m/drop-upto zero? [0 1 2 3 4 5 6]) [1 2 3 4 5 6])) 331 | (is (= (m/drop-upto zero? [1 2 3 4 5 6 7]) []))) 332 | 333 | (testing "transducers" 334 | (is (= (into [] (m/drop-upto zero?) [1 2 3 0 4 5 6]) [4 5 6])) 335 | (is (= (into [] (m/drop-upto zero?) [0 1 2 3 4 5 6]) [1 2 3 4 5 6])) 336 | (is (= (into [] (m/drop-upto zero?) [1 2 3 4 5 6 7]) [])))) 337 | 338 | (deftest test-partition-after 339 | (testing "sequences" 340 | (is (= (m/partition-after identity nil) '())) 341 | (is (= (m/partition-after even? [1 3 5 6 8 9 10 11]) '((1 3 5 6) (8) (9 10) (11)))) 342 | (is (= (m/partition-after even? [2 4 6 8 10]) '((2) (4) (6) (8) (10)))) 343 | (is (= (m/partition-after even? [1 3 5 7 9]) '((1 3 5 7 9))))) 344 | 345 | (testing "transducers" 346 | (is (= (transduce (m/partition-after identity) conj nil) [])) 347 | (is (= (transduce (m/partition-after even?) conj [1 3 5 6 8 9 10 11]) 348 | [[1 3 5 6] [8] [9 10] [11]])) 349 | (is (= (sequence (m/partition-after even?) [2 4 6 8 10]) '([2] [4] [6] [8] [10]))) 350 | (is (= (into [] (m/partition-after even?) [1 3 5 7 9]) [[1 3 5 7 9]])) 351 | (is (= (transduce (m/partition-after even?) 352 | (completing (fn [coll x] 353 | (cond-> (conj coll x) (= [8] x) reduced))) 354 | [] 355 | [1 3 5 6 8 9]) 356 | [[1 3 5 6] [8]])))) 357 | 358 | (deftest test-partition-before 359 | (testing "sequences" 360 | (is (= (m/partition-before identity nil) '())) 361 | (is (= (m/partition-before even? [1 3 5 6 8 9 10 11]) '((1 3 5) (6) (8 9) (10 11)))) 362 | (is (= (m/partition-before even? [2 4 6 8 10]) '((2) (4) (6) (8) (10)))) 363 | (is (= (m/partition-before even? [1 3 5 7 9]) '((1 3 5 7 9))))) 364 | 365 | (testing "transducers" 366 | (is (= (transduce (m/partition-before identity) conj nil) [])) 367 | (is (= (transduce (m/partition-before even?) conj [1 3 5 6 8 9 10 11]) 368 | [[1 3 5] [6] [8 9] [10 11]])) 369 | (is (= (sequence (m/partition-before even?) [2 4 6 8 10]) '([2] [4] [6] [8] [10]))) 370 | (is (= (into [] (m/partition-before even?) [1 3 5 7 9]) [[1 3 5 7 9]])) 371 | (is (= (transduce (m/partition-before even?) 372 | (completing (fn [coll x] 373 | (cond-> (conj coll x) (= [6] x) reduced))) 374 | [] 375 | [1 3 5 6 8 9]) 376 | [[1 3 5] [6]])))) 377 | 378 | (deftest test-partition-between 379 | (testing "sequences" 380 | (is (= (m/partition-between = nil) '())) 381 | (is (= (m/partition-between < [1]) '((1)))) 382 | (is (= (m/partition-between < [1 1 2 2 1 1 3 3 3]) 383 | '((1 1) (2 2 1 1) (3 3 3)))) 384 | (is (= (m/partition-between < [1 2 3 4]) '((1) (2) (3) (4)))) 385 | (is (= (m/partition-between < [4 3 2 1]) '((4 3 2 1))))) 386 | 387 | (testing "transducers" 388 | (is (= (transduce (m/partition-between <) conj nil) [])) 389 | (is (= (transduce (m/partition-between <) conj [1]) [[1]])) 390 | (is (= (transduce (m/partition-between <) conj [1 1 2 2 1 1 3 3 3]) 391 | [[1 1] [2 2 1 1] [3 3 3]])) 392 | (is (= (sequence (m/partition-between <) [1 2 3 4]) '([1] [2] [3] [4]))) 393 | (is (= (into [] (m/partition-between <) [4 3 2 1]) [[4 3 2 1]])) 394 | (is (= (transduce (m/partition-between <) 395 | (completing (fn [coll x] 396 | (cond-> (conj coll x) (= [8] x) reduced))) 397 | [] 398 | [1 2 3 2 8 9]) 399 | [[1] [2] [3 2] [8]])))) 400 | 401 | (deftest test-window 402 | (testing "sequences" 403 | (is (= (m/window 2 nil) '())) 404 | (is (= (m/window 2 [:a]) '((:a)))) 405 | (is (= (m/window 2 [:a :b]) '((:a) (:a :b)))) 406 | (is (= (m/window 2 [:a :b :c]) '((:a) (:a :b) (:b :c)))) 407 | (is (= (take 3 (m/window 0 [:a :b :c])) '(() () ()))) 408 | (is (= (m/window 10 [:a :b :c]) '((:a) (:a :b) (:a :b :c))))) 409 | (testing "transducers" 410 | (is (= (transduce (m/window 2) conj nil) [])) 411 | (is (= (transduce (m/window 2) conj [:a]) [[:a]])) 412 | (is (= (transduce (m/window 2) conj [:a :b]) [[:a] [:a :b]])) 413 | (is (= (transduce (m/window 2) conj [:a :b :c]) [[:a] [:a :b] [:b :c]])) 414 | (is (= (transduce (m/window 0) conj [:a :b :c]) [[] [] []])) 415 | (is (= (transduce (m/window 10) conj [:a :b :c]) [[:a] [:a :b] [:a :b :c]])) 416 | (is (= (transduce (m/window 2) 417 | (completing (fn [coll x] 418 | (cond-> (conj coll x) (= [:a :b] x) reduced))) 419 | [] 420 | [:a :b :c :d]) 421 | [[:a] [:a :b]])))) 422 | 423 | (deftest test-indexed 424 | (testing "sequences" 425 | (is (= (m/indexed [:a :b :c :d]) 426 | [[0 :a] [1 :b] [2 :c] [3 :d]])) 427 | (is (= (m/indexed []) 428 | []))) 429 | 430 | (testing "transducers" 431 | (is (= (into [] (m/indexed) [:a :b :c :d]) 432 | [[0 :a] [1 :b] [2 :c] [3 :d]])) 433 | (is (= (into [] (m/indexed) []) 434 | [])))) 435 | 436 | (deftest test-insert-nth 437 | (testing "sequences" 438 | (is (= (m/insert-nth 0 :a [1 2 3 4]) [:a 1 2 3 4])) 439 | (is (= (m/insert-nth 1 :a [1 2 3 4]) [1 :a 2 3 4])) 440 | (is (= (m/insert-nth 3 :a [1 2 3 4]) [1 2 3 :a 4])) 441 | (is (= (m/insert-nth 4 :a [1 2 3 4]) [1 2 3 4 :a]))) 442 | 443 | (testing "transducers" 444 | (is (= (into [] (m/insert-nth 0 :a) [1 2 3 4]) [:a 1 2 3 4])) 445 | (is (= (into [] (m/insert-nth 1 :a) [1 2 3 4]) [1 :a 2 3 4])) 446 | (is (= (into [] (m/insert-nth 3 :a) [1 2 3 4]) [1 2 3 :a 4])) 447 | (is (= (into [] (m/insert-nth 4 :a) [1 2 3 4]) [1 2 3 4 :a])))) 448 | 449 | (deftest test-remove-nth 450 | (testing "sequences" 451 | (is (= (m/remove-nth 0 [1 2 3 4]) [2 3 4])) 452 | (is (= (m/remove-nth 1 [1 2 3 4]) [1 3 4])) 453 | (is (= (m/remove-nth 3 [1 2 3 4]) [1 2 3]))) 454 | 455 | (testing "transducers" 456 | (is (= (into [] (m/remove-nth 0) [1 2 3 4]) [2 3 4])) 457 | (is (= (into [] (m/remove-nth 1) [1 2 3 4]) [1 3 4])) 458 | (is (= (into [] (m/remove-nth 3) [1 2 3 4]) [1 2 3])))) 459 | 460 | (deftest test-replace-nth 461 | (testing "sequences" 462 | (is (= (m/replace-nth 0 :a [1 2 3 4]) [:a 2 3 4])) 463 | (is (= (m/replace-nth 1 :a [1 2 3 4]) [1 :a 3 4])) 464 | (is (= (m/replace-nth 3 :a [1 2 3 4]) [1 2 3 :a]))) 465 | 466 | (testing "transducers" 467 | (is (= (into [] (m/replace-nth 0 :a) [1 2 3 4]) [:a 2 3 4])) 468 | (is (= (into [] (m/replace-nth 1 :a) [1 2 3 4]) [1 :a 3 4])) 469 | (is (= (into [] (m/replace-nth 3 :a) [1 2 3 4]) [1 2 3 :a])))) 470 | 471 | (deftest test-abs 472 | (is (= (m/abs -3) 3)) 473 | (is (= (m/abs 2) 2)) 474 | (is (= (m/abs -2.1) 2.1)) 475 | (is (= (m/abs 1.8) 1.8)) 476 | #?@(:clj [(is (= (m/abs -1/3) 1/3)) 477 | (is (= (m/abs 1/2) 1/2)) 478 | (is (= (m/abs 3N) 3N)) 479 | (is (= (m/abs -4N) 4N))])) 480 | 481 | (deftest test-deref-swap! 482 | (let [a (atom 0)] 483 | (is (= (m/deref-swap! a inc) 0)) 484 | (is (= @a 1)) 485 | (is (= (m/deref-swap! a inc) 1)) 486 | (is (= @a 2)))) 487 | 488 | (deftest test-deref-reset! 489 | (let [a (atom 0)] 490 | (is (= (m/deref-reset! a 3) 0)) 491 | (is (= @a 3)) 492 | (is (= (m/deref-reset! a 1) 3)) 493 | (is (= @a 1)))) 494 | 495 | (deftest test-ex-message 496 | (is (= (m/ex-message (ex-info "foo" {})) "foo")) 497 | (is (= (m/ex-message (new #?(:clj Exception :cljs js/Error) "bar")) "bar"))) 498 | 499 | (deftest test-ex-cause 500 | (let [cause (new #?(:clj Exception :cljs js/Error) "foo")] 501 | (is (= (m/ex-cause (ex-info "foo" {} cause)) cause)) 502 | #?(:clj (is (= (m/ex-cause (Exception. "foo" cause)) cause))))) 503 | 504 | (deftest test-uuid? 505 | (let [x #uuid "d1a4adfa-d9cf-4aa5-9f05-a15365d1bfa6"] 506 | (is (m/uuid? x)) 507 | (is (not (m/uuid? 2))) 508 | (is (not (m/uuid? (str x)))) 509 | (is (not (m/uuid? nil))))) 510 | 511 | (deftest test-uuid 512 | (let [x (m/uuid "d1a4adfa-d9cf-4aa5-9f05-a15365d1bfa6")] 513 | (is (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) x)) 514 | (is (= x #uuid "d1a4adfa-d9cf-4aa5-9f05-a15365d1bfa6")))) 515 | 516 | (deftest test-random-uuid 517 | (let [x (m/random-uuid) 518 | y (m/random-uuid)] 519 | (is (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) x)) 520 | (is (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) y)) 521 | (is (not= x y)))) 522 | 523 | (deftest test-regexp? 524 | (is (m/regexp? #"x")) 525 | (is (not (m/regexp? "x"))) 526 | (is (not (m/regexp? nil)))) 527 | 528 | (deftest test-index-of 529 | (is (nil? (m/index-of nil :a))) 530 | (is (nil? (m/index-of [] :a))) 531 | (is (nil? (m/index-of '() :a))) 532 | 533 | (is (nil? (m/index-of [:a] :b))) 534 | (is (nil? (m/index-of '(:a) :b))) 535 | 536 | (is (= 1 (m/index-of [:a :b :c :d] :b))) 537 | (is (= 2 (m/index-of '(:a :b :c :d) :c))) 538 | 539 | (is (= 1 (m/index-of (range 0 10) 1))) 540 | (is (= 1 (m/index-of (map str [:a :b]) ":b")))) 541 | 542 | (deftest test-find-in 543 | (is (nil? (m/find-in {} [:a]))) 544 | (is (nil? (m/find-in {} [:a :b]))) 545 | (is (nil? (m/find-in {:a {}} [:a :b]))) 546 | (is (= [:a 1] (m/find-in {:a 1} [:a]))) 547 | (is (= [:b 2] (m/find-in {:a {:b 2}} [:a :b]))) 548 | (is (= [:b {:c 3}] (m/find-in {:a {:b {:c 3}}} [:a :b]))) 549 | (is (= [:c 3] (m/find-in {:a {:b {:c 3}}} [:a :b :c])))) 550 | -------------------------------------------------------------------------------- /src/medley/core.cljc: -------------------------------------------------------------------------------- 1 | (ns medley.core 2 | "A small collection of useful, mostly pure functions that might not look out 3 | of place in the clojure.core namespace." 4 | (:refer-clojure :exclude [abs boolean? ex-cause ex-message random-uuid regexp? 5 | uuid uuid?])) 6 | 7 | (defn find-first 8 | "Finds the first item in a collection that matches a predicate. Returns a 9 | transducer when no collection is provided." 10 | ([pred] 11 | (fn [rf] 12 | (fn 13 | ([] (rf)) 14 | ([result] (rf result)) 15 | ([result x] 16 | (if (pred x) 17 | (ensure-reduced (rf result x)) 18 | result))))) 19 | ([pred coll] 20 | (reduce (fn [_ x] (when (pred x) (reduced x))) nil coll))) 21 | 22 | (defn dissoc-in 23 | "Dissociate a value in a nested associative structure, identified by a sequence 24 | of keys. Any collections left empty by the operation will be dissociated from 25 | their containing structures." 26 | ([m ks] 27 | (if-let [[k & ks] (seq ks)] 28 | (if (seq ks) 29 | (let [v (dissoc-in (get m k) ks)] 30 | (if (empty? v) 31 | (dissoc m k) 32 | (assoc m k v))) 33 | (dissoc m k)) 34 | m)) 35 | ([m ks & kss] 36 | (if-let [[ks' & kss] (seq kss)] 37 | (recur (dissoc-in m ks) ks' kss) 38 | (dissoc-in m ks)))) 39 | 40 | (defn- editable? [coll] 41 | #?(:clj (instance? clojure.lang.IEditableCollection coll) 42 | :cljs (satisfies? cljs.core/IEditableCollection coll))) 43 | 44 | (defn- assoc-some-transient! [m k v] 45 | (if (nil? v) m (assoc! m k v))) 46 | 47 | (defn assoc-some 48 | "Associates a key k, with a value v in a map m, if and only if v is not nil." 49 | ([m k v] 50 | (if (nil? v) m (assoc m k v))) 51 | ([m k v & kvs] 52 | (if (editable? m) 53 | (loop [acc (assoc-some-transient! (transient (or m {})) k v) 54 | kvs kvs] 55 | (if (next kvs) 56 | (recur (assoc-some-transient! acc (first kvs) (second kvs)) (nnext kvs)) 57 | (if (zero? (count acc)) 58 | m 59 | (with-meta (persistent! acc) (meta m))))) 60 | (loop [acc (assoc-some m k v) 61 | kvs kvs] 62 | (if (next kvs) 63 | (recur (assoc-some acc (first kvs) (second kvs)) (nnext kvs)) 64 | acc))))) 65 | 66 | (defn update-existing 67 | "Updates a value in a map given a key and a function, if and only if the key 68 | exists in the map. See: `clojure.core/update`." 69 | {:arglists '([m k f & args]) 70 | :added "1.1.0"} 71 | ([m k f] 72 | (if-let [kv (find m k)] (assoc m k (f (val kv))) m)) 73 | ([m k f x] 74 | (if-let [kv (find m k)] (assoc m k (f (val kv) x)) m)) 75 | ([m k f x y] 76 | (if-let [kv (find m k)] (assoc m k (f (val kv) x y)) m)) 77 | ([m k f x y z] 78 | (if-let [kv (find m k)] (assoc m k (f (val kv) x y z)) m)) 79 | ([m k f x y z & more] 80 | (if-let [kv (find m k)] (assoc m k (apply f (val kv) x y z more)) m))) 81 | 82 | (defn update-existing-in 83 | "Updates a value in a nested associative structure, if and only if the key 84 | path exists. See: `clojure.core/update-in`." 85 | {:added "1.3.0"} 86 | [m ks f & args] 87 | (let [up (fn up [m ks f args] 88 | (let [[k & ks] ks] 89 | (if-let [kv (find m k)] 90 | (if ks 91 | (assoc m k (up (val kv) ks f args)) 92 | (assoc m k (apply f (val kv) args))) 93 | m)))] 94 | (up m ks f args))) 95 | 96 | (defn- reduce-map [f coll] 97 | (let [coll' (if (record? coll) (into {} coll) coll)] 98 | (if (editable? coll') 99 | (persistent! (reduce-kv (f assoc!) (transient (empty coll')) coll')) 100 | (reduce-kv (f assoc) (empty coll') coll')))) 101 | 102 | (defn map-entry 103 | "Create a map entry for a key and value pair." 104 | [k v] 105 | #?(:clj (clojure.lang.MapEntry. k v) 106 | :cljs (cljs.core/MapEntry. k v nil))) 107 | 108 | (defn map-kv 109 | "Maps a function over the key/value pairs of an associative collection. Expects 110 | a function that takes two arguments, the key and value, and returns the new 111 | key and value as a collection of two elements." 112 | [f coll] 113 | (reduce-map (fn [xf] (fn [m k v] (let [[k v] (f k v)] (xf m k v)))) coll)) 114 | 115 | (defn map-keys 116 | "Maps a function over the keys of an associative collection." 117 | [f coll] 118 | (reduce-map (fn [xf] (fn [m k v] (xf m (f k) v))) coll)) 119 | 120 | (defn map-vals 121 | "Maps a function over the values of one or more associative collections. 122 | The function should accept number-of-colls arguments. Any keys which are not 123 | shared among all collections are ignored." 124 | ([f coll] 125 | (reduce-map (fn [xf] (fn [m k v] (xf m k (f v)))) coll)) 126 | ([f c1 & colls] 127 | (reduce-map 128 | (fn [xf] 129 | (fn [m k v] 130 | (if (every? #(contains? % k) colls) 131 | (xf m k (apply f v (map #(get % k) colls))) 132 | m))) 133 | c1))) 134 | 135 | (defn map-kv-keys 136 | "Maps a function over the key/value pairs of an associative collection, using 137 | the return of the function as the new key." 138 | {:added "1.2.0"} 139 | [f coll] 140 | (reduce-map (fn [xf] (fn [m k v] (xf m (f k v) v))) coll)) 141 | 142 | (defn map-kv-vals 143 | "Maps a function over the key/value pairs of an associative collection, using 144 | the return of the function as the new value." 145 | {:added "1.2.0"} 146 | [f coll] 147 | (reduce-map (fn [xf] (fn [m k v] (xf m k (f k v)))) coll)) 148 | 149 | (defn filter-kv 150 | "Returns a new associative collection of the items in coll for which 151 | `(pred (key item) (val item))` returns true." 152 | [pred coll] 153 | (reduce-map (fn [xf] (fn [m k v] (if (pred k v) (xf m k v) m))) coll)) 154 | 155 | (defn filter-keys 156 | "Returns a new associative collection of the items in coll for which 157 | `(pred (key item))` returns true." 158 | [pred coll] 159 | (reduce-map (fn [xf] (fn [m k v] (if (pred k) (xf m k v) m))) coll)) 160 | 161 | (defn filter-vals 162 | "Returns a new associative collection of the items in coll for which 163 | `(pred (val item))` returns true." 164 | [pred coll] 165 | (reduce-map (fn [xf] (fn [m k v] (if (pred v) (xf m k v) m))) coll)) 166 | 167 | (defn remove-kv 168 | "Returns a new associative collection of the items in coll for which 169 | `(pred (key item) (val item))` returns false." 170 | [pred coll] 171 | (filter-kv (complement pred) coll)) 172 | 173 | (defn remove-keys 174 | "Returns a new associative collection of the items in coll for which 175 | `(pred (key item))` returns false." 176 | [pred coll] 177 | (filter-keys (complement pred) coll)) 178 | 179 | (defn remove-vals 180 | "Returns a new associative collection of the items in coll for which 181 | `(pred (val item))` returns false." 182 | [pred coll] 183 | (filter-vals (complement pred) coll)) 184 | 185 | (defn queue 186 | "Creates an empty persistent queue, or one populated with a collection." 187 | ([] #?(:clj clojure.lang.PersistentQueue/EMPTY 188 | :cljs cljs.core/PersistentQueue.EMPTY)) 189 | ([coll] (into (queue) coll))) 190 | 191 | (defn queue? 192 | "Returns true if x implements clojure.lang.PersistentQueue." 193 | [x] 194 | (instance? #?(:clj clojure.lang.PersistentQueue 195 | :cljs cljs.core/PersistentQueue) x)) 196 | 197 | (defn boolean? 198 | "Returns true if x is a boolean." 199 | [x] 200 | #?(:clj (instance? Boolean x) 201 | :cljs (or (true? x) (false? x)))) 202 | 203 | (defn least 204 | "Return the least argument (as defined by the compare function) in O(n) time." 205 | {:arglists '([& xs])} 206 | ([] nil) 207 | ([a] a) 208 | ([a b] (if (neg? (compare a b)) a b)) 209 | ([a b & more] (reduce least (least a b) more))) 210 | 211 | (defn least-by 212 | "Return the argument for which (keyfn x) is least. Determined by the compare 213 | function in O(n) time. Prefer `clojure.core/min-key` if keyfn returns numbers." 214 | {:arglists '([keyfn & xs]) 215 | :added "1.6.0"} 216 | ([_] nil) 217 | ([_ x] x) 218 | ([keyfn x y] (if (neg? (compare (keyfn x) (keyfn y))) x y)) 219 | ([keyfn x y & more] 220 | (let [kx (keyfn x) ky (keyfn y) 221 | [v kv] (if (neg? (compare kx ky)) [x kx] [y ky])] 222 | (loop [v v kv kv more more] 223 | (if more 224 | (let [w (first more) 225 | kw (keyfn w)] 226 | (if (pos? (compare kw kv)) 227 | (recur v kv (next more)) 228 | (recur w kw (next more)))) 229 | v))))) 230 | 231 | (defn greatest 232 | "Find the greatest argument (as defined by the compare function) in O(n) time." 233 | {:arglists '([& xs])} 234 | ([] nil) 235 | ([a] a) 236 | ([a b] (if (pos? (compare a b)) a b)) 237 | ([a b & more] (reduce greatest (greatest a b) more))) 238 | 239 | (defn greatest-by 240 | "Return the argument for which (keyfn x) is greatest. Determined by the compare 241 | function in O(n) time. Prefer `clojure.core/max-key` if keyfn returns numbers." 242 | {:arglists '([keyfn & xs]) 243 | :added "1.6.0"} 244 | ([_] nil) 245 | ([_ x] x) 246 | ([keyfn x y] (if (pos? (compare (keyfn x) (keyfn y))) x y)) 247 | ([keyfn x y & more] 248 | (let [kx (keyfn x) ky (keyfn y) 249 | [v kv] (if (pos? (compare kx ky)) [x kx] [y ky])] 250 | (loop [v v kv kv more more] 251 | (if more 252 | (let [w (first more) 253 | kw (keyfn w)] 254 | (if (neg? (compare kw kv)) 255 | (recur v kv (next more)) 256 | (recur w kw (next more)))) 257 | v))))) 258 | 259 | (defn join 260 | "Lazily concatenates a collection of collections into a flat sequence." 261 | {:added "1.1.0"} 262 | [colls] 263 | (lazy-seq 264 | (when-let [s (seq colls)] 265 | (concat (first s) (join (rest s)))))) 266 | 267 | (defn deep-merge 268 | "Recursively merges maps together. If all the maps supplied have nested maps 269 | under the same keys, these nested maps are merged. Otherwise the value is 270 | overwritten, as in `clojure.core/merge`." 271 | {:arglists '([& maps]) 272 | :added "1.1.0"} 273 | ([]) 274 | ([a] a) 275 | ([a b] 276 | (when (or a b) 277 | (letfn [(merge-entry [m e] 278 | (let [k (key e) 279 | v' (val e)] 280 | (if (contains? m k) 281 | (assoc m k (let [v (get m k)] 282 | (if (and (map? v) (map? v')) 283 | (deep-merge v v') 284 | v'))) 285 | (assoc m k v'))))] 286 | (reduce merge-entry (or a {}) (seq b))))) 287 | ([a b & more] 288 | (reduce deep-merge (or a {}) (cons b more)))) 289 | 290 | (defn mapply 291 | "Applies a function f to the argument list formed by concatenating 292 | everything but the last element of args with the last element of 293 | args. This is useful for applying a function that accepts keyword 294 | arguments to a map." 295 | {:arglists '([f & args])} 296 | ([f m] (apply f (apply concat m))) 297 | ([f a & args] (apply f a (apply concat (butlast args) (last args))))) 298 | 299 | (defn collate-by 300 | "Similar to `clojure.core/group-by`, this groups values in a collection, 301 | coll, based on the return value of a function, keyf applied to each element. 302 | 303 | Unlike `group-by`, the values of the map are constructed via an initf and 304 | collatef function. The initf function is applied to the first element 305 | matched by keyf, and defaults to the identity function. The collatef function 306 | takes the result of initf and the next keyed element, and produces a new 307 | value. 308 | 309 | To put this in context, the `group-by` function can be defined as: 310 | 311 | (defn group-by [f coll] 312 | (collate-by f conj vector coll)) 313 | 314 | While the `medley.core/index-by` function can be (and is) defined as: 315 | 316 | (defn index-by [f coll] 317 | (collate-by f (fn [_ x] x) coll))" 318 | {:added "1.8.0"} 319 | ([keyf collatef coll] 320 | (collate-by keyf collatef identity coll)) 321 | ([keyf collatef initf coll] 322 | (persistent! 323 | (reduce (fn [m v] 324 | (let [k (keyf v)] 325 | (assoc! m k #?(:clj (if-let [kv (find m k)] 326 | (collatef (val kv) v) 327 | (initf v)) 328 | :cljs (if (contains? m k) 329 | (collatef (get m k) v) 330 | (initf v)))))) 331 | (transient {}) 332 | coll)))) 333 | 334 | (defn index-by 335 | "Returns a map of the elements of coll keyed by the result of f on each 336 | element. The value at each key will be the last element in coll associated 337 | with that key. This function is similar to `clojure.core/group-by`, except 338 | that elements with the same key are overwritten, rather than added to a 339 | vector of values." 340 | {:added "1.2.0"} 341 | [f coll] 342 | #_(persistent! (reduce #(assoc! %1 (f %2) %2) (transient {}) coll)) 343 | (collate-by f (fn [_ x] x) coll)) 344 | 345 | (defn interleave-all 346 | "Returns a lazy seq of the first item in each coll, then the second, etc. 347 | Unlike `clojure.core/interleave`, the returned seq contains all items in the 348 | supplied collections, even if the collections are different sizes." 349 | {:arglists '([& colls])} 350 | ([] ()) 351 | ([c1] (lazy-seq c1)) 352 | ([c1 c2] 353 | (lazy-seq 354 | (let [s1 (seq c1), s2 (seq c2)] 355 | (if (and s1 s2) 356 | (cons (first s1) (cons (first s2) (interleave-all (rest s1) (rest s2)))) 357 | (or s1 s2))))) 358 | ([c1 c2 & colls] 359 | (lazy-seq 360 | (let [ss (keep seq (conj colls c2 c1))] 361 | (when (seq ss) 362 | (concat (map first ss) (apply interleave-all (map rest ss)))))))) 363 | 364 | (defn distinct-by 365 | "Returns a lazy sequence of the elements of coll, removing any elements that 366 | return duplicate values when passed to a function f. Returns a stateful 367 | transducer when no collection is provided." 368 | ([f] 369 | (fn [rf] 370 | (let [seen (volatile! #{})] 371 | (fn 372 | ([] (rf)) 373 | ([result] (rf result)) 374 | ([result x] 375 | (let [fx (f x)] 376 | (if (contains? @seen fx) 377 | result 378 | (do (vswap! seen conj fx) 379 | (rf result x))))))))) 380 | ([f coll] 381 | (let [step (fn step [xs seen] 382 | (lazy-seq 383 | ((fn [[x :as xs] seen] 384 | (when-let [s (seq xs)] 385 | (let [fx (f x)] 386 | (if (contains? seen fx) 387 | (recur (rest s) seen) 388 | (cons x (step (rest s) (conj seen fx))))))) 389 | xs seen)))] 390 | (step coll #{})))) 391 | 392 | (defn dedupe-by 393 | "Returns a lazy sequence of the elements of coll, removing any **consecutive** 394 | elements that return duplicate values when passed to a function f. Returns a 395 | stateful transducer when no collection is provided." 396 | ([f] 397 | (fn [rf] 398 | (let [pv (volatile! ::none)] 399 | (fn 400 | ([] (rf)) 401 | ([result] (rf result)) 402 | ([result x] 403 | (let [prior @pv 404 | fx (f x)] 405 | (vreset! pv fx) 406 | (if (= prior fx) 407 | result 408 | (rf result x)))))))) 409 | ([f coll] 410 | (sequence (dedupe-by f) coll))) 411 | 412 | (defn take-upto 413 | "Returns a lazy sequence of successive items from coll up to and including 414 | the first item for which `(pred item)` returns true. Returns a transducer 415 | when no collection is provided." 416 | ([pred] 417 | (fn [rf] 418 | (fn 419 | ([] (rf)) 420 | ([result] (rf result)) 421 | ([result x] 422 | (let [result (rf result x)] 423 | (if (pred x) 424 | (ensure-reduced result) 425 | result)))))) 426 | ([pred coll] 427 | (lazy-seq 428 | (when-let [s (seq coll)] 429 | (let [x (first s)] 430 | (cons x (when-not (pred x) (take-upto pred (rest s))))))))) 431 | 432 | (defn drop-upto 433 | "Returns a lazy sequence of the items in coll starting *after* the first item 434 | for which `(pred item)` returns true. Returns a stateful transducer when no 435 | collection is provided." 436 | ([pred] 437 | (fn [rf] 438 | (let [dv (volatile! true)] 439 | (fn 440 | ([] (rf)) 441 | ([result] (rf result)) 442 | ([result x] 443 | (if @dv 444 | (do (when (pred x) (vreset! dv false)) result) 445 | (rf result x))))))) 446 | ([pred coll] 447 | (rest (drop-while (complement pred) coll)))) 448 | 449 | (defn partition-between 450 | "Applies pred to successive values in coll, splitting it each time `(pred 451 | prev-item item)` returns logical true. Returns a lazy seq of partitions. 452 | Returns a stateful transducer when no collection is provided." 453 | {:added "1.7.0"} 454 | ([pred] 455 | (fn [rf] 456 | (let [part #?(:clj (java.util.ArrayList.) :cljs (array-list)) 457 | prev (volatile! ::none)] 458 | (fn 459 | ([] (rf)) 460 | ([result] 461 | (rf (if (.isEmpty part) 462 | result 463 | (let [v (vec (.toArray part))] 464 | (.clear part) 465 | (unreduced (rf result v)))))) 466 | ([result input] 467 | (let [p @prev] 468 | (vreset! prev input) 469 | (if (or (#?(:clj identical? :cljs keyword-identical?) p ::none) 470 | (not (pred p input))) 471 | (do (.add part input) result) 472 | (let [v (vec (.toArray part))] 473 | (.clear part) 474 | (let [ret (rf result v)] 475 | (when-not (reduced? ret) 476 | (.add part input)) 477 | ret))))))))) 478 | ([pred coll] 479 | (lazy-seq 480 | (letfn [(take-part [prev coll] 481 | (lazy-seq 482 | (when-let [[x & xs] (seq coll)] 483 | (when-not (pred prev x) 484 | (cons x (take-part x xs))))))] 485 | (when-let [[x & xs] (seq coll)] 486 | (let [run (take-part x xs)] 487 | (cons (cons x run) 488 | (partition-between pred 489 | (lazy-seq (drop (count run) xs)))))))))) 490 | 491 | (defn partition-after 492 | "Returns a lazy sequence of partitions, splitting after `(pred item)` returns 493 | true. Returns a stateful transducer when no collection is provided." 494 | {:added "1.5.0"} 495 | ([pred] 496 | (partition-between (fn [x _] (pred x)))) 497 | ([pred coll] 498 | (partition-between (fn [x _] (pred x)) coll))) 499 | 500 | (defn partition-before 501 | "Returns a lazy sequence of partitions, splitting before `(pred item)` returns 502 | true. Returns a stateful transducer when no collection is provided." 503 | {:added "1.5.0"} 504 | ([pred] 505 | (partition-between (fn [_ x] (pred x)))) 506 | ([pred coll] 507 | (partition-between (fn [_ x] (pred x)) coll))) 508 | 509 | (defn window 510 | "A sliding window, returning a lazy sequence of partitions, containing each 511 | element and n-1 preceeding elements, when present. Therefore partitions at the 512 | start may contain fewer items than the rest. Returns a stateful transducer 513 | when no collection is provided. For a sliding window containing each element 514 | and n-1 _following_ elements, use `clojure.core/partition` with a `step` size 515 | of 1." 516 | {:added "1.9.0"} 517 | ([n] 518 | (fn [rf] 519 | (let [part #?(:clj (java.util.ArrayList. n) :cljs (array))] 520 | (fn 521 | ([] (rf)) 522 | ([result] (rf result)) 523 | ([result x] 524 | #?(:clj (.add part x) :cljs (.push part x)) 525 | (when (< n #?(:clj (.size part) :cljs (.-length part))) 526 | #?(:clj (.remove part 0) :cljs (.shift part))) 527 | (rf result (vec #?(:clj (.toArray part) :cljs (.slice part))))))))) 528 | ([n coll] 529 | (letfn [(part [part-n coll] 530 | (let [run (doall (take part-n coll))] 531 | (lazy-seq 532 | (when (== part-n (count run)) 533 | (cons run 534 | (part (min n (inc part-n)) 535 | (if (== n part-n) (rest coll) coll)))))))] 536 | (part (min 1 n) coll)))) 537 | 538 | (defn indexed 539 | "Returns an ordered, lazy sequence of vectors `[index item]`, where item is a 540 | value in coll, and index its position starting from zero. Returns a stateful 541 | transducer when no collection is provided." 542 | ([] 543 | (fn [rf] 544 | (let [i (volatile! -1)] 545 | (fn 546 | ([] (rf)) 547 | ([result] (rf result)) 548 | ([result x] 549 | (rf result [(vswap! i inc) x])))))) 550 | ([coll] 551 | (map-indexed vector coll))) 552 | 553 | (defn insert-nth 554 | "Returns a lazy sequence of the items in coll, with a new item inserted at 555 | the supplied index, followed by all subsequent items of the collection. Runs 556 | in O(n) time. Returns a stateful transducer when no collection is provided." 557 | {:added "1.2.0"} 558 | ([index item] 559 | (fn [rf] 560 | (let [idx (volatile! (inc index))] 561 | (fn 562 | ([] (rf)) 563 | ([result] 564 | (if (= @idx 1) 565 | (rf (rf result item)) 566 | (rf result))) 567 | ([result x] 568 | (if (zero? (vswap! idx dec)) 569 | (rf (rf result item) x) 570 | (rf result x))))))) 571 | ([index item coll] 572 | (lazy-seq 573 | (if (zero? index) 574 | (cons item coll) 575 | (when (seq coll) 576 | (cons (first coll) (insert-nth (dec index) item (rest coll)))))))) 577 | 578 | (defn remove-nth 579 | "Returns a lazy sequence of the items in coll, except for the item at the 580 | supplied index. Runs in O(n) time. Returns a stateful transducer when no 581 | collection is provided." 582 | {:added "1.2.0"} 583 | ([index] 584 | (fn [rf] 585 | (let [idx (volatile! (inc index))] 586 | (fn 587 | ([] (rf)) 588 | ([result] (rf result)) 589 | ([result x] 590 | (if (zero? (vswap! idx dec)) 591 | result 592 | (rf result x))))))) 593 | ([index coll] 594 | (lazy-seq 595 | (if (zero? index) 596 | (rest coll) 597 | (when (seq coll) 598 | (cons (first coll) (remove-nth (dec index) (rest coll)))))))) 599 | 600 | (defn replace-nth 601 | "Returns a lazy sequence of the items in coll, with a new item replacing the 602 | item at the supplied index. Runs in O(n) time. Returns a stateful transducer 603 | when no collection is provided." 604 | {:added "1.2.0"} 605 | ([index item] 606 | (fn [rf] 607 | (let [idx (volatile! (inc index))] 608 | (fn 609 | ([] (rf)) 610 | ([result] (rf result)) 611 | ([result x] 612 | (if (zero? (vswap! idx dec)) 613 | (rf result item) 614 | (rf result x))))))) 615 | ([index item coll] 616 | (lazy-seq 617 | (if (zero? index) 618 | (cons item (rest coll)) 619 | (when (seq coll) 620 | (cons (first coll) (replace-nth (dec index) item (rest coll)))))))) 621 | 622 | (defn abs 623 | "Returns the absolute value of a number." 624 | [x] 625 | (if (neg? x) (- x) x)) 626 | 627 | (defn deref-swap! 628 | "Atomically swaps the value of the atom to be `(apply f x args)`, where x is 629 | the current value of the atom, then returns the original value of the atom. 630 | This function therefore acts like an atomic `deref` then `swap!`." 631 | {:arglists '([atom f & args])} 632 | ([atom f] 633 | #?(:clj (loop [] 634 | (let [value @atom] 635 | (if (compare-and-set! atom value (f value)) 636 | value 637 | (recur)))) 638 | :cljs (let [value @atom] 639 | (reset! atom (f value)) 640 | value))) 641 | ([atom f & args] 642 | (deref-swap! atom #(apply f % args)))) 643 | 644 | (defn deref-reset! 645 | "Sets the value of the atom without regard for the current value, then returns 646 | the original value of the atom. See also: [[deref-swap!]]." 647 | [atom newval] 648 | (deref-swap! atom (constantly newval))) 649 | 650 | (defn ex-message 651 | "Returns the message attached to the given Error/Throwable object. For all 652 | other types returns nil. Same as `cljs.core/ex-message` except it works for 653 | Clojure as well as ClojureScript." 654 | [ex] 655 | #?(:clj (when (instance? Throwable ex) (.getMessage ^Throwable ex)) 656 | :cljs (cljs.core/ex-message ex))) 657 | 658 | (defn ex-cause 659 | "Returns the cause attached to the given ExceptionInfo/Throwable object. For 660 | all other types returns nil. Same as `cljs.core/ex-cause` except it works for 661 | Clojure as well as ClojureScript." 662 | [ex] 663 | #?(:clj (when (instance? Throwable ex) (.getCause ^Throwable ex)) 664 | :cljs (cljs.core/ex-cause ex))) 665 | 666 | (defn uuid? 667 | "Returns true if the value is a UUID." 668 | [x] 669 | (instance? #?(:clj java.util.UUID :cljs cljs.core/UUID) x)) 670 | 671 | (defn uuid 672 | "Returns a UUID generated from the supplied string. Same as `cljs.core/uuid` 673 | in ClojureScript, while in Clojure it returns a `java.util.UUID` object." 674 | [s] 675 | #?(:clj (java.util.UUID/fromString s) 676 | :cljs (cljs.core/uuid s))) 677 | 678 | (defn random-uuid 679 | "Generates a new random UUID. Same as `cljs.core/random-uuid` except it works 680 | for Clojure as well as ClojureScript." 681 | [] 682 | #?(:clj (java.util.UUID/randomUUID) 683 | :cljs (cljs.core/random-uuid))) 684 | 685 | (defn regexp? 686 | "Returns true if the value is a regular expression." 687 | {:added "1.4.0"} 688 | [x] 689 | (instance? #?(:clj java.util.regex.Pattern :cljs js/RegExp) x)) 690 | 691 | (defn index-of 692 | "Returns the index of the first occurrence of the item in the sequential 693 | collection coll, or nil if not found." 694 | {:added "1.9.0"} 695 | [^java.util.List coll item] 696 | (when (some? coll) 697 | (let [index (.indexOf coll item)] 698 | (when-not (neg? index) index)))) 699 | 700 | (defn find-in 701 | "Similar to `clojure.core/find`, except that it finds a key/value pair in an 702 | nested associate structure `m`, given a sequence of keys `ks`. See also: 703 | `clojure.core/get-in`." 704 | {:added "1.9.0"} 705 | [m ks] 706 | (if (next ks) 707 | (-> (get-in m (butlast ks)) (find (last ks))) 708 | (find m (first ks)))) 709 | --------------------------------------------------------------------------------