├── .gitignore ├── README.md ├── ReleaseNotes.md ├── epl-v10.html ├── profiles.clj ├── project.clj ├── release.sh ├── src └── pallet │ └── thread_expr.clj └── test └── pallet └── thread_expr_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | /logs 6 | /doc 7 | doc-src/VERSIONS.md 8 | doc-src/INTRO.md 9 | pom.xml 10 | pom.xml.asc 11 | *.jar 12 | *.class 13 | .lein-deps-sum 14 | .lein-failures 15 | .lein-plugins 16 | .lein-repl-history 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pallet.thread-expr 2 | 3 | A library containing macros for use in clojure threading expressions (using ->). 4 | 5 | See 6 | [reference documentation](http://pallet.github.com/thread-expr/autodoc/index.html) 7 | and 8 | [annotated source](http://pallet.github.com/thread-expr/marginalia/uberdoc.html). 9 | 10 | ## Examples 11 | 12 | **Threaded arg exposure:** 13 | 14 | `arg->` exposes the threaded arg, binding it to the supplied variable. For 15 | example: 16 | 17 | ```clojure 18 | (-> 2 19 | (arg-> [x] 20 | (* (inc x)))) 21 | 22 | ;=> 6 23 | ``` 24 | 25 | Expands to: 26 | 27 | ```clojure 28 | (-> 2 29 | ((fn [arg] (let [x arg] (-> arg (* inc x)))))) 30 | 31 | ;=> 6 32 | ``` 33 | 34 | Note the extra set of parens in the expansion; the threading macro feeds the 35 | current argument in as `arg`, binds it to the supplied var using `let`, and 36 | resumes threading for all forms inside of `arg->`. 37 | 38 | **Threaded list comprehension:** 39 | 40 | The following use of `for->`: 41 | 42 | ```clojure 43 | (-> 1 44 | (for-> [x [1 2 3]] 45 | (+ x))) 46 | 47 | ;=> 7 48 | ``` 49 | 50 | Expands to: 51 | 52 | ```clojure 53 | (-> 1 54 | (+ 1) 55 | (+ 2) 56 | (+ 3)) 57 | 58 | ;=> 7 59 | ``` 60 | 61 | ## Installation 62 | 63 | thread-expr is distributed as a jar, and is available in the 64 | [clojars repository](http://clojars.org/com.palletops/thread-expr). 65 | 66 | Installation is with leiningen or your favourite maven repository aware build 67 | tool. 68 | 69 | ### lein/cake project.clj 70 | 71 | :dependencies [[com.palletops/thread-expr "1.3.0"]] 72 | 73 | ### maven pom.xml 74 | 75 | 76 | 77 | org.cloudhoist 78 | thread-expr 79 | 1.3.0 80 | 81 | 82 | 83 | 84 | 85 | clojars 86 | http://clojars.org/repo 87 | 88 | 89 | 90 | ## License 91 | 92 | Licensed under [EPL](http://www.eclipse.org/legal/epl-v10.html) 93 | 94 | Copyright 2011, 2012, 2013 Hugo Duncan. 95 | -------------------------------------------------------------------------------- /ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | # thread-expr Release Notes 2 | 3 | The latest release is 1.3.0. 4 | 5 | ## 1.3.0 6 | 7 | - Use lein as the build tool and deploy to clojars 8 | Deploys with the com.palletops group id. 9 | 10 | ## 1.2.0 11 | 12 | - Add indentation metadata to macros 13 | This ensures the macros indent properly (at least in ritz). 14 | 15 | - make 1.3 compatible while maintaining 1.2 backwards compatibility 16 | 17 | ## 1.1.0 18 | 19 | - Fixed bug with duplicated apply-map->. 20 | 21 | - Added ->> macros 22 | These are last argument threading macros. 23 | 24 | - Added `require` dependency for symbol macros. Added documentation and more 25 | bindings to `-->`; all are now supported, except for `apply-map` and 26 | `let-with-arg`. 27 | 28 | - added -->; similar to ->, but uses symbol macro-let to bind many of the 29 | thread-expr functions. 30 | 31 | 32 | ## 1.0.0 33 | 34 | This is the initial standalone release. The library has been extracted 35 | from the main [pallet repository](https://github.com/pallet/pallet). 36 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /profiles.clj: -------------------------------------------------------------------------------- 1 | {:dev {:dependencies [[ch.qos.logback/logback-classic "1.0.9"]] 2 | :plugins [[lein-set-version "0.3.0"]]} 3 | :doc {:dependencies [[com.palletops/pallet-codox "0.1.0-SNAPSHOT"]] 4 | :plugins [[codox/codox.leiningen "0.6.4"] 5 | [lein-marginalia "0.7.1"]] 6 | :codox {:writer codox-md.writer/write-docs 7 | :output-dir "doc/1.3/api" 8 | :src-dir-uri "https://github.com/pallet/pallet-common/blob/develop" 9 | :src-linenum-anchor-prefix "L"} 10 | :aliases {"marg" ["marg" "-d" "doc/1.3/annotated"] 11 | "codox" ["doc"] 12 | "doc" ["do" "codox," "marg"]}} 13 | :1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]} 14 | :1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]} 15 | :1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]} 16 | :release 17 | {:set-version 18 | {:updates [{:path "README.md" :no-snapshot true}]}}} 19 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.palletops/thread-expr "1.3.1-SNAPSHOT" 2 | :description "Thread-expr provides macros for use within a clojure threaded 3 | argument expressions (-> and ->>)." 4 | :url "http://palletops.com" 5 | :license {:name "Eclipse Public License" 6 | :url "http://www.eclipse.org/legal/epl-v10.html"} 7 | :dependencies [[org.clojure/clojure "1.2.1"] 8 | [org.clojure/tools.macro "0.1.1"]]) 9 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # release thread-expr 4 | 5 | if [[ $# -lt 3 ]]; then 6 | echo "usage: $(basename $0) previous-version new-version next-version" >&2 7 | exit 1 8 | fi 9 | 10 | previous_version=$1 11 | version=$2 12 | next_version=$3 13 | 14 | echo "" 15 | echo "Start release of $version, previous version is $previous_version" 16 | echo "" 17 | echo "" 18 | 19 | lein do clean, with-profile default:1.3:1.4:1.5 test && \ 20 | git flow release start $version || exit 1 21 | 22 | lein with-profile +release set-version ${version} :previous-version ${previous_version} \ 23 | || { echo "set version failed" >2 ; exit 1; } 24 | 25 | echo "" 26 | echo "" 27 | echo "Changes since $previous_version" 28 | git --no-pager log --pretty=changelog thread-expr-$previous_version.. 29 | echo "" 30 | echo "" 31 | echo "Now edit project.clj, ReleaseNotes and README" 32 | 33 | $EDITOR project.clj 34 | $EDITOR ReleaseNotes.md 35 | $EDITOR README.md 36 | 37 | echo -n "commiting project.clj, release notes and readme. enter to continue:" \ 38 | && read x \ 39 | && git add project.clj ReleaseNotes.md README.md \ 40 | && git commit -m "Updated project.clj, release notes and readme for $version" \ 41 | && echo -n "Peform release. enter to continue:" && read x \ 42 | && lein do clean, install, test, deploy clojars \ 43 | && git flow release finish $version \ 44 | && echo "Now push to github. Don't forget the tags!" \ 45 | && lein with-profile +doc doc \ 46 | && lein with-profile +release set-version ${next_version} \ 47 | && git add project.clj \ 48 | && git commit -m "Updated version for next release cycle" 49 | -------------------------------------------------------------------------------- /src/pallet/thread_expr.clj: -------------------------------------------------------------------------------- 1 | (ns pallet.thread-expr 2 | "Macros that can be used in an expression thread." 3 | (:require [clojure.tools.macro :as macro])) 4 | 5 | (letfn [(for- [threader arg seq-exprs body] 6 | `(reduce #(%2 %1) 7 | ~arg 8 | (for ~seq-exprs 9 | (fn [arg#] (~threader arg# ~@body)))))] 10 | (defmacro for-> 11 | "Apply a thread expression to a sequence. 12 | eg. 13 | (-> 1 14 | (for-> [x [1 2 3]] 15 | (+ x))) 16 | => 7" 17 | {:indent 1} 18 | [arg seq-exprs & body] 19 | (for- 'clojure.core/-> arg seq-exprs body)) 20 | 21 | (defmacro for->> 22 | "Apply a thread expression to a sequence. 23 | eg. 24 | (->> 1 25 | (for->> [x [1 2 3]] 26 | (+ x))) 27 | => 7" 28 | {:indent 1} 29 | [seq-exprs & body] 30 | (let [arg (last body) 31 | body (butlast body)] 32 | (for- 'clojure.core/->> arg seq-exprs body)))) 33 | 34 | (letfn [(when- [threader arg condition body] 35 | `(let [arg# ~arg] 36 | (if ~condition 37 | (~threader arg# ~@body) 38 | arg#)))] 39 | 40 | (defmacro when-> 41 | "A `when` form that can appear in a request thread. 42 | eg. 43 | (-> 1 44 | (when-> true 45 | (+ 1))) 46 | => 2" 47 | {:indent 1} 48 | [arg condition & body] 49 | (when- 'clojure.core/-> arg condition body)) 50 | 51 | (defmacro when->> 52 | "A `when` form that can appear in a request thread. 53 | eg. 54 | (->> 1 55 | (when->> true 56 | (+ 1))) 57 | => 2" 58 | {:indent 1} 59 | [condition & body] 60 | (let [arg (last body) 61 | body (butlast body)] 62 | (when- 'clojure.core/->> arg condition body)))) 63 | 64 | (letfn [(when-not- 65 | [threader arg condition body] 66 | `(let [arg# ~arg] 67 | (if-not ~condition 68 | (~threader arg# ~@body) 69 | arg#)))] 70 | 71 | (defmacro when-not-> 72 | "A `when-not` form that can appear in a request thread. 73 | eg. 74 | (-> 1 75 | (when-not-> true 76 | (+ 1))) 77 | => 1" 78 | {:indent 1} 79 | [arg condition & body] 80 | (when-not- 'clojure.core/-> arg condition body)) 81 | 82 | (defmacro when-not->> 83 | "A `when-not` form that can appear in a request thread. 84 | eg. 85 | (->> 1 86 | (when-not->> true 87 | (+ 1))) 88 | => 1" 89 | {:indent 1} 90 | [condition & body] 91 | (let [arg (last body) 92 | body (butlast body)] 93 | (when-not- 'clojure.core/->> arg condition body)))) 94 | 95 | (letfn [(let- 96 | [threader arg binding body] 97 | `(let ~binding 98 | (~threader ~arg ~@body)))] 99 | 100 | (defmacro let-> 101 | "A `let` form that can appear in a request thread. 102 | eg. 103 | (-> 1 104 | (let-> [a 1] 105 | (+ a))) 106 | => 2" 107 | {:indent 1} 108 | [arg binding & body] 109 | (let- 'clojure.core/-> arg binding body)) 110 | 111 | (defmacro let->> 112 | "A `let` form that can appear in a request thread. 113 | eg. 114 | (->> 1 115 | (let->> [a 1] 116 | (+ a))) 117 | => 2" 118 | {:indent 1} 119 | [binding & body] 120 | (let [arg (last body) 121 | body (butlast body)] 122 | (let- 'clojure.core/->> arg binding body)))) 123 | 124 | (letfn [(binding- 125 | [threader arg bindings body] 126 | `(binding ~bindings 127 | (~threader ~arg ~@body)))] 128 | 129 | (defmacro binding-> 130 | "A `binding` form that can appear in a request thread. 131 | eg. 132 | (def *a* 0) 133 | (-> 1 134 | (binding-> [*a* 1] 135 | (+ *a*))) 136 | => 2" 137 | {:indent 1} 138 | [arg bindings & body] 139 | (binding- 'clojure.core/-> arg bindings body)) 140 | 141 | (defmacro binding->> 142 | "A `binding` form that can appear in a request thread. 143 | eg. 144 | (def *a* 0) 145 | (->> 1 146 | (binding->> [*a* 1] 147 | (+ *a*))) 148 | => 2" 149 | {:indent 1} 150 | [bindings & body] 151 | (let [arg (last body) 152 | body (butlast body)] 153 | (binding- 'clojure.core/->> arg bindings body)))) 154 | 155 | (letfn [(when-let- 156 | [threader arg binding body] 157 | `(let [arg# ~arg] 158 | (if-let ~binding 159 | (~threader arg# ~@body) 160 | arg#)))] 161 | 162 | (defmacro when-let-> 163 | "A `when-let` form that can appear in a request thread. 164 | eg. 165 | (-> 1 166 | (when-let-> [a 1] 167 | (+ a))) 168 | => 2" 169 | {:indent 1} 170 | [arg binding & body] 171 | (when-let- 'clojure.core/-> arg binding body)) 172 | 173 | (defmacro when-let->> 174 | "A `when-let` form that can appear in a request thread. 175 | eg. 176 | (->> 1 177 | (when-let->> [a 1] 178 | (+ a))) 179 | => 2" 180 | {:indent 1} 181 | [binding & body] 182 | (let [arg (last body) 183 | body (butlast body)] 184 | (when-let- 'clojure.core/->> arg binding body)))) 185 | 186 | (letfn [(if- 187 | ([threader arg condition form] 188 | `(let [arg# ~arg] 189 | (if ~condition 190 | (~threader arg# ~form) 191 | arg#))) 192 | ([threader arg condition form else-form] 193 | `(let [arg# ~arg] 194 | (if ~condition 195 | (~threader arg# ~form) 196 | (~threader arg# ~else-form)))))] 197 | 198 | (defmacro if-> 199 | "An `if` form that can appear in a request thread 200 | eg. 201 | (-> 1 202 | (if-> true 203 | (+ 1) 204 | (+ 2))) 205 | => 2" 206 | {:indent 1} 207 | ([arg condition form] 208 | (if- 'clojure.core/-> arg condition form)) 209 | ([arg condition form else-form] 210 | (if- 'clojure.core/-> arg condition form else-form))) 211 | 212 | (defmacro if->> 213 | "An `if` form that can appear in a request thread 214 | eg. 215 | (->> 1 216 | (if->> true 217 | (+ 1) 218 | (+ 2))) 219 | => 2" 220 | {:indent 1} 221 | ([condition form arg] 222 | (if- 'clojure.core/->> arg condition form)) 223 | ([condition form else-form arg] 224 | (if- 'clojure.core/->> arg condition form else-form)))) 225 | 226 | (letfn [(if-not- 227 | ([threader arg condition form] 228 | `(let [arg# ~arg] 229 | (if-not ~condition 230 | (~threader arg# ~form) 231 | arg#))) 232 | ([threader arg condition form else-form] 233 | `(let [arg# ~arg] 234 | (if-not ~condition 235 | (~threader arg# ~form) 236 | (~threader arg# ~else-form)))))] 237 | 238 | (defmacro if-not-> 239 | "An `if-not` form that can appear in a request thread 240 | eg. 241 | (-> 1 242 | (if-not-> true 243 | (+ 1) 244 | (+ 2))) 245 | => 3" 246 | {:indent 1} 247 | ([arg condition form] 248 | (if-not- 'clojure.core/-> arg condition form)) 249 | ([arg condition form else-form] 250 | (if-not- 'clojure.core/-> arg condition form else-form))) 251 | 252 | (defmacro if-not->> 253 | "An `if-not` form that can appear in a request thread 254 | eg. 255 | (->> 1 256 | (if-not->> true 257 | (+ 1) 258 | (+ 2))) 259 | => 3" 260 | ([condition form arg] 261 | (if-not- 'clojure.core/->> arg condition form)) 262 | ([condition form else-form arg] 263 | (if-not- 'clojure.core/->> arg condition form else-form)))) 264 | 265 | (letfn [(arg- 266 | [threader arg sym body] 267 | `(let [~sym ~arg] 268 | (~threader ~sym ~@body)))] 269 | 270 | (defmacro arg-> 271 | "Lexically assign the threaded argument to the specified symbol. 272 | 273 | (-> 1 274 | (arg-> [x] (+ x))) 275 | 276 | => 2" 277 | {:indent 1} 278 | [arg [sym] & body] 279 | (arg- 'clojure.core/-> arg sym body)) 280 | 281 | (defmacro arg->> 282 | "Lexically assign the threaded argument to the specified symbol. 283 | 284 | (->> 1 285 | (arg->> [x] (+ x))) 286 | 287 | => 2" 288 | {:indent 1} 289 | [[sym] & body] 290 | (let [arg (last body) 291 | body (butlast body)] 292 | (arg- 'clojure.core/->> arg sym body)))) 293 | 294 | (letfn [(let-with-arg- 295 | [threader arg arg-symbol binding body] 296 | `(let [~arg-symbol ~arg] 297 | (let ~binding 298 | (~threader ~arg-symbol ~@body))))] 299 | 300 | (defmacro let-with-arg-> 301 | "A `let` form that can appear in a request thread, and assign the 302 | value of the threaded arg. 303 | 304 | eg. 305 | (-> 1 306 | (let-with-arg-> val [a 1] 307 | (+ a val))) 308 | => 3" 309 | {:indent 2} 310 | [arg arg-symbol binding & body] 311 | (let-with-arg- 'clojure.core/-> arg arg-symbol binding body)) 312 | 313 | (defmacro let-with-arg->> 314 | "A `let` form that can appear in a request thread, and assign the 315 | value of the threaded arg. 316 | 317 | eg. 318 | (->> 1 319 | (let-with-arg->> val [a 1] 320 | (+ a val))) 321 | => 3" 322 | {:indent 2} 323 | [arg-symbol binding & body] 324 | (let [arg (last body) 325 | body (butlast body)] 326 | (let-with-arg- 'clojure.core/->> arg arg-symbol binding body)))) 327 | 328 | (letfn [(apply- 329 | [arg f arg-coll] 330 | `(let [arg# ~arg] 331 | (apply ~f arg# ~@arg-coll)))] 332 | 333 | (defmacro apply-> 334 | "Apply in a threaded expression. 335 | e.g. 336 | (-> 1 337 | (apply-> + [1 2 3])) 338 | => 7" 339 | [arg f & arg-coll] 340 | (apply- arg f arg-coll)) 341 | 342 | (defmacro apply->> 343 | "Apply in a threaded expression. 344 | e.g. 345 | (->> 1 346 | (apply->> + [1 2 3])) 347 | => 7" 348 | [f & arg-coll] 349 | (let [arg (last arg-coll) 350 | arg-coll (butlast arg-coll)] 351 | (apply- arg f arg-coll)))) 352 | 353 | (letfn [(apply-map- 354 | [arg f arg-coll] 355 | `(let [arg# ~arg] 356 | (apply ~f arg# 357 | ~@(butlast arg-coll) 358 | (apply concat ~(last arg-coll)))))] 359 | 360 | (defmacro apply-map-> 361 | "Apply in a threaded expression. 362 | e.g. 363 | (-> :a 364 | (apply-map-> hash-map 1 {:b 2})) 365 | => {:a 1 :b 2}" 366 | [arg f & arg-coll] 367 | (apply-map- arg f arg-coll)) 368 | 369 | (defmacro apply-map->> 370 | "Apply in a threaded expression. 371 | e.g. 372 | (->> :a 373 | (apply-map->> hash-map 1 {:b 2})) 374 | => {:a 1 :b 2}" 375 | [f & arg-coll] 376 | (let [arg (last arg-coll) 377 | arg-coll (butlast arg-coll)] 378 | (apply-map- arg f arg-coll)))) 379 | 380 | (defmacro --> 381 | "Similar to `clojure.core/->`, with added symbol macros for the 382 | various threading macros found in `pallet.thread-expr`. Currently 383 | supported symbol macros are: 384 | 385 | * binding 386 | * for 387 | * let 388 | * when, when-not, when-let 389 | * if, if-not 390 | * apply 391 | * expose-request-as (bound to `pallet.thread-expr/arg->` 392 | 393 | Examples: 394 | 395 | (--> 5 396 | (let [y 1] 397 | (for [x (range 3)] 398 | (+ x y))) 399 | (+ 1)) 400 | => 12 401 | 402 | (--> 5 403 | (expose-request-as [x] (+ x)) 404 | (+ 1)) 405 | => 11" 406 | [& forms] 407 | `(macro/symbol-macrolet 408 | [~'binding binding-> 409 | ~'for for-> 410 | ~'if if-> 411 | ~'if-not if-not-> 412 | ~'when when-> 413 | ~'when-not when-not-> 414 | ~'when-let when-let-> 415 | ~'let let-> 416 | ~'apply apply-> 417 | ~'expose-request-as arg->] 418 | (-> ~@forms))) 419 | 420 | (defmacro -->> 421 | "Similar to `clojure.core/->>`, with added symbol macros for the 422 | various threading macros found in `pallet.thread-expr`. Currently 423 | supported symbol macros are: 424 | 425 | * binding 426 | * for 427 | * let 428 | * when, when-not, when-let 429 | * if, if-not 430 | * apply 431 | * expose-request-as (bound to `pallet.thread-expr/arg->>` 432 | 433 | Examples: 434 | 435 | (-->> 5 436 | (let [y 1] 437 | (for [x (range 3)] 438 | (+ x y))) 439 | (+ 1)) 440 | => 12 441 | 442 | (-->> 5 443 | (expose-request-as [x] (+ x)) 444 | (+ 1)) 445 | => 11" 446 | [& forms] 447 | `(macro/symbol-macrolet 448 | [~'binding binding->> 449 | ~'for for->> 450 | ~'if if->> 451 | ~'if-not if-not->> 452 | ~'when when->> 453 | ~'when-not when-not->> 454 | ~'when-let when-let->> 455 | ~'let let->> 456 | ~'apply apply->> 457 | ~'expose-request-as arg->>] 458 | (->> ~@forms))) 459 | -------------------------------------------------------------------------------- /test/pallet/thread_expr_test.clj: -------------------------------------------------------------------------------- 1 | (ns pallet.thread-expr-test 2 | (:use 3 | pallet.thread-expr 4 | clojure.test)) 5 | 6 | (deftest for->-test 7 | (is (= 7 (-> 1 (for-> [x [1 2 3]] (+ x))))) 8 | (is (= 13 (-> 1 (for-> [x [1 2 3]] (+ x) (+ x))))) 9 | (is (= 1 (-> 1 (for-> [x []] (+ x))))) 10 | (is (= 55 (-> 1 (for-> [x [1 2 3] 11 | y [2 3 4] 12 | :let [z (dec x)]] (+ x y z)))))) 13 | 14 | (deftest for->>-test 15 | (is (= 7 (->> 1 (for->> [x [1 2 3]] (+ x))))) 16 | (is (= 13 (->> 1 (for->> [x [1 2 3]] (+ x) (+ x))))) 17 | (is (= 1 (->> 1 (for->> [x []] (+ x))))) 18 | (is (= 55 (->> 1 (for->> [x [1 2 3] 19 | y [2 3 4] 20 | :let [z (dec x)]] (+ x y z)))))) 21 | 22 | (deftest when->test 23 | (is (= 2 (-> 1 (when-> true (+ 1))))) 24 | (is (= 1 (-> 1 (when-> false (+ 1)))))) 25 | 26 | (deftest when->>test 27 | (is (= 2 (->> 1 (when->> true (+ 1))))) 28 | (is (= 1 (->> 1 (when->> false (+ 1)))))) 29 | 30 | (deftest when-not->test 31 | (is (= 1 (-> 1 (when-not-> true (+ 1))))) 32 | (is (= 2 (-> 1 (when-not-> false (+ 1)))))) 33 | 34 | (deftest when-not->>test 35 | (is (= 1 (->> 1 (when-not->> true (+ 1))))) 36 | (is (= 2 (->> 1 (when-not->> false (+ 1)))))) 37 | 38 | (deftest when-let->test 39 | (is (= 2) (-> 1 (when-let-> [a 1] (+ a)))) 40 | (is (= 1) (-> 1 (when-let-> [a nil] (+ a))))) 41 | 42 | (deftest when-let->>test 43 | (is (= 2) (->> 1 (when-let->> [a 1] (+ a)))) 44 | (is (= 1) (->> 1 (when-let->> [a nil] (+ a))))) 45 | 46 | (deftest let->test 47 | (is (= 2) (-> 1 (let-> [a 1] (+ a))))) 48 | 49 | (deftest let->>test 50 | (is (= 2) (->> 1 (let->> [a 1] (+ a))))) 51 | 52 | (def ^{:dynamic true} *a* 0) 53 | (deftest binding->test 54 | (is (= 2) (-> 1 (binding-> [*a* 1] (+ *a*))))) 55 | 56 | (deftest binding->>test 57 | (is (= 2) (->> 1 (binding->> [*a* 1] (+ *a*))))) 58 | 59 | (deftest if->test 60 | (is (= 2 (-> 1 (if-> true (+ 1) (+ 2))))) 61 | (is (= 3 (-> 1 (if-> false (+ 1) (+ 2))))) 62 | (is (= 1 (-> 1 (if-> false (+ 1)))))) 63 | 64 | (deftest if->>test 65 | (is (= 2 (->> 1 (if->> true (+ 1) (+ 2))))) 66 | (is (= 3 (->> 1 (if->> false (+ 1) (+ 2))))) 67 | (is (= 1 (->> 1 (if->> false (+ 1)))))) 68 | 69 | (deftest if-not->test 70 | (is (= 3 (-> 1 (if-not-> true (+ 1) (+ 2))))) 71 | (is (= 2 (-> 1 (if-not-> false (+ 1) (+ 2)))))) 72 | 73 | (deftest if-not->>test 74 | (is (= 3 (->> 1 (if-not->> true (+ 1) (+ 2))))) 75 | (is (= 2 (->> 1 (if-not->> false (+ 1) (+ 2)))))) 76 | 77 | (deftest arg->test 78 | (is (= 6 (-> 2 (arg-> [x] (* (inc x))))))) 79 | 80 | (deftest arg->>test 81 | (is (= 6 (->> 2 (arg->> [x] (* (inc x))))))) 82 | 83 | (deftest let-with-arg->test 84 | (is (= 3 (-> 1 (let-with-arg-> arg [a 1] (+ a arg)))))) 85 | 86 | (deftest let-with-arg->>test 87 | (is (= 3 (->> 1 (let-with-arg->> arg [a 1] (+ a arg)))))) 88 | 89 | (deftest apply->test 90 | (is (= 7 (-> 1 (apply-> + [1 2 3]))))) 91 | 92 | (deftest apply->>test 93 | (is (= 7 (->> 1 (apply->> + [1 2 3]))))) 94 | 95 | (deftest apply-map->test 96 | (is (= {:a 1 :b 2} (-> :a (apply-map-> hash-map 1 {:b 2}))))) 97 | 98 | (deftest apply-map->>test 99 | (is (= {:a 1 :b 2} (->> :a (apply-map->> hash-map 1 {:b 2}))))) 100 | 101 | (deftest -->test 102 | (is (= 12 (--> 5 103 | (let [y 1] 104 | (for [x (range 3)] 105 | (+ x y))) 106 | (+ 1))) 107 | (= 11 (--> 5 108 | (expose-request-as [x] (+ x)) 109 | (+ 1))))) 110 | 111 | (deftest -->>test 112 | (is (= 12 (-->> 5 113 | (let [y 1] 114 | (for [x (range 3)] 115 | (+ x y))) 116 | (+ 1))) 117 | (= 11 (-->> 5 118 | (expose-request-as [x] (+ x)) 119 | (+ 1))))) 120 | --------------------------------------------------------------------------------