├── .gitignore ├── CHANGES ├── LICENSE ├── README.md ├── dev-resources ├── test-comment-headers.in ├── test-comment-headers.out ├── test-disambiguation-of-join.in ├── test-disambiguation-of-join.out ├── test-dotted-alias.in ├── test-dotted-alias.out ├── test-long-files.in ├── test-long-files.out ├── test-metadata.in ├── test-metadata.out ├── test-prefer-orig-ns-refs.in ├── test-prefer-orig-ns-refs.out ├── test-prettify-libspecs.out ├── test-prettify-long-libspecs.out ├── test-prettify-no-miserly-refers.out ├── test-prettify-short-requires.out ├── test-reconstruct.in ├── test-reconstruct.out └── test-stitch-up.out ├── project.clj ├── resources └── swank_elisp_payloads.clj ├── slamhound.el ├── src ├── slam │ ├── hound.clj │ └── hound │ │ ├── asplode.clj │ │ ├── future.clj │ │ ├── prettify.clj │ │ ├── regrow.clj │ │ ├── search.clj │ │ └── stitch.clj └── swank │ └── payload │ └── slamhound.el ├── test └── slam │ ├── hound │ ├── asplode_test.clj │ ├── prettify_test.clj │ ├── regrow_test.clj │ ├── search_test.clj │ ├── stitch_test.clj │ └── test │ │ └── util.clj │ └── hound_test.clj └── todo.org /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | /.lein-repl-history 13 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Changes from 1.5.4 to 1.5.5, 30 April 2014 2 | ========================================== 3 | 4 | - Bind *ns* to file's ns during read. This solves an issue where Slamhound may 5 | incorrectly alias namespaces as `user` when run from the command line. 6 | 7 | - Add support for definterface and gen-class. 8 | 9 | Changes from 1.5.3 to 1.5.4, 03 April 2014 10 | ========================================== 11 | 12 | - Prefer imports whose package name matches a project namespace. 13 | 14 | - Detect class names in metadata used for Java annotations. 15 | 16 | - Fix detection of metadata attached to literal collections. 17 | 18 | - Fix two file handle leaks. 19 | 20 | - Call (shutdown-agents) in slam.hound/-main. 21 | 22 | - Allow the file arguments of `swap-in-reconstructed-ns-form` and 23 | `reconstruct` to be anything implementing clojure.java.io/Coercions. 24 | 25 | - Use the system file separator when searching for files to reconstruct. 26 | 27 | - Parse ns declarations that incorrectly use symbols instead of keywords for 28 | ns reference clauses. e.g. `(require [])` vs `(:require [])` 29 | 30 | Changes from 1.5.2 to 1.5.3, 23 March 2014 31 | ========================================== 32 | 33 | - Add support for the :rename option in :require and :use forms. 34 | 35 | - Add support for core.typed. 36 | 37 | - Blacklist all namespaces beginning with "cljs". When Slamhound supports 38 | ClojureScript, this will be revisited. 39 | 40 | - Correctly identify aliases that shadow existing namespaces. 41 | 42 | - Fix "Disjoin current ns from candidate namespaces" from 1.5.0. 43 | 44 | - Update ns pretty-printing for long :require libspecs. 45 | 46 | Changes from 1.5.1 to 1.5.2, 14 March 2014 47 | ========================================== 48 | 49 | - Handle case where an alias is used to refer to private vars. 50 | 51 | - Use a cache to greatly improve the performance of repeated searches for 52 | a missing reference type. 53 | 54 | - Improve class import search performance. 55 | 56 | - Find namespaces with trailing and mixed dashes/underscores when importing 57 | classes created by deftype and defrecord. 58 | 59 | Changes from 1.5.0 to 1.5.1, 29 January 2014 60 | ============================================ 61 | 62 | - Require matching ns when importing classes created by deftype and defrecord. 63 | 64 | - Support vars with Unicode characters. 65 | 66 | - No more reflection warnings. 67 | 68 | Changes from 1.4.0 to 1.5.0, 24 November 2013 69 | ============================================= 70 | 71 | - Prefer aliases with shorter "alias" distance (number of insertions to create 72 | anchored subsequence). 73 | 74 | - Prefer alias that are namespace initialisms. 75 | 76 | - Prevent multiple aliases to a single namespace. 77 | 78 | - Disjoin current ns from candidate namespaces. 79 | 80 | - Prefer candidates from project namespaces. 81 | 82 | - Find consumed references within syntax-quotes. Closes #14 83 | 84 | - Find special case token `/` and vars with trailing `'`s during regrow. 85 | 86 | - Prefer capitalized vars that shadow class names when they appear in the old 87 | ns map. Closes #27 88 | 89 | - Use old ns form to disambiguate potential classes for import. Closes #26 90 | 91 | - The :require flags :reload, :reload-all, and :verbose are parsed and 92 | re-emitted. 93 | 94 | - Parse multiple ns clauses of the same type. 95 | 96 | - Metadata maps in ns forms are parsed and re-emitted. 97 | 98 | - Multiple :key value options are emitted per libspec; i.e. no separate :as 99 | and :refer vectors. 100 | 101 | - Refer candidates are now subject to :exclude rules from the old ns. 102 | 103 | - Excluding vars in clojure.core also excludes vars from cljs.core. 104 | 105 | - New ns form parser for more reliable disambiguation. 106 | https://github.com/technomancy/slamhound/pull/53 107 | 108 | - Allow slamhound.el to work with either nrepl.el or cider. 109 | 110 | - Include dynamically generated Classes in :import search. 111 | 112 | - Avoid excessive tempfile creation. 113 | 114 | - Honor existing :refer :all and :use clauses. 115 | https://github.com/technomancy/slamhound/issues/29#issuecomment-22086914 116 | 117 | - Preserve file comment headers. 118 | 119 | - Fix multiline docstrings. 120 | 121 | - Drop support for slime from elisp interface. 122 | 123 | Changes from 1.3.3 to 1.4.0, 20 June 2013 124 | ========================================= 125 | 126 | - Force print newlines between namespaces in short :requires. Closes #23 127 | 128 | - Fix docstring unescaping. Closes #40 129 | 130 | - Prefer candidates where last segment matches. 131 | 132 | - Ignore temp files when globbing directories. 133 | 134 | - Make sure the PushBackReader is actually closed. 135 | 136 | - Remove lein1 compatibility gunk. 137 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Source code distributed under the Eclipse Public License - v 1.0: 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF 5 | 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 code and 12 | documentation distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | 16 | i) changes to the Program, and 17 | 18 | ii) additions to the Program; 19 | 20 | where such changes and/or additions to the Program originate from and 21 | are distributed by that particular Contributor. A Contribution 22 | 'originates' from a Contributor if it was added to the Program by such 23 | Contributor itself or anyone acting on such Contributor's 24 | behalf. Contributions do not include additions to the Program which: 25 | (i) are separate modules of software distributed in conjunction with 26 | the Program under their own license agreement, and (ii) are not 27 | derivative works of the Program. 28 | 29 | "Contributor" means any person or entity that distributes the Program. 30 | 31 | "Licensed Patents" mean patent claims licensable by a Contributor 32 | which are necessarily infringed by the use or sale of its Contribution 33 | alone or when combined with the Program. 34 | 35 | "Program" means the Contributions distributed in accordance with this 36 | Agreement. 37 | 38 | "Recipient" means anyone who receives the Program under this 39 | Agreement, including all Contributors. 40 | 41 | 2. GRANT OF RIGHTS 42 | 43 | a) Subject to the terms of this Agreement, each Contributor hereby 44 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 45 | license to reproduce, prepare derivative works of, publicly display, 46 | publicly perform, distribute and sublicense the Contribution of such 47 | Contributor, if any, and such derivative works, in source code and 48 | object code form. 49 | 50 | b) Subject to the terms of this Agreement, each Contributor hereby 51 | grants Recipient a non-exclusive, worldwide, royalty-free patent 52 | license under Licensed Patents to make, use, sell, offer to sell, 53 | import and otherwise transfer the Contribution of such Contributor, if 54 | any, in source code and object code form. This patent license shall 55 | apply to the combination of the Contribution and the Program if, at 56 | the time the Contribution is added by the Contributor, such addition 57 | of the Contribution causes such combination to be covered by the 58 | Licensed Patents. The patent license shall not apply to any other 59 | combinations which include the Contribution. No hardware per se is 60 | licensed hereunder. 61 | 62 | c) Recipient understands that although each Contributor grants the 63 | licenses to its Contributions set forth herein, no assurances are 64 | provided by any Contributor that the Program does not infringe the 65 | patent or other intellectual property rights of any other entity. Each 66 | Contributor disclaims any liability to Recipient for claims brought by 67 | any other entity based on infringement of intellectual property rights 68 | or otherwise. As a condition to exercising the rights and licenses 69 | granted hereunder, each Recipient hereby assumes sole responsibility 70 | to secure any other intellectual property rights needed, if any. For 71 | example, if a third party patent license is required to allow 72 | Recipient to distribute the Program, it is Recipient's responsibility 73 | to acquire that license before distributing the Program. 74 | 75 | d) Each Contributor represents that to its knowledge it has sufficient 76 | copyright rights in its Contribution, if any, to grant the copyright 77 | license set forth in this Agreement. 78 | 79 | 3. REQUIREMENTS 80 | 81 | A Contributor may choose to distribute the Program in object code form 82 | under its own license agreement, provided that: 83 | 84 | a) it complies with the terms and conditions of this Agreement; and 85 | 86 | b) its license agreement: 87 | 88 | i) effectively disclaims on behalf of all Contributors all warranties 89 | and conditions, express and implied, including warranties or 90 | conditions of title and non-infringement, and implied warranties or 91 | conditions of merchantability and fitness for a particular purpose; 92 | 93 | ii) effectively excludes on behalf of all Contributors all liability 94 | for damages, including direct, indirect, special, incidental and 95 | consequential damages, such as lost profits; 96 | 97 | iii) states that any provisions which differ from this Agreement are 98 | offered by that Contributor alone and not by any other party; and 99 | 100 | iv) states that source code for the Program is available from such 101 | Contributor, and informs licensees how to obtain it in a reasonable 102 | manner on or through a medium customarily used for software exchange. 103 | 104 | When the Program is made available in source code form: 105 | 106 | a) it must be made available under this Agreement; and 107 | 108 | b) a copy of this Agreement must be included with each copy of the Program. 109 | 110 | Contributors may not remove or alter any copyright notices contained 111 | within the Program. 112 | 113 | Each Contributor must identify itself as the originator of its 114 | Contribution, if any, in a manner that reasonably allows subsequent 115 | Recipients to identify the originator of the Contribution. 116 | 117 | 4. COMMERCIAL DISTRIBUTION 118 | 119 | Commercial distributors of software may accept certain 120 | responsibilities with respect to end users, business partners and the 121 | like. While this license is intended to facilitate the commercial use 122 | of the Program, the Contributor who includes the Program in a 123 | commercial product offering should do so in a manner which does not 124 | create potential liability for other Contributors. Therefore, if a 125 | Contributor includes the Program in a commercial product offering, 126 | such Contributor ("Commercial Contributor") hereby agrees to defend 127 | and indemnify every other Contributor ("Indemnified Contributor") 128 | against any losses, damages and costs (collectively "Losses") arising 129 | from claims, lawsuits and other legal actions brought by a third party 130 | against the Indemnified Contributor to the extent caused by the acts 131 | or omissions of such Commercial Contributor in connection with its 132 | distribution of the Program in a commercial product offering. The 133 | obligations in this section do not apply to any claims or Losses 134 | relating to any actual or alleged intellectual property 135 | infringement. In order to qualify, an Indemnified Contributor must: a) 136 | promptly notify the Commercial Contributor in writing of such claim, 137 | and b) allow the Commercial Contributor tocontrol, and cooperate with 138 | the Commercial Contributor in, the defense and any related settlement 139 | negotiations. The Indemnified Contributor may participate in any such 140 | claim at its own expense. 141 | 142 | For example, a Contributor might include the Program in a commercial 143 | product offering, Product X. That Contributor is then a Commercial 144 | Contributor. If that Commercial Contributor then makes performance 145 | claims, or offers warranties related to Product X, those performance 146 | claims and warranties are such Commercial Contributor's responsibility 147 | alone. Under this section, the Commercial Contributor would have to 148 | defend claims against the other Contributors related to those 149 | performance claims and warranties, and if a court requires any other 150 | Contributor to pay any damages as a result, the Commercial Contributor 151 | must pay those damages. 152 | 153 | 5. NO WARRANTY 154 | 155 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 156 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 157 | KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY 158 | WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 159 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 160 | responsible for determining the appropriateness of using and 161 | distributing the Program and assumes all risks associated with its 162 | exercise of rights under this Agreement , including but not limited to 163 | the risks and costs of program errors, compliance with applicable 164 | laws, damage to or loss of data, programs or equipment, and 165 | unavailability or interruption of operations. 166 | 167 | 6. DISCLAIMER OF LIABILITY 168 | 169 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR 170 | ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 171 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 172 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 173 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 174 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 175 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 176 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 177 | 178 | 7. GENERAL 179 | 180 | If any provision of this Agreement is invalid or unenforceable under 181 | applicable law, it shall not affect the validity or enforceability of 182 | the remainder of the terms of this Agreement, and without further 183 | action by the parties hereto, such provision shall be reformed to the 184 | minimum extent necessary to make such provision valid and enforceable. 185 | 186 | If Recipient institutes patent litigation against any entity 187 | (including a cross-claim or counterclaim in a lawsuit) alleging that 188 | the Program itself (excluding combinations of the Program with other 189 | software or hardware) infringes such Recipient's patent(s), then such 190 | Recipient's rights granted under Section 2(b) shall terminate as of 191 | the date such litigation is filed. 192 | 193 | All Recipient's rights under this Agreement shall terminate if it 194 | fails to comply with any of the material terms or conditions of this 195 | Agreement and does not cure such failure in a reasonable period of 196 | time after becoming aware of such noncompliance. If all Recipient's 197 | rights under this Agreement terminate, Recipient agrees to cease use 198 | and distribution of the Program as soon as reasonably 199 | practicable. However, Recipient's obligations under this Agreement and 200 | any licenses granted by Recipient relating to the Program shall 201 | continue and survive. 202 | 203 | Everyone is permitted to copy and distribute copies of this Agreement, 204 | but in order to avoid inconsistency the Agreement is copyrighted and 205 | may only be modified in the following manner. The Agreement Steward 206 | reserves the right to publish new versions (including revisions) of 207 | this Agreement from time to time. No one other than the Agreement 208 | Steward has the right to modify this Agreement. The Eclipse Foundation 209 | is the initial Agreement Steward. The Eclipse Foundation may assign 210 | the responsibility to serve as the Agreement Steward to a suitable 211 | separate entity. Each new version of the Agreement will be given a 212 | distinguishing version number. The Program (including Contributions) 213 | may always be distributed subject to the version of the Agreement 214 | under which it was received. In addition, after a new version of the 215 | Agreement is published, Contributor may elect to distribute the 216 | Program (including its Contributions) under the new version. Except as 217 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives 218 | no rights or licenses to the intellectual property of any Contributor 219 | under this Agreement, whether expressly, by implication, estoppel or 220 | otherwise. All rights in the Program not expressly granted under this 221 | Agreement are reserved. 222 | 223 | This Agreement is governed by the laws of the State of New York and 224 | the intellectual property laws of the United States of America. No 225 | party to this Agreement will bring a legal action under this Agreement 226 | more than one year after the cause of action arose. Each party waives 227 | its rights to a jury trial in any resulting litigation. 228 | 229 | 230 | 231 | Images distributed under the Creative Commons Attribution + ShareAlike 232 | License version 3.0: 233 | 234 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS 235 | CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS 236 | PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE 237 | WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS 238 | PROHIBITED. 239 | 240 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND 241 | AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS 242 | LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU 243 | THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH 244 | TERMS AND CONDITIONS. 245 | 246 | 1. Definitions 247 | 248 | "Adaptation" means a work based upon the Work, or upon the Work 249 | and other pre-existing works, such as a translation, adaptation, 250 | derivative work, arrangement of music or other alterations of a 251 | literary or artistic work, or phonogram or performance and 252 | includes cinematographic adaptations or any other form in which 253 | the Work may be recast, transformed, or adapted including in any 254 | form recognizably derived from the original, except that a work 255 | that constitutes a Collection will not be considered an Adaptation 256 | for the purpose of this License. For the avoidance of doubt, where 257 | the Work is a musical work, performance or phonogram, the 258 | synchronization of the Work in timed-relation with a moving image 259 | ("synching") will be considered an Adaptation for the purpose of 260 | this License. 261 | 262 | "Collection" means a collection of literary or artistic works, 263 | such as encyclopedias and anthologies, or performances, phonograms 264 | or broadcasts, or other works or subject matter other than works 265 | listed in Section 1(f) below, which, by reason of the selection 266 | and arrangement of their contents, constitute intellectual 267 | creations, in which the Work is included in its entirety in 268 | unmodified form along with one or more other contributions, each 269 | constituting separate and independent works in themselves, which 270 | together are assembled into a collective whole. A work that 271 | constitutes a Collection will not be considered an Adaptation (as 272 | defined below) for the purposes of this License. 273 | 274 | "Creative Commons Compatible License" means a license that is 275 | listed at http://creativecommons.org/compatiblelicenses that has 276 | been approved by Creative Commons as being essentially equivalent 277 | to this License, including, at a minimum, because that license: 278 | (i) contains terms that have the same purpose, meaning and effect 279 | as the License Elements of this License; and, (ii) explicitly 280 | permits the relicensing of adaptations of works made available 281 | under that license under this License or a Creative Commons 282 | jurisdiction license with the same License Elements as this 283 | License. 284 | 285 | "Distribute" means to make available to the public the original 286 | and copies of the Work or Adaptation, as appropriate, through sale 287 | or other transfer of ownership. 288 | 289 | "License Elements" means the following high-level license 290 | attributes as selected by Licensor and indicated in the title of 291 | this License: Attribution, ShareAlike. 292 | 293 | "Licensor" means the individual, individuals, entity or entities 294 | that offer(s) the Work under the terms of this License. 295 | 296 | "Original Author" means, in the case of a literary or artistic 297 | work, the individual, individuals, entity or entities who created 298 | the Work or if no individual or entity can be identified, the 299 | publisher; and in addition (i) in the case of a performance the 300 | actors, singers, musicians, dancers, and other persons who act, 301 | sing, deliver, declaim, play in, interpret or otherwise perform 302 | literary or artistic works or expressions of folklore; (ii) in the 303 | case of a phonogram the producer being the person or legal entity 304 | who first fixes the sounds of a performance or other sounds; and, 305 | (iii) in the case of broadcasts, the organization that transmits 306 | the broadcast. 307 | 308 | "Work" means the literary and/or artistic work offered under the 309 | terms of this License including without limitation any production 310 | in the literary, scientific and artistic domain, whatever may be 311 | the mode or form of its expression including digital form, such as 312 | a book, pamphlet and other writing; a lecture, address, sermon or 313 | other work of the same nature; a dramatic or dramatico-musical 314 | work; a choreographic work or entertainment in dumb show; a 315 | musical composition with or without words; a cinematographic work 316 | to which are assimilated works expressed by a process analogous to 317 | cinematography; a work of drawing, painting, architecture, 318 | sculpture, engraving or lithography; a photographic work to which 319 | are assimilated works expressed by a process analogous to 320 | photography; a work of applied art; an illustration, map, plan, 321 | sketch or three-dimensional work relative to geography, 322 | topography, architecture or science; a performance; a broadcast; a 323 | phonogram; a compilation of data to the extent it is protected as 324 | a copyrightable work; or a work performed by a variety or circus 325 | performer to the extent it is not otherwise considered a literary 326 | or artistic work. 327 | 328 | "You" means an individual or entity exercising rights under this 329 | License who has not previously violated the terms of this License 330 | with respect to the Work, or who has received express permission 331 | from the Licensor to exercise rights under this License despite a 332 | previous violation. 333 | 334 | "Publicly Perform" means to perform public recitations of the Work 335 | and to communicate to the public those public recitations, by any 336 | means or process, including by wire or wireless means or public 337 | digital performances; to make available to the public Works in 338 | such a way that members of the public may access these Works from 339 | a place and at a place individually chosen by them; to perform the 340 | Work to the public by any means or process and the communication 341 | to the public of the performances of the Work, including by public 342 | digital performance; to broadcast and rebroadcast the Work by any 343 | means including signs, sounds or images. 344 | 345 | "Reproduce" means to make copies of the Work by any means 346 | including without limitation by sound or visual recordings and the 347 | right of fixation and reproducing fixations of the Work, including 348 | storage of a protected performance or phonogram in digital form or 349 | other electronic medium. 350 | 351 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, 352 | limit, or restrict any uses free from copyright or rights arising from 353 | limitations or exceptions that are provided for in connection with the 354 | copyright protection under copyright law or other applicable laws. 355 | 356 | 3. License Grant. Subject to the terms and conditions of this License, 357 | Licensor hereby grants You a worldwide, royalty-free, non-exclusive, 358 | perpetual (for the duration of the applicable copyright) license to 359 | exercise the rights in the Work as stated below: 360 | 361 | to Reproduce the Work, to incorporate the Work into one or more 362 | Collections, and to Reproduce the Work as incorporated in the 363 | Collections; 364 | 365 | to create and Reproduce Adaptations provided that any such 366 | Adaptation, including any translation in any medium, takes 367 | reasonable steps to clearly label, demarcate or otherwise identify 368 | that changes were made to the original Work. For example, a 369 | translation could be marked "The original work was translated from 370 | English to Spanish," or a modification could indicate "The 371 | original work has been modified."; 372 | 373 | to Distribute and Publicly Perform the Work including as 374 | incorporated in Collections; and, 375 | 376 | to Distribute and Publicly Perform Adaptations. 377 | 378 | For the avoidance of doubt: 379 | 380 | Non-waivable Compulsory License Schemes. In those 381 | jurisdictions in which the right to collect royalties through 382 | any statutory or compulsory licensing scheme cannot be waived, 383 | the Licensor reserves the exclusive right to collect such 384 | royalties for any exercise by You of the rights granted under 385 | this License; 386 | 387 | Waivable Compulsory License Schemes. In those jurisdictions in 388 | which the right to collect royalties through any statutory or 389 | compulsory licensing scheme can be waived, the Licensor waives 390 | the exclusive right to collect such royalties for any exercise 391 | by You of the rights granted under this License; and, 392 | 393 | Voluntary License Schemes. The Licensor waives the right to 394 | collect royalties, whether individually or, in the event that 395 | the Licensor is a member of a collecting society that 396 | administers voluntary licensing schemes, via that society, 397 | from any exercise by You of the rights granted under this 398 | License. 399 | 400 | The above rights may be exercised in all media and formats whether now 401 | known or hereafter devised. The above rights include the right to make 402 | such modifications as are technically necessary to exercise the rights 403 | in other media and formats. Subject to Section 8(f), all rights not 404 | expressly granted by Licensor are hereby reserved. 405 | 406 | 4. Restrictions. The license granted in Section 3 above is expressly 407 | made subject to and limited by the following restrictions: 408 | 409 | You may Distribute or Publicly Perform the Work only under the 410 | terms of this License. You must include a copy of, or the Uniform 411 | Resource Identifier (URI) for, this License with every copy of the 412 | Work You Distribute or Publicly Perform. You may not offer or 413 | impose any terms on the Work that restrict the terms of this 414 | License or the ability of the recipient of the Work to exercise 415 | the rights granted to that recipient under the terms of the 416 | License. You may not sublicense the Work. You must keep intact all 417 | notices that refer to this License and to the disclaimer of 418 | warranties with every copy of the Work You Distribute or Publicly 419 | Perform. When You Distribute or Publicly Perform the Work, You may 420 | not impose any effective technological measures on the Work that 421 | restrict the ability of a recipient of the Work from You to 422 | exercise the rights granted to that recipient under the terms of 423 | the License. This Section 4(a) applies to the Work as incorporated 424 | in a Collection, but this does not require the Collection apart 425 | from the Work itself to be made subject to the terms of this 426 | License. If You create a Collection, upon notice from any Licensor 427 | You must, to the extent practicable, remove from the Collection 428 | any credit as required by Section 4(c), as requested. If You 429 | create an Adaptation, upon notice from any Licensor You must, to 430 | the extent practicable, remove from the Adaptation any credit as 431 | required by Section 4(c), as requested. 432 | 433 | You may Distribute or Publicly Perform an Adaptation only under 434 | the terms of: (i) this License; (ii) a later version of this 435 | License with the same License Elements as this License; (iii) a 436 | Creative Commons jurisdiction license (either this or a later 437 | license version) that contains the same License Elements as this 438 | License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative 439 | Commons Compatible License. If you license the Adaptation under 440 | one of the licenses mentioned in (iv), you must comply with the 441 | terms of that license. If you license the Adaptation under the 442 | terms of any of the licenses mentioned in (i), (ii) or (iii) (the 443 | "Applicable License"), you must comply with the terms of the 444 | Applicable License generally and the following provisions: (I) You 445 | must include a copy of, or the URI for, the Applicable License 446 | with every copy of each Adaptation You Distribute or Publicly 447 | Perform; (II) You may not offer or impose any terms on the 448 | Adaptation that restrict the terms of the Applicable License or 449 | the ability of the recipient of the Adaptation to exercise the 450 | rights granted to that recipient under the terms of the Applicable 451 | License; (III) You must keep intact all notices that refer to the 452 | Applicable License and to the disclaimer of warranties with every 453 | copy of the Work as included in the Adaptation You Distribute or 454 | Publicly Perform; (IV) when You Distribute or Publicly Perform the 455 | Adaptation, You may not impose any effective technological 456 | measures on the Adaptation that restrict the ability of a 457 | recipient of the Adaptation from You to exercise the rights 458 | granted to that recipient under the terms of the Applicable 459 | License. This Section 4(b) applies to the Adaptation as 460 | incorporated in a Collection, but this does not require the 461 | Collection apart from the Adaptation itself to be made subject to 462 | the terms of the Applicable License. 463 | 464 | If You Distribute, or Publicly Perform the Work or any Adaptations 465 | or Collections, You must, unless a request has been made pursuant 466 | to Section 4(a), keep intact all copyright notices for the Work 467 | and provide, reasonable to the medium or means You are utilizing: 468 | (i) the name of the Original Author (or pseudonym, if applicable) 469 | if supplied, and/or if the Original Author and/or Licensor 470 | designate another party or parties (e.g., a sponsor institute, 471 | publishing entity, journal) for attribution ("Attribution 472 | Parties") in Licensor's copyright notice, terms of service or by 473 | other reasonable means, the name of such party or parties; (ii) 474 | the title of the Work if supplied; (iii) to the extent reasonably 475 | practicable, the URI, if any, that Licensor specifies to be 476 | associated with the Work, unless such URI does not refer to the 477 | copyright notice or licensing information for the Work; and (iv) , 478 | consistent with Ssection 3(b), in the case of an Adaptation, a 479 | credit identifying the use of the Work in the Adaptation (e.g., 480 | "French translation of the Work by Original Author," or 481 | "Screenplay based on original Work by Original Author"). The 482 | credit required by this Section 4(c) may be implemented in any 483 | reasonable manner; provided, however, that in the case of a 484 | Adaptation or Collection, at a minimum such credit will appear, if 485 | a credit for all contributing authors of the Adaptation or 486 | Collection appears, then as part of these credits and in a manner 487 | at least as prominent as the credits for the other contributing 488 | authors. For the avoidance of doubt, You may only use the credit 489 | required by this Section for the purpose of attribution in the 490 | manner set out above and, by exercising Your rights under this 491 | License, You may not implicitly or explicitly assert or imply any 492 | connection with, sponsorship or endorsement by the Original 493 | Author, Licensor and/or Attribution Parties, as appropriate, of 494 | You or Your use of the Work, without the separate, express prior 495 | written permission of the Original Author, Licensor and/or 496 | Attribution Parties. 497 | 498 | Except as otherwise agreed in writing by the Licensor or as may be 499 | otherwise permitted by applicable law, if You Reproduce, 500 | Distribute or Publicly Perform the Work either by itself or as 501 | part of any Adaptations or Collections, You must not distort, 502 | mutilate, modify or take other derogatory action in relation to 503 | the Work which would be prejudicial to the Original Author's honor 504 | or reputation. Licensor agrees that in those jurisdictions 505 | (e.g. Japan), in which any exercise of the right granted in 506 | Section 3(b) of this License (the right to make Adaptations) would 507 | be deemed to be a distortion, mutilation, modification or other 508 | derogatory action prejudicial to the Original Author's honor and 509 | reputation, the Licensor will waive or not assert, as appropriate, 510 | this Section, to the fullest extent permitted by the applicable 511 | national law, to enable You to reasonably exercise Your right 512 | under Section 3(b) of this License (right to make Adaptations) but 513 | not otherwise. 514 | 515 | 5. Representations, Warranties and Disclaimer 516 | 517 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, 518 | LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR 519 | WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, 520 | STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF 521 | TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, 522 | NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, 523 | OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT 524 | DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED 525 | WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 526 | 527 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY 528 | APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY 529 | LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR 530 | EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, 531 | EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 532 | 533 | 7. Termination 534 | 535 | This License and the rights granted hereunder will terminate 536 | automatically upon any breach by You of the terms of this 537 | License. Individuals or entities who have received Adaptations or 538 | Collections from You under this License, however, will not have 539 | their licenses terminated provided such individuals or entities 540 | remain in full compliance with those licenses. Sections 1, 2, 5, 541 | 6, 7, and 8 will survive any termination of this License. 542 | 543 | Subject to the above terms and conditions, the license granted 544 | here is perpetual (for the duration of the applicable copyright in 545 | the Work). Notwithstanding the above, Licensor reserves the right 546 | to release the Work under different license terms or to stop 547 | distributing the Work at any time; provided, however that any such 548 | election will not serve to withdraw this License (or any other 549 | license that has been, or is required to be, granted under the 550 | terms of this License), and this License will continue in full 551 | force and effect unless terminated as stated above. 552 | 553 | 8. Miscellaneous 554 | 555 | Each time You Distribute or Publicly Perform the Work or a 556 | Collection, the Licensor offers to the recipient a license to the 557 | Work on the same terms and conditions as the license granted to 558 | You under this License. 559 | 560 | Each time You Distribute or Publicly Perform an Adaptation, 561 | Licensor offers to the recipient a license to the original Work on 562 | the same terms and conditions as the license granted to You under 563 | this License. 564 | 565 | If any provision of this License is invalid or unenforceable under 566 | applicable law, it shall not affect the validity or enforceability 567 | of the remainder of the terms of this License, and without further 568 | action by the parties to this agreement, such provision shall be 569 | reformed to the minimum extent necessary to make such provision 570 | valid and enforceable. 571 | 572 | No term or provision of this License shall be deemed waived and no 573 | breach consented to unless such waiver or consent shall be in 574 | writing and signed by the party to be charged with such waiver or 575 | consent. 576 | 577 | This License constitutes the entire agreement between the parties 578 | with respect to the Work licensed here. There are no 579 | understandings, agreements or representations with respect to the 580 | Work not specified here. Licensor shall not be bound by any 581 | additional provisions that may appear in any communication from 582 | You. This License may not be modified without the mutual written 583 | agreement of the Licensor and You. 584 | 585 | The rights granted under, and the subject matter referenced, in 586 | this License were drafted utilizing the terminology of the Berne 587 | Convention for the Protection of Literary and Artistic Works (as 588 | amended on September 28, 1979), the Rome Convention of 1961, the 589 | WIPO Copyright Treaty of 1996, the WIPO Performances and 590 | Phonograms Treaty of 1996 and the Universal Copyright Convention 591 | (as revised on July 24, 1971). These rights and subject matter 592 | take effect in the relevant jurisdiction in which the License 593 | terms are sought to be enforced according to the corresponding 594 | provisions of the implementation of those treaty provisions in the 595 | applicable national law. If the standard suite of rights granted 596 | under applicable copyright law includes additional rights not 597 | granted under this License, such additional rights are deemed to 598 | be included in the License; this License is not intended to 599 | restrict the license of any rights under applicable law. 600 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slamhound 2 | 3 | They sent a slamhound on Turner's trail in New Delhi, slotted it 4 | to his pheromones and the color of his hair. It caught up with him 5 | on a street called Chandni Chauk and came scrambling for his 6 | rented BMW through a forest of bare brown legs and pedicab 7 | tires. Its core was a kilogram of recrystallized hexogene and 8 | flaked TNT. He didn't see it coming. The last he saw of India was 9 | the pink stucco facade of a place called the Khush-Oil Hotel. 10 | 11 | Because he had a good agent, he had a good contract. Because he 12 | had a good contract, he was in Singapore an hour after the 13 | explosion. Most of him, anyway. The Dutch surgeon liked to joke 14 | about that, how an unspecified percentage of Turner hadn't made it 15 | out of Palam International on that first flight and had to spend 16 | the night there in a shed, in a support vat. 17 | 18 | It took the Dutchman and his team three months to put Turner 19 | together again. They cloned a square meter of skin for him, grew 20 | it on slabs of collagen and shark-cartilage polysaccharides. They 21 | bought eyes and genitals on the open market. The eyes were green. 22 | 23 | -- Count Zero, page 1. By William Gibson 24 | 25 | Slamhound rips your ns form apart and reconstructs it. No Dutch 26 | surgeon required. 27 | 28 | Add `[slamhound "1.5.5"]` to the `:dependencies` of your `:user` profile. 29 | 30 | ### This project is no longer under active development. 31 | 32 | ## Screencast 33 | 34 | 35 | 36 | ## Leiningen Usage 37 | 38 | Make an alias for `run -m slam.hound` in your `:user` profile: 39 | 40 | ```clj 41 | :aliases {"slamhound" ["run" "-m" "slam.hound"]} 42 | ``` 43 | 44 | Take a namespace with a sparse ns form that won't compile: 45 | 46 | $ cat src/my/namespace.clj # before: ns form is missing clauses 47 | 48 | (ns my.namespace 49 | "I have a doc string.") 50 | 51 | (defn -main [& args] 52 | (pprint args) 53 | (io/copy (ByteArrayInputStream. (.getBytes "hello")) 54 | (first args))) 55 | 56 | Then run slamhound on it: 57 | 58 | $ lein slamhound src/my/namespace.clj # [... thinking ...] 59 | 60 | $ cat src/my/namespace.clj # after: spits out new ns form 61 | (ns my.namespace 62 | "I have a doc string." 63 | (:require [clojure.java.io :as io] 64 | [clojure.pprint :refer [pprint]]) 65 | (:import (java.io ByteArrayInputStream))) 66 | 67 | Like magic. 68 | 69 | Running on a directory will perform the same operation on every .clj file inside. 70 | 71 | ## Repl Usage 72 | 73 | You can reconstruct namespaces from a repl to avoid the slow startup time: 74 | 75 | user=> (require '[slam.hound :refer [reconstruct]]) 76 | nil 77 | user=> (println (reconstruct "src/my/namespace.clj")) 78 | (ns my.namespace 79 | "I have a doc string." 80 | (:require [clojure.java.io :as io] 81 | [clojure.pprint :refer [pprint]]) 82 | (:import (java.io ByteArrayInputStream))) 83 | 84 | Or to reconstruct files in place: 85 | 86 | user=> (slam.hound/swap-in-reconstructed-ns-form "src/my/namespace.clj") 87 | nil 88 | ;; Reload the file in your editor to pick up changes 89 | 90 | ## Emacs Usage 91 | 92 | The included `slamhound.el` allows for convenient access within nREPL 93 | or SLIME sessions via `M-x slamhound`. Install manually or via 94 | [Marmalade](http://marmalade-repo.org). 95 | 96 | ## Vim Usage 97 | 98 | Install [`vim-slamhound`](https://github.com/guns/vim-slamhound) for use 99 | of the `:Slamhound` command within Clojure buffers. 100 | 101 | ## Light Table Usage 102 | 103 | Install [`slamhound-lt`](https://github.com/chadhq/slamhound-lt) from the 104 | central plugin repository. 105 | 106 | ## Shortcomings 107 | 108 | ### Syntax-quoted references 109 | 110 | Slamhound will only find references in a namespace that are consumed 111 | within the namespace itself. For example, if you have a macro that 112 | refers to a var inside syntax-quote (backtick), but the macro is only 113 | called from other namespaces, then Slamhound won't detect the reference 114 | and will instead report the failure in the namespace in which the macro 115 | is called. 116 | 117 | You can work around this problem by attaching dummy metadata to the 118 | `defmacro` form to prevent it from compiling without the necessary 119 | references being present: 120 | 121 | ```clj 122 | (defmacro defexample 123 | "Metadata can be attached to a macro by specifying an `attr-map` 124 | between the docstring and the params vector." 125 | {:requires [AClass a-var an/aliased-var #'a-macro]} 126 | [name & args] 127 | `(do (def ~(str name \*) (AClass. ~@args)) 128 | (def ~(str name \-) (a-var ~@args)) 129 | (def ~(str name \+) (an/aliased-var ~@args)) 130 | (def ~(str name \!) (a-macro ~@args)))) 131 | ``` 132 | 133 | Notice that macros must be referenced with `#'` to avoid a `Can't take 134 | the value of a macro` error. 135 | 136 | If the syntax quoted references refer to functions, classes, or constant 137 | data, unquoting them will work as well: 138 | 139 | ```clj 140 | (defmacro defexample 141 | {:requires [#'a-macro]} 142 | [name & args] 143 | `(do (def ~(str name \*) (new ~AClass ~@args)) 144 | (def ~(str name \-) (~a-var ~@args)) 145 | (def ~(str name \+) (~an/aliased-var ~@args)) 146 | (def ~(str name \!) (a-macro ~@args)))) 147 | ``` 148 | 149 | In this case `Aclass`, `a-var`, and `an/aliased-var` must be resolved at 150 | compile time, so Slamhound will successfully find these references. 151 | 152 | Note that the shorter reader macro form `(AClass. ~@args)` must be 153 | expanded to `(new ~AClass ~@args)` and that macros cannot be unquoted. 154 | 155 | ### Fully qualified and dynamically resolved Vars 156 | 157 | Slamhound will also not find references to fully-qualified vars or 158 | vars resolved at runtime since it relies on detecting compilation 159 | failures to determine when it's done. 160 | 161 | ### PermGen memory pressure 162 | 163 | Slamhound aggressively creates and destroys namespaces during 164 | reconstruction. This taxes the class loading system, so there is a 165 | possibility that the JVM may run out of PermGen space in large projects. 166 | If this happens, try running your JVM with the following option to 167 | enable garbage collection of discarded classes: 168 | 169 | ``` 170 | -XX:+CMSClassUnloadingEnabled 171 | ``` 172 | 173 | ### Side effects during namespace evaluation 174 | 175 | As namespaces may potentially be reloaded many times during 176 | reconstruction, it is important that no side-effects (aside from var 177 | definition) occur during namespace evaluation. 178 | 179 | ## Leiningen 1.x 180 | 181 | The `lein-slamhound` plugin is deprecated, and the `:aliases` approach 182 | above is recommended for users of Leiningen 2. However, if you are 183 | still using Leiningen 1.x you can use the `run` task: 184 | 185 | $ lein run -m slam.hound src/foo 186 | 187 | Since Leiningen 1.x doesn't support partially-applied aliases, you 188 | would have to make a shell alias if you don't want to type the full 189 | invocation out every time. 190 | 191 | ## License 192 | 193 | Copyright © 2011-2012 Phil Hagelberg and contributors 194 | 195 | Distributed under the Eclipse Public License, the same as Clojure. 196 | -------------------------------------------------------------------------------- /dev-resources/test-comment-headers.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | ;; Copyright © Phil Hagelberg 4 | ;; Eclipse License 5 | ;; Trailing tabs here are intentional! 6 | 7 | 8 | 9 | (ns slamhound-test) 10 | -------------------------------------------------------------------------------- /dev-resources/test-comment-headers.out: -------------------------------------------------------------------------------- 1 | ;; Copyright © Phil Hagelberg 2 | ;; Eclipse License 3 | ;; Trailing tabs here are intentional! 4 | 5 | (ns slamhound-test) 6 | -------------------------------------------------------------------------------- /dev-resources/test-disambiguation-of-join.in: -------------------------------------------------------------------------------- 1 | (ns disambiguate-join 2 | (:use clojure.string) 3 | (:require [clojure.string :refer [join]] 4 | [clojure.set :refer [join]])) 5 | 6 | (defn f [xs] 7 | (join "," xs)) 8 | -------------------------------------------------------------------------------- /dev-resources/test-disambiguation-of-join.out: -------------------------------------------------------------------------------- 1 | (ns disambiguate-join 2 | (:require [clojure.string :refer :all])) 3 | 4 | (defn f [xs] 5 | (join "," xs)) 6 | -------------------------------------------------------------------------------- /dev-resources/test-dotted-alias.in: -------------------------------------------------------------------------------- 1 | (ns slamhound.sample 2 | "Testing using dot in alias" 3 | (:require 4 | [clojure.string :as string] 5 | [clojure.java.io :as java.io])) 6 | 7 | (java.io/resource "some-resource") 8 | -------------------------------------------------------------------------------- /dev-resources/test-dotted-alias.out: -------------------------------------------------------------------------------- 1 | (ns slamhound.sample 2 | "Testing using dot in alias" 3 | (:require [clojure.java.io :as java.io])) 4 | 5 | (java.io/resource "some-resource") 6 | -------------------------------------------------------------------------------- /dev-resources/test-long-files.in: -------------------------------------------------------------------------------- 1 | (ns test-namespace 2 | (:require [clojure.string :as str] 3 | [clojure.java.io :as io] 4 | [clojure.set :as set])) 5 | 6 | (defn replace-commas [replacement] 7 | (str/replace "asdf" #"," replacement)) 8 | 9 | (defn replace-commas [replacement] 10 | (str/replace "asdf" #"," replacement)) 11 | 12 | (defn replace-commas [replacement] 13 | (str/replace "asdf" #"," replacement)) 14 | 15 | (defn replace-commas [replacement] 16 | (str/replace "asdf" #"," replacement)) 17 | 18 | (defn replace-commas [replacement] 19 | (str/replace "asdf" #"," replacement)) 20 | 21 | (defn replace-commas [replacement] 22 | (str/replace "asdf" #"," replacement)) 23 | 24 | (defn replace-commas [replacement] 25 | (str/replace "asdf" #"," replacement)) 26 | 27 | (defn replace-commas [replacement] 28 | (str/replace "asdf" #"," replacement)) 29 | 30 | (defn replace-commas [replacement] 31 | (str/replace "asdf" #"," replacement)) 32 | 33 | (defn replace-commas [replacement] 34 | (str/replace "asdf" #"," replacement)) 35 | 36 | (defn replace-commas [replacement] 37 | (str/replace "asdf" #"," replacement)) 38 | 39 | (defn replace-commas [replacement] 40 | (str/replace "asdf" #"," replacement)) 41 | 42 | (defn replace-commas [replacement] 43 | (str/replace "asdf" #"," replacement)) 44 | 45 | (defn replace-commas [replacement] 46 | (str/replace "asdf" #"," replacement)) 47 | 48 | (defn replace-commas [replacement] 49 | (str/replace "asdf" #"," replacement)) 50 | 51 | (defn replace-commas [replacement] 52 | (str/replace "asdf" #"," replacement)) 53 | 54 | (defn replace-commas [replacement] 55 | (str/replace "asdf" #"," replacement)) 56 | 57 | (defn replace-commas [replacement] 58 | (str/replace "asdf" #"," replacement)) 59 | 60 | (defn replace-commas [replacement] 61 | (str/replace "asdf" #"," replacement)) 62 | 63 | (defn replace-commas [replacement] 64 | (str/replace "asdf" #"," replacement)) 65 | 66 | (defn replace-commas [replacement] 67 | (str/replace "asdf" #"," replacement)) 68 | 69 | (defn replace-commas [replacement] 70 | (str/replace "asdf" #"," replacement)) 71 | 72 | (defn replace-commas [replacement] 73 | (str/replace "asdf" #"," replacement)) 74 | 75 | (defn replace-commas [replacement] 76 | (str/replace "asdf" #"," replacement)) 77 | 78 | (defn replace-commas [replacement] 79 | (str/replace "asdf" #"," replacement)) 80 | 81 | (defn replace-commas [replacement] 82 | (str/replace "asdf" #"," replacement)) 83 | 84 | (defn replace-commas [replacement] 85 | (str/replace "asdf" #"," replacement)) 86 | 87 | (defn replace-commas [replacement] 88 | (str/replace "asdf" #"," replacement)) 89 | 90 | (defn replace-commas [replacement] 91 | (str/replace "asdf" #"," replacement)) 92 | 93 | (defn replace-commas [replacement] 94 | (str/replace "asdf" #"," replacement)) 95 | 96 | (defn replace-commas [replacement] 97 | (str/replace "asdf" #"," replacement)) 98 | 99 | (defn replace-commas [replacement] 100 | (str/replace "asdf" #"," replacement)) 101 | 102 | (defn replace-commas [replacement] 103 | (str/replace "asdf" #"," replacement)) 104 | 105 | (defn replace-commas [replacement] 106 | (str/replace "asdf" #"," replacement)) 107 | 108 | (defn replace-commas [replacement] 109 | (str/replace "asdf" #"," replacement)) 110 | 111 | (defn replace-commas [replacement] 112 | (str/replace "asdf" #"," replacement)) 113 | 114 | (defn replace-commas [replacement] 115 | (str/replace "asdf" #"," replacement)) 116 | 117 | (defn replace-commas [replacement] 118 | (str/replace "asdf" #"," replacement)) 119 | 120 | (defn replace-commas [replacement] 121 | (str/replace "asdf" #"," replacement)) 122 | 123 | (defn replace-commas [replacement] 124 | (str/replace "asdf" #"," replacement)) 125 | 126 | (defn replace-commas [replacement] 127 | (str/replace "asdf" #"," replacement)) 128 | 129 | (defn replace-commas [replacement] 130 | (str/replace "asdf" #"," replacement)) 131 | 132 | (defn replace-commas [replacement] 133 | (str/replace "asdf" #"," replacement)) 134 | 135 | (defn replace-commas [replacement] 136 | (str/replace "asdf" #"," replacement)) 137 | 138 | (defn replace-commas [replacement] 139 | (str/replace "asdf" #"," replacement)) 140 | 141 | (defn replace-commas [replacement] 142 | (str/replace "asdf" #"," replacement)) 143 | 144 | (defn replace-commas [replacement] 145 | (str/replace "asdf" #"," replacement)) 146 | 147 | (defn replace-commas [replacement] 148 | (str/replace "asdf" #"," replacement)) 149 | 150 | (defn replace-commas [replacement] 151 | (str/replace "asdf" #"," replacement)) 152 | 153 | (defn replace-commas [replacement] 154 | (str/replace "asdf" #"," replacement)) 155 | 156 | (defn replace-commas [replacement] 157 | (str/replace "asdf" #"," replacement)) 158 | 159 | (defn replace-commas [replacement] 160 | (str/replace "asdf" #"," replacement)) 161 | 162 | (defn replace-commas [replacement] 163 | (str/replace "asdf" #"," replacement)) 164 | 165 | (defn replace-commas [replacement] 166 | (str/replace "asdf" #"," replacement)) 167 | 168 | (defn replace-commas [replacement] 169 | (str/replace "asdf" #"," replacement)) 170 | 171 | (defn replace-commas [replacement] 172 | (str/replace "asdf" #"," replacement)) 173 | 174 | (defn replace-commas [replacement] 175 | (str/replace "asdf" #"," replacement)) 176 | 177 | (defn replace-commas [replacement] 178 | (str/replace "asdf" #"," replacement)) 179 | 180 | (defn replace-commas [replacement] 181 | (str/replace "asdf" #"," replacement)) 182 | 183 | (defn replace-commas [replacement] 184 | (str/replace "asdf" #"," replacement)) 185 | 186 | (defn replace-commas [replacement] 187 | (str/replace "asdf" #"," replacement)) 188 | 189 | (defn replace-commas [replacement] 190 | (str/replace "asdf" #"," replacement)) 191 | 192 | (defn replace-commas [replacement] 193 | (str/replace "asdf" #"," replacement)) 194 | 195 | (defn replace-commas [replacement] 196 | (str/replace "asdf" #"," replacement)) 197 | 198 | (defn replace-commas [replacement] 199 | (str/replace "asdf" #"," replacement)) 200 | 201 | (defn replace-commas [replacement] 202 | (str/replace "asdf" #"," replacement)) 203 | 204 | (defn replace-commas [replacement] 205 | (str/replace "asdf" #"," replacement)) 206 | 207 | (defn replace-commas [replacement] 208 | (str/replace "asdf" #"," replacement)) 209 | 210 | (defn replace-commas [replacement] 211 | (str/replace "asdf" #"," replacement)) 212 | 213 | (defn replace-commas [replacement] 214 | (str/replace "asdf" #"," replacement)) 215 | 216 | (defn replace-commas [replacement] 217 | (str/replace "asdf" #"," replacement)) 218 | 219 | (defn replace-commas [replacement] 220 | (str/replace "asdf" #"," replacement)) 221 | 222 | (defn replace-commas [replacement] 223 | (str/replace "asdf" #"," replacement)) 224 | 225 | (defn replace-commas [replacement] 226 | (str/replace "asdf" #"," replacement)) 227 | 228 | (defn replace-commas [replacement] 229 | (str/replace "asdf" #"," replacement)) 230 | 231 | (defn replace-commas [replacement] 232 | (str/replace "asdf" #"," replacement)) 233 | 234 | (defn replace-commas [replacement] 235 | (str/replace "asdf" #"," replacement)) 236 | 237 | (defn replace-commas [replacement] 238 | (str/replace "asdf" #"," replacement)) 239 | 240 | (defn replace-commas [replacement] 241 | (str/replace "asdf" #"," replacement)) 242 | 243 | (defn replace-commas [replacement] 244 | (str/replace "asdf" #"," replacement)) 245 | 246 | (defn replace-commas [replacement] 247 | (str/replace "asdf" #"," replacement)) 248 | 249 | (defn replace-commas [replacement] 250 | (str/replace "asdf" #"," replacement)) 251 | 252 | (defn replace-commas [replacement] 253 | (str/replace "asdf" #"," replacement)) 254 | 255 | (defn replace-commas [replacement] 256 | (str/replace "asdf" #"," replacement)) 257 | 258 | (defn replace-commas [replacement] 259 | (str/replace "asdf" #"," replacement)) 260 | 261 | (defn replace-commas [replacement] 262 | (str/replace "asdf" #"," replacement)) 263 | 264 | (defn replace-commas [replacement] 265 | (str/replace "asdf" #"," replacement)) 266 | 267 | (defn replace-commas [replacement] 268 | (str/replace "asdf" #"," replacement)) 269 | 270 | (defn replace-commas [replacement] 271 | (str/replace "asdf" #"," replacement)) 272 | 273 | (defn replace-commas [replacement] 274 | (str/replace "asdf" #"," replacement)) 275 | 276 | (defn replace-commas [replacement] 277 | (str/replace "asdf" #"," replacement)) 278 | 279 | (defn replace-commas [replacement] 280 | (str/replace "asdf" #"," replacement)) 281 | 282 | (defn replace-commas [replacement] 283 | (str/replace "asdf" #"," replacement)) 284 | 285 | (defn replace-commas [replacement] 286 | (str/replace "asdf" #"," replacement)) 287 | 288 | (defn replace-commas [replacement] 289 | (str/replace "asdf" #"," replacement)) 290 | 291 | (defn replace-commas [replacement] 292 | (str/replace "asdf" #"," replacement)) 293 | 294 | (defn replace-commas [replacement] 295 | (str/replace "asdf" #"," replacement)) 296 | 297 | (defn replace-commas [replacement] 298 | (str/replace "asdf" #"," replacement)) 299 | 300 | (defn replace-commas [replacement] 301 | (str/replace "asdf" #"," replacement)) 302 | 303 | (defn replace-commas [replacement] 304 | (str/replace "asdf" #"," replacement)) 305 | 306 | (defn replace-commas [replacement] 307 | (str/replace "asdf" #"," replacement)) 308 | 309 | (defn replace-commas [replacement] 310 | (str/replace "asdf" #"," replacement)) 311 | 312 | (defn replace-commas [replacement] 313 | (str/replace "asdf" #"," replacement)) 314 | 315 | (defn replace-commas [replacement] 316 | (str/replace "asdf" #"," replacement)) 317 | 318 | (defn replace-commas [replacement] 319 | (str/replace "asdf" #"," replacement)) 320 | 321 | (defn replace-commas [replacement] 322 | (str/replace "asdf" #"," replacement)) 323 | 324 | (defn replace-commas [replacement] 325 | (str/replace "asdf" #"," replacement)) 326 | 327 | (defn replace-commas [replacement] 328 | (str/replace "asdf" #"," replacement)) 329 | 330 | (defn replace-commas [replacement] 331 | (str/replace "asdf" #"," replacement)) 332 | 333 | (defn replace-commas [replacement] 334 | (str/replace "asdf" #"," replacement)) 335 | 336 | (defn replace-commas [replacement] 337 | (str/replace "asdf" #"," replacement)) 338 | 339 | (defn replace-commas [replacement] 340 | (str/replace "asdf" #"," replacement)) 341 | 342 | (defn replace-commas [replacement] 343 | (str/replace "asdf" #"," replacement)) 344 | 345 | (defn replace-commas [replacement] 346 | (str/replace "asdf" #"," replacement)) 347 | 348 | (defn replace-commas [replacement] 349 | (str/replace "asdf" #"," replacement)) 350 | 351 | (defn replace-commas [replacement] 352 | (str/replace "asdf" #"," replacement)) 353 | 354 | (defn replace-commas [replacement] 355 | (str/replace "asdf" #"," replacement)) 356 | 357 | (defn replace-commas [replacement] 358 | (str/replace "asdf" #"," replacement)) 359 | 360 | (defn replace-commas [replacement] 361 | (str/replace "asdf" #"," replacement)) 362 | 363 | (defn replace-commas [replacement] 364 | (str/replace "asdf" #"," replacement)) 365 | 366 | (defn replace-commas [replacement] 367 | (str/replace "asdf" #"," replacement)) 368 | 369 | (defn replace-commas [replacement] 370 | (str/replace "asdf" #"," replacement)) 371 | 372 | (defn replace-commas [replacement] 373 | (str/replace "asdf" #"," replacement)) 374 | 375 | (defn replace-commas [replacement] 376 | (str/replace "asdf" #"," replacement)) 377 | 378 | (defn replace-commas [replacement] 379 | (str/replace "asdf" #"," replacement)) 380 | 381 | (defn replace-commas [replacement] 382 | (str/replace "asdf" #"," replacement)) 383 | 384 | (defn replace-commas [replacement] 385 | (str/replace "asdf" #"," replacement)) 386 | 387 | (defn replace-commas [replacement] 388 | (str/replace "asdf" #"," replacement)) 389 | 390 | (defn replace-commas [replacement] 391 | (str/replace "asdf" #"," replacement)) 392 | 393 | (defn replace-commas [replacement] 394 | (str/replace "asdf" #"," replacement)) 395 | 396 | (defn replace-commas [replacement] 397 | (str/replace "asdf" #"," replacement)) 398 | 399 | (defn replace-commas [replacement] 400 | (str/replace "asdf" #"," replacement)) 401 | 402 | (defn replace-commas [replacement] 403 | (str/replace "asdf" #"," replacement)) 404 | 405 | (defn replace-commas [replacement] 406 | (str/replace "asdf" #"," replacement)) 407 | 408 | (defn replace-commas [replacement] 409 | (str/replace "asdf" #"," replacement)) 410 | 411 | (defn replace-commas [replacement] 412 | (str/replace "asdf" #"," replacement)) 413 | 414 | (defn replace-commas [replacement] 415 | (str/replace "asdf" #"," replacement)) 416 | 417 | (defn replace-commas [replacement] 418 | (str/replace "asdf" #"," replacement)) 419 | 420 | (defn replace-commas [replacement] 421 | (str/replace "asdf" #"," replacement)) 422 | 423 | (defn replace-commas [replacement] 424 | (str/replace "asdf" #"," replacement)) 425 | 426 | (defn replace-commas [replacement] 427 | (str/replace "asdf" #"," replacement)) 428 | 429 | (defn replace-commas [replacement] 430 | (str/replace "asdf" #"," replacement)) 431 | 432 | (defn replace-commas [replacement] 433 | (str/replace "asdf" #"," replacement)) 434 | 435 | (defn replace-commas [replacement] 436 | (str/replace "asdf" #"," replacement)) 437 | 438 | (defn replace-commas [replacement] 439 | (str/replace "asdf" #"," replacement)) 440 | 441 | (defn replace-commas [replacement] 442 | (str/replace "asdf" #"," replacement)) 443 | 444 | (defn replace-commas [replacement] 445 | (str/replace "asdf" #"," replacement)) 446 | 447 | (defn replace-commas [replacement] 448 | (str/replace "asdf" #"," replacement)) 449 | 450 | (defn replace-commas [replacement] 451 | (str/replace "asdf" #"," replacement)) 452 | 453 | (defn replace-commas [replacement] 454 | (str/replace "asdf" #"," replacement)) 455 | 456 | (defn replace-commas [replacement] 457 | (str/replace "asdf" #"," replacement)) 458 | 459 | (defn replace-commas [replacement] 460 | (str/replace "asdf" #"," replacement)) 461 | 462 | (defn replace-commas [replacement] 463 | (str/replace "asdf" #"," replacement)) 464 | 465 | (defn replace-commas [replacement] 466 | (str/replace "asdf" #"," replacement)) 467 | 468 | (defn replace-commas [replacement] 469 | (str/replace "asdf" #"," replacement)) 470 | 471 | (defn replace-commas [replacement] 472 | (str/replace "asdf" #"," replacement)) 473 | 474 | (defn replace-commas [replacement] 475 | (str/replace "asdf" #"," replacement)) 476 | 477 | (defn replace-commas [replacement] 478 | (str/replace "asdf" #"," replacement)) 479 | 480 | (defn replace-commas [replacement] 481 | (str/replace "asdf" #"," replacement)) 482 | 483 | (defn replace-commas [replacement] 484 | (str/replace "asdf" #"," replacement)) 485 | 486 | (defn replace-commas [replacement] 487 | (str/replace "asdf" #"," replacement)) 488 | 489 | (defn replace-commas [replacement] 490 | (str/replace "asdf" #"," replacement)) 491 | 492 | (defn replace-commas [replacement] 493 | (str/replace "asdf" #"," replacement)) 494 | 495 | (defn replace-commas [replacement] 496 | (str/replace "asdf" #"," replacement)) 497 | 498 | (defn replace-commas [replacement] 499 | (str/replace "asdf" #"," replacement)) 500 | 501 | (defn replace-commas [replacement] 502 | (str/replace "asdf" #"," replacement)) 503 | 504 | (defn replace-commas [replacement] 505 | (str/replace "asdf" #"," replacement)) 506 | 507 | (defn replace-commas [replacement] 508 | (str/replace "asdf" #"," replacement)) 509 | 510 | (defn replace-commas [replacement] 511 | (str/replace "asdf" #"," replacement)) 512 | 513 | (defn replace-commas [replacement] 514 | (str/replace "asdf" #"," replacement)) 515 | 516 | (defn replace-commas [replacement] 517 | (str/replace "asdf" #"," replacement)) 518 | 519 | (defn replace-commas [replacement] 520 | (str/replace "asdf" #"," replacement)) 521 | 522 | (defn replace-commas [replacement] 523 | (str/replace "asdf" #"," replacement)) 524 | 525 | (defn replace-commas [replacement] 526 | (str/replace "asdf" #"," replacement)) 527 | 528 | (defn replace-commas [replacement] 529 | (str/replace "asdf" #"," replacement)) 530 | 531 | (defn replace-commas [replacement] 532 | (str/replace "asdf" #"," replacement)) 533 | 534 | (defn replace-commas [replacement] 535 | (str/replace "asdf" #"," replacement)) 536 | 537 | (defn replace-commas [replacement] 538 | (str/replace "asdf" #"," replacement)) 539 | 540 | (defn replace-commas [replacement] 541 | (str/replace "asdf" #"," replacement)) 542 | 543 | (defn replace-commas [replacement] 544 | (str/replace "asdf" #"," replacement)) 545 | 546 | (defn replace-commas [replacement] 547 | (str/replace "asdf" #"," replacement)) 548 | 549 | (defn replace-commas [replacement] 550 | (str/replace "asdf" #"," replacement)) 551 | 552 | (defn replace-commas [replacement] 553 | (str/replace "asdf" #"," replacement)) 554 | 555 | (defn replace-commas [replacement] 556 | (str/replace "asdf" #"," replacement)) 557 | 558 | (defn replace-commas [replacement] 559 | (str/replace "asdf" #"," replacement)) 560 | 561 | (defn replace-commas [replacement] 562 | (str/replace "asdf" #"," replacement)) 563 | 564 | (defn replace-commas [replacement] 565 | (str/replace "asdf" #"," replacement)) 566 | 567 | (defn replace-commas [replacement] 568 | (str/replace "asdf" #"," replacement)) 569 | 570 | (defn replace-commas [replacement] 571 | (str/replace "asdf" #"," replacement)) 572 | 573 | (defn replace-commas [replacement] 574 | (str/replace "asdf" #"," replacement)) 575 | 576 | (defn replace-commas [replacement] 577 | (str/replace "asdf" #"," replacement)) 578 | 579 | (defn replace-commas [replacement] 580 | (str/replace "asdf" #"," replacement)) 581 | 582 | (defn replace-commas [replacement] 583 | (str/replace "asdf" #"," replacement)) 584 | 585 | (defn replace-commas [replacement] 586 | (str/replace "asdf" #"," replacement)) 587 | 588 | (defn replace-commas [replacement] 589 | (str/replace "asdf" #"," replacement)) 590 | 591 | (defn replace-commas [replacement] 592 | (str/replace "asdf" #"," replacement)) 593 | 594 | (defn replace-commas [replacement] 595 | (str/replace "asdf" #"," replacement)) 596 | 597 | (defn replace-commas [replacement] 598 | (str/replace "asdf" #"," replacement)) 599 | 600 | (defn replace-commas [replacement] 601 | (str/replace "asdf" #"," replacement)) 602 | 603 | (defn replace-commas [replacement] 604 | (str/replace "asdf" #"," replacement)) 605 | 606 | (defn replace-commas [replacement] 607 | (str/replace "asdf" #"," replacement)) 608 | 609 | (defn replace-commas [replacement] 610 | (str/replace "asdf" #"," replacement)) 611 | 612 | (defn replace-commas [replacement] 613 | (str/replace "asdf" #"," replacement)) 614 | 615 | (defn replace-commas [replacement] 616 | (str/replace "asdf" #"," replacement)) 617 | 618 | (defn replace-commas [replacement] 619 | (str/replace "asdf" #"," replacement)) 620 | 621 | (defn replace-commas [replacement] 622 | (str/replace "asdf" #"," replacement)) 623 | 624 | (defn replace-commas [replacement] 625 | (str/replace "asdf" #"," replacement)) 626 | 627 | (defn replace-commas [replacement] 628 | (str/replace "asdf" #"," replacement)) 629 | 630 | (defn replace-commas [replacement] 631 | (str/replace "asdf" #"," replacement)) 632 | 633 | (defn replace-commas [replacement] 634 | (str/replace "asdf" #"," replacement)) 635 | 636 | (defn replace-commas [replacement] 637 | (str/replace "asdf" #"," replacement)) 638 | 639 | (defn replace-commas [replacement] 640 | (str/replace "asdf" #"," replacement)) 641 | 642 | (defn replace-commas [replacement] 643 | (str/replace "asdf" #"," replacement)) 644 | 645 | (defn replace-commas [replacement] 646 | (str/replace "asdf" #"," replacement)) 647 | 648 | (defn replace-commas [replacement] 649 | (str/replace "asdf" #"," replacement)) 650 | 651 | (defn replace-commas [replacement] 652 | (str/replace "asdf" #"," replacement)) 653 | 654 | (defn replace-commas [replacement] 655 | (str/replace "asdf" #"," replacement)) 656 | 657 | (defn replace-commas [replacement] 658 | (str/replace "asdf" #"," replacement)) 659 | 660 | (defn replace-commas [replacement] 661 | (str/replace "asdf" #"," replacement)) 662 | 663 | (defn replace-commas [replacement] 664 | (str/replace "asdf" #"," replacement)) 665 | 666 | (defn replace-commas [replacement] 667 | (str/replace "asdf" #"," replacement)) 668 | 669 | (defn replace-commas [replacement] 670 | (str/replace "asdf" #"," replacement)) 671 | 672 | (defn replace-commas [replacement] 673 | (str/replace "asdf" #"," replacement)) 674 | 675 | (defn replace-commas [replacement] 676 | (str/replace "asdf" #"," replacement)) 677 | 678 | (defn replace-commas [replacement] 679 | (str/replace "asdf" #"," replacement)) 680 | 681 | (defn replace-commas [replacement] 682 | (str/replace "asdf" #"," replacement)) 683 | 684 | (defn replace-commas [replacement] 685 | (str/replace "asdf" #"," replacement)) 686 | 687 | (defn replace-commas [replacement] 688 | (str/replace "asdf" #"," replacement)) 689 | 690 | (defn replace-commas [replacement] 691 | (str/replace "asdf" #"," replacement)) 692 | 693 | (defn replace-commas [replacement] 694 | (str/replace "asdf" #"," replacement)) 695 | 696 | (defn replace-commas [replacement] 697 | (str/replace "asdf" #"," replacement)) 698 | 699 | (defn replace-commas [replacement] 700 | (str/replace "asdf" #"," replacement)) 701 | 702 | (defn replace-commas [replacement] 703 | (str/replace "asdf" #"," replacement)) 704 | 705 | (defn replace-commas [replacement] 706 | (str/replace "asdf" #"," replacement)) 707 | 708 | (defn replace-commas [replacement] 709 | (str/replace "asdf" #"," replacement)) 710 | 711 | (defn replace-commas [replacement] 712 | (str/replace "asdf" #"," replacement)) 713 | 714 | (defn replace-commas [replacement] 715 | (str/replace "asdf" #"," replacement)) 716 | 717 | (defn replace-commas [replacement] 718 | (str/replace "asdf" #"," replacement)) 719 | 720 | (defn replace-commas [replacement] 721 | (str/replace "asdf" #"," replacement)) 722 | 723 | (defn replace-commas [replacement] 724 | (str/replace "asdf" #"," replacement)) 725 | 726 | (defn replace-commas [replacement] 727 | (str/replace "asdf" #"," replacement)) 728 | 729 | (defn replace-commas [replacement] 730 | (str/replace "asdf" #"," replacement)) 731 | 732 | (defn replace-commas [replacement] 733 | (str/replace "asdf" #"," replacement)) 734 | 735 | (defn replace-commas [replacement] 736 | (str/replace "asdf" #"," replacement)) 737 | 738 | (defn replace-commas [replacement] 739 | (str/replace "asdf" #"," replacement)) 740 | 741 | (defn replace-commas [replacement] 742 | (str/replace "asdf" #"," replacement)) 743 | 744 | (defn replace-commas [replacement] 745 | (str/replace "asdf" #"," replacement)) 746 | 747 | (defn replace-commas [replacement] 748 | (str/replace "asdf" #"," replacement)) 749 | 750 | (defn replace-commas [replacement] 751 | (str/replace "asdf" #"," replacement)) 752 | 753 | (defn replace-commas [replacement] 754 | (str/replace "asdf" #"," replacement)) 755 | 756 | (defn replace-commas [replacement] 757 | (str/replace "asdf" #"," replacement)) 758 | 759 | (defn replace-commas [replacement] 760 | (str/replace "asdf" #"," replacement)) 761 | 762 | (defn replace-commas [replacement] 763 | (str/replace "asdf" #"," replacement)) 764 | 765 | (defn replace-commas [replacement] 766 | (str/replace "asdf" #"," replacement)) 767 | 768 | (defn replace-commas [replacement] 769 | (str/replace "asdf" #"," replacement)) 770 | 771 | (defn replace-commas [replacement] 772 | (str/replace "asdf" #"," replacement)) 773 | 774 | (defn replace-commas [replacement] 775 | (str/replace "asdf" #"," replacement)) 776 | 777 | (defn replace-commas [replacement] 778 | (str/replace "asdf" #"," replacement)) 779 | 780 | (defn replace-commas [replacement] 781 | (str/replace "asdf" #"," replacement)) 782 | 783 | (defn replace-commas [replacement] 784 | (str/replace "asdf" #"," replacement)) 785 | 786 | (defn replace-commas [replacement] 787 | (str/replace "asdf" #"," replacement)) 788 | 789 | (defn replace-commas [replacement] 790 | (str/replace "asdf" #"," replacement)) 791 | 792 | (defn replace-commas [replacement] 793 | (str/replace "asdf" #"," replacement)) 794 | 795 | (defn replace-commas [replacement] 796 | (str/replace "asdf" #"," replacement)) 797 | 798 | (defn replace-commas [replacement] 799 | (str/replace "asdf" #"," replacement)) 800 | 801 | (defn replace-commas [replacement] 802 | (str/replace "asdf" #"," replacement)) 803 | 804 | (defn replace-commas [replacement] 805 | (str/replace "asdf" #"," replacement)) 806 | 807 | (defn replace-commas [replacement] 808 | (str/replace "asdf" #"," replacement)) 809 | 810 | (defn replace-commas [replacement] 811 | (str/replace "asdf" #"," replacement)) 812 | 813 | (defn replace-commas [replacement] 814 | (str/replace "asdf" #"," replacement)) 815 | 816 | (defn replace-commas [replacement] 817 | (str/replace "asdf" #"," replacement)) 818 | 819 | (defn replace-commas [replacement] 820 | (str/replace "asdf" #"," replacement)) 821 | 822 | (defn replace-commas [replacement] 823 | (str/replace "asdf" #"," replacement)) 824 | 825 | (defn replace-commas [replacement] 826 | (str/replace "asdf" #"," replacement)) 827 | 828 | (defn replace-commas [replacement] 829 | (str/replace "asdf" #"," replacement)) 830 | 831 | (defn replace-commas [replacement] 832 | (str/replace "asdf" #"," replacement)) 833 | 834 | (defn replace-commas [replacement] 835 | (str/replace "asdf" #"," replacement)) 836 | 837 | (defn replace-commas [replacement] 838 | (str/replace "asdf" #"," replacement)) 839 | 840 | (defn replace-commas [replacement] 841 | (str/replace "asdf" #"," replacement)) 842 | 843 | (defn replace-commas [replacement] 844 | (str/replace "asdf" #"," replacement)) 845 | 846 | (defn replace-commas [replacement] 847 | (str/replace "asdf" #"," replacement)) 848 | 849 | (defn replace-commas [replacement] 850 | (str/replace "asdf" #"," replacement)) 851 | 852 | (defn replace-commas [replacement] 853 | (str/replace "asdf" #"," replacement)) 854 | 855 | (defn replace-commas [replacement] 856 | (str/replace "asdf" #"," replacement)) 857 | 858 | (defn replace-commas [replacement] 859 | (str/replace "asdf" #"," replacement)) 860 | 861 | (defn replace-commas [replacement] 862 | (str/replace "asdf" #"," replacement)) 863 | 864 | (defn replace-commas [replacement] 865 | (str/replace "asdf" #"," replacement)) 866 | 867 | (defn replace-commas [replacement] 868 | (str/replace "asdf" #"," replacement)) 869 | 870 | (defn replace-commas [replacement] 871 | (str/replace "asdf" #"," replacement)) 872 | 873 | (defn replace-commas [replacement] 874 | (str/replace "asdf" #"," replacement)) 875 | 876 | (defn replace-commas [replacement] 877 | (str/replace "asdf" #"," replacement)) 878 | 879 | (defn replace-commas [replacement] 880 | (str/replace "asdf" #"," replacement)) 881 | 882 | (defn replace-commas [replacement] 883 | (str/replace "asdf" #"," replacement)) 884 | 885 | (defn replace-commas [replacement] 886 | (str/replace "asdf" #"," replacement)) 887 | 888 | (defn replace-commas [replacement] 889 | (str/replace "asdf" #"," replacement)) 890 | 891 | (defn replace-commas [replacement] 892 | (str/replace "asdf" #"," replacement)) 893 | 894 | (defn replace-commas [replacement] 895 | (str/replace "asdf" #"," replacement)) 896 | 897 | (defn replace-commas [replacement] 898 | (str/replace "asdf" #"," replacement)) 899 | 900 | (defn replace-commas [replacement] 901 | (str/replace "asdf" #"," replacement)) 902 | 903 | (defn replace-commas [replacement] 904 | (str/replace "asdf" #"," replacement)) 905 | 906 | (defn replace-commas [replacement] 907 | (str/replace "asdf" #"," replacement)) 908 | 909 | (defn replace-commas [replacement] 910 | (str/replace "asdf" #"," replacement)) 911 | 912 | (defn replace-commas [replacement] 913 | (str/replace "asdf" #"," replacement)) 914 | 915 | (defn replace-commas [replacement] 916 | (str/replace "asdf" #"," replacement)) 917 | 918 | (defn replace-commas [replacement] 919 | (str/replace "asdf" #"," replacement)) 920 | 921 | (defn replace-commas [replacement] 922 | (str/replace "asdf" #"," replacement)) 923 | 924 | (defn replace-commas [replacement] 925 | (str/replace "asdf" #"," replacement)) 926 | 927 | (defn replace-commas [replacement] 928 | (str/replace "asdf" #"," replacement)) 929 | 930 | (defn replace-commas [replacement] 931 | (str/replace "asdf" #"," replacement)) 932 | 933 | (defn replace-commas [replacement] 934 | (str/replace "asdf" #"," replacement)) 935 | 936 | (defn replace-commas [replacement] 937 | (str/replace "asdf" #"," replacement)) 938 | 939 | (defn replace-commas [replacement] 940 | (str/replace "asdf" #"," replacement)) 941 | 942 | (defn replace-commas [replacement] 943 | (str/replace "asdf" #"," replacement)) 944 | 945 | (defn replace-commas [replacement] 946 | (str/replace "asdf" #"," replacement)) 947 | 948 | (defn replace-commas [replacement] 949 | (str/replace "asdf" #"," replacement)) 950 | 951 | (defn replace-commas [replacement] 952 | (str/replace "asdf" #"," replacement)) 953 | 954 | (defn replace-commas [replacement] 955 | (str/replace "asdf" #"," replacement)) 956 | 957 | (defn replace-commas [replacement] 958 | (str/replace "asdf" #"," replacement)) 959 | 960 | (defn replace-commas [replacement] 961 | (str/replace "asdf" #"," replacement)) 962 | 963 | (defn replace-commas [replacement] 964 | (str/replace "asdf" #"," replacement)) 965 | 966 | (defn replace-commas [replacement] 967 | (str/replace "asdf" #"," replacement)) 968 | 969 | (defn replace-commas [replacement] 970 | (str/replace "asdf" #"," replacement)) 971 | 972 | (defn replace-commas [replacement] 973 | (str/replace "asdf" #"," replacement)) 974 | 975 | (defn replace-commas [replacement] 976 | (str/replace "asdf" #"," replacement)) 977 | 978 | (defn replace-commas [replacement] 979 | (str/replace "asdf" #"," replacement)) 980 | 981 | (defn replace-commas [replacement] 982 | (str/replace "asdf" #"," replacement)) 983 | 984 | (defn replace-commas [replacement] 985 | (str/replace "asdf" #"," replacement)) 986 | 987 | (defn replace-commas [replacement] 988 | (str/replace "asdf" #"," replacement)) 989 | 990 | (defn replace-commas [replacement] 991 | (str/replace "asdf" #"," replacement)) 992 | 993 | (defn replace-commas [replacement] 994 | (str/replace "asdf" #"," replacement)) 995 | 996 | (defn replace-commas [replacement] 997 | (str/replace "asdf" #"," replacement)) 998 | 999 | (defn replace-commas [replacement] 1000 | (str/replace "asdf" #"," replacement)) 1001 | 1002 | (defn replace-commas [replacement] 1003 | (str/replace "asdf" #"," replacement)) 1004 | 1005 | (defn replace-commas [replacement] 1006 | (str/replace "asdf" #"," replacement)) 1007 | 1008 | (defn replace-commas [replacement] 1009 | (str/replace "asdf" #"," replacement)) 1010 | 1011 | (defn replace-commas [replacement] 1012 | (str/replace "asdf" #"," replacement)) 1013 | 1014 | (defn replace-commas [replacement] 1015 | (str/replace "asdf" #"," replacement)) 1016 | 1017 | (defn replace-commas [replacement] 1018 | (str/replace "asdf" #"," replacement)) 1019 | 1020 | (defn replace-commas [replacement] 1021 | (str/replace "asdf" #"," replacement)) 1022 | 1023 | (defn replace-commas [replacement] 1024 | (str/replace "asdf" #"," replacement)) 1025 | 1026 | (defn replace-commas [replacement] 1027 | (str/replace "asdf" #"," replacement)) 1028 | 1029 | (defn replace-commas [replacement] 1030 | (str/replace "asdf" #"," replacement)) 1031 | 1032 | (defn replace-commas [replacement] 1033 | (str/replace "asdf" #"," replacement)) 1034 | 1035 | (defn replace-commas [replacement] 1036 | (str/replace "asdf" #"," replacement)) 1037 | 1038 | (defn replace-commas [replacement] 1039 | (str/replace "asdf" #"," replacement)) 1040 | 1041 | (defn replace-commas [replacement] 1042 | (str/replace "asdf" #"," replacement)) 1043 | 1044 | (defn replace-commas [replacement] 1045 | (str/replace "asdf" #"," replacement)) 1046 | 1047 | (defn replace-commas [replacement] 1048 | (str/replace "asdf" #"," replacement)) 1049 | 1050 | (defn replace-commas [replacement] 1051 | (str/replace "asdf" #"," replacement)) 1052 | 1053 | (defn replace-commas [replacement] 1054 | (str/replace "asdf" #"," replacement)) 1055 | 1056 | (defn replace-commas [replacement] 1057 | (str/replace "asdf" #"," replacement)) 1058 | 1059 | (defn replace-commas [replacement] 1060 | (str/replace "asdf" #"," replacement)) 1061 | 1062 | (defn replace-commas [replacement] 1063 | (str/replace "asdf" #"," replacement)) 1064 | 1065 | (defn replace-commas [replacement] 1066 | (str/replace "asdf" #"," replacement)) 1067 | 1068 | (defn replace-commas [replacement] 1069 | (str/replace "asdf" #"," replacement)) 1070 | 1071 | (defn replace-commas [replacement] 1072 | (str/replace "asdf" #"," replacement)) 1073 | 1074 | (defn replace-commas [replacement] 1075 | (str/replace "asdf" #"," replacement)) 1076 | 1077 | (defn replace-commas [replacement] 1078 | (str/replace "asdf" #"," replacement)) 1079 | 1080 | (defn replace-commas [replacement] 1081 | (str/replace "asdf" #"," replacement)) 1082 | 1083 | (defn replace-commas [replacement] 1084 | (str/replace "asdf" #"," replacement)) 1085 | 1086 | (defn replace-commas [replacement] 1087 | (str/replace "asdf" #"," replacement)) 1088 | 1089 | (defn replace-commas [replacement] 1090 | (str/replace "asdf" #"," replacement)) 1091 | 1092 | (defn replace-commas [replacement] 1093 | (str/replace "asdf" #"," replacement)) 1094 | 1095 | (defn replace-commas [replacement] 1096 | (str/replace "asdf" #"," replacement)) 1097 | 1098 | (defn replace-commas [replacement] 1099 | (str/replace "asdf" #"," replacement)) 1100 | 1101 | (defn replace-commas [replacement] 1102 | (str/replace "asdf" #"," replacement)) 1103 | 1104 | (defn replace-commas [replacement] 1105 | (str/replace "asdf" #"," replacement)) 1106 | 1107 | (defn replace-commas [replacement] 1108 | (str/replace "asdf" #"," replacement)) 1109 | 1110 | (defn replace-commas [replacement] 1111 | (str/replace "asdf" #"," replacement)) 1112 | 1113 | (defn replace-commas [replacement] 1114 | (str/replace "asdf" #"," replacement)) 1115 | 1116 | (defn replace-commas [replacement] 1117 | (str/replace "asdf" #"," replacement)) 1118 | 1119 | (defn replace-commas [replacement] 1120 | (str/replace "asdf" #"," replacement)) 1121 | 1122 | (defn replace-commas [replacement] 1123 | (str/replace "asdf" #"," replacement)) 1124 | 1125 | (defn replace-commas [replacement] 1126 | (str/replace "asdf" #"," replacement)) 1127 | 1128 | (defn replace-commas [replacement] 1129 | (str/replace "asdf" #"," replacement)) 1130 | 1131 | (defn replace-commas [replacement] 1132 | (str/replace "asdf" #"," replacement)) 1133 | 1134 | (defn replace-commas [replacement] 1135 | (str/replace "asdf" #"," replacement)) 1136 | 1137 | (defn replace-commas [replacement] 1138 | (str/replace "asdf" #"," replacement)) 1139 | 1140 | (defn replace-commas [replacement] 1141 | (str/replace "asdf" #"," replacement)) 1142 | 1143 | (defn replace-commas [replacement] 1144 | (str/replace "asdf" #"," replacement)) 1145 | 1146 | (defn replace-commas [replacement] 1147 | (str/replace "asdf" #"," replacement)) 1148 | 1149 | (defn replace-commas [replacement] 1150 | (str/replace "asdf" #"," replacement)) 1151 | 1152 | (defn replace-commas [replacement] 1153 | (str/replace "asdf" #"," replacement)) 1154 | 1155 | (defn replace-commas [replacement] 1156 | (str/replace "asdf" #"," replacement)) 1157 | 1158 | (defn replace-commas [replacement] 1159 | (str/replace "asdf" #"," replacement)) 1160 | 1161 | (defn replace-commas [replacement] 1162 | (str/replace "asdf" #"," replacement)) 1163 | 1164 | (defn replace-commas [replacement] 1165 | (str/replace "asdf" #"," replacement)) 1166 | 1167 | (defn replace-commas [replacement] 1168 | (str/replace "asdf" #"," replacement)) 1169 | 1170 | (defn replace-commas [replacement] 1171 | (str/replace "asdf" #"," replacement)) 1172 | 1173 | (defn replace-commas [replacement] 1174 | (str/replace "asdf" #"," replacement)) 1175 | 1176 | (defn replace-commas [replacement] 1177 | (str/replace "asdf" #"," replacement)) 1178 | 1179 | (defn replace-commas [replacement] 1180 | (str/replace "asdf" #"," replacement)) 1181 | 1182 | (defn replace-commas [replacement] 1183 | (str/replace "asdf" #"," replacement)) 1184 | 1185 | (defn replace-commas [replacement] 1186 | (str/replace "asdf" #"," replacement)) 1187 | 1188 | (defn replace-commas [replacement] 1189 | (str/replace "asdf" #"," replacement)) 1190 | 1191 | (defn replace-commas [replacement] 1192 | (str/replace "asdf" #"," replacement)) 1193 | 1194 | (defn replace-commas [replacement] 1195 | (str/replace "asdf" #"," replacement)) 1196 | 1197 | (defn replace-commas [replacement] 1198 | (str/replace "asdf" #"," replacement)) 1199 | 1200 | (defn replace-commas [replacement] 1201 | (str/replace "asdf" #"," replacement)) 1202 | 1203 | (defn replace-commas [replacement] 1204 | (str/replace "asdf" #"," replacement)) 1205 | -------------------------------------------------------------------------------- /dev-resources/test-long-files.out: -------------------------------------------------------------------------------- 1 | (ns test-namespace 2 | (:require [clojure.string :as str])) 3 | 4 | (defn replace-commas [replacement] 5 | (str/replace "asdf" #"," replacement)) 6 | 7 | (defn replace-commas [replacement] 8 | (str/replace "asdf" #"," replacement)) 9 | 10 | (defn replace-commas [replacement] 11 | (str/replace "asdf" #"," replacement)) 12 | 13 | (defn replace-commas [replacement] 14 | (str/replace "asdf" #"," replacement)) 15 | 16 | (defn replace-commas [replacement] 17 | (str/replace "asdf" #"," replacement)) 18 | 19 | (defn replace-commas [replacement] 20 | (str/replace "asdf" #"," replacement)) 21 | 22 | (defn replace-commas [replacement] 23 | (str/replace "asdf" #"," replacement)) 24 | 25 | (defn replace-commas [replacement] 26 | (str/replace "asdf" #"," replacement)) 27 | 28 | (defn replace-commas [replacement] 29 | (str/replace "asdf" #"," replacement)) 30 | 31 | (defn replace-commas [replacement] 32 | (str/replace "asdf" #"," replacement)) 33 | 34 | (defn replace-commas [replacement] 35 | (str/replace "asdf" #"," replacement)) 36 | 37 | (defn replace-commas [replacement] 38 | (str/replace "asdf" #"," replacement)) 39 | 40 | (defn replace-commas [replacement] 41 | (str/replace "asdf" #"," replacement)) 42 | 43 | (defn replace-commas [replacement] 44 | (str/replace "asdf" #"," replacement)) 45 | 46 | (defn replace-commas [replacement] 47 | (str/replace "asdf" #"," replacement)) 48 | 49 | (defn replace-commas [replacement] 50 | (str/replace "asdf" #"," replacement)) 51 | 52 | (defn replace-commas [replacement] 53 | (str/replace "asdf" #"," replacement)) 54 | 55 | (defn replace-commas [replacement] 56 | (str/replace "asdf" #"," replacement)) 57 | 58 | (defn replace-commas [replacement] 59 | (str/replace "asdf" #"," replacement)) 60 | 61 | (defn replace-commas [replacement] 62 | (str/replace "asdf" #"," replacement)) 63 | 64 | (defn replace-commas [replacement] 65 | (str/replace "asdf" #"," replacement)) 66 | 67 | (defn replace-commas [replacement] 68 | (str/replace "asdf" #"," replacement)) 69 | 70 | (defn replace-commas [replacement] 71 | (str/replace "asdf" #"," replacement)) 72 | 73 | (defn replace-commas [replacement] 74 | (str/replace "asdf" #"," replacement)) 75 | 76 | (defn replace-commas [replacement] 77 | (str/replace "asdf" #"," replacement)) 78 | 79 | (defn replace-commas [replacement] 80 | (str/replace "asdf" #"," replacement)) 81 | 82 | (defn replace-commas [replacement] 83 | (str/replace "asdf" #"," replacement)) 84 | 85 | (defn replace-commas [replacement] 86 | (str/replace "asdf" #"," replacement)) 87 | 88 | (defn replace-commas [replacement] 89 | (str/replace "asdf" #"," replacement)) 90 | 91 | (defn replace-commas [replacement] 92 | (str/replace "asdf" #"," replacement)) 93 | 94 | (defn replace-commas [replacement] 95 | (str/replace "asdf" #"," replacement)) 96 | 97 | (defn replace-commas [replacement] 98 | (str/replace "asdf" #"," replacement)) 99 | 100 | (defn replace-commas [replacement] 101 | (str/replace "asdf" #"," replacement)) 102 | 103 | (defn replace-commas [replacement] 104 | (str/replace "asdf" #"," replacement)) 105 | 106 | (defn replace-commas [replacement] 107 | (str/replace "asdf" #"," replacement)) 108 | 109 | (defn replace-commas [replacement] 110 | (str/replace "asdf" #"," replacement)) 111 | 112 | (defn replace-commas [replacement] 113 | (str/replace "asdf" #"," replacement)) 114 | 115 | (defn replace-commas [replacement] 116 | (str/replace "asdf" #"," replacement)) 117 | 118 | (defn replace-commas [replacement] 119 | (str/replace "asdf" #"," replacement)) 120 | 121 | (defn replace-commas [replacement] 122 | (str/replace "asdf" #"," replacement)) 123 | 124 | (defn replace-commas [replacement] 125 | (str/replace "asdf" #"," replacement)) 126 | 127 | (defn replace-commas [replacement] 128 | (str/replace "asdf" #"," replacement)) 129 | 130 | (defn replace-commas [replacement] 131 | (str/replace "asdf" #"," replacement)) 132 | 133 | (defn replace-commas [replacement] 134 | (str/replace "asdf" #"," replacement)) 135 | 136 | (defn replace-commas [replacement] 137 | (str/replace "asdf" #"," replacement)) 138 | 139 | (defn replace-commas [replacement] 140 | (str/replace "asdf" #"," replacement)) 141 | 142 | (defn replace-commas [replacement] 143 | (str/replace "asdf" #"," replacement)) 144 | 145 | (defn replace-commas [replacement] 146 | (str/replace "asdf" #"," replacement)) 147 | 148 | (defn replace-commas [replacement] 149 | (str/replace "asdf" #"," replacement)) 150 | 151 | (defn replace-commas [replacement] 152 | (str/replace "asdf" #"," replacement)) 153 | 154 | (defn replace-commas [replacement] 155 | (str/replace "asdf" #"," replacement)) 156 | 157 | (defn replace-commas [replacement] 158 | (str/replace "asdf" #"," replacement)) 159 | 160 | (defn replace-commas [replacement] 161 | (str/replace "asdf" #"," replacement)) 162 | 163 | (defn replace-commas [replacement] 164 | (str/replace "asdf" #"," replacement)) 165 | 166 | (defn replace-commas [replacement] 167 | (str/replace "asdf" #"," replacement)) 168 | 169 | (defn replace-commas [replacement] 170 | (str/replace "asdf" #"," replacement)) 171 | 172 | (defn replace-commas [replacement] 173 | (str/replace "asdf" #"," replacement)) 174 | 175 | (defn replace-commas [replacement] 176 | (str/replace "asdf" #"," replacement)) 177 | 178 | (defn replace-commas [replacement] 179 | (str/replace "asdf" #"," replacement)) 180 | 181 | (defn replace-commas [replacement] 182 | (str/replace "asdf" #"," replacement)) 183 | 184 | (defn replace-commas [replacement] 185 | (str/replace "asdf" #"," replacement)) 186 | 187 | (defn replace-commas [replacement] 188 | (str/replace "asdf" #"," replacement)) 189 | 190 | (defn replace-commas [replacement] 191 | (str/replace "asdf" #"," replacement)) 192 | 193 | (defn replace-commas [replacement] 194 | (str/replace "asdf" #"," replacement)) 195 | 196 | (defn replace-commas [replacement] 197 | (str/replace "asdf" #"," replacement)) 198 | 199 | (defn replace-commas [replacement] 200 | (str/replace "asdf" #"," replacement)) 201 | 202 | (defn replace-commas [replacement] 203 | (str/replace "asdf" #"," replacement)) 204 | 205 | (defn replace-commas [replacement] 206 | (str/replace "asdf" #"," replacement)) 207 | 208 | (defn replace-commas [replacement] 209 | (str/replace "asdf" #"," replacement)) 210 | 211 | (defn replace-commas [replacement] 212 | (str/replace "asdf" #"," replacement)) 213 | 214 | (defn replace-commas [replacement] 215 | (str/replace "asdf" #"," replacement)) 216 | 217 | (defn replace-commas [replacement] 218 | (str/replace "asdf" #"," replacement)) 219 | 220 | (defn replace-commas [replacement] 221 | (str/replace "asdf" #"," replacement)) 222 | 223 | (defn replace-commas [replacement] 224 | (str/replace "asdf" #"," replacement)) 225 | 226 | (defn replace-commas [replacement] 227 | (str/replace "asdf" #"," replacement)) 228 | 229 | (defn replace-commas [replacement] 230 | (str/replace "asdf" #"," replacement)) 231 | 232 | (defn replace-commas [replacement] 233 | (str/replace "asdf" #"," replacement)) 234 | 235 | (defn replace-commas [replacement] 236 | (str/replace "asdf" #"," replacement)) 237 | 238 | (defn replace-commas [replacement] 239 | (str/replace "asdf" #"," replacement)) 240 | 241 | (defn replace-commas [replacement] 242 | (str/replace "asdf" #"," replacement)) 243 | 244 | (defn replace-commas [replacement] 245 | (str/replace "asdf" #"," replacement)) 246 | 247 | (defn replace-commas [replacement] 248 | (str/replace "asdf" #"," replacement)) 249 | 250 | (defn replace-commas [replacement] 251 | (str/replace "asdf" #"," replacement)) 252 | 253 | (defn replace-commas [replacement] 254 | (str/replace "asdf" #"," replacement)) 255 | 256 | (defn replace-commas [replacement] 257 | (str/replace "asdf" #"," replacement)) 258 | 259 | (defn replace-commas [replacement] 260 | (str/replace "asdf" #"," replacement)) 261 | 262 | (defn replace-commas [replacement] 263 | (str/replace "asdf" #"," replacement)) 264 | 265 | (defn replace-commas [replacement] 266 | (str/replace "asdf" #"," replacement)) 267 | 268 | (defn replace-commas [replacement] 269 | (str/replace "asdf" #"," replacement)) 270 | 271 | (defn replace-commas [replacement] 272 | (str/replace "asdf" #"," replacement)) 273 | 274 | (defn replace-commas [replacement] 275 | (str/replace "asdf" #"," replacement)) 276 | 277 | (defn replace-commas [replacement] 278 | (str/replace "asdf" #"," replacement)) 279 | 280 | (defn replace-commas [replacement] 281 | (str/replace "asdf" #"," replacement)) 282 | 283 | (defn replace-commas [replacement] 284 | (str/replace "asdf" #"," replacement)) 285 | 286 | (defn replace-commas [replacement] 287 | (str/replace "asdf" #"," replacement)) 288 | 289 | (defn replace-commas [replacement] 290 | (str/replace "asdf" #"," replacement)) 291 | 292 | (defn replace-commas [replacement] 293 | (str/replace "asdf" #"," replacement)) 294 | 295 | (defn replace-commas [replacement] 296 | (str/replace "asdf" #"," replacement)) 297 | 298 | (defn replace-commas [replacement] 299 | (str/replace "asdf" #"," replacement)) 300 | 301 | (defn replace-commas [replacement] 302 | (str/replace "asdf" #"," replacement)) 303 | 304 | (defn replace-commas [replacement] 305 | (str/replace "asdf" #"," replacement)) 306 | 307 | (defn replace-commas [replacement] 308 | (str/replace "asdf" #"," replacement)) 309 | 310 | (defn replace-commas [replacement] 311 | (str/replace "asdf" #"," replacement)) 312 | 313 | (defn replace-commas [replacement] 314 | (str/replace "asdf" #"," replacement)) 315 | 316 | (defn replace-commas [replacement] 317 | (str/replace "asdf" #"," replacement)) 318 | 319 | (defn replace-commas [replacement] 320 | (str/replace "asdf" #"," replacement)) 321 | 322 | (defn replace-commas [replacement] 323 | (str/replace "asdf" #"," replacement)) 324 | 325 | (defn replace-commas [replacement] 326 | (str/replace "asdf" #"," replacement)) 327 | 328 | (defn replace-commas [replacement] 329 | (str/replace "asdf" #"," replacement)) 330 | 331 | (defn replace-commas [replacement] 332 | (str/replace "asdf" #"," replacement)) 333 | 334 | (defn replace-commas [replacement] 335 | (str/replace "asdf" #"," replacement)) 336 | 337 | (defn replace-commas [replacement] 338 | (str/replace "asdf" #"," replacement)) 339 | 340 | (defn replace-commas [replacement] 341 | (str/replace "asdf" #"," replacement)) 342 | 343 | (defn replace-commas [replacement] 344 | (str/replace "asdf" #"," replacement)) 345 | 346 | (defn replace-commas [replacement] 347 | (str/replace "asdf" #"," replacement)) 348 | 349 | (defn replace-commas [replacement] 350 | (str/replace "asdf" #"," replacement)) 351 | 352 | (defn replace-commas [replacement] 353 | (str/replace "asdf" #"," replacement)) 354 | 355 | (defn replace-commas [replacement] 356 | (str/replace "asdf" #"," replacement)) 357 | 358 | (defn replace-commas [replacement] 359 | (str/replace "asdf" #"," replacement)) 360 | 361 | (defn replace-commas [replacement] 362 | (str/replace "asdf" #"," replacement)) 363 | 364 | (defn replace-commas [replacement] 365 | (str/replace "asdf" #"," replacement)) 366 | 367 | (defn replace-commas [replacement] 368 | (str/replace "asdf" #"," replacement)) 369 | 370 | (defn replace-commas [replacement] 371 | (str/replace "asdf" #"," replacement)) 372 | 373 | (defn replace-commas [replacement] 374 | (str/replace "asdf" #"," replacement)) 375 | 376 | (defn replace-commas [replacement] 377 | (str/replace "asdf" #"," replacement)) 378 | 379 | (defn replace-commas [replacement] 380 | (str/replace "asdf" #"," replacement)) 381 | 382 | (defn replace-commas [replacement] 383 | (str/replace "asdf" #"," replacement)) 384 | 385 | (defn replace-commas [replacement] 386 | (str/replace "asdf" #"," replacement)) 387 | 388 | (defn replace-commas [replacement] 389 | (str/replace "asdf" #"," replacement)) 390 | 391 | (defn replace-commas [replacement] 392 | (str/replace "asdf" #"," replacement)) 393 | 394 | (defn replace-commas [replacement] 395 | (str/replace "asdf" #"," replacement)) 396 | 397 | (defn replace-commas [replacement] 398 | (str/replace "asdf" #"," replacement)) 399 | 400 | (defn replace-commas [replacement] 401 | (str/replace "asdf" #"," replacement)) 402 | 403 | (defn replace-commas [replacement] 404 | (str/replace "asdf" #"," replacement)) 405 | 406 | (defn replace-commas [replacement] 407 | (str/replace "asdf" #"," replacement)) 408 | 409 | (defn replace-commas [replacement] 410 | (str/replace "asdf" #"," replacement)) 411 | 412 | (defn replace-commas [replacement] 413 | (str/replace "asdf" #"," replacement)) 414 | 415 | (defn replace-commas [replacement] 416 | (str/replace "asdf" #"," replacement)) 417 | 418 | (defn replace-commas [replacement] 419 | (str/replace "asdf" #"," replacement)) 420 | 421 | (defn replace-commas [replacement] 422 | (str/replace "asdf" #"," replacement)) 423 | 424 | (defn replace-commas [replacement] 425 | (str/replace "asdf" #"," replacement)) 426 | 427 | (defn replace-commas [replacement] 428 | (str/replace "asdf" #"," replacement)) 429 | 430 | (defn replace-commas [replacement] 431 | (str/replace "asdf" #"," replacement)) 432 | 433 | (defn replace-commas [replacement] 434 | (str/replace "asdf" #"," replacement)) 435 | 436 | (defn replace-commas [replacement] 437 | (str/replace "asdf" #"," replacement)) 438 | 439 | (defn replace-commas [replacement] 440 | (str/replace "asdf" #"," replacement)) 441 | 442 | (defn replace-commas [replacement] 443 | (str/replace "asdf" #"," replacement)) 444 | 445 | (defn replace-commas [replacement] 446 | (str/replace "asdf" #"," replacement)) 447 | 448 | (defn replace-commas [replacement] 449 | (str/replace "asdf" #"," replacement)) 450 | 451 | (defn replace-commas [replacement] 452 | (str/replace "asdf" #"," replacement)) 453 | 454 | (defn replace-commas [replacement] 455 | (str/replace "asdf" #"," replacement)) 456 | 457 | (defn replace-commas [replacement] 458 | (str/replace "asdf" #"," replacement)) 459 | 460 | (defn replace-commas [replacement] 461 | (str/replace "asdf" #"," replacement)) 462 | 463 | (defn replace-commas [replacement] 464 | (str/replace "asdf" #"," replacement)) 465 | 466 | (defn replace-commas [replacement] 467 | (str/replace "asdf" #"," replacement)) 468 | 469 | (defn replace-commas [replacement] 470 | (str/replace "asdf" #"," replacement)) 471 | 472 | (defn replace-commas [replacement] 473 | (str/replace "asdf" #"," replacement)) 474 | 475 | (defn replace-commas [replacement] 476 | (str/replace "asdf" #"," replacement)) 477 | 478 | (defn replace-commas [replacement] 479 | (str/replace "asdf" #"," replacement)) 480 | 481 | (defn replace-commas [replacement] 482 | (str/replace "asdf" #"," replacement)) 483 | 484 | (defn replace-commas [replacement] 485 | (str/replace "asdf" #"," replacement)) 486 | 487 | (defn replace-commas [replacement] 488 | (str/replace "asdf" #"," replacement)) 489 | 490 | (defn replace-commas [replacement] 491 | (str/replace "asdf" #"," replacement)) 492 | 493 | (defn replace-commas [replacement] 494 | (str/replace "asdf" #"," replacement)) 495 | 496 | (defn replace-commas [replacement] 497 | (str/replace "asdf" #"," replacement)) 498 | 499 | (defn replace-commas [replacement] 500 | (str/replace "asdf" #"," replacement)) 501 | 502 | (defn replace-commas [replacement] 503 | (str/replace "asdf" #"," replacement)) 504 | 505 | (defn replace-commas [replacement] 506 | (str/replace "asdf" #"," replacement)) 507 | 508 | (defn replace-commas [replacement] 509 | (str/replace "asdf" #"," replacement)) 510 | 511 | (defn replace-commas [replacement] 512 | (str/replace "asdf" #"," replacement)) 513 | 514 | (defn replace-commas [replacement] 515 | (str/replace "asdf" #"," replacement)) 516 | 517 | (defn replace-commas [replacement] 518 | (str/replace "asdf" #"," replacement)) 519 | 520 | (defn replace-commas [replacement] 521 | (str/replace "asdf" #"," replacement)) 522 | 523 | (defn replace-commas [replacement] 524 | (str/replace "asdf" #"," replacement)) 525 | 526 | (defn replace-commas [replacement] 527 | (str/replace "asdf" #"," replacement)) 528 | 529 | (defn replace-commas [replacement] 530 | (str/replace "asdf" #"," replacement)) 531 | 532 | (defn replace-commas [replacement] 533 | (str/replace "asdf" #"," replacement)) 534 | 535 | (defn replace-commas [replacement] 536 | (str/replace "asdf" #"," replacement)) 537 | 538 | (defn replace-commas [replacement] 539 | (str/replace "asdf" #"," replacement)) 540 | 541 | (defn replace-commas [replacement] 542 | (str/replace "asdf" #"," replacement)) 543 | 544 | (defn replace-commas [replacement] 545 | (str/replace "asdf" #"," replacement)) 546 | 547 | (defn replace-commas [replacement] 548 | (str/replace "asdf" #"," replacement)) 549 | 550 | (defn replace-commas [replacement] 551 | (str/replace "asdf" #"," replacement)) 552 | 553 | (defn replace-commas [replacement] 554 | (str/replace "asdf" #"," replacement)) 555 | 556 | (defn replace-commas [replacement] 557 | (str/replace "asdf" #"," replacement)) 558 | 559 | (defn replace-commas [replacement] 560 | (str/replace "asdf" #"," replacement)) 561 | 562 | (defn replace-commas [replacement] 563 | (str/replace "asdf" #"," replacement)) 564 | 565 | (defn replace-commas [replacement] 566 | (str/replace "asdf" #"," replacement)) 567 | 568 | (defn replace-commas [replacement] 569 | (str/replace "asdf" #"," replacement)) 570 | 571 | (defn replace-commas [replacement] 572 | (str/replace "asdf" #"," replacement)) 573 | 574 | (defn replace-commas [replacement] 575 | (str/replace "asdf" #"," replacement)) 576 | 577 | (defn replace-commas [replacement] 578 | (str/replace "asdf" #"," replacement)) 579 | 580 | (defn replace-commas [replacement] 581 | (str/replace "asdf" #"," replacement)) 582 | 583 | (defn replace-commas [replacement] 584 | (str/replace "asdf" #"," replacement)) 585 | 586 | (defn replace-commas [replacement] 587 | (str/replace "asdf" #"," replacement)) 588 | 589 | (defn replace-commas [replacement] 590 | (str/replace "asdf" #"," replacement)) 591 | 592 | (defn replace-commas [replacement] 593 | (str/replace "asdf" #"," replacement)) 594 | 595 | (defn replace-commas [replacement] 596 | (str/replace "asdf" #"," replacement)) 597 | 598 | (defn replace-commas [replacement] 599 | (str/replace "asdf" #"," replacement)) 600 | 601 | (defn replace-commas [replacement] 602 | (str/replace "asdf" #"," replacement)) 603 | 604 | (defn replace-commas [replacement] 605 | (str/replace "asdf" #"," replacement)) 606 | 607 | (defn replace-commas [replacement] 608 | (str/replace "asdf" #"," replacement)) 609 | 610 | (defn replace-commas [replacement] 611 | (str/replace "asdf" #"," replacement)) 612 | 613 | (defn replace-commas [replacement] 614 | (str/replace "asdf" #"," replacement)) 615 | 616 | (defn replace-commas [replacement] 617 | (str/replace "asdf" #"," replacement)) 618 | 619 | (defn replace-commas [replacement] 620 | (str/replace "asdf" #"," replacement)) 621 | 622 | (defn replace-commas [replacement] 623 | (str/replace "asdf" #"," replacement)) 624 | 625 | (defn replace-commas [replacement] 626 | (str/replace "asdf" #"," replacement)) 627 | 628 | (defn replace-commas [replacement] 629 | (str/replace "asdf" #"," replacement)) 630 | 631 | (defn replace-commas [replacement] 632 | (str/replace "asdf" #"," replacement)) 633 | 634 | (defn replace-commas [replacement] 635 | (str/replace "asdf" #"," replacement)) 636 | 637 | (defn replace-commas [replacement] 638 | (str/replace "asdf" #"," replacement)) 639 | 640 | (defn replace-commas [replacement] 641 | (str/replace "asdf" #"," replacement)) 642 | 643 | (defn replace-commas [replacement] 644 | (str/replace "asdf" #"," replacement)) 645 | 646 | (defn replace-commas [replacement] 647 | (str/replace "asdf" #"," replacement)) 648 | 649 | (defn replace-commas [replacement] 650 | (str/replace "asdf" #"," replacement)) 651 | 652 | (defn replace-commas [replacement] 653 | (str/replace "asdf" #"," replacement)) 654 | 655 | (defn replace-commas [replacement] 656 | (str/replace "asdf" #"," replacement)) 657 | 658 | (defn replace-commas [replacement] 659 | (str/replace "asdf" #"," replacement)) 660 | 661 | (defn replace-commas [replacement] 662 | (str/replace "asdf" #"," replacement)) 663 | 664 | (defn replace-commas [replacement] 665 | (str/replace "asdf" #"," replacement)) 666 | 667 | (defn replace-commas [replacement] 668 | (str/replace "asdf" #"," replacement)) 669 | 670 | (defn replace-commas [replacement] 671 | (str/replace "asdf" #"," replacement)) 672 | 673 | (defn replace-commas [replacement] 674 | (str/replace "asdf" #"," replacement)) 675 | 676 | (defn replace-commas [replacement] 677 | (str/replace "asdf" #"," replacement)) 678 | 679 | (defn replace-commas [replacement] 680 | (str/replace "asdf" #"," replacement)) 681 | 682 | (defn replace-commas [replacement] 683 | (str/replace "asdf" #"," replacement)) 684 | 685 | (defn replace-commas [replacement] 686 | (str/replace "asdf" #"," replacement)) 687 | 688 | (defn replace-commas [replacement] 689 | (str/replace "asdf" #"," replacement)) 690 | 691 | (defn replace-commas [replacement] 692 | (str/replace "asdf" #"," replacement)) 693 | 694 | (defn replace-commas [replacement] 695 | (str/replace "asdf" #"," replacement)) 696 | 697 | (defn replace-commas [replacement] 698 | (str/replace "asdf" #"," replacement)) 699 | 700 | (defn replace-commas [replacement] 701 | (str/replace "asdf" #"," replacement)) 702 | 703 | (defn replace-commas [replacement] 704 | (str/replace "asdf" #"," replacement)) 705 | 706 | (defn replace-commas [replacement] 707 | (str/replace "asdf" #"," replacement)) 708 | 709 | (defn replace-commas [replacement] 710 | (str/replace "asdf" #"," replacement)) 711 | 712 | (defn replace-commas [replacement] 713 | (str/replace "asdf" #"," replacement)) 714 | 715 | (defn replace-commas [replacement] 716 | (str/replace "asdf" #"," replacement)) 717 | 718 | (defn replace-commas [replacement] 719 | (str/replace "asdf" #"," replacement)) 720 | 721 | (defn replace-commas [replacement] 722 | (str/replace "asdf" #"," replacement)) 723 | 724 | (defn replace-commas [replacement] 725 | (str/replace "asdf" #"," replacement)) 726 | 727 | (defn replace-commas [replacement] 728 | (str/replace "asdf" #"," replacement)) 729 | 730 | (defn replace-commas [replacement] 731 | (str/replace "asdf" #"," replacement)) 732 | 733 | (defn replace-commas [replacement] 734 | (str/replace "asdf" #"," replacement)) 735 | 736 | (defn replace-commas [replacement] 737 | (str/replace "asdf" #"," replacement)) 738 | 739 | (defn replace-commas [replacement] 740 | (str/replace "asdf" #"," replacement)) 741 | 742 | (defn replace-commas [replacement] 743 | (str/replace "asdf" #"," replacement)) 744 | 745 | (defn replace-commas [replacement] 746 | (str/replace "asdf" #"," replacement)) 747 | 748 | (defn replace-commas [replacement] 749 | (str/replace "asdf" #"," replacement)) 750 | 751 | (defn replace-commas [replacement] 752 | (str/replace "asdf" #"," replacement)) 753 | 754 | (defn replace-commas [replacement] 755 | (str/replace "asdf" #"," replacement)) 756 | 757 | (defn replace-commas [replacement] 758 | (str/replace "asdf" #"," replacement)) 759 | 760 | (defn replace-commas [replacement] 761 | (str/replace "asdf" #"," replacement)) 762 | 763 | (defn replace-commas [replacement] 764 | (str/replace "asdf" #"," replacement)) 765 | 766 | (defn replace-commas [replacement] 767 | (str/replace "asdf" #"," replacement)) 768 | 769 | (defn replace-commas [replacement] 770 | (str/replace "asdf" #"," replacement)) 771 | 772 | (defn replace-commas [replacement] 773 | (str/replace "asdf" #"," replacement)) 774 | 775 | (defn replace-commas [replacement] 776 | (str/replace "asdf" #"," replacement)) 777 | 778 | (defn replace-commas [replacement] 779 | (str/replace "asdf" #"," replacement)) 780 | 781 | (defn replace-commas [replacement] 782 | (str/replace "asdf" #"," replacement)) 783 | 784 | (defn replace-commas [replacement] 785 | (str/replace "asdf" #"," replacement)) 786 | 787 | (defn replace-commas [replacement] 788 | (str/replace "asdf" #"," replacement)) 789 | 790 | (defn replace-commas [replacement] 791 | (str/replace "asdf" #"," replacement)) 792 | 793 | (defn replace-commas [replacement] 794 | (str/replace "asdf" #"," replacement)) 795 | 796 | (defn replace-commas [replacement] 797 | (str/replace "asdf" #"," replacement)) 798 | 799 | (defn replace-commas [replacement] 800 | (str/replace "asdf" #"," replacement)) 801 | 802 | (defn replace-commas [replacement] 803 | (str/replace "asdf" #"," replacement)) 804 | 805 | (defn replace-commas [replacement] 806 | (str/replace "asdf" #"," replacement)) 807 | 808 | (defn replace-commas [replacement] 809 | (str/replace "asdf" #"," replacement)) 810 | 811 | (defn replace-commas [replacement] 812 | (str/replace "asdf" #"," replacement)) 813 | 814 | (defn replace-commas [replacement] 815 | (str/replace "asdf" #"," replacement)) 816 | 817 | (defn replace-commas [replacement] 818 | (str/replace "asdf" #"," replacement)) 819 | 820 | (defn replace-commas [replacement] 821 | (str/replace "asdf" #"," replacement)) 822 | 823 | (defn replace-commas [replacement] 824 | (str/replace "asdf" #"," replacement)) 825 | 826 | (defn replace-commas [replacement] 827 | (str/replace "asdf" #"," replacement)) 828 | 829 | (defn replace-commas [replacement] 830 | (str/replace "asdf" #"," replacement)) 831 | 832 | (defn replace-commas [replacement] 833 | (str/replace "asdf" #"," replacement)) 834 | 835 | (defn replace-commas [replacement] 836 | (str/replace "asdf" #"," replacement)) 837 | 838 | (defn replace-commas [replacement] 839 | (str/replace "asdf" #"," replacement)) 840 | 841 | (defn replace-commas [replacement] 842 | (str/replace "asdf" #"," replacement)) 843 | 844 | (defn replace-commas [replacement] 845 | (str/replace "asdf" #"," replacement)) 846 | 847 | (defn replace-commas [replacement] 848 | (str/replace "asdf" #"," replacement)) 849 | 850 | (defn replace-commas [replacement] 851 | (str/replace "asdf" #"," replacement)) 852 | 853 | (defn replace-commas [replacement] 854 | (str/replace "asdf" #"," replacement)) 855 | 856 | (defn replace-commas [replacement] 857 | (str/replace "asdf" #"," replacement)) 858 | 859 | (defn replace-commas [replacement] 860 | (str/replace "asdf" #"," replacement)) 861 | 862 | (defn replace-commas [replacement] 863 | (str/replace "asdf" #"," replacement)) 864 | 865 | (defn replace-commas [replacement] 866 | (str/replace "asdf" #"," replacement)) 867 | 868 | (defn replace-commas [replacement] 869 | (str/replace "asdf" #"," replacement)) 870 | 871 | (defn replace-commas [replacement] 872 | (str/replace "asdf" #"," replacement)) 873 | 874 | (defn replace-commas [replacement] 875 | (str/replace "asdf" #"," replacement)) 876 | 877 | (defn replace-commas [replacement] 878 | (str/replace "asdf" #"," replacement)) 879 | 880 | (defn replace-commas [replacement] 881 | (str/replace "asdf" #"," replacement)) 882 | 883 | (defn replace-commas [replacement] 884 | (str/replace "asdf" #"," replacement)) 885 | 886 | (defn replace-commas [replacement] 887 | (str/replace "asdf" #"," replacement)) 888 | 889 | (defn replace-commas [replacement] 890 | (str/replace "asdf" #"," replacement)) 891 | 892 | (defn replace-commas [replacement] 893 | (str/replace "asdf" #"," replacement)) 894 | 895 | (defn replace-commas [replacement] 896 | (str/replace "asdf" #"," replacement)) 897 | 898 | (defn replace-commas [replacement] 899 | (str/replace "asdf" #"," replacement)) 900 | 901 | (defn replace-commas [replacement] 902 | (str/replace "asdf" #"," replacement)) 903 | 904 | (defn replace-commas [replacement] 905 | (str/replace "asdf" #"," replacement)) 906 | 907 | (defn replace-commas [replacement] 908 | (str/replace "asdf" #"," replacement)) 909 | 910 | (defn replace-commas [replacement] 911 | (str/replace "asdf" #"," replacement)) 912 | 913 | (defn replace-commas [replacement] 914 | (str/replace "asdf" #"," replacement)) 915 | 916 | (defn replace-commas [replacement] 917 | (str/replace "asdf" #"," replacement)) 918 | 919 | (defn replace-commas [replacement] 920 | (str/replace "asdf" #"," replacement)) 921 | 922 | (defn replace-commas [replacement] 923 | (str/replace "asdf" #"," replacement)) 924 | 925 | (defn replace-commas [replacement] 926 | (str/replace "asdf" #"," replacement)) 927 | 928 | (defn replace-commas [replacement] 929 | (str/replace "asdf" #"," replacement)) 930 | 931 | (defn replace-commas [replacement] 932 | (str/replace "asdf" #"," replacement)) 933 | 934 | (defn replace-commas [replacement] 935 | (str/replace "asdf" #"," replacement)) 936 | 937 | (defn replace-commas [replacement] 938 | (str/replace "asdf" #"," replacement)) 939 | 940 | (defn replace-commas [replacement] 941 | (str/replace "asdf" #"," replacement)) 942 | 943 | (defn replace-commas [replacement] 944 | (str/replace "asdf" #"," replacement)) 945 | 946 | (defn replace-commas [replacement] 947 | (str/replace "asdf" #"," replacement)) 948 | 949 | (defn replace-commas [replacement] 950 | (str/replace "asdf" #"," replacement)) 951 | 952 | (defn replace-commas [replacement] 953 | (str/replace "asdf" #"," replacement)) 954 | 955 | (defn replace-commas [replacement] 956 | (str/replace "asdf" #"," replacement)) 957 | 958 | (defn replace-commas [replacement] 959 | (str/replace "asdf" #"," replacement)) 960 | 961 | (defn replace-commas [replacement] 962 | (str/replace "asdf" #"," replacement)) 963 | 964 | (defn replace-commas [replacement] 965 | (str/replace "asdf" #"," replacement)) 966 | 967 | (defn replace-commas [replacement] 968 | (str/replace "asdf" #"," replacement)) 969 | 970 | (defn replace-commas [replacement] 971 | (str/replace "asdf" #"," replacement)) 972 | 973 | (defn replace-commas [replacement] 974 | (str/replace "asdf" #"," replacement)) 975 | 976 | (defn replace-commas [replacement] 977 | (str/replace "asdf" #"," replacement)) 978 | 979 | (defn replace-commas [replacement] 980 | (str/replace "asdf" #"," replacement)) 981 | 982 | (defn replace-commas [replacement] 983 | (str/replace "asdf" #"," replacement)) 984 | 985 | (defn replace-commas [replacement] 986 | (str/replace "asdf" #"," replacement)) 987 | 988 | (defn replace-commas [replacement] 989 | (str/replace "asdf" #"," replacement)) 990 | 991 | (defn replace-commas [replacement] 992 | (str/replace "asdf" #"," replacement)) 993 | 994 | (defn replace-commas [replacement] 995 | (str/replace "asdf" #"," replacement)) 996 | 997 | (defn replace-commas [replacement] 998 | (str/replace "asdf" #"," replacement)) 999 | 1000 | (defn replace-commas [replacement] 1001 | (str/replace "asdf" #"," replacement)) 1002 | 1003 | (defn replace-commas [replacement] 1004 | (str/replace "asdf" #"," replacement)) 1005 | 1006 | (defn replace-commas [replacement] 1007 | (str/replace "asdf" #"," replacement)) 1008 | 1009 | (defn replace-commas [replacement] 1010 | (str/replace "asdf" #"," replacement)) 1011 | 1012 | (defn replace-commas [replacement] 1013 | (str/replace "asdf" #"," replacement)) 1014 | 1015 | (defn replace-commas [replacement] 1016 | (str/replace "asdf" #"," replacement)) 1017 | 1018 | (defn replace-commas [replacement] 1019 | (str/replace "asdf" #"," replacement)) 1020 | 1021 | (defn replace-commas [replacement] 1022 | (str/replace "asdf" #"," replacement)) 1023 | 1024 | (defn replace-commas [replacement] 1025 | (str/replace "asdf" #"," replacement)) 1026 | 1027 | (defn replace-commas [replacement] 1028 | (str/replace "asdf" #"," replacement)) 1029 | 1030 | (defn replace-commas [replacement] 1031 | (str/replace "asdf" #"," replacement)) 1032 | 1033 | (defn replace-commas [replacement] 1034 | (str/replace "asdf" #"," replacement)) 1035 | 1036 | (defn replace-commas [replacement] 1037 | (str/replace "asdf" #"," replacement)) 1038 | 1039 | (defn replace-commas [replacement] 1040 | (str/replace "asdf" #"," replacement)) 1041 | 1042 | (defn replace-commas [replacement] 1043 | (str/replace "asdf" #"," replacement)) 1044 | 1045 | (defn replace-commas [replacement] 1046 | (str/replace "asdf" #"," replacement)) 1047 | 1048 | (defn replace-commas [replacement] 1049 | (str/replace "asdf" #"," replacement)) 1050 | 1051 | (defn replace-commas [replacement] 1052 | (str/replace "asdf" #"," replacement)) 1053 | 1054 | (defn replace-commas [replacement] 1055 | (str/replace "asdf" #"," replacement)) 1056 | 1057 | (defn replace-commas [replacement] 1058 | (str/replace "asdf" #"," replacement)) 1059 | 1060 | (defn replace-commas [replacement] 1061 | (str/replace "asdf" #"," replacement)) 1062 | 1063 | (defn replace-commas [replacement] 1064 | (str/replace "asdf" #"," replacement)) 1065 | 1066 | (defn replace-commas [replacement] 1067 | (str/replace "asdf" #"," replacement)) 1068 | 1069 | (defn replace-commas [replacement] 1070 | (str/replace "asdf" #"," replacement)) 1071 | 1072 | (defn replace-commas [replacement] 1073 | (str/replace "asdf" #"," replacement)) 1074 | 1075 | (defn replace-commas [replacement] 1076 | (str/replace "asdf" #"," replacement)) 1077 | 1078 | (defn replace-commas [replacement] 1079 | (str/replace "asdf" #"," replacement)) 1080 | 1081 | (defn replace-commas [replacement] 1082 | (str/replace "asdf" #"," replacement)) 1083 | 1084 | (defn replace-commas [replacement] 1085 | (str/replace "asdf" #"," replacement)) 1086 | 1087 | (defn replace-commas [replacement] 1088 | (str/replace "asdf" #"," replacement)) 1089 | 1090 | (defn replace-commas [replacement] 1091 | (str/replace "asdf" #"," replacement)) 1092 | 1093 | (defn replace-commas [replacement] 1094 | (str/replace "asdf" #"," replacement)) 1095 | 1096 | (defn replace-commas [replacement] 1097 | (str/replace "asdf" #"," replacement)) 1098 | 1099 | (defn replace-commas [replacement] 1100 | (str/replace "asdf" #"," replacement)) 1101 | 1102 | (defn replace-commas [replacement] 1103 | (str/replace "asdf" #"," replacement)) 1104 | 1105 | (defn replace-commas [replacement] 1106 | (str/replace "asdf" #"," replacement)) 1107 | 1108 | (defn replace-commas [replacement] 1109 | (str/replace "asdf" #"," replacement)) 1110 | 1111 | (defn replace-commas [replacement] 1112 | (str/replace "asdf" #"," replacement)) 1113 | 1114 | (defn replace-commas [replacement] 1115 | (str/replace "asdf" #"," replacement)) 1116 | 1117 | (defn replace-commas [replacement] 1118 | (str/replace "asdf" #"," replacement)) 1119 | 1120 | (defn replace-commas [replacement] 1121 | (str/replace "asdf" #"," replacement)) 1122 | 1123 | (defn replace-commas [replacement] 1124 | (str/replace "asdf" #"," replacement)) 1125 | 1126 | (defn replace-commas [replacement] 1127 | (str/replace "asdf" #"," replacement)) 1128 | 1129 | (defn replace-commas [replacement] 1130 | (str/replace "asdf" #"," replacement)) 1131 | 1132 | (defn replace-commas [replacement] 1133 | (str/replace "asdf" #"," replacement)) 1134 | 1135 | (defn replace-commas [replacement] 1136 | (str/replace "asdf" #"," replacement)) 1137 | 1138 | (defn replace-commas [replacement] 1139 | (str/replace "asdf" #"," replacement)) 1140 | 1141 | (defn replace-commas [replacement] 1142 | (str/replace "asdf" #"," replacement)) 1143 | 1144 | (defn replace-commas [replacement] 1145 | (str/replace "asdf" #"," replacement)) 1146 | 1147 | (defn replace-commas [replacement] 1148 | (str/replace "asdf" #"," replacement)) 1149 | 1150 | (defn replace-commas [replacement] 1151 | (str/replace "asdf" #"," replacement)) 1152 | 1153 | (defn replace-commas [replacement] 1154 | (str/replace "asdf" #"," replacement)) 1155 | 1156 | (defn replace-commas [replacement] 1157 | (str/replace "asdf" #"," replacement)) 1158 | 1159 | (defn replace-commas [replacement] 1160 | (str/replace "asdf" #"," replacement)) 1161 | 1162 | (defn replace-commas [replacement] 1163 | (str/replace "asdf" #"," replacement)) 1164 | 1165 | (defn replace-commas [replacement] 1166 | (str/replace "asdf" #"," replacement)) 1167 | 1168 | (defn replace-commas [replacement] 1169 | (str/replace "asdf" #"," replacement)) 1170 | 1171 | (defn replace-commas [replacement] 1172 | (str/replace "asdf" #"," replacement)) 1173 | 1174 | (defn replace-commas [replacement] 1175 | (str/replace "asdf" #"," replacement)) 1176 | 1177 | (defn replace-commas [replacement] 1178 | (str/replace "asdf" #"," replacement)) 1179 | 1180 | (defn replace-commas [replacement] 1181 | (str/replace "asdf" #"," replacement)) 1182 | 1183 | (defn replace-commas [replacement] 1184 | (str/replace "asdf" #"," replacement)) 1185 | 1186 | (defn replace-commas [replacement] 1187 | (str/replace "asdf" #"," replacement)) 1188 | 1189 | (defn replace-commas [replacement] 1190 | (str/replace "asdf" #"," replacement)) 1191 | 1192 | (defn replace-commas [replacement] 1193 | (str/replace "asdf" #"," replacement)) 1194 | 1195 | (defn replace-commas [replacement] 1196 | (str/replace "asdf" #"," replacement)) 1197 | 1198 | (defn replace-commas [replacement] 1199 | (str/replace "asdf" #"," replacement)) 1200 | 1201 | (defn replace-commas [replacement] 1202 | (str/replace "asdf" #"," replacement)) 1203 | -------------------------------------------------------------------------------- /dev-resources/test-metadata.in: -------------------------------------------------------------------------------- 1 | (ns test-metadata) 2 | 3 | ;; Metadata on def'd symbols 4 | (do 5 | (def ^Pattern pattern #".") 6 | (def ^{:tag UUID} uuid #uuid "4103a2f9-225c-495b-a2cd-3cc93610d819") 7 | (def ^{Keyword []} import-as-metadata-key :keyword) 8 | (defmacro ^{:requires [AtomicLong]} ->atomic-long [n] 9 | `(AtomicLong. ~n))) 10 | 11 | ;; Metadata via attr-map 12 | (defmacro ->linked-list 13 | {:requires [LinkedList]} 14 | [] 15 | `(LinkedList.)) 16 | 17 | ;; Create macro with an embedded reference, then consume it 18 | (do 19 | (defmacro ->atomic-bool [n] 20 | `(AtomicBoolean. (not= 0 ~n))) 21 | 22 | (def atomic-bool (->atomic-bool 0))) 23 | 24 | ;; Metadata on symbols and lists 25 | (do 26 | (defn ->iterator [& xs] 27 | (let [ll (->linked-list)] 28 | (doseq [x xs] 29 | (.add ll x)) 30 | (.listIterator ll))) 31 | 32 | (defn dotest [] 33 | (let [^Iterator iter (->iterator (io/file "project.clj") 34 | (io/resource "test-metadata.in"))] 35 | (.toPath ^File (.next iter)) 36 | (.getFile ^{:tag URL} (.next iter))))) 37 | 38 | ;; Annotations 39 | (do 40 | (defprotocol IFoo 41 | (foo [this])) 42 | 43 | (deftype Foo [^{WebServiceRefs []} field] 44 | ^{SupportedOptions []} 45 | IFoo 46 | (^{Retention RetentionPolicy/RUNTIME} foo [this])) 47 | 48 | (cc/deftype Bar [] 49 | IFoo 50 | (^{ElementType 42} foo [this]))) 51 | -------------------------------------------------------------------------------- /dev-resources/test-metadata.out: -------------------------------------------------------------------------------- 1 | (ns test-metadata 2 | (:require [clojure.core :as cc] 3 | [clojure.java.io :as io]) 4 | (:import (clojure.lang Keyword) 5 | (java.io File) 6 | (java.lang.annotation ElementType Retention RetentionPolicy) 7 | (java.net URL) 8 | (java.util Iterator LinkedList UUID) 9 | (java.util.concurrent.atomic AtomicBoolean AtomicLong) 10 | (java.util.regex Pattern) 11 | (javax.annotation.processing SupportedOptions) 12 | (javax.xml.ws WebServiceRefs))) 13 | -------------------------------------------------------------------------------- /dev-resources/test-prefer-orig-ns-refs.in: -------------------------------------------------------------------------------- 1 | (ns prefer-string-over-set-or-korma 2 | (:require [clojure.string :refer [join]]) 3 | (:use [slam.hound-test :only [intersection]])) 4 | 5 | (defn do-it! [] 6 | (join "," ["a" "b" "c"]) 7 | (intersection #{:a} #{:b})) 8 | -------------------------------------------------------------------------------- /dev-resources/test-prefer-orig-ns-refs.out: -------------------------------------------------------------------------------- 1 | (ns prefer-string-over-set-or-korma 2 | (:require [clojure.string :refer [join]] 3 | [slam.hound-test :refer [intersection]])) 4 | 5 | (defn do-it! [] 6 | (join "," ["a" "b" "c"]) 7 | (intersection #{:a} #{:b})) 8 | -------------------------------------------------------------------------------- /dev-resources/test-prettify-libspecs.out: -------------------------------------------------------------------------------- 1 | (ns foo 2 | (:require [clojure.pprint :as pp :refer [*print-miser-width* 3 | cl-format code-dispatch 4 | formatter-out pprint 5 | pprint-logical-block 6 | pprint-newline 7 | with-pprint-dispatch 8 | write-out]])) 9 | -------------------------------------------------------------------------------- /dev-resources/test-prettify-long-libspecs.out: -------------------------------------------------------------------------------- 1 | (ns foo 2 | (:require [clojure.pprint :as pp 3 | :refer [formatter-out pprint-logical-block] 4 | :rename {formatter-out fout, pprint-logical-block block}])) 5 | -------------------------------------------------------------------------------- /dev-resources/test-prettify-no-miserly-refers.out: -------------------------------------------------------------------------------- 1 | (ns foo 2 | (:require [my.very.sequipedalian.namespace :refer [alpha beta gamma 3 | delta epsilon]])) 4 | -------------------------------------------------------------------------------- /dev-resources/test-prettify-short-requires.out: -------------------------------------------------------------------------------- 1 | (ns foo 2 | (:require [clojure.java.io :as io] 3 | [clojure.pprint :refer [pprint]])) 4 | -------------------------------------------------------------------------------- /dev-resources/test-reconstruct.in: -------------------------------------------------------------------------------- 1 | (ns slamhound.sample 2 | "Testing some things going on here." 3 | (:use [slam.hound.stitch :only [ns-from-map]] 4 | [clojure.test :only [is]] 5 | ;; 'testing' isn't used, so gets dropped 6 | [clojure.test :only [deftest testing]]) 7 | (:require [clojure.java.io :as io] 8 | [clojure.set :as set]) 9 | (:import java.io.File java.io.ByteArrayInputStream 10 | clojure.lang.Compiler$BodyExpr 11 | java.util.UUID) 12 | (:refer-clojure :exclude [compile test]) 13 | (:gen-class)) 14 | 15 | (set/union #{:a} #{:b}) 16 | 17 | (UUID/randomUUID) 18 | 19 | (instance? Compiler$BodyExpr nil) 20 | 21 | (instance? ExampleRecord nil) 22 | 23 | (io/copy (ByteArrayInputStream. (.getBytes "remotely")) 24 | (doto (File. "/tmp/remotely-human") .deleteOnExit)) 25 | 26 | (deftest ^:unit test-ns-to-map 27 | (is (= (ns-from-map {:ns 'slam.hound})))) 28 | 29 | (defmacro example-macro 30 | {:requires [BitSet sh #'with-sh-env]} 31 | [] 32 | `(do (BitSet.) 33 | (with-sh-env {} (sh "true")) 34 | (~string/join \, [1 2 3]))) 35 | -------------------------------------------------------------------------------- /dev-resources/test-reconstruct.out: -------------------------------------------------------------------------------- 1 | (ns slamhound.sample 2 | "Testing some things going on here." 3 | (:require [clojure.java.io :as io] 4 | [clojure.java.shell :refer [sh with-sh-env]] 5 | [clojure.set :as set] 6 | [clojure.string :as string] 7 | [clojure.test :refer [deftest is]] 8 | [slam.hound-test] 9 | [slam.hound.stitch :refer [ns-from-map]]) 10 | (:import (clojure.lang Compiler$BodyExpr) 11 | (java.io ByteArrayInputStream File) 12 | (java.util BitSet UUID) 13 | (slam.hound_test ExampleRecord)) 14 | (:refer-clojure :exclude [compile test]) 15 | (:gen-class)) 16 | -------------------------------------------------------------------------------- /dev-resources/test-stitch-up.out: -------------------------------------------------------------------------------- 1 | (ns slamhound.sample 2 | "Testing some \"things\" 3 | going on here." 4 | {:slamhound-skip true, :zzz "zzz"} 5 | (:require [clojure.java.io :as io] 6 | [clojure.set :as set :refer [union]] 7 | [clojure.test :refer [deftest is]] 8 | [slam.hound.stitch :refer [ns-from-map]]) 9 | (:import (clojure.lang Compiler$BodyExpr) 10 | (java.io ByteArrayInputStream File) 11 | (java.util UUID)) 12 | (:refer-clojure :exclude [compile test]) 13 | (:gen-class)) 14 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject slamhound "1.5.6-SNAPSHOT" 2 | :description "Rips your ns apart and reconstructs it. We have the technology." 3 | :url "https://github.com/technomancy/slamhound" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :main ^:skip-aot slam.hound 7 | :dependencies [[org.clojure/clojure "1.4.0"]] 8 | :profiles {:dev {:dependencies [[org.clojure/tools.trace "0.7.3"] 9 | ;; Last version of CLJS compatible with 10 | ;; Clojure 1.4.0 11 | [org.clojure/clojurescript "0.0-1535"] 12 | [org.clojars.runa/clj-schema "0.7.0"] 13 | [korma "0.3.0-beta11"]]}} 14 | :test-selectors {:default (constantly true) 15 | :integration :integration 16 | :unit :unit}) 17 | -------------------------------------------------------------------------------- /resources/swank_elisp_payloads.clj: -------------------------------------------------------------------------------- 1 | ["swank/payload/slamhound.el"] -------------------------------------------------------------------------------- /slamhound.el: -------------------------------------------------------------------------------- 1 | ;;; slamhound.el --- Rip Clojure namespaces apart and rebuild them. 2 | 3 | ;; Copyright © 2011-2012 Phil Hagelberg 4 | ;; 5 | ;; Author: Phil Hagelberg 6 | ;; URL: https://github.com/technomancy/slamhound 7 | ;; Version: 2.1.0 8 | ;; Keywords: tools, lisp 9 | 10 | ;; This file is not part of GNU Emacs. 11 | 12 | ;;; Commentary: 13 | 14 | ;; Destroys the ns form of a clojure-mode buffer and attempts to 15 | ;; rebuild it by searching the classpath. Requires an active 16 | ;; connection to either slime or nrepl. 17 | 18 | ;; M-x slamhound 19 | 20 | ;; If the namespace cannot be reconstructed for whatever reason, the 21 | ;; file will remain untouched and the reason will be shown. 22 | 23 | ;;; License: 24 | 25 | ;; This program is free software; you can redistribute it and/or 26 | ;; modify it under the terms of the GNU General Public License 27 | ;; as published by the Free Software Foundation; either version 3 28 | ;; of the License, or (at your option) any later version. 29 | ;; 30 | ;; This program is distributed in the hope that it will be useful, 31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 33 | ;; GNU General Public License for more details. 34 | ;; 35 | ;; You should have received a copy of the GNU General Public License 36 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 37 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 38 | ;; Boston, MA 02110-1301, USA. 39 | 40 | ;;; Code: 41 | 42 | (or (require 'nrepl-client nil t) 43 | (require 'nrepl nil t) 44 | (error "Please install either the nrepl.el or cider package.")) 45 | 46 | (defun slamhound-clj-string (filename) 47 | (format "%s" `(do (require 'slam.hound) 48 | (try (print (.trim (slam.hound/reconstruct 49 | ,(format "\"%s\"" filename)))) 50 | (catch Exception e 51 | (println :error (.getMessage e))))))) 52 | 53 | ;;;###autoload 54 | (defun slamhound () 55 | "Run slamhound on the current buffer. 56 | 57 | Requires active nrepl or slime connection." 58 | (interactive) 59 | (let* ((code (slamhound-clj-string buffer-file-name)) 60 | (result (plist-get (nrepl-send-string-sync code) :stdout))) 61 | (if (string-match "^:error \\(.*\\)" result) 62 | (error (match-string 1 result)) 63 | (goto-char (point-min)) 64 | ;; skip any header comments before the ns 65 | (forward-sexp) 66 | (backward-kill-sexp) 67 | (insert result)))) 68 | 69 | (provide 'slamhound) 70 | ;;; slamhound.el ends here 71 | -------------------------------------------------------------------------------- /src/slam/hound.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound 2 | (:require [clojure.java.io :as io] 3 | [clojure.string :as string] 4 | [slam.hound.asplode :refer [asplode]] 5 | [slam.hound.regrow :refer [regrow with-regrow-cache]] 6 | [slam.hound.stitch :refer [stitch-up]]) 7 | (:import (java.io File PushbackReader))) 8 | 9 | (defn reconstruct 10 | "Return a newly reconstructed version of file's ns form. The file parameter 11 | can be anything supported by clojure.java.io/reader." 12 | [file] 13 | ;; Reconstructing consists of three distinct phases: 14 | ;; asploding, regrowing, and stitching. 15 | (with-regrow-cache 16 | (-> file 17 | asplode 18 | regrow 19 | stitch-up))) 20 | 21 | (defn- read-comment-header 22 | "Read leading blank and comment lines from rdr." 23 | [^PushbackReader rdr] 24 | ;; An implementation using BufferedReader#readLine would be simpler, but 25 | ;; would have to make an assumption about what kind of line terminators the 26 | ;; file actually contains. 27 | (loop [buf (StringBuilder.) state :ws] 28 | (let [c (.read rdr)] 29 | (if (= c -1) ; EOF 30 | (str buf) 31 | (let [ch (char c)] 32 | (case state 33 | :comment (recur (.append buf ch) 34 | ;; CRLF and LF both end with LF 35 | (if (= ch \newline) :ws :comment)) 36 | :ws (cond (= ch \;) (recur (.append buf ch) :comment) 37 | (Character/isWhitespace ch) (recur (.append buf ch) :ws) 38 | :else (do (.unread rdr c) (str buf))))))))) 39 | 40 | (defn- tidy-comment-header [s] 41 | (-> s 42 | (string/replace-first #"\A\s*\n" "") 43 | (string/replace-first #"\n\s*\z" "\n\n"))) 44 | 45 | (defn swap-in-reconstructed-ns-form 46 | "Reconstruct file's ns form and rewrite the file on disk with the new form. 47 | The file parameter can be anything supported by clojure.java.io/file." 48 | [file] 49 | (let [file (io/file file) 50 | tmp-file (File/createTempFile "slamhound_tmp" ".clj")] 51 | (try 52 | (io/copy file tmp-file) 53 | (let [new-ns (string/trim (reconstruct tmp-file))] 54 | (with-open [rdr (PushbackReader. (io/reader tmp-file)) 55 | writer (io/writer file :append true)] 56 | (io/copy "" file) 57 | ;; Preserve comment header 58 | (let [header (read-comment-header rdr)] 59 | (when-not (string/blank? header) 60 | (io/copy (tidy-comment-header header) writer))) 61 | ;; move the reader past the namespace form; discard value 62 | (read rdr) 63 | ;; append the reconstructed ns form 64 | (io/copy new-ns writer) 65 | ;; append the body 66 | (io/copy rdr writer))) 67 | (finally 68 | (.delete tmp-file))))) 69 | 70 | (def ^:private ^:dynamic *testing?* false) 71 | 72 | (defn -main 73 | "Takes a file or dir and rewrites the .clj files with reconstructed ns forms. 74 | This function is strictly intended for use from a command line." 75 | [& file-or-dirs] 76 | (with-regrow-cache 77 | (doseq [file-or-dir file-or-dirs 78 | file (file-seq (io/file file-or-dir)) 79 | :let [file-str (string/replace 80 | (str file) 81 | (System/getProperty "file.separator") 82 | "/")] 83 | :when (re-find #"/[^\./]+\.clj$" file-str)] 84 | (try 85 | (swap-in-reconstructed-ns-form file) 86 | (catch Exception e 87 | (println "Failed to reconstruct:" file) 88 | (if (System/getenv "DEBUG") 89 | (.printStackTrace e) 90 | (println (.getMessage e))))))) 91 | (when-not *testing?* 92 | (shutdown-agents))) 93 | 94 | ;; See https://github.com/technomancy/nrepl-discover 95 | (defn ^{:nrepl/op {:name "slam" 96 | :args [["file" "file" "File: "]] 97 | :doc "Rewrite the ns form of a given file."}} 98 | slam-ns 99 | "Slamhound rewrite command intended to be exposed for direct editor use." 100 | [{:keys [transport file] :as msg}] 101 | (let [nrepl-send (resolve 'clojure.tools.nrepl.transport/send) 102 | response-for (resolve 'clojure.tools.nrepl.misc/response-for)] 103 | (nrepl-send transport (response-for msg :message "Reconstructing...")) 104 | (swap-in-reconstructed-ns-form file) 105 | (nrepl-send transport (response-for msg :reload file)))) 106 | -------------------------------------------------------------------------------- /src/slam/hound/asplode.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.asplode 2 | (:require [clojure.java.io :as io] 3 | [slam.hound.future :refer [as->* cond->*]]) 4 | (:import (java.io PushbackReader))) 5 | 6 | (def empty-ns-references 7 | ;; NOTE: :verbose, :reload, and :reload-all can actually be specified per 8 | ;; libspec, but this is not mentioned in the docstring for require, so 9 | ;; we consider it an implementation detail. 10 | {:import #{} ; #{class-sym} 11 | :require #{} ; #{ns-sym} 12 | :alias {} ; {ns-sym ns-sym} 13 | :refer {} ; {ns-sym #{var-sym}} 14 | :xrefer #{} ; #{ns-sym} - exclusively referred namespaces 15 | :refer-all #{} ; #{ns-sym} 16 | :exclude {} ; {ns-sym #{var-sym}} 17 | :rename {} ; {ns-sym {var-sym var-sym}} 18 | :reload #{} ; #{ns-sym} 19 | :reload-all #{} ; #{ns-sym} 20 | :verbose #{} ; #{ns-sym} 21 | :load nil ; a seq of file paths 22 | :gen-class nil ; a seq of option pairs, possibly empty 23 | }) 24 | 25 | (def ns-clauses 26 | "Set of valid keys that begin clauses in ns forms." 27 | #{:refer-clojure :use :require :import :load :gen-class}) 28 | 29 | (defn- libspec? 30 | "Returns true if x is a libspec. 31 | Copied from clojure.core/libspec?" 32 | [x] 33 | (or (symbol? x) 34 | (and (vector? x) 35 | (or 36 | (nil? (second x)) 37 | (keyword? (second x)))))) 38 | 39 | (defn- prependss 40 | "Prepends a symbol or a seq to coll. 41 | Adapted from clojure.core/prependss" 42 | [prefix x coll] 43 | (letfn [(p [sym] (if prefix (symbol (str prefix \. sym)) sym))] 44 | (if (symbol? x) 45 | (into [(p x)] coll) 46 | (let [[x & xs] x] 47 | (into [(p x)] (concat xs coll)))))) 48 | 49 | (defn- vmerge 50 | "Value-dependent merge, intended for merging ns reference maps." 51 | [m1 m2] 52 | (reduce-kv 53 | (fn [m k v] 54 | (cond (map? v) (merge-with vmerge m {k v}) 55 | (set? v) (assoc m k (into (get m k #{}) v)) 56 | (vector? v) (assoc m k (into (get m k []) v)) 57 | :else (assoc m k v))) 58 | m1 m2)) 59 | 60 | (defn expand-imports 61 | "Expand import-lists into a set of symbols. 62 | 63 | cf. clojure.core/import" 64 | [specs] 65 | (reduce 66 | (fn [s spec] 67 | (if (symbol? spec) 68 | (conj s spec) 69 | (let [[p & qs] spec] 70 | ;; Note that prefix lists with only a prefix are ignored 71 | (into s (map #(symbol (str p \. %)) qs))))) 72 | #{} specs)) 73 | 74 | (defn expand-libspecs 75 | "Expand any prefix lists and flags within specs and return a flat set of 76 | libspecs. 77 | 78 | cf. clojure.core/load-libs" 79 | [specs] 80 | (let [flags (filter keyword? specs) 81 | opts (interleave flags (repeat true)) 82 | args (remove keyword? specs)] 83 | (reduce 84 | (fn [s arg] 85 | (if (libspec? arg) 86 | (conj s (prependss nil arg opts)) 87 | (let [[prefix & more] arg] 88 | (into s (mapv #(prependss prefix % opts) more))))) 89 | #{} args))) 90 | 91 | (defn parse-refers 92 | "Parse as `(clojure.core/refer ~ns-sym ~@filters), returning a map with 93 | :exclude, :refer, :xrefer, :refer-all, and :rename. 94 | 95 | If the :exclusive option is true, the :only option also adds ns-sym to the 96 | :xrefer set." 97 | [ns-sym filters & opts] 98 | (if (seq filters) 99 | (let [{:keys [exclude only rename]} (apply hash-map filters) 100 | {:keys [exclusive]} (apply hash-map opts)] 101 | (cond->* {} 102 | exclude (assoc :exclude {ns-sym (set exclude)}) 103 | only (as->* m 104 | (cond->* (assoc m :refer {ns-sym (set only)}) 105 | exclusive (assoc :xrefer #{ns-sym}))) 106 | rename (assoc :rename {ns-sym rename}))) 107 | {:refer-all #{ns-sym}})) 108 | 109 | (defn parse-requires 110 | "Parse a list of require libspecs, returning a map with :require, :alias, 111 | :refer, :refer-all, :reload, :reload-all, and :verbose. Expands prefix lists. 112 | 113 | cf. clojure.core/load-libs" 114 | [specs] 115 | (reduce 116 | (fn [m [ns-sym & opts]] 117 | (if (seq opts) 118 | (let [{:keys [as refer rename reload reload-all verbose]} 119 | (apply hash-map opts)] 120 | (vmerge m (cond->* {} 121 | as (assoc :alias {ns-sym as}) 122 | refer (as->* m (if (= refer :all) 123 | (assoc m :refer-all #{ns-sym}) 124 | (assoc m :refer {ns-sym (set refer)}))) 125 | rename (as->* m (vmerge m (parse-refers 126 | ns-sym [:rename rename]))) 127 | reload (assoc :reload (conj (get m :reload #{}) ns-sym)) 128 | reload-all (assoc :reload-all 129 | (conj (get m :reload-all #{}) ns-sym)) 130 | verbose (assoc :verbose 131 | (conj (get m :verbose #{}) ns-sym))))) 132 | (vmerge m {:require #{ns-sym}}))) 133 | {} (expand-libspecs specs))) 134 | 135 | (defn parse-uses 136 | "Parse a list of use libspecs, via parse-refers and parse-requires." 137 | [specs] 138 | (reduce 139 | (fn [m [ns-sym & opts]] 140 | (if (seq opts) 141 | (reduce-kv 142 | (fn [m k v] 143 | (vmerge m (if (contains? #{:exclude :only :rename} k) 144 | (parse-refers ns-sym [k v]) 145 | (parse-requires [[ns-sym k v]])))) 146 | m (apply hash-map opts)) 147 | (vmerge m (parse-refers ns-sym [])))) 148 | {} (expand-libspecs specs))) 149 | 150 | (defn parse-libs 151 | "Parse ns reference declaration and intelligently merge into nsrefs." 152 | [nsrefs kw specs] 153 | (case kw 154 | :refer-clojure (vmerge nsrefs (parse-refers 155 | 'clojure.core specs :exclusive true)) 156 | :use (vmerge nsrefs (parse-uses specs)) 157 | :require (reduce (fn [m s] (vmerge m (parse-requires s))) nsrefs specs) 158 | :import (vmerge nsrefs {:import (expand-imports specs)}) 159 | :load (assoc nsrefs :load specs) 160 | :gen-class (assoc nsrefs :gen-class specs))) 161 | 162 | (defn ns-to-map [ns-form] 163 | (let [[_ ns-name maybe-doc & clauses] ns-form 164 | ns-meta (meta ns-name) 165 | [ns-meta clauses] (if (string? maybe-doc) 166 | [(assoc ns-meta :doc maybe-doc) clauses] 167 | [ns-meta (cons maybe-doc clauses)]) 168 | [ns-meta clauses] (if (map? (first clauses)) 169 | [(merge ns-meta (first clauses)) (rest clauses)] 170 | [ns-meta clauses])] 171 | (reduce 172 | (fn [m [clause & body]] 173 | ;; (:gen-class) with no arguments is valid 174 | (let [clause (keyword clause) 175 | body (if (and (nil? body) (= clause :gen-class)) 176 | [] 177 | body) 178 | ;; Separate require clauses may have different flags 179 | add (if (= clause :require) conj into)] 180 | (assoc m clause (add (get m clause []) body)))) 181 | {:meta ns-meta :name ns-name} clauses))) 182 | 183 | (defn parse-ns-map 184 | "Reduce namespace map into slam.hound.asplode/empty-ns-references" 185 | [ns-map] 186 | (reduce (fn [m k] 187 | (if-let [v (ns-map k)] 188 | (parse-libs m k v) 189 | m)) 190 | empty-ns-references ns-clauses)) 191 | 192 | (defn preserve-ns-references 193 | "Extract map of :gen-class, :load, :reload, :reload-all, :verbose, :refer, 194 | :exclude, and :rename that should be preserved in an ns declaration." 195 | [ns-map] 196 | (let [{:keys [gen-class load refer exclude rename 197 | reload reload-all verbose]} ns-map 198 | assoc-clojure (fn [m kw vs] 199 | (if-let [v (get vs 'clojure.core)] 200 | (assoc m kw {'clojure.core v}) 201 | m))] 202 | (-> (cond->* {} 203 | gen-class (assoc :gen-class gen-class) 204 | load (assoc :load load) 205 | (seq reload) (assoc :reload reload) 206 | (seq reload-all) (assoc :reload-all reload-all) 207 | (seq verbose) (assoc :verbose verbose)) 208 | (assoc-clojure :refer refer) 209 | (assoc-clojure :exclude exclude) 210 | (assoc-clojure :rename rename)))) 211 | 212 | (defn asplode [file] 213 | (with-open [rdr (PushbackReader. (io/reader file))] 214 | (let [ns-map (ns-to-map (read rdr)) 215 | old-ns (parse-ns-map ns-map) 216 | stripped-ns (-> (apply dissoc ns-map ns-clauses) 217 | (assoc :old old-ns) 218 | (merge (preserve-ns-references old-ns))) 219 | ;; All unqualified, unquoted symbols are qualified to *ns* by the 220 | ;; reader during syntax-quote expansion, so we must be sure that 221 | ;; *ns* is set to the file's namespace in order for the regrow step 222 | ;; to work properly. 223 | body (binding [*ns* (create-ns (:name ns-map))] 224 | (doall 225 | (take-while #(not= ::done %) 226 | (repeatedly #(read rdr false ::done)))))] 227 | [stripped-ns body]))) 228 | -------------------------------------------------------------------------------- /src/slam/hound/future.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.future 2 | "Backports from future versions of Clojure") 3 | 4 | (defmacro cond->* 5 | "Takes an expression and a set of test/form pairs. Threads expr (via ->) 6 | through each form for which the corresponding test 7 | expression is true. Note that, unlike cond branching, cond->* threading does 8 | not short circuit after the first true test expression." 9 | {:added "1.5"} 10 | [expr & clauses] 11 | (assert (even? (count clauses))) 12 | (let [g (gensym) 13 | pstep (fn [[test step]] `(if ~test (-> ~g ~step) ~g))] 14 | `(let [~g ~expr 15 | ~@(interleave (repeat g) (map pstep (partition 2 clauses)))] 16 | ~g))) 17 | 18 | (defmacro as->* 19 | "Binds name to expr, evaluates the first form in the lexical context 20 | of that binding, then binds name to that result, repeating for each 21 | successive form, returning the result of the last form." 22 | {:added "1.5"} 23 | [expr name & forms] 24 | `(let [~name ~expr 25 | ~@(interleave (repeat name) forms)] 26 | ~name)) 27 | -------------------------------------------------------------------------------- /src/slam/hound/prettify.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.prettify 2 | "Format a namespace declaration using pretty print with custom dispatch. 3 | 4 | Documentation on Common Lisp format strings: 5 | https://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html" 6 | (:require [clojure.pprint :refer [*print-miser-width* cl-format code-dispatch 7 | formatter-out pprint pprint-logical-block 8 | pprint-newline with-pprint-dispatch 9 | write-out]] 10 | [clojure.string :refer [escape]])) 11 | 12 | (defn- brackets 13 | "Figure out which kind of brackets to use" 14 | [form] 15 | (if (vector? form) 16 | ["[" "]"] 17 | ["(" ")"])) 18 | 19 | (defn- pprint-indented-coll 20 | "Pretty print a collection, setting an indent point before the head element 21 | and reflowing to multiple lines as necessary." 22 | [coll] 23 | ((formatter-out "~:i~{~w~^ ~:_~}") coll)) 24 | 25 | (defn- pprint-liblist 26 | "Pretty print dispatch chunk for ns libspecs and prefix lists." 27 | [form] 28 | (let [[start end] (brackets form) 29 | [head & tail] form] 30 | (pprint-logical-block :prefix start :suffix end 31 | (write-out head) 32 | (when (seq tail) 33 | ((formatter-out " ")) 34 | (let [opts (partition-all 2 tail)] 35 | ;; Is this a libspec? i.e. [head :key1 val1 :key2 val2] 36 | (if (every? keyword? (map first opts)) 37 | (let [ncoll (count (filter coll? (map second opts)))] 38 | (loop [[[key val] & next-opts] opts] 39 | (when (> ncoll 1) 40 | ((formatter-out "~:_"))) 41 | ((formatter-out "~w ") key) 42 | (if (sequential? val) 43 | (let [[start end] (brackets val)] 44 | (pprint-logical-block :prefix start :suffix end 45 | (pprint-indented-coll val))) 46 | (write-out val)) 47 | (when next-opts 48 | ((formatter-out " ")) 49 | (recur next-opts)))) 50 | (pprint-indented-coll tail))))))) 51 | 52 | (defn- pprint-ns-reference 53 | "Pretty print a single reference (import, use, etc.) from a namespace decl" 54 | [reference] 55 | (if (sequential? reference) 56 | (let [[start end] (brackets reference) 57 | [keyw & args] reference] 58 | (pprint-logical-block :prefix start :suffix end 59 | ((formatter-out "~w~:i") keyw) 60 | (loop [args args] 61 | (when (seq args) 62 | ((formatter-out " ")) 63 | (let [arg (first args)] 64 | (if (sequential? arg) 65 | (do (pprint-liblist arg) 66 | (when (next args) 67 | ;; liblists should always be on their own line 68 | ((formatter-out "~:@_")))) 69 | (do 70 | (write-out arg) 71 | (when (next args) 72 | ((formatter-out "~:_")))))) 73 | (recur (next args)))))) 74 | (write-out reference))) 75 | 76 | (defn- pprint-ns 77 | "The pretty print dispatch chunk for the ns macro" 78 | [alis] 79 | (if (next alis) 80 | (let [[ns-sym ns-name & stuff] alis 81 | [doc-str stuff] (if (string? (first stuff)) 82 | [(first stuff) (next stuff)] 83 | [nil stuff]) 84 | [attr-map references] (if (map? (first stuff)) 85 | [(first stuff) (next stuff)] 86 | [nil stuff])] 87 | (pprint-logical-block :prefix "(" :suffix ")" 88 | ((formatter-out "~w ~1I~@_~w") ns-sym ns-name) 89 | (when (or doc-str attr-map (seq references)) 90 | ((formatter-out "~@:_"))) 91 | (when doc-str 92 | (let [doc-str (escape doc-str {\\ "\\\\" \" "\\\""})] 93 | (cl-format true "\"~a\"~:[~;~:@_~]" doc-str 94 | (or attr-map (seq references))))) 95 | (when attr-map 96 | ((formatter-out "~w~:[~;~:@_~]") attr-map (seq references))) 97 | (when references 98 | (loop [references references] 99 | (pprint-ns-reference (first references)) 100 | (when-let [references (next references)] 101 | (pprint-newline :linear) 102 | (recur references)))))) 103 | (write-out alis))) 104 | 105 | (defn augmented-dispatch 106 | "A wrapper for code-dispatch that supports better ns printing" 107 | [form] 108 | (if (and (seq? form) (= 'ns (first form))) 109 | (pprint-ns form) 110 | (code-dispatch form))) 111 | 112 | (defn prettify 113 | "Pretty print the ns-form to a string" 114 | [ns-form] 115 | (with-out-str 116 | ;; :miser mode often results in ugly libspecs, so disable it 117 | (binding [*print-miser-width* nil] 118 | (with-pprint-dispatch augmented-dispatch 119 | (pprint ns-form))))) 120 | -------------------------------------------------------------------------------- /src/slam/hound/regrow.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.regrow 2 | (:require [clojure.set :as set] 3 | [clojure.string :as string] 4 | [slam.hound.future :refer [as->* cond->*]] 5 | [slam.hound.search :as search] 6 | [slam.hound.stitch :as stitch]) 7 | (:import (clojure.lang IMapEntry IRecord) 8 | (java.util.regex Pattern))) 9 | 10 | (def ^:dynamic *debug* false) 11 | 12 | ;; sometimes we can't rely on stdout (testing slamhound.el) 13 | (def debug-log (atom [])) 14 | 15 | (defn- debug [& msg] 16 | (when *debug* 17 | (swap! debug-log conj msg) 18 | (apply prn msg))) 19 | 20 | (def ^:dynamic *cache* nil) 21 | 22 | (defmacro with-regrow-cache [& body] 23 | `(binding [*cache* (or *cache* (atom {}))] 24 | ~@body)) 25 | 26 | (defmacro ^:private caching [key & body] 27 | `(if *cache* 28 | (if-let [v# (get @*cache* ~key)] 29 | v# 30 | (let [v# (do ~@body)] 31 | (swap! *cache* assoc ~key v#) 32 | v#)) 33 | (do ~@body))) 34 | 35 | (defn- all-ns-imports [] 36 | (caching :all-ns-imports 37 | (mapv ns-imports (all-ns)))) 38 | 39 | (defn- ns->symbols [] 40 | (caching :ns->symbols 41 | (let [xs (all-ns)] 42 | (zipmap xs (mapv (comp set keys ns-publics) xs))))) 43 | 44 | (defn- symbols->ns-syms [] 45 | (caching :symbols->ns 46 | (reduce 47 | (fn [m ns] (let [ns-sym (ns-name ns)] 48 | (reduce 49 | (fn [m k] (assoc m k (conj (get m k #{}) ns-sym))) 50 | m (keys (ns-publics ns))))) 51 | {} (all-ns)))) 52 | 53 | (defn- walk 54 | "Adapted from clojure.walk/walk and clojure.walk/prewalk; this version 55 | preserves metadata on compound forms." 56 | [f form] 57 | (-> (cond 58 | (list? form) (apply list (map f form)) 59 | (instance? IMapEntry form) (vec (map f form)) 60 | (seq? form) (doall (map f form)) 61 | (instance? IRecord form) (reduce (fn [r x] (conj r (f x))) form form) 62 | (coll? form) (into (empty form) (map f form)) 63 | :else form) 64 | (as->* form' 65 | (if-let [m (meta form)] 66 | (with-meta form' m) 67 | form')))) 68 | 69 | (defn- prewalk [f form] 70 | (walk (partial prewalk f) (f form))) 71 | 72 | (defn- capitalized? [x] 73 | (Character/isUpperCase ^Character (first (name x)))) 74 | 75 | (def ^:private missing-sym-patterns 76 | (let [sym-pat #"(/|\D[^\p{javaWhitespace},/]*)" 77 | patterns (->> [#"Unable to resolve \w+: " 78 | "Can't resolve: " 79 | "No such namespace: " 80 | "Cannot resolve type: " ; core.typed 81 | "java.lang.ClassNotFoundException:? " ; http://dev.clojure.org/jira/browse/CLJ-1403 82 | ] 83 | (mapv #(Pattern/compile (str % sym-pat))))] 84 | (into [#"No such var: (\S+)/.*"] patterns))) 85 | 86 | (defn- missing-sym-name [msg] 87 | (second (some #(re-find % msg) missing-sym-patterns))) 88 | 89 | (defn- failure-details [msg old-ns-map] 90 | (when-let [sym-name (missing-sym-name msg)] 91 | (let [sym (symbol sym-name) 92 | ts (cond (capitalized? sym-name) 93 | (if (some #(contains? % sym) (vals (:refer old-ns-map))) 94 | [:refer :import] 95 | [:import :refer]) 96 | 97 | (re-find #"Unable to resolve var: \S+/" msg) 98 | [:alias :refer] 99 | 100 | (re-find #"No such (var|namespace)" msg) 101 | [:alias] 102 | 103 | (re-find #"java\.lang\.ClassNotFoundException" msg) 104 | [:alias] 105 | 106 | :else 107 | [:refer :import]) 108 | rename? (some #{sym} (mapcat (comp vals val) (:rename old-ns-map)))] 109 | ;; Always attempt a rename first if there is a valid candidate 110 | {:missing sym 111 | :possible-types (if (and rename? (some #{:refer} ts)) 112 | (into [:rename] ts) 113 | ts)}))) 114 | 115 | (defn- check-for-failure [ns-map body] 116 | (let [sandbox-ns `slamhound.sandbox# 117 | ns-form (stitch/ns-from-map (assoc ns-map :name sandbox-ns))] 118 | (binding [*ns* (create-ns sandbox-ns)] 119 | (try 120 | (eval `(do ~ns-form ~@body nil)) 121 | (catch Exception e 122 | (or (failure-details (str (type e) " " (.getMessage e)) (:old ns-map)) 123 | (do (debug :not-found ns-form) 124 | (throw e)))) 125 | (finally 126 | (remove-ns (.name *ns*))))))) 127 | 128 | (defn- symbols-in-body [body] 129 | (filter symbol? (remove coll? (rest (tree-seq coll? seq body))))) 130 | 131 | (defn- remove-var-form 132 | "Remove (var symbol) forms from body" 133 | [expr] 134 | (if (and (coll? expr) (= (first expr) 'var)) 135 | nil 136 | expr)) 137 | 138 | (def ^:private ns-qualifed-syms 139 | (memoize 140 | (fn [body] 141 | (apply merge-with set/union {} 142 | (for [ss (symbols-in-body body) 143 | :let [[_ alias var-name] (re-matches #"(.+)/(.+)" (str ss))] 144 | :when alias] 145 | {(symbol alias) #{(symbol var-name)}}))))) 146 | 147 | (defn- make-munged-ns-pattern [package-name] 148 | (->> (string/split package-name #"_") 149 | (map #(Pattern/quote %)) 150 | (string/join "[_-]") 151 | (#(Pattern/compile (str "\\A" % "_*\\z"))))) 152 | 153 | (defn- find-matching-ns 154 | "Match a Java package name to a Clojure Namespace. 155 | Returns an ns symbol or nil." 156 | [package-name] 157 | ;; Try the simple case before doing a search 158 | (let [ns-sym (symbol (string/replace package-name \_ \-))] 159 | (if (find-ns ns-sym) 160 | ns-sym 161 | (let [pat (make-munged-ns-pattern package-name)] 162 | (first (filter #(re-find pat (str %)) (map ns-name (all-ns)))))))) 163 | 164 | (defn- ns-import-candidates 165 | "Search (all-ns) for imports that match missing-sym, returning a set of 166 | class symbols. This is slower than scanning through the list of static 167 | package names, but will successfully find dynamically created classes such 168 | as those created by deftype, defrecord, and definterface." 169 | [missing-sym] 170 | (reduce (fn [s imports] 171 | (if-let [^Class cls (get imports missing-sym)] 172 | (conj s (symbol (.getCanonicalName cls))) 173 | s)) 174 | #{} (all-ns-imports))) 175 | 176 | (defn- alias-candidates [type missing body] 177 | (set 178 | (let [syms-with-alias (get (ns-qualifed-syms body) missing)] 179 | (when (seq syms-with-alias) 180 | (let [ns->syms (ns->symbols)] 181 | (for [ns (all-ns) 182 | :when (set/subset? syms-with-alias (ns->syms ns))] 183 | (ns-name ns))))))) 184 | 185 | (defn- candidates 186 | "Return a set of class or ns symbols that match the given constraints." 187 | [type missing body old-ns-map] 188 | (case type 189 | :import (into (ns-import-candidates missing) 190 | (get @search/available-classes-by-last-segment missing)) 191 | :alias (let [cs (alias-candidates type missing body)] 192 | (if (seq cs) 193 | cs 194 | ;; Try the alias search again without dynamically resolved vars 195 | ;; in case #' was used to resolve private vars in an aliased ns 196 | (let [body' (prewalk remove-var-form body)] 197 | (if (= body' body) 198 | cs 199 | (alias-candidates type missing body'))))) 200 | :refer (get (symbols->ns-syms) missing) 201 | :rename (reduce-kv 202 | (fn [s ns orig->rename] 203 | (cond->* s 204 | (some #{missing} (vals orig->rename)) (conj ns))) 205 | #{} (:rename old-ns-map)))) 206 | 207 | (defn- filter-excludes 208 | "Disjoin namespace symbols from candidates that match the :exclude and 209 | :xrefer values in old-ns-map." 210 | [type missing old-ns-map candidates] 211 | (if (= type :refer) 212 | (let [{:keys [exclude xrefer refer]} old-ns-map 213 | cs (reduce (fn [s [ns syms]] 214 | (if (and (contains? s ns) 215 | (contains? syms missing)) 216 | (disj s ns) 217 | s)) 218 | candidates exclude) 219 | cs (reduce (fn [s ns] 220 | (if (and (contains? s ns) 221 | (not (contains? (refer ns) missing))) 222 | (disj s ns) 223 | s)) 224 | cs xrefer)] 225 | cs) 226 | candidates)) 227 | 228 | (defn- last-segment [s] 229 | (peek (string/split s #"\."))) 230 | 231 | (def ^:private disambiguator-blacklist 232 | (if-let [v (resolve 'user/slamhound-disambiguator-blacklist)] 233 | @v 234 | #"\Acljs\.|swank|lancet")) 235 | 236 | (defn- in-originals-fn 237 | "To what extent is the candidate present in the original ns-map?" 238 | [type missing old-ns-map] 239 | (fn [candidate] 240 | (case type 241 | :import (if (contains? (:import old-ns-map) candidate) 242 | 0 243 | 1) 244 | :alias (let [as (:alias old-ns-map)] 245 | (if (and (contains? as candidate) 246 | (= (as candidate) missing)) 247 | 0 248 | 1)) 249 | :refer (let [{:keys [refer-all refer]} old-ns-map 250 | all? (contains? refer-all candidate) 251 | ref? (and (contains? refer candidate) 252 | (contains? (refer candidate) missing))] 253 | (cond (and all? ref?) 0 254 | ref? 1 255 | all? 2 256 | :else 3)) 257 | ;; Renames are only considered when they exist in the original ns 258 | :rename 0))) 259 | 260 | (defn- last-segment-matches-fn 261 | "Does the last segment of the candidate match the missing alias?" 262 | [type missing] 263 | (let [alias (name missing)] 264 | (fn [candidate] 265 | (if (and (= type :alias) 266 | (= alias (last-segment (name candidate)))) 267 | 0 268 | 1)))) 269 | 270 | (defn- is-project-namespace-fn 271 | "Is the namespace or class defined in a file on the classpath, as opposed 272 | to a jar?" 273 | [type] 274 | (fn [candidate] 275 | (if (= type :import) 276 | (if (find-matching-ns (second (re-find #"(.*)\." (str candidate)))) 277 | 0 278 | 1) 279 | (if (contains? (search/namespaces-from-files) candidate) 280 | 0 281 | 1)))) 282 | 283 | (defn- alias-distance [^String alias ^String cand] 284 | (if (= (first alias) (first cand)) 285 | (let [alen (.length alias) 286 | clen (.length cand)] 287 | (loop [d 0 ; alias-distance 288 | i 1 ; alias index 289 | j 1 ; candidate index 290 | matched? true ; current alias match state 291 | ] 292 | (if (or (>= i alen) (>= j clen)) 293 | (if (and matched? (= i alen)) 294 | d 295 | Long/MAX_VALUE) 296 | (if (= (.charAt alias i) (.charAt cand j)) 297 | (recur d (inc i) (inc j) true) 298 | (recur (inc d) i (inc j) false))))) 299 | Long/MAX_VALUE)) 300 | 301 | (defn- alias-distance-fn 302 | "If the candidate shares the same first character with the missing alias, 303 | how many characters must be added between the first and last characters of 304 | the alias to form a subsequence of the last segment of the candidate? 305 | 306 | e.g. 0: st -> clojure.string 307 | 1: st -> clojure.set 308 | 2: st -> my.switchboard 309 | MAX_VALUE: str -> clojure.set 310 | MAX_VALUE: ring -> clojure.string" 311 | [type missing] 312 | (let [alias (name missing)] 313 | (fn [candidate] 314 | (if (= type :alias) 315 | (alias-distance alias (last-segment (name candidate))) 316 | Long/MAX_VALUE)))) 317 | 318 | (defn- initials-match-alias-fn 319 | "Do the initials of the candidate match the missing alias?" 320 | [type missing] 321 | (let [alias (name missing)] 322 | (fn [candidate] 323 | (if (and (= type :alias) 324 | (= alias (->> (string/split (name candidate) #"\.") 325 | (map first) 326 | (string/join)))) 327 | 0 328 | 1)))) 329 | 330 | (defn disambiguate 331 | "Select the most likely class or ns symbol in the given set of candidates, 332 | returning [type candidate-sym]" 333 | [candidates type missing ns-maps] 334 | ;; TODO: prefer things in classes to jars 335 | (debug :disambiguating missing :in candidates) 336 | (let [{:keys [old-ns-map new-ns-map]} ns-maps 337 | cs (cond->* candidates 338 | ;; Current ns is never a valid reference source 339 | true (disj (:name new-ns-map)) 340 | ;; Prevent multiple aliases to a single namespace (ugh) 341 | (= type :alias) (set/difference (set (keys (:alias new-ns-map))))) 342 | cs (->> cs 343 | (filter-excludes type missing old-ns-map) 344 | (remove #(re-find disambiguator-blacklist (str %))) 345 | (sort-by (juxt (in-originals-fn type missing old-ns-map) 346 | (last-segment-matches-fn type missing) 347 | (is-project-namespace-fn type) 348 | (alias-distance-fn type missing) 349 | (initials-match-alias-fn type missing) 350 | (comp count str))))] 351 | (when-let [c (first cs)] 352 | ;; Honor any old [c :refer :all] specs - issue #50 353 | (if (and (= type :refer) 354 | (contains? (:refer-all old-ns-map) c)) 355 | [:refer-all c] 356 | [type c])))) 357 | 358 | (defn- update-imports-in 359 | "Adds candidate to :import entry in ns-map. If the candidate class package 360 | name corresponds to a Clojure namespace, the matching namespace is added to 361 | the :require entry. 362 | 363 | While deftypes and defrecords implement clojure.lang.IType, there is no 364 | metadata that can determine if a class foo_bar.Baz has been created by 365 | gen-class or geninterface. Therefore we can only rely on convention." 366 | [ns-map candidate] 367 | (let [pkg (stitch/get-package (Class/forName (str candidate))) 368 | ns-map (update-in ns-map [:import] #(conj (or % #{}) candidate))] 369 | (if-let [ns-sym (find-matching-ns pkg)] 370 | (update-in ns-map [:require] #(conj (or % #{}) ns-sym)) 371 | ns-map))) 372 | 373 | (defn grow-ns-map 374 | "Return a new ns-map augmented with candidate ns reference(s)." 375 | [ns-map type missing body] 376 | (let [old-ns-map (:old ns-map) 377 | cs (candidates type missing body old-ns-map)] 378 | (if-let [[type c] (disambiguate cs type missing {:old-ns-map old-ns-map 379 | :new-ns-map ns-map})] 380 | (case type 381 | :import (update-imports-in ns-map c) 382 | :alias (update-in ns-map [:alias] assoc c missing) 383 | :refer (update-in ns-map [:refer c] #(conj (or % #{}) missing)) 384 | :refer-all (update-in ns-map [:refer-all] #(conj (or % #{}) c)) 385 | :rename (let [renames (get-in old-ns-map [:rename c]) 386 | orig (first (first (filter #(= missing (val %)) renames)))] 387 | (-> ns-map 388 | (update-in [:refer c] #(conj (or % #{}) orig)) 389 | (update-in [:rename c] assoc orig missing)))) 390 | ns-map))) 391 | 392 | (defonce pre-load-namespaces 393 | (delay 394 | (doseq [namespace (search/namespaces) 395 | :when (not (re-find #"example|lancet$" (name namespace)))] 396 | (try (with-out-str (require namespace)) 397 | (catch Throwable _))))) 398 | 399 | (defn- strip-ns-qualified-symbol-fn 400 | "Return a function that de-qualifies a symbol that is qualified 401 | to ns-sym. This is necessary when regrowing from a body that was 402 | processed by the reader without all needed references. See commit 403 | 54cba5c67fe0004378ea4e381ce9df800b510b63 for further elaboration." 404 | [ns-sym] 405 | (let [pat (Pattern/compile (str "\\A\\Q" ns-sym "\\E/(.+)"))] 406 | (fn [expr] 407 | (if (symbol? expr) 408 | (if-let [m (re-find pat (str expr))] 409 | (with-meta (symbol (second m)) (meta expr)) 410 | expr) 411 | expr)))) 412 | 413 | (defn- conj-annotation-keys! 414 | "Conjoin unresolved class symbols in the metadata of all subforms of expr 415 | intended to be used as a Java annotations. 416 | 417 | cf. https://groups.google.com/forum/#!topic/clojure/0hKOFQXAwRc 418 | https://github.com/technomancy/slamhound/issues/72#issuecomment-39041664" 419 | [annotations-ref expr] 420 | ;; Rich Hickey: 421 | ;; It supports annotations for definterface/type/record types (put in 422 | ;; metadata on type name), deftype/record fields (in metadata on field 423 | ;; names), and deftype/record methods (in metadata on method name) 424 | (when (and (list? expr) 425 | (symbol? (first expr)) 426 | (re-find #"\A(?:[^/]+/)?def(?:interface|record|type)\z" 427 | (str (first expr)))) 428 | (prewalk 429 | (fn [form] 430 | (when (symbol? form) 431 | ;; Capitalized, unqualified, single segment symbols only 432 | (let [ks (filter #(and (symbol? %) 433 | (re-find #"\A\p{Lu}[^./]+\z" (str %))) 434 | (keys (meta form)))] 435 | (when (seq ks) 436 | (apply swap! annotations-ref conj ks)))) 437 | form) 438 | expr))) 439 | 440 | (defn- munge-body 441 | "Strip erroneously qualified symbols and possibly concat a sequence of 442 | unknown class symbols." 443 | [body ns-sym] 444 | (let [strip (strip-ns-qualified-symbol-fn ns-sym) 445 | annotations-ref (atom #{}) 446 | body (prewalk #(let [expr (strip %)] 447 | (conj-annotation-keys! annotations-ref expr) 448 | expr) 449 | body)] 450 | (concat body @annotations-ref))) 451 | 452 | (defn regrow [[ns-map body]] 453 | (force pre-load-namespaces) 454 | (if (:slamhound-skip (:meta ns-map)) 455 | (merge ns-map (:old ns-map)) 456 | (let [body (munge-body body (:name ns-map))] 457 | (loop [ns-map ns-map 458 | last-missing nil 459 | type-to-try 0] 460 | (if-let [{:keys [missing possible-types]} (check-for-failure ns-map body)] 461 | (let [type-idx (if (= last-missing missing) 462 | (inc type-to-try) 463 | 0)] 464 | (if-let [type (get possible-types type-idx)] 465 | (recur (grow-ns-map ns-map type missing body) missing type-idx) 466 | (throw (Exception. (str "Couldn't resolve " missing 467 | ", got as far as " ns-map))))) 468 | ns-map))))) 469 | -------------------------------------------------------------------------------- /src/slam/hound/search.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.search 2 | "Search the classpath for vars and classes." 3 | (:require [clojure.java.io :refer [file reader]] 4 | [clojure.string :as string]) 5 | (:import (java.io BufferedReader File FilenameFilter InputStreamReader 6 | PushbackReader) 7 | (java.util StringTokenizer) 8 | (java.util.jar JarEntry JarFile) 9 | (java.util.regex Pattern))) 10 | 11 | ;;; Mostly taken from leiningen.util.ns and swank.util.class-browse. 12 | 13 | ;; TODO: replace with bultitude? but that doesn't do classes 14 | 15 | ;;; Clojure namespaces 16 | 17 | (def classpath-files 18 | (for [f (.split (System/getProperty "java.class.path") 19 | (System/getProperty "path.separator"))] 20 | (file f))) 21 | 22 | (defn clj? [^String path] 23 | (.endsWith path ".clj")) 24 | 25 | (defn jar? [^File f] 26 | (and (.isFile f) (.endsWith (.getName f) ".jar"))) 27 | 28 | (defn class-file? [^String path] 29 | (.endsWith path ".class")) 30 | 31 | (defn clojure-fn-file? [f] 32 | (re-find #"\$.*__\d+\.class" f)) 33 | 34 | (defn clojure-ns-file? [^String path] 35 | (.endsWith path "__init.class")) 36 | 37 | (defn read-ns-form [r f] 38 | (let [form (try (read r false ::done) 39 | (catch Exception _ ::done))] 40 | (if (and (list? form) (= 'ns (first form))) 41 | form 42 | (when-not (= ::done form) 43 | (recur r f))))) 44 | 45 | (defn find-ns-form [^File f] 46 | (when (and (.isFile f) (clj? (.getName f))) 47 | (with-open [rdr (PushbackReader. (reader f))] 48 | (read-ns-form rdr f)))) 49 | 50 | (defn namespaces-in-dir [dir] 51 | (sort (for [f (file-seq (file dir)) 52 | :let [ns-form (find-ns-form f)] 53 | :when ns-form] 54 | (second ns-form)))) 55 | 56 | (defn ns-in-jar-entry [^JarFile jarfile ^JarEntry entry] 57 | (with-open [rdr (-> jarfile 58 | (.getInputStream (.getEntry jarfile (.getName entry))) 59 | InputStreamReader. 60 | BufferedReader. 61 | PushbackReader.)] 62 | (read-ns-form rdr jarfile))) 63 | 64 | (defn namespaces-in-jar [^File jar] 65 | (with-open [jarfile (JarFile. jar)] 66 | (doall 67 | (for [^JarEntry entry (enumeration-seq (.entries jarfile)) 68 | :when (and (not (.isDirectory entry)) 69 | (clj? (.getName entry)))] 70 | (when-let [ns-form (ns-in-jar-entry jarfile entry)] 71 | (second ns-form)))))) 72 | 73 | (defn- filter-ns [file-pred ns-pred paths] 74 | (-> (mapcat ns-pred (filter file-pred paths)) 75 | set 76 | (disj nil))) 77 | 78 | (defn namespaces-from-files 79 | ([] (namespaces-from-files classpath-files)) 80 | ([files] (filter-ns (fn [^File f] (.isDirectory f)) namespaces-in-dir files))) 81 | 82 | (defn namespaces-from-jars 83 | ([] (namespaces-from-jars classpath-files)) 84 | ([files] (filter-ns jar? namespaces-in-jar files))) 85 | 86 | (defn namespaces 87 | ([] (namespaces classpath-files)) 88 | ([files] (into (namespaces-from-files files) (namespaces-from-jars files)))) 89 | 90 | ;;; Java classes 91 | 92 | ;; could probably be simplified 93 | 94 | (defn expand-wildcard 95 | "Expands a wildcard path entry to its matching .jar files (JDK 1.6+). 96 | If not expanding, returns the path entry as a single-element vector." 97 | [^String path] 98 | (let [f (File. path)] 99 | (if (= (.getName f) "*") 100 | (-> f .getParentFile 101 | (.list (proxy [FilenameFilter] [] 102 | (accept [d n] (jar? (file n)))))) 103 | [f]))) 104 | 105 | (defn class-or-ns-name 106 | "Returns the Java class or Clojure namespace name for a class relative path." 107 | [^String path] 108 | (-> (if (clojure-ns-file? path) 109 | (-> path (.replace "__init.class" "") (.replace "_" "-")) 110 | (.replace path ".class" "")) 111 | (.replace File/separator "."))) 112 | 113 | (def path-class-files nil) 114 | (defmulti path-class-files 115 | "Returns a list of classes found on the specified path location 116 | (jar or directory), each comprised of a map with the following keys: 117 | :name Java class or Clojure namespace name 118 | :loc Classpath entry (directory or jar) on which the class is located 119 | :file Path of the class file, relative to :loc" 120 | (fn [^File f _] 121 | (cond (.isDirectory f) :dir 122 | (jar? f) :jar 123 | (class-file? (.getName f)) :class))) 124 | 125 | (defmethod path-class-files :default 126 | [& _] []) 127 | 128 | (defmethod path-class-files :jar 129 | ;; Build class info for all jar entry class files. 130 | [^File f ^File loc] 131 | (with-open [jarfile (JarFile. f)] 132 | (try 133 | (doall 134 | (for [^JarEntry entry (enumeration-seq (.entries jarfile)) 135 | :let [path (.getName entry)] 136 | :when (class-file? path)] 137 | (class-or-ns-name path))) 138 | ;; Fail gracefully if jar is unreadable 139 | (catch Exception _ [])))) 140 | 141 | (defmethod path-class-files :dir 142 | ;; Dispatch directories and files (excluding jars) recursively. 143 | [^File d ^File loc] 144 | (let [fs (.listFiles d (proxy [FilenameFilter] [] 145 | (accept [d n] (not (jar? (file n))))))] 146 | (reduce concat (for [f fs] (path-class-files f loc))))) 147 | 148 | (defmethod path-class-files :class 149 | ;; Build class info using file path relative to parent classpath entry 150 | ;; location. Make sure it decends; a class can't be on classpath directly. 151 | [^File f ^File loc] 152 | (let [fp (str f), lp (str loc) 153 | loc-pattern (re-pattern (str "^" (Pattern/quote lp)))] 154 | (if (re-find loc-pattern fp) ; must be descendent of loc 155 | (let [fpr (.substring fp (inc (count lp)))] 156 | [(class-or-ns-name fpr)]) 157 | []))) 158 | 159 | (defn scan-paths 160 | "Takes one or more classpath strings, scans each classpath entry location, and 161 | returns a list of all class file paths found, each relative to its parent 162 | directory or jar on the classpath." 163 | ([cp] 164 | (if cp 165 | (let [entries (enumeration-seq 166 | (StringTokenizer. cp File/pathSeparator)) 167 | locs (mapcat expand-wildcard entries)] 168 | (reduce concat (for [loc locs] 169 | (path-class-files loc loc)))) 170 | ())) 171 | ([cp & more] 172 | (reduce #(concat %1 (scan-paths %2)) (scan-paths cp) more))) 173 | 174 | (def available-classes 175 | (->> (scan-paths (System/getProperty "sun.boot.class.path") 176 | (System/getProperty "java.ext.dirs") 177 | (System/getProperty "java.class.path")) 178 | (remove clojure-fn-file?) 179 | (map symbol))) 180 | 181 | (def available-classes-by-last-segment 182 | (delay 183 | (group-by #(symbol (peek (string/split (str %) #"\."))) available-classes))) 184 | -------------------------------------------------------------------------------- /src/slam/hound/stitch.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.stitch 2 | (:require [clojure.set :as set] 3 | [slam.hound.future :refer [cond->*]] 4 | [slam.hound.prettify :refer [prettify]])) 5 | 6 | (defn get-package [^Class cls] 7 | (if-let [pkg (.getPackage cls)] 8 | (.getName pkg) 9 | ;; Fall back to string matching for dynamically generated classes 10 | (second (re-find #"(.*)\." (.getCanonicalName cls))))) 11 | 12 | (defn- group-by-package [imports] 13 | (for [[package classes] (group-by get-package (map resolve imports))] 14 | (cons (symbol package) 15 | (sort (for [^Class c classes] 16 | (-> c .getName (.split "\\.") last symbol)))))) 17 | 18 | (defn metadata-from-map 19 | "Returns a vector of: [docstring? meta-map?]" 20 | [ns-map] 21 | (let [meta (:meta ns-map) 22 | doc (:doc meta) 23 | meta (into (sorted-map) (dissoc meta :doc))] 24 | (cond->* [] 25 | doc (conj doc) 26 | (seq meta) (conj meta)))) 27 | 28 | (defn keyword-list-from-map 29 | "Returns a cons list of keyword and the value of the keyword in ns-map" 30 | [kw ns-map] 31 | (when-let [s (ns-map kw)] 32 | (cons kw s))) 33 | 34 | (defn imports-from-map 35 | "Returns an :import form from an ns-map with {:import #{class-syms}}" 36 | [ns-map] 37 | (let [imports (:import ns-map)] 38 | (when (seq imports) 39 | (cons :import (sort-by str (group-by-package imports)))))) 40 | 41 | (defn- ns-requires [ns-sym ns-map] 42 | (if (= ns-sym 'clojure.core) 43 | ;; clojure.core is only valid in a :require clause as an alias 44 | (let [{:keys [alias]} ns-map] 45 | (cond->* [ns-sym] 46 | (get alias ns-sym) (conj :as (alias ns-sym)))) 47 | (let [{:keys [alias refer refer-all exclude rename]} ns-map 48 | refer (if (get refer-all ns-sym) #{} refer)] 49 | (cond->* [ns-sym] 50 | (get alias ns-sym) (conj :as (alias ns-sym)) 51 | (get refer ns-sym) (conj :refer (vec (sort (refer ns-sym)))) 52 | (get refer-all ns-sym) (conj :refer :all) 53 | (get exclude ns-sym) (conj :exclude (vec (sort (exclude ns-sym)))) 54 | (get rename ns-sym) (conj :rename (into (sorted-map) (rename ns-sym))))))) 55 | 56 | (defn- group-by-require-flags 57 | "Returns map of {#{require-flag} #{ns-sym}}" 58 | [ns-syms ns-map] 59 | (let [{:keys [reload reload-all verbose]} ns-map 60 | rs {:reload (set/intersection ns-syms reload) 61 | :reload-all (set/intersection ns-syms reload-all) 62 | :verbose (set/intersection ns-syms verbose) 63 | :none (set/difference ns-syms reload reload-all verbose)} 64 | ;; Invert the map of {require-flag #{ns-sym}} such that the set 65 | ;; members are the new keys and the old keys are grouped in a set. 66 | rs (reduce-kv 67 | (fn [m k s] 68 | (reduce (fn [m v] (assoc m v (conj (get m v #{}) k))) m s)) 69 | {} rs)] 70 | ;; Invert the map of {ns-sym #{require-flag}}, returning a map of 71 | ;; {#{require-flag} #{ns-sym}} 72 | (reduce-kv (fn [m k v] 73 | (let [v (disj v :none)] 74 | (assoc m v (conj (get m v #{}) k)))) 75 | {} rs))) 76 | 77 | (defn requires-from-map 78 | "Returns a vector of :require forms from an ns-map with: 79 | {:require #{ns-syms} 80 | :alias {ns-sym ns-sym} 81 | :refer {ns-sym #{var-syms}} 82 | :refer-all #{ns-sym} 83 | :exclude {ns-sym #{var-syms}} 84 | :rename {ns-sym {var-sym var-sym}} 85 | :reload #{ns-sym} 86 | :reload-all #{ns-sym} 87 | :verbose #{ns-sym}}" 88 | [ns-map] 89 | (let [{:keys [require alias refer refer-all exclude rename]} ns-map 90 | ;; Build the set of namespaces that will be required 91 | nss (reduce into #{} [require refer-all 92 | (mapcat keys [refer exclude rename])]) 93 | ;; Refers from clojure.core are handled via :refer-clojure 94 | nss (disj nss 'clojure.core) 95 | ;; However, aliasing clojure.core in a :require is fine 96 | nss (into nss (keys alias))] 97 | (when (seq nss) 98 | (let [flags->nss (->> (group-by-require-flags nss ns-map) 99 | (sort-by (comp count key)))] 100 | (mapv (fn [[flags nss]] 101 | (let [reqs (map #(ns-requires % ns-map) nss) 102 | clause (cons :require (sort-by str reqs))] 103 | (concat clause (sort flags)))) 104 | flags->nss))))) 105 | 106 | (defn refer-clojure-from-map 107 | "Return a :refer-clojure form from an ns-map with: 108 | {:refer {'clojure.core #{var-syms}} 109 | :refer-all #{'clojure.core} 110 | :exclude {'clojure.core #{var-syms}} 111 | :rename {'clojure.core {var-sym var-sym}}}" 112 | [ns-map] 113 | (let [{:keys [refer refer-all exclude rename]} ns-map 114 | c 'clojure.core] 115 | (when (some #{c} (concat refer-all (mapcat keys [refer exclude rename]))) 116 | (let [rs (if (and (not (contains? refer-all c)) 117 | (contains? refer c)) 118 | [:only (vec (sort (refer c)))] 119 | []) 120 | rs (cond->* rs 121 | (get exclude c) (conj :exclude (vec (sort (exclude c)))) 122 | (get rename c) (conj :rename (into (sorted-map) (rename c))))] 123 | (when (seq rs) 124 | (cons :refer-clojure rs)))))) 125 | 126 | (defn ns-from-map 127 | "Generate an ns-form from an ns-map in the form of 128 | #'slam.hound.asplode/empty-ns-references" 129 | [ns-map] 130 | `(~'ns ~(:name ns-map) 131 | ~@(metadata-from-map ns-map) 132 | ~@(requires-from-map ns-map) 133 | ~@(filter identity [(imports-from-map ns-map) 134 | (refer-clojure-from-map ns-map) 135 | (keyword-list-from-map :gen-class ns-map) 136 | (keyword-list-from-map :load ns-map)]))) 137 | 138 | (defn stitch-up [ns-map] 139 | (-> ns-map 140 | ns-from-map 141 | prettify)) 142 | -------------------------------------------------------------------------------- /src/swank/payload/slamhound.el: -------------------------------------------------------------------------------- 1 | ../../../slamhound.el -------------------------------------------------------------------------------- /test/slam/hound/asplode_test.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.asplode-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [slam.hound.asplode :refer [asplode expand-imports expand-libspecs 4 | ns-to-map parse-libs parse-ns-map 5 | parse-refers parse-requires parse-uses 6 | preserve-ns-references]]) 7 | (:import (java.io StringReader))) 8 | 9 | (deftest ^:unit test-expand-imports 10 | (is (= (expand-imports '((my.prefix Foo Bar Baz) 11 | (empty.prefix.list) 12 | my.single.ClassSymbol)) 13 | '#{my.prefix.Foo 14 | my.prefix.Bar 15 | my.prefix.Baz 16 | my.single.ClassSymbol}))) 17 | 18 | (deftest ^:unit test-expand-libspecs 19 | (is (= (expand-libspecs '[[my.ns.foo :as f :verbose true] 20 | [my.ns [bar :as b] [baz :as z :verbose true]] 21 | my.ns.quux 22 | :reload-all]) 23 | '#{[my.ns.foo :as f :verbose true :reload-all true] 24 | [my.ns.bar :as b :reload-all true] 25 | [my.ns.baz :as z :verbose true :reload-all true] 26 | [my.ns.quux :reload-all true]}))) 27 | 28 | (deftest ^:unit test-parse-refers 29 | (is (= (parse-refers 'my.ns '[:only [foo] :exclude [bar] :rename {baz mybaz}]) 30 | '{:refer {my.ns #{foo}} 31 | :exclude {my.ns #{bar}} 32 | :rename {my.ns {baz mybaz}}})) 33 | (is (= (parse-refers 'my.ns []) 34 | '{:refer-all #{my.ns}})) 35 | (testing "exclusive-refer?" 36 | (is (= (parse-refers 'my.ns '[:only [foo]] :exclusive true) 37 | '{:refer {my.ns #{foo}} :xrefer #{my.ns}})))) 38 | 39 | (deftest ^:unit test-parse-requires 40 | (is (= (parse-requires '[[my.ns.foo :as foo :refer [foo]] 41 | [my.ns [bar :as bar] [baz :refer :all]]]) 42 | '{:alias {my.ns.foo foo 43 | my.ns.bar bar} 44 | :refer {my.ns.foo #{foo}} 45 | :refer-all #{my.ns.baz}})) 46 | (is (= (parse-requires '[my.ns.foo [my.ns [bar] [baz]]]) 47 | '{:require #{my.ns.foo my.ns.bar my.ns.baz}})) 48 | (is (= (parse-requires '[[my.ns.foo :as foo] 49 | [my.ns.bar :refer [bar]] 50 | :verbose :reload-all]) 51 | '{:alias {my.ns.foo foo} 52 | :refer {my.ns.bar #{bar}} 53 | :verbose #{my.ns.foo my.ns.bar} 54 | :reload-all #{my.ns.foo my.ns.bar}})) 55 | (is (= (parse-requires '[[clojure.set :refer [union] :rename {union ∪}]]) 56 | '{:refer {clojure.set #{union}} 57 | :rename {clojure.set {union ∪}}}))) 58 | 59 | (deftest ^:unit test-parse-uses 60 | (is (= (parse-uses '[my.ns.base 61 | [my.ns [foo :exclude [foo]] [bar :only [bar]]] 62 | [my.ns.baz :as baz :only [baz]] 63 | [my.ns.quux]]) 64 | '{:refer {my.ns.bar #{bar} 65 | my.ns.baz #{baz}} 66 | :refer-all #{my.ns.base my.ns.quux} 67 | :alias {my.ns.baz baz} 68 | :exclude {my.ns.foo #{foo}}}))) 69 | 70 | (deftest ^:unit test-parse-libs 71 | (testing "refer-clojure" 72 | (is (= (parse-libs {:exclude '{foo #{foo}}} 73 | :refer-clojure 74 | '[:exclude [defn defrecord]]) 75 | '{:exclude {foo #{foo} 76 | clojure.core #{defn defrecord}}})) 77 | (is (= (parse-libs {} :refer-clojure '[:only [defn]]) 78 | '{:refer {clojure.core #{defn}} 79 | :xrefer #{clojure.core}}))) 80 | (testing "load, gen-class" 81 | (is (= (parse-libs {:load ["/foo"]} :load ["/bar" "/baz"]) 82 | {:load ["/bar" "/baz"]})) 83 | (is (= (parse-libs {:gen-class [:init 'foo]} :gen-class [:name 'bar]) 84 | {:gen-class [:name 'bar]})))) 85 | 86 | (deftest ^:unit test-ns-to-map 87 | (testing "recognizes maps as metadata" 88 | (is (= (:meta (ns-to-map '(ns my.ns {:foo "foo"}))) 89 | {:foo "foo"})) 90 | (is (= (:meta (ns-to-map '(ns my.ns "With docstring" {:bar "bar"}))) 91 | {:bar "bar" :doc "With docstring"}))) 92 | (testing "parses empty :gen-class" 93 | (is (= (ns-to-map '(ns my.ns (:gen-class))) 94 | '{:name my.ns :meta nil :gen-class []})) 95 | (is (= (ns-to-map '(ns my.ns (:gen-class :name my.ns.Foo))) 96 | '{:name my.ns :meta nil :gen-class [:name my.ns.Foo]}))) 97 | (testing "parses multiple clauses of the same type" 98 | (is (= (ns-to-map '(ns my.ns 99 | (:require [foo :as f]) 100 | (:require [bar :as b] :reload))) 101 | '{:name my.ns :meta nil 102 | :require [([foo :as f]) ([bar :as b] :reload)]}))) 103 | (testing "keywordizes clause keys" 104 | (is (= (ns-to-map '(ns my.ns 105 | (require [foo :as f]) 106 | (import foo.Baz))) 107 | '{:name my.ns 108 | :meta nil 109 | :require [([foo :as f])] 110 | :import [foo.Baz]})))) 111 | 112 | (deftest ^:unit test-parse-ns-map 113 | (is (= (parse-ns-map '{:require [([foo :as f :refer [r]] :reload) 114 | ([bar :as b] baz [qux])] 115 | :use [[foo :only [u]]] 116 | :import [(my.ns A B C) my.ns.D] 117 | :refer-clojure [:only [defn]] 118 | :load ["/a" "/b" "/c"] 119 | :gen-class []}) 120 | '{:refer {foo #{r u} clojure.core #{defn}} 121 | :alias {foo f bar b} 122 | :require #{baz qux} 123 | :import #{my.ns.A my.ns.B my.ns.C my.ns.D} 124 | :xrefer #{clojure.core} 125 | :load ["/a" "/b" "/c"] 126 | :gen-class [] 127 | :exclude {} 128 | :rename {} 129 | :refer-all #{} 130 | :reload-all #{} 131 | :verbose #{} 132 | :reload #{foo}}))) 133 | 134 | (deftest ^:unit test-preserve-ns-references 135 | (testing "retains :gen-class and :load" 136 | (is (= (preserve-ns-references '{:gen-class [:foo foo :bar bar] 137 | :load ["foo" "bar"] 138 | :refer {my.ns #{foo}}}) 139 | '{:gen-class [:foo foo :bar bar] 140 | :load ["foo" "bar"]})) 141 | (is (= (preserve-ns-references {:gen-class [] :load []}) 142 | {:gen-class [] :load []})) 143 | (is (= (preserve-ns-references {:gen-class nil :load []}) 144 | {:load []}))) 145 | (testing "retains :reload, :reload-all, and :verbose" 146 | (is (= (preserve-ns-references '{:reload #{my.ns}}) 147 | '{:reload #{my.ns}})) 148 | (is (= (preserve-ns-references '{:reload-all #{my.ns} :verbose #{my.ns}}) 149 | '{:reload-all #{my.ns} :verbose #{my.ns}}))) 150 | (testing "retains refers, exclusions, and renames for clojure.core" 151 | (is (= (preserve-ns-references '{:exclude {clojure.core #{==} 152 | my.ns #{foo}} 153 | :rename {clojure.core {/ div} 154 | my.ns {bar -bar}} 155 | :refer {clojure.core #{== + - * /} 156 | my.ns #{baz}}}) 157 | '{:exclude {clojure.core #{==}} 158 | :rename {clojure.core {/ div}} 159 | :refer {clojure.core #{== + - * /}}})))) 160 | 161 | (deftest ^:unit test-asplode 162 | (is (= (asplode (StringReader. 163 | (str '(ns slamhound.sample 164 | "Testing some things going on here." 165 | (:use [slam.hound.stitch :only [ns-from-map]] 166 | [clojure.test :only [is]] 167 | [clojure.test :only [deftest]]) 168 | (:require [clojure.java.io :as io] 169 | [clojure.set 170 | :refer :all 171 | :rename {union ∪ intersection ∩}]) 172 | (:import java.io.File java.io.ByteArrayInputStream 173 | clojure.lang.Compiler$BodyExpr 174 | java.util.UUID) 175 | (:refer-clojure :exclude [compile test]) 176 | (:gen-class)) 177 | '(do something)))) 178 | '[{:old {:import #{java.io.File 179 | java.io.ByteArrayInputStream 180 | java.util.UUID 181 | clojure.lang.Compiler$BodyExpr} 182 | :require #{} 183 | :alias {clojure.java.io io} 184 | :refer {clojure.test #{deftest is} 185 | slam.hound.stitch #{ns-from-map}} 186 | :xrefer #{} 187 | :refer-all #{clojure.set} 188 | :exclude {clojure.core #{test compile}} 189 | :rename {clojure.set {union ∪ intersection ∩}} 190 | :reload #{} 191 | :reload-all #{} 192 | :verbose #{} 193 | :load nil 194 | :gen-class []} 195 | :gen-class [] 196 | :exclude {clojure.core #{test compile}} 197 | :meta {:doc "Testing some things going on here."} 198 | :name slamhound.sample} 199 | ((do something))])) 200 | (testing "output is constant regardless of *ns*" 201 | (let [buf "(ns slamhound.sample) (eval `foo)"] 202 | (is (= (asplode (StringReader. buf)) 203 | (binding [*ns* (find-ns 'user)] 204 | (asplode (StringReader. buf)))))))) 205 | -------------------------------------------------------------------------------- /test/slam/hound/prettify_test.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.prettify-test 2 | (:require [clojure.java.io :as io] 3 | [clojure.pprint :refer [*print-right-margin*]] 4 | [clojure.test :refer [deftest is testing]] 5 | [slam.hound.prettify :as p])) 6 | 7 | (defn prettify [form] 8 | (binding [*print-right-margin* 72] 9 | (p/prettify form))) 10 | 11 | (deftest ^:unit test-prettify 12 | (testing "always inserts newlines inside short requires" 13 | (is (= (slurp (io/resource "test-prettify-short-requires.out")) 14 | (prettify 15 | '(ns foo 16 | (:require [clojure.java.io :as io] 17 | [clojure.pprint :refer [pprint]])))))) 18 | (testing "does not print :refer vectors in :miser mode" 19 | (is (= (slurp (io/resource "test-prettify-no-miserly-refers.out")) 20 | (prettify 21 | '(ns foo 22 | (:require [my.very.sequipedalian.namespace 23 | :refer [alpha beta gamma delta epsilon]])))))) 24 | (testing "keeps multiple libspec option keys and values together" 25 | (is (= (slurp (io/resource "test-prettify-libspecs.out")) 26 | (prettify 27 | '(ns foo 28 | (:require [clojure.pprint 29 | :as pp 30 | :refer [*print-miser-width* cl-format code-dispatch 31 | formatter-out pprint pprint-logical-block 32 | pprint-newline with-pprint-dispatch 33 | write-out]]))))) 34 | (is (= (slurp (io/resource "test-prettify-long-libspecs.out")) 35 | (prettify 36 | '(ns foo 37 | (:require [clojure.pprint 38 | :as pp 39 | :refer [formatter-out pprint-logical-block] 40 | :rename {formatter-out fout 41 | pprint-logical-block block}]))))))) 42 | -------------------------------------------------------------------------------- /test/slam/hound/regrow_test.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.regrow-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [slam.hound.regrow :refer [disambiguate grow-ns-map regrow]]) 4 | (:refer-clojure :exclude [/])) 5 | 6 | ;; Classes and vars for testing 7 | (defrecord RegrowTestRecord []) 8 | (deftype TreeSet []) 9 | (definterface HashSet) 10 | (def +i-must-be-a-cl-user+ true) 11 | (def -+_$?!*><='' :horribly-named-var) 12 | (def / :special-case-token) 13 | (def CapitalVar true) 14 | (def Pattern "Not java.util.Pattern") 15 | (def trim (constantly "Conflicts with clojure.string/trim")) 16 | (def τ (* Math/PI 2)) 17 | (def ∩ :intersection) 18 | (def ★ :star) 19 | (def ^:private private-var "Can be resolved with #'") 20 | 21 | ;; Explicitly require korma for tests below 22 | (require 'korma.core) 23 | 24 | (def candidates #'slam.hound.regrow/candidates) 25 | 26 | (deftest ^:unit test-candidates 27 | (testing "finds static and dynamically created Java packages" 28 | (is (= (candidates :import 'TreeSet '(TreeSet) {}) 29 | '#{java.util.TreeSet slam.hound.regrow_test.TreeSet})) 30 | (is (= (candidates :import 'Compiler$BodyExpr '(Compiler$BodyExpr) {}) 31 | '#{clojure.lang.Compiler$BodyExpr})) 32 | (is (= (candidates :import 'RegrowTestRecord '((RegrowTestRecord.)) {}) 33 | '#{slam.hound.regrow_test.RegrowTestRecord}))) 34 | (testing "finds aliased namespaces" 35 | (is (= (candidates :alias 's '((s/join #{:a} #{:b})) {}) 36 | '#{clojure.set clojure.string korma.core})) 37 | (is (= (candidates :alias 'r '((def foo r/trim) 38 | (def bar #'r/private-var)) {}) 39 | (candidates :alias 'r '((def foo #'r/trim)) {}) 40 | '#{clojure.string slam.hound.regrow-test}))) 41 | (testing "finds referred vars" 42 | (is (= (candidates :refer 'join '((join #{:a} #{:b})) {}) 43 | '#{clojure.set clojure.string korma.core}))) 44 | (testing "finds renamed vars from old ns" 45 | (is (= (candidates :rename '∩ '((∩ #{:a} #{:b})) 46 | '{:rename {clojure.set {intersection ∩} 47 | clojure.string {join j}}}) 48 | '#{clojure.set})))) 49 | 50 | (deftest test-alias-distance 51 | (let [d #'slam.hound.regrow/alias-distance 52 | max? (partial = Long/MAX_VALUE)] 53 | (is (max? (d "zbc" "abcdef"))) 54 | (is (max? (d "azc" "abcdef"))) 55 | (is (max? (d "abz" "abcdef"))) 56 | (is (max? (d "abcd" "abc"))) 57 | (is (= (d "a" "abcdef") 0)) 58 | (is (= (d "abc" "abcdef") 0)) 59 | (is (= (d "ace" "abcdef") 2)) 60 | (is (= (d "fbb" "foo-bar-baz") 6)))) 61 | 62 | (deftest ^:unit test-disambiguate 63 | (testing "removes candidate's own namespace" 64 | (is (= (disambiguate '#{foo bar} :alias 'foo '{:new-ns-map {:name foo}}) 65 | '[:alias bar]))) 66 | (testing "removes namespaces beginning with cljs from candidates" 67 | (is (nil? (disambiguate '#{cljs.core} :refer 'defn {}))) 68 | (is (nil? (disambiguate '#{cljs.repl} :refer 'load-file {})))) 69 | (testing "removes candidates matching disambiguator-blacklist" 70 | (is (nil? (disambiguate '#{swank lancet} :alias 'swank {})))) 71 | (testing "removes namespaces with excluded vars" 72 | (is (nil? (disambiguate '#{clojure.string clojure.set} 73 | :refer 'join 74 | '{:old-ns-map 75 | {:exclude {clojure.string #{join} 76 | clojure.set #{join}}}}))) 77 | (is (nil? (disambiguate '#{clojure.core core.logic} :refer '== 78 | '{:old-ns-map 79 | {:xrefer #{clojure.core core.logic} 80 | :refer {clojure.core #{} core.logic #{}}}})))) 81 | (testing "enforces one alias per namespace" 82 | (is (nil? (disambiguate '#{clojure.string} 83 | :alias 's 84 | '{:new-ns-map {:alias {clojure.string string}}})))) 85 | (testing "prefers imports from old ns" 86 | (is (= (disambiguate '#{java.util.TreeSet slam.hound.regrow_test.TreeSet} 87 | :import 'TreeSet 88 | '{:old-ns-map 89 | {:import #{java.util.TreeSet}}}) 90 | '[:import java.util.TreeSet]))) 91 | (testing "prefers aliases from old ns" 92 | (is (= (disambiguate '#{a zzz} :alias 'x 93 | '{:old-ns-map {:alias {zzz x}}}) 94 | '[:alias zzz]))) 95 | (testing "prefers refers from old ns" 96 | (is (= (disambiguate '#{a zzz} :refer 'x 97 | '{:old-ns-map {:refer {zzz #{x}}}}) 98 | '[:refer zzz]))) 99 | (testing "prefers explicit refers over mass refers from old ns" 100 | (is (= (disambiguate '#{clojure.set clojure.string} 101 | :refer 'join 102 | '{:old-ns-map 103 | {:refer-all #{clojure.set} 104 | :refer {clojure.string #{join}}}}) 105 | '[:refer clojure.string]))) 106 | (testing "prefers aliases where the last segment matches" 107 | (is (= (disambiguate '#{clojure.set clojure.string} :alias 'string {}) 108 | '[:alias clojure.string]))) 109 | (testing "prefers candidates in project namespaces" 110 | (is (= (disambiguate '#{clojure.string slam.hound.regrow-test} 111 | :refer 'trim {}) 112 | '[:refer slam.hound.regrow-test])) 113 | (is (= (disambiguate '#{java.util.TreeSet slam.hound.regrow_test.TreeSet} 114 | :import 'TreeSet {}) 115 | '[:import slam.hound.regrow_test.TreeSet]))) 116 | (testing "prefers candidates whose initials match the alias" 117 | (is (= (disambiguate '#{xray.yankee.zulu abc} :alias 'xyz {}) 118 | '[:alias xray.yankee.zulu]))) 119 | (testing "prefers candidates with the shortest alias-distance" 120 | (is (= (disambiguate '#{clojure.string clojure.core} :alias 's {}) 121 | '[:alias clojure.string])) 122 | (is (= (disambiguate '#{clojure.string clojure.set} :alias 'st {}) 123 | '[:alias clojure.string]))) 124 | (testing "prefers shortest candidates when no other predicates match" 125 | (is (= (disambiguate '#{clojure.java.io clojure.set clojure.string} 126 | :alias 'a {}) 127 | '[:alias clojure.set]))) 128 | (testing "changes type to :refer-all when top candidate is in old :refer-all" 129 | (is (= (disambiguate '#{clojure.set clojure.string} 130 | :refer 'join 131 | '{:old-ns-map {:refer-all #{clojure.set}}}) 132 | '[:refer-all clojure.set])))) 133 | 134 | (deftest ^:unit test-grow-ns-map 135 | (testing "finds basic imports, aliases, and refers" 136 | (is (= (grow-ns-map {} :import 'RegrowTestRecord '((RegrowTestRecord.))) 137 | '{:import #{slam.hound.regrow_test.RegrowTestRecord} 138 | :require #{slam.hound.regrow-test}})) 139 | (is (= (grow-ns-map {} :import 'HashSet '(HashSet)) 140 | '{:import #{slam.hound.regrow_test.HashSet} 141 | :require #{slam.hound.regrow-test}})) 142 | (is (= (grow-ns-map {} :alias 'string '((string/join))) 143 | '{:alias {clojure.string string}})) 144 | (is (= (grow-ns-map {} :refer 'pprint '((pprint []))) 145 | '{:refer {clojure.pprint #{pprint}}}))) 146 | (testing "finds imports + requires for underscored namespaces" 147 | (binding [*ns* (create-ns `slam.hound.underscored_ns#)] 148 | (try 149 | (eval `(defrecord ~'Underscored_Record [])) 150 | (is (= (grow-ns-map {} :import 'Underscored_Record 151 | '((Underscored_Record.))) 152 | {:import #{(symbol (str *ns* ".Underscored_Record"))} 153 | :require #{(.name *ns*)}})) 154 | (finally 155 | (remove-ns (.name *ns*)))))) 156 | (testing "honors old :refer :all" 157 | (is (= (grow-ns-map '{:old {:refer-all #{clojure.pprint}}} 158 | :refer 'pprint '((pprint []))) 159 | '{:old {:refer-all #{clojure.pprint}} 160 | :refer-all #{clojure.pprint}}))) 161 | (testing "finds capitalized vars" 162 | (is (= (grow-ns-map '{} :refer 'CapitalVar '((type CapitalVar))) 163 | '{:refer {slam.hound.regrow-test #{CapitalVar}}}))) 164 | (testing "find renamed vars" 165 | (is (= (grow-ns-map '{:old {:rename {clojure.string {trim t} 166 | slam.hound.regrow-test {trim t}}}} 167 | :rename 't '((def trim-fn t))) 168 | '{:refer {slam.hound.regrow-test #{trim}} 169 | :rename {slam.hound.regrow-test {trim t}} 170 | :old {:rename {clojure.string {trim t} 171 | slam.hound.regrow-test {trim t}}}})))) 172 | 173 | (deftest ^:unit test-regrow 174 | (testing "prefers capitalized vars referred in old ns to classes" 175 | (is (= (regrow '[{:old {:refer {slam.hound.regrow-test #{Pattern}}}} 176 | ((def p Pattern))]) 177 | '{:old {:refer {slam.hound.regrow-test #{Pattern}}} 178 | :refer {slam.hound.regrow-test #{Pattern}}})) 179 | (is (= (regrow '[{:old {:refer {my.ns #{BitSet}}}} 180 | ((def bs BitSet))]) 181 | '{:old {:refer {my.ns #{BitSet}}} 182 | :import #{java.util.BitSet}}))) 183 | (testing "finds referred vars with strange names" 184 | (is (= (regrow '[{} ((assert +i-must-be-a-cl-user+))]) 185 | '{:refer {slam.hound.regrow-test #{+i-must-be-a-cl-user+}}})) 186 | (is (= (regrow '[{} ((keyword? -+_$?!*><=''))]) 187 | '{:refer {slam.hound.regrow-test #{-+_$?!*><=''}}})) 188 | (is (= (regrow '[{:old {:exclude {clojure.core #{/}}} 189 | :exclude {clojure.core #{/}}} 190 | ((keyword? /))]) 191 | '{:old {:exclude {clojure.core #{/}}} 192 | :exclude {clojure.core #{/}} 193 | :refer {slam.hound.regrow-test #{/}}})) 194 | (is (= (regrow '[{} ((def unicode-syms [τ ∩ ★]))]) 195 | '{:refer {slam.hound.regrow-test #{τ ∩ ★}}}))) 196 | (testing "finds consumed references within syntax-quotes" 197 | (is (= (regrow '[{:name slam.hound.regrow-test} 198 | ((eval `(instance? Named :foo)))]) 199 | '{:name slam.hound.regrow-test 200 | :import #{clojure.lang.Named}}))) 201 | (testing "finds aliases that conflict with namespaces" 202 | (is (= (regrow '[{:name slam.hound.regrow-test} 203 | ((def foo user/union))]) 204 | '{:name slam.hound.regrow-test 205 | :alias {clojure.set user}})) 206 | ;; Please never do this 207 | (is (= (regrow '[{:name slam.hound.regrow-test} 208 | ((def foo clojure.string/union))]) 209 | '{:name slam.hound.regrow-test 210 | :alias {clojure.set clojure.string}}))) 211 | (testing "finds referred vars with dashed ns aliases" 212 | (is (= (regrow '[{:name slam.hound.regrow-test} 213 | ((with-redefs [c-s/union (constantly [])] c-s/union))]) 214 | '{:name slam.hound.regrow-test 215 | :alias {clojure.set c-s}})))) 216 | -------------------------------------------------------------------------------- /test/slam/hound/search_test.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.search-test 2 | (:require [clojure.java.io :as io] 3 | [clojure.test :refer [deftest is]] 4 | [slam.hound.search :refer [classpath-files 5 | namespaces 6 | namespaces-from-files 7 | namespaces-from-jars]]) 8 | (:import (java.io File))) 9 | 10 | (def slamhound-namespaces 11 | '#{slam.hound 12 | slam.hound.asplode 13 | slam.hound.future 14 | slam.hound.prettify 15 | slam.hound.regrow 16 | slam.hound.search 17 | slam.hound.stitch}) 18 | 19 | (def korma-namespaces 20 | '#{korma.config 21 | korma.core 22 | korma.db 23 | korma.sql.engine 24 | korma.sql.fns 25 | korma.sql.utils}) 26 | 27 | (def korma-jar 28 | (first (filter (fn [^File f] (re-find #"\Akorma-.+\.jar\z" (.getName f))) 29 | classpath-files))) 30 | 31 | (deftest ^:unit test-namespaces-from-files 32 | (is (= (namespaces-from-files (file-seq (io/file "src"))) 33 | slamhound-namespaces))) 34 | 35 | (deftest ^:unit test-namespaces-from-jars 36 | (is (= (namespaces-from-jars [korma-jar]) 37 | korma-namespaces))) 38 | 39 | (deftest ^:unit test-namespaces 40 | (is (= (namespaces (cons korma-jar (file-seq (io/file "src")))) 41 | (into korma-namespaces slamhound-namespaces)))) 42 | -------------------------------------------------------------------------------- /test/slam/hound/stitch_test.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.stitch-test 2 | (:require [clojure.java.io :as io] 3 | [clojure.test :refer [deftest is testing]] 4 | [slam.hound.stitch :refer [imports-from-map keyword-list-from-map 5 | metadata-from-map ns-from-map 6 | refer-clojure-from-map requires-from-map 7 | stitch-up]])) 8 | 9 | (deftest ^:unit test-keyword-list-from-map 10 | (is (= (keyword-list-from-map 11 | :gen-class '{:gen-class [:name Foo :extends Bar]}) 12 | '(:gen-class :name Foo :extends Bar))) 13 | (is (= (keyword-list-from-map :foo '{:foo []}) '(:foo))) 14 | (is (nil? (keyword-list-from-map :foo {:foo nil})))) 15 | 16 | (deftest ^:unit test-metadata-from-map 17 | (is (= (metadata-from-map {}) [])) 18 | (is (= (metadata-from-map {:meta {:doc "foo"}}) ["foo"])) 19 | (is (= (metadata-from-map {:meta {:bar "bar"}}) [{:bar "bar"}])) 20 | (is (= (metadata-from-map {:meta {:doc "foo" :bar "bar"}}) 21 | ["foo" {:bar "bar"}]))) 22 | 23 | (deftest ^:unit test-imports-from-map 24 | (is (= (imports-from-map '{:import #{java.util.BitSet 25 | java.util.Random 26 | java.io.File}}) 27 | '(:import (java.io File) 28 | (java.util BitSet Random)))) 29 | (is (nil? (imports-from-map {:import #{}})))) 30 | 31 | (deftest ^:unit test-requires-from-map 32 | (is (= (requires-from-map '{:import #{} 33 | :require #{clojure.xml} 34 | :alias {clojure.string string} 35 | :refer {clojure.string #{trim} 36 | clojure.set #{difference}} 37 | :refer-all #{clojure.java.shell} 38 | :exclude {clojure.java.shell [with-sh-env]} 39 | :rename {clojure.java.shell {sh ssshhh}} 40 | :verbose #{clojure.string clojure.set 41 | clojure.java.shell clojure.xml} 42 | :reload-all #{clojure.string clojure.set 43 | clojure.java.shell clojure.xml}}) 44 | '((:require [clojure.java.shell :refer :all :exclude [with-sh-env] 45 | :rename {sh ssshhh}] 46 | [clojure.set :refer [difference]] 47 | [clojure.string :as string :refer [trim]] 48 | [clojure.xml] 49 | :reload-all :verbose)))) 50 | (testing "sorting" 51 | (is (= (str (requires-from-map '{:rename {my.ns {c cc a aa b bb}}})) 52 | "[(:require [my.ns :rename {a aa, b bb, c cc}])]")) 53 | (is (= (requires-from-map '{:refer {my.ns [c b a]}}) 54 | '[(:require [my.ns :refer [a b c]])]))) 55 | (testing "special handling of clojure.core" 56 | (is (nil? (requires-from-map '{:refer-all #{clojure.core}}))) 57 | (is (= (requires-from-map '{:alias {clojure.core core} 58 | :refer {clojure.core #{replace}}}) 59 | '[(:require [clojure.core :as core])]))) 60 | (testing "returns multiple require clauses for each set of require flags" 61 | (is (= (requires-from-map '{:require #{a b c d e} 62 | :verbose #{a b x} 63 | :reload #{a c y} 64 | :reload-all #{b d z}}) 65 | '[(:require [e]) 66 | (:require [c] :reload) 67 | (:require [d] :reload-all) 68 | (:require [a] :reload :verbose) 69 | (:require [b] :reload-all :verbose)]))) 70 | (testing "does not insert vector of :refer when ns is mass referred" 71 | (is (= (requires-from-map '{:alias {my.ns mn} 72 | :refer {my.ns #{foo bar baz}} 73 | :refer-all #{my.ns}}) 74 | '[(:require [my.ns :as mn :refer :all])])))) 75 | 76 | (deftest test-refer-clojure-from-map 77 | (is (nil? (refer-clojure-from-map '{:refer-all #{clojure.core}}))) 78 | (is (= (refer-clojure-from-map '{:refer-all #{clojure.core} 79 | :rename {clojure.core {/ div}} 80 | :exclude {clojure.core [* + -]}}) 81 | '(:refer-clojure :exclude [* + -] :rename {/ div}))) 82 | (is (= (refer-clojure-from-map '{:refer {clojure.core [refer]}}) 83 | '(:refer-clojure :only [refer]))) 84 | (is (= (refer-clojure-from-map '{:refer {clojure.core []}}) 85 | '(:refer-clojure :only []))) ; This is a common invocation 86 | (is (nil? (refer-clojure-from-map '{:refer {clojure.java.io [io]}})))) 87 | 88 | (deftest ^:unit test-ns-from-map 89 | (is (= (ns-from-map '{:name my.ns 90 | :meta {:doc "My example namespace."} 91 | :import #{java.util.BitSet java.util.Random} 92 | :require #{clojure.xml} 93 | :alias {clojure.string string} 94 | :refer {clojure.core [+ - * /] 95 | clojure.string #{trim} 96 | clojure.set #{difference}} 97 | :refer-all #{clojure.java.shell} 98 | :exclude {clojure.java.shell [with-sh-env]} 99 | :rename {clojure.java.shell {sh ssshhh}} 100 | :gen-class [:name Foo] 101 | :load ["/foo" "/bar"]}) 102 | '(ns my.ns 103 | "My example namespace." 104 | (:require [clojure.java.shell :refer :all :exclude [with-sh-env] 105 | :rename {sh ssshhh}] 106 | [clojure.set :refer [difference]] 107 | [clojure.string :as string :refer [trim]] 108 | [clojure.xml]) 109 | (:import (java.util BitSet Random)) 110 | (:refer-clojure :only [* + - /]) 111 | (:gen-class :name Foo) 112 | (:load "/foo" "/bar")))) 113 | (is (= (ns-from-map '{:name my.ns 114 | :alias {clojure.string string 115 | clojure.set set} 116 | :refer {clojure.string [upper-case lower-case]} 117 | :exclude {clojure.core [test compile]}}) 118 | '(ns my.ns 119 | (:require [clojure.set :as set] 120 | [clojure.string 121 | :as string 122 | :refer [lower-case upper-case]]) 123 | (:refer-clojure :exclude [compile test])))) 124 | (testing "generates multiple require clauses for each set of require flags" 125 | (is (= (ns-from-map '{:name my.ns 126 | :require #{foo bar baz} 127 | :reload-all #{bar}}) 128 | '(ns my.ns 129 | (:require [baz] [foo]) 130 | (:require [bar] :reload-all)))))) 131 | 132 | (deftest ^:unit test-stitch-up 133 | (is (= (slurp (io/resource "test-stitch-up.out")) 134 | (stitch-up '{:name slamhound.sample 135 | :meta {:doc "Testing some \"things\"\n going on here." 136 | :zzz "zzz" 137 | :slamhound-skip true} 138 | :import #{java.io.File 139 | java.io.ByteArrayInputStream 140 | clojure.lang.Compiler$BodyExpr 141 | java.util.UUID} 142 | :alias {clojure.java.io io 143 | clojure.set set} 144 | :refer {slam.hound.stitch #{ns-from-map} 145 | clojure.test #{is deftest} 146 | clojure.set #{union}} 147 | :exclude {clojure.core #{compile test}} 148 | :gen-class []})))) 149 | -------------------------------------------------------------------------------- /test/slam/hound/test/util.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound.test.util 2 | (:require [clojure.java.io :as io] 3 | [clojure.test :as t]) 4 | (:import (java.io File))) 5 | 6 | (defmacro with-tempfile 7 | {:requires [File]} 8 | [tmp-sym & body] 9 | `(let [~tmp-sym (File/createTempFile "slamhound_test" ".clj")] 10 | (try 11 | ~@body 12 | (finally 13 | (.delete ~tmp-sym))))) 14 | 15 | (defmacro with-transform-test 16 | "Copy contents of `in` to a tempfile, execute body with tempfile bound to 17 | tmp-sym, then finally compare the transformed contents of the tempfile with 18 | the contents of `out`. 19 | 20 | `in` and `out` are urls that will be passed to clojure.java.io/resource." 21 | {:requires [#'t/testing #'with-tempfile]} 22 | [string {:keys [in out]} [tmp-sym] & body] 23 | `(t/testing ~string 24 | (with-tempfile ~tmp-sym 25 | (try 26 | (spit ~tmp-sym (slurp (~io/resource ~in))) 27 | ~@body 28 | (catch Throwable e# 29 | (spit ~tmp-sym e#)) 30 | (finally 31 | (t/is (= (slurp ~tmp-sym) 32 | (slurp (~io/resource ~out))))))))) 33 | -------------------------------------------------------------------------------- /test/slam/hound_test.clj: -------------------------------------------------------------------------------- 1 | (ns slam.hound-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [slam.hound :refer [-main reconstruct 4 | swap-in-reconstructed-ns-form]] 5 | [slam.hound.test.util :refer [with-tempfile with-transform-test]]) 6 | (:import (java.io StringReader))) 7 | 8 | ;; For testing 9 | (defrecord ExampleRecord []) 10 | (def intersection (constantly "Conflicts with clojure.set/intersection")) 11 | 12 | (deftest ^:unit test-swap-in-reconstructed-ns-form 13 | (testing "original file is preserved on exceptions" 14 | (with-tempfile tmp 15 | (let [buf "(ns foo)\n(FOO/bar)"] 16 | (spit tmp buf) 17 | (is (thrown? Throwable (swap-in-reconstructed-ns-form tmp))) 18 | (is (= buf (slurp tmp)))))) 19 | (testing "accepts a String argument" 20 | (with-tempfile tmp 21 | (let [buf "(ns slamhound-test)"] 22 | (spit tmp buf) 23 | (swap-in-reconstructed-ns-form (.getPath tmp)) 24 | (is (= buf (slurp tmp))))))) 25 | 26 | (deftest ^:integration test-regression 27 | (is (= "(ns foo.bar\n (:require [clj-schema.validation :as val]))\n" 28 | (reconstruct (StringReader. 29 | (str '(ns foo.bar 30 | (:require [clj-schema.validation :as val])) 31 | '(val/validation-errors [[:name] String] 32 | {:name "Bob"}))))))) 33 | 34 | (deftest ^:integration test-finds-alias-vars-in-nested-maps-and-sets 35 | (is (= "(ns foo.bar\n (:require [clj-schema.validation :as val]))\n" 36 | (reconstruct (StringReader. 37 | (str '(ns foo.bar 38 | (:require [clj-schema.validation :as val])) 39 | '#{:x {:a (val/validation-errors 40 | [[:name] String] 41 | {:name "Bob"})}})))))) 42 | 43 | (deftest ^:unit test-reconstruct 44 | (with-transform-test "basic ns reconstruction" 45 | {:in "test-reconstruct.in" 46 | :out "test-reconstruct.out"} 47 | [tmp] 48 | (let [buf (reconstruct tmp)] 49 | (spit tmp buf)))) 50 | 51 | (deftest ^:integration test-prefers-references-from-orig-ns 52 | (with-transform-test "preference for references in original ns" 53 | {:in "test-prefer-orig-ns-refs.in" 54 | :out "test-prefer-orig-ns-refs.out"} 55 | [tmp] 56 | (swap-in-reconstructed-ns-form tmp)) 57 | (with-transform-test "if both clojure.string/join and clojure.set/join 58 | are referred in the original ns, prefer 59 | clojure.string/join if clojure.string is also mass 60 | referred." 61 | {:in "test-disambiguation-of-join.in" 62 | :out "test-disambiguation-of-join.out"} 63 | [tmp] 64 | (swap-in-reconstructed-ns-form tmp))) 65 | 66 | (deftest ^:integration test-comment-header-preservation 67 | (with-transform-test "tidy preservation of comment headers" 68 | {:in "test-comment-headers.in" 69 | :out "test-comment-headers.out"} 70 | [tmp] 71 | (swap-in-reconstructed-ns-form tmp))) 72 | 73 | (deftest ^:integration test-long-file-preservation 74 | (with-transform-test "long files are not truncated" 75 | {:in "test-long-files.in" 76 | :out "test-long-files.out"} 77 | [tmp] 78 | (binding [slam.hound/*testing?* true] 79 | (-main tmp)))) 80 | 81 | (deftest ^:integration test-detect-references-in-metadata 82 | (with-transform-test "detection of references in metadata" 83 | {:in "test-metadata.in" 84 | :out "test-metadata.out"} 85 | [tmp] 86 | (let [buf (reconstruct tmp)] 87 | (spit tmp buf)))) 88 | 89 | (deftest ^:integration test-using-dots-in-alias 90 | (with-transform-test "dots in ns alias are fine" 91 | {:in "test-dotted-alias.in" 92 | :out "test-dotted-alias.out"} 93 | [tmp] 94 | (swap-in-reconstructed-ns-form tmp))) 95 | -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | * Deferred reference validation 2 | technomancy, from https://github.com/technomancy/slamhound/issues/19: 3 | 4 | "I've thought a bit about trying to walk all defmacro bodies in the 5 | namespace and check for symbols with a namespace, but that would almost 6 | positively result in false positives." 7 | 8 | "I'd rather not handle this than handle this in a way that would have edge 9 | cases which could make it go looking for vars that don't exist." 10 | 11 | Does a solution exist that does not produce false positives, nor 12 | accidentally call (launch-missiles)? 13 | 14 | - [ ] Detect qualified references within syntax-quote 15 | - [ ] Detect fully qualified namespaces 16 | * Static Analysis 17 | With the advent of official Clojure analyzers (see org.clojure/tools.analyzer 18 | and related projects), it may be possible to augment or replace Slamhound's 19 | failure-driven missing reference detection algorithm with static analysis. 20 | 21 | This has the potential to greatly improve the performance of the regrow 22 | step, and may also allow us to support ClojureScript. 23 | 24 | - [ ] Investigate use of a static analyzer to detect missing ns references 25 | * Performance 26 | The slowest operation in slamhound is the pre-loading of namespaces, which 27 | is CPU bound (note that it is always slow regardless of the disk cache). 28 | 29 | Pre-loading in parallel with (Executors/newFixedThreadPool #cpu-threads) 30 | does divide the execution time by ~ 0.8*cpu-threads, but obviously does not 31 | decrease the actual amount of work. 32 | 33 | Also, GC thrashing due to PermGen appears to still be an issue with 34 | `-XX:+CMSClassUnloadingEnabled`. Initial investigation shows that we may 35 | need to implement our own classloader. 36 | 37 | - [ ] Improve performance of slam.hound.reload/pre-load-namespaces if possible 38 | - [ ] Investigate impact of a Slamhound optimized classloader on PermGen 39 | * Testing 40 | Our unit tests rely heavily on references defined in the test namespaces and 41 | in external dependencies. It would be better to create and destroy a mock 42 | environment to clearly see the inputs for disambiguation. 43 | 44 | This leads quite naturally to property based testing with test.check. 45 | 46 | - [ ] Replace test dependencies with mock namespaces that are easy to control 47 | - [ ] Convert tests to test.check where appropriate (e.g. slam.hound.regrow-test) 48 | * Minor enhancements 49 | - [X] Sort alias candidates by L̶e̶v̶e̶n̶s̶h̶t̶e̶i̶n̶ "alias" distance, with emphasis on initial letters 50 | - [X] Satisfy all reflection warnings (there are only a handful) 51 | - [X] Support Unicode characters in regrow/missing-sym-name 52 | - [ ] Count dashes as word separators when calculating "alias" distance 53 | - [ ] Sort refer candidates by matching arity. e.g. (join []) should prefer clojure.string/join 54 | --------------------------------------------------------------------------------