├── .circleci └── config.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── kaocha ├── deps.edn ├── package-lock.json ├── package.json ├── pom.xml ├── src └── mate │ ├── core.cljc │ ├── io.cljc │ └── re_frame.cljc ├── test-resource └── public │ └── foobar.txt ├── test └── mate │ ├── core_test.cljc │ ├── io_test.cljc │ ├── re_frame_test.cljc │ ├── test-resource.json │ └── test-resource.txt └── tests.edn /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/clojure:tools-deps-1.11.1.1113-bullseye-node 6 | working_directory: ~/mate 7 | steps: 8 | - checkout: 9 | path: ~/mate 10 | - restore_cache: 11 | keys: 12 | - 'clj-v1-{{ checksum "deps.edn" }}-{{ checksum "package-lock.json" }}' 13 | - 'clj-v1' 14 | - run: npm ci 15 | - run: mkdir -p test-results 16 | - run: bin/kaocha --plugin kaocha.plugin/junit-xml --junit-xml-file test-results/kaocha/results.xml 17 | - store_test_results: 18 | path: test-results 19 | - save_cache: 20 | key: 'clj-v1-{{checksum "deps.edn"}}-{{ checksum "package-lock.json" }}' 21 | paths: 22 | - ~/.m2 23 | - ~/.cljs/.aot_cache 24 | - ~/node_modules 25 | - ~/.gitlibs 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cljs_node_repl/ 2 | .cpcache/ 3 | .idea/ 4 | .nrepl-port 5 | *.iml 6 | node_modules 7 | out/ 8 | *.jar 9 | 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Unreleased 2 | 3 | - New macros `mate.core/apply->`, `mate.core/apply->>` and theirs tests. 4 | - New function `mate.core/mapcat-indexed` and its tests. 5 | - New functions `mate.core/partial->`, `mate.core/partial->>` and their tests. 6 | 7 | ## v0.0.9 8 | 9 | ### Renamed (breaking change) 10 | 11 | - `indexed-re-find` -> `re-find-indexed` for consistency with `map-indexed` and `seq-indexed`. 12 | - `indexed-re-group` -> `re-group-indexed` for the same reason. 13 | 14 | ## v0.0.8 15 | 16 | ### Added 17 | 18 | - `re-with-flags` (Cljs) 19 | - `indexed-re-find` (Clj & Cljs) 20 | - `indexed-re-groups` (Clj). 21 | 22 | ## v0.0.7 23 | 24 | ### Added 25 | 26 | - Thread macro `if->`. 27 | - Thread macro `let->`. 28 | 29 | ## v0.0.6 30 | 31 | ### Added 32 | 33 | - Added tests. 34 | - Added n-arity on function `mate.re-frame/conj-fx`. 35 | 36 | ## v0.0.5 37 | 38 | ### Added 39 | 40 | - New function `mate.core/ungroup-keys` and its test. 41 | - New functions `mate.re-frame/into-fx` and `mate.re-frame/into-fx-using-db`, and some tests. 42 | 43 | ## v0.0.4 44 | 45 | ### Added 46 | 47 | Added namespace `mate.re-frame` with utility functions useful for composing functions 48 | on Re-frame effects via the `->` thread macro. 49 | 50 | See `/test/mate/re_frame_test.cljc` for example of usage. 51 | 52 | ## v0.0.3 53 | 54 | ### Added 55 | 56 | In `mate.io`: 57 | - `implies` 58 | 59 | ### Fixed 60 | 61 | - Function `inline-resource*` should be within `#?(:clj ...)`. 62 | 63 | ## v0.0.2 64 | 65 | ### Added 66 | 67 | In `mate.io`: 68 | - `inline-resource` 69 | 70 | ## v0.0.1 71 | 72 | ### Added 73 | 74 | In `mate.core`: 75 | - `seq-indexed` 76 | - `comp->` 77 | - `group-by` 78 | - `index-by` 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Mate [![CircleCI](https://circleci.com/gh/green-coder/mate.svg?style=svg)](https://circleci.com/gh/green-coder/mate) 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/taipei.404/mate.svg)](https://clojars.org/taipei.404/mate) 4 | [![Cljdoc badge](https://cljdoc.org/badge/taipei.404/mate)](https://cljdoc.org/d/taipei.404/mate/CURRENT) 5 | [![Clojars download_badge](https://img.shields.io/clojars/dt/taipei.404/mate?color=opal)](https://clojars.org/taipei.404/mate) 6 | 7 | Useful functions, some of which I wish were in the Clojure standard library. 8 | 9 | Examples of usage: 10 | - [test/mate/core_test.cljc](test/mate/core_test.cljc) 11 | - [test/mate/io_test.cljc](test/mate/io_test.cljc) 12 | - [test/mate/re_frame_test.cljc](test/mate/re_frame_test.cljc) 13 | 14 | ## License 15 | 16 | This project is distributed under the Eclipse Public License v2.0. 17 | -------------------------------------------------------------------------------- /bin/kaocha: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | clojure -M:test -m kaocha.runner "$@" 3 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {} ;; This part intentionally left empty 3 | :aliases {:dev {:extra-deps {org.clojure/clojure {:mvn/version "1.11.1"} 4 | org.clojure/clojurescript {:mvn/version "1.11.60"}}} 5 | :test {:extra-paths ["test" "test-resource"] 6 | :extra-deps {lambdaisland/kaocha {:mvn/version "1.71.1119"} 7 | lambdaisland/kaocha-cljs {:mvn/version "1.4.130"} 8 | lambdaisland/kaocha-junit-xml {:mvn/version "1.17.101"} 9 | org.clojure/test.check {:mvn/version "1.1.1"} 10 | org.clojure/data.json {:mvn/version "2.4.0"}}} 11 | 12 | ; clojure -M:outdated --upgrade 13 | :outdated {:extra-deps {com.github.liquidz/antq {:mvn/version "2.1.946"}} 14 | :main-opts ["-m" "antq.core"]} 15 | 16 | :depstar {:replace-deps {com.github.seancorfield/depstar {:mvn/version "2.1.303"}} 17 | :exec-fn hf.depstar/jar 18 | :exec-args {:sync-pom true 19 | :group-id "taipei.404" 20 | :artifact-id "mate" 21 | :version "0.0.9" 22 | :jar "mate.jar"}}}} 23 | ;; Memo for deploying a new release: 24 | ;; - Change the version above, then build the jar: 25 | ;; clojure -X:depstar 26 | ;; - add a tag "v0.x.y" to the latest commit and push to repo 27 | ;; - deploy: 28 | ;; mvn deploy:deploy-file -Dfile=mate.jar -DpomFile=pom.xml -DrepositoryId=clojars -Durl=https://clojars.org/repo/ 29 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mate", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "devDependencies": { 8 | "isomorphic-ws": "^4.0.1", 9 | "ws": "^7.0.1" 10 | } 11 | }, 12 | "node_modules/isomorphic-ws": { 13 | "version": "4.0.1", 14 | "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", 15 | "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", 16 | "dev": true, 17 | "peerDependencies": { 18 | "ws": "*" 19 | } 20 | }, 21 | "node_modules/ws": { 22 | "version": "7.5.7", 23 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", 24 | "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", 25 | "dev": true, 26 | "engines": { 27 | "node": ">=8.3.0" 28 | }, 29 | "peerDependencies": { 30 | "bufferutil": "^4.0.1", 31 | "utf-8-validate": "^5.0.2" 32 | }, 33 | "peerDependenciesMeta": { 34 | "bufferutil": { 35 | "optional": true 36 | }, 37 | "utf-8-validate": { 38 | "optional": true 39 | } 40 | } 41 | } 42 | }, 43 | "dependencies": { 44 | "isomorphic-ws": { 45 | "version": "4.0.1", 46 | "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", 47 | "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", 48 | "dev": true, 49 | "requires": {} 50 | }, 51 | "ws": { 52 | "version": "7.5.7", 53 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", 54 | "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", 55 | "dev": true, 56 | "requires": {} 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "isomorphic-ws": "^4.0.1", 4 | "ws": "^7.0.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | jar 5 | taipei.404 6 | mate 7 | 0.0.9 8 | mate 9 | Useful functions, some of which I wish were in the Clojure standard library. 10 | https://github.com/green-coder/mate 11 | 12 | https://github.com/green-coder/mate 13 | scm:git:git://github.com/green-coder/mate.git 14 | scm:git:ssh://git@github.com/green-coder/mate.git 15 | made-in-taiwan 16 | 17 | 18 | 19 | Eclipse Public License v2.0 20 | http://www.eclipse.org/legal/epl-v20.html 21 | 22 | 23 | 24 | 25 | green-coder 26 | Vincent Cantin 27 | 28 | 29 | 30 | 31 | org.clojure 32 | clojure 33 | 1.10.3 34 | 35 | 36 | 37 | src 38 | 39 | 40 | 41 | clojars 42 | https://repo.clojars.org/ 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/mate/core.cljc: -------------------------------------------------------------------------------- 1 | (ns mate.core 2 | #?(:cljs (:require-macros mate.core)) 3 | (:refer-clojure :exclude [group-by]) 4 | (:require [clojure.core :as cc])) 5 | 6 | (defmacro implies 7 | "`(implies x y)` expands to `(or (not x) y)` while being more 8 | descriptive of the logical intent." 9 | [x y] 10 | `(or (not ~x) ~y)) 11 | 12 | (defn seq-indexed 13 | "Returns an indexed sequence from the collection `coll`." 14 | [coll] 15 | (map-indexed vector coll)) 16 | 17 | (defn mapcat-indexed 18 | "A composition of map-indexed and cat." 19 | ([f] 20 | (comp (map-indexed f) 21 | cat)) 22 | ([f coll] 23 | (->> coll 24 | (map-indexed f) 25 | (apply concat)))) 26 | 27 | #?(:clj 28 | (defn re-groups-indexed 29 | "Same as re-groups, but returns pair(s) `[index group]` instead of group(s)." 30 | [m] 31 | (let [group-count (.groupCount m)] 32 | (if (zero? group-count) 33 | [(.start m) (.group m)] 34 | (loop [result [] 35 | index 0] 36 | (if (<= index group-count) 37 | (recur (conj result [(.start m index) (.group m index)]) 38 | (inc index)) 39 | result)))))) 40 | 41 | #?(:cljs 42 | (defn re-with-flags 43 | "Returns a new RegEx with additional flags." 44 | [^js re flags] 45 | (js/RegExp. (.-source re) (str (.-flags re) flags)))) 46 | 47 | #?(:clj 48 | (defn re-find-indexed 49 | "Same as re-find, but returns a pair `[index match]` when there is a match." 50 | ([^java.util.regex.Matcher m] 51 | (when (.find m) 52 | (re-groups-indexed m))) 53 | ([^java.util.regex.Pattern re s] 54 | (let [m (re-matcher re s)] 55 | (re-find-indexed m)))) 56 | 57 | :cljs 58 | (defn re-find-indexed 59 | "Same as re-find, but returns a pair `[index match]` when there is a match." 60 | [^js re s] 61 | (when-some [^array m (.exec re s)] 62 | (let [group-count (count m)] 63 | (if (== group-count 1) 64 | [(.-index m) (aget m 0)] 65 | (loop [result [] 66 | index 0] 67 | (if (< index group-count) 68 | (recur (conj result [(some-> (.-indices m) (aget index 0)) 69 | (aget m index)]) 70 | (inc index)) 71 | result))))))) 72 | 73 | (defmacro comp-> 74 | "Same as `comp` but with the arguments in reverse order." 75 | [& args] 76 | `(comp ~@(reverse args))) 77 | 78 | (defmacro if-> 79 | "If branching threading macro." 80 | [x test then-form else-form] 81 | `(let [val# ~x] 82 | (if ~test 83 | (-> val# ~then-form) 84 | (-> val# ~else-form)))) 85 | 86 | (defmacro let-> 87 | "Let threading macro." 88 | [x bindings body] 89 | `(let [val# ~x] 90 | (let ~bindings 91 | (-> val# ~body)))) 92 | 93 | (defmacro apply-> 94 | "Apply threading macro." 95 | [x f & args] 96 | `(apply ~f ~x ~@args)) 97 | 98 | (defmacro apply->> 99 | "Apply on coll threading macro." 100 | [coll f & args] 101 | `(apply ~f ~@args ~coll)) 102 | 103 | (defn partial-> 104 | "" 105 | [f & args] 106 | (fn [x] 107 | (apply f x args))) 108 | 109 | (defn partial->> 110 | "" 111 | ([f a] (fn [x] (f a x))) 112 | ([f a b] (fn [x] (f a b x))) 113 | ([f a b c] (fn [x] (f a b c x))) 114 | ([f a b c d] (fn [x] (f a b c d x))) 115 | ([f a b c d e] (fn [x] (f a b c d e x))) 116 | ([f a b c d e & more] (fn [x] (apply f a b c d e (concat more [x]))))) 117 | 118 | (defn group-by 119 | "Same as clojure.core/group-by, but with some handy new arities which apply 120 | custom map & reduce operations to the elements grouped together under the same key." 121 | ([kf coll] 122 | ;(group-by kf identity conj [] coll) 123 | (cc/group-by kf coll)) 124 | ([kf vf coll] 125 | (group-by kf vf conj [] coll)) 126 | ([kf vf rf coll] 127 | (group-by kf vf rf (rf) coll)) 128 | ([kf vf rf init coll] 129 | (->> coll 130 | (reduce (fn [ret x] 131 | (let [k (kf x) 132 | v (vf x)] 133 | (assoc! ret k (rf (get ret k init) v)))) 134 | (transient {})) 135 | persistent!))) 136 | 137 | (defn index-by 138 | "Returns a hashmap made of `[key value]` pairs from items in the collection `coll` 139 | where the keys are `(kf item)` and the values are `(vf item)`. 140 | `vf` defaults to the identify function." 141 | ([kf coll] 142 | (index-by kf identity coll)) 143 | ([kf vf coll] 144 | (->> coll 145 | (reduce (fn [ret x] 146 | (assoc! ret (kf x) (vf x))) 147 | (transient {})) 148 | persistent!))) 149 | 150 | (defn ungroup-keys 151 | "From a hashmap where the keys are grouped using sequential collections (lists, vectors ...) or using sets, 152 | this function returns a hashmap where those keys are ungrouped." 153 | [m] 154 | (into {} 155 | (mapcat (fn [[k v]] 156 | (if (or (sequential? k) 157 | (set? k)) 158 | (mapv (fn [k-item] 159 | [k-item v]) 160 | k) 161 | [[k v]]))) 162 | m)) 163 | -------------------------------------------------------------------------------- /src/mate/io.cljc: -------------------------------------------------------------------------------- 1 | (ns mate.io 2 | (:require [clojure.string :as str] 3 | #?(:clj [clojure.java.io :as io])) 4 | #?(:cljs (:require-macros [mate.io]))) 5 | 6 | (defn- resource-path 7 | "Returns the path of a resource. 8 | 9 | If `resource-name` starts with a './', the path is built 10 | using the parent directory of `namespace-obj`." 11 | [namespace-obj resource-name] 12 | (if (str/starts-with? resource-name "./") 13 | (let [namespace-str (name (ns-name namespace-obj)) 14 | namespace-dir-path (-> namespace-str 15 | (str/split #"\.") 16 | butlast 17 | (->> (str/join "/")) 18 | (str/replace #"-" {"-" "_"}))] 19 | (str namespace-dir-path "/" (subs resource-name (count "./")))) 20 | resource-name)) 21 | 22 | #_(resource-path *ns* "foo") 23 | #_(resource-path *ns* "./foo") 24 | 25 | #?(:clj 26 | (defn inline-resource* 27 | ([namespace-obj resource-name] 28 | (inline-resource* namespace-obj resource-name identity)) 29 | ([namespace-obj resource-name f & args] 30 | (let [resource-name (eval resource-name) 31 | path (resource-path namespace-obj resource-name) 32 | resource (io/resource path) 33 | _ (assert (some? resource) 34 | (str "Resource at path \"" path "\" not found.")) 35 | resource-content (slurp resource)] 36 | (apply f resource-content args))))) 37 | 38 | #_(inline-resource* *ns* "./test-resource.txt") 39 | #_(inline-resource* *ns* "./test-resource.txt" str/upper-case) 40 | 41 | #?(:clj 42 | (defmacro inline-resource 43 | "Inlines the content of a resource at \"macro expansion\" time, 44 | optionally transformed by `f & args` in the same way that using `clojure.core/update`." 45 | ([resource-name] 46 | (inline-resource* *ns* (eval resource-name))) 47 | ([resource-name f & args] 48 | (apply inline-resource* 49 | *ns* 50 | (eval resource-name) 51 | (eval f) 52 | (map eval args))))) 53 | -------------------------------------------------------------------------------- /src/mate/re_frame.cljc: -------------------------------------------------------------------------------- 1 | (ns mate.re-frame) 2 | 3 | ;; The functions below are used for composing together different functions 4 | ;; inside a re-frame event handler via the thread macro `->`. 5 | ;; The `effects` parameter is what is returned by a typical re-frame event handlers, 6 | ;; and has the general shape `{:db db, :fx [...]}`. 7 | 8 | ;; This pattern hopes to encourage: 9 | ;; - grouping together different changes inside the event-handler, reducing the usage of `{:fx [[:dispatch next-thing-to-do-event]]}`, 10 | ;; - defining the logic in top-level functions outside of the `(rf/reg-event-fx ...)` expressions. 11 | 12 | (defn update-db 13 | "Update the value of `:db` in the `effects` hashmap." 14 | [effects f & args] 15 | (apply update effects :db f args)) 16 | 17 | (defn into-fx 18 | "Appends a collection of effects to the value of `:fx` in the `effects` hashmap." 19 | [effects fxs] 20 | (update effects :fx (fnil into []) (remove nil?) fxs)) 21 | 22 | (defn conj-fx 23 | "Appends one or more effects to the value of `:fx` in the `effects` hashmap." 24 | ([effects fx] 25 | (cond-> effects 26 | (some? fx) 27 | (update :fx (fnil conj []) fx))) 28 | ([effects fx & fxs] 29 | (into-fx effects (cons fx fxs)))) 30 | 31 | (defn conj-fx-using-db 32 | "Invoke `f` with the value of `:db` from the `effects` hashmap and conj its result 33 | to the `:fx` collection in `effects`." 34 | [effects f & args] 35 | (conj-fx effects (apply f (:db effects) args))) 36 | 37 | (defn into-fx-using-db 38 | "Invoke `f` with the value of `:db` from the `effects` hashmap and appends its resulting collection 39 | into the `:fx` collection in `effects`." 40 | [effects f & args] 41 | (into-fx effects (apply f (:db effects) args))) 42 | -------------------------------------------------------------------------------- /test-resource/public/foobar.txt: -------------------------------------------------------------------------------- 1 | Content of foobar.txt -------------------------------------------------------------------------------- /test/mate/core_test.cljc: -------------------------------------------------------------------------------- 1 | (ns mate.core-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [mate.core :as m])) 4 | 5 | (deftest implies-test 6 | (is (true? (m/implies true true))) 7 | (is (false? (m/implies true false))) 8 | (is (true? (m/implies false true))) 9 | (is (true? (m/implies false false)))) 10 | 11 | (deftest seq-indexed-test 12 | (is (= '([0 :a] [1 :a] [2 :b] [3 :a] [4 :b]) 13 | (m/seq-indexed [:a :a :b :a :b])))) 14 | 15 | (deftest mapcat-indexed-test 16 | (is (= '[:a 0 :b 1 :c 2] 17 | (into [] 18 | (m/mapcat-indexed (fn [index x] [x index])) 19 | [:a :b :c]))) 20 | (is (= '(:a 0 :b 1 :c 2) 21 | (m/mapcat-indexed (fn [index x] [x index]) 22 | [:a :b :c])))) 23 | 24 | (deftest re-find-indexed-test 25 | ;; Test with no subgroup 26 | (is (= [4 "${aaa}"] 27 | (m/re-find-indexed #"\$\{[^\{\}]*\}" "xxx ${aaa} ${bbb} yyy"))) 28 | 29 | ;; Test with 1 subgroup 30 | (let [re (-> #"\$\{([^\{\}]*)\}") 31 | ;; flag "d" generate indices for substring matches in Javascript. 32 | ;; See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags 33 | #?@(:cljs [re (m/re-with-flags re "d")])] 34 | (is (= [[4 "${aaa}"] [6 "aaa"]] 35 | (m/re-find-indexed re "xxx ${aaa} ${bbb} yyy"))))) 36 | 37 | (deftest comp->-test 38 | (is (= "3" 39 | ((m/comp-> inc str) 2)))) 40 | 41 | (deftest if->-test 42 | (is (= [:a #_:b 43 | :c 44 | #_:d #_:e :f :g] 45 | (-> [] 46 | (m/if-> true 47 | (conj :a) 48 | (conj :b)) 49 | (conj :c) 50 | (m/if-> false 51 | (-> 52 | (conj :d) 53 | (conj :e)) 54 | (-> 55 | (conj :f) 56 | (conj :g))))))) 57 | 58 | (deftest let->-test 59 | (is (= [:a :b :c :d] 60 | (-> [] 61 | (m/let-> [a :a] 62 | (conj a)) 63 | (m/let-> [b :b 64 | c :c] 65 | (-> 66 | (conj b) 67 | (conj c))) 68 | (conj :d))))) 69 | 70 | (deftest apply->-test 71 | (is (= [:a :b :c :d] 72 | (-> [:a] 73 | (m/apply-> conj :b [:c :d]))))) 74 | 75 | (deftest apply->>-test 76 | (is (= [:a :b :c :d] 77 | (-> [:c :d] 78 | (m/apply->> conj [:a] :b))))) 79 | 80 | (deftest partial->-test 81 | (is (= [1 2 3] 82 | (-> 1 83 | ((m/partial-> vector 2 3))))) 84 | 85 | (is (= [[1 10] [2 10] [3 10]] 86 | (->> [1 2 3] 87 | (mapv (m/partial-> vector 10)))))) 88 | 89 | (deftest partial->>-test 90 | (is (= [1 2 3] 91 | (->> 3 92 | ((m/partial->> vector 1 2)))))) 93 | 94 | (deftest group-by-test 95 | (let [coll [[:a 1] [:a 2] [:b 3] [:a 4] [:b 5]]] 96 | (is (= {:a [[:a 1] [:a 2] [:a 4]] 97 | :b [[:b 3] [:b 5]]} 98 | (m/group-by first coll))) 99 | 100 | (is (= {:a [1 2 4] 101 | :b [3 5]} 102 | (m/group-by first second coll))) 103 | 104 | (is (= {:a 7 105 | :b 8} 106 | (m/group-by first second + coll))) 107 | 108 | (is (= {:a 17 109 | :b 18} 110 | (m/group-by first second + 10 coll))))) 111 | 112 | (deftest index-by-test 113 | (let [coll [[:a 1] [:a 2] [:b 3] [:a 4] [:b 5]]] 114 | (is (= {:a [:a 4] 115 | :b [:b 5]} 116 | (m/index-by first coll))) 117 | 118 | (is (= {:a 4 119 | :b 5} 120 | (m/index-by first second coll))))) 121 | 122 | (deftest ungroup-keys-test 123 | (is (= {:a 1 124 | :b 1 125 | :c 2 126 | :d 3 127 | :e 3 128 | {:f :g} 4} 129 | (m/ungroup-keys {:a "this value will be overwritten" 130 | [:a :b] 1 131 | :c 2 132 | #{:d :e} 3 133 | {:f :g} 4})))) 134 | -------------------------------------------------------------------------------- /test/mate/io_test.cljc: -------------------------------------------------------------------------------- 1 | (ns mate.io-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [mate.io :as mio] 4 | #?(:clj [clojure.data.json :as json]))) 5 | 6 | (deftest inline-resource-test 7 | (testing "Finds the resource using a relative path." 8 | (is (= (mio/inline-resource (str "./" "test-resource.txt")) 9 | "Content of test-resource.txt"))) 10 | 11 | (testing "Finds the resource using an absolute path." 12 | (is (= (mio/inline-resource (str "mate/" "test-resource.txt")) 13 | "Content of test-resource.txt"))) 14 | 15 | (testing "Reads and transform the content using multiple arguments." 16 | (is (= (mio/inline-resource "./test-resource.txt" 17 | str " FOO" " BAR") 18 | "Content of test-resource.txt FOO BAR"))) 19 | 20 | (testing "Reads the JSON content as string and parse it to get a Clojure data structure." 21 | (is (= (mio/inline-resource "./test-resource.json" 22 | json/read-json) 23 | [{:a 1} "hello"])))) 24 | -------------------------------------------------------------------------------- /test/mate/re_frame_test.cljc: -------------------------------------------------------------------------------- 1 | (ns mate.re-frame-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [mate.re-frame :as mr])) 4 | 5 | (defn- store-user-in-db [db user-id user-name access-token refresh-token] 6 | (update db :user assoc 7 | :id user-id 8 | :name user-name 9 | :access-token access-token 10 | :refresh-token refresh-token)) 11 | 12 | (defn- user-logged-in-notification-fx [db] 13 | [:notification {:level :info 14 | :message (str (-> db :user :name) 15 | " logged in")}]) 16 | 17 | (defn- load-user-data-fx [db] 18 | [:http-get (-> db :user :id) [:picture-url :accepted-end-user-agreement? :preferred-books]]) 19 | 20 | ;; The composing functions can be used at any level in your code, just keep using the -> pattern 21 | ;; to keep things tidy (or not). 22 | (defn- complicated-handler [effects foo-value] 23 | (-> effects 24 | (mr/update-db assoc :foo foo-value) 25 | (mr/conj-fx [:foo-system-changed]))) 26 | 27 | (defn- save-to-royal-storage [db] 28 | [[:save-to-royal-storage (:queen-browsing-history db)] 29 | [:one-minute-silence]]) 30 | 31 | (deftest update-db-test 32 | (is (= {:db {:foo "bar" 33 | :a {:b 1}}} 34 | (-> {:db {}} 35 | (mr/update-db assoc :foo "bar") 36 | (mr/update-db update-in [:a :b] (fnil inc 0)))))) 37 | 38 | (deftest into-fx-test 39 | (is (= {:db {:foo "bar"} 40 | :fx []} 41 | (-> {:db {:foo "bar"}} 42 | (mr/into-fx nil)))) 43 | (is (= {:db {:foo "bar"} 44 | :fx []} 45 | (-> {:db {:foo "bar"}} 46 | (mr/into-fx [nil])))) 47 | (is (= {:db {:foo "bar"} 48 | :fx [[:foo-fx 1] 49 | [:foo-fx 2]]} 50 | (-> {:db {:foo "bar"}} 51 | (mr/into-fx [[:foo-fx 1] 52 | nil 53 | [:foo-fx 2]]))))) 54 | 55 | (deftest conj-fx-test 56 | (is (= {:db {:foo "bar"}} 57 | (-> {:db {:foo "bar"}} 58 | (mr/conj-fx nil)))) 59 | (is (= {:db {:foo "bar"} 60 | :fx [[:foo-fx 1]]} 61 | (-> {:db {:foo "bar"}} 62 | (mr/conj-fx [:foo-fx 1])))) 63 | (is (= {:db {:foo "bar"} 64 | :fx [[:foo-fx 1] 65 | [:bar-fx 2]]} 66 | (-> {:db {:foo "bar"}} 67 | (mr/conj-fx [:foo-fx 1] 68 | [:bar-fx 2]))))) 69 | 70 | (deftest usage-example 71 | (let [db {:foo "bar"}] 72 | (is (= {:db {:foo "new-bar" 73 | :user {:id 1 74 | :name "Alice" 75 | :access-token 123 76 | :refresh-token 456}} 77 | :fx [[:sound :login-success] 78 | [:notification {:level :info 79 | :message "Alice logged in"}] 80 | [:http-get 1 [:picture-url :accepted-end-user-agreement? :preferred-books]] 81 | [:save-to-royal-storage (:queen-browsing-history db)] 82 | [:one-minute-silence] 83 | [:foo-system-changed]]} 84 | (-> {:db db} 85 | (mr/conj-fx [:sound :login-success]) 86 | (mr/update-db store-user-in-db 1 "Alice" 123 456) 87 | (mr/conj-fx-using-db user-logged-in-notification-fx) 88 | (mr/conj-fx-using-db load-user-data-fx) 89 | (mr/into-fx-using-db save-to-royal-storage) 90 | (complicated-handler "new-bar")))))) 91 | -------------------------------------------------------------------------------- /test/mate/test-resource.json: -------------------------------------------------------------------------------- 1 | [{"a": 1}, "hello"] 2 | -------------------------------------------------------------------------------- /test/mate/test-resource.txt: -------------------------------------------------------------------------------- 1 | Content of test-resource.txt -------------------------------------------------------------------------------- /tests.edn: -------------------------------------------------------------------------------- 1 | #kaocha/v1 2 | {:tests [{:id :unit 3 | :type :kaocha.type/clojure.test} 4 | {:id :unit-cljs 5 | :type :kaocha.type/cljs}]} 6 | --------------------------------------------------------------------------------