├── .github └── workflows │ ├── ci.yml │ └── clean.yml ├── .gitignore ├── .scalafmt.conf ├── LICENSE ├── LICENSE.scalaz ├── README.md ├── benchmark └── src │ ├── it │ └── scala │ │ └── io │ │ └── iteratee │ │ └── benchmark │ │ └── FileModuleBenchmarkSpec.scala │ ├── main │ └── scala │ │ └── io │ │ └── iteratee │ │ └── benchmark │ │ ├── Benchmark.scala │ │ ├── FileModuleBenchmark.scala │ │ └── FreeTryModule.scala │ └── test │ └── scala │ └── io │ └── iteratee │ └── benchmark │ ├── InMemoryBenchmarkSpec.scala │ └── StreamingBenchmarkSpec.scala ├── build.sbt ├── core └── src │ └── main │ └── scala │ └── io │ └── iteratee │ ├── Enumeratee.scala │ ├── Enumerator.scala │ ├── Iteratee.scala │ ├── internal │ ├── Step.scala │ └── package.scala │ └── modules │ ├── EnumerateeModule.scala │ ├── EnumeratorModule.scala │ ├── IterateeModule.scala │ ├── Module.scala │ └── package.scala ├── files └── src │ └── main │ └── scala │ └── io │ └── iteratee │ └── files │ ├── modules │ ├── FileModule.scala │ └── package.scala │ └── package.scala ├── project ├── build.properties └── plugins.sbt ├── scalastyle-config.xml ├── testing ├── jvm │ └── src │ │ └── main │ │ └── scala │ │ └── io │ │ └── iteratee │ │ └── testing │ │ └── files │ │ └── FileModuleSuite.scala └── shared │ └── src │ └── main │ └── scala │ └── io │ └── iteratee │ └── testing │ ├── ArbitraryEnumerators.scala │ ├── ArbitraryInstances.scala │ ├── BaseSuite.scala │ ├── EnumerateeSuite.scala │ ├── EnumeratorSuite.scala │ ├── EqInstances.scala │ └── IterateeSuite.scala ├── tests ├── jvm │ └── src │ │ ├── it │ │ └── scala │ │ │ └── io │ │ │ └── iteratee │ │ │ └── files │ │ │ └── IOTests.scala │ │ ├── main │ │ └── resources │ │ │ └── io │ │ │ └── iteratee │ │ │ └── examples │ │ │ └── pg │ │ │ └── 11231 │ │ │ ├── 11231.txt │ │ │ └── 11231.zip │ │ └── test │ │ └── scala │ │ └── io │ │ └── iteratee │ │ └── FutureTests.scala └── shared │ └── src │ ├── main │ └── scala │ │ └── io │ │ └── iteratee │ │ └── tests │ │ └── ModuleSuites.scala │ └── test │ └── scala │ └── io │ └── iteratee │ ├── EitherTTests.scala │ ├── EitherTests.scala │ ├── EvalTests.scala │ ├── IdTests.scala │ ├── OptionTests.scala │ └── TryTests.scala └── version.sbt /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Continuous Integration 9 | 10 | on: 11 | pull_request: 12 | branches: ['**'] 13 | push: 14 | branches: ['**'] 15 | 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | jobs: 20 | build: 21 | name: Build and Test 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest] 25 | scala: [2.12.14, 2.13.6, 3.0.2] 26 | java: [adopt@1.8] 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - name: Checkout current branch (full) 30 | uses: actions/checkout@v2 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: Setup Java and Scala 35 | uses: olafurpg/setup-scala@v13 36 | with: 37 | java-version: ${{ matrix.java }} 38 | 39 | - name: Cache sbt 40 | uses: actions/cache@v2 41 | with: 42 | path: | 43 | ~/.sbt 44 | ~/.ivy2/cache 45 | ~/.coursier/cache/v1 46 | ~/.cache/coursier/v1 47 | ~/AppData/Local/Coursier/Cache/v1 48 | ~/Library/Caches/Coursier/v1 49 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 50 | 51 | - name: Check that workflows are up to date 52 | run: sbt ++${{ matrix.scala }} githubWorkflowCheck 53 | 54 | - name: Test 55 | run: sbt ++${{ matrix.scala }} clean coverage scalastyle scalafmtCheckAll scalafmtSbtCheck test coverageReport 56 | 57 | - uses: codecov/codecov-action@v1 58 | -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | run: | 21 | # Customize those three lines with your repository and credentials: 22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 23 | 24 | # A shortcut to call GitHub API. 25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 26 | 27 | # A temporary file which receives HTTP response headers. 28 | TMPFILE=/tmp/tmp.$$ 29 | 30 | # An associative array, key: artifact name, value: number of artifacts of that name. 31 | declare -A ARTCOUNT 32 | 33 | # Process all artifacts on this repository, loop on returned "pages". 34 | URL=$REPO/actions/artifacts 35 | while [[ -n "$URL" ]]; do 36 | 37 | # Get current page, get response headers in a temporary file. 38 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 39 | 40 | # Get URL of next page. Will be empty if we are at the last page. 41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 42 | rm -f $TMPFILE 43 | 44 | # Number of artifacts on this page: 45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 46 | 47 | # Loop on all artifacts on this page. 48 | for ((i=0; $i < $COUNT; i++)); do 49 | 50 | # Get name of artifact and count instances of this name. 51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 53 | 54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 57 | ghapi -X DELETE $REPO/actions/artifacts/$id 58 | done 59 | done 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | target/ 3 | .idea/ 4 | .idea_modules/ 5 | .DS_STORE 6 | .cache 7 | .settings 8 | .project 9 | .classpath 10 | .bsp/ 11 | tmp/ 12 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.0.6 2 | continuationIndent.defnSite = 2 3 | docstrings.style = Asterisk 4 | includeCurlyBraceInSelectChains = false 5 | maxColumn = 120 6 | newlines.alwaysBeforeElseAfterCurlyIf = false 7 | newlines.alwaysBeforeMultilineDef = false 8 | optIn.breakChainOnFirstMethodDot = false 9 | spaces.inImportCurlyBraces = false 10 | rewrite.rules = [ 11 | AvoidInfix, 12 | RedundantParens, 13 | AsciiSortImports, 14 | PreferCurlyFors 15 | ] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE.scalaz: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2014 Tony Morris, Runar Bjarnason, Tom Adams, Kristian Domagala, Brad Clow, Ricky Clarkson, Paul Chiusano, Trygve Laugstøl, Nick Partridge, Jason Zaugg 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. The name of the author may not be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iteratee.io 2 | 3 | [![Build status](https://img.shields.io/travis/travisbrown/iteratee/main.svg)](https://travis-ci.org/travisbrown/iteratee) 4 | [![Coverage status](https://img.shields.io/codecov/c/github/travisbrown/iteratee/main.svg)](https://codecov.io/github/travisbrown/iteratee) 5 | [![Gitter](https://img.shields.io/badge/gitter-join%20chat-green.svg)](https://gitter.im/travisbrown/iteratee) 6 | [![Maven Central](https://img.shields.io/maven-central/v/io.iteratee/iteratee-core_2.13.svg)](https://maven-badges.herokuapp.com/maven-central/io.iteratee/iteratee-core_2.13) 7 | 8 | This project is an iteratee implementation for [Cats][cats] that began as a port of 9 | [Scalaz][scalaz]'s [iteratee package][scalaz-iteratee], although the API and implementation are now 10 | very different from Scalaz's. There are [API docs][api-docs] (but they're a work in progress), and 11 | I've published a [blog post][intro] introducing the project. 12 | 13 | The motivations for the port are similar to those for [circe][circe]—in particular I'm aiming for a 14 | more consistent API, better performance, and better documentation. 15 | 16 | Note that this library doesn't support many of the use cases that [fs2][fs2] (formerly Scalaz 17 | Stream) is designed to handle. It doesn't support nondeterministic reading from multiple streams, 18 | for example, and in general is a less appropriate choice for situations where concurrency and 19 | parallelism are primary goals. Where the use cases of fs2 and this library do overlap, however, it's 20 | often likely to be a simpler, faster solution. 21 | 22 | The initial performance benchmarks look promising. For example, here are the throughput results for 23 | summing a sequence of numbers with this library and `cats.Id` (`II`), this library and Monix's 24 | `Task` (`IM`), this library and Scalaz's `Task` (`IT`), this library and Twitter futures (`IR`), 25 | Scalaz Stream (`S`), scalaz-iteratee (`Z`), [play-iteratee][play-iteratee] (`P`), the Scala 26 | collections library (`C`), and fs2 (`F`). Higher numbers are better. 27 | 28 | ``` 29 | Benchmark Mode Cnt Score Error Units 30 | InMemoryBenchmark.sumInts0II thrpt 80 10225.388 ± 191.612 ops/s 31 | InMemoryBenchmark.sumInts1IM thrpt 80 13395.800 ± 30.912 ops/s 32 | InMemoryBenchmark.sumInts2IT thrpt 80 18609.579 ± 47.491 ops/s 33 | InMemoryBenchmark.sumInts3IR thrpt 80 15999.740 ± 114.949 ops/s 34 | InMemoryBenchmark.sumInts4S thrpt 80 72.074 ± 1.209 ops/s 35 | InMemoryBenchmark.sumInts5Z thrpt 80 310.472 ± 4.368 ops/s 36 | InMemoryBenchmark.sumInts6P thrpt 80 43.071 ± 0.543 ops/s 37 | InMemoryBenchmark.sumInts7C thrpt 80 12975.042 ± 48.702 ops/s 38 | InMemoryBenchmark.sumInts8F thrpt 80 9610.699 ± 41.936 ops/s 39 | ``` 40 | 41 | And the results for collecting the first 10,000 values from an infinite stream of non-negative 42 | numbers into a `Vector`: 43 | 44 | ``` 45 | Benchmark Mode Cnt Score Error Units 46 | StreamingBenchmark.takeLongs0II thrpt 80 2787.725 ± 16.812 ops/s 47 | StreamingBenchmark.takeLongs1IM thrpt 80 1617.848 ± 19.899 ops/s 48 | StreamingBenchmark.takeLongs2IT thrpt 80 1052.494 ± 7.707 ops/s 49 | StreamingBenchmark.takeLongs3IR thrpt 80 979.514 ± 26.197 ops/s 50 | StreamingBenchmark.takeLongs4S thrpt 80 56.882 ± 0.969 ops/s 51 | StreamingBenchmark.takeLongs5Z thrpt 80 154.103 ± 10.350 ops/s 52 | StreamingBenchmark.takeLongs6P thrpt 80 1.216 ± 0.005 ops/s 53 | StreamingBenchmark.takeLongs7C thrpt 80 3273.158 ± 55.187 ops/s 54 | StreamingBenchmark.takeLongs8F thrpt 80 7.915 ± 0.044 ops/s 55 | ``` 56 | 57 | And allocation rates (lower is better): 58 | 59 | ``` 60 | Benchmark Mode Cnt Score Error Units 61 | InMemoryBenchmark.sumInts0II:gc.alloc.rate.norm thrpt 20 159953.462 ± 11.863 B/op 62 | InMemoryBenchmark.sumInts1IM:gc.alloc.rate.norm thrpt 20 160203.272 ± 5.949 B/op 63 | InMemoryBenchmark.sumInts2IT:gc.alloc.rate.norm thrpt 20 160622.026 ± 6.323 B/op 64 | InMemoryBenchmark.sumInts3IR:gc.alloc.rate.norm thrpt 20 160398.303 ± 6.685 B/op 65 | InMemoryBenchmark.sumInts4S:gc.alloc.rate.norm thrpt 20 63936897.241 ± 320928.043 B/op 66 | InMemoryBenchmark.sumInts5Z:gc.alloc.rate.norm thrpt 20 16401510.998 ± 6.115 B/op 67 | InMemoryBenchmark.sumInts6P:gc.alloc.rate.norm thrpt 20 13802446.593 ± 229152.745 B/op 68 | InMemoryBenchmark.sumInts7C:gc.alloc.rate.norm thrpt 20 159851.547 ± 14.556 B/op 69 | InMemoryBenchmark.sumInts8F:gc.alloc.rate.norm thrpt 20 260454.260 ± 1522.736 B/op 70 | 71 | Benchmark Mode Cnt Score Error Units 72 | StreamingBenchmark.takeLongs0II:gc.alloc.rate.norm thrpt 20 3043720.338 ± 0.018 B/op 73 | StreamingBenchmark.takeLongs1IM:gc.alloc.rate.norm thrpt 20 3444961.639 ± 4.168 B/op 74 | StreamingBenchmark.takeLongs2IT:gc.alloc.rate.norm thrpt 20 5804308.795 ± 61718.228 B/op 75 | StreamingBenchmark.takeLongs3IR:gc.alloc.rate.norm thrpt 20 5124124.296 ± 5.147 B/op 76 | StreamingBenchmark.takeLongs4S:gc.alloc.rate.norm thrpt 20 75347149.315 ± 555268.150 B/op 77 | StreamingBenchmark.takeLongs5Z:gc.alloc.rate.norm thrpt 20 28588033.048 ± 238419.245 B/op 78 | StreamingBenchmark.takeLongs6P:gc.alloc.rate.norm thrpt 20 1206196498.000 ± 71329.621 B/op 79 | StreamingBenchmark.takeLongs7C:gc.alloc.rate.norm thrpt 20 526752.310 ± 0.029 B/op 80 | StreamingBenchmark.takeLongs8F:gc.alloc.rate.norm thrpt 20 531380973.839 ± 13505581.754 B/op 81 | ``` 82 | 83 | ## License 84 | 85 | iteratee.io is licensed under the **[Apache License, Version 2.0][apache]** (the 86 | "License"); you may not use this software except in compliance with the License. 87 | 88 | Unless required by applicable law or agreed to in writing, software 89 | distributed under the License is distributed on an "AS IS" BASIS, 90 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 91 | See the License for the specific language governing permissions and 92 | limitations under the License. 93 | 94 | [apache]: http://www.apache.org/licenses/LICENSE-2.0 95 | [api-docs]: http://travisbrown.github.io/iteratee/api/#io.iteratee.package 96 | [cats]: https://github.com/typelevel/cats 97 | [circe]: https://github.com/travisbrown/circe 98 | [fs2]: https://github.com/functional-streams-for-scala/fs2 99 | [intro]: https://meta.plasm.us/posts/2016/01/08/yet-another-iteratee-library/ 100 | [monix]: https://github.com/monixio/monix 101 | [play-iteratee]: https://www.playframework.com/documentation/2.5.x/Iteratees 102 | [scalaz]: https://github.com/scalaz/scalaz 103 | [scalaz-iteratee]: https://github.com/scalaz/scalaz/tree/series/7.2.x/iteratee/src/main/scala/scalaz/iteratee 104 | -------------------------------------------------------------------------------- /benchmark/src/it/scala/io/iteratee/benchmark/FileModuleBenchmarkSpec.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.benchmark 2 | 3 | import org.scalatest.FlatSpec 4 | 5 | class FileModuleBenchmarkSpec extends FlatSpec { 6 | val benchmark: FileModuleBenchmark = new FileModuleBenchmark 7 | val length = 4.516696895337672 8 | 9 | "The FileModule benchmark" should "correctly calculate the average word length using IO" in { 10 | assert(benchmark.avgWordLengthIO === length) 11 | } 12 | 13 | it should "correctly calculate the average word length using Try in Free" in { 14 | assert(benchmark.avgWordLengthTF === length) 15 | } 16 | 17 | it should "correctly calculate the average word length using Monix's Task" in { 18 | assert(benchmark.avgWordLengthM === length) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /benchmark/src/main/scala/io/iteratee/benchmark/Benchmark.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.benchmark 2 | 3 | import cats.{Id, catsInstancesForId} 4 | import cats.effect.IO 5 | import cats.effect.unsafe.implicits.global 6 | import fs2.{Stream => StreamF} 7 | import io.{iteratee => i} 8 | import java.util.concurrent.TimeUnit 9 | import org.openjdk.jmh.annotations._ 10 | import scala.Predef.intWrapper 11 | 12 | class InMemoryExampleData { 13 | private[this] val count = 10000 14 | 15 | val intsC: Vector[Int] = (0 until count).toVector 16 | val intsII: i.Enumerator[Id, Int] = i.Enumerator.enumVector[Id, Int](intsC) 17 | val intsIO: i.Enumerator[IO, Int] = i.Enumerator.enumVector[IO, Int](intsC) 18 | val intsF: StreamF[IO, Int] = StreamF.emits(intsC) 19 | } 20 | 21 | class StreamingExampleData { 22 | val longStreamII: i.Enumerator[Id, Long] = i.Enumerator.iterate[Id, Long](0L)(_ + 1L) 23 | val longStreamIO: i.Enumerator[IO, Long] = i.Enumerator.StackUnsafe.iterate[IO, Long](0L)(_ + 1L) 24 | val longStreamF: StreamF[IO, Long] = StreamF.iterate(0L)(_ + 1L) 25 | val longStreamC: Stream[Long] = Stream.iterate(0L)(_ + 1L) 26 | } 27 | 28 | /** 29 | * Compare the performance of iteratee operations. 30 | * 31 | * The following command will run the benchmarks with reasonable settings: 32 | * 33 | * > sbt "benchmark/jmh:run -i 10 -wi 10 -f 2 -t 1 io.iteratee.benchmark.InMemoryBenchmark" 34 | */ 35 | @State(Scope.Thread) 36 | @BenchmarkMode(Array(Mode.Throughput)) 37 | @OutputTimeUnit(TimeUnit.SECONDS) 38 | class InMemoryBenchmark extends InMemoryExampleData { 39 | @Benchmark 40 | def sumInts0II: Int = intsII.into(i.Iteratee.sum) 41 | 42 | @Benchmark 43 | def sumInts1IO: Int = intsIO.into(i.Iteratee.sum).unsafeRunSync() 44 | 45 | @Benchmark 46 | def sumInts3F: Int = intsF.fold1(_ + _).compile.last.unsafeRunSync().get 47 | 48 | @Benchmark 49 | def sumInts4C: Int = intsC.sum 50 | } 51 | 52 | /** 53 | * Compare the performance of iteratee operations. 54 | * 55 | * The following command will run the benchmarks with reasonable settings: 56 | * 57 | * > sbt "benchmark/jmh:run -i 10 -wi 10 -f 2 -t 1 io.iteratee.benchmark.StreamingBenchmark" 58 | */ 59 | @State(Scope.Thread) 60 | @BenchmarkMode(Array(Mode.Throughput)) 61 | @OutputTimeUnit(TimeUnit.SECONDS) 62 | class StreamingBenchmark extends StreamingExampleData { 63 | val count = 10000 64 | 65 | @Benchmark 66 | def takeLongs0II: Vector[Long] = longStreamII.into(i.Iteratee.take(count)) 67 | 68 | @Benchmark 69 | def takeLongs1IO: Vector[Long] = longStreamIO.into(i.Iteratee.take(count)).unsafeRunSync() 70 | 71 | @Benchmark 72 | def takeLongs3F: Vector[Long] = longStreamF.take(count.toLong).compile.toVector.unsafeRunSync() 73 | 74 | @Benchmark 75 | def takeLongs4C: Vector[Long] = longStreamC.take(count).toVector 76 | } 77 | -------------------------------------------------------------------------------- /benchmark/src/main/scala/io/iteratee/benchmark/FileModuleBenchmark.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.benchmark 2 | 3 | import cats.Monad 4 | import cats.effect.IO 5 | import cats.effect.unsafe.implicits.global 6 | import cats.free.Free 7 | import io.iteratee.{Enumerator, Iteratee} 8 | import java.io.InputStream 9 | import java.util.concurrent.TimeUnit 10 | import org.openjdk.jmh.annotations._ 11 | import scala.Predef.refArrayOps 12 | import scala.util.Try 13 | 14 | /** 15 | * Compare the performance of iteratee operations. 16 | * 17 | * The following command will run the benchmarks with reasonable settings: 18 | * 19 | * > sbt "benchmark/jmh:run -i 10 -wi 10 -f 2 -t 1 io.iteratee.benchmark.FileModuleBenchmark" 20 | */ 21 | @State(Scope.Thread) 22 | @BenchmarkMode(Array(Mode.Throughput)) 23 | @OutputTimeUnit(TimeUnit.SECONDS) 24 | class FileModuleBenchmark { 25 | private def bartebly: InputStream = getClass.getResourceAsStream("/io/iteratee/examples/pg/11231/11231.txt") 26 | 27 | def linesIO: Enumerator[IO, String] = io.iteratee.files.modules.io.readLinesFromStream(bartebly) 28 | def linesTF: Enumerator[Free[Try, *], String] = FreeTryModule.readLinesFromStream(bartebly) 29 | 30 | def words[F[_]: Monad](line: String): Enumerator[F, String] = Enumerator.enumIndexedSeq(line.split(" ").toIndexedSeq) 31 | def avgLen[F[_]: Monad]: Iteratee[F, String, Double] = Iteratee 32 | .length[F, String] 33 | .zip( 34 | Iteratee.foldMap[F, String, Int](_.length) 35 | ) 36 | .map { 37 | case (count, totalLengths) => (totalLengths.toDouble / count.toDouble) 38 | } 39 | 40 | @Benchmark 41 | def avgWordLengthIO: Double = linesIO.flatMap(words[IO]).into(avgLen).unsafeRunSync() 42 | 43 | @Benchmark 44 | def avgWordLengthTF: Double = linesTF.flatMap(words[Free[Try, *]]).into(avgLen[Free[Try, *]]).runTailRec.get 45 | } 46 | -------------------------------------------------------------------------------- /benchmark/src/main/scala/io/iteratee/benchmark/FreeTryModule.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.benchmark 2 | 3 | import cats.{Monad, MonadError} 4 | import cats.effect.Sync 5 | import cats.free.Free 6 | import cats.instances.try_._ 7 | import io.iteratee.files.modules.FileModule 8 | import io.iteratee.modules.{EnumerateeModule, EnumeratorErrorModule, IterateeErrorModule, Module} 9 | import scala.util.Try 10 | 11 | object FreeTryModule 12 | extends Module[Free[Try, *]] 13 | with EnumerateeModule[Free[Try, *]] 14 | with EnumeratorErrorModule[Free[Try, *], Throwable] 15 | with IterateeErrorModule[Free[Try, *], Throwable] 16 | with FileModule[Free[Try, *]] { 17 | final type M[f[_]] = Sync[f] 18 | 19 | final protected val F: Sync[Free[Try, *]] = new Sync[Free[Try, *]] { 20 | private[this] val FF = Monad[Free[Try, *]] 21 | def pure[A](x: A): Free[Try, A] = FF.pure(x) 22 | def raiseError[A](e: Throwable): Free[Try, A] = Free.liftF(MonadError[Try, Throwable].raiseError(e)) 23 | def handleErrorWith[A](fa: Free[Try, A])(f: Throwable => Free[Try, A]): Free[Try, A] = Free.liftF( 24 | MonadError[Try, Throwable].handleErrorWith(fa.runTailRec)(e => f(e).runTailRec) 25 | ) 26 | def flatMap[A, B](fa: Free[Try, A])(f: A => Free[Try, B]): Free[Try, B] = FF.flatMap(fa)(f) 27 | def tailRecM[A, B](a: A)(f: A => Free[Try, Either[A, B]]): Free[Try, B] = FF.tailRecM(a)(f) 28 | 29 | def suspend[A](hint: Sync.Type)(thunk: => A): Free[Try, A] = Free.defer(Free.pure(thunk)) 30 | 31 | def monotonic: Free[Try, scala.concurrent.duration.FiniteDuration] = Predef.??? 32 | def realTime: Free[Try, scala.concurrent.duration.FiniteDuration] = Predef.??? 33 | def canceled: Free[Try, Unit] = Predef.??? 34 | def forceR[A, B](fa: Free[Try, A])(fb: Free[Try, B]): Free[Try, B] = Predef.??? 35 | def onCancel[A](fa: Free[Try, A], fin: Free[Try, Unit]): Free[Try, A] = Predef.??? 36 | def rootCancelScope: cats.effect.kernel.CancelScope = Predef.??? 37 | def uncancelable[A](body: cats.effect.kernel.Poll[Free[Try, *]] => Free[Try, A]): Free[Try, A] = Predef.??? 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /benchmark/src/test/scala/io/iteratee/benchmark/InMemoryBenchmarkSpec.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.benchmark 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | 5 | class InMemoryBenchmarkSpec extends AnyFlatSpec { 6 | val benchmark: InMemoryBenchmark = new InMemoryBenchmark 7 | val sum = 49995000 8 | 9 | "The in-memory benchmark" should "correctly calculate the sum using io.iteratee.modules.id" in { 10 | assert(benchmark.sumInts0II === sum) 11 | } 12 | 13 | it should "correctly calculate the sum using cats.effect.IO" in { 14 | assert(benchmark.sumInts1IO === sum) 15 | } 16 | 17 | it should "correctly calculate the sum using fs2" in { 18 | assert(benchmark.sumInts3F === sum) 19 | } 20 | 21 | it should "correctly calculate the sum using the collections library" in { 22 | assert(benchmark.sumInts4C === sum) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /benchmark/src/test/scala/io/iteratee/benchmark/StreamingBenchmarkSpec.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.benchmark 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import scala.Predef.intWrapper 5 | 6 | class StreamingBenchmarkSpec extends AnyFlatSpec { 7 | val benchmark: StreamingBenchmark = new StreamingBenchmark 8 | val taken = (0 until 10000).toVector 9 | 10 | "The streaming benchmark" should "correctly gather elements using io.iteratee.modules.id" in { 11 | assert(benchmark.takeLongs0II === taken) 12 | } 13 | 14 | it should "correctly gather elements using cats.effect.IO" in { 15 | assert(benchmark.takeLongs1IO === taken) 16 | } 17 | 18 | it should "correctly gather elements using fs2" in { 19 | assert(benchmark.takeLongs3F === taken) 20 | } 21 | 22 | it should "correctly gather elements using the collections library" in { 23 | assert(benchmark.takeLongs4C === taken) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import ReleaseTransformations._ 2 | import org.scalafmt.sbt.ScalafmtPlugin.scalafmtConfigSettings 3 | import sbtcrossproject.{CrossType, crossProject} 4 | import scala.xml.{Elem, Node => XmlNode, NodeSeq => XmlNodeSeq} 5 | import scala.xml.transform.{RewriteRule, RuleTransformer} 6 | 7 | ThisBuild / organization := "io.iteratee" 8 | ThisBuild / crossScalaVersions := List("2.12.14", "2.13.6", "3.0.2") 9 | ThisBuild / scalaVersion := crossScalaVersions.value.last 10 | 11 | ThisBuild / githubWorkflowJavaVersions := Seq("adopt@1.8") 12 | ThisBuild / githubWorkflowPublishTargetBranches := Nil 13 | ThisBuild / githubWorkflowBuild := Seq( 14 | WorkflowStep.Sbt( 15 | List( 16 | "clean", 17 | "coverage", 18 | "scalastyle", 19 | "scalafmtCheckAll", 20 | "scalafmtSbtCheck", 21 | "test", 22 | "coverageReport" 23 | ), 24 | id = None, 25 | name = Some("Test") 26 | ), 27 | WorkflowStep.Use( 28 | UseRef.Public("codecov", "codecov-action", "v1") 29 | ) 30 | ) 31 | 32 | val compilerOptions = Seq( 33 | "-deprecation", 34 | "-encoding", 35 | "UTF-8", 36 | "-feature", 37 | "-language:existentials", 38 | "-language:higherKinds", 39 | "-unchecked", 40 | "-Yno-adapted-args", 41 | "-Ywarn-dead-code", 42 | "-Ywarn-numeric-widen", 43 | "-Ywarn-unused-import", 44 | "-Xfuture" 45 | ) 46 | 47 | val catsVersion = "2.6.1" 48 | val catsEffectVersion = "3.2.9" 49 | val fs2Version = "3.1.5" 50 | 51 | val scalaTestVersion = "3.2.10" 52 | val scalaCheckVersion = "1.15.4" 53 | val disciplineVersion = "1.1.5" 54 | 55 | /** 56 | * Some terrible hacks to work around Cats's decision to have builds for different Scala versions depend on different 57 | * versions of Discipline, etc. 58 | */ 59 | def priorTo2_13(scalaVersion: String): Boolean = 60 | CrossVersion.partialVersion(scalaVersion) match { 61 | case Some((2, minor)) if minor < 13 => true 62 | case _ => false 63 | } 64 | 65 | lazy val previousIterateeVersion = "0.19.0" 66 | 67 | val docMappingsApiDir = settingKey[String]("Subdirectory in site target directory for API docs") 68 | 69 | lazy val baseSettings = Seq( 70 | scalacOptions ++= { 71 | if (priorTo2_13(scalaVersion.value)) compilerOptions 72 | else 73 | compilerOptions.flatMap { 74 | case "-Ywarn-unused-import" => Some("-Ywarn-unused:imports") 75 | case "-Yno-adapted-args" => None 76 | case "-Xfuture" => None 77 | case other => Some(other) 78 | } 79 | }, 80 | scalacOptions += "-Yno-predef", 81 | Compile / console / scalacOptions := { 82 | if (priorTo2_13(scalaVersion.value)) compilerOptions 83 | else 84 | compilerOptions.flatMap { 85 | case "-Ywarn-unused-import" => Some("-Ywarn-unused:imports") 86 | case "-Yno-adapted-args" => None 87 | case "-Xfuture" => None 88 | case other => Some(other) 89 | } 90 | }, 91 | Test / compile / scalacOptions := { 92 | if (priorTo2_13(scalaVersion.value)) compilerOptions 93 | else 94 | compilerOptions.flatMap { 95 | case "-Ywarn-unused-import" => Some("-Ywarn-unused:imports") 96 | case "-Yno-adapted-args" => None 97 | case "-Xfuture" => None 98 | case other => Some(other) 99 | } 100 | }, 101 | scalacOptions ++= { 102 | if (scalaVersion.value.startsWith("3")) Seq("-Ykind-projector:underscores") 103 | else Seq("-Xsource:3", "-P:kind-projector:underscore-placeholders") 104 | }, 105 | coverageHighlighting := true, 106 | coverageEnabled := { 107 | if (scalaVersion.value.startsWith("3")) false else coverageEnabled.value 108 | }, 109 | Compile / scalastyleSources ++= (Compile / sourceDirectories).value, 110 | libraryDependencies ++= { 111 | if (scalaVersion.value.startsWith("3")) Nil 112 | else 113 | Seq( 114 | compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)) 115 | ) 116 | } 117 | ) 118 | 119 | lazy val allSettings = baseSettings ++ publishSettings 120 | 121 | lazy val commonJsSettings = Seq( 122 | Global / scalaJSStage := FastOptStage 123 | ) 124 | 125 | lazy val docSettings = Seq( 126 | docMappingsApiDir := "api", 127 | addMappingsToSiteDir(ScalaUnidoc / packageDoc / mappings, docMappingsApiDir), 128 | ScalaUnidoc / unidoc / scalacOptions ++= Seq( 129 | "-groups", 130 | "-implicits", 131 | "-doc-source-url", 132 | scmInfo.value.get.browseUrl + "/tree/main€{FILE_PATH}.scala", 133 | "-sourcepath", 134 | (LocalRootProject / baseDirectory).value.getAbsolutePath 135 | ), 136 | git.remoteRepo := "git@github.com:travisbrown/iteratee.git", 137 | ScalaUnidoc / unidoc / unidocProjectFilter := 138 | inAnyProject -- inProjects(coreJS, benchmark, testingJS, testsJVM, testsJS) 139 | ) 140 | 141 | lazy val iteratee = project 142 | .in(file(".")) 143 | .enablePlugins(GhpagesPlugin, ScalaUnidocPlugin) 144 | .settings(allSettings) 145 | .settings(docSettings) 146 | .settings(noPublishSettings) 147 | .aggregate(coreJVM, coreJS, files, testingJVM, testingJS, testsJVM, testsJS, benchmark) 148 | .dependsOn(coreJVM, files) 149 | 150 | lazy val core = crossProject(JSPlatform, JVMPlatform) 151 | .withoutSuffixFor(JVMPlatform) 152 | .crossType(CrossType.Pure) 153 | .in(file("core")) 154 | .settings( 155 | moduleName := "iteratee-core", 156 | name := "core" 157 | ) 158 | .settings(allSettings: _*) 159 | .settings( 160 | libraryDependencies += "org.typelevel" %%% "cats-core" % catsVersion 161 | ) 162 | .jvmSettings( 163 | mimaPreviousArtifacts := Set("io.iteratee" %% "iteratee-core" % previousIterateeVersion) 164 | ) 165 | .jsSettings(commonJsSettings: _*) 166 | 167 | lazy val coreJVM = core.jvm 168 | lazy val coreJS = core.js 169 | 170 | lazy val testing = crossProject(JSPlatform, JVMPlatform) 171 | .withoutSuffixFor(JVMPlatform) 172 | .crossType(CrossType.Full) 173 | .in(file("testing")) 174 | .settings( 175 | moduleName := "iteratee-testing", 176 | name := "testing" 177 | ) 178 | .settings(allSettings: _*) 179 | .settings( 180 | libraryDependencies ++= Seq( 181 | "org.scalacheck" %%% "scalacheck" % scalaCheckVersion, 182 | "org.scalatest" %%% "scalatest" % scalaTestVersion, 183 | "org.scalatestplus" %%% "scalacheck-1-15" % "3.2.10.0", 184 | "org.typelevel" %%% "cats-laws" % catsVersion, 185 | "org.typelevel" %%% "discipline-core" % disciplineVersion 186 | ), 187 | coverageExcludedPackages := "io\\.iteratee\\.testing\\..*" 188 | ) 189 | .jsSettings(commonJsSettings: _*) 190 | .jvmConfigure(_.dependsOn(files)) 191 | .dependsOn(core) 192 | 193 | lazy val testingJVM = testing.jvm 194 | lazy val testingJS = testing.js 195 | 196 | lazy val tests = crossProject(JSPlatform, JVMPlatform) 197 | .withoutSuffixFor(JVMPlatform) 198 | .crossType(CrossType.Full) 199 | .in(file("tests")) 200 | .configs(IntegrationTest) 201 | .settings( 202 | moduleName := "iteratee-tests", 203 | name := "tests" 204 | ) 205 | .settings(allSettings: _*) 206 | .settings(noPublishSettings: _*) 207 | .settings(Defaults.itSettings: _*) 208 | .settings(inConfig(IntegrationTest)(scalafmtConfigSettings)) 209 | .settings( 210 | libraryDependencies ++= Seq( 211 | "org.scalacheck" %%% "scalacheck" % scalaCheckVersion, 212 | "org.scalatest" %%% "scalatest" % scalaTestVersion, 213 | "org.scalatestplus" %%% "scalacheck-1-15" % "3.2.10.0", 214 | "org.typelevel" %%% "cats-laws" % catsVersion, 215 | "org.typelevel" %%% "discipline-core" % disciplineVersion 216 | ), 217 | scalacOptions ~= { 218 | _.filterNot(Set("-Yno-predef")) 219 | }, 220 | coverageExcludedPackages := "io\\.iteratee\\.tests\\..*" 221 | ) 222 | .jvmSettings( 223 | fork := false, 224 | libraryDependencies ++= Seq( 225 | "org.typelevel" %% "cats-effect-laws" % catsEffectVersion % "it" 226 | ) 227 | ) 228 | .jsSettings(commonJsSettings: _*) 229 | .jsSettings( 230 | Test / test := {} 231 | ) 232 | .jvmConfigure(_.dependsOn(files)) 233 | .dependsOn(testing) 234 | 235 | lazy val testsJVM = tests.jvm 236 | lazy val testsJS = tests.js 237 | 238 | lazy val files = project 239 | .settings( 240 | moduleName := "iteratee-files", 241 | mimaPreviousArtifacts := Set("io.iteratee" %% "iteratee-files" % previousIterateeVersion), 242 | libraryDependencies ++= Seq( 243 | "org.typelevel" %% "cats-effect" % catsEffectVersion 244 | ) 245 | ) 246 | .settings(allSettings) 247 | .dependsOn(coreJVM) 248 | 249 | lazy val benchmark = project 250 | .configs(IntegrationTest) 251 | .settings( 252 | moduleName := "iteratee-benchmark" 253 | ) 254 | .settings(allSettings ++ Defaults.itSettings) 255 | .settings(inConfig(IntegrationTest)(scalafmtConfigSettings)) 256 | .settings(noPublishSettings) 257 | .settings( 258 | libraryDependencies ++= Seq( 259 | "co.fs2" %% "fs2-core" % fs2Version, 260 | "org.scalatest" %% "scalatest" % scalaTestVersion % Test, 261 | "org.typelevel" %% "cats-free" % catsVersion 262 | ) 263 | ) 264 | .enablePlugins(JmhPlugin) 265 | .dependsOn(coreJVM, testsJVM) 266 | 267 | lazy val publishSettings = Seq( 268 | releaseCrossBuild := true, 269 | releaseVcsSign := true, 270 | releasePublishArtifactsAction := PgpKeys.publishSigned.value, 271 | homepage := Some(url("https://github.com/travisbrown/iteratee")), 272 | licenses := Seq("Apache 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), 273 | publishMavenStyle := true, 274 | Test / publishArtifact := false, 275 | pomIncludeRepository := { _ => 276 | false 277 | }, 278 | publishTo := { 279 | val nexus = "https://oss.sonatype.org/" 280 | if (isSnapshot.value) 281 | Some("snapshots".at(nexus + "content/repositories/snapshots")) 282 | else 283 | Some("releases".at(nexus + "service/local/staging/deploy/maven2")) 284 | }, 285 | autoAPIMappings := true, 286 | apiURL := Some(url("https://travisbrown.github.io/iteratee/api/")), 287 | scmInfo := Some( 288 | ScmInfo( 289 | url("https://github.com/travisbrown/iteratee"), 290 | "scm:git:git@github.com:travisbrown/iteratee.git" 291 | ) 292 | ), 293 | developers := List( 294 | Developer( 295 | "travisbrown", 296 | "Travis Brown", 297 | "travisrobertbrown@gmail.com", 298 | url("https://twitter.com/travisbrown") 299 | ) 300 | ), 301 | pomPostProcess := { (node: XmlNode) => 302 | new RuleTransformer( 303 | new RewriteRule { 304 | private def isTestScope(elem: Elem): Boolean = 305 | elem.label == "dependency" && elem.child.exists(child => child.label == "scope" && child.text == "test") 306 | 307 | override def transform(node: XmlNode): XmlNodeSeq = node match { 308 | case elem: Elem if isTestScope(elem) => Nil 309 | case _ => node 310 | } 311 | } 312 | ).transform(node).head 313 | } 314 | ) 315 | 316 | lazy val noPublishSettings = Seq( 317 | publish := {}, 318 | publishLocal := {}, 319 | publishArtifact := false 320 | ) 321 | 322 | credentials ++= ( 323 | for { 324 | username <- Option(System.getenv().get("SONATYPE_USERNAME")) 325 | password <- Option(System.getenv().get("SONATYPE_PASSWORD")) 326 | } yield Credentials( 327 | "Sonatype Nexus Repository Manager", 328 | "oss.sonatype.org", 329 | username, 330 | password 331 | ) 332 | ).toSeq 333 | 334 | val jvmProjects = Seq( 335 | "benchmark", 336 | "core", 337 | "files", 338 | "testing", 339 | "tests" 340 | ) 341 | 342 | val jsProjects = Seq( 343 | "coreJS", 344 | "testingJS", 345 | "testsJS" 346 | ) 347 | 348 | addCommandAlias("testJVM", jvmProjects.map(";" + _ + "/test").mkString) 349 | addCommandAlias( 350 | "validateJVM", 351 | ";testJVM;tests/it:test;scalafmtCheck;scalafmtSbtCheck;test:scalafmtCheck;it:scalafmtCheck;scalastyle;unidoc" 352 | ) 353 | addCommandAlias("testJS", jsProjects.map(";" + _ + "/test").mkString) 354 | addCommandAlias( 355 | "validateJS", 356 | ";testJS;scalafmtCheck;scalafmtSbtCheck;test:scalafmtCheck;it:scalafmtCheck;scalastyle;unidoc" 357 | ) 358 | addCommandAlias("validate", ";validateJVM;validateJS") 359 | -------------------------------------------------------------------------------- /core/src/main/scala/io/iteratee/Enumerator.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.{Applicative, Defer, Eval, FlatMap, Monad, MonadError, Monoid, Semigroup} 4 | import cats.kernel.Eq 5 | import io.iteratee.internal.Step 6 | import scala.Predef._ 7 | import scala.util.{Left, Right} 8 | 9 | /** 10 | * @tparam F 11 | * The effect type constructor 12 | * @tparam E 13 | * The type of the enumerated data 14 | */ 15 | abstract class Enumerator[F[_], E] extends Serializable { self => 16 | def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] 17 | 18 | final def through[I](enumeratee: Enumeratee[F, E, I])(implicit M: FlatMap[F]): Enumerator[F, I] = 19 | enumeratee.wrap(this) 20 | 21 | final def intoStep[A](s: Step[F, E, A])(implicit F: FlatMap[F]): F[A] = F.flatMap(self(s))(_.run) 22 | 23 | final def into[A](iteratee: Iteratee[F, E, A])(implicit F: FlatMap[F]): F[A] = F.flatMap(iteratee.state)(intoStep) 24 | 25 | final def map[B](f: E => B)(implicit F: Monad[F]): Enumerator[F, B] = through(Enumeratee.map(f)) 26 | 27 | final def flatMapM[B](f: E => F[B])(implicit F: Monad[F]): Enumerator[F, B] = through(Enumeratee.flatMapM(f)) 28 | 29 | final def flatMap[B](f: E => Enumerator[F, B])(implicit F: Monad[F]): Enumerator[F, B] = 30 | through(Enumeratee.flatMap(f)) 31 | 32 | final def take(n: Long)(implicit F: Monad[F]): Enumerator[F, E] = through(Enumeratee.take[F, E](n)) 33 | final def takeWhile(p: E => Boolean)(implicit F: Monad[F]): Enumerator[F, E] = through(Enumeratee.takeWhile[F, E](p)) 34 | final def takeWhileM(p: E => F[Boolean])(implicit F: Monad[F]): Enumerator[F, E] = through(Enumeratee.takeWhileM(p)) 35 | final def drop(n: Long)(implicit F: Monad[F]): Enumerator[F, E] = through(Enumeratee.drop[F, E](n)) 36 | final def dropWhile(p: E => Boolean)(implicit F: Monad[F]): Enumerator[F, E] = through(Enumeratee.dropWhile[F, E](p)) 37 | final def dropWhileM(p: E => F[Boolean])(implicit F: Monad[F]): Enumerator[F, E] = through(Enumeratee.dropWhileM(p)) 38 | final def scan[A](init: A)(f: (A, E) => A)(implicit F: Monad[F]): Enumerator[F, A] = 39 | through(Enumeratee.scan[F, E, A](init)(f)) 40 | final def scanM[A](init: A)(f: (A, E) => F[A])(implicit F: Monad[F]): Enumerator[F, A] = 41 | through(Enumeratee.scanM[F, E, A](init)(f)) 42 | final def collect[B](pf: PartialFunction[E, B])(implicit F: Monad[F]): Enumerator[F, B] = 43 | through(Enumeratee.collect[F, E, B](pf)) 44 | final def filter(p: E => Boolean)(implicit F: Monad[F]): Enumerator[F, E] = through(Enumeratee.filter[F, E](p)) 45 | final def filterM(p: E => F[Boolean])(implicit F: Monad[F]): Enumerator[F, E] = through(Enumeratee.filterM(p)) 46 | final def sequenceI[B](iteratee: Iteratee[F, E, B])(implicit F: Monad[F]): Enumerator[F, B] = 47 | through(Enumeratee.sequenceI(iteratee)) 48 | final def uniq(implicit F: Monad[F], E: Eq[E]): Enumerator[F, E] = through(Enumeratee.uniq[F, E]) 49 | final def zipWithIndex(implicit F: Monad[F]): Enumerator[F, (E, Long)] = through(Enumeratee.zipWithIndex[F, E]) 50 | final def grouped(n: Int)(implicit F: Monad[F]): Enumerator[F, Vector[E]] = through(Enumeratee.grouped[F, E](n)) 51 | final def splitOn(p: E => Boolean)(implicit F: Monad[F]): Enumerator[F, Vector[E]] = 52 | through(Enumeratee.splitOn[F, E](p)) 53 | final def cross[E2](e2: Enumerator[F, E2])(implicit F: Monad[F]): Enumerator[F, (E, E2)] = 54 | through(Enumeratee.cross(e2)) 55 | final def intersperse(delim: E)(implicit F: Monad[F]): Enumerator[F, E] = through(Enumeratee.intersperse[F, E](delim)) 56 | 57 | final def prepend(e: E)(implicit F: Monad[F]): Enumerator[F, E] = new Enumerator[F, E] { 58 | def apply[A](step: Step[F, E, A]): F[Step[F, E, A]] = 59 | if (step.isDone) self(step) else F.flatMap(step.feedEl(e))(self(_)) 60 | } 61 | 62 | final def append(e2: Enumerator[F, E])(implicit F: FlatMap[F]): Enumerator[F, E] = new Enumerator[F, E] { 63 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.flatMap(self(s))(e2(_)) 64 | } 65 | 66 | final def flatten[B](implicit M: Monad[F], ev: E =:= F[B]): Enumerator[F, B] = flatMap(e => Enumerator.liftM(ev(e))) 67 | 68 | final def bindM[G[_], B](f: E => G[Enumerator[F, B]])(implicit F: Monad[F], G: Monad[G]): F[G[Enumerator[F, B]]] = 69 | map(f).into( 70 | Iteratee.fold[F, G[Enumerator[F, B]], G[Enumerator[F, B]]](G.pure(Enumerator.empty)) { case (acc, concat) => 71 | G.flatMap(acc)(en => G.map(concat)(append => Semigroup[Enumerator[F, B]].combine(en, append))) 72 | } 73 | ) 74 | 75 | final def reduced[B](b: B)(f: (B, E) => B)(implicit F: Monad[F]): Enumerator[F, B] = new Enumerator[F, B] { 76 | final def apply[A](step: Step[F, B, A]): F[Step[F, B, A]] = 77 | F.flatMap(self(Step.fold[F, E, B](b)(f)))(next => F.flatMap(next.run)(step.feedEl)) 78 | } 79 | 80 | final def reducedM[B](b: B)(f: (B, E) => F[B])(implicit F: Monad[F]): Enumerator[F, B] = new Enumerator[F, B] { 81 | final def apply[A](step: Step[F, B, A]): F[Step[F, B, A]] = 82 | F.flatMap(self(Step.foldM[F, E, B](b)(f)))(next => F.flatMap(next.run)(step.feedEl)) 83 | } 84 | 85 | final def toVector(implicit F: Monad[F]): F[Vector[E]] = into(Iteratee.consume) 86 | 87 | final def ensure[T](action: F[Unit])(implicit F: MonadError[F, T]): Enumerator[F, E] = 88 | ensureEval(Eval.now(action)) 89 | 90 | final def ensureEval[T](action: Eval[F[Unit]])(implicit F: MonadError[F, T]): Enumerator[F, E] = 91 | new Enumerator[F, E] { 92 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.flatMap( 93 | F.handleErrorWith(self(s))(e => F.flatMap(action.value)(_ => F.raiseError(e))) 94 | )(result => F.map(action.value)(_ => result)) 95 | } 96 | 97 | final def handleErrorWith[T](f: T => Enumerator[F, E])(implicit F: MonadError[F, T]): Enumerator[F, E] = 98 | new Enumerator[F, E] { 99 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = 100 | F.handleErrorWith(self(s))(t => f(t)(s)) 101 | } 102 | } 103 | 104 | final object Enumerator { 105 | private[this] final val defaultChunkSize: Int = 1024 106 | 107 | implicit final def enumeratorMonoid[F[_], E](implicit F: Monad[F]): Monoid[Enumerator[F, E]] = 108 | new Monoid[Enumerator[F, E]] { 109 | def combine(e1: Enumerator[F, E], e2: Enumerator[F, E]): Enumerator[F, E] = e1.append(e2) 110 | def empty: Enumerator[F, E] = Enumerator.empty 111 | } 112 | 113 | private case class DeferEnumerator[F[_], E](build: () => Enumerator[F, E]) extends Enumerator[F, E] { 114 | lazy val built: Enumerator[F, E] = { 115 | @annotation.tailrec 116 | def loop(fn: () => Enumerator[F, E]): Enumerator[F, E] = 117 | fn() match { 118 | case DeferEnumerator(nextFn) => loop(nextFn) 119 | case notDefer => notDefer 120 | } 121 | loop(build) 122 | } 123 | 124 | def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = built(s) 125 | } 126 | 127 | def defer[F[_], A](e: => Enumerator[F, A]): Enumerator[F, A] = 128 | DeferEnumerator(() => e) 129 | 130 | implicit def enumeratorDefer[F[_]]: Defer[Enumerator[F, *]] = 131 | new Defer[Enumerator[F, *]] { 132 | def defer[A](e: => Enumerator[F, A]): Enumerator[F, A] = 133 | DeferEnumerator(() => e) 134 | } 135 | 136 | implicit final def enumeratorMonad[F[_]](implicit F: Monad[F]): Monad[Enumerator[F, *]] = 137 | new Monad[Enumerator[F, *]] { 138 | final override def map[A, B](fa: Enumerator[F, A])(f: A => B): Enumerator[F, B] = fa.map(f) 139 | final def flatMap[A, B](fa: Enumerator[F, A])(f: A => Enumerator[F, B]): Enumerator[F, B] = fa.flatMap(f) 140 | final def pure[E](e: E): Enumerator[F, E] = Enumerator.enumOne[F, E](e) 141 | final def tailRecM[A, B](a: A)(f: A => Enumerator[F, Either[A, B]]): Enumerator[F, B] = 142 | f(a).through(Enumeratee.tailRecM(f)) 143 | } 144 | 145 | /** 146 | * An enumerator that produces the given values. 147 | */ 148 | final def enumerate[F[_], E](xs: E*)(implicit F: Applicative[F]): Enumerator[F, E] = new Enumerator[F, E] { 149 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = s.feed(xs) 150 | } 151 | 152 | /** 153 | * Lift an effectful value into an enumerator. 154 | */ 155 | final def liftM[F[_], E](fa: F[E])(implicit F: FlatMap[F]): Enumerator[F, E] = 156 | new Enumerator[F, E] { 157 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.flatMap(fa)(s.feedEl) 158 | } 159 | 160 | /** 161 | * Lift an effectful value in a [[cats.Eval]] into an enumerator. 162 | */ 163 | final def liftMEval[F[_], E](fa: Eval[F[E]])(implicit F: FlatMap[F]): Enumerator[F, E] = 164 | new Enumerator[F, E] { 165 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.flatMap(fa.value)(s.feedEl) 166 | } 167 | 168 | /** 169 | * Create a failed enumerator with the given error. 170 | */ 171 | final def fail[F[_], T, E](e: T)(implicit F: MonadError[F, T]): Enumerator[F, E] = 172 | Enumerator.liftM(F.raiseError[E](e)) 173 | 174 | /** 175 | * An empty enumerator. 176 | */ 177 | final def empty[F[_], E](implicit F: Applicative[F]): Enumerator[F, E] = 178 | new Enumerator[F, E] { 179 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.pure(s) 180 | } 181 | 182 | /** 183 | * An enumerator that forces the evaluation of an effect when it is consumed. 184 | */ 185 | final def perform[F[_], E, B](f: F[B])(implicit F: FlatMap[F]): Enumerator[F, E] = 186 | new Enumerator[F, E] { 187 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.map(f)(_ => s) 188 | } 189 | 190 | private[iteratee] case class EnumOne[F[_], E](e: E, app: Applicative[F]) extends Enumerator[F, E] { 191 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = s.feedEl(e) 192 | } 193 | 194 | /** 195 | * An enumerator that produces a single value. 196 | */ 197 | final def enumOne[F[_]: Applicative, E](e: E): Enumerator[F, E] = 198 | EnumOne[F, E](e, implicitly) 199 | 200 | /** 201 | * An enumerator that either produces a single value or fails. 202 | */ 203 | final def enumEither[F[_], T, E](either: Either[T, E])(implicit F: MonadError[F, T]): Enumerator[F, E] = 204 | either match { 205 | case Right(a) => enumOne(a) 206 | case Left(t) => fail(t) 207 | } 208 | 209 | private[this] abstract class ChunkedIteratorEnumerator[F[_], E](implicit F: Monad[F]) extends Enumerator[F, E] { 210 | protected[this] def chunks: Iterator[Seq[E]] 211 | 212 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.tailRecM((s, chunks)) { case (step, it) => 213 | if (it.isEmpty || step.isDone) F.pure(Right(step)) 214 | else { 215 | F.map(step.feed(it.next()))(s => Left((s, it))) 216 | } 217 | } 218 | } 219 | 220 | /** 221 | * An enumerator that produces values from an iterable collection. 222 | */ 223 | final def enumIterable[F[_]: Monad, E](xs: Iterable[E], chunkSize: Int = defaultChunkSize): Enumerator[F, E] = 224 | new ChunkedIteratorEnumerator[F, E] { 225 | final protected[this] def chunks: Iterator[Seq[E]] = xs.grouped(math.max(chunkSize, 1)).map(_.toSeq) 226 | } 227 | 228 | /** 229 | * An enumerator that produces values from a stream. 230 | */ 231 | final def enumStream[F[_]: Monad, E](xs: Stream[E], chunkSize: Int = defaultChunkSize): Enumerator[F, E] = 232 | new ChunkedIteratorEnumerator[F, E] { 233 | final protected[this] def chunks: Iterator[Seq[E]] = xs.grouped(math.max(chunkSize, 1)) 234 | } 235 | 236 | /** 237 | * An enumerator that produces values from a list. 238 | */ 239 | final def enumList[F[_], E](xs: List[E])(implicit F: Applicative[F]): Enumerator[F, E] = 240 | new Enumerator[F, E] { 241 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = s.feed(xs) 242 | } 243 | 244 | /** 245 | * An enumerator that produces values from a vector. 246 | */ 247 | final def enumVector[F[_], E](xs: Vector[E])(implicit F: Applicative[F]): Enumerator[F, E] = 248 | new Enumerator[F, E] { 249 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = s.feed(xs) 250 | } 251 | 252 | /** 253 | * An enumerator that produces values from a slice of an indexed sequence. 254 | */ 255 | final def enumIndexedSeq[F[_], E]( 256 | xs: IndexedSeq[E], 257 | min: Int = 0, 258 | max: Int = Int.MaxValue 259 | )(implicit F: Applicative[F]): Enumerator[F, E] = 260 | new Enumerator[F, E] { 261 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = s.feed(xs.slice(min, max)) 262 | } 263 | 264 | /** 265 | * An enumerator that repeats the given value indefinitely. 266 | */ 267 | final def repeat[F[_], E](e: E)(implicit F: Monad[F]): Enumerator[F, E] = new Enumerator[F, E] { self => 268 | final def apply[A](step: Step[F, E, A]): F[Step[F, E, A]] = F.tailRecM(step) { s => 269 | if (s.isDone) F.pure(Right(s)) else F.map(s.feedEl(e))(Left(_)) 270 | } 271 | } 272 | 273 | /** 274 | * An enumerator that iteratively performs an operation and returns the results. 275 | */ 276 | final def iterate[F[_], E](init: E)(f: E => E)(implicit F: Monad[F]): Enumerator[F, E] = 277 | new Enumerator[F, E] { 278 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.tailRecM((s, init)) { case (step, last) => 279 | if (step.isDone) F.pure(Right(step)) 280 | else { 281 | F.map(step.feedEl(last))(s1 => Left((s1, f(last)))) 282 | } 283 | } 284 | } 285 | 286 | /** 287 | * An enumerator that iteratively performs an effectful operation and returns the results. 288 | */ 289 | final def iterateM[F[_], E](init: E)(f: E => F[E])(implicit F: Monad[F]): Enumerator[F, E] = 290 | new Enumerator[F, E] { 291 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.tailRecM((s, init)) { case (step, last) => 292 | if (step.isDone) F.pure(Right(step)) 293 | else { 294 | F.map2(step.feedEl(last), f(last))((s1, next) => Left((s1, next))) 295 | } 296 | } 297 | } 298 | 299 | /** 300 | * An enumerator that iteratively performs an operation until `None` is generated and returns the results. 301 | */ 302 | final def iterateUntil[F[_], E](init: E)(f: E => Option[E])(implicit F: Monad[F]): Enumerator[F, E] = 303 | new Enumerator[F, E] { 304 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.tailRecM((s, Some(init): Option[E])) { 305 | case (step, Some(last)) if !step.isDone => F.map(step.feedEl(last))(s1 => Left((s1, f(last)))) 306 | case (step, _) => F.pure(Right(step)) 307 | } 308 | } 309 | 310 | /** 311 | * An enumerator that iteratively performs an effectful operation until `None` is generated and returns the results. 312 | */ 313 | final def iterateUntilM[F[_], E](init: E)(f: E => F[Option[E]])(implicit F: Monad[F]): Enumerator[F, E] = 314 | new Enumerator[F, E] { 315 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.tailRecM((s, Some(init): Option[E])) { 316 | case (step, Some(last)) if !step.isDone => 317 | F.map2(step.feedEl(last), f(last))((s1, next) => Left((s1, next))) 318 | case (step, _) => F.pure(Right(step)) 319 | } 320 | } 321 | 322 | /** 323 | * An enumerator that returns the result of an effectful operation until `None` is generated. 324 | */ 325 | final def generateM[F[_], E](f: F[Option[E]])(implicit F: Monad[F]): Enumerator[F, E] = 326 | new Enumerator[F, E] { 327 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = F.tailRecM(s) { step => 328 | if (step.isDone) F.pure(Right(step)) 329 | else 330 | F.flatMap(f) { 331 | case Some(e) => F.map(step.feedEl(e))(Left(_)) 332 | case None => F.pure(Right(step)) 333 | } 334 | } 335 | } 336 | 337 | /** 338 | * Enumerators that rely on `F` to provide stack safety. 339 | * 340 | * These implementations will generally be more efficient than the default ones, but will not be stack safe unless 341 | * recursive monadic binding in `F` is stack safe. 342 | */ 343 | final object StackUnsafe { 344 | private[this] abstract class ChunkedIteratorEnumerator[F[_], E](implicit F: Monad[F]) extends Enumerator[F, E] { 345 | def chunks: Iterator[Vector[E]] 346 | 347 | private[this] final def go[A](it: Iterator[Vector[E]], step: Step[F, E, A]): F[Step[F, E, A]] = 348 | if (it.isEmpty || step.isDone) F.pure(step) 349 | else { 350 | val next = it.next() 351 | 352 | F.flatMap(step.feed(next))(go(it, _)) 353 | } 354 | 355 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = go(chunks, s) 356 | } 357 | 358 | /** 359 | * An enumerator that produces values from a stream. 360 | */ 361 | final def enumStream[F[_]: Monad, E](xs: Stream[E], chunkSize: Int = defaultChunkSize): Enumerator[F, E] = 362 | new ChunkedIteratorEnumerator[F, E] { 363 | final def chunks: Iterator[Vector[E]] = xs.grouped(chunkSize).map(_.toVector) 364 | } 365 | 366 | /** 367 | * An enumerator that repeats the given value indefinitely. 368 | * 369 | * Note that this implementation will only be stack safe if recursive monadic binding in `F` is stack safe. 370 | */ 371 | final def repeat[F[_], E](e: E)(implicit F: Monad[F]): Enumerator[F, E] = 372 | new Enumerator[F, E] { 373 | final def apply[A](step: Step[F, E, A]): F[Step[F, E, A]] = 374 | if (step.isDone) F.pure(step) else F.flatMap(step.feedEl(e))(apply) 375 | } 376 | 377 | /** 378 | * An enumerator that iteratively performs an operation and returns the results. 379 | * 380 | * Note that this implementation will only be stack safe if recursive monadic binding in `F` is stack safe. 381 | */ 382 | def iterate[F[_], E](init: E)(f: E => E)(implicit F: Monad[F]): Enumerator[F, E] = 383 | new Enumerator[F, E] { 384 | private[this] def loop[A](step: Step[F, E, A], last: E): F[Step[F, E, A]] = 385 | if (step.isDone) F.pure(step) else F.flatMap(step.feedEl(last))(loop(_, f(last))) 386 | 387 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = loop(s, init) 388 | } 389 | 390 | /** 391 | * An enumerator that iteratively performs an effectful operation and returns the results. 392 | * 393 | * Note that this implementation will only be stack safe if recursive monadic binding in `F` is stack safe. 394 | */ 395 | def iterateM[F[_], E](init: E)(f: E => F[E])(implicit F: Monad[F]): Enumerator[F, E] = 396 | new Enumerator[F, E] { 397 | private[this] def loop[A](step: Step[F, E, A], last: E): F[Step[F, E, A]] = 398 | if (step.isDone) F.pure(step) else F.flatten(F.map2(step.feedEl(last), f(last))(loop)) 399 | 400 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = loop(s, init) 401 | } 402 | 403 | /** 404 | * An enumerator that iteratively performs an operation until `None` is generated and returns the results. 405 | * 406 | * Note that this implementation will only be stack safe if recursive monadic binding in `F` is stack safe. 407 | */ 408 | def iterateUntil[F[_], E](init: E)(f: E => Option[E])(implicit F: Monad[F]): Enumerator[F, E] = 409 | new Enumerator[F, E] { 410 | private[this] def loop[A](step: Step[F, E, A], last: Option[E]): F[Step[F, E, A]] = 411 | last match { 412 | case Some(last) if !step.isDone => F.flatMap(step.feedEl(last))(loop(_, f(last))) 413 | case _ => F.pure(step) 414 | } 415 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = loop(s, Some(init)) 416 | } 417 | 418 | /** 419 | * An enumerator that iteratively performs an effectful operation until `None` is generated and returns the results. 420 | * 421 | * Note that this implementation will only be stack safe if recursive monadic binding in `F` is stack safe. 422 | */ 423 | def iterateUntilM[F[_], E](init: E)(f: E => F[Option[E]])(implicit F: Monad[F]): Enumerator[F, E] = 424 | new Enumerator[F, E] { 425 | private[this] def loop[A](step: Step[F, E, A], last: Option[E]): F[Step[F, E, A]] = 426 | last match { 427 | case Some(last) if !step.isDone => F.flatten(F.map2(step.feedEl(last), f(last))(loop)) 428 | case _ => F.pure(step) 429 | } 430 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = loop(s, Some(init)) 431 | } 432 | 433 | /** 434 | * An enumerator that returns the result of an effectful operation until `None` is generated. 435 | * 436 | * Note that this implementation will only be stack safe if recursive monadic binding in `F` is stack safe. 437 | */ 438 | def generateM[F[_], E](f: F[Option[E]])(implicit F: Monad[F]): Enumerator[F, E] = 439 | new Enumerator[F, E] { 440 | final def apply[A](s: Step[F, E, A]): F[Step[F, E, A]] = 441 | if (s.isDone) F.pure(s) 442 | else 443 | F.flatMap(f) { 444 | case None => F.pure(s) 445 | case Some(e) => F.flatMap(s.feedEl(e))(apply) 446 | } 447 | } 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /core/src/main/scala/io/iteratee/Iteratee.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.{Applicative, Comonad, Contravariant, Eval, Functor, Monad, MonadError, Monoid, MonoidK, Semigroup} 4 | import cats.arrow.FunctionK 5 | import cats.data.NonEmptyList 6 | import io.iteratee.internal.Step 7 | 8 | /** 9 | * An iteratee processes a stream of elements of type `E` and returns a value of type `F[A]`. 10 | * 11 | * @see 12 | * [[io.iteratee.internal.Step]] 13 | * 14 | * @tparam F 15 | * A type constructor representing a context for effects 16 | * @tparam E 17 | * The type of the input data 18 | * @tparam A 19 | * The type of the calculated result 20 | */ 21 | sealed class Iteratee[F[_], E, A] private[iteratee] (final val state: F[Step[F, E, A]]) extends Serializable { self => 22 | 23 | /** 24 | * Advance this [[Iteratee]] with the given [[Enumerator]]. 25 | */ 26 | final def apply(enumerator: Enumerator[F, E])(implicit F: Monad[F]): Iteratee[F, E, A] = 27 | Iteratee.iteratee(F.flatMap(state)(enumerator(_))) 28 | 29 | /** 30 | * Reduce this [[Iteratee]] to an effectful value using the given functions. 31 | */ 32 | final def fold[Z](ifCont: (NonEmptyList[E] => Iteratee[F, E, A]) => Z, ifDone: (A, List[E]) => Z)(implicit 33 | F: Functor[F] 34 | ): F[Z] = F.map(state)(_.fold(f => ifCont(in => Iteratee.iteratee(f(in))), ifDone)) 35 | 36 | /** 37 | * Run this iteratee and close the stream so that it must produce an effectful value. 38 | */ 39 | final def run(implicit F: Monad[F]): F[A] = F.flatMap(state)(_.run) 40 | 41 | /** 42 | * Map a function over the result of this [[Iteratee]]. 43 | */ 44 | final def map[B](f: A => B)(implicit F: Functor[F]): Iteratee[F, E, B] = Iteratee.iteratee(F.map(state)(_.map(f))) 45 | 46 | /** 47 | * Replace the result of this [[Iteratee]]. 48 | */ 49 | final def as[B](b: B)(implicit F: Functor[F]): Iteratee[F, E, B] = Iteratee.iteratee(F.map(state)(_.as(b))) 50 | 51 | /** 52 | * Map a monadic function over the result of this [[Iteratee]]. 53 | */ 54 | final def flatMapM[B](f: A => F[B])(implicit F: Monad[F]): Iteratee[F, E, B] = 55 | Iteratee.iteratee(F.flatMap(state)(_.bind(a => F.map(f(a))(Step.done[F, E, B](_))))) 56 | 57 | /** 58 | * Map a function returning an [[Iteratee]] over the result. 59 | */ 60 | final def flatMap[B](f: A => Iteratee[F, E, B])(implicit F: Monad[F]): Iteratee[F, E, B] = 61 | Iteratee.iteratee(F.flatMap(state)(_.bind(a => f(a).state))) 62 | 63 | /** 64 | * Transform the inputs to this [[Iteratee]]. 65 | */ 66 | final def contramap[E2](f: E2 => E)(implicit F: Functor[F]): Iteratee[F, E2, A] = 67 | Iteratee.iteratee(F.map(state)(_.contramap(f))) 68 | 69 | /** 70 | * Create a new [[Iteratee]] that first processes values with the given [[Enumeratee]]. 71 | */ 72 | final def through[O](enumeratee: Enumeratee[F, O, E])(implicit F: Monad[F]): Iteratee[F, O, A] = 73 | Iteratee.joinI(Iteratee.iteratee(F.flatMap(state)(enumeratee(_)))) 74 | 75 | /** 76 | * Transform the context of this [[Iteratee]]. 77 | */ 78 | final def mapI[G[_]: Applicative](f: FunctionK[F, G])(implicit F: Applicative[F]): Iteratee[G, E, A] = 79 | Iteratee.iteratee(f(F.map(state)(_.mapI(f)))) 80 | 81 | /** 82 | * Lift this [[Iteratee]] into a different context. 83 | */ 84 | final def up[G[_]](implicit G: Applicative[G], F: Comonad[F], F0: Applicative[F]): Iteratee[G, E, A] = mapI( 85 | new FunctionK[F, G] { 86 | final def apply[A](a: F[A]): G[A] = G.pure(F.extract(a)) 87 | } 88 | ) 89 | 90 | /** 91 | * Zip this [[Iteratee]] with another to create an iteratee that returns a pair of their results. 92 | */ 93 | final def zip[B](other: Iteratee[F, E, B])(implicit F: Applicative[F]): Iteratee[F, E, (A, B)] = 94 | Iteratee.iteratee(F.map2(self.state, other.state)(_.zip(_))) 95 | 96 | /** 97 | * If this [[Iteratee]] has failed, use the provided function to recover. 98 | */ 99 | final def handleErrorWith[T](f: T => Iteratee[F, E, A])(implicit F: MonadError[F, T]): Iteratee[F, E, A] = 100 | Iteratee.iteratee(F.handleErrorWith(state)(e => f(e).state)) 101 | 102 | /** 103 | * Create a new [[Iteratee]] that throws away the value this one returns. 104 | */ 105 | final def discard(implicit F: Functor[F]): Iteratee[F, E, Unit] = as(()) 106 | 107 | /** 108 | * Ensure that an action will be performed when this iteratee is done, whether or not it succeeds. 109 | */ 110 | final def ensure[T](action: F[Unit])(implicit F: MonadError[F, T]): Iteratee[F, E, A] = 111 | ensureEval(Eval.now(action)) 112 | 113 | /** 114 | * Ensure that an action will be performed when this iteratee is done, whether or not it succeeds. 115 | */ 116 | final def ensureEval[T](action: Eval[F[Unit]])(implicit F: MonadError[F, T]): Iteratee[F, E, A] = 117 | handleErrorWith[T](_ => Iteratee.iteratee(F.flatMap(action.value)(_ => state))).flatMapM(result => 118 | F.map(action.value)(_ => result) 119 | ) 120 | } 121 | 122 | private[iteratee] class IterateeLowPriorityInstances { 123 | protected[this] class IterateeMonad[F[_], E](implicit F: Monad[F]) extends Monad[Iteratee[F, E, *]] { 124 | final def pure[A](a: A): Iteratee[F, E, A] = Iteratee.fromStep(Step.done[F, E, A](a)) 125 | override final def map[A, B](fa: Iteratee[F, E, A])(f: A => B): Iteratee[F, E, B] = fa.map(f) 126 | final def flatMap[A, B](fa: Iteratee[F, E, A])(f: A => Iteratee[F, E, B]): Iteratee[F, E, B] = fa.flatMap(f) 127 | 128 | final def tailRecM[A, B](a: A)(f: A => Iteratee[F, E, Either[A, B]]): Iteratee[F, E, B] = Iteratee.fromStep( 129 | Step.tailRecM[F, E, A, B](a)(f(_).state) 130 | ) 131 | } 132 | 133 | implicit final def iterateeMonad[F[_], E](implicit F: Monad[F]): Monad[Iteratee[F, E, *]] = new IterateeMonad[F, E] 134 | } 135 | 136 | /** 137 | * @groupname Constructors 138 | * Constructors 139 | * @groupprio Constructors 140 | * 0 141 | * 142 | * @groupname Utilities 143 | * Miscellaneous utilities 144 | * @groupprio Utilities 145 | * 1 146 | * 147 | * @groupname Collection 148 | * Collection operation iteratees 149 | * @groupprio Collection 150 | * 2 151 | */ 152 | final object Iteratee extends IterateeLowPriorityInstances { 153 | implicit final def iterateeContravariant[F[_], A](implicit F: Monad[F]): Contravariant[Iteratee[F, *, A]] = 154 | new Contravariant[Iteratee[F, *, A]] { 155 | def contramap[E, E2](r: Iteratee[F, E, A])(f: E2 => E) = r.contramap(f) 156 | } 157 | 158 | implicit final def iterateeMonadError[F[_], T, E](implicit F: MonadError[F, T]): MonadError[Iteratee[F, E, *], T] = 159 | new IterateeMonadError[F, T, E] 160 | 161 | private[this] class IterateeMonadError[F[_], T, E](implicit F: MonadError[F, T]) 162 | extends IterateeMonad[F, E] 163 | with MonadError[Iteratee[F, E, *], T] { 164 | final def raiseError[A](e: T): Iteratee[F, E, A] = Iteratee.fail(e)(F) 165 | final def handleErrorWith[A](fa: Iteratee[F, E, A])(f: T => Iteratee[F, E, A]): Iteratee[F, E, A] = 166 | fa.handleErrorWith(f)(F) 167 | } 168 | 169 | /** 170 | * Create an incomplete [[Iteratee]] that will use the given function to process the next input. 171 | * 172 | * @group Constructors 173 | */ 174 | final def cont[F[_]: Applicative, E, A]( 175 | ifInput: NonEmptyList[E] => Iteratee[F, E, A], 176 | ifEnd: F[A] 177 | ): Iteratee[F, E, A] = fromStep(Step.cont(es => ifInput(es).state, ifEnd)) 178 | 179 | /** 180 | * Create a new completed [[Iteratee]] with the given result. 181 | * 182 | * @group Constructors 183 | */ 184 | final def done[F[_]: Applicative, E, A](value: A): Iteratee[F, E, A] = fromStep(Step.done(value)) 185 | 186 | /** 187 | * Create an [[Iteratee]] from a [[io.iteratee.internal.Step]] in a context. 188 | * 189 | * @group Utilities 190 | */ 191 | final def iteratee[F[_], E, A](s: F[Step[F, E, A]]): Iteratee[F, E, A] = new Iteratee[F, E, A](s) 192 | 193 | /** 194 | * Create an [[Iteratee]] from a [[io.iteratee.internal.Step]]. 195 | * 196 | * @group Utilities 197 | */ 198 | final def fromStep[F[_], E, A](s: Step[F, E, A])(implicit F: Applicative[F]): Iteratee[F, E, A] = 199 | iteratee(F.pure(s)) 200 | 201 | /** 202 | * Lift an effectful value into an iteratee. 203 | * 204 | * @group Utilities 205 | */ 206 | final def liftM[F[_], E, A](fa: F[A])(implicit F: Monad[F]): Iteratee[F, E, A] = 207 | iteratee(Step.liftM(fa)) 208 | 209 | /** 210 | * Lift an effectful value into an iteratee. 211 | * 212 | * @group Utilities 213 | */ 214 | final def liftMEval[F[_], E, A](fa: Eval[F[A]])(implicit F: Monad[F]): Iteratee[F, E, A] = 215 | iteratee(Step.liftMEval(fa)) 216 | 217 | /** 218 | * Create a failed iteratee with the given error. 219 | * 220 | * @group Utilities 221 | */ 222 | final def fail[F[_], T, E, A](e: T)(implicit F: MonadError[F, T]): Iteratee[F, E, A] = liftM(F.raiseError[A](e)) 223 | 224 | /** 225 | * An iteratee that reads nothing from a stream. 226 | * 227 | * @group Utilities 228 | */ 229 | final def identity[F[_]: Applicative, E]: Iteratee[F, E, Unit] = done[F, E, Unit](()) 230 | 231 | /** 232 | * Collapse an [[Iteratee]] returning a [[io.iteratee.internal.Step]] into one layer. 233 | * 234 | * @group Utilities 235 | */ 236 | final def joinI[F[_], E, I, B](it: Iteratee[F, E, Step[F, I, B]])(implicit F: Monad[F]): Iteratee[F, E, B] = 237 | iteratee(F.flatMap(it.state)(Step.joinI(_))) 238 | 239 | /** 240 | * An [[Iteratee]] that folds a stream using an initial value and an accumulation function. 241 | * 242 | * @group Collection 243 | */ 244 | final def fold[F[_]: Applicative, E, A](init: A)(f: (A, E) => A): Iteratee[F, E, A] = 245 | Iteratee.fromStep(Step.fold[F, E, A](init)(f)) 246 | 247 | /** 248 | * An [[Iteratee]] that folds a stream using an initial value and a monadic accumulation function. 249 | * 250 | * @group Collection 251 | */ 252 | final def foldM[F[_], E, A](init: A)(f: (A, E) => F[A])(implicit F: Monad[F]): Iteratee[F, E, A] = 253 | Iteratee.fromStep(Step.foldM[F, E, A](init)(f)) 254 | 255 | /** 256 | * An [[Iteratee]] that collects all the elements in a stream in a vector. 257 | * 258 | * @group Collection 259 | */ 260 | final def consume[F[_]: Applicative, A]: Iteratee[F, A, Vector[A]] = fromStep(Step.consume[F, A]) 261 | 262 | /** 263 | * An [[Iteratee]] that collects all the elements in a stream in a given collection type. 264 | * 265 | * @group Collection 266 | */ 267 | final def consumeIn[F[_]: Applicative, A, C[_]: Applicative: MonoidK]: Iteratee[F, A, C[A]] = 268 | fromStep(Step.consumeIn[F, A, C]) 269 | 270 | /** 271 | * An [[Iteratee]] that returns the first value in a stream. 272 | * 273 | * @group Collection 274 | */ 275 | final def head[F[_]: Applicative, E]: Iteratee[F, E, Option[E]] = fromStep(Step.head[F, E]) 276 | 277 | /** 278 | * An [[Iteratee]] that returns the first value in a stream without consuming it. 279 | * 280 | * @group Collection 281 | */ 282 | final def peek[F[_]: Applicative, E]: Iteratee[F, E, Option[E]] = fromStep(Step.peek[F, E]) 283 | 284 | /** 285 | * An [[Iteratee]] that returns the last value in a stream. 286 | * 287 | * @group Collection 288 | */ 289 | final def last[F[_]: Applicative, E]: Iteratee[F, E, Option[E]] = fromStep(Step.last[F, E]) 290 | 291 | /** 292 | * An [[Iteratee]] that returns a given number of the first values in a stream. 293 | * 294 | * @group Collection 295 | */ 296 | final def take[F[_]: Applicative, A](n: Int): Iteratee[F, A, Vector[A]] = fromStep(Step.take[F, A](n)) 297 | 298 | /** 299 | * An [[Iteratee]] that returns values from a stream as long as they satisfy the given predicate. 300 | * 301 | * @group Collection 302 | */ 303 | final def takeWhile[F[_]: Applicative, A](p: A => Boolean): Iteratee[F, A, Vector[A]] = 304 | fromStep(Step.takeWhile[F, A](p)) 305 | 306 | /** 307 | * An [[Iteratee]] that drops a given number of the values from a stream. 308 | * 309 | * @group Collection 310 | */ 311 | final def drop[F[_]: Applicative, E](n: Int): Iteratee[F, E, Unit] = fromStep(Step.drop[F, E](n)) 312 | 313 | /** 314 | * An [[Iteratee]] that drops values from a stream as long as they satisfy the given predicate. 315 | * 316 | * @group Collection 317 | */ 318 | final def dropWhile[F[_]: Applicative, E](p: E => Boolean): Iteratee[F, E, Unit] = 319 | fromStep(Step.dropWhile[F, E](p)) 320 | 321 | /** 322 | * An [[Iteratee]] that collects all inputs in reverse order. 323 | * 324 | * @group Collection 325 | */ 326 | final def reversed[F[_]: Applicative, A]: Iteratee[F, A, List[A]] = 327 | fold[F, A, List[A]](Nil)((acc, e) => e :: acc) 328 | 329 | /** 330 | * An [[Iteratee]] that counts the number of values in a stream. 331 | * 332 | * @group Collection 333 | */ 334 | final def length[F[_]: Applicative, E]: Iteratee[F, E, Long] = fromStep(Step.length[F, E]) 335 | 336 | /** 337 | * An [[Iteratee]] that combines values using an [[cats.Monoid]] instance. 338 | * 339 | * @group Collection 340 | */ 341 | final def sum[F[_]: Applicative, E: Monoid]: Iteratee[F, E, E] = fromStep(Step.sum[F, E]) 342 | 343 | /** 344 | * An [[Iteratee]] that combines values using a function to a type with a [[cats.Monoid]] instance. 345 | * 346 | * @group Collection 347 | */ 348 | final def foldMap[F[_]: Applicative, E, A: Monoid](f: E => A): Iteratee[F, E, A] = Iteratee.fromStep(Step.foldMap(f)) 349 | 350 | /** 351 | * An [[Iteratee]] that combines values using an effectful function to a type with a [[cats.Monoid]] instance. 352 | * 353 | * @group Collection 354 | */ 355 | final def foldMapM[F[_]: Applicative, E, A: Monoid](f: E => F[A]): Iteratee[F, E, A] = 356 | Iteratee.fromStep(Step.foldMapM(f)) 357 | 358 | /** 359 | * An [[Iteratee]] that combines values using a function to a type with a [[cats.Semigroup]] instance. 360 | * 361 | * @group Collection 362 | */ 363 | final def foldMapOption[F[_], E, A](f: E => A)(implicit 364 | F: Applicative[F], 365 | A: Semigroup[A] 366 | ): Iteratee[F, E, Option[A]] = 367 | foldMap[F, E, Option[A]](e => Some(f(e)))(F, cats.kernel.instances.option.catsKernelStdMonoidForOption(A)) 368 | 369 | /** 370 | * An [[Iteratee]] that combines values using an effectful function to a type with a [[cats.Semigroup]] instance. 371 | * 372 | * @group Collection 373 | */ 374 | final def foldMapMOption[F[_], E, A](f: E => F[A])(implicit 375 | F: Applicative[F], 376 | A: Semigroup[A] 377 | ): Iteratee[F, E, Option[A]] = 378 | foldMapM[F, E, Option[A]](e => F.map(f(e))(Some(_)))( 379 | F, 380 | cats.kernel.instances.option.catsKernelStdMonoidForOption(A) 381 | ) 382 | 383 | /** 384 | * An [[Iteratee]] that checks if the stream is at its end. 385 | * 386 | * @group Collection 387 | */ 388 | final def isEnd[F[_]: Applicative, E]: Iteratee[F, E, Boolean] = Iteratee.fromStep(Step.isEnd) 389 | 390 | /** 391 | * An [[Iteratee]] that runs a function for its side effects. 392 | */ 393 | def foreach[F[_]: Applicative, A](f: A => Unit): Iteratee[F, A, Unit] = fold(())((_, a) => f(a)) 394 | 395 | /** 396 | * An [[Iteratee]] that runs an effectful function for its side effects. 397 | */ 398 | def foreachM[F[_]: Monad, A](f: A => F[Unit]): Iteratee[F, A, Unit] = foldM(())((_, a) => f(a)) 399 | } 400 | -------------------------------------------------------------------------------- /core/src/main/scala/io/iteratee/internal/Step.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | package internal 3 | 4 | import cats.{Applicative, Eval, Monad, Monoid, MonoidK, Semigroup} 5 | import cats.arrow.FunctionK 6 | import cats.data.NonEmptyList 7 | 8 | /** 9 | * Represents the current state of an [[io.iteratee.Iteratee]]. 10 | * 11 | * @tparam F 12 | * The effect type constructor 13 | * @tparam E 14 | * The type of the input data 15 | * @tparam A 16 | * The type of the result calculated by the [[io.iteratee.Iteratee]] 17 | */ 18 | sealed abstract class Step[F[_], E, A] extends Serializable { 19 | 20 | /** 21 | * Reduce this [[Step]] to a value using the given functions. 22 | */ 23 | def fold[Z](ifCont: (NonEmptyList[E] => F[Step[F, E, A]]) => Z, ifDone: (A, List[E]) => Z): Z 24 | 25 | def isDone: Boolean 26 | 27 | /** 28 | * Run this [[Step]] so that it produces a value in an effectful context. 29 | */ 30 | def run: F[A] 31 | 32 | /** 33 | * Feed a chunk (possibly empty) to this [[Step]]. 34 | */ 35 | def feed(chunk: Seq[E]): F[Step[F, E, A]] 36 | 37 | /** 38 | * Feed a single element to this [[Step]]. 39 | * 40 | * Must be consistent with `feed` and `feedNonEmpty`. 41 | */ 42 | def feedEl(e: E): F[Step[F, E, A]] 43 | 44 | /** 45 | * Feed a chunk that is known to be non-empty to this [[Step]]. 46 | * 47 | * Note that this method is unsafe! If you do not know that the chunk contains at least one element, you must call 48 | * `feed` instead. 49 | */ 50 | protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, A]] 51 | 52 | /** 53 | * Map a function over the value of this [[Step]]. 54 | */ 55 | def map[B](f: A => B): Step[F, E, B] 56 | 57 | /** 58 | * Replace the value of this [[Step]]. 59 | */ 60 | def as[B](b: B): Step[F, E, B] 61 | 62 | /** 63 | * Map a function over the inputs of this [[Step]]. 64 | */ 65 | def contramap[E2](f: E2 => E): Step[F, E2, A] 66 | 67 | /** 68 | * Transform the context of this [[Step]]. 69 | */ 70 | def mapI[G[_]: Applicative](f: FunctionK[F, G]): Step[G, E, A] 71 | 72 | /** 73 | * Map a function returning a [[Step]] in a monadic context over the value of this [[Step]] and flatten the result. 74 | */ 75 | def bind[B](f: A => F[Step[F, E, B]])(implicit M: Monad[F]): F[Step[F, E, B]] 76 | 77 | /** 78 | * Zip this [[Step]] with another. 79 | */ 80 | def zip[B](other: Step[F, E, B]): Step[F, E, (A, B)] 81 | } 82 | 83 | /** 84 | * @groupname Constructors 85 | * Constructors 86 | * @groupprio Constructors 87 | * 0 88 | * 89 | * @groupname Utilities 90 | * Miscellaneous utilities 91 | * @groupprio Utilities 92 | * 1 93 | * 94 | * @groupname Collection 95 | * Collection operation steps 96 | * @groupprio Collection 97 | * 2 98 | */ 99 | final object Step { self => 100 | private[this] final class Done[F[_], E, A](val value: A)(private[internal] val remaining: Seq[E])(implicit 101 | F: Applicative[F] 102 | ) extends Step[F, E, A] { 103 | final def fold[Z](ifCont: (NonEmptyList[E] => F[Step[F, E, A]]) => Z, ifDone: (A, List[E]) => Z): Z = 104 | ifDone(value, remaining.toList) 105 | 106 | final def isDone: Boolean = true 107 | final def run: F[A] = F.pure(value) 108 | final def feed(chunk: Seq[E]): F[Step[F, E, A]] = F.pure(doneWithLeftovers(value, remaining ++ chunk)) 109 | final def feedEl(e: E): F[Step[F, E, A]] = F.pure(doneWithLeftovers(value, remaining :+ e)) 110 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, A]] = 111 | F.pure(doneWithLeftovers(value, remaining ++ chunk)) 112 | 113 | final def map[B](f: A => B): Step[F, E, B] = doneWithLeftovers(f(value), remaining) 114 | final def as[B](b: B): Step[F, E, B] = doneWithLeftovers(b, remaining) 115 | final def contramap[E2](f: E2 => E): Step[F, E2, A] = done(value) 116 | final def mapI[G[_]: Applicative](f: FunctionK[F, G]): Step[G, E, A] = doneWithLeftovers(value, remaining) 117 | 118 | final def bind[B](f: A => F[Step[F, E, B]])(implicit M: Monad[F]): F[Step[F, E, B]] = 119 | M.flatMap(f(value)) { next => 120 | if (next.isDone) { 121 | F.pure(doneWithLeftovers(next.asInstanceOf[Done[F, E, B]].value, remaining)) 122 | } else { 123 | val c = remaining.lengthCompare(1) 124 | 125 | if (c < 0) F.pure(next) else if (c == 0) next.feedEl(remaining(0)) else next.feedNonEmpty(remaining) 126 | } 127 | } 128 | 129 | final def zip[B](other: Step[F, E, B]): Step[F, E, (A, B)] = if (other.isDone) { 130 | val asDone = other.asInstanceOf[Done[F, E, B]] 131 | val newRemaining = if (remaining.size <= asDone.remaining.size) remaining else asDone.remaining 132 | 133 | doneWithLeftovers((value, asDone.value), newRemaining) 134 | } else { 135 | other.map((value, _)) 136 | } 137 | } 138 | 139 | private[internal] abstract class BaseCont[F[_], E, A](implicit F: Applicative[F]) extends Step[F, E, A] { self => 140 | final def fold[Z](ifCont: (NonEmptyList[E] => F[Step[F, E, A]]) => Z, ifDone: (A, List[E]) => Z): Z = 141 | ifCont { nev => 142 | if (nev.tail.isEmpty) feedEl(nev.head) else feedNonEmpty(nev.toList) 143 | } 144 | 145 | final def isDone: Boolean = false 146 | 147 | final def feed(chunk: Seq[E]): F[Step[F, E, A]] = { 148 | val c = chunk.lengthCompare(1) 149 | 150 | if (c < 0) F.pure(this) else if (c == 0) feedEl(chunk(0)) else feedNonEmpty(chunk) 151 | } 152 | 153 | final def as[B](b: B): Step[F, E, B] = map(_ => b) 154 | 155 | final def mapI[G[_]: Applicative](f: FunctionK[F, G]): Step[G, E, A] = new Cont[G, E, A] { 156 | final def run: G[A] = f(self.run) 157 | final def feedEl(e: E): G[Step[G, E, A]] = f(F.map(self.feedEl(e))(_.mapI(f))) 158 | final protected def feedNonEmpty(chunk: Seq[E]): G[Step[G, E, A]] = 159 | f(F.map(self.feedNonEmpty(chunk))(_.mapI(f))) 160 | } 161 | 162 | final def zip[B](other: Step[F, E, B]): Step[F, E, (A, B)] = if (other.isDone) { 163 | map((_, other.asInstanceOf[Done[F, E, B]].value)) 164 | } else { 165 | new Cont[F, E, (A, B)] { 166 | final def run: F[(A, B)] = F.product(self.run, other.run) 167 | final def feedEl(e: E): F[Step[F, E, (A, B)]] = F.map2(self.feedEl(e), other.feedEl(e))(_.zip(_)) 168 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, (A, B)]] = 169 | F.map2(self.feedNonEmpty(chunk), other.feedNonEmpty(chunk))(_.zip(_)) 170 | } 171 | } 172 | } 173 | 174 | abstract class Cont[F[_], E, A](implicit F: Applicative[F]) extends BaseCont[F, E, A] { self => 175 | final def map[B](f: A => B): Step[F, E, B] = new Cont[F, E, B] { 176 | final def run: F[B] = F.map(self.run)(f) 177 | final def feedEl(e: E): F[Step[F, E, B]] = F.map(self.feedEl(e))(_.map(f)) 178 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, B]] = 179 | F.map(self.feedNonEmpty(chunk))(_.map(f)) 180 | } 181 | 182 | final def contramap[E2](f: E2 => E): Step[F, E2, A] = new Cont[F, E2, A] { 183 | final def run: F[A] = self.run 184 | final def feedEl(e: E2): F[Step[F, E2, A]] = F.map(self.feedEl(f(e)))(_.contramap(f)) 185 | final protected def feedNonEmpty(chunk: Seq[E2]): F[Step[F, E2, A]] = 186 | F.map(self.feedNonEmpty(chunk.map(f)))(_.contramap(f)) 187 | } 188 | 189 | final def bind[B](f: A => F[Step[F, E, B]])(implicit M: Monad[F]): F[Step[F, E, B]] = F.pure( 190 | new Cont[F, E, B] { 191 | final def run: F[B] = M.flatMap(self.run)(a => M.flatMap(f(a))(_.run)) 192 | final def feedEl(e: E): F[Step[F, E, B]] = M.flatMap(self.feedEl(e))(_.bind(f)) 193 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, B]] = 194 | M.flatMap(self.feedNonEmpty(chunk))(_.bind(f)) 195 | } 196 | ) 197 | } 198 | 199 | final object Cont { 200 | abstract class WithValue[F[_], E, A](value: A)(implicit F: Applicative[F]) extends Cont[F, E, A] { 201 | final def run: F[A] = F.pure(value) 202 | } 203 | } 204 | 205 | private[this] abstract class PureCont[F[_], E, A](implicit F: Applicative[F]) extends BaseCont[F, E, A] { self => 206 | protected def runPure: A 207 | protected def feedElPure(e: E): Step[F, E, A] 208 | protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, A] 209 | 210 | final def run: F[A] = F.pure(runPure) 211 | final def feedEl(e: E): F[Step[F, E, A]] = F.pure(feedElPure(e)) 212 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, A]] = F.pure(feedNonEmptyPure(chunk)) 213 | 214 | final def map[B](f: A => B): Step[F, E, B] = new PureCont[F, E, B] { 215 | final def runPure: B = f(self.runPure) 216 | final def feedElPure(e: E): Step[F, E, B] = self.feedElPure(e).map(f) 217 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, B] = self.feedNonEmptyPure(chunk).map(f) 218 | } 219 | 220 | final def contramap[E2](f: E2 => E): Step[F, E2, A] = new PureCont[F, E2, A] { 221 | final def runPure: A = self.runPure 222 | final def feedElPure(e: E2): Step[F, E2, A] = self.feedElPure(f(e)).contramap(f) 223 | final protected def feedNonEmptyPure(chunk: Seq[E2]): Step[F, E2, A] = 224 | self.feedNonEmptyPure(chunk.map(f)).contramap(f) 225 | } 226 | 227 | final def bind[B](f: A => F[Step[F, E, B]])(implicit M: Monad[F]): F[Step[F, E, B]] = F.pure( 228 | new Cont[F, E, B] { 229 | final def run: F[B] = M.flatMap(f(self.runPure))(_.run) 230 | final def feedEl(e: E): F[Step[F, E, B]] = self.feedElPure(e).bind(f) 231 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, B]] = self.feedNonEmptyPure(chunk).bind(f) 232 | } 233 | ) 234 | } 235 | 236 | private[this] object PureCont { 237 | abstract class WithValue[F[_], E, A](val runPure: A)(implicit F: Applicative[F]) extends PureCont[F, E, A] 238 | } 239 | 240 | /** 241 | * Create an incomplete [[Step]] that will use the given functions to process the next input. 242 | * 243 | * @group Constructors 244 | */ 245 | final def cont[F[_], E, A]( 246 | onInput: NonEmptyList[E] => F[Step[F, E, A]], 247 | onEnd: F[A] 248 | )(implicit F: Applicative[F]): Step[F, E, A] = new Cont[F, E, A] { 249 | final def run: F[A] = onEnd 250 | final def feedEl(e: E): F[Step[F, E, A]] = onInput(NonEmptyList(e, Nil)) 251 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, A]] = 252 | onInput(NonEmptyList.fromListUnsafe(chunk.toList)) 253 | } 254 | 255 | /** 256 | * Create a new completed [[Step]] with the given result. 257 | * 258 | * @group Constructors 259 | */ 260 | final def done[F[_]: Applicative, E, A](value: A): Step[F, E, A] = new Done(value)(Nil) 261 | 262 | /** 263 | * Create a new completed [[Step]] with the given result and leftover input. 264 | * 265 | * @group Constructors 266 | */ 267 | final def doneWithLeftovers[F[_]: Applicative, E, A](value: A, remaining: Seq[E]): Step[F, E, A] = 268 | new Done(value)(remaining) 269 | 270 | /** 271 | * Lift an effectful value into a [[Step]]. 272 | * 273 | * @group Utilities 274 | */ 275 | final def liftM[F[_], E, A](fa: F[A])(implicit F: Monad[F]): F[Step[F, E, A]] = F.map(fa)(done(_)) 276 | 277 | /** 278 | * Lift an effectful value in a [[cats.Eval]] into a [[Step]]. 279 | * 280 | * @group Utilities 281 | */ 282 | final def liftMEval[F[_], E, A](fa: Eval[F[A]])(implicit F: Monad[F]): F[Step[F, E, A]] = F.pure( 283 | new Cont[F, E, A] { 284 | final def run: F[A] = fa.value 285 | final def feedEl(e: E): F[Step[F, E, A]] = F.map(fa.value)(doneWithLeftovers(_, e :: Nil)) 286 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, A]] = F.map(fa.value)(doneWithLeftovers(_, chunk)) 287 | } 288 | ) 289 | 290 | /** 291 | * Collapse a nested [[Step]] into one layer. 292 | * 293 | * @group Utilities 294 | */ 295 | final def joinI[F[_], A, B, C](step: Step[F, A, Step[F, B, C]])(implicit F: Monad[F]): F[Step[F, A, C]] = 296 | step.bind { next => 297 | if (next.isDone) F.pure(done(next.asInstanceOf[Done[F, B, C]].value)) else F.map(next.run)(done(_)) 298 | } 299 | 300 | /** 301 | * A [[Step]] that folds a stream using an initial value and an accumulation function. 302 | * 303 | * @group Collection 304 | */ 305 | final def fold[F[_], E, A](init: A)(f: (A, E) => A)(implicit F: Applicative[F]): Step[F, E, A] = 306 | new PureCont.WithValue[F, E, A](init) { 307 | final def feedElPure(e: E): Step[F, E, A] = self.fold(f(init, e))(f) 308 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, A] = self.fold(chunk.foldLeft(init)(f))(f) 309 | } 310 | 311 | /** 312 | * A [[Step]] that folds a stream using an initial value and a monadic accumulation function. 313 | * 314 | * @group Collection 315 | */ 316 | final def foldM[F[_], E, A](init: A)(f: (A, E) => F[A])(implicit F: Monad[F]): Step[F, E, A] = 317 | new Cont.WithValue[F, E, A](init) { 318 | final def feedEl(e: E): F[Step[F, E, A]] = F.map(f(init, e))(foldM(_)(f)) 319 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, A]] = 320 | F.map(chunk.tail.foldLeft(f(init, chunk.head))((fa, e) => F.flatMap(fa)(a => f(a, e))))(foldM(_)(f)) 321 | } 322 | 323 | /** 324 | * A [[Step]] that collects all the elements in a stream in a vector. 325 | * 326 | * @group Collection 327 | */ 328 | final def consume[F[_]: Applicative, A]: Step[F, A, Vector[A]] = new ConsumeCont(Vector.empty) 329 | 330 | private[this] final class ConsumeCont[F[_], E](acc: Vector[E])(implicit F: Applicative[F]) 331 | extends PureCont.WithValue[F, E, Vector[E]](acc) { 332 | final def feedElPure(e: E): Step[F, E, Vector[E]] = new ConsumeCont(acc :+ e) 333 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Vector[E]] = new ConsumeCont(acc ++ chunk) 334 | } 335 | 336 | /** 337 | * A [[Step]] that collects all the elements in a stream in a given collection type. 338 | * 339 | * @group Collection 340 | */ 341 | final def consumeIn[F[_], E, C[_]](implicit 342 | F: Applicative[F], 343 | M: MonoidK[C], 344 | C: Applicative[C] 345 | ): Step[F, E, C[E]] = new ConsumeInCont(M.empty) 346 | 347 | private[this] final class ConsumeInCont[F[_], E, C[_]](acc: C[E])(implicit 348 | F: Applicative[F], 349 | M: MonoidK[C], 350 | C: Applicative[C] 351 | ) extends PureCont.WithValue[F, E, C[E]](acc) { 352 | final def feedElPure(e: E): Step[F, E, C[E]] = new ConsumeInCont(M.combineK(acc, C.pure(e))) 353 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, C[E]] = new ConsumeInCont( 354 | chunk.foldLeft(acc)((a, e) => M.combineK(a, C.pure(e))) 355 | ) 356 | } 357 | 358 | /** 359 | * A [[Step]] that returns the first value in a stream. 360 | * 361 | * @group Collection 362 | */ 363 | final def head[F[_], E](implicit F: Applicative[F]): Step[F, E, Option[E]] = new PureCont[F, E, Option[E]] { 364 | final def runPure: Option[E] = None 365 | final def feedElPure(e: E): Step[F, E, Option[E]] = done(Some(e)) 366 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Option[E]] = 367 | doneWithLeftovers(Some(chunk.head), chunk.tail) 368 | } 369 | 370 | /** 371 | * A [[Step]] that returns the first value in a stream without consuming it. 372 | * 373 | * @group Collection 374 | */ 375 | final def peek[F[_], E](implicit F: Applicative[F]): Step[F, E, Option[E]] = new PureCont[F, E, Option[E]] { 376 | final def runPure: Option[E] = None 377 | final def feedElPure(e: E): Step[F, E, Option[E]] = doneWithLeftovers(Some(e), e :: Nil) 378 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Option[E]] = 379 | doneWithLeftovers(Some(chunk.head), chunk) 380 | } 381 | 382 | /** 383 | * A [[Step]] that returns the first value in a stream. 384 | * 385 | * @group Collection 386 | */ 387 | final def last[F[_], E](implicit F: Applicative[F]): Step[F, E, Option[E]] = new LastCont(None) 388 | 389 | private[this] final class LastCont[F[_], E](acc: Option[E])(implicit F: Applicative[F]) 390 | extends PureCont.WithValue[F, E, Option[E]](acc) { 391 | final def feedElPure(e: E): Step[F, E, Option[E]] = new LastCont(Some(e)) 392 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Option[E]] = new LastCont(Some(chunk.last)) 393 | } 394 | 395 | /** 396 | * A [[Step]] that counts the number of values in a stream. 397 | * 398 | * @group Collection 399 | */ 400 | final def length[F[_]: Applicative, A]: Step[F, A, Long] = new LengthCont(0L) 401 | 402 | private[this] final class LengthCont[F[_], E](acc: Long)(implicit F: Applicative[F]) 403 | extends PureCont.WithValue[F, E, Long](acc) { 404 | final def feedElPure(e: E): Step[F, E, Long] = new LengthCont(acc + 1L) 405 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Long] = new LengthCont(acc + chunk.size.toLong) 406 | } 407 | 408 | /** 409 | * A [[Step]] that sums of values in a stream. 410 | * 411 | * @group Collection 412 | */ 413 | final def sum[F[_], E](implicit F: Applicative[F], M: Monoid[E]): Step[F, E, E] = new SumCont(M.empty) 414 | 415 | private[this] final class SumCont[F[_], E](acc: E)(implicit F: Applicative[F], M: Semigroup[E]) 416 | extends PureCont.WithValue[F, E, E](acc) { 417 | final def feedElPure(e: E): Step[F, E, E] = new SumCont(M.combine(acc, e)) 418 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, E] = 419 | new SumCont(M.combine(acc, chunk.reduce(M.combine))) 420 | } 421 | 422 | /** 423 | * A [[Step]] that transforms and sums values in a stream. 424 | * 425 | * @group Collection 426 | */ 427 | final def foldMap[F[_], E, A](f: E => A)(implicit F: Applicative[F], M: Monoid[A]): Step[F, E, A] = 428 | new FoldMapCont(M.empty)(f) 429 | 430 | private[this] final class FoldMapCont[F[_], E, A](acc: A)(f: E => A)(implicit F: Applicative[F], M: Semigroup[A]) 431 | extends PureCont.WithValue[F, E, A](acc) { 432 | final def feedElPure(e: E): Step[F, E, A] = new FoldMapCont(M.combine(acc, f(e)))(f) 433 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, A] = 434 | new FoldMapCont(M.combine(acc, chunk.map(f).reduce(M.combine)))(f) 435 | } 436 | 437 | /** 438 | * A [[Step]] that transforms and sums values in a stream. 439 | * 440 | * @group Collection 441 | */ 442 | final def foldMapM[F[_], E, A](f: E => F[A])(implicit F: Applicative[F], M: Monoid[A]): Step[F, E, A] = 443 | new FoldMapMCont(M.empty)(f) 444 | 445 | private[this] final class FoldMapMCont[F[_], E, A](acc: A)(f: E => F[A])(implicit F: Applicative[F], M: Semigroup[A]) 446 | extends Cont.WithValue[F, E, A](acc) { 447 | final def feedEl(e: E): F[Step[F, E, A]] = F.map(f(e))(a => new FoldMapMCont(M.combine(acc, a))(f)) 448 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, A]] = F.map( 449 | chunk.foldLeft(F.pure(acc)) { case (acc, e) => 450 | F.map2(acc, f(e))(M.combine) 451 | } 452 | )(new FoldMapMCont(_)(f)) 453 | } 454 | 455 | /** 456 | * A [[Step]] that returns a given number of the first values in a stream. 457 | * 458 | * @group Collection 459 | */ 460 | final def take[F[_]: Applicative, E](n: Int): Step[F, E, Vector[E]] = 461 | if (n <= 0) done[F, E, Vector[E]](Vector.empty) else new TakeCont(Vector.empty, n) 462 | 463 | private[this] final class TakeCont[F[_], E](acc: Vector[E], n: Int)(implicit F: Applicative[F]) 464 | extends PureCont.WithValue[F, E, Vector[E]](acc) { 465 | final def feedElPure(e: E): Step[F, E, Vector[E]] = if (n == 1) done(acc :+ e) else new TakeCont(acc :+ e, n - 1) 466 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Vector[E]] = { 467 | val diff = n - chunk.size 468 | 469 | if (diff > 0) new TakeCont(acc ++ chunk, diff) 470 | else if (diff == 0) done(acc ++ chunk) 471 | else { 472 | val (taken, left) = chunk.splitAt(n) 473 | 474 | doneWithLeftovers(acc ++ taken, left) 475 | } 476 | } 477 | } 478 | 479 | /** 480 | * A [[Step]] that returns values from a stream as long as they satisfy the given predicate. 481 | * 482 | * @group Collection 483 | */ 484 | final def takeWhile[F[_]: Applicative, E](p: E => Boolean): Step[F, E, Vector[E]] = 485 | new TakeWhileCont(Vector.empty, p) 486 | 487 | private[this] final class TakeWhileCont[F[_], E](acc: Vector[E], p: E => Boolean)(implicit F: Applicative[F]) 488 | extends PureCont.WithValue[F, E, Vector[E]](acc) { 489 | final def feedElPure(e: E): Step[F, E, Vector[E]] = 490 | if (p(e)) new TakeWhileCont(acc :+ e, p) else doneWithLeftovers(acc, e :: Nil) 491 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Vector[E]] = { 492 | val (before, after) = chunk.span(p) 493 | 494 | if (after.isEmpty) new TakeWhileCont(acc ++ before, p) else doneWithLeftovers(acc ++ before, after) 495 | } 496 | } 497 | 498 | /** 499 | * A [[Step]] that drops a given number of the values from a stream. 500 | * 501 | * @group Collection 502 | */ 503 | final def drop[F[_], E](n: Int)(implicit F: Applicative[F]): Step[F, E, Unit] = 504 | if (n <= 0) done(()) 505 | else 506 | new PureCont[F, E, Unit] { 507 | final def runPure: Unit = () 508 | final def feedElPure(e: E): Step[F, E, Unit] = drop(n - 1) 509 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Unit] = { 510 | val len = chunk.size 511 | 512 | if (len <= n) drop(n - len) else doneWithLeftovers((), chunk.drop(n)) 513 | } 514 | } 515 | 516 | /** 517 | * A [[Step]] that drops values from a stream as long as they satisfy the given predicate. 518 | * 519 | * @group Collection 520 | */ 521 | final def dropWhile[F[_]: Applicative, E](p: E => Boolean): Step[F, E, Unit] = 522 | new DropWhileCont(p) 523 | 524 | private[this] final class DropWhileCont[F[_], E](p: E => Boolean)(implicit F: Applicative[F]) 525 | extends PureCont[F, E, Unit] { 526 | final def runPure: Unit = () 527 | final def feedElPure(e: E): Step[F, E, Unit] = if (p(e)) dropWhile(p) else doneWithLeftovers((), e :: Nil) 528 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Unit] = { 529 | val after = chunk.dropWhile(p) 530 | 531 | if (after.isEmpty) dropWhile(p) else doneWithLeftovers((), after) 532 | } 533 | } 534 | 535 | final def isEnd[F[_], E](implicit F: Applicative[F]): Step[F, E, Boolean] = new PureCont[F, E, Boolean] { 536 | final def runPure: Boolean = true 537 | final def feedElPure(e: E): Step[F, E, Boolean] = doneWithLeftovers(false, e :: Nil) 538 | final protected def feedNonEmptyPure(chunk: Seq[E]): Step[F, E, Boolean] = doneWithLeftovers(false, chunk) 539 | } 540 | 541 | private[this] class TailRecMCont[F[_], E, A, B](a: A)(f: A => F[Step[F, E, Either[A, B]]])(implicit 542 | F: Monad[F] 543 | ) extends Step.Cont[F, E, B] { 544 | final def run: F[B] = F.tailRecM[A, B](a)(a => F.flatMap(f(a))(_.run)) 545 | 546 | private[this] def loop(s: Step[F, E, Either[A, B]]): F[Either[Step[F, E, Either[A, B]], Step[F, E, B]]] = 547 | if (s.isDone) { 548 | val asDone = s.asInstanceOf[Done[F, E, Either[A, B]]] 549 | 550 | asDone.value match { 551 | case Right(b) => F.pure(Right(s.as(b))) 552 | case Left(a) => 553 | val c = asDone.remaining.lengthCompare(1) 554 | 555 | if (c < 0) { 556 | F.pure(Right(new TailRecMCont(a)(f))) 557 | } else if (c == 0) { 558 | F.flatMap(f(a))(s => F.map(s.feedEl(asDone.remaining.head))(Left(_))) 559 | } else { 560 | F.flatMap(f(a))(s => F.map(s.feedNonEmpty(asDone.remaining))(Left(_))) 561 | } 562 | } 563 | } else { 564 | F.map( 565 | s.bind[B] { 566 | case Right(b) => F.pure(done(b)) 567 | case Left(a) => F.pure(new TailRecMCont(a)(f)) 568 | } 569 | )(Right(_)) 570 | } 571 | 572 | final def feedEl(e: E): F[Step[F, E, B]] = 573 | F.flatMap(f(a))(s => F.flatMap(s.feedEl(e))(F.tailRecM(_)(loop))) 574 | 575 | final protected def feedNonEmpty(chunk: Seq[E]): F[Step[F, E, B]] = 576 | F.flatMap(f(a))(s => F.flatMap(s.feedNonEmpty(chunk))(F.tailRecM(_)(loop))) 577 | } 578 | 579 | final def tailRecM[F[_]: Monad, E, A, B](a: A)(f: A => F[Step[F, E, Either[A, B]]]): Step[F, E, B] = 580 | new TailRecMCont(a)(f) 581 | } 582 | -------------------------------------------------------------------------------- /core/src/main/scala/io/iteratee/internal/package.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | /** 4 | * Internal types and utilities. 5 | * 6 | * While some of these types and utilities are part of the public API and are used in the implementation of 7 | * [[io.iteratee]], they are not designed for clarity or ease-of-use, and should never be needed for idiomatic use of 8 | * the library. 9 | */ 10 | package object internal 11 | -------------------------------------------------------------------------------- /core/src/main/scala/io/iteratee/modules/EnumerateeModule.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.modules 2 | 3 | import cats.kernel.Eq 4 | import io.iteratee.{Enumeratee, Enumerator, Iteratee} 5 | 6 | /** 7 | * @groupname Enumeratees 8 | * Enumeratees 9 | * @groupprio Enumeratees 10 | * 2 11 | */ 12 | trait EnumerateeModule[F[_]] { this: Module[F] => 13 | 14 | /** 15 | * Map a function over a stream. 16 | * 17 | * @group Enumeratees 18 | */ 19 | final def map[O, I](f: O => I): Enumeratee[F, O, I] = Enumeratee.map(f)(F) 20 | 21 | /** 22 | * Map a function returning a value in a context over a stream. 23 | * 24 | * @group Enumeratees 25 | */ 26 | final def flatMapM[O, I](f: O => F[I]): Enumeratee[F, O, I] = Enumeratee.flatMapM(f)(F) 27 | 28 | /** 29 | * Map a function returning an [[Enumerator]] over a stream and flatten the results. 30 | * 31 | * @group Enumeratees 32 | */ 33 | final def flatMap[O, I](f: O => Enumerator[F, I]): Enumeratee[F, O, I] = Enumeratee.flatMap(f)(F) 34 | 35 | /** 36 | * An [[Enumeratee]] that takes a given number of the first values in a stream. 37 | * 38 | * @group Enumeratees 39 | */ 40 | final def take[E](n: Long): Enumeratee[F, E, E] = Enumeratee.take(n)(F) 41 | 42 | /** 43 | * An [[Enumeratee]] that takes values from a stream as long as they satisfy the given predicate. 44 | * 45 | * @group Enumeratees 46 | */ 47 | final def takeWhile[E](p: E => Boolean): Enumeratee[F, E, E] = Enumeratee.takeWhile(p)(F) 48 | 49 | /** 50 | * An [[Enumeratee]] that takes values from a stream as long as they satisfy the given monadic predicate. 51 | * 52 | * @group Enumeratees 53 | */ 54 | final def takeWhileM[E](p: E => F[Boolean]): Enumeratee[F, E, E] = Enumeratee.takeWhileM(p)(F) 55 | 56 | /** 57 | * An [[Enumeratee]] that drops a given number of the first values in a stream. 58 | * 59 | * @group Enumeratees 60 | */ 61 | final def drop[E](n: Long): Enumeratee[F, E, E] = Enumeratee.drop(n)(F) 62 | 63 | /** 64 | * An [[Enumeratee]] that drops values from a stream as long as they satisfy the given predicate. 65 | * 66 | * @group Enumeratees 67 | */ 68 | final def dropWhile[E](p: E => Boolean): Enumeratee[F, E, E] = Enumeratee.dropWhile(p)(F) 69 | 70 | /** 71 | * An [[Enumeratee]] that drops values from a stream as long as they satisfy the given monadic predicate. 72 | * 73 | * @group Enumeratees 74 | */ 75 | final def dropWhileM[E](p: E => F[Boolean]): Enumeratee[F, E, E] = Enumeratee.dropWhileM(p)(F) 76 | 77 | /** 78 | * Transform values using a [[scala.PartialFunction]] and drop values that aren't matched. 79 | * 80 | * @group Enumeratees 81 | */ 82 | final def collect[O, I](pf: PartialFunction[O, I]): Enumeratee[F, O, I] = Enumeratee.collect(pf)(F) 83 | 84 | /** 85 | * Drop values that do not satisfy the given predicate. 86 | * 87 | * @group Enumeratees 88 | */ 89 | final def filter[E](p: E => Boolean): Enumeratee[F, E, E] = Enumeratee.filter(p)(F) 90 | 91 | /** 92 | * Drop values that do not satisfy the given monadic predicate. 93 | * 94 | * @group Enumeratees 95 | */ 96 | final def filterM[E](p: E => F[Boolean]): Enumeratee[F, E, E] = Enumeratee.filterM(p)(F) 97 | 98 | /** 99 | * Apply the given [[Iteratee]] repeatedly. 100 | * 101 | * @group Enumeratees 102 | */ 103 | final def sequenceI[O, I](iteratee: Iteratee[F, O, I]): Enumeratee[F, O, I] = Enumeratee.sequenceI(iteratee)(F) 104 | 105 | /** 106 | * An [[Enumeratee]] that folds a stream and emits intermediate results. 107 | * 108 | * @group Enumeratees 109 | */ 110 | final def scan[O, I](init: I)(f: (I, O) => I): Enumeratee[F, O, I] = Enumeratee.scan(init)(f)(F) 111 | 112 | /** 113 | * An [[Enumeratee]] that folds a stream using an effectful function while emitting intermediate results. 114 | * 115 | * @group Enumeratees 116 | */ 117 | final def scanM[O, I](init: I)(f: (I, O) => F[I]): Enumeratee[F, O, I] = Enumeratee.scanM(init)(f)(F) 118 | 119 | /** 120 | * Run an iteratee and then use the provided function to combine the result with the remaining elements. 121 | * 122 | * @group Enumeratees 123 | */ 124 | final def remainderWithResult[O, R, I](iteratee: Iteratee[F, O, R])(f: (R, O) => I): Enumeratee[F, O, I] = 125 | Enumeratee.remainderWithResult(iteratee)(f)(F) 126 | 127 | /** 128 | * Run an iteratee and then use the provided effectful function to combine the result with the remaining elements. 129 | * 130 | * @group Enumeratees 131 | */ 132 | final def remainderWithResultM[O, R, I](iteratee: Iteratee[F, O, R])(f: (R, O) => F[I]): Enumeratee[F, O, I] = 133 | Enumeratee.remainderWithResultM(iteratee)(f)(F) 134 | 135 | /** 136 | * Collapse consecutive duplicates. 137 | * 138 | * @note 139 | * Assumes that the stream is sorted. 140 | * @group Enumeratees 141 | */ 142 | final def uniq[E](implicit E: Eq[E]): Enumeratee[F, E, E] = Enumeratee.uniq[F, E](F, E) 143 | 144 | /** 145 | * Zip with the number of elements that have been encountered. 146 | * 147 | * @group Enumeratees 148 | */ 149 | final def zipWithIndex[E]: Enumeratee[F, E, (E, Long)] = Enumeratee.zipWithIndex(F) 150 | 151 | /** 152 | * Split the stream into groups of a given length. 153 | * 154 | * @group Enumeratees 155 | */ 156 | final def grouped[E](n: Int): Enumeratee[F, E, Vector[E]] = Enumeratee.grouped(n)(F) 157 | 158 | /** 159 | * Split the stream using the given predicate to identify delimiters. 160 | * 161 | * @group Enumeratees 162 | */ 163 | final def splitOn[E](p: E => Boolean): Enumeratee[F, E, Vector[E]] = Enumeratee.splitOn(p)(F) 164 | 165 | /** 166 | * Transform a stream by taking the cross-product with the given [[Enumerator]]. 167 | * 168 | * @group Enumeratees 169 | */ 170 | final def cross[E1, E2](e2: Enumerator[F, E2]): Enumeratee[F, E1, (E1, E2)] = Enumeratee.cross(e2)(F) 171 | 172 | /** 173 | * Add a value `delim` between every two items in a stream. 174 | * 175 | * @group Enumeratees 176 | */ 177 | final def intersperse[E](delim: E): Enumeratee[F, E, E] = Enumeratee.intersperse(delim)(F) 178 | 179 | /** 180 | * Inject a value into a stream. 181 | */ 182 | final def injectValue[E](e: E): Enumeratee[F, E, E] = Enumeratee.injectValue(e)(F) 183 | 184 | /** 185 | * Inject zero or more values into a stream. 186 | */ 187 | final def injectValues[E](es: Seq[E]): Enumeratee[F, E, E] = Enumeratee.injectValues(es)(F) 188 | } 189 | -------------------------------------------------------------------------------- /core/src/main/scala/io/iteratee/modules/EnumeratorModule.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.modules 2 | 3 | import cats.MonadError 4 | import io.iteratee.Enumerator 5 | 6 | /** 7 | * @groupname Enumerators 8 | * Enumerators 9 | * @groupprio Enumerators 10 | * 0 11 | * 12 | * @groupname Helpers 13 | * Helper classes 14 | * @groupprio Helpers 15 | * 4 16 | */ 17 | trait EnumeratorModule[F[_]] { this: Module[F] => 18 | private[this] final def defaultChunkSize: Int = 1024 19 | 20 | /** 21 | * Lift an effectful value into an enumerator. 22 | * 23 | * @group Enumerators 24 | */ 25 | final def liftToEnumerator[E](fe: F[E]): Enumerator[F, E] = Enumerator.liftM(fe)(F) 26 | 27 | /** 28 | * An enumerator that produces the given values. 29 | * 30 | * @group Enumerators 31 | */ 32 | final def enumerate[E](xs: E*): Enumerator[F, E] = Enumerator.enumerate(xs: _*)(F) 33 | 34 | /** 35 | * An empty enumerator. 36 | * 37 | * @group Enumerators 38 | */ 39 | final def empty[E]: Enumerator[F, E] = Enumerator.empty(F) 40 | 41 | /** 42 | * @group Helpers 43 | */ 44 | sealed class PerformPartiallyApplied[E] { 45 | final def apply[A](fa: F[A]): Enumerator[F, E] = Enumerator.perform(fa)(F) 46 | } 47 | 48 | /** 49 | * An enumerator that forces the evaluation of an effect when it is consumed. 50 | * 51 | * @group Enumerators 52 | */ 53 | final def perform[E]: PerformPartiallyApplied[E] = new PerformPartiallyApplied[E] 54 | 55 | /** 56 | * An enumerator that produces a single value. 57 | * 58 | * @group Enumerators 59 | */ 60 | final def enumOne[E](e: E): Enumerator[F, E] = Enumerator.enumOne(e)(F) 61 | 62 | /** 63 | * An enumerator that produces values from an iterable collection. 64 | * 65 | * @group Enumerators 66 | */ 67 | final def enumIterable[E](es: Iterable[E], chunkSize: Int = defaultChunkSize): Enumerator[F, E] = 68 | Enumerator.enumIterable(es, chunkSize)(F) 69 | 70 | /** 71 | * An enumerator that produces values from a stream. 72 | * 73 | * @group Enumerators 74 | */ 75 | final def enumStream[E](es: Stream[E], chunkSize: Int = defaultChunkSize): Enumerator[F, E] = 76 | Enumerator.enumStream(es, chunkSize)(F) 77 | 78 | /** 79 | * An enumerator that produces values from a list. 80 | * 81 | * @group Enumerators 82 | */ 83 | final def enumList[E](es: List[E]): Enumerator[F, E] = Enumerator.enumList(es)(F) 84 | 85 | /** 86 | * An enumerator that produces values from a vector. 87 | * 88 | * @group Enumerators 89 | */ 90 | final def enumVector[E](es: Vector[E]): Enumerator[F, E] = Enumerator.enumVector(es)(F) 91 | 92 | /** 93 | * An enumerator that produces values from a slice of an indexed sequence. 94 | * 95 | * @group Enumerators 96 | */ 97 | final def enumIndexedSeq[E](es: IndexedSeq[E], min: Int = 0, max: Int = Int.MaxValue): Enumerator[F, E] = 98 | Enumerator.enumIndexedSeq(es, min, max)(F) 99 | 100 | /** 101 | * An enumerator that repeats the given value indefinitely. 102 | * 103 | * @group Enumerators 104 | */ 105 | final def repeat[E](e: E): Enumerator[F, E] = Enumerator.repeat(e)(F) 106 | 107 | /** 108 | * An enumerator that iteratively performs an operation and returns the results. 109 | * 110 | * @group Enumerators 111 | */ 112 | final def iterate[E](init: E)(f: E => E): Enumerator[F, E] = Enumerator.iterate(init)(f)(F) 113 | 114 | /** 115 | * An enumerator that iteratively performs an effectful operation and returns the results. 116 | * 117 | * @group Enumerators 118 | */ 119 | final def iterateM[E](init: E)(f: E => F[E]): Enumerator[F, E] = Enumerator.iterateM(init)(f)(F) 120 | 121 | /** 122 | * An enumerator that iteratively performs an operation until None is produced and returns the results. 123 | * 124 | * @group Enumerators 125 | */ 126 | final def iterateUntil[E](init: E)(f: E => Option[E]): Enumerator[F, E] = Enumerator.iterateUntil(init)(f)(F) 127 | 128 | /** 129 | * An enumerator that iteratively performs an effectful operation until None is produced and returns the results. 130 | * 131 | * @group Enumerators 132 | */ 133 | final def iterateUntilM[E](init: E)(f: E => F[Option[E]]): Enumerator[F, E] = Enumerator.iterateUntilM(init)(f)(F) 134 | 135 | /** 136 | * An enumerator that returns the result of an effectful operation until `None` is generated. 137 | * 138 | * @group Enumerators 139 | */ 140 | final def generateM[E](f: F[Option[E]]): Enumerator[F, E] = Enumerator.generateM(f)(F) 141 | } 142 | 143 | trait EnumeratorErrorModule[F[_], T] extends EnumeratorModule[F] { 144 | this: Module[F] { type M[f[_]] <: MonadError[f, T] } => 145 | 146 | /** 147 | * Create a failed enumerator with the given error. 148 | * 149 | * @group Iteratees 150 | */ 151 | final def failEnumerator[E](t: T): Enumerator[F, E] = Enumerator.fail(t)(F) 152 | 153 | /** 154 | * An enumerator that either produces a single value or fails. 155 | * 156 | * @group Iteratees 157 | */ 158 | final def enumEither[E](either: Either[T, E]): Enumerator[F, E] = Enumerator.enumEither(either)(F) 159 | } 160 | -------------------------------------------------------------------------------- /core/src/main/scala/io/iteratee/modules/IterateeModule.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.modules 2 | 3 | import cats.{Applicative, MonadError, Monoid, MonoidK, Semigroup} 4 | import cats.data.NonEmptyList 5 | import io.iteratee.Iteratee 6 | 7 | /** 8 | * @groupname Iteratees 9 | * Iteratees 10 | * @groupprio Iteratees 11 | * 1 12 | * 13 | * @groupname Helpers 14 | * Helper classes 15 | * @groupprio Helpers 16 | * 4 17 | */ 18 | trait IterateeModule[F[_]] { self: Module[F] => 19 | 20 | /** 21 | * Create an incomplete [[Iteratee]] that will use the given function to process the next input. 22 | * 23 | * @group Constructors 24 | */ 25 | final def cont[E, A](ifInput: NonEmptyList[E] => Iteratee[F, E, A], ifEnd: F[A]): Iteratee[F, E, A] = 26 | Iteratee.cont(ifInput, ifEnd)(F) 27 | 28 | /** 29 | * Create a new completed [[Iteratee]] with the given result. 30 | * 31 | * @group Constructors 32 | */ 33 | final def done[E, A](value: A): Iteratee[F, E, A] = Iteratee.done(value)(F) 34 | 35 | /** 36 | * @group Helpers 37 | */ 38 | sealed class LiftToIterateePartiallyApplied[E] { 39 | final def apply[A](fa: F[A]): Iteratee[F, E, A] = Iteratee.liftM(fa)(F) 40 | } 41 | 42 | /** 43 | * Lift an effectful value into an iteratee. 44 | * 45 | * @group Iteratees 46 | */ 47 | final def liftToIteratee[E]: LiftToIterateePartiallyApplied[E] = new LiftToIterateePartiallyApplied[E] 48 | 49 | /** 50 | * An iteratee that reads nothing from a stream. 51 | * 52 | * @group Iteratees 53 | */ 54 | final def identityIteratee[E]: Iteratee[F, E, Unit] = Iteratee.identity(F) 55 | 56 | /** 57 | * An [[Iteratee]] that folds a stream using an initial value and an accumulation function. 58 | * 59 | * @group Iteratees 60 | */ 61 | final def fold[E, A](init: A)(f: (A, E) => A): Iteratee[F, E, A] = Iteratee.fold(init)(f)(F) 62 | 63 | /** 64 | * An [[Iteratee]] that folds a stream using an initial value and a monadic accumulation function. 65 | * 66 | * @group Iteratees 67 | */ 68 | final def foldM[E, A](init: A)(f: (A, E) => F[A]): Iteratee[F, E, A] = Iteratee.foldM(init)(f)(F) 69 | 70 | /** 71 | * An [[Iteratee]] that collects all the elements in a stream in a vector. 72 | * 73 | * @group Iteratees 74 | */ 75 | final def consume[E]: Iteratee[F, E, Vector[E]] = Iteratee.consume(F) 76 | 77 | /** 78 | * An [[Iteratee]] that collects all the elements in a stream in a given collection type. 79 | * 80 | * @group Iteratees 81 | */ 82 | final def consumeIn[E, C[_]](implicit C: Applicative[C], M: MonoidK[C]): Iteratee[F, E, C[E]] = 83 | Iteratee.consumeIn(F, C, M) 84 | 85 | /** 86 | * An [[Iteratee]] that returns the first value in a stream. 87 | * 88 | * @group Iteratees 89 | */ 90 | final def head[E]: Iteratee[F, E, Option[E]] = Iteratee.head(F) 91 | 92 | /** 93 | * An [[Iteratee]] that returns the first value in a stream without consuming it. 94 | * 95 | * @group Iteratees 96 | */ 97 | final def peek[E]: Iteratee[F, E, Option[E]] = Iteratee.peek(F) 98 | 99 | /** 100 | * An [[Iteratee]] that returns the last value in a stream. 101 | * 102 | * @group Iteratees 103 | */ 104 | final def last[E]: Iteratee[F, E, Option[E]] = Iteratee.last(F) 105 | 106 | /** 107 | * An [[Iteratee]] that returns a given number of the first values in a stream. 108 | * 109 | * @group Iteratees 110 | */ 111 | final def takeI[E](n: Int): Iteratee[F, E, Vector[E]] = Iteratee.take(n)(F) 112 | 113 | /** 114 | * An [[Iteratee]] that returns values from a stream as long as they satisfy the given predicate. 115 | * 116 | * @group Iteratees 117 | */ 118 | final def takeWhileI[E](p: E => Boolean): Iteratee[F, E, Vector[E]] = Iteratee.takeWhile(p)(F) 119 | 120 | /** 121 | * An [[Iteratee]] that drops a given number of the values from a stream. 122 | * 123 | * @group Iteratees 124 | */ 125 | final def dropI[E](n: Int): Iteratee[F, E, Unit] = Iteratee.drop(n)(F) 126 | 127 | /** 128 | * An [[Iteratee]] that drops values from a stream as long as they satisfy the given predicate. 129 | * 130 | * @group Iteratees 131 | */ 132 | final def dropWhileI[E](p: E => Boolean): Iteratee[F, E, Unit] = Iteratee.dropWhile(p)(F) 133 | 134 | /** 135 | * An [[Iteratee]] that collects all inputs in reverse order. 136 | * 137 | * @group Iteratees 138 | */ 139 | final def reversed[E]: Iteratee[F, E, List[E]] = Iteratee.reversed(F) 140 | 141 | /** 142 | * An [[Iteratee]] that counts the number of values in a stream. 143 | * 144 | * @group Iteratees 145 | */ 146 | final def length[E]: Iteratee[F, E, Long] = Iteratee.length(F) 147 | 148 | /** 149 | * An [[Iteratee]] that combines values using an [[cats.Monoid]] instance. 150 | * 151 | * @group Iteratees 152 | */ 153 | final def sum[E](implicit E: Monoid[E]): Iteratee[F, E, E] = Iteratee.sum(F, E) 154 | 155 | /** 156 | * An [[Iteratee]] that combines values using a function to a type with a [[cats.Monoid]] instance. 157 | * 158 | * @group Iteratees 159 | */ 160 | final def foldMap[E, A](f: E => A)(implicit A: Monoid[A]): Iteratee[F, E, A] = Iteratee.foldMap(f)(F, A) 161 | 162 | /** 163 | * An [[Iteratee]] that combines values using an effectful function to a type with a [[cats.Monoid]] instance. 164 | * 165 | * @group Iteratees 166 | */ 167 | final def foldMapM[E, A](f: E => F[A])(implicit A: Monoid[A]): Iteratee[F, E, A] = Iteratee.foldMapM(f)(F, A) 168 | 169 | /** 170 | * An [[Iteratee]] that combines values using a function to a type with a [[cats.Semigroup]] instance. 171 | * 172 | * @group Iteratees 173 | */ 174 | final def foldMapOption[E, A](f: E => A)(implicit A: Semigroup[A]): Iteratee[F, E, Option[A]] = 175 | Iteratee.foldMapOption(f)(F, A) 176 | 177 | /** 178 | * An [[Iteratee]] that combines values using an effectful function to a type with a [[cats.Semigroup]] instance. 179 | * 180 | * @group Iteratees 181 | */ 182 | final def foldMapMOption[E, A](f: E => F[A])(implicit A: Semigroup[A]): Iteratee[F, E, Option[A]] = 183 | Iteratee.foldMapMOption(f)(F, A) 184 | 185 | /** 186 | * An [[Iteratee]] that checks if the stream is at its end. 187 | * 188 | * @group Iteratees 189 | */ 190 | final def isEnd[E]: Iteratee[F, E, Boolean] = Iteratee.isEnd(F) 191 | 192 | /** 193 | * An [[Iteratee]] that runs a function for its side effects. 194 | * 195 | * @group Iteratees 196 | */ 197 | final def foreach[E](f: E => Unit): Iteratee[F, E, Unit] = Iteratee.foreach(f)(F) 198 | 199 | /** 200 | * An [[Iteratee]] that runs an effectful function for its side effects. 201 | * 202 | * @group Iteratees 203 | */ 204 | final def foreachM[A](f: A => F[Unit]): Iteratee[F, A, Unit] = Iteratee.foreachM(f)(F) 205 | } 206 | 207 | trait IterateeErrorModule[F[_], T] extends IterateeModule[F] { 208 | this: Module[F] { type M[f[_]] <: MonadError[f, T] } => 209 | 210 | /** 211 | * Create a failed iteratee with the given error. 212 | * 213 | * @group Iteratees 214 | */ 215 | final def failIteratee[E, A](t: T): Iteratee[F, E, A] = Iteratee.fail(t)(F) 216 | } 217 | -------------------------------------------------------------------------------- /core/src/main/scala/io/iteratee/modules/Module.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.modules 2 | 3 | import cats.Monad 4 | import io.iteratee.{Enumerator, Iteratee} 5 | 6 | /** 7 | * @groupname Syntax 8 | * Extension methods 9 | * @groupprio Syntax 10 | * 3 11 | */ 12 | trait Module[F[_]] { 13 | type M[f[_]] <: Monad[f] 14 | 15 | protected def F: M[F] 16 | 17 | /** 18 | * @group Syntax 19 | */ 20 | final object syntax { 21 | final implicit class EffectfulValueOps[A](fa: F[A]) { 22 | final def intoEnumerator: Enumerator[F, A] = Enumerator.liftM(fa)(F) 23 | final def intoIteratee[E]: Iteratee[F, E, A] = Iteratee.liftM(fa)(F) 24 | } 25 | } 26 | } 27 | 28 | object Module { 29 | private[this] class FromMonad[F[_]](monad: Monad[F]) extends Module[F] { 30 | type M[F[T]] = Monad[F] 31 | def F: Monad[F] = monad 32 | } 33 | 34 | def apply[F[_]](implicit monad: Monad[F]): Module[F] = 35 | new FromMonad(monad) 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/scala/io/iteratee/modules/package.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.{Eval, Id, Monad, MonadError, catsInstancesForId} 4 | import cats.data.EitherT 5 | import cats.instances.either.catsStdInstancesForEither 6 | import cats.instances.future.catsStdInstancesForFuture 7 | import cats.instances.option.catsStdInstancesForOption 8 | import cats.instances.try_.catsStdInstancesForTry 9 | import scala.concurrent.{ExecutionContext, Future} 10 | import scala.util.Try 11 | 12 | package object modules { 13 | def future(implicit ec0: ExecutionContext): FutureModule = new FutureModule { 14 | final protected def ec: ExecutionContext = ec0 15 | } 16 | } 17 | 18 | package modules { 19 | final object either extends EitherModule 20 | final object eitherT extends EitherTModule 21 | final object eval extends EvalModule 22 | final object id extends IdModule 23 | final object option extends OptionModule 24 | final object try_ extends TryModule 25 | 26 | trait EitherModule 27 | extends Module[Either[Throwable, *]] 28 | with EnumerateeModule[Either[Throwable, *]] 29 | with EnumeratorErrorModule[Either[Throwable, *], Throwable] 30 | with IterateeErrorModule[Either[Throwable, *], Throwable] { 31 | final type M[f[_]] = MonadError[f, Throwable] 32 | 33 | final protected val F: MonadError[Either[Throwable, *], Throwable] = catsStdInstancesForEither 34 | } 35 | 36 | trait EitherTModule 37 | extends Module[EitherT[Eval, Throwable, *]] 38 | with EnumerateeModule[EitherT[Eval, Throwable, *]] 39 | with EnumeratorErrorModule[EitherT[Eval, Throwable, *], Throwable] 40 | with IterateeErrorModule[EitherT[Eval, Throwable, *], Throwable] { 41 | final type M[f[_]] = MonadError[f, Throwable] 42 | 43 | final protected val F: MonadError[EitherT[Eval, Throwable, *], Throwable] = 44 | EitherT.catsDataMonadErrorForEitherT 45 | } 46 | 47 | trait EvalModule 48 | extends Module[Eval] 49 | with EnumerateeModule[Eval] 50 | with EnumeratorModule[Eval] 51 | with IterateeModule[Eval] { 52 | final type M[f[_]] = Monad[f] 53 | 54 | final protected val F: Monad[Eval] = Eval.catsBimonadForEval 55 | } 56 | 57 | trait FutureModule 58 | extends Module[Future] 59 | with EnumerateeModule[Future] 60 | with EnumeratorErrorModule[Future, Throwable] 61 | with IterateeErrorModule[Future, Throwable] { 62 | final type M[f[_]] = MonadError[f, Throwable] 63 | 64 | protected def ec: ExecutionContext 65 | final protected val F: MonadError[Future, Throwable] = catsStdInstancesForFuture(ec) 66 | } 67 | 68 | trait IdModule extends Module[Id] with EnumerateeModule[Id] with EnumeratorModule[Id] with IterateeModule[Id] { 69 | final type M[f[_]] = Monad[f] 70 | 71 | final protected val F: Monad[Id] = catsInstancesForId 72 | } 73 | 74 | trait OptionModule 75 | extends Module[Option] 76 | with EnumerateeModule[Option] 77 | with EnumeratorModule[Option] 78 | with IterateeModule[Option] { 79 | final type M[f[_]] = Monad[f] 80 | 81 | final protected val F: Monad[Option] = catsStdInstancesForOption 82 | } 83 | 84 | trait TryModule 85 | extends Module[Try] 86 | with EnumerateeModule[Try] 87 | with EnumeratorErrorModule[Try, Throwable] 88 | with IterateeErrorModule[Try, Throwable] { 89 | final type M[f[_]] = MonadError[f, Throwable] 90 | 91 | final protected val F: MonadError[Try, Throwable] = catsStdInstancesForTry 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /files/src/main/scala/io/iteratee/files/modules/FileModule.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.files.modules 2 | 3 | import cats.effect.Sync 4 | import _root_.io.iteratee.{Enumerator, Iteratee} 5 | import _root_.io.iteratee.modules.Module 6 | import java.io.{File, InputStream, OutputStream} 7 | import java.util.zip.ZipEntry 8 | 9 | trait FileModule[F[_]] { this: Module[F] { type M[f[_]] = Sync[f] } => 10 | final def readLines(file: File): Enumerator[F, String] = _root_.io.iteratee.files.readLines[F](file)(F) 11 | final def readLinesFromStream(stream: InputStream): Enumerator[F, String] = 12 | _root_.io.iteratee.files.readLinesFromStream[F](stream)(F) 13 | final def readBytes(file: File): Enumerator[F, Array[Byte]] = _root_.io.iteratee.files.readBytes[F](file)(F) 14 | final def readBytesFromStream(stream: InputStream): Enumerator[F, Array[Byte]] = 15 | _root_.io.iteratee.files.readBytesFromStream[F](stream)(F) 16 | final def readZipStreams(file: File): Enumerator[F, (ZipEntry, InputStream)] = 17 | _root_.io.iteratee.files.readZipStreams[F](file)(F) 18 | final def listFiles(dir: File): Enumerator[F, File] = _root_.io.iteratee.files.listFiles[F](dir)(F) 19 | final def listFilesRec(dir: File): Enumerator[F, File] = _root_.io.iteratee.files.listFilesRec[F](dir)(F) 20 | final def writeLines(file: File): Iteratee[F, String, Unit] = _root_.io.iteratee.files.writeLines[F](file)(F) 21 | final def writeLinesToStream(stream: OutputStream): Iteratee[F, String, Unit] = 22 | _root_.io.iteratee.files.writeLinesToStream[F](stream)(F) 23 | final def writeBytes(file: File): Iteratee[F, Array[Byte], Unit] = 24 | _root_.io.iteratee.files.writeBytes[F](file)(F) 25 | final def writeBytesToStream(stream: OutputStream): Iteratee[F, Array[Byte], Unit] = 26 | _root_.io.iteratee.files.writeBytesToStream[F](stream)(F) 27 | } 28 | -------------------------------------------------------------------------------- /files/src/main/scala/io/iteratee/files/modules/package.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.files 2 | 3 | import cats.effect.{IO, Sync} 4 | import io.iteratee.modules.{EnumerateeModule, EnumeratorErrorModule, IterateeErrorModule, Module} 5 | 6 | package modules { 7 | final object io extends IOModule 8 | 9 | trait IOModule 10 | extends FileModule[IO] 11 | with Module[IO] 12 | with EnumerateeModule[IO] 13 | with EnumeratorErrorModule[IO, Throwable] 14 | with IterateeErrorModule[IO, Throwable] { 15 | type M[f[_]] = Sync[f] 16 | 17 | protected val F: Sync[IO] = IO.asyncForIO 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /files/src/main/scala/io/iteratee/files/package.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.effect.Sync 4 | import io.iteratee.internal.Step 5 | import java.io.{ 6 | BufferedInputStream, 7 | BufferedOutputStream, 8 | BufferedReader, 9 | BufferedWriter, 10 | File, 11 | FileInputStream, 12 | FileOutputStream, 13 | FileReader, 14 | FileWriter, 15 | InputStream, 16 | InputStreamReader, 17 | OutputStream, 18 | OutputStreamWriter 19 | } 20 | import java.util.zip.{ZipEntry, ZipFile} 21 | import scala.Predef.genericArrayOps 22 | import scala.collection.JavaConverters._ 23 | 24 | package object files { 25 | def readLines[F[_]](file: File)(implicit F: Sync[F]): Enumerator[F, String] = 26 | enumerateLines(new BufferedReader(new FileReader(file))) 27 | 28 | def readLinesFromStream[F[_]](stream: InputStream)(implicit F: Sync[F]): Enumerator[F, String] = 29 | enumerateLines(new BufferedReader(new InputStreamReader(stream))) 30 | 31 | def readBytes[F[_]](file: File)(implicit F: Sync[F]): Enumerator[F, Array[Byte]] = 32 | enumerateBytes(new BufferedInputStream(new FileInputStream(file))) 33 | 34 | def readBytesFromStream[F[_]](stream: InputStream)(implicit F: Sync[F]): Enumerator[F, Array[Byte]] = 35 | enumerateBytes(new BufferedInputStream(stream)) 36 | 37 | def readZipStreams[F[_]](file: File)(implicit F: Sync[F]): Enumerator[F, (ZipEntry, InputStream)] = 38 | Enumerator.liftM(F.delay(new ZipFile(file))).flatMap { zipFile => 39 | new ZipFileEnumerator(zipFile, zipFile.entries.asScala).ensure(F.delay(zipFile.close())) 40 | } 41 | 42 | def listFiles[F[_]](dir: File)(implicit F: Sync[F]): Enumerator[F, File] = 43 | Enumerator.liftM(F.delay(dir.listFiles)).flatMap { 44 | case null => Enumerator.empty[F, File] 45 | case files => Enumerator.enumVector(Vector(files: _*)) 46 | } 47 | 48 | def listFilesRec[F[_]](dir: File)(implicit F: Sync[F]): Enumerator[F, File] = listFiles[F](dir).flatMap { 49 | case item if item.isDirectory => listFilesRec(item) 50 | case item => Enumerator.enumOne(item) 51 | } 52 | 53 | def writeLines[F[_]](file: File)(implicit F: Sync[F]): Iteratee[F, String, Unit] = 54 | Iteratee.liftM(F.delay(new BufferedWriter(new FileWriter(file)))).flatMap { writer => 55 | Iteratee 56 | .foldM[F, String, Unit](())((_, line) => 57 | F.delay { 58 | writer.write(line) 59 | writer.newLine() 60 | } 61 | ) 62 | .ensure(F.delay(writer.close())) 63 | } 64 | 65 | def writeLinesToStream[F[_]](stream: OutputStream)(implicit F: Sync[F]): Iteratee[F, String, Unit] = 66 | Iteratee.liftM(F.delay(new BufferedWriter(new OutputStreamWriter(stream)))).flatMap { writer => 67 | Iteratee 68 | .foldM[F, String, Unit](())((_, line) => 69 | F.delay { 70 | writer.write(line) 71 | writer.newLine() 72 | } 73 | ) 74 | .ensure(F.delay(writer.close())) 75 | } 76 | 77 | def writeBytes[F[_]](file: File)(implicit F: Sync[F]): Iteratee[F, Array[Byte], Unit] = 78 | Iteratee.liftM(F.delay(new BufferedOutputStream(new FileOutputStream(file)))).flatMap { stream => 79 | Iteratee 80 | .foldM[F, Array[Byte], Unit](())((_, bytes) => F.delay(stream.write(bytes))) 81 | .ensure(F.delay(stream.close())) 82 | } 83 | 84 | def writeBytesToStream[F[_]](stream: OutputStream)(implicit F: Sync[F]): Iteratee[F, Array[Byte], Unit] = 85 | Iteratee.liftM(F.delay(new BufferedOutputStream(stream))).flatMap { stream => 86 | Iteratee 87 | .foldM[F, Array[Byte], Unit](())((_, bytes) => F.delay(stream.write(bytes))) 88 | .ensure(F.delay(stream.close())) 89 | } 90 | 91 | private[this] def enumerateLines[F[_]](reader: => BufferedReader)(implicit F: Sync[F]): Enumerator[F, String] = 92 | Enumerator.liftM(F.delay(reader)).flatMap(reader => new LineEnumerator(reader).ensure(F.delay(reader.close()))) 93 | 94 | private[this] def enumerateBytes[F[_]](stream: => InputStream)(implicit F: Sync[F]): Enumerator[F, Array[Byte]] = 95 | Enumerator.liftM(F.delay(stream)).flatMap(reader => new ByteEnumerator(stream).ensure(F.delay(stream.close()))) 96 | 97 | private[this] final class LineEnumerator[F[_]](reader: BufferedReader)(implicit F: Sync[F]) 98 | extends Enumerator[F, String] { 99 | final def apply[A](s: Step[F, String, A]): F[Step[F, String, A]] = 100 | if (s.isDone) F.pure(s) 101 | else 102 | F.flatMap(F.delay(reader.readLine())) { 103 | case null => F.pure(s) 104 | case line => F.flatMap(s.feedEl(line))(apply) 105 | } 106 | } 107 | 108 | private[this] final class ByteEnumerator[F[_]](stream: InputStream, bufferSize: Int = 8192)(implicit F: Sync[F]) 109 | extends Enumerator[F, Array[Byte]] { 110 | final def apply[A](s: Step[F, Array[Byte], A]): F[Step[F, Array[Byte], A]] = 111 | if (s.isDone) F.pure(s) 112 | else 113 | F.flatten( 114 | F.delay { 115 | val array = new Array[Byte](bufferSize) 116 | val bytesRead = stream.read(array, 0, bufferSize) 117 | val read = if (bytesRead == bufferSize) array else array.slice(0, bytesRead) 118 | 119 | if (bytesRead == -1) F.pure(s) else F.flatMap(s.feedEl(read))(apply(_)) 120 | } 121 | ) 122 | } 123 | 124 | private[this] final class ZipFileEnumerator[F[_]](zipFile: ZipFile, iterator: Iterator[ZipEntry])(implicit F: Sync[F]) 125 | extends Enumerator[F, (ZipEntry, InputStream)] { 126 | final def apply[A](s: Step[F, (ZipEntry, InputStream), A]): F[Step[F, (ZipEntry, InputStream), A]] = 127 | if (s.isDone) F.pure(s) 128 | else 129 | F.flatten( 130 | F.delay( 131 | if (iterator.hasNext) { 132 | val entry = iterator.next() 133 | 134 | F.flatMap(s.feedEl((entry, zipFile.getInputStream(entry))))(apply) 135 | } else F.pure(s) 136 | ) 137 | ) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.5 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.13.0") 2 | addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") 3 | addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0") 4 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") 5 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") 6 | addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") 7 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.2") 8 | addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") 9 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") 10 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1") 11 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") 12 | addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") 13 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.1") 14 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") 15 | -------------------------------------------------------------------------------- /scalastyle-config.xml: -------------------------------------------------------------------------------- 1 | 2 | Scalastyle standard configuration 3 | 4 | 5 | FOR 6 | IF 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | true 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /testing/jvm/src/main/scala/io/iteratee/testing/files/FileModuleSuite.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.testing.files 2 | 3 | import cats.Monad 4 | import io.iteratee.modules.{EnumeratorModule, IterateeModule, Module} 5 | import io.iteratee.files.modules.FileModule 6 | import io.iteratee.testing.ModuleSuite 7 | import java.io.{File, FileInputStream, FileOutputStream} 8 | import org.scalacheck.Gen 9 | import scala.Predef._ 10 | 11 | abstract class FileModuleSuite[F[_]: Monad] extends ModuleSuite[F] { 12 | this: Module[F] with EnumeratorModule[F] with IterateeModule[F] with FileModule[F] => 13 | type M[f[_]] = cats.effect.Sync[f] 14 | 15 | "readLines" should "enumerate text lines from a file" in { 16 | val txt = new File(getClass.getResource("/io/iteratee/examples/pg/11231/11231.txt").toURI) 17 | val enumerator = readLines(txt).flatMap(line => enumVector(line.trim.split("\\s+").toVector)) 18 | 19 | assert(enumerator.into(length) === F.pure(17973)) 20 | } 21 | 22 | it should "work with an iteratee that stops early" in { 23 | val txt = new File(getClass.getResource("/io/iteratee/examples/pg/11231/11231.txt").toURI) 24 | val result = "The Project Gutenberg EBook of Bartleby, The Scrivener, by Herman Melville" 25 | val enumerator = readLines(txt) 26 | 27 | assert(enumerator.into(head) === F.pure(Some(result))) 28 | } 29 | 30 | "readLinesFromStream" should "enumerate text lines from a stream" in { 31 | val txt = new File(getClass.getResource("/io/iteratee/examples/pg/11231/11231.txt").toURI) 32 | val stream = new FileInputStream(txt) 33 | val enumerator = readLinesFromStream(stream).flatMap(line => enumVector(line.trim.split("\\s+").toVector)) 34 | 35 | assert(enumerator.into(length) === F.pure(17973)) 36 | } 37 | 38 | "readBytes" should "enumerate bytes from a file" in { 39 | val txt = new File(getClass.getResource("/io/iteratee/examples/pg/11231/11231.txt").toURI) 40 | val enumerator = readBytes(txt).flatMap(bytes => enumVector(bytes.toVector)) 41 | 42 | assert(enumerator.into(length) === F.pure(105397)) 43 | } 44 | 45 | "readBytesFromStream" should "enumerate bytes from a stream" in { 46 | val zip = new File(getClass.getResource("/io/iteratee/examples/pg/11231/11231.zip").toURI) 47 | val enumerator = readZipStreams(zip).flatMap { case (_, stream) => 48 | readBytesFromStream(stream) 49 | }.flatMap(bytes => enumVector(bytes.toVector)) 50 | 51 | assert(enumerator.into(length) === F.pure(105397)) 52 | } 53 | 54 | "readZipStreams" should "enumerate files in a zip archive" in { 55 | val zip = new File(getClass.getResource("/io/iteratee/examples/pg/11231/11231.zip").toURI) 56 | 57 | val enumerator = readZipStreams(zip).flatMap { case (_, stream) => 58 | readLinesFromStream(stream) 59 | } 60 | 61 | assert(enumerator.into(length) === F.pure(1981)) 62 | } 63 | 64 | it should "work with an iteratee that stops early" in { 65 | val zip = new File(getClass.getResource("/io/iteratee/examples/pg/11231/11231.zip").toURI) 66 | val enumerator = readZipStreams(zip).map(_._1.getName) 67 | 68 | assert(enumerator.into(head) === F.pure(Some("11231.txt"))) 69 | } 70 | 71 | "listFiles" should "enumerate files in a directory" in { 72 | val dir = new File(getClass.getResource("/io/iteratee/examples/pg/11231").toURI) 73 | val result = Vector("11231.txt", "11231.zip") 74 | val enumerator = listFiles(dir) 75 | 76 | assert(F.map(enumerator.toVector)(_.map(_.getName).sorted) === F.pure(result)) 77 | } 78 | 79 | it should "fail properly on a file" in { 80 | val notDir = new File(getClass.getResource("/io/iteratee/examples/pg/11231/11231.txt").toURI) 81 | val enumerator = listFiles(notDir) 82 | 83 | assert(F.map(enumerator.toVector)(_.isEmpty) === F.pure(true)) 84 | } 85 | 86 | "listFilesRec" should "enumerate files in a directory recursively" in { 87 | val dir = new File(getClass.getResource("/io/iteratee/examples/pg").toURI) 88 | val result = Vector("11231.txt", "11231.zip") 89 | val enumerator = listFilesRec(dir) 90 | 91 | assert(F.map(enumerator.toVector)(_.map(_.getName).sorted) === F.pure(result)) 92 | } 93 | 94 | "writeLines" should "write arbitrary lines to a temporary file" in forAll(Gen.listOf(Gen.alphaStr)) { lines => 95 | val tmp = File.createTempFile("it-writeLines", ".txt") 96 | tmp.deleteOnExit() 97 | 98 | assert(enumList(lines).into(writeLines(tmp)) === F.pure(())) 99 | assert(readLines(tmp).toVector === F.pure(lines.toVector)) 100 | } 101 | 102 | "writeLinesToStream" should "write arbitrary lines to a temporary file" in forAll(Gen.listOf(Gen.alphaStr)) { lines => 103 | val tmp = File.createTempFile("it-writeLinesToStream", ".txt") 104 | tmp.deleteOnExit() 105 | val stream = new FileOutputStream(tmp) 106 | 107 | assert(enumList(lines).into(writeLinesToStream(stream)) === F.pure(())) 108 | assert(readLines(tmp).toVector === F.pure(lines.toVector)) 109 | } 110 | 111 | "writeBytes" should "write arbitrary bytes to a temporary file" in forAll { (bytes: List[Array[Byte]]) => 112 | val tmp = File.createTempFile("it-writeBytes", ".txt") 113 | tmp.deleteOnExit() 114 | 115 | assert(enumList(bytes).into(writeBytes(tmp)) === F.pure(())) 116 | assert(readBytes(tmp).toVector.map(_.flatMap(_.toVector)) === F.pure(bytes.toVector.flatten)) 117 | } 118 | 119 | "writeBytesToStream" should "write arbitrary bytes to a temporary file" in forAll { (bytes: List[Array[Byte]]) => 120 | val tmp = File.createTempFile("it-writeBytesToStream", ".txt") 121 | tmp.deleteOnExit() 122 | 123 | assert(enumList(bytes).into(writeBytes(tmp)) === F.pure(())) 124 | assert(readBytes(tmp).toVector.map(_.flatMap(_.toVector)) === F.pure(bytes.toVector.flatten)) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /testing/shared/src/main/scala/io/iteratee/testing/ArbitraryEnumerators.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.testing 2 | 3 | import io.iteratee.{Enumerator, Iteratee} 4 | import io.iteratee.modules.{EnumeratorModule, IterateeModule, Module} 5 | import org.scalacheck.{Arbitrary, Gen} 6 | 7 | trait ArbitraryEnumerators[F[_]] { 8 | this: ModuleSuite[F] with Module[F] with EnumeratorModule[F] with IterateeModule[F] => 9 | 10 | case class EnumeratorAndValues[A](enumerator: Enumerator[F, A], values: Vector[A]) { 11 | def resultWithLeftovers[Z](iteratee: Iteratee[F, A, Z]): F[(Z, Vector[A])] = enumerator.into( 12 | iteratee.flatMap(result => consume[A].map(leftovers => (result, leftovers))(F))(F) 13 | )(F) 14 | } 15 | 16 | private[this] val maxDepth = 4 17 | 18 | private[this] def appendGenerator[A: Arbitrary](depth: Int): List[Gen[EnumeratorAndValues[A]]] = 19 | if (depth < maxDepth) 20 | List( 21 | for { 22 | EnumeratorAndValues(enumerator1, list1) <- generate[A](depth + 1) 23 | EnumeratorAndValues(enumerator2, list2) <- generate[A](depth + 1) 24 | } yield EnumeratorAndValues(enumerator1.append(enumerator2)(F), list1 ++ list2) 25 | ) 26 | else Nil 27 | 28 | private[this] def generate[A](depth: Int)(implicit A: Arbitrary[A]): Gen[EnumeratorAndValues[A]] = 29 | Gen.oneOf( 30 | Gen.const(EnumeratorAndValues[A](empty, Vector.empty)), 31 | A.arbitrary.map(a => EnumeratorAndValues(enumOne(a), Vector(a))), 32 | ( 33 | for { 34 | vals <- Gen.listOf(A.arbitrary) 35 | enumerator <- Gen.oneOf( 36 | enumStream(vals.toStream), 37 | enumList(vals), 38 | enumVector(vals.toVector) 39 | ) 40 | } yield EnumeratorAndValues(enumerator, vals.toVector) 41 | ) :: appendGenerator[A](depth): _* 42 | ) 43 | 44 | implicit def arbitraryEnumeratorAndValues[A: Arbitrary]: Arbitrary[EnumeratorAndValues[A]] = 45 | Arbitrary(generate(0)) 46 | } 47 | -------------------------------------------------------------------------------- /testing/shared/src/main/scala/io/iteratee/testing/ArbitraryInstances.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.testing 2 | 3 | import cats.Monad 4 | import io.iteratee.{Enumeratee, Enumerator, Iteratee} 5 | import org.scalacheck.{Arbitrary, Gen} 6 | import scala.Predef._ 7 | 8 | trait ArbitraryInstances { 9 | implicit def arbitraryEnumerator[F[_]: Monad, A](implicit 10 | A: Arbitrary[A] 11 | ): Arbitrary[Enumerator[F, A]] = 12 | Arbitrary( 13 | for { 14 | n <- Gen.chooseNum(0, 16) 15 | as <- Gen.containerOfN[List, A](n, A.arbitrary) 16 | en <- Gen.oneOf( 17 | Enumerator.enumList[F, A](as), 18 | Enumerator.enumStream[F, A](as.toStream) 19 | ) 20 | } yield en 21 | ) 22 | 23 | implicit def arbitraryIntIteratee[F[_]: Monad]: Arbitrary[Iteratee[F, Int, Int]] = { 24 | val M: Monad[({ type L[x] = Iteratee[F, Int, x] })#L] = implicitly 25 | val F = Iteratee.fold[F, Int, Int](0)(_ + _) 26 | 27 | Arbitrary( 28 | for { 29 | n <- Gen.chooseNum(0, 16) 30 | a <- Arbitrary.arbitrary[Int] 31 | r <- Arbitrary.arbitrary[Vector[Int]] 32 | it <- Gen.oneOf[Iteratee[F, Int, Int]]( 33 | Iteratee.done[F, Int, Int](a), 34 | Iteratee.drop[F, Int](n).flatMap(_ => F), 35 | Iteratee.drop[F, Int](n).flatMap(_ => M.pure(a)), 36 | Iteratee.head[F, Int].map(_.getOrElse(0)), 37 | Iteratee.peek[F, Int].flatMap(_ => F), 38 | Iteratee.peek[F, Int].flatMap(head => M.pure(a + head.getOrElse(0))), 39 | Iteratee.take[F, Int](n).flatMap(taken => M.pure(taken.sum)), 40 | Iteratee.identity[F, Int].flatMap(_ => F), 41 | Iteratee.identity[F, Int].flatMap(_ => M.pure(a)) 42 | ) 43 | } yield it 44 | ) 45 | } 46 | 47 | implicit def arbitraryVectorIteratee[F[_]: Monad, A](implicit 48 | A: Arbitrary[A] 49 | ): Arbitrary[Iteratee[F, Vector[A], Vector[A]]] = { 50 | val M: Monad[({ type L[x] = Iteratee[F, Vector[A], x] })#L] = implicitly 51 | val F = Iteratee.fold[F, Vector[A], Vector[A]](Vector.empty)(_ ++ _) 52 | 53 | Arbitrary( 54 | for { 55 | n <- Gen.chooseNum(0, 16) 56 | asSize <- Gen.chooseNum(0, 128) 57 | as <- Gen.containerOfN[Vector, A](asSize, A.arbitrary) 58 | rSize <- Gen.chooseNum(0, 128) 59 | r <- Gen.containerOfN[Vector, Vector[A]](rSize, Arbitrary.arbitrary[Vector[A]]) 60 | it <- Gen.oneOf[Iteratee[F, Vector[A], Vector[A]]]( 61 | Iteratee.done[F, Vector[A], Vector[A]](as), 62 | Iteratee.drop[F, Vector[A]](n).flatMap(_ => F), 63 | Iteratee.drop[F, Vector[A]](n).flatMap(_ => M.pure(as)), 64 | Iteratee.head[F, Vector[A]].map(_.getOrElse(Vector.empty)), 65 | Iteratee.peek[F, Vector[A]].flatMap(_ => F), 66 | Iteratee.peek[F, Vector[A]].flatMap(head => M.pure(as ++ head.fold(Vector.empty[A])(_.take(n)))), 67 | Iteratee.take[F, Vector[A]](n).flatMap(taken => M.pure(taken.flatMap(_.headOption))), 68 | Iteratee.identity[F, Vector[A]].flatMap(_ => F), 69 | Iteratee.identity[F, Vector[A]].flatMap(_ => M.pure(as)) 70 | ) 71 | } yield it 72 | ) 73 | } 74 | 75 | implicit def arbitraryVectorUnitIteratee[F[_]: Monad, A](implicit 76 | A: Arbitrary[A] 77 | ): Arbitrary[Iteratee[F, Vector[A], Unit]] = Arbitrary( 78 | arbitraryVectorIteratee[F, A].arbitrary.map(_.discard) 79 | ) 80 | 81 | implicit def arbitraryFunctionIteratee[F[_]: Monad, A]: Arbitrary[Iteratee[F, A, Vector[Int] => Vector[Int]]] = { 82 | val M: Monad[({ type L[x] = Iteratee[F, A, x] })#L] = implicitly 83 | 84 | Arbitrary( 85 | Gen 86 | .oneOf( 87 | (as: Vector[Int]) => Vector(as.size), 88 | (as: Vector[Int]) => as, 89 | (as: Vector[Int]) => as.drop(2), 90 | (as: Vector[Int]) => as.map(_ * 2) 91 | ) 92 | .map(M.pure(_)) 93 | ) 94 | } 95 | 96 | implicit def arbitraryEnumeratee[F[_]: Monad]: Arbitrary[Enumeratee[F, Int, Int]] = 97 | Arbitrary( 98 | for { 99 | a <- Arbitrary.arbitrary[Int] 100 | f <- Arbitrary.arbitrary[Int => Int] 101 | en <- arbitraryEnumerator[F, Int].arbitrary 102 | et <- Gen.oneOf[Enumeratee[F, Int, Int]]( 103 | Enumeratee.map[F, Int, Int](f), 104 | Enumeratee.map[F, Int, Int](_ + 1), 105 | Enumeratee.map[F, Int, Int](_ => a), 106 | Enumeratee.flatMap[F, Int, Int](_ => en), 107 | Enumeratee.filter[F, Int](_ % 2 == 0), 108 | Enumeratee.collect[F, Int, Int] { 109 | case i if i.toString.last != '0' => i 110 | }, 111 | Enumeratee.sequenceI[F, Int, Int](Iteratee.take(2).map(_.head)), 112 | Enumeratee.uniq[F, Int] 113 | ) 114 | } yield et 115 | ) 116 | } 117 | -------------------------------------------------------------------------------- /testing/shared/src/main/scala/io/iteratee/testing/BaseSuite.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.testing 2 | 3 | import cats.instances.AllInstances 4 | import cats.kernel.Eq 5 | import cats.syntax.AllSyntax 6 | import io.iteratee.modules.{EnumeratorModule, IterateeModule, Module} 7 | import org.scalatest.flatspec.AnyFlatSpec 8 | import org.scalatestplus.scalacheck.{Checkers, ScalaCheckDrivenPropertyChecks} 9 | import org.typelevel.discipline.Laws 10 | 11 | class BaseSuite 12 | extends AnyFlatSpec 13 | with ScalaCheckDrivenPropertyChecks 14 | with AllInstances 15 | with AllSyntax 16 | with ArbitraryInstances 17 | with EqInstances { 18 | override def convertToEqualizer[T](left: T): Equalizer[T] = 19 | sys.error("Intentionally ambiguous implicit for Equalizer") 20 | 21 | def checkLaws(name: String, ruleSet: Laws#RuleSet): Unit = ruleSet.all.properties.zipWithIndex.foreach { 22 | case ((id, prop), 0) => name should s"obey $id" in Checkers.check(prop) 23 | case ((id, prop), _) => it should s"obey $id" in Checkers.check(prop) 24 | } 25 | } 26 | 27 | abstract class ModuleSuite[F[_]] extends BaseSuite with ArbitraryEnumerators[F] { 28 | this: Module[F] with EnumeratorModule[F] with IterateeModule[F] => 29 | 30 | def monadName: String 31 | implicit def eqF[A: Eq]: Eq[F[A]] 32 | } 33 | -------------------------------------------------------------------------------- /testing/shared/src/main/scala/io/iteratee/testing/EnumerateeSuite.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.testing 2 | 3 | import cats.Monad 4 | import cats.laws.discipline.{CategoryTests, ProfunctorTests} 5 | import io.iteratee.{Enumeratee, Iteratee} 6 | import io.iteratee.modules.{EnumerateeModule, EnumeratorModule, IterateeModule, Module} 7 | import org.scalacheck.{Arbitrary, Gen} 8 | import scala.Predef._ 9 | 10 | abstract class EnumerateeSuite[F[_]: Monad] extends ModuleSuite[F] { 11 | this: Module[F] with EnumerateeModule[F] with EnumeratorModule[F] with IterateeModule[F] => 12 | 13 | type EnumerateeF[O, I] = Enumeratee[F, O, I] 14 | 15 | checkLaws(s"Enumeratee[$monadName, Int, Int]", ProfunctorTests[EnumerateeF].profunctor[Int, Int, Int, Int, Int, Int]) 16 | checkLaws(s"Enumeratee[$monadName, Int, Int]", CategoryTests[EnumerateeF].category[Int, Int, Int, Int]) 17 | 18 | "into" should "transform the inputs to an iteratee" in forAll { 19 | (eav: EnumeratorAndValues[Int], iteratee: Iteratee[F, Int, Int], enumeratee: Enumeratee[F, Int, Int]) => 20 | eav.enumerator.into(enumeratee.into(iteratee)) === eav.enumerator.into(iteratee.through(enumeratee)) 21 | } 22 | 23 | "map" should "transform the stream" in forAll { (eav: EnumeratorAndValues[Int]) => 24 | assert(eav.enumerator.through(map(_ + 1)).toVector === F.pure(eav.values.map(_ + 1))) 25 | } 26 | 27 | "flatMapM" should "transform the stream with a pure effectful function" in forAll { (eav: EnumeratorAndValues[Int]) => 28 | assert(eav.enumerator.through(flatMapM(i => F.pure(i + 1))).toVector === F.pure(eav.values.map(_ + 1))) 29 | } 30 | 31 | "flatMap" should "transform the stream with a function into enumerators" in { 32 | forAll { (eav: EnumeratorAndValues[Int]) => 33 | val enumerator = eav.enumerator.through(flatMap(v => enumVector(Vector(v, v)))) 34 | 35 | assert(enumerator.toVector === F.pure(eav.values.flatMap(v => Vector(v, v)))) 36 | } 37 | } 38 | 39 | it should "work with an iteratee that stops early" in forAll { (eav: EnumeratorAndValues[Int]) => 40 | val enumerator = eav.enumerator.through(flatMap(v => enumVector(Vector(v, v)))) 41 | 42 | assert(enumerator.into(head) === F.pure(eav.values.flatMap(v => Vector(v, v)).headOption)) 43 | } 44 | 45 | "take" should "consume the specified number of values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 46 | /** 47 | * This isn't a comprehensive way to avoid SI-9581, but it seems to keep clear of the cases ScalaCheck is likely to 48 | * run into. 49 | */ 50 | whenever(n != Int.MaxValue) { 51 | val expected = F.pure((eav.values.take(n), eav.values.drop(n))) 52 | 53 | assert(eav.resultWithLeftovers(consume[Int].through(take(n.toLong))) === expected) 54 | } 55 | } 56 | 57 | it should "work with more than Int.MaxValue values" in forAll { (n: Int) => 58 | val items = Vector.fill(1000000)(()) 59 | val totalSize: Long = Int.MaxValue.toLong + math.max(1, n).toLong 60 | val enumerator = repeat(()).flatMap(_ => enumVector(items)).through(take(totalSize)) 61 | 62 | assert(enumerator.into(length) === F.pure(totalSize)) 63 | } 64 | 65 | it should "work when it ends mid-chunk" in forAll { (v: Vector[Int]) => 66 | assert(enumVector(v).through(take(v.size.toLong - 1L)).toVector === F.pure(v.dropRight(1))) 67 | } 68 | 69 | it should "work with wrap" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 70 | /** 71 | * This isn't a comprehensive way to avoid SI-9581, but it seems to keep clear of the cases ScalaCheck is likely to 72 | * run into. 73 | */ 74 | whenever(n != Int.MaxValue) { 75 | val eavNew = eav.copy(enumerator = take[Int](n.toLong).wrap(eav.enumerator)) 76 | 77 | assert(eavNew.resultWithLeftovers(consume) === F.pure((eav.values.take(n), Vector.empty))) 78 | } 79 | } 80 | 81 | "takeWhile" should "consume the specified values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 82 | assert(eav.resultWithLeftovers(consume[Int].through(takeWhile(_ < n))) === F.pure(eav.values.span(_ < n))) 83 | } 84 | 85 | it should "work with wrap" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 86 | val eavNew = eav.copy(enumerator = takeWhile[Int](_ < n).wrap(eav.enumerator)) 87 | 88 | assert(eavNew.resultWithLeftovers(consume) === F.pure((eav.values.takeWhile(_ < n), Vector.empty))) 89 | } 90 | 91 | it should "work when it ends mid-chunk" in forAll { (n: Byte) => 92 | val v = (0 to n.toInt).toVector 93 | 94 | assert(enumVector(v).through(takeWhile(_ < n.toInt)).toVector === F.pure(v.dropRight(1))) 95 | } 96 | 97 | "takeWhileM" should "consume the specified values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 98 | val result = eav.resultWithLeftovers(consume[Int].through(takeWhileM(i => F.pure(i < n)))) 99 | val expected = F.pure(eav.values.span(_ < n)) 100 | assert(result === expected) 101 | } 102 | 103 | it should "work with wrap" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 104 | val eavNew = eav.copy(enumerator = takeWhileM[Int](i => F.pure(i < n)).wrap(eav.enumerator)) 105 | 106 | assert(eavNew.resultWithLeftovers(consume) === F.pure((eav.values.takeWhile(_ < n), Vector.empty))) 107 | } 108 | 109 | it should "work when it ends mid-chunk" in forAll { (n: Byte) => 110 | val v = (0 to n.toInt).toVector 111 | 112 | assert(enumVector(v).through(takeWhileM(i => F.pure(i < n.toInt))).toVector === F.pure(v.dropRight(1))) 113 | } 114 | 115 | "drop" should "drop the specified number of values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 116 | assert(eav.resultWithLeftovers(consume[Int].through(drop(n.toLong))) === F.pure((eav.values.drop(n), Vector.empty))) 117 | } 118 | 119 | it should "work with one left over" in forAll { (v: Vector[Int]) => 120 | assert(enumVector(v).through(drop(v.size.toLong - 1L)).toVector === F.pure(v.lastOption.toVector)) 121 | } 122 | 123 | it should "work with more than Int.MaxValue values" in forAll { (n: Int) => 124 | val items = Vector.fill(1000000)(()) 125 | val totalSize: Long = Int.MaxValue.toLong + math.max(1, n).toLong 126 | val enumerator = repeat(()).flatMap(_ => enumVector(items)).through(drop(totalSize)) 127 | 128 | assert(enumerator.into(head) === F.pure(Some(()))) 129 | } 130 | 131 | "dropWhile" should "drop the specified values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 132 | val expected = F.pure((eav.values.dropWhile(_ < n), Vector.empty[Int])) 133 | 134 | assert(eav.resultWithLeftovers(consume[Int].through(dropWhile(_ < n))) === expected) 135 | } 136 | 137 | it should "work with one left over" in forAll { (n: Byte) => 138 | val v = (0 to n.toInt).toVector 139 | 140 | assert(enumVector(v).through(dropWhile(_ < n.toInt)).toVector === F.pure(v.lastOption.toVector)) 141 | } 142 | 143 | "dropWhileM" should "drop the specified values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 144 | val expected = F.pure((eav.values.dropWhile(_ < n), Vector.empty[Int])) 145 | 146 | assert(eav.resultWithLeftovers(consume[Int].through(dropWhileM(i => F.pure(i < n)))) === expected) 147 | } 148 | 149 | it should "work with one left over" in forAll { (n: Byte) => 150 | val v = (0 to n.toInt).toVector 151 | 152 | assert(enumVector(v).through(dropWhileM(i => F.pure(i < n.toInt))).toVector === F.pure(v.lastOption.toVector)) 153 | } 154 | 155 | "collect" should "filter the stream using a partial function" in { 156 | forAll { (eav: EnumeratorAndValues[Int]) => 157 | val pf: PartialFunction[Int, Int] = { 158 | case v if v % 2 == 0 => v + 1 159 | } 160 | 161 | assert(eav.enumerator.through(collect(pf)).toVector === F.pure(eav.values.collect(pf))) 162 | } 163 | } 164 | 165 | "filter" should "filter the stream" in forAll { (eav: EnumeratorAndValues[Int]) => 166 | val p: Int => Boolean = _ % 2 == 0 167 | 168 | assert(eav.enumerator.through(filter(p)).toVector === F.pure(eav.values.filter(p))) 169 | } 170 | 171 | "filterM" should "filter the stream with a pure effectful function" in forAll { (eav: EnumeratorAndValues[Int]) => 172 | val p: Int => Boolean = _ % 2 == 0 173 | val fp: Int => F[Boolean] = i => F.pure(p(i)) 174 | 175 | assert(eav.enumerator.through(filterM(fp)).toVector === F.pure(eav.values.filter(p))) 176 | } 177 | 178 | "sequenceI" should "repeatedly apply an iteratee" in { 179 | forAll(Arbitrary.arbitrary[EnumeratorAndValues[Int]], Gen.posNum[Int]) { (eav, n) => 180 | assert(eav.enumerator.through(sequenceI(takeI(n))).toVector === F.pure(eav.values.grouped(n).toVector)) 181 | } 182 | } 183 | 184 | it should "work with an iteratee that stops early" in { 185 | forAll(Arbitrary.arbitrary[EnumeratorAndValues[Int]], Gen.posNum[Int]) { (eav, n) => 186 | val expected = eav.values.grouped(n).toVector.headOption 187 | 188 | assert(eav.enumerator.through(sequenceI(takeI(n))).into(head) === F.pure(expected)) 189 | } 190 | } 191 | 192 | it should "be stack safe even for large chunks" in { 193 | val groupedSize = 3 194 | val xs = (0 until 10000).toVector 195 | val expected = xs.grouped(groupedSize).size.toLong 196 | 197 | assert(enumVector(xs).sequenceI(takeI(groupedSize)).into(length) === F.pure(expected)) 198 | } 199 | 200 | "scan" should "match the standard library's scanLeft" in { 201 | forAll { (eav: EnumeratorAndValues[Int], init: String, f: (String, Int) => String) => 202 | assert(eav.enumerator.through(scan(init)(f)).toVector === F.pure(eav.values.scanLeft(init)(f))) 203 | } 204 | } 205 | 206 | "scanM" should "match the standard library's scanLeft with pure" in { 207 | forAll { (eav: EnumeratorAndValues[Int], init: String, f: (String, Int) => String) => 208 | val ff: (String, Int) => F[String] = (s, i) => F.pure(f(s, i)) 209 | 210 | assert(eav.enumerator.scanM(init)(ff).toVector === F.pure(eav.values.scanLeft(init)(f))) 211 | } 212 | } 213 | 214 | "remainderWithResult" should "return an empty result for iteratees that consume all input" in { 215 | forAll { (eav: EnumeratorAndValues[Int]) => 216 | val enumeratee = remainderWithResult(consume[Int])((r, i) => r) 217 | 218 | assert(eav.enumerator.through(enumeratee).toVector === F.pure(Vector.empty)) 219 | } 220 | } 221 | 222 | it should "add the first n values to subsequent values" in { 223 | forAll { (eav: EnumeratorAndValues[Int], n: Byte) => 224 | val enumeratee = remainderWithResult(takeI[Int](n.toInt))((r, i) => i + r.sum) 225 | 226 | val (firstN, rest) = eav.values.splitAt(n.toInt) 227 | val expected = rest.map(_ + firstN.sum) 228 | 229 | assert(eav.enumerator.through(enumeratee).toVector === F.pure(expected)) 230 | } 231 | } 232 | 233 | "remainderWithResultM" should "return an empty result for iteratees that consume all input" in { 234 | forAll { (eav: EnumeratorAndValues[Int]) => 235 | val enumeratee = remainderWithResultM(consume[Int])((r, i) => F.pure(r)) 236 | 237 | assert(eav.enumerator.through(enumeratee).toVector === F.pure(Vector.empty)) 238 | } 239 | } 240 | 241 | it should "add the first n values to subsequent values" in { 242 | forAll { (eav: EnumeratorAndValues[Int], n: Byte) => 243 | val enumeratee = remainderWithResultM(takeI[Int](n.toInt))((r, i) => F.pure(i + r.sum)) 244 | 245 | val (firstN, rest) = eav.values.splitAt(n.toInt) 246 | val expected = rest.map(_ + firstN.sum) 247 | 248 | assert(eav.enumerator.through(enumeratee).toVector === F.pure(expected)) 249 | } 250 | } 251 | 252 | "uniq" should "drop duplicate values" in forAll { (xs: Vector[Int]) => 253 | val sorted = xs.sorted 254 | 255 | assert(enumVector(sorted).through(uniq).toVector === F.pure(sorted.distinct)) 256 | } 257 | 258 | it should "work with an iteratee that stops early" in forAll { (xs: Vector[Int]) => 259 | val sorted = xs.sorted 260 | 261 | assert(enumVector(sorted).through(uniq).into(head) === F.pure(sorted.distinct.headOption)) 262 | } 263 | 264 | it should "work with known duplicates" in { 265 | val enumerator = enumVector(Vector(1, 2, 3, 4)) 266 | .append(enumVector(Vector(4, 5, 6, 7))) 267 | .append(enumOne(7)) 268 | .append(enumOne(8)) 269 | .append(enumVector(Vector(8, 8, 8))) 270 | .append(enumVector(Vector(9, 10))) 271 | val result = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 272 | 273 | assert(enumerator.through(uniq).toVector === F.pure(result)) 274 | } 275 | 276 | "zipWithIndex" should "zip a stream's values with their indices" in forAll { (eav: EnumeratorAndValues[Int]) => 277 | val result = eav.values.zipWithIndex.map { case (v, i) => 278 | (v, i.toLong) 279 | } 280 | 281 | assert(eav.enumerator.through(zipWithIndex).toVector === F.pure(result)) 282 | } 283 | 284 | it should "work with an iteratee that stops early" in forAll { (eav: EnumeratorAndValues[Int]) => 285 | val result = eav.values.zipWithIndex.map { case (v, i) => 286 | (v, i.toLong) 287 | } 288 | 289 | assert(eav.enumerator.through(zipWithIndex).into(head) === F.pure(result.headOption)) 290 | } 291 | 292 | "grouped" should "group values from the stream" in { 293 | forAll(Arbitrary.arbitrary[EnumeratorAndValues[Int]], Gen.posNum[Int]) { (eav, n) => 294 | assert(eav.enumerator.through(grouped(n)).toVector === F.pure(eav.values.grouped(n).toVector)) 295 | } 296 | } 297 | 298 | "splitOn" should "split the stream on a predicate" in forAll { (eav: EnumeratorAndValues[Int]) => 299 | val p: Int => Boolean = _ % 2 == 0 300 | 301 | def splitOnEvens(xs: Vector[Int]): Vector[Vector[Int]] = if (xs.isEmpty) Vector.empty 302 | else { 303 | val (before, after) = xs.span(x => !p(x)) 304 | 305 | before +: splitOnEvens(after.drop(1)) 306 | } 307 | 308 | assert(eav.enumerator.through(splitOn(p)).toVector === F.pure(splitOnEvens(eav.values))) 309 | } 310 | 311 | "cross" should "take the cross product of two enumerators" in { 312 | forAll { (eav1: EnumeratorAndValues[Int], eav2: EnumeratorAndValues[Int]) => 313 | val result = for { 314 | v1 <- eav1.values 315 | v2 <- eav2.values 316 | } yield (v1, v2) 317 | 318 | assert(eav1.enumerator.through(cross(eav2.enumerator)).toVector === F.pure(result)) 319 | } 320 | } 321 | 322 | "intersperse" should "intersperse values in the stream with a delimiter" in { 323 | forAll { (eav: EnumeratorAndValues[Int], delim: Int) => 324 | val expected = eav.values 325 | .zip(Stream.continually(delim)) 326 | .flatMap { case (x, y) => 327 | Vector(x, y) 328 | } 329 | .dropRight(1) 330 | 331 | assert(eav.resultWithLeftovers(consume[Int].through(intersperse(delim))) === F.pure((expected, Vector.empty))) 332 | } 333 | } 334 | 335 | "injectValue" should "add a value at the head of the stream" in { 336 | forAll { (eav: EnumeratorAndValues[Int], e: Int) => 337 | val expected = e +: eav.values 338 | 339 | assert(eav.resultWithLeftovers(consume[Int].through(injectValue(e))) === F.pure((expected, Vector.empty))) 340 | } 341 | } 342 | 343 | "injectValues" should "add values at the head of the stream" in { 344 | forAll { (eav: EnumeratorAndValues[Int], es: Seq[Int]) => 345 | val expected = es.toVector ++ eav.values 346 | 347 | assert(eav.resultWithLeftovers(consume[Int].through(injectValues(es))) === F.pure((expected, Vector.empty))) 348 | } 349 | } 350 | 351 | "chunks" should "observe chunks" in forAll { (vs: Vector[Vector[Int]]) => 352 | val cs = vs.filter(_.nonEmpty) 353 | 354 | val enumerator = cs.foldLeft(empty[Int]) { case (e, chunk) => 355 | e.append(enumVector(chunk)) 356 | } 357 | 358 | assert(enumerator.through(Enumeratee.chunks[F, Int]).toVector === F.pure(cs)) 359 | } 360 | 361 | "rechunk" should "work correctly" in forAll { (eav: EnumeratorAndValues[Int], n: Byte) => 362 | val expected = eav.values.grouped(if (n > 0) n.toInt else 1).toVector 363 | val enumeratee = Enumeratee.rechunk[F, Int](n.toInt).andThen(Enumeratee.chunks) 364 | 365 | assert(eav.enumerator.through(enumeratee).toVector === F.pure(expected)) 366 | } 367 | 368 | it should "correctly handle some corner cases" in { 369 | val enumerator1 = enumIndexedSeq(0 to 20).through(Enumeratee.rechunk(5)) 370 | val enumerator2 = iterate(0)(_ + 1).through(Enumeratee.rechunk(5)) 371 | val enumerator3 = enumVector((0 until 5).toVector) 372 | val enumerator4 = enumerator3.append(enumerator3).through(Enumeratee.rechunk(5)) 373 | 374 | assert(enumerator1.into(takeI(6)) === F.pure((0 until 6).toVector)) 375 | assert(enumerator2.into(takeI(6)) === F.pure((0 until 6).toVector)) 376 | assert(enumerator4.toVector === F.pure(((0 until 5) ++ (0 until 5)).toVector)) 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /testing/shared/src/main/scala/io/iteratee/testing/EnumeratorSuite.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.testing 2 | 3 | import cats.{Eval, Monad} 4 | import cats.kernel.laws.discipline.MonoidTests 5 | import cats.laws.discipline.{MonadTests, SemigroupalTests} 6 | import io.iteratee.Enumerator 7 | import io.iteratee.modules.{EnumerateeModule, EnumeratorModule, IterateeModule, Module} 8 | import scala.Predef._ 9 | 10 | abstract class EnumeratorSuite[F[_]: Monad] extends ModuleSuite[F] { 11 | this: Module[F] with EnumerateeModule[F] with EnumeratorModule[F] with IterateeModule[F] => 12 | 13 | type EnumeratorF[E] = Enumerator[F, E] 14 | 15 | implicit val isomorphisms: SemigroupalTests.Isomorphisms[EnumeratorF] = 16 | SemigroupalTests.Isomorphisms.invariant[EnumeratorF] 17 | 18 | checkLaws(s"Enumerator[$monadName, Int]", MonoidTests[Enumerator[F, Int]].monoid) 19 | checkLaws(s"Enumerator[$monadName, Int]", MonadTests[EnumeratorF].monad[Int, Int, Int]) 20 | 21 | "liftToEnumerator" should "lift a value in a context into an enumerator" in forAll { (i: Int) => 22 | assert(liftToEnumerator(F.pure(i)).toVector === F.pure(Vector(i))) 23 | } 24 | 25 | "liftMEval" should "lift a value in a context into an enumerator" in forAll { (i: Int) => 26 | var counter = 0 27 | val eval = Eval.later { 28 | counter += i 29 | F.pure(i) 30 | } 31 | 32 | val enumerator = Enumerator.liftMEval(eval) 33 | 34 | assert(counter === 0) 35 | assert(enumerator.toVector === F.pure(Vector(i))) 36 | assert(counter === i) 37 | } 38 | 39 | "enumerate" should "enumerate varargs values" in forAll { (xs: List[Int]) => 40 | assert(enumerate(xs: _*).toVector === F.pure(xs.toVector)) 41 | } 42 | 43 | "empty" should "not enumerate any values" in { 44 | assert(empty[Int].toVector === F.pure(Vector.empty)) 45 | } 46 | 47 | "enumOne" should "enumerate a single value" in forAll { (i: Int) => 48 | assert(enumOne(i).toVector === F.pure(Vector(i))) 49 | } 50 | 51 | "enumIterable" should "enumerate values from an iterable" in forAll { (xs: Iterable[Int], chunkSize: Int) => 52 | assert(enumIterable(xs, chunkSize).toVector === F.pure(xs.toVector)) 53 | } 54 | 55 | "enumStream" should "enumerate values from a stream" in forAll { (xs: Stream[Int], chunkSize: Int) => 56 | assert(enumStream(xs, chunkSize).toVector === F.pure(xs.toVector)) 57 | } 58 | 59 | "enumList" should "enumerate values from a list" in forAll { (xs: List[Int]) => 60 | assert(enumList(xs).toVector === F.pure(xs.toVector)) 61 | } 62 | 63 | "enumVector" should "enumerate values from a vector" in forAll { (xs: Vector[Int]) => 64 | assert(enumVector(xs).toVector === F.pure(xs)) 65 | } 66 | 67 | it should "enumerate a vector with a single element" in forAll { (x: Int) => 68 | assert(enumVector(Vector(x)).toVector === F.pure(Vector(x))) 69 | } 70 | 71 | "enumIndexedSeq" should "enumerate a slice of values from an indexed sequence" in { 72 | forAll { (xs: Vector[Int], start: Int, count: Int) => 73 | // Check for overflow (workaround for #11990 in 2.13.2). 74 | val until = if (start + count < 0) Int.MaxValue else start + count 75 | 76 | assert(enumIndexedSeq(xs, start, until).toVector === F.pure(xs.slice(start, until))) 77 | } 78 | } 79 | 80 | it should "enumerate a slice of the first hundred values from an indexed sequence" in { 81 | forAll { (xs: Vector[Int]) => 82 | assert(enumIndexedSeq(xs, 0, 100).toVector === F.pure(xs.slice(0, 100))) 83 | } 84 | } 85 | 86 | "repeat" should "repeat a value" in forAll { (i: Int, count: Short) => 87 | assert(repeat(i).into(takeI(count.toInt)) === F.pure(Vector.fill(count.toInt)(i))) 88 | } 89 | 90 | "iterate" should "enumerate values by applying a function iteratively" in forAll { (n: Int, count: Short) => 91 | assert(iterate(n)(_ + 1).into(takeI(count.toInt)) === F.pure(Vector.iterate(n, count.toInt)(_ + 1))) 92 | } 93 | 94 | "iterateM" should "enumerate values by applying a pure function iteratively" in { 95 | forAll { (n: Int, count: Short) => 96 | assert(iterateM(n)(i => F.pure(i + 1)).into(takeI(count.toInt)) === F.pure(Vector.iterate(n, count.toInt)(_ + 1))) 97 | } 98 | } 99 | 100 | "iterateUntil" should "apply a function until it returns an empty result" in forAll { (n: Short) => 101 | val count = math.abs(n.toInt) 102 | val enumerator = iterateUntil(0)(i => if (i == count) None else Some(i + 1)) 103 | 104 | assert(enumerator.toVector === F.pure((0 to count).toVector)) 105 | } 106 | 107 | it should "work with finished iteratee (#71)" in forAll { (n: Short, fewer: Byte) => 108 | val count = math.abs(n.toInt) 109 | val taken = n - math.abs(fewer.toInt) 110 | val enumerator = iterateUntil(0)(i => if (i == count) None else Some(i + 1)) 111 | 112 | assert(enumerator.into(takeI(taken)) === F.pure((0 to count).toVector.take(taken))) 113 | } 114 | 115 | "iterateUntilM" should "apply a pure function until it returns an empty result" in forAll { (n: Short) => 116 | val count = math.abs(n.toInt) 117 | val enumerator = iterateUntilM(0)(i => F.pure(if (i == count) None else Some(i + 1))) 118 | 119 | assert(enumerator.toVector === F.pure((0 to count).toVector)) 120 | } 121 | 122 | it should "work with finished iteratee (#71)" in forAll { (n: Short, fewer: Byte) => 123 | val count = math.abs(n.toInt) 124 | val taken = n - math.abs(fewer.toInt) 125 | val enumerator = iterateUntilM(0)(i => F.pure(if (i == count) None else Some(i + 1))) 126 | 127 | assert(enumerator.into(takeI(taken)) === F.pure((0 to count).toVector.take(taken))) 128 | } 129 | 130 | "toVector" should "collect all the values in the stream" in forAll { (eav: EnumeratorAndValues[Int]) => 131 | assert(eav.enumerator.toVector === F.pure(eav.values)) 132 | } 133 | 134 | "prepend" should "prepend a value to a stream" in forAll { (eav: EnumeratorAndValues[Int], v: Int) => 135 | assert(eav.enumerator.prepend(v).toVector === F.pure(v +: eav.values)) 136 | } 137 | 138 | it should "work with a done iteratee" in { 139 | assert(enumOne(0).append(enumOne(2).prepend(1)).into(head) === F.pure((Some(0)))) 140 | } 141 | 142 | "bindM" should "bind through Option" in forAll { (eav: EnumeratorAndValues[Int]) => 143 | val enumeratorF: F[Option[Enumerator[F, String]]] = eav.enumerator.bindM(v => Option(enumOne(v.toString))) 144 | 145 | assert(enumeratorF.map(_.map(_.toVector)) === F.pure(Option(F.pure(eav.values.map(_.toString))))) 146 | } 147 | 148 | "intoEnumerator" should "be available on values in a context" in forAll { (i: Int) => 149 | import syntax._ 150 | 151 | assert(F.pure(i).intoEnumerator.toVector === F.pure(Vector(i))) 152 | } 153 | 154 | "flatten" should "collapse enumerated values in the context" in forAll { (v: Int) => 155 | assert(enumOne(F.pure(v)).flatten[Int].toVector === F.pure(Vector(v))) 156 | } 157 | 158 | "reduced" should "reduce the stream with a function" in forAll { (eav: EnumeratorAndValues[Int]) => 159 | assert(eav.enumerator.reduced(Vector.empty[Int])(_ :+ _).toVector === F.pure(Vector(eav.values))) 160 | } 161 | 162 | it should "reduce the stream with a pure function" in forAll { (eav: EnumeratorAndValues[Int]) => 163 | assert(eav.enumerator.reducedM(Vector.empty[Int])((i, s) => F.pure(i :+ s)).toVector === F.pure(Vector(eav.values))) 164 | } 165 | 166 | "map" should "transform the stream" in forAll { (eav: EnumeratorAndValues[Int]) => 167 | assert(eav.enumerator.map(_ + 1).toVector === F.pure(eav.values.map(_ + 1))) 168 | } 169 | 170 | "flatMapM" should "transform the stream with a pure effectful function" in forAll { (eav: EnumeratorAndValues[Int]) => 171 | assert(eav.enumerator.flatMapM(i => F.pure(i + 1)).toVector === F.pure(eav.values.map(_ + 1))) 172 | } 173 | 174 | "flatMap" should "transform the stream with a function into enumerators" in { 175 | forAll { (eav: EnumeratorAndValues[Int]) => 176 | val enumerator = eav.enumerator.flatMap(v => enumVector(Vector(v, v))) 177 | 178 | assert(enumerator.toVector === F.pure(eav.values.flatMap(v => Vector(v, v)))) 179 | } 180 | } 181 | 182 | "take" should "match using an Enumeratee directly" in forAll { (eav: EnumeratorAndValues[Int], n: Long) => 183 | assert(eav.enumerator.take(n) === eav.enumerator.through(take(n))) 184 | } 185 | 186 | "takeWhile" should "match using an Enumeratee directly" in { 187 | forAll { (eav: EnumeratorAndValues[Int], p: Int => Boolean) => 188 | assert(eav.enumerator.takeWhile(p) === eav.enumerator.through(takeWhile(p))) 189 | } 190 | } 191 | 192 | "takeWhileM" should "match using an Enumeratee directly" in { 193 | forAll { (eav: EnumeratorAndValues[Int], p: Int => Boolean) => 194 | assert(eav.enumerator.takeWhileM(p.andThen(F.pure)) === eav.enumerator.through(takeWhileM(p.andThen(F.pure)))) 195 | } 196 | } 197 | 198 | "drop" should "match using an Enumeratee directly" in forAll { (eav: EnumeratorAndValues[Int], n: Long) => 199 | assert(eav.enumerator.drop(n) === eav.enumerator.through(drop(n))) 200 | } 201 | 202 | "dropWhile" should "match using an Enumeratee directly" in { 203 | forAll { (eav: EnumeratorAndValues[Int], p: Int => Boolean) => 204 | assert(eav.enumerator.dropWhile(p) === eav.enumerator.through(dropWhile(p))) 205 | } 206 | } 207 | 208 | "dropWhileM" should "match using an Enumeratee directly" in { 209 | forAll { (eav: EnumeratorAndValues[Int], p: Int => Boolean) => 210 | assert(eav.enumerator.dropWhileM(p.andThen(F.pure)) === eav.enumerator.through(dropWhileM(p.andThen(F.pure)))) 211 | } 212 | } 213 | 214 | "scan" should "match using an Enumeratee directly" in { 215 | forAll { (eav: EnumeratorAndValues[Int], init: String, f: (String, Int) => String) => 216 | assert(eav.enumerator.scan(init)(f) === eav.enumerator.through(scan(init)(f))) 217 | } 218 | } 219 | 220 | "scanM" should "match using an Enumeratee directly" in { 221 | forAll { (eav: EnumeratorAndValues[Int], init: String, f: (String, Int) => String) => 222 | val ff: (String, Int) => F[String] = (s, i) => F.pure(f(s, i)) 223 | 224 | assert(eav.enumerator.scanM(init)(ff) === eav.enumerator.through(scanM(init)(ff))) 225 | } 226 | } 227 | 228 | "collect" should "match using an Enumeratee directly" in { 229 | forAll { (eav: EnumeratorAndValues[Int], f: Int => Option[String]) => 230 | val pf: PartialFunction[Int, String] = { 231 | case x if f(x).isDefined => f(x).get 232 | } 233 | assert(eav.enumerator.collect(pf) === eav.enumerator.through(collect(pf))) 234 | } 235 | } 236 | 237 | "filter" should "match using an Enumeratee directly" in forAll { (eav: EnumeratorAndValues[Int], p: Int => Boolean) => 238 | assert(eav.enumerator.filter(p) === eav.enumerator.through(filter(p))) 239 | } 240 | 241 | "filterM" should "match using an Enumeratee directly" in { 242 | forAll { (eav: EnumeratorAndValues[Int], p: Int => Boolean) => 243 | assert(eav.enumerator.filterM(p.andThen(F.pure)) === eav.enumerator.through(filterM(p.andThen(F.pure)))) 244 | } 245 | } 246 | 247 | "sequenceI" should "match using an Enumeratee directly" in forAll { (eav: EnumeratorAndValues[Int]) => 248 | assert(eav.enumerator.sequenceI(takeI(2)) === eav.enumerator.through(sequenceI(takeI(2)))) 249 | } 250 | 251 | "uniq" should "match using an Enumeratee directly" in forAll { (eav: EnumeratorAndValues[Int]) => 252 | assert(eav.enumerator.uniq === eav.enumerator.through(uniq)) 253 | } 254 | 255 | "zipWithIndex" should "match using an Enumeratee directly" in forAll { (eav: EnumeratorAndValues[Int]) => 256 | assert(eav.enumerator.zipWithIndex === eav.enumerator.through(zipWithIndex)) 257 | } 258 | 259 | "grouped" should "match using an Enumeratee directly" in forAll { (eav: EnumeratorAndValues[Int]) => 260 | assert(eav.enumerator.grouped(2) === eav.enumerator.through(grouped(2))) 261 | } 262 | 263 | "splitOn" should "match using an Enumeratee directly" in { 264 | forAll { (eav: EnumeratorAndValues[Int], p: Int => Boolean) => 265 | assert(eav.enumerator.splitOn(p) === eav.enumerator.through(splitOn(p))) 266 | } 267 | } 268 | 269 | "cross" should "match using an Enumeratee directly" in forAll { (eav: EnumeratorAndValues[Int]) => 270 | assert(eav.enumerator.cross(enumList(List(1, 2))) === eav.enumerator.through(cross(enumList(List(1, 2))))) 271 | } 272 | 273 | "intersperse" should "match using an Enumeratee directly" in forAll { (eav: EnumeratorAndValues[Int]) => 274 | assert(eav.enumerator.intersperse(-1) === eav.enumerator.through(intersperse(-1))) 275 | } 276 | } 277 | 278 | abstract class StackSafeEnumeratorSuite[F[_]: Monad] extends EnumeratorSuite[F] { 279 | this: Module[F] with EnumerateeModule[F] with EnumeratorModule[F] with IterateeModule[F] => 280 | 281 | "StackUnsafe.enumStream" should "be consistent with enumStream" in forAll { (xs: Stream[Int]) => 282 | val expected = enumStream(xs).toVector 283 | 284 | assert(Enumerator.StackUnsafe.enumStream[F, Int](xs).toVector === expected) 285 | } 286 | 287 | "StackUnsafe.repeat" should "be consistent with repeat" in forAll { (i: Int, count: Short) => 288 | val expected = repeat(i).into(takeI(count.toInt)) 289 | 290 | assert(Enumerator.StackUnsafe.repeat[F, Int](i).into(takeI(count.toInt)) === expected) 291 | } 292 | 293 | "StackUnsafe.iterate" should "be consistent with iterate" in forAll { (n: Int, count: Short) => 294 | val expected = iterate(n)(_ + 1).into(takeI(count.toInt)) 295 | 296 | assert(Enumerator.StackUnsafe.iterate[F, Int](n)(_ + 1).into(takeI(count.toInt)) === expected) 297 | } 298 | 299 | "StackUnsafe.iterateM" should "be consistent with iterateM" in forAll { (n: Int, count: Short) => 300 | val expected = iterateM(n)(i => F.pure(i + 1)).into(takeI(count.toInt)) 301 | 302 | assert(Enumerator.StackUnsafe.iterateM[F, Int](n)(i => F.pure(i + 1)).into(takeI(count.toInt)) === expected) 303 | } 304 | 305 | "StackUnsafe.iterateUntil" should "be consistent with iterateUntil" in forAll { (n: Short) => 306 | val count = math.abs(n.toInt) 307 | val expected = iterateUntil(0)(i => if (i == count) None else Some(i + 1)).toVector 308 | val enumerator = Enumerator.StackUnsafe.iterateUntil[F, Int](0)(i => if (i == count) None else Some(i + 1)) 309 | 310 | assert(enumerator.toVector === expected) 311 | } 312 | 313 | "StackUnsafe.iterateUntilM" should "be consistent with iterateUntilM" in forAll { (n: Short) => 314 | val count = math.abs(n.toInt) 315 | val expected = iterateUntilM(0)(i => F.pure(if (i == count) None else Some(i + 1))).toVector 316 | val enumerator = Enumerator.StackUnsafe.iterateUntilM[F, Int](0)(i => F.pure(if (i == count) None else Some(i + 1))) 317 | 318 | assert(enumerator.toVector === expected) 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /testing/shared/src/main/scala/io/iteratee/testing/EqInstances.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.testing 2 | 3 | import cats.Monad 4 | import cats.kernel.Eq 5 | import io.iteratee.{Enumeratee, Enumerator, Iteratee} 6 | import org.scalacheck.Arbitrary 7 | import scala.Predef._ 8 | 9 | trait EqInstances { 10 | implicit def eqThrowable: Eq[Throwable] = Eq.fromUniversalEquals 11 | 12 | implicit def eqEnumerator[F[_]: Monad, A: Eq](implicit eqFVA: Eq[F[Vector[A]]]): Eq[Enumerator[F, A]] = 13 | Eq.by(_.toVector) 14 | 15 | implicit def eqIteratee[F[_]: Monad, A: Eq: Arbitrary, B: Eq: Arbitrary](implicit 16 | eqFB: Eq[F[B]] 17 | ): Eq[Iteratee[F, A, B]] = { 18 | val e0 = Enumerator.empty[F, A] 19 | val e1 = Enumerator.enumList[F, A](Arbitrary.arbitrary[List[A]].sample.get) 20 | val e2 = Enumerator.enumStream[F, A](Arbitrary.arbitrary[Stream[A]].sample.get) 21 | val e3 = Enumerator.enumVector[F, A](Arbitrary.arbitrary[Vector[A]].sample.get) 22 | 23 | Eq.instance { (i, j) => 24 | eqFB.eqv(e0.into(i), e0.into(j)) && 25 | eqFB.eqv(e1.into(i), e1.into(j)) && 26 | eqFB.eqv(e2.into(i), e2.into(j)) && 27 | eqFB.eqv(e3.into(i), e3.into(j)) 28 | } 29 | } 30 | 31 | implicit def eqEnumeratee[F[_]: Monad, A: Eq: Arbitrary, B: Eq: Arbitrary](implicit 32 | eqFVB: Eq[F[Vector[B]]] 33 | ): Eq[Enumeratee[F, A, B]] = { 34 | val e0 = Enumerator.empty[F, A] 35 | val e1 = Enumerator.enumList[F, A](Arbitrary.arbitrary[List[A]].sample.get) 36 | val e2 = Enumerator.enumStream[F, A](Arbitrary.arbitrary[Stream[A]].sample.get) 37 | val e3 = Enumerator.enumVector[F, A](Arbitrary.arbitrary[Vector[A]].sample.get) 38 | 39 | Eq.instance { (i, j) => 40 | eqFVB.eqv(e0.through(i).toVector, e0.through(j).toVector) && 41 | eqFVB.eqv(e1.through(i).toVector, e1.through(j).toVector) && 42 | eqFVB.eqv(e2.through(i).toVector, e2.through(j).toVector) && 43 | eqFVB.eqv(e3.through(i).toVector, e3.through(j).toVector) 44 | } 45 | } 46 | } 47 | 48 | object EqInstances extends EqInstances 49 | -------------------------------------------------------------------------------- /testing/shared/src/main/scala/io/iteratee/testing/IterateeSuite.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.testing 2 | 3 | import cats.{Eq, Eval, Monad, MonadError} 4 | import cats.data.{EitherT, NonEmptyList} 5 | import cats.laws.discipline.{ContravariantTests, MonadErrorTests, MonadTests, SemigroupalTests} 6 | import io.iteratee.Iteratee 7 | import io.iteratee.internal.Step 8 | import io.iteratee.modules.{EnumerateeModule, EnumeratorModule, IterateeErrorModule, IterateeModule, Module} 9 | import org.scalacheck.{Arbitrary, Cogen} 10 | import scala.Predef._ 11 | 12 | abstract class IterateeSuite[F[_]: Monad] extends BaseIterateeSuite[F] { 13 | this: EnumerateeModule[F] with EnumeratorModule[F] with IterateeModule[F] with Module[F] => 14 | 15 | checkLaws( 16 | s"Iteratee[$monadName, Vector[Int], Vector[Int]]", 17 | MonadTests[VectorIntFoldingIteratee].monad[Vector[Int], Vector[Int], Vector[Int]] 18 | ) 19 | } 20 | 21 | abstract class IterateeErrorSuite[F[_], T: Arbitrary: Eq: Cogen](implicit 22 | MEF: MonadError[F, T] 23 | ) extends BaseIterateeSuite[F] { 24 | this: EnumerateeModule[F] with EnumeratorModule[F] with IterateeErrorModule[F, T] with Module[F] { 25 | type M[f[_]] <: MonadError[f, T] 26 | } => 27 | 28 | implicit val monadError: MonadError[VectorIntFoldingIteratee, T] = Iteratee.iterateeMonadError[F, T, Vector[Int]] 29 | 30 | implicit val arbitraryVectorIntFoldingIteratee: Arbitrary[VectorIntFoldingIteratee[Vector[Int]]] = 31 | arbitraryVectorIteratee[F, Int] 32 | 33 | implicit val eqVectorIntIteratee: Eq[VectorIntFoldingIteratee[Vector[Int]]] = 34 | eqIteratee[F, Vector[Int], Vector[Int]] 35 | 36 | implicit val eqEitherUnitIteratee: Eq[VectorIntFoldingIteratee[Either[T, Unit]]] = 37 | eqIteratee[F, Vector[Int], Either[T, Unit]] 38 | 39 | implicit val eqEitherVectorIntIteratee: Eq[VectorIntFoldingIteratee[Either[T, Vector[Int]]]] = 40 | eqIteratee[F, Vector[Int], Either[T, Vector[Int]]] 41 | 42 | implicit val eqVectorInt3Iteratee: Eq[VectorIntFoldingIteratee[(Vector[Int], Vector[Int], Vector[Int])]] = 43 | eqIteratee[F, Vector[Int], (Vector[Int], Vector[Int], Vector[Int])] 44 | 45 | implicit val eqEitherTVectorInt: Eq[EitherT[({ type L[x] = Iteratee[F, Vector[Int], x] })#L, T, Vector[Int]]] = 46 | EitherT.catsDataEqForEitherT(eqEitherVectorIntIteratee) 47 | 48 | implicit val arbitraryVectorIntFunctionIteratee: Arbitrary[VectorIntFoldingIteratee[Vector[Int] => Vector[Int]]] = 49 | arbitraryFunctionIteratee[F, Vector[Int]] 50 | 51 | checkLaws( 52 | s"Iteratee[$monadName, Vector[Int], Vector[Int]]", 53 | MonadErrorTests[VectorIntFoldingIteratee, T].monadError[Vector[Int], Vector[Int], Vector[Int]] 54 | ) 55 | 56 | "ensureEval" should "be executed when the iteratee is done" in forAll { (eav: EnumeratorAndValues[Int]) => 57 | var done = false 58 | 59 | val iteratee = consume[Int].ensureEval(Eval.always(F.pure { done = true })) 60 | 61 | assert(!done) 62 | assert(eav.resultWithLeftovers(iteratee) === F.pure((eav.values, Vector.empty))) 63 | assert(done) 64 | } 65 | } 66 | 67 | abstract class BaseIterateeSuite[F[_]: Monad] extends ModuleSuite[F] { 68 | this: EnumerateeModule[F] with EnumeratorModule[F] with IterateeModule[F] with Module[F] => 69 | 70 | implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration( 71 | minSize = 0, 72 | sizeRange = 5000 73 | ) 74 | 75 | type VectorIntProducingIteratee[E] = Iteratee[F, E, Vector[Int]] 76 | type VectorIntFoldingIteratee[A] = Iteratee[F, Vector[Int], A] 77 | 78 | implicit val isomorphisms: SemigroupalTests.Isomorphisms[VectorIntFoldingIteratee] = 79 | SemigroupalTests.Isomorphisms.invariant[VectorIntFoldingIteratee] 80 | 81 | def myDrain(acc: List[Int]): Iteratee[F, Int, List[Int]] = cont[Int, List[Int]]( 82 | els => myDrain(acc ::: els.toList), 83 | F.pure(acc) 84 | ) 85 | 86 | checkLaws( 87 | s"Iteratee[$monadName, Int, Vector[Int]]", 88 | ContravariantTests[VectorIntProducingIteratee].contravariant[Vector[Int], Int, Vector[Int]] 89 | ) 90 | 91 | checkLaws( 92 | s"Iteratee[$monadName, Int, Vector[Int]]", 93 | ContravariantTests[VectorIntProducingIteratee].invariant[Vector[Int], Int, Vector[Int]] 94 | ) 95 | 96 | "cont" should "work recursively in an iteratee returning a list" in forAll { (eav: EnumeratorAndValues[Int]) => 97 | assert(eav.enumerator.into(myDrain(Nil)) === F.map(eav.enumerator.toVector)(_.toList)) 98 | } 99 | 100 | it should "work with fold with one value" in forAll { (es: List[Int]) => 101 | val folded = myDrain(es).fold[F[List[Int]]](_(NonEmptyList(0, Nil)).run, (_, _) => F.pure(Nil)) 102 | 103 | assert(F.flatten(folded) === F.pure(es :+ 0)) 104 | } 105 | 106 | it should "work with fold with multiple values" in forAll { (es: List[Int]) => 107 | val folded = myDrain(es).fold[F[List[Int]]](_(NonEmptyList(0, List(1, 2, 3))).run, (_, _) => F.pure(Nil)) 108 | 109 | assert(F.flatten(folded) === F.pure(es ++ Vector(0, 1, 2, 3))) 110 | } 111 | 112 | "done" should "work correctly" in forAll { (eav: EnumeratorAndValues[Int], s: String) => 113 | assert(eav.resultWithLeftovers(done(s)) === F.pure((s, eav.values))) 114 | } 115 | 116 | it should "work with fold with no leftovers" in forAll { (s: String) => 117 | assert(done[Int, String](s).fold(_ => None, (v, r) => Some((v, r))) === F.pure(Some((s, Nil)))) 118 | } 119 | 120 | "Step.doneWithLeftovers" should "do something vaguely reasonable with exactly one leftover" in { 121 | forAll { (eav: EnumeratorAndValues[Int], s: String, e: Int) => 122 | val iteratee = Iteratee.fromStep(Step.doneWithLeftovers(s, List(e))) 123 | 124 | assert(eav.resultWithLeftovers(iteratee) === F.pure((s, e +: eav.values))) 125 | } 126 | } 127 | 128 | it should "do something vaguely reasonable with leftovers" in { 129 | forAll { (eav: EnumeratorAndValues[Int], s: String, es: List[Int]) => 130 | val iteratee = Iteratee.fromStep(Step.doneWithLeftovers(s, es)) 131 | 132 | assert(eav.resultWithLeftovers(iteratee) === F.pure((s, es.toVector ++ eav.values))) 133 | } 134 | } 135 | 136 | it should "do something vaguely reasonable with fold with leftovers" in forAll { (s: String, es: List[Int]) => 137 | val iteratee = Iteratee.fromStep(Step.doneWithLeftovers[F, Int, String](s, es)) 138 | 139 | assert(iteratee.fold(_ => None, (v, r) => Some((v, r))) === F.pure(Some((s, es)))) 140 | } 141 | 142 | "liftToIteratee" should "lift a value in a context into an iteratee" in forAll { (i: Int) => 143 | assert(liftToIteratee(F.pure(i)).run === F.pure(i)) 144 | } 145 | 146 | it should "lift a value in a context when run on an enumerator" in forAll { (eav: EnumeratorAndValues[Int], i: Int) => 147 | assert(eav.enumerator.into(liftToIteratee(F.pure(i))) === F.pure(i)) 148 | } 149 | 150 | "liftMEval" should "lift a value in a context into an iteratee" in forAll { (i: Int) => 151 | var counter = 0 152 | val eval = Eval.later { 153 | counter += i 154 | F.pure(i) 155 | } 156 | 157 | assert(counter === 0) 158 | assert(Iteratee.liftMEval(eval).run === F.pure(i)) 159 | assert(counter === i) 160 | } 161 | 162 | it should "lift a value in a context when run on an enumerator" in forAll { (eav: EnumeratorAndValues[Int], i: Int) => 163 | var counter = 0 164 | val eval = Eval.later { 165 | counter += i 166 | F.pure(i) 167 | } 168 | 169 | assert(counter === 0) 170 | assert(eav.enumerator.into(Iteratee.liftMEval(eval)) === F.pure(i)) 171 | assert(counter === i) 172 | } 173 | 174 | "identityIteratee" should "consume no input" in forAll { (eav: EnumeratorAndValues[Int], it: Iteratee[F, Int, Int]) => 175 | assert(eav.resultWithLeftovers(identityIteratee) === F.pure(((), eav.values))) 176 | assert(eav.resultWithLeftovers(identityIteratee.flatMap(_ => it)) === eav.resultWithLeftovers(it)) 177 | } 178 | 179 | "consume" should "consume the entire stream" in forAll { (eav: EnumeratorAndValues[Int]) => 180 | val result = eav.resultWithLeftovers(consume) 181 | 182 | assert(result === F.pure((eav.values, Vector.empty))) 183 | assert(result === eav.resultWithLeftovers(identityIteratee.flatMap(_ => consume))) 184 | } 185 | 186 | "consumeIn" should "consume the entire stream" in forAll { (eav: EnumeratorAndValues[Int]) => 187 | assert(eav.resultWithLeftovers(consumeIn[Int, List]) === F.pure((eav.values.toList, Vector.empty))) 188 | } 189 | 190 | "reversed" should "consume and reverse the stream" in forAll { (eav: EnumeratorAndValues[Int]) => 191 | assert(eav.resultWithLeftovers(reversed) === F.pure((eav.values.toList.reverse, Vector.empty))) 192 | } 193 | 194 | "head" should "consume and return the first value" in forAll { (eav: EnumeratorAndValues[Int]) => 195 | val result = (eav.values.headOption, eav.values.drop(1)) 196 | 197 | assert(eav.resultWithLeftovers(head[Int]) === F.pure(result)) 198 | } 199 | 200 | "peek" should "consume the first value without consuming it" in forAll { (eav: EnumeratorAndValues[Int]) => 201 | val result = (eav.values.headOption, eav.values) 202 | 203 | assert(eav.resultWithLeftovers(peek[Int]) === F.pure(result)) 204 | } 205 | 206 | "last" should "return the last value" in forAll { (eav: EnumeratorAndValues[Int]) => 207 | val result = (eav.values.lastOption, Vector.empty[Int]) 208 | 209 | assert(eav.resultWithLeftovers(last[Int]) === F.pure(result)) 210 | } 211 | 212 | "takeI" should "consume the specified number of values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 213 | /** 214 | * This isn't a comprehensive way to avoid SI-9581, but it seems to keep clear of the cases ScalaCheck is likely to 215 | * run into. 216 | */ 217 | whenever(n != Int.MaxValue) { 218 | assert(eav.resultWithLeftovers(takeI[Int](n)) === F.pure((eav.values.take(n), eav.values.drop(n)))) 219 | } 220 | } 221 | 222 | "takeWhileI" should "consume the specified values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 223 | assert(eav.resultWithLeftovers(takeWhileI(_ < n)) === F.pure(eav.values.span(_ < n))) 224 | } 225 | 226 | "dropI" should "drop the specified number of values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 227 | /** 228 | * This isn't a comprehensive way to avoid SI-9581, but it seems to keep clear of the cases ScalaCheck is likely to 229 | * run into. 230 | */ 231 | whenever(n != Int.MaxValue) { 232 | assert(eav.resultWithLeftovers(dropI[Int](n)) === F.pure(((), eav.values.drop(n)))) 233 | } 234 | } 235 | 236 | "dropWhileI" should "drop the specified values" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 237 | assert(eav.resultWithLeftovers(dropWhileI(_ < n)) === F.pure(((), eav.values.dropWhile(_ < n)))) 238 | } 239 | 240 | it should "drop the specified values with nothing left in chunk" in { 241 | val iteratee = for { 242 | _ <- dropWhileI[Int](_ < 100) 243 | r <- consume 244 | } yield r 245 | 246 | assert(enumVector(Vector(1, 2, 3)).into(iteratee) === F.pure(Vector.empty)) 247 | } 248 | 249 | "fold" should "collapse the stream into a value" in forAll { (eav: EnumeratorAndValues[Int]) => 250 | assert(eav.resultWithLeftovers(fold[Int, Int](0)(_ + _)) === F.pure((eav.values.sum, Vector.empty))) 251 | } 252 | 253 | "foldM" should "effectfully collapse the stream into a value" in forAll { (eav: EnumeratorAndValues[Int]) => 254 | val result = (eav.values.sum, Vector.empty) 255 | 256 | assert(eav.resultWithLeftovers(foldM[Int, Int](0)((acc, i) => F.pure(acc + i))) === F.pure(result)) 257 | } 258 | 259 | "length" should "return the length of the stream" in forAll { (eav: EnumeratorAndValues[Int]) => 260 | assert(eav.resultWithLeftovers(length) === F.pure((eav.values.size.toLong, Vector.empty))) 261 | } 262 | 263 | "sum" should "return the sum of a stream of integers" in forAll { (eav: EnumeratorAndValues[Int]) => 264 | assert(eav.resultWithLeftovers(sum) === F.pure((eav.values.sum, Vector.empty))) 265 | } 266 | 267 | "isEnd" should "indicate whether a stream has ended" in forAll { (eav: EnumeratorAndValues[Int]) => 268 | assert(eav.resultWithLeftovers(isEnd) === F.pure((eav.values.isEmpty, eav.values))) 269 | assert(eav.resultWithLeftovers(consume.flatMap(_ => isEnd)) === F.pure((true, Vector.empty))) 270 | } 271 | 272 | "foreach" should "perform an operation on all values in a stream" in forAll { (eav: EnumeratorAndValues[Int]) => 273 | var total = 0 274 | val iteratee = foreach[Int](i => total += i) 275 | 276 | assert(eav.resultWithLeftovers(iteratee) === F.pure(((), Vector.empty)) && total === eav.values.sum) 277 | } 278 | 279 | "foreach" should "perform an operation on all values in a grouped stream" in { 280 | forAll { (eav: EnumeratorAndValues[Int]) => 281 | val eavg = EnumeratorAndValues(eav.enumerator.grouped(3), eav.values.grouped(3).toVector) 282 | 283 | var total = 0 284 | val iteratee = foreach[Vector[Int]](is => total += is.sum) 285 | 286 | assert(eavg.resultWithLeftovers(iteratee) === F.pure(((), Vector.empty)) && total === eavg.values.flatten.sum) 287 | } 288 | } 289 | 290 | "foreachM" should "perform an effectful operation on all values in a stream" in { 291 | forAll { (eav: EnumeratorAndValues[Int]) => 292 | var total = 0 293 | val iteratee = foreachM[Int](i => F.pure(total += i)) 294 | 295 | assert(eav.resultWithLeftovers(iteratee) === F.pure(((), Vector.empty)) && total === eav.values.sum) 296 | } 297 | } 298 | 299 | "discard" should "throw away the result" in forAll { (eav: EnumeratorAndValues[Int]) => 300 | var total = 0 301 | val iteratee = fold[Int, Int](0) { case (acc, i) => 302 | total += i 303 | i 304 | } 305 | 306 | assert(eav.resultWithLeftovers(iteratee.discard) === F.pure(((), Vector.empty))) 307 | assert(total === eav.values.sum) 308 | } 309 | 310 | "apply" should "process the values in a stream" in forAll { (eav: EnumeratorAndValues[Int]) => 311 | assert(consume.apply(eav.enumerator).apply(eav.enumerator).run === F.pure(eav.values ++ eav.values)) 312 | } 313 | 314 | "flatMapM" should "apply an effectful function" in { 315 | forAll { (eav: EnumeratorAndValues[Int], iteratee: Iteratee[F, Int, Int]) => 316 | assert(eav.enumerator.into(iteratee.flatMapM(F.pure)) === eav.enumerator.into(iteratee)) 317 | } 318 | } 319 | 320 | "contramap" should "apply a function on incoming values" in { 321 | forAll { (eav: EnumeratorAndValues[Int], iteratee: Iteratee[F, Int, Int]) => 322 | assert(eav.enumerator.into(iteratee.contramap(_ + 1)) === eav.enumerator.map(_ + 1).into(iteratee)) 323 | } 324 | } 325 | 326 | "through" should "pipe incoming values through an enumeratee" in forAll { (eav: EnumeratorAndValues[Int]) => 327 | val result = (eav.values.sum + eav.values.size, Vector.empty) 328 | 329 | assert(eav.resultWithLeftovers(sum[Int].through(map(_ + 1))) === F.pure(result)) 330 | } 331 | 332 | "as" should "replace the result" in { 333 | forAll { (eav: EnumeratorAndValues[Int], iteratee: Iteratee[F, Int, Int], value: Int) => 334 | assert(eav.enumerator.into(iteratee.as(value)) === F.pure(value)) 335 | } 336 | } 337 | 338 | "zip" should "zip two iteratees" in forAll { (eav: EnumeratorAndValues[Int]) => 339 | val result = ((eav.values.sum, eav.values.size.toLong), Vector.empty) 340 | 341 | assert(eav.resultWithLeftovers(sum[Int].zip(length)) === F.pure(result)) 342 | } 343 | 344 | it should "zip two iteratees with leftovers (scalaz/scalaz#1068)" in { 345 | forAll { (eav: EnumeratorAndValues[Int], m: Int, n: Int) => 346 | /** 347 | * This isn't a comprehensive way to avoid SI-9581, but it seems to keep clear of the cases ScalaCheck is likely 348 | * to run into. 349 | */ 350 | whenever(m != Int.MaxValue && n != Int.MaxValue) { 351 | val result = ((eav.values.take(m), eav.values.take(n)), eav.values.drop(math.max(m, n))) 352 | 353 | assert(eav.resultWithLeftovers(takeI[Int](m).zip(takeI[Int](n))) === F.pure(result)) 354 | } 355 | } 356 | } 357 | 358 | it should "zip two iteratees where leftover sizes must be compared" in forAll { (eav: EnumeratorAndValues[Int]) => 359 | val iteratee = takeI[Int](2).zip(takeI(3)) 360 | val result = ((eav.values.take(2), eav.values.take(3)), eav.values.drop(3)) 361 | 362 | assert(eav.resultWithLeftovers(iteratee) === F.pure(result)) 363 | } 364 | 365 | it should "zip two iteratees where only one has leftovers" in forAll { (v: Vector[Int], n: Int) => 366 | val taken = n % (v.size + 1) 367 | val enumerator = enumVector(v) 368 | val iteratee1 = takeI[Int](v.size).zip(takeI(taken)).flatMap(r => consume.map((r, _))) 369 | val iteratee2 = takeI[Int](taken).zip(takeI(v.size)).flatMap(r => consume.map((r, _))) 370 | 371 | val result1 = (v, v.take(taken)) 372 | val result2 = (v.take(taken), v) 373 | 374 | assert(enumerator.into(iteratee1) === F.pure((result1, Vector.empty))) 375 | assert(enumerator.into(iteratee2) === F.pure((result2, Vector.empty))) 376 | } 377 | 378 | it should "zip two iteratees with single leftovers" in { 379 | val es = Vector(1, 2, 3, 4) 380 | val enumerator = enumVector(es) 381 | val iteratee1 = takeI[Int](2).zip(takeI(3)).zip(takeI(4)) 382 | val iteratee2 = takeI[Int](2).zip(takeI(3)).zip(consume) 383 | val result = ((es.take(2), es.take(3)), es) 384 | 385 | assert(enumerator.into(iteratee1) === F.pure(result)) 386 | assert(enumerator.into(iteratee2) === F.pure(result)) 387 | } 388 | 389 | "foldMap" should "sum a stream while transforming it" in forAll { (eav: EnumeratorAndValues[Int]) => 390 | val result = F.pure((eav.values.sum + eav.values.size, Vector.empty[Int])) 391 | 392 | assert(eav.resultWithLeftovers(foldMap(_ + 1)) === result) 393 | } 394 | 395 | "foldMapM" should "sum a stream while transforming it" in forAll { (eav: EnumeratorAndValues[Int]) => 396 | val result = F.pure((eav.values.sum + eav.values.size, Vector.empty[Int])) 397 | 398 | assert(eav.resultWithLeftovers(foldMapM(e => F.pure(e + 1))) === result) 399 | } 400 | 401 | "foldMapOption" should "sum a stream while transforming it" in 402 | forAll { (eav: EnumeratorAndValues[Int]) => 403 | val result = F.pure( 404 | ( 405 | if (eav.values.isEmpty) None else Some(eav.values.sum + eav.values.size), 406 | Vector.empty[Int] 407 | ) 408 | ) 409 | 410 | assert(eav.resultWithLeftovers(foldMapOption(_ + 1)) === result) 411 | } 412 | 413 | "foldMapMOption" should "sum a stream while transforming it" in 414 | forAll { (eav: EnumeratorAndValues[Int]) => 415 | val result = F.pure( 416 | ( 417 | if (eav.values.isEmpty) None else Some(eav.values.sum + eav.values.size), 418 | Vector.empty[Int] 419 | ) 420 | ) 421 | 422 | assert(eav.resultWithLeftovers(foldMapMOption(e => F.pure(e + 1))) === result) 423 | } 424 | 425 | "intoIteratee" should "be available on values in a context" in forAll { (i: Int) => 426 | import syntax._ 427 | 428 | assert(F.pure(i).intoIteratee.run === F.pure(i)) 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /tests/jvm/src/it/scala/io/iteratee/files/IOTests.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.files 2 | 3 | import cats.effect.IO 4 | import cats.effect.laws.util.{TestContext, TestInstances} 5 | import cats.kernel.Eq 6 | import io.iteratee.files.modules.IOModule 7 | import io.iteratee.testing.{EnumerateeSuite, IterateeErrorSuite, StackSafeEnumeratorSuite} 8 | import io.iteratee.testing.EqInstances.eqThrowable 9 | import io.iteratee.testing.files.FileModuleSuite 10 | 11 | trait IOSuite extends IOModule { 12 | def monadName: String = "IO" 13 | 14 | private[this] lazy val context: TestContext = TestContext() 15 | 16 | implicit def eqF[A](implicit A: Eq[A]): Eq[IO[A]] = TestInstances.eqIO(A, context) 17 | } 18 | 19 | class IOFileTests extends FileModuleSuite[IO] with IOSuite 20 | class IOEnumerateeTests extends EnumerateeSuite[IO] with IOSuite 21 | class IOEnumeratorTests extends StackSafeEnumeratorSuite[IO] with IOSuite 22 | class IOIterateeTests extends IterateeErrorSuite[IO, Throwable] with IOSuite 23 | -------------------------------------------------------------------------------- /tests/jvm/src/main/resources/io/iteratee/examples/pg/11231/11231.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/travisbrown/iteratee/2a51408163088da50e7e26bb2103017b51eada85/tests/jvm/src/main/resources/io/iteratee/examples/pg/11231/11231.zip -------------------------------------------------------------------------------- /tests/jvm/src/test/scala/io/iteratee/FutureTests.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.instances.future._ 4 | import cats.laws.discipline.arbitrary.catsLawsCogenForThrowable 5 | import io.iteratee.testing.{EnumerateeSuite, IterateeErrorSuite, StackSafeEnumeratorSuite} 6 | import io.iteratee.testing.EqInstances.eqThrowable 7 | import io.iteratee.tests.FutureSuite 8 | import io.iteratee.tests.FutureSuite.arbitraryNonFatalThrowable 9 | import scala.concurrent.ExecutionContext.Implicits.global 10 | import scala.concurrent.Future 11 | 12 | class FutureEnumerateeTests extends EnumerateeSuite[Future] with FutureSuite 13 | class FutureEnumeratorTests extends StackSafeEnumeratorSuite[Future] with FutureSuite 14 | class FutureIterateeTests extends IterateeErrorSuite[Future, Throwable] with FutureSuite 15 | -------------------------------------------------------------------------------- /tests/shared/src/main/scala/io/iteratee/tests/ModuleSuites.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee.tests 2 | 3 | import cats.Eval 4 | import cats.data.EitherT 5 | import cats.instances.either.catsStdEqForEither 6 | import cats.instances.option.catsKernelStdEqForOption 7 | import cats.instances.try_.catsStdEqForTry 8 | import cats.kernel.Eq 9 | import io.iteratee.modules._ 10 | import io.iteratee.testing.EqInstances.eqThrowable 11 | import org.scalacheck.Arbitrary 12 | import scala.concurrent.{Await, ExecutionContext, Future} 13 | import scala.concurrent.ExecutionContext.Implicits.global 14 | import scala.concurrent.duration._ 15 | import scala.util.{Failure, Success, Try} 16 | 17 | trait EitherSuite extends EitherModule { 18 | def monadName: String = "Either[Throwable, *]" 19 | 20 | implicit def eqF[A](implicit A: Eq[A]): Eq[Either[Throwable, A]] = catsStdEqForEither(Eq.fromUniversalEquals, A) 21 | } 22 | 23 | trait EitherTSuite extends EitherTModule { 24 | def monadName: String = "EitherT[Eval, Throwable, *]" 25 | 26 | implicit def eqEval[A](implicit A: Eq[A]): Eq[Eval[Either[Throwable, A]]] = 27 | Eval.catsEqForEval(catsStdEqForEither(Eq.fromUniversalEquals, A)) 28 | 29 | implicit def eqF[A](implicit A: Eq[A]): Eq[EitherT[Eval, Throwable, A]] = EitherT.catsDataEqForEitherT(eqEval(A)) 30 | } 31 | 32 | trait EvalSuite extends EvalModule { 33 | def monadName: String = "Eval" 34 | 35 | implicit def eqF[A: Eq]: Eq[Eval[A]] = Eval.catsEqForEval[A] 36 | } 37 | 38 | trait FutureSuite extends FutureModule { 39 | def monadName: String = "Future" 40 | 41 | protected def ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global 42 | 43 | implicit def eqF[A](implicit A: Eq[A]): Eq[Future[A]] = new Eq[Future[A]] { 44 | def liftToTry[A](f: Future[A]): Future[Try[A]] = f.map(Success(_)).recover { case t => 45 | Failure(t) 46 | } 47 | 48 | def eqv(fx: Future[A], fy: Future[A]): Boolean = 49 | Await.result( 50 | liftToTry(fx).zip(liftToTry(fy)).map { case (tx, ty) => 51 | Eq[Try[A]].eqv(tx, ty) 52 | }, 53 | 20.seconds 54 | ) 55 | } 56 | } 57 | 58 | object FutureSuite { 59 | 60 | /** 61 | * Needed because `scala.concurrent.Future` boxes `java.lang.Error`. 62 | */ 63 | implicit val arbitraryNonFatalThrowable: Arbitrary[Throwable] = 64 | Arbitrary(Arbitrary.arbitrary[Exception].map(identity)) 65 | } 66 | 67 | trait IdSuite extends IdModule { 68 | def monadName: String = "Id" 69 | 70 | implicit def eqF[A](implicit A: Eq[A]): Eq[A] = A 71 | } 72 | 73 | trait OptionSuite extends OptionModule { 74 | def monadName: String = "Option" 75 | 76 | implicit def eqF[A](implicit A: Eq[A]): Eq[Option[A]] = catsKernelStdEqForOption 77 | } 78 | 79 | trait TrySuite extends TryModule { 80 | def monadName: String = "Try" 81 | 82 | implicit def eqF[A](implicit A: Eq[A]): Eq[Try[A]] = catsStdEqForTry(A, Eq.fromUniversalEquals) 83 | } 84 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/io/iteratee/EitherTTests.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.{Eval, Id, MonadError, catsInstancesForId} 4 | import cats.arrow.FunctionK 5 | import cats.data.EitherT 6 | import cats.laws.discipline.arbitrary.catsLawsCogenForThrowable 7 | import io.iteratee.testing.{EnumerateeSuite, IterateeErrorSuite, StackSafeEnumeratorSuite} 8 | import io.iteratee.testing.EqInstances.eqThrowable 9 | import io.iteratee.tests.EitherTSuite 10 | import org.scalacheck.Arbitrary 11 | 12 | class EitherTEnumerateeTests extends EnumerateeSuite[({ type L[x] = EitherT[Eval, Throwable, x] })#L] with EitherTSuite 13 | 14 | class EitherTEnumeratorTests 15 | extends StackSafeEnumeratorSuite[({ type L[x] = EitherT[Eval, Throwable, x] })#L] 16 | with EitherTSuite { 17 | type ETE[A] = EitherT[Eval, Throwable, A] 18 | 19 | "ensure" should "perform an action after the enumerator is done" in forAll { (eav: EnumeratorAndValues[Int]) => 20 | var counter = 0 21 | val action = EitherT.right[Throwable](Eval.always(counter += 1)) 22 | val enumerator = eav.enumerator.ensure(action) 23 | 24 | assert(counter === 0) 25 | assert(enumerator.toVector === F.pure(eav.values)) 26 | assert(counter === 1) 27 | } 28 | 29 | it should "perform its action in the case of failure" in forAll { (eav: EnumeratorAndValues[Int], message: String) => 30 | val error: Throwable = new Exception(message) 31 | var counter = 0 32 | val action = EitherT.right[Throwable](Eval.always(counter += 1)) 33 | val enumerator = failEnumerator(error).append(eav.enumerator).ensure(action) 34 | 35 | assert(counter == 0) 36 | assert(enumerator.toVector.value.value === Left(error)) 37 | assert(counter === 1) 38 | } 39 | 40 | it should "work without necessarily consuming all elements" in forAll { (eav: EnumeratorAndValues[Int]) => 41 | var counter = 0 42 | val action = EitherT.right[Throwable](Eval.always(counter += 1)) 43 | val enumerator = eav.enumerator.ensure(action) 44 | val n = math.max(0, eav.values.size - 2) 45 | 46 | assert(counter == 0) 47 | assert(enumerator.into(takeI(n)) === F.pure(eav.values.take(n))) 48 | assert(counter === 1) 49 | } 50 | 51 | "failEnumerator" should "return a failed enumerator" in forAll { (eav: EnumeratorAndValues[Int], message: String) => 52 | val error: Throwable = new Exception(message) 53 | 54 | assert(eav.enumerator.append(failEnumerator(error)).toVector.value.value === Left(error)) 55 | } 56 | 57 | "enumEither" should "either enumerate a single value or fail" in forAll { (either: Either[Throwable, Int]) => 58 | assert(enumEither(either).toVector === EitherT.fromEither[Eval](either).map(Vector(_))) 59 | } 60 | 61 | "handleErrorWith" should "allow recovery from failures" in { 62 | forAll { (eav: EnumeratorAndValues[Int], message: String) => 63 | val error: Throwable = new Exception(message) 64 | val enumerator = failEnumerator(error).handleErrorWith[Throwable](_ => eav.enumerator) 65 | 66 | assert(enumerator.toVector.value.value === Right(eav.values)) 67 | } 68 | } 69 | } 70 | 71 | class EitherTIterateeTests 72 | extends IterateeErrorSuite[({ type L[x] = EitherT[Eval, Throwable, x] })#L, Throwable] 73 | with EitherTSuite { 74 | type ETE[A] = EitherT[Eval, Throwable, A] 75 | 76 | "failIteratee" should "return an iteratee that always fails" in { 77 | forAll { (eav: EnumeratorAndValues[Int], message: String) => 78 | val error: Throwable = new Exception(message) 79 | val result = MonadError[ETE, Throwable].raiseError[(String, Vector[Int])](error) 80 | 81 | assert(eav.resultWithLeftovers(failIteratee(error)) === result) 82 | } 83 | } 84 | 85 | "mapI" should "transform the stream with a natural transformation" in { 86 | forAll(Arbitrary.arbitrary[EnumeratorAndValues[Int]], arbitraryIntIteratee[Id].arbitrary) { (eav, iteratee) => 87 | val pureEnumerator = Enumerator.enumVector[Id, Int](eav.values) 88 | 89 | val xorIteratee = iteratee.mapI( 90 | new FunctionK[Id, ETE] { 91 | def apply[A](a: A): ETE[A] = F.pure(a) 92 | } 93 | ) 94 | 95 | assert(eav.enumerator.into(xorIteratee) === F.pure(pureEnumerator.into(iteratee))) 96 | } 97 | } 98 | 99 | "up" should "lift an iteratee into a larger context" in forAll { (eav: EnumeratorAndValues[Int], n: Int) => 100 | whenever(n != Int.MaxValue) { 101 | val iteratee = Iteratee.take[Id, Int](n).up[ETE] 102 | val result = (eav.values.take(n), eav.values.drop(n)) 103 | 104 | assert(eav.resultWithLeftovers(iteratee) === F.pure(result)) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/io/iteratee/EitherTests.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.laws.discipline.arbitrary.catsLawsCogenForThrowable 4 | import io.iteratee.testing.{EnumerateeSuite, EnumeratorSuite, IterateeErrorSuite} 5 | import io.iteratee.testing.EqInstances.eqThrowable 6 | import io.iteratee.tests.EitherSuite 7 | 8 | class EitherEnumerateeTests extends EnumerateeSuite[({ type L[x] = Either[Throwable, x] })#L] with EitherSuite 9 | class EitherEnumeratorTests extends EnumeratorSuite[({ type L[x] = Either[Throwable, x] })#L] with EitherSuite 10 | class EitherIterateeTests 11 | extends IterateeErrorSuite[({ type L[x] = Either[Throwable, x] })#L, Throwable] 12 | with EitherSuite 13 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/io/iteratee/EvalTests.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.Eval 4 | import io.iteratee.testing.{EnumerateeSuite, IterateeSuite, StackSafeEnumeratorSuite} 5 | import io.iteratee.tests.EvalSuite 6 | 7 | class EvalEnumerateeTests extends EnumerateeSuite[Eval] with EvalSuite 8 | 9 | class EvalEnumeratorTests extends StackSafeEnumeratorSuite[Eval] with EvalSuite { 10 | "perform" should "perform an action" in forAll { (eav: EnumeratorAndValues[Int]) => 11 | var counter = 0 12 | val action = perform[Int](Eval.always(counter += 1)) 13 | val enumerator = action.append(eav.enumerator).append(action) 14 | 15 | assert(counter === 0) 16 | assert(enumerator.toVector === Eval.now(eav.values)) 17 | assert(counter === 2) 18 | } 19 | 20 | "generateM" should "enumerate values generated by an effectful function" in forAll { (n: Short) => 21 | val count = math.abs(n.toInt) 22 | var counter = 0 23 | val enumerator = generateM( 24 | Eval.always( 25 | if (counter > count) None 26 | else 27 | Some { 28 | val result = counter 29 | counter += 1 30 | result 31 | } 32 | ) 33 | ) 34 | 35 | assert(enumerator.toVector === Eval.now((0 to count).toVector)) 36 | assert(counter == count + 1) 37 | } 38 | 39 | "StackUnsafe.generateM" should "enumerate values generated by an effectful function" in forAll { (n: Short) => 40 | val count = math.abs(n.toInt) 41 | var counter = 0 42 | val enumerator = Enumerator.StackUnsafe.generateM( 43 | Eval.always( 44 | if (counter > count) None 45 | else 46 | Some { 47 | val result = counter 48 | counter += 1 49 | result 50 | } 51 | ) 52 | ) 53 | 54 | assert(enumerator.toVector === Eval.now((0 to count).toVector)) 55 | assert(counter == count + 1) 56 | } 57 | } 58 | 59 | class EvalIterateeTests extends IterateeSuite[Eval] with EvalSuite 60 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/io/iteratee/IdTests.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.{Id, catsInstancesForId} 4 | import io.iteratee.testing.{EnumerateeSuite, EnumeratorSuite, IterateeSuite} 5 | import io.iteratee.tests.IdSuite 6 | 7 | class IdEnumerateeTests extends EnumerateeSuite[Id] with IdSuite 8 | class IdEnumeratorTests extends EnumeratorSuite[Id] with IdSuite 9 | class IdIterateeTests extends IterateeSuite[Id] with IdSuite 10 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/io/iteratee/OptionTests.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import io.iteratee.testing.{EnumerateeSuite, EnumeratorSuite, IterateeSuite} 4 | import io.iteratee.tests.OptionSuite 5 | 6 | class OptionEnumerateeTests extends EnumerateeSuite[Option] with OptionSuite 7 | class OptionEnumeratorTests extends EnumeratorSuite[Option] with OptionSuite 8 | class OptionIterateeTests extends IterateeSuite[Option] with OptionSuite 9 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/io/iteratee/TryTests.scala: -------------------------------------------------------------------------------- 1 | package io.iteratee 2 | 3 | import cats.instances.try_._ 4 | import cats.laws.discipline.arbitrary.catsLawsCogenForThrowable 5 | import io.iteratee.testing.{EnumerateeSuite, EnumeratorSuite, IterateeErrorSuite} 6 | import io.iteratee.testing.EqInstances.eqThrowable 7 | import io.iteratee.tests.TrySuite 8 | import scala.util.Try 9 | 10 | class TryEnumerateeTests extends EnumerateeSuite[Try] with TrySuite 11 | class TryEnumeratorTests extends EnumeratorSuite[Try] with TrySuite 12 | class TryIterateeTests extends IterateeErrorSuite[Try, Throwable] with TrySuite 13 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.20.1-SNAPSHOT" 2 | --------------------------------------------------------------------------------