├── .codeclimate.yml ├── .envrc ├── .ghci ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci-native.yaml │ ├── ci-nix.yaml │ └── greetings.yml ├── .gitignore ├── .hlint.yaml ├── .stylish-haskell.yaml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── Setup.hs ├── flake.lock ├── flake.nix ├── ipfs.cabal ├── library └── Network │ ├── IPFS.hs │ └── IPFS │ ├── Add.hs │ ├── Add │ └── Error.hs │ ├── BinPath │ └── Types.hs │ ├── Bytes │ └── Types.hs │ ├── CID │ └── Types.hs │ ├── Client.hs │ ├── Client │ ├── Add.hs │ ├── Cat.hs │ ├── DAG │ │ ├── Put │ │ │ └── Types.hs │ │ └── Types.hs │ ├── Error │ │ └── Types.hs │ ├── Param.hs │ ├── Pin.hs │ ├── Stat.hs │ └── Streaming │ │ └── Pin.hs │ ├── DAG.hs │ ├── DAG │ ├── Link.hs │ ├── Link │ │ └── Types.hs │ └── Node │ │ └── Types.hs │ ├── Error.hs │ ├── File │ ├── Form │ │ └── Types.hs │ └── Types.hs │ ├── Gateway │ └── Types.hs │ ├── Get.hs │ ├── Get │ └── Error.hs │ ├── Ignored │ └── Types.hs │ ├── Info │ └── Types.hs │ ├── Internal │ ├── Orphanage │ │ ├── ByteString │ │ │ └── Lazy.hs │ │ ├── Natural.hs │ │ └── Utf8Builder.hs │ └── UTF8.hs │ ├── Local │ └── Class.hs │ ├── MIME │ └── RawPlainText │ │ └── Types.hs │ ├── Name │ └── Types.hs │ ├── Path │ └── Types.hs │ ├── Peer.hs │ ├── Peer │ ├── Error.hs │ └── Types.hs │ ├── Pin.hs │ ├── Prelude.hs │ ├── Process.hs │ ├── Process │ ├── Error.hs │ └── Types.hs │ ├── Remote │ ├── Class.hs │ └── Error.hs │ ├── SparseTree.hs │ ├── SparseTree │ └── Types.hs │ ├── Stat.hs │ ├── Stat │ ├── Error.hs │ └── Types.hs │ ├── Timeout │ └── Types.hs │ ├── Types.hs │ └── URL │ └── Types.hs ├── nix ├── commands.nix ├── pkgs.nix ├── sources.json └── sources.nix ├── package.yaml ├── shell.nix ├── stack.yaml ├── stack.yaml.lock └── test ├── coverage-code └── Main.hs ├── coverage-docs └── Main.hs ├── doctest └── Main.hs ├── lint └── Main.hs └── testsuite └── Main.hs /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | hlint: 3 | enabled: true 4 | ratings: 5 | paths: 6 | - "**.hs" 7 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | eval "$(lorri direnv)" 2 | -------------------------------------------------------------------------------- /.ghci: -------------------------------------------------------------------------------- 1 | :seti -XOverloadedStrings 2 | :seti -XScopedTypeVariables 3 | 4 | :set -Wall 5 | :set -fno-warn-type-defaults 6 | :set -fwarn-unused-binds 7 | :set -fwarn-unused-imports 8 | 9 | :set +s 10 | :set +t 11 | :set +m 12 | 13 | :set -fobject-code 14 | :set -fno-code 15 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## 1. Purpose 4 | 5 | A primary goal of `FISSON` is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 6 | 7 | This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. 8 | 9 | We invite all those who participate in `FISSION` to help us create safe and positive experiences for everyone. 10 | 11 | ## 2. Open Source Citizenship 12 | 13 | A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. 14 | 15 | Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. 16 | 17 | If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. 18 | 19 | ## 3. Expected Behavior 20 | 21 | The following behaviors are expected and requested of all community members: 22 | 23 | * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 24 | * Exercise consideration and respect in your speech and actions. 25 | * Attempt collaboration before conflict. 26 | * Refrain from demeaning, discriminatory, or harassing behavior and speech. 27 | * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. 28 | * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. 29 | 30 | ## 4. Unacceptable Behavior 31 | 32 | The following behaviors are considered harassment and are unacceptable within our community: 33 | 34 | * Violence, threats of violence or violent language directed against another person. 35 | * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. 36 | * Posting or displaying sexually explicit or violent material. 37 | * Posting or threatening to post other people’s personally identifying information ("doxing"). 38 | * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. 39 | * Inappropriate photography or recording. 40 | * Inappropriate physical contact. You should have someone’s consent before touching them. 41 | * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. 42 | * Deliberate intimidation, stalking or following (online or in person). 43 | * Advocating for, or encouraging, any of the above behavior. 44 | * Sustained disruption of community events, including talks and presentations. 45 | 46 | ## 5. Consequences of Unacceptable Behavior 47 | 48 | Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. 49 | 50 | Anyone asked to stop unacceptable behavior is expected to comply immediately. 51 | 52 | If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). 53 | 54 | ## 6. Reporting Guidelines 55 | 56 | If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. hello@brooklynzelenka.com. 57 | 58 | 59 | 60 | Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. 61 | 62 | ## 7. Addressing Grievances 63 | 64 | If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify the maintainers with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. 65 | 66 | 67 | 68 | ## 8. Scope 69 | 70 | We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business. 71 | 72 | This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. 73 | 74 | ## 9. Contact info 75 | 76 | hello@fission.codes 77 | 78 | ## 10. License and attribution 79 | 80 | This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). 81 | 82 | Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). 83 | 84 | Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/) 85 | 86 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: "\U0001F41B bug" 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Summary 11 | 12 | ## Problem 13 | 14 | Describe the immediate problem 15 | 16 | ### Impact 17 | 18 | The impact that this bug has 19 | 20 | ## Solution 21 | 22 | Describe the sort of fix that would solve the issue 23 | 24 | # Detail 25 | 26 | **Describe the bug** 27 | A clear and concise description of what the bug is. 28 | 29 | **To Reproduce** 30 | Steps to reproduce the behavior: 31 | 1. Go to '...' 32 | 2. Click on '....' 33 | 3. Scroll down to '....' 34 | 4. See error 35 | 36 | **Expected behavior** 37 | A clear and concise description of what you expected to happen. 38 | 39 | **Screenshots** 40 | If applicable, add screenshots to help explain your problem. 41 | 42 | **Desktop (please complete the following information):** 43 | - OS: [e.g. iOS] 44 | - Browser [e.g. chrome, safari] 45 | - Version [e.g. 22] 46 | 47 | **Smartphone (please complete the following information):** 48 | - Device: [e.g. iPhone6] 49 | - OS: [e.g. iOS8.1] 50 | - Browser [e.g. stock browser, safari] 51 | - Version [e.g. 22] 52 | 53 | **Additional context** 54 | Add any other context about the problem here. 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: "\U0001F497 enhancement" 6 | assignees: '' 7 | 8 | --- 9 | 10 | NB: Feature requests will only be considered if they solve a pain 11 | 12 | # Summary 13 | 14 | ## Problem 15 | 16 | Describe the pain that this feature will solve 17 | 18 | ### Impact 19 | 20 | The impact of not having this feature 21 | 22 | ## Solution 23 | 24 | Describe the solution 25 | 26 | # Detail 27 | 28 | **Is your feature request related to a problem? Please describe.** 29 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 30 | 31 | **Describe the solution you'd like** 32 | A clear and concise description of what you want to happen. 33 | 34 | **Describe alternatives you've considered** 35 | A clear and concise description of any alternative solutions or features you've considered. 36 | 37 | **Additional context** 38 | Add any other context or screenshots about the feature request here. 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | A similar PR may already be submitted! 2 | Please search among the [Pull request](../) before creating one. 3 | 4 | Thanks for submitting a pull request! Please provide enough information so that others can review your pull request: 5 | 6 | For more information, see the `CONTRIBUTING` guide. 7 | 8 | 9 | ## Summary 10 | 11 | 12 | This PR fixes/implements the following **bugs/features** 13 | 14 | * [ ] Bug 1 15 | * [ ] Bug 2 16 | * [ ] Feature 1 17 | * [ ] Feature 2 18 | * [ ] Breaking changes 19 | 20 | 21 | 22 | Explain the **motivation** for making this change. What existing problem does the pull request solve? 23 | 24 | 25 | 26 | ## Test plan (required) 27 | 28 | Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. 29 | 30 | 31 | 32 | 33 | ## Closing issues 34 | 35 | 36 | Fixes # 37 | 38 | ## After Merge 39 | * [ ] Does this change invalidate any docs or tutorials? _If so ensure the changes needed are either made or recorded_ 40 | * [ ] Does this change require a release to be made? Is so please create and deploy the release 41 | -------------------------------------------------------------------------------- /.github/workflows/ci-native.yaml: -------------------------------------------------------------------------------- 1 | name: 🧪 Tests (Native) 2 | 3 | # Trigger the workflow on push or pull request, but only for the main branch 4 | on: 5 | pull_request: 6 | push: 7 | branches: [main] 8 | 9 | jobs: 10 | stack: 11 | name: 🖥️ ${{ matrix.os }} 🏭 GHC ${{ matrix.ghc }} 📚 Stack ${{ matrix.stack }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | stack: 16 | - "2.5.1" 17 | ghc: 18 | - "8.10.4" 19 | os: 20 | - macos-10.15 21 | - ubuntu-18.04 22 | - ubuntu-20.04 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | name: 📤 Checkout 27 | if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/main' 28 | 29 | - uses: haskell/actions/setup@v1 30 | name: 🧱 Set up Haskell Stack 31 | with: 32 | ghc-version: ${{ matrix.ghc }} 33 | stack-version: ${{ matrix.stack }} 34 | 35 | - uses: actions/cache@v2 36 | name: 🗄️ Cache ~/.stack 37 | with: 38 | path: ~/.stack 39 | key: ${{ matrix.os }}-${{ matrix.ghc }}-stack-ci-2 40 | 41 | - name: 🧪 Test 42 | run: | 43 | stack test --no-nix 44 | -------------------------------------------------------------------------------- /.github/workflows/ci-nix.yaml: -------------------------------------------------------------------------------- 1 | name: 🧪 Tests (Nix) 2 | 3 | # Trigger the workflow on push or pull request, but only for the main branch 4 | on: 5 | pull_request: 6 | push: 7 | branches: [main] 8 | 9 | jobs: 10 | stack: 11 | name: 🖥️ ${{ matrix.os }} ❄️ Nix 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: 16 | - macos-latest 17 | - ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | name: 📤 Checkout 22 | if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/main' 23 | 24 | - uses: cachix/install-nix-action@v13 25 | name: ❄️ Set up Nix 26 | with: 27 | nix_path: nixpkgs=channel:nixos-unstable 28 | 29 | - uses: actions/cache@v2 30 | name: 🗄️ Cache ~/.stack 31 | with: 32 | path: ~/.stack 33 | key: ${{ matrix.os }}-stack-nix-ci-4 34 | 35 | - name: 🧪 Test 36 | run: | 37 | stack test --nix 38 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 👋 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: 'Thank you for submitting an issue! It means a lot that you took the time -- it helps us be better 🙏' 13 | pr-message: "Thank you for submitting a PR 🎉 It's very appreciated!" 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/elm,vim,node,xcode,ocaml,linux,macos,emacs,elixir,phoenix,windows,haskell,reasonml,reactnative,sublimetext,visualstudio,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=elm,vim,node,xcode,ocaml,linux,macos,emacs,elixir,phoenix,windows,haskell,reasonml,reactnative,sublimetext,visualstudio,visualstudiocode 4 | 5 | result 6 | 7 | # Created by https://www.toptal.com/developers/gitignore/api/haskell 8 | # Edit at https://www.toptal.com/developers/gitignore?templates=haskell 9 | 10 | ### Haskell ### 11 | dist 12 | dist-* 13 | cabal-dev 14 | *.o 15 | *.hi 16 | *.hie 17 | *.chi 18 | *.chs.h 19 | *.dyn_o 20 | *.dyn_hi 21 | .hpc 22 | .hsenv 23 | .cabal-sandbox/ 24 | cabal.sandbox.config 25 | *.prof 26 | *.aux 27 | *.hp 28 | *.eventlog 29 | .stack-work/ 30 | cabal.project.local 31 | cabal.project.local~ 32 | .HTF/ 33 | .ghc.environment.* 34 | 35 | # End of https://www.toptal.com/developers/gitignore/api/haskell 36 | 37 | ### Elm ### 38 | # elm-package generated files 39 | elm-stuff 40 | # elm-repl generated files 41 | repl-temp-* 42 | 43 | ### Emacs ### 44 | # -*- mode: gitignore; -*- 45 | *~ 46 | \#*\# 47 | /.emacs.desktop 48 | /.emacs.desktop.lock 49 | *.elc 50 | auto-save-list 51 | tramp 52 | .\#* 53 | 54 | # Org-mode 55 | .org-id-locations 56 | *_archive 57 | 58 | # flymake-mode 59 | *_flymake.* 60 | 61 | # eshell files 62 | /eshell/history 63 | /eshell/lastdir 64 | 65 | # elpa packages 66 | /elpa/ 67 | 68 | # reftex files 69 | *.rel 70 | 71 | # AUCTeX auto folder 72 | /auto/ 73 | 74 | # cask packages 75 | .cask/ 76 | dist/ 77 | 78 | # Flycheck 79 | flycheck_*.el 80 | 81 | # server auth directory 82 | /server/ 83 | 84 | # projectiles files 85 | .projectile 86 | 87 | # directory configuration 88 | .dir-locals.el 89 | 90 | # network security 91 | /network-security.data 92 | 93 | 94 | ### Haskell ### 95 | dist 96 | dist-* 97 | cabal-dev 98 | *.o 99 | *.hi 100 | *.chi 101 | *.chs.h 102 | *.dyn_o 103 | *.dyn_hi 104 | .hpc 105 | .hsenv 106 | .cabal-sandbox/ 107 | cabal.sandbox.config 108 | *.prof 109 | *.aux 110 | *.hp 111 | *.eventlog 112 | .stack-work/ 113 | cabal.project.local 114 | cabal.project.local~ 115 | .HTF/ 116 | .ghc.environment.* 117 | 118 | ### Linux ### 119 | 120 | # temporary files which can be created if a process still has a handle open of a deleted file 121 | .fuse_hidden* 122 | 123 | # KDE directory preferences 124 | .directory 125 | 126 | # Linux trash folder which might appear on any partition or disk 127 | .Trash-* 128 | 129 | # .nfs files are created when an open file is removed but is still being accessed 130 | .nfs* 131 | 132 | ### macOS ### 133 | # General 134 | .DS_Store 135 | .AppleDouble 136 | .LSOverride 137 | 138 | # Icon must end with two \r 139 | Icon 140 | 141 | # Thumbnails 142 | ._* 143 | 144 | # Files that might appear in the root of a volume 145 | .DocumentRevisions-V100 146 | .fseventsd 147 | .Spotlight-V100 148 | .TemporaryItems 149 | .Trashes 150 | .VolumeIcon.icns 151 | .com.apple.timemachine.donotpresent 152 | 153 | # Directories potentially created on remote AFP share 154 | .AppleDB 155 | .AppleDesktop 156 | Network Trash Folder 157 | Temporary Items 158 | .apdisk 159 | 160 | ### Node ### 161 | # Logs 162 | logs 163 | *.log 164 | npm-debug.log* 165 | yarn-debug.log* 166 | yarn-error.log* 167 | lerna-debug.log* 168 | 169 | # Diagnostic reports (https://nodejs.org/api/report.html) 170 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 171 | 172 | # Runtime data 173 | pids 174 | *.pid 175 | *.seed 176 | *.pid.lock 177 | 178 | # Directory for instrumented libs generated by jscoverage/JSCover 179 | lib-cov 180 | 181 | # Coverage directory used by tools like istanbul 182 | coverage 183 | *.lcov 184 | 185 | # nyc test coverage 186 | .nyc_output 187 | 188 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 189 | .grunt 190 | 191 | # Bower dependency directory (https://bower.io/) 192 | bower_components 193 | 194 | # node-waf configuration 195 | .lock-wscript 196 | 197 | # Compiled binary addons (https://nodejs.org/api/addons.html) 198 | build/Release 199 | 200 | # Dependency directories 201 | node_modules/ 202 | jspm_packages/ 203 | 204 | # TypeScript v1 declaration files 205 | typings/ 206 | 207 | # TypeScript cache 208 | *.tsbuildinfo 209 | 210 | # Optional npm cache directory 211 | .npm 212 | 213 | # Optional eslint cache 214 | .eslintcache 215 | 216 | # Optional REPL history 217 | .node_repl_history 218 | 219 | # Output of 'npm pack' 220 | *.tgz 221 | 222 | # Yarn Integrity file 223 | .yarn-integrity 224 | 225 | # dotenv environment variables file 226 | .env 227 | .env.test 228 | 229 | # parcel-bundler cache (https://parceljs.org/) 230 | .cache 231 | 232 | # next.js build output 233 | .next 234 | 235 | # nuxt.js build output 236 | .nuxt 237 | 238 | # vuepress build output 239 | .vuepress/dist 240 | 241 | # Serverless directories 242 | .serverless/ 243 | 244 | # FuseBox cache 245 | .fusebox/ 246 | 247 | # DynamoDB Local files 248 | .dynamodb/ 249 | 250 | ### OCaml ### 251 | *.annot 252 | *.cmo 253 | *.cma 254 | *.cmi 255 | *.a 256 | *.cmx 257 | *.cmxs 258 | *.cmxa 259 | 260 | # ocamlbuild working directory 261 | _build/ 262 | 263 | # ocamlbuild targets 264 | *.byte 265 | *.native 266 | 267 | # oasis generated files 268 | setup.data 269 | setup.log 270 | 271 | # Merlin configuring file for Vim and Emacs 272 | .merlin 273 | 274 | # Dune generated files 275 | *.install 276 | 277 | # Local OPAM switch 278 | _opam/ 279 | 280 | ### Phoenix ### 281 | # gitignore template for Phoenix projects 282 | # website: http://www.phoenixframework.org/ 283 | # 284 | # Recommended template: Elixir.gitignore 285 | 286 | # Temporary files 287 | /tmp 288 | 289 | # Static artifacts 290 | /node_modules 291 | /assets/node_modules 292 | 293 | # Since we are building assets from web/static, 294 | # we ignore priv/static. You may want to comment 295 | # this depending on your deployment strategy. 296 | /priv/static/ 297 | 298 | # Installer-related files 299 | /installer/_build 300 | /installer/tmp 301 | /installer/doc 302 | /installer/deps 303 | 304 | ### ReactNative ### 305 | # React Native Stack Base 306 | 307 | .expo 308 | __generated__ 309 | 310 | ### ReactNative.Xcode Stack ### 311 | # Xcode 312 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 313 | 314 | ## User settings 315 | xcuserdata/ 316 | 317 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 318 | *.xcscmblueprint 319 | *.xccheckout 320 | 321 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 322 | build/ 323 | DerivedData/ 324 | *.moved-aside 325 | *.pbxuser 326 | !default.pbxuser 327 | *.mode1v3 328 | !default.mode1v3 329 | *.mode2v3 330 | !default.mode2v3 331 | *.perspectivev3 332 | !default.perspectivev3 333 | 334 | ## Xcode Patch 335 | *.xcodeproj/* 336 | !*.xcodeproj/project.pbxproj 337 | !*.xcodeproj/xcshareddata/ 338 | !*.xcworkspace/contents.xcworkspacedata 339 | /*.gcno 340 | 341 | ### ReactNative.macOS Stack ### 342 | # General 343 | 344 | # Icon must end with two \r 345 | Icon 346 | 347 | 348 | # Thumbnails 349 | 350 | # Files that might appear in the root of a volume 351 | 352 | # Directories potentially created on remote AFP share 353 | 354 | ### ReactNative.Node Stack ### 355 | # Logs 356 | 357 | # Diagnostic reports (https://nodejs.org/api/report.html) 358 | 359 | # Runtime data 360 | 361 | # Directory for instrumented libs generated by jscoverage/JSCover 362 | 363 | # Coverage directory used by tools like istanbul 364 | 365 | # nyc test coverage 366 | 367 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 368 | 369 | # Bower dependency directory (https://bower.io/) 370 | 371 | # node-waf configuration 372 | 373 | # Compiled binary addons (https://nodejs.org/api/addons.html) 374 | 375 | # Dependency directories 376 | 377 | # TypeScript v1 declaration files 378 | 379 | # TypeScript cache 380 | 381 | # Optional npm cache directory 382 | 383 | # Optional eslint cache 384 | 385 | # Optional REPL history 386 | 387 | # Output of 'npm pack' 388 | 389 | # Yarn Integrity file 390 | 391 | # dotenv environment variables file 392 | 393 | # parcel-bundler cache (https://parceljs.org/) 394 | 395 | # next.js build output 396 | 397 | # nuxt.js build output 398 | 399 | # vuepress build output 400 | 401 | # Serverless directories 402 | 403 | # FuseBox cache 404 | 405 | # DynamoDB Local files 406 | 407 | ### ReactNative.Gradle Stack ### 408 | .gradle 409 | 410 | # Ignore Gradle GUI config 411 | gradle-app.setting 412 | 413 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 414 | !gradle-wrapper.jar 415 | 416 | # Cache of project 417 | .gradletasknamecache 418 | 419 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 420 | # gradle/wrapper/gradle-wrapper.properties 421 | 422 | ### ReactNative.Linux Stack ### 423 | 424 | # temporary files which can be created if a process still has a handle open of a deleted file 425 | 426 | # KDE directory preferences 427 | 428 | # Linux trash folder which might appear on any partition or disk 429 | 430 | # .nfs files are created when an open file is removed but is still being accessed 431 | 432 | ### ReactNative.Buck Stack ### 433 | buck-out/ 434 | .buckconfig.local 435 | .buckd/ 436 | .buckversion 437 | .fakebuckversion 438 | 439 | ### ReactNative.Android Stack ### 440 | # Built application files 441 | *.apk 442 | *.ap_ 443 | *.aab 444 | 445 | # Files for the ART/Dalvik VM 446 | *.dex 447 | 448 | # Java class files 449 | *.class 450 | 451 | # Generated files 452 | bin/ 453 | gen/ 454 | out/ 455 | release/ 456 | 457 | # Gradle files 458 | .gradle/ 459 | 460 | # Local configuration file (sdk path, etc) 461 | local.properties 462 | 463 | # Proguard folder generated by Eclipse 464 | proguard/ 465 | 466 | # Log Files 467 | 468 | # Android Studio Navigation editor temp files 469 | .navigation/ 470 | 471 | # Android Studio captures folder 472 | captures/ 473 | 474 | # IntelliJ 475 | *.iml 476 | .idea/workspace.xml 477 | .idea/tasks.xml 478 | .idea/gradle.xml 479 | .idea/assetWizardSettings.xml 480 | .idea/dictionaries 481 | .idea/libraries 482 | # Android Studio 3 in .gitignore file. 483 | .idea/caches 484 | .idea/modules.xml 485 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 486 | .idea/navEditor.xml 487 | 488 | # Keystore files 489 | # Uncomment the following lines if you do not want to check your keystore files in. 490 | #*.jks 491 | #*.keystore 492 | 493 | # External native build folder generated in Android Studio 2.2 and later 494 | .externalNativeBuild 495 | 496 | # Google Services (e.g. APIs or Firebase) 497 | # google-services.json 498 | 499 | # Freeline 500 | freeline.py 501 | freeline/ 502 | freeline_project_description.json 503 | 504 | # fastlane 505 | fastlane/report.xml 506 | fastlane/Preview.html 507 | fastlane/screenshots 508 | fastlane/test_output 509 | fastlane/readme.md 510 | 511 | # Version control 512 | vcs.xml 513 | 514 | # lint 515 | lint/intermediates/ 516 | lint/generated/ 517 | lint/outputs/ 518 | lint/tmp/ 519 | # lint/reports/ 520 | 521 | ### Reasonml ### 522 | /lib 523 | 524 | ### SublimeText ### 525 | # Cache files for Sublime Text 526 | *.tmlanguage.cache 527 | *.tmPreferences.cache 528 | *.stTheme.cache 529 | 530 | # Workspace files are user-specific 531 | *.sublime-workspace 532 | 533 | # Project files should be checked into the repository, unless a significant 534 | # proportion of contributors will probably not be using Sublime Text 535 | # *.sublime-project 536 | 537 | # SFTP configuration file 538 | sftp-config.json 539 | 540 | # Package control specific files 541 | Package Control.last-run 542 | Package Control.ca-list 543 | Package Control.ca-bundle 544 | Package Control.system-ca-bundle 545 | Package Control.cache/ 546 | Package Control.ca-certs/ 547 | Package Control.merged-ca-bundle 548 | Package Control.user-ca-bundle 549 | oscrypto-ca-bundle.crt 550 | bh_unicode_properties.cache 551 | 552 | # Sublime-github package stores a github token in this file 553 | # https://packagecontrol.io/packages/sublime-github 554 | GitHub.sublime-settings 555 | 556 | ### Vim ### 557 | # Swap 558 | [._]*.s[a-v][a-z] 559 | [._]*.sw[a-p] 560 | [._]s[a-rt-v][a-z] 561 | [._]ss[a-gi-z] 562 | [._]sw[a-p] 563 | 564 | # Session 565 | Session.vim 566 | Sessionx.vim 567 | 568 | # Temporary 569 | .netrwhist 570 | # Auto-generated tag files 571 | tags 572 | # Persistent undo 573 | [._]*.un~ 574 | 575 | ### VisualStudioCode ### 576 | .vscode/* 577 | 578 | ### VisualStudioCode Patch ### 579 | # Ignore all local history of files 580 | .history 581 | 582 | ### Windows ### 583 | # Windows thumbnail cache files 584 | Thumbs.db 585 | Thumbs.db:encryptable 586 | ehthumbs.db 587 | ehthumbs_vista.db 588 | 589 | # Dump file 590 | *.stackdump 591 | 592 | # Folder config file 593 | [Dd]esktop.ini 594 | 595 | # Recycle Bin used on file shares 596 | $RECYCLE.BIN/ 597 | 598 | # Windows Installer files 599 | *.cab 600 | *.msi 601 | *.msix 602 | *.msm 603 | *.msp 604 | 605 | # Windows shortcuts 606 | *.lnk 607 | 608 | ### Xcode ### 609 | # Xcode 610 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 611 | 612 | 613 | 614 | 615 | 616 | ### Xcode Patch ### 617 | **/xcshareddata/WorkspaceSettings.xcsettings 618 | 619 | ### VisualStudio ### 620 | ## Ignore Visual Studio temporary files, build results, and 621 | ## files generated by popular Visual Studio add-ons. 622 | ## 623 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 624 | 625 | # User-specific files 626 | *.rsuser 627 | *.suo 628 | *.user 629 | *.userosscache 630 | *.sln.docstates 631 | 632 | # User-specific files (MonoDevelop/Xamarin Studio) 633 | *.userprefs 634 | 635 | # Mono auto generated files 636 | mono_crash.* 637 | 638 | # Build results 639 | [Dd]ebug/ 640 | [Dd]ebugPublic/ 641 | [Rr]elease/ 642 | [Rr]eleases/ 643 | x64/ 644 | x86/ 645 | [Aa][Rr][Mm]/ 646 | [Aa][Rr][Mm]64/ 647 | bld/ 648 | [Bb]in/ 649 | [Oo]bj/ 650 | [Ll]og/ 651 | 652 | # Visual Studio 2015/2017 cache/options directory 653 | .vs/ 654 | # Uncomment if you have tasks that create the project's static files in wwwroot 655 | #wwwroot/ 656 | 657 | # Visual Studio 2017 auto generated files 658 | Generated\ Files/ 659 | 660 | # MSTest test Results 661 | [Tt]est[Rr]esult*/ 662 | [Bb]uild[Ll]og.* 663 | 664 | # NUnit 665 | *.VisualState.xml 666 | TestResult.xml 667 | nunit-*.xml 668 | 669 | # Build Results of an ATL Project 670 | [Dd]ebugPS/ 671 | [Rr]eleasePS/ 672 | dlldata.c 673 | 674 | # Benchmark Results 675 | BenchmarkDotNet.Artifacts/ 676 | 677 | # .NET Core 678 | project.lock.json 679 | project.fragment.lock.json 680 | artifacts/ 681 | 682 | # StyleCop 683 | StyleCopReport.xml 684 | 685 | # Files built by Visual Studio 686 | *_i.c 687 | *_p.c 688 | *_h.h 689 | *.ilk 690 | *.meta 691 | *.obj 692 | *.iobj 693 | *.pch 694 | *.pdb 695 | *.ipdb 696 | *.pgc 697 | *.pgd 698 | *.rsp 699 | *.sbr 700 | *.tlb 701 | *.tli 702 | *.tlh 703 | *.tmp 704 | *.tmp_proj 705 | *_wpftmp.csproj 706 | *.vspscc 707 | *.vssscc 708 | .builds 709 | *.pidb 710 | *.svclog 711 | *.scc 712 | 713 | # Chutzpah Test files 714 | _Chutzpah* 715 | 716 | # Visual C++ cache files 717 | ipch/ 718 | *.aps 719 | *.ncb 720 | *.opendb 721 | *.opensdf 722 | *.sdf 723 | *.cachefile 724 | *.VC.db 725 | *.VC.VC.opendb 726 | 727 | # Visual Studio profiler 728 | *.psess 729 | *.vsp 730 | *.vspx 731 | *.sap 732 | 733 | # Visual Studio Trace Files 734 | *.e2e 735 | 736 | # TFS 2012 Local Workspace 737 | $tf/ 738 | 739 | # Guidance Automation Toolkit 740 | *.gpState 741 | 742 | # ReSharper is a .NET coding add-in 743 | _ReSharper*/ 744 | *.[Rr]e[Ss]harper 745 | *.DotSettings.user 746 | 747 | # JustCode is a .NET coding add-in 748 | .JustCode 749 | 750 | # TeamCity is a build add-in 751 | _TeamCity* 752 | 753 | # DotCover is a Code Coverage Tool 754 | *.dotCover 755 | 756 | # AxoCover is a Code Coverage Tool 757 | .axoCover/* 758 | !.axoCover/settings.json 759 | 760 | # Visual Studio code coverage results 761 | *.coverage 762 | *.coveragexml 763 | 764 | # NCrunch 765 | _NCrunch_* 766 | .*crunch*.local.xml 767 | nCrunchTemp_* 768 | 769 | # MightyMoose 770 | *.mm.* 771 | AutoTest.Net/ 772 | 773 | # Web workbench (sass) 774 | .sass-cache/ 775 | 776 | # Installshield output folder 777 | [Ee]xpress/ 778 | 779 | # DocProject is a documentation generator add-in 780 | DocProject/buildhelp/ 781 | DocProject/Help/*.HxT 782 | DocProject/Help/*.HxC 783 | DocProject/Help/*.hhc 784 | DocProject/Help/*.hhk 785 | DocProject/Help/*.hhp 786 | DocProject/Help/Html2 787 | DocProject/Help/html 788 | 789 | # Click-Once directory 790 | publish/ 791 | 792 | # Publish Web Output 793 | *.[Pp]ublish.xml 794 | *.azurePubxml 795 | # Note: Comment the next line if you want to checkin your web deploy settings, 796 | # but database connection strings (with potential passwords) will be unencrypted 797 | *.pubxml 798 | *.publishproj 799 | 800 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 801 | # checkin your Azure Web App publish settings, but sensitive information contained 802 | # in these scripts will be unencrypted 803 | PublishScripts/ 804 | 805 | # NuGet Packages 806 | *.nupkg 807 | # NuGet Symbol Packages 808 | *.snupkg 809 | # The packages folder can be ignored because of Package Restore 810 | **/[Pp]ackages/* 811 | # except build/, which is used as an MSBuild target. 812 | !**/[Pp]ackages/build/ 813 | # Uncomment if necessary however generally it will be regenerated when needed 814 | #!**/[Pp]ackages/repositories.config 815 | # NuGet v3's project.json files produces more ignorable files 816 | *.nuget.props 817 | *.nuget.targets 818 | 819 | # Microsoft Azure Build Output 820 | csx/ 821 | *.build.csdef 822 | 823 | # Microsoft Azure Emulator 824 | ecf/ 825 | rcf/ 826 | 827 | # Windows Store app package directories and files 828 | AppPackages/ 829 | BundleArtifacts/ 830 | Package.StoreAssociation.xml 831 | _pkginfo.txt 832 | *.appx 833 | *.appxbundle 834 | *.appxupload 835 | 836 | # Visual Studio cache files 837 | # files ending in .cache can be ignored 838 | *.[Cc]ache 839 | # but keep track of directories ending in .cache 840 | !?*.[Cc]ache/ 841 | 842 | # Others 843 | ClientBin/ 844 | ~$* 845 | *.dbmdl 846 | *.dbproj.schemaview 847 | *.jfm 848 | *.pfx 849 | *.publishsettings 850 | orleans.codegen.cs 851 | 852 | # Including strong name files can present a security risk 853 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 854 | #*.snk 855 | 856 | # Since there are multiple workflows, uncomment next line to ignore bower_components 857 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 858 | #bower_components/ 859 | 860 | # RIA/Silverlight projects 861 | Generated_Code/ 862 | 863 | # Backup & report files from converting an old project file 864 | # to a newer Visual Studio version. Backup files are not needed, 865 | # because we have git ;-) 866 | _UpgradeReport_Files/ 867 | Backup*/ 868 | UpgradeLog*.XML 869 | UpgradeLog*.htm 870 | ServiceFabricBackup/ 871 | *.rptproj.bak 872 | 873 | # SQL Server files 874 | *.mdf 875 | *.ldf 876 | *.ndf 877 | 878 | # Business Intelligence projects 879 | *.rdl.data 880 | *.bim.layout 881 | *.bim_*.settings 882 | *.rptproj.rsuser 883 | *- [Bb]ackup.rdl 884 | *- [Bb]ackup ([0-9]).rdl 885 | *- [Bb]ackup ([0-9][0-9]).rdl 886 | 887 | # Microsoft Fakes 888 | FakesAssemblies/ 889 | 890 | # GhostDoc plugin setting file 891 | *.GhostDoc.xml 892 | 893 | # Node.js Tools for Visual Studio 894 | .ntvs_analysis.dat 895 | 896 | # Visual Studio 6 build log 897 | *.plg 898 | 899 | # Visual Studio 6 workspace options file 900 | *.opt 901 | 902 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 903 | *.vbw 904 | 905 | # Visual Studio LightSwitch build output 906 | **/*.HTMLClient/GeneratedArtifacts 907 | **/*.DesktopClient/GeneratedArtifacts 908 | **/*.DesktopClient/ModelManifest.xml 909 | **/*.Server/GeneratedArtifacts 910 | **/*.Server/ModelManifest.xml 911 | _Pvt_Extensions 912 | 913 | # Paket dependency manager 914 | .paket/paket.exe 915 | paket-files/ 916 | 917 | # FAKE - F# Make 918 | .fake/ 919 | 920 | # CodeRush personal settings 921 | .cr/personal 922 | 923 | # Python Tools for Visual Studio (PTVS) 924 | __pycache__/ 925 | *.pyc 926 | 927 | # Cake - Uncomment if you are using it 928 | # tools/** 929 | # !tools/packages.config 930 | 931 | # Tabs Studio 932 | *.tss 933 | 934 | # Telerik's JustMock configuration file 935 | *.jmconfig 936 | 937 | # BizTalk build output 938 | *.btp.cs 939 | *.btm.cs 940 | *.odx.cs 941 | *.xsd.cs 942 | 943 | # OpenCover UI analysis results 944 | OpenCover/ 945 | 946 | # Azure Stream Analytics local run output 947 | ASALocalRun/ 948 | 949 | # MSBuild Binary and Structured Log 950 | *.binlog 951 | 952 | # NVidia Nsight GPU debugger configuration file 953 | *.nvuser 954 | 955 | # MFractors (Xamarin productivity tool) working folder 956 | .mfractor/ 957 | 958 | # Local History for Visual Studio 959 | .localhistory/ 960 | 961 | # BeatPulse healthcheck temp database 962 | healthchecksdb 963 | 964 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 965 | MigrationBackup/ 966 | 967 | # End of https://www.gitignore.io/api/elm,vim,node,xcode,ocaml,linux,macos,emacs,elixir,phoenix,windows,haskell,reasonml,reactnative,sublimetext,visualstudio,visualstudiocode 968 | 969 | 970 | # Created by https://www.gitignore.io/api/latex 971 | # Edit at https://www.gitignore.io/?templates=latex 972 | 973 | ### LaTeX ### 974 | ## Core latex/pdflatex auxiliary files: 975 | *.aux 976 | *.lof 977 | *.log 978 | *.lot 979 | *.fls 980 | *.out 981 | *.toc 982 | *.fmt 983 | *.fot 984 | *.cb 985 | *.cb2 986 | .*.lb 987 | 988 | ## Intermediate documents: 989 | *.dvi 990 | *.xdv 991 | *-converted-to.* 992 | # these rules might exclude image files for figures etc. 993 | # *.ps 994 | # *.eps 995 | # *.pdf 996 | 997 | ## Generated if empty string is given at "Please type another file name for output:" 998 | .pdf 999 | 1000 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 1001 | *.bbl 1002 | *.bcf 1003 | *.blg 1004 | *-blx.aux 1005 | *-blx.bib 1006 | *.run.xml 1007 | 1008 | ## Build tool auxiliary files: 1009 | *.fdb_latexmk 1010 | *.synctex 1011 | *.synctex(busy) 1012 | *.synctex.gz 1013 | *.synctex.gz(busy) 1014 | *.pdfsync 1015 | 1016 | ## Build tool directories for auxiliary files 1017 | # latexrun 1018 | latex.out/ 1019 | 1020 | ## Auxiliary and intermediate files from other packages: 1021 | # algorithms 1022 | *.alg 1023 | *.loa 1024 | 1025 | # achemso 1026 | acs-*.bib 1027 | 1028 | # amsthm 1029 | *.thm 1030 | 1031 | # beamer 1032 | *.nav 1033 | *.pre 1034 | *.snm 1035 | *.vrb 1036 | 1037 | # changes 1038 | *.soc 1039 | 1040 | # comment 1041 | *.cut 1042 | 1043 | # cprotect 1044 | *.cpt 1045 | 1046 | # elsarticle (documentclass of Elsevier journals) 1047 | *.spl 1048 | 1049 | # endnotes 1050 | *.ent 1051 | 1052 | # fixme 1053 | *.lox 1054 | 1055 | # feynmf/feynmp 1056 | *.mf 1057 | *.mp 1058 | *.t[1-9] 1059 | *.t[1-9][0-9] 1060 | *.tfm 1061 | 1062 | #(r)(e)ledmac/(r)(e)ledpar 1063 | *.end 1064 | *.?end 1065 | *.[1-9] 1066 | *.[1-9][0-9] 1067 | *.[1-9][0-9][0-9] 1068 | *.[1-9]R 1069 | *.[1-9][0-9]R 1070 | *.[1-9][0-9][0-9]R 1071 | *.eledsec[1-9] 1072 | *.eledsec[1-9]R 1073 | *.eledsec[1-9][0-9] 1074 | *.eledsec[1-9][0-9]R 1075 | *.eledsec[1-9][0-9][0-9] 1076 | *.eledsec[1-9][0-9][0-9]R 1077 | 1078 | # glossaries 1079 | *.acn 1080 | *.acr 1081 | *.glg 1082 | *.glo 1083 | *.gls 1084 | *.glsdefs 1085 | 1086 | # uncomment this for glossaries-extra (will ignore makeindex's style files!) 1087 | # *.ist 1088 | 1089 | # gnuplottex 1090 | *-gnuplottex-* 1091 | 1092 | # gregoriotex 1093 | *.gaux 1094 | *.gtex 1095 | 1096 | # htlatex 1097 | *.4ct 1098 | *.4tc 1099 | *.idv 1100 | *.lg 1101 | *.trc 1102 | *.xref 1103 | 1104 | # hyperref 1105 | *.brf 1106 | 1107 | # knitr 1108 | *-concordance.tex 1109 | # TODO Comment the next line if you want to keep your tikz graphics files 1110 | *.tikz 1111 | *-tikzDictionary 1112 | 1113 | # listings 1114 | *.lol 1115 | 1116 | # luatexja-ruby 1117 | *.ltjruby 1118 | 1119 | # makeidx 1120 | *.idx 1121 | *.ilg 1122 | *.ind 1123 | 1124 | # minitoc 1125 | *.maf 1126 | *.mlf 1127 | *.mlt 1128 | *.mtc[0-9]* 1129 | *.slf[0-9]* 1130 | *.slt[0-9]* 1131 | *.stc[0-9]* 1132 | 1133 | # minted 1134 | _minted* 1135 | *.pyg 1136 | 1137 | # morewrites 1138 | *.mw 1139 | 1140 | # nomencl 1141 | *.nlg 1142 | *.nlo 1143 | *.nls 1144 | 1145 | # pax 1146 | *.pax 1147 | 1148 | # pdfpcnotes 1149 | *.pdfpc 1150 | 1151 | # sagetex 1152 | *.sagetex.sage 1153 | *.sagetex.py 1154 | *.sagetex.scmd 1155 | 1156 | # scrwfile 1157 | *.wrt 1158 | 1159 | # sympy 1160 | *.sout 1161 | *.sympy 1162 | sympy-plots-for-*.tex/ 1163 | 1164 | # pdfcomment 1165 | *.upa 1166 | *.upb 1167 | 1168 | # pythontex 1169 | *.pytxcode 1170 | pythontex-files-*/ 1171 | 1172 | # tcolorbox 1173 | *.listing 1174 | 1175 | # thmtools 1176 | *.loe 1177 | 1178 | # TikZ & PGF 1179 | *.dpth 1180 | *.md5 1181 | *.auxlock 1182 | 1183 | # todonotes 1184 | *.tdo 1185 | 1186 | # vhistory 1187 | *.hst 1188 | *.ver 1189 | 1190 | # easy-todo 1191 | *.lod 1192 | 1193 | # xcolor 1194 | *.xcp 1195 | 1196 | # xmpincl 1197 | *.xmpi 1198 | 1199 | # xindy 1200 | *.xdy 1201 | 1202 | # xypic precompiled matrices 1203 | *.xyc 1204 | 1205 | # endfloat 1206 | *.ttt 1207 | *.fff 1208 | 1209 | # Latexian 1210 | TSWLatexianTemp* 1211 | 1212 | ## Editors: 1213 | # WinEdt 1214 | *.bak 1215 | *.sav 1216 | 1217 | # Texpad 1218 | .texpadtmp 1219 | 1220 | # LyX 1221 | *.lyx~ 1222 | 1223 | # Kile 1224 | *.backup 1225 | 1226 | # KBibTeX 1227 | *~[0-9]* 1228 | 1229 | # auto folder when using emacs and auctex 1230 | ./auto/* 1231 | *.el 1232 | 1233 | # expex forward references with \gathertags 1234 | *-tags.tex 1235 | 1236 | # standalone packages 1237 | *.sta 1238 | 1239 | ### LaTeX Patch ### 1240 | # glossaries 1241 | *.glstex 1242 | 1243 | # End of https://www.gitignore.io/api/latex 1244 | -------------------------------------------------------------------------------- /.hlint.yaml: -------------------------------------------------------------------------------- 1 | # HLint configuration file 2 | # https://github.com/ndmitchell/hlint 3 | ########################## 4 | 5 | # This file contains a template configuration file, which is typically 6 | # placed as .hlint.yaml in the root of your project 7 | 8 | # Specify additional command line arguments 9 | # 10 | - arguments: 11 | - --color 12 | - --cpp-simple 13 | - -XQuasiQuotes 14 | 15 | # Control which extensions/flags/modules/functions can be used 16 | # 17 | # - extensions: 18 | # - default: false # all extension are banned by default 19 | # - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used 20 | # 21 | # - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module 22 | # 23 | # - flags: 24 | # - {name: -w, within: []} # -w is allowed nowhere 25 | # 26 | - modules: 27 | # if you import Data.Set qualified, it must be as 'Set' 28 | # - {name: Control.Arrow, within: []} # Certain modules are banned entirely 29 | - {name: [Data.HashMap.Strict], as: HashMap} 30 | - {name: [Data.ByteString], as: BS} 31 | - {name: [Data.ByteString.Lazy], as: BL} 32 | - {name: [Data.ByteString.Char8], as: C} 33 | - {name: [Data.ByteString.Lazy.Char8], as: CL} 34 | - {name: [Data.Text], as: T} 35 | - {name: [Data.Text.IO], as: TIO} 36 | - {name: [Data.Text.Lazy.IO], as: TLIO} 37 | - {name: [Data.Text.Encoding], as: TE} 38 | - {name: [Data.Text.Lazy.Encoding], as: TLE} 39 | - {name: [Data.Aeson], as: JSON} 40 | 41 | # - functions: 42 | # - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules 43 | 44 | 45 | # Add custom hints for this project 46 | # 47 | # Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar" 48 | # - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x} 49 | 50 | 51 | # Turn on hints that are off by default 52 | # 53 | # Ban "module X(module X) where", to require a real export list 54 | # - warn: {name: Use explicit module export list} 55 | # 56 | # Replace a $ b $ c with a . b $ c 57 | # - group: {name: dollar, enabled: true} 58 | # 59 | # Generalise map to fmap, ++ to <> 60 | - group: {name: generalise, enabled: true} 61 | 62 | # Ignore some builtin hints 63 | - ignore: {name: Use String} 64 | # - ignore: {name: Use let} 65 | # - ignore: {name: Use const, within: SpecialModule} # Only within certain modules 66 | 67 | # Define some custom infix operators 68 | # - fixity: infixr 3 ~^#^~ 69 | 70 | # To generate a suitable file for HLint do: 71 | # $ hlint --default > .hlint.yaml 72 | -------------------------------------------------------------------------------- /.stylish-haskell.yaml: -------------------------------------------------------------------------------- 1 | # stylish-haskell configuration file 2 | # ================================== 3 | 4 | # The stylish-haskell tool is mainly configured by specifying steps. These steps 5 | # are a list, so they have an order, and one specific step may appear more than 6 | # once (if needed). Each file is processed by these steps in the given order. 7 | steps: 8 | # Convert some ASCII sequences to their Unicode equivalents. This is disabled 9 | # by default. 10 | # - unicode_syntax: 11 | # # In order to make this work, we also need to insert the UnicodeSyntax 12 | # # language pragma. If this flag is set to true, we insert it when it's 13 | # # not already present. You may want to disable it if you configure 14 | # # language extensions using some other method than pragmas. Default: 15 | # # true. 16 | # add_language_pragma: true 17 | 18 | # Format record definitions. This is disabled by default. 19 | # 20 | # You can control the layout of record fields. The only rules that can't be configured 21 | # are these: 22 | # 23 | # - "|" is always aligned with "=" 24 | # - "," in fields is always aligned with "{" 25 | # - "}" is likewise always aligned with "{" 26 | # 27 | # - records: 28 | # # How to format equals sign between type constructor and data constructor. 29 | # # Possible values: 30 | # # - "same_line" -- leave "=" AND data constructor on the same line as the type constructor. 31 | # # - "indent N" -- insert a new line and N spaces from the beginning of the next line. 32 | # equals: "indent 2" 33 | # 34 | # # How to format first field of each record constructor. 35 | # # Possible values: 36 | # # - "same_line" -- "{" and first field goes on the same line as the data constructor. 37 | # # - "indent N" -- insert a new line and N spaces from the beginning of the data constructor 38 | # first_field: "indent 2" 39 | # 40 | # # How many spaces to insert between the column with "," and the beginning of the comment in the next line. 41 | # field_comment: 2 42 | # 43 | # # How many spaces to insert before "deriving" clause. Deriving clauses are always on separate lines. 44 | # deriving: 2 45 | 46 | # Align the right hand side of some elements. This is quite conservative 47 | # and only applies to statements where each element occupies a single 48 | # line. All default to true. 49 | - simple_align: 50 | cases: true 51 | top_level_patterns: true 52 | records: true 53 | 54 | # Import cleanup 55 | - imports: 56 | # There are different ways we can align names and lists. 57 | # 58 | # - global: Align the import names and import list throughout the entire 59 | # file. 60 | # 61 | # - file: Like global, but don't add padding when there are no qualified 62 | # imports in the file. 63 | # 64 | # - group: Only align the imports per group (a group is formed by adjacent 65 | # import lines). 66 | # 67 | # - none: Do not perform any alignment. 68 | # 69 | # Default: global. 70 | align: global 71 | 72 | # The following options affect only import list alignment. 73 | # 74 | # List align has following options: 75 | # 76 | # - after_alias: Import list is aligned with end of import including 77 | # 'as' and 'hiding' keywords. 78 | # 79 | # > import qualified Data.List as List (concat, foldl, foldr, head, 80 | # > init, last, length) 81 | # 82 | # - with_alias: Import list is aligned with start of alias or hiding. 83 | # 84 | # > import qualified Data.List as List (concat, foldl, foldr, head, 85 | # > init, last, length) 86 | # 87 | # - with_module_name: Import list is aligned `list_padding` spaces after 88 | # the module name. 89 | # 90 | # > import qualified Data.List as List (concat, foldl, foldr, head, 91 | # init, last, length) 92 | # 93 | # This is mainly intended for use with `pad_module_names: false`. 94 | # 95 | # > import qualified Data.List as List (concat, foldl, foldr, head, 96 | # init, last, length, scanl, scanr, take, drop, 97 | # sort, nub) 98 | # 99 | # - new_line: Import list starts always on new line. 100 | # 101 | # > import qualified Data.List as List 102 | # > (concat, foldl, foldr, head, init, last, length) 103 | # 104 | # Default: after_alias 105 | list_align: after_alias 106 | 107 | # Right-pad the module names to align imports in a group: 108 | # 109 | # - true: a little more readable 110 | # 111 | # > import qualified Data.List as List (concat, foldl, foldr, 112 | # > init, last, length) 113 | # > import qualified Data.List.Extra as List (concat, foldl, foldr, 114 | # > init, last, length) 115 | # 116 | # - false: diff-safe 117 | # 118 | # > import qualified Data.List as List (concat, foldl, foldr, init, 119 | # > last, length) 120 | # > import qualified Data.List.Extra as List (concat, foldl, foldr, 121 | # > init, last, length) 122 | # 123 | # Default: true 124 | pad_module_names: true 125 | 126 | # Long list align style takes effect when import is too long. This is 127 | # determined by 'columns' setting. 128 | # 129 | # - inline: This option will put as much specs on same line as possible. 130 | # 131 | # - new_line: Import list will start on new line. 132 | # 133 | # - new_line_multiline: Import list will start on new line when it's 134 | # short enough to fit to single line. Otherwise it'll be multiline. 135 | # 136 | # - multiline: One line per import list entry. 137 | # Type with constructor list acts like single import. 138 | # 139 | # > import qualified Data.Map as M 140 | # > ( empty 141 | # > , singleton 142 | # > , ... 143 | # > , delete 144 | # > ) 145 | # 146 | # Default: inline 147 | long_list_align: inline 148 | 149 | # Align empty list (importing instances) 150 | # 151 | # Empty list align has following options 152 | # 153 | # - inherit: inherit list_align setting 154 | # 155 | # - right_after: () is right after the module name: 156 | # 157 | # > import Vector.Instances () 158 | # 159 | # Default: inherit 160 | empty_list_align: inherit 161 | 162 | # List padding determines indentation of import list on lines after import. 163 | # This option affects 'long_list_align'. 164 | # 165 | # - : constant value 166 | # 167 | # - module_name: align under start of module name. 168 | # Useful for 'file' and 'group' align settings. 169 | # 170 | # Default: 4 171 | list_padding: 4 172 | 173 | # Separate lists option affects formatting of import list for type 174 | # or class. The only difference is single space between type and list 175 | # of constructors, selectors and class functions. 176 | # 177 | # - true: There is single space between Foldable type and list of it's 178 | # functions. 179 | # 180 | # > import Data.Foldable (Foldable (fold, foldl, foldMap)) 181 | # 182 | # - false: There is no space between Foldable type and list of it's 183 | # functions. 184 | # 185 | # > import Data.Foldable (Foldable(fold, foldl, foldMap)) 186 | # 187 | # Default: true 188 | separate_lists: true 189 | 190 | # Space surround option affects formatting of import lists on a single 191 | # line. The only difference is single space after the initial 192 | # parenthesis and a single space before the terminal parenthesis. 193 | # 194 | # - true: There is single space associated with the enclosing 195 | # parenthesis. 196 | # 197 | # > import Data.Foo ( foo ) 198 | # 199 | # - false: There is no space associated with the enclosing parenthesis 200 | # 201 | # > import Data.Foo (foo) 202 | # 203 | # Default: false 204 | space_surround: false 205 | 206 | # Language pragmas 207 | - language_pragmas: 208 | # We can generate different styles of language pragma lists. 209 | # 210 | # - vertical: Vertical-spaced language pragmas, one per line. 211 | # 212 | # - compact: A more compact style. 213 | # 214 | # - compact_line: Similar to compact, but wrap each line with 215 | # `{-#LANGUAGE #-}'. 216 | # 217 | # Default: vertical. 218 | style: vertical 219 | 220 | # Align affects alignment of closing pragma brackets. 221 | # 222 | # - true: Brackets are aligned in same column. 223 | # 224 | # - false: Brackets are not aligned together. There is only one space 225 | # between actual import and closing bracket. 226 | # 227 | # Default: true 228 | align: true 229 | 230 | # stylish-haskell can detect redundancy of some language pragmas. If this 231 | # is set to true, it will remove those redundant pragmas. Default: true. 232 | remove_redundant: true 233 | 234 | # Language prefix to be used for pragma declaration, this allows you to 235 | # use other options non case-sensitive like "language" or "Language". 236 | # If a non correct String is provided, it will default to: LANGUAGE. 237 | language_prefix: LANGUAGE 238 | 239 | # Replace tabs by spaces. This is disabled by default. 240 | # - tabs: 241 | # # Number of spaces to use for each tab. Default: 8, as specified by the 242 | # # Haskell report. 243 | # spaces: 8 244 | 245 | # Remove trailing whitespace 246 | - trailing_whitespace: {} 247 | 248 | # Squash multiple spaces between the left and right hand sides of some 249 | # elements into single spaces. Basically, this undoes the effect of 250 | # simple_align but is a bit less conservative. 251 | # - squash: {} 252 | 253 | # A common setting is the number of columns (parts of) code will be wrapped 254 | # to. Different steps take this into account. 255 | # 256 | # Set this to null to disable all line wrapping. 257 | # 258 | # Default: 80. 259 | columns: 80 260 | 261 | # By default, line endings are converted according to the OS. You can override 262 | # preferred format here. 263 | # 264 | # - native: Native newline format. CRLF on Windows, LF on other OSes. 265 | # 266 | # - lf: Convert to LF ("\n"). 267 | # 268 | # - crlf: Convert to CRLF ("\r\n"). 269 | # 270 | # Default: native. 271 | newline: native 272 | 273 | # Sometimes, language extensions are specified in a cabal file or from the 274 | # command line instead of using language pragmas in the file. stylish-haskell 275 | # needs to be aware of these, so it can parse the file correctly. 276 | # 277 | # No language extensions are enabled by default. 278 | language_extensions: 279 | - ApplicativeDo 280 | - BangPatterns 281 | - BinaryLiterals 282 | - BlockArguments 283 | - ConstraintKinds 284 | - DataKinds 285 | - DefaultSignatures 286 | - DeriveAnyClass 287 | - DeriveFoldable 288 | - DeriveFunctor 289 | - DeriveGeneric 290 | - DeriveLift 291 | - DeriveTraversable 292 | - DerivingStrategies 293 | - DuplicateRecordFields 294 | - EmptyDataDecls 295 | - FlexibleContexts 296 | - FlexibleInstances 297 | - FunctionalDependencies 298 | - GADTs 299 | - GeneralizedNewtypeDeriving 300 | - InstanceSigs 301 | - KindSignatures 302 | - LambdaCase 303 | - LiberalTypeSynonyms 304 | - MultiParamTypeClasses 305 | - MultiWayIf 306 | - NamedFieldPuns 307 | - NoImplicitPrelude 308 | - NoMonomorphismRestriction 309 | - NumericUnderscores 310 | - OverloadedStrings 311 | - OverloadedLabels 312 | - OverloadedLists 313 | - PostfixOperators 314 | - RankNTypes 315 | - RecordWildCards 316 | - ScopedTypeVariables 317 | - StandaloneDeriving 318 | - TupleSections 319 | - TypeApplications 320 | - TypeFamilies 321 | - TypeSynonymInstances 322 | - TemplateHaskell 323 | - TypeOperators 324 | - ViewPatterns 325 | 326 | # - TemplateHaskell 327 | # - QuasiQuotes 328 | 329 | # Attempt to find the cabal file in ancestors of the current directory, and 330 | # parse options (currently only language extensions) from that. 331 | # 332 | # Default: true 333 | cabal: true 334 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | - initial release 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | package = ipfs 2 | 3 | stack_yaml = STACK_YAML="stack.yaml" 4 | stack = $(stack_yaml) stack 5 | 6 | setup: 7 | stack install ghcid 8 | 9 | build: 10 | $(stack) build --fast $(package):lib 11 | 12 | release: 13 | $(stack) build 14 | 15 | docs: 16 | $(stack) haddock $(package) --open 17 | 18 | docserver: 19 | http-server ./.stack-work/dist/x86_64-osx/Cabal-3.0.1.0/doc/html/$(package) -p 1313 & \ 20 | open http://localhost:1313 21 | 22 | doctest: 23 | $(stack) test :fission-doctest --fast 24 | 25 | unit-test: 26 | $(stack) test :fission-test --fast 27 | 28 | test: 29 | make unit-test && make doctest 30 | 31 | test-ghci: 32 | $(stack) ghci $(package):test:$(package)-tests --ghci-options='-j6 +RTS -A128m' 33 | 34 | bench: 35 | $(stack) build --bench $(package) 36 | 37 | dev: 38 | $(stack) exec -- ghcid -c "stack ghci $(package):lib --test" 39 | 40 | live: 41 | $(stack) exec -- yesod devel 42 | 43 | .PHONY : build dirty run install ghci test test-ghci watch doctest lint 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ipfs-haskell 2 | 3 | [![Build Status](https://travis-ci.org/fission-suite/PROJECTNAME.svg?branch=master)](https://travis-ci.org/fission-suite/ipfs-haskell) 4 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/fission-suite/blob/master/LICENSE) 5 | [![Maintainability](https://api.codeclimate.com/v1/badges/44fb6a8a0cfd88bc41ef/maintainability)](https://codeclimate.com/github/fission-suite/ipfs-haskell/maintainability) 6 | [![Built by FISSION](https://img.shields.io/badge/⌘-Built_by_FISSION-purple.svg)](https://fission.codes) 7 | [![Discord](https://img.shields.io/discord/478735028319158273.svg)](https://discord.gg/zAQBDEq) 8 | [![Discourse](https://img.shields.io/discourse/https/talk.fission.codes/topics)](https://talk.fission.codes) 9 | 10 | Documentation: [ipfs on hackage](http://hackage.haskell.org/package/ipfs) 11 | 12 | A library for integrating IPFS into your haskell applications. Interact with the IPFS network by shelling out to a local IPFS node or communicating via the HTTP interface of a remote node. 13 | 14 | # QuickStart 15 | 16 | Define instances for `MonadLocalIPFS` and/or `MonadRemoteIPFS`. Each requires only one function: 17 | 18 | ```haskell 19 | class Monad m => MonadRemoteIPFS m where 20 | runRemote :: Servant.ClientM a -> m (Either Servant.ClientError a) 21 | 22 | class Monad m => MonadLocalIPFS m where 23 | runLocal :: 24 | [IPFS.Opt] 25 | -> Lazy.ByteString 26 | -> m (Either Process.Error Process.RawMessage) 27 | ``` 28 | 29 | We use RIO processes to shell out to a local IPFS node and Servant for HTTP requests to a remote node. 30 | 31 | After that, simply add `MonadLocalIPFS m` as a constraint to a function and you'll be able to call IPFS within it. 32 | For instance: 33 | ```haskell 34 | import Network.IPFS 35 | import qualified Network.IPFS.Add as IPFS 36 | import Network.IPFS.File.Types as File 37 | 38 | add :: 39 | MonadLocalIPFS m 40 | => File.Serialzed 41 | -> m () 42 | add (Serialized rawData) = IPFS.addRaw rawData >>= \case 43 | Right newCID -> 44 | -- ... 45 | Left err -> 46 | -- ... 47 | 48 | ``` 49 | 50 | You can see example instances below: 51 | ```haskell 52 | instance 53 | ( HasProcessContext cfg 54 | , HasLogFunc cfg 55 | , Has IPFS.BinPath cfg 56 | , Has IPFS.Timeout cfg 57 | ) 58 | => MonadLocalIPFS (RIO cfg) where 59 | runLocal opts arg = do 60 | IPFS.BinPath ipfs <- view hasLens 61 | IPFS.Timeout secs <- view hasLens 62 | let opts' = ("--timeout=" <> show secs <> "s") : opts 63 | 64 | runProc readProcess ipfs (byteStringInput arg) byteStringOutput opts' >>= \case 65 | (ExitSuccess, contents, _) -> 66 | return $ Right contents 67 | (ExitFailure _, _, stdErr) 68 | | Lazy.isSuffixOf "context deadline exceeded" stdErr -> 69 | return . Left $ Process.Timeout secs 70 | | otherwise -> 71 | return . Left $ Process.UnknownErr stdErr 72 | 73 | instance 74 | ( Has IPFS.URL cfg 75 | , Has HTTP.Manager cfg 76 | ) 77 | => MonadRemoteIPFS (RIO cfg) where 78 | runRemote query = do 79 | IPFS.URL url <- view hasLens 80 | manager <- view hasLens 81 | 82 | url 83 | & mkClientEnv manager 84 | & runClientM query 85 | & liftIO 86 | ``` 87 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1673956053, 7 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-utils": { 20 | "locked": { 21 | "lastModified": 1678901627, 22 | "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", 23 | "owner": "numtide", 24 | "repo": "flake-utils", 25 | "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "type": "github" 32 | } 33 | }, 34 | "nixpkgs": { 35 | "locked": { 36 | "lastModified": 1680519927, 37 | "narHash": "sha256-iYaec3MmYjduOoSxQoVspJS9JUhcTduREzsj+L+384g=", 38 | "owner": "NixOS", 39 | "repo": "nixpkgs", 40 | "rev": "6e46a75930af12a7c18db76b48be60b72dd6d031", 41 | "type": "github" 42 | }, 43 | "original": { 44 | "owner": "NixOS", 45 | "ref": "release-22.11", 46 | "repo": "nixpkgs", 47 | "type": "github" 48 | } 49 | }, 50 | "root": { 51 | "inputs": { 52 | "flake-compat": "flake-compat", 53 | "flake-utils": "flake-utils", 54 | "nixpkgs": "nixpkgs" 55 | } 56 | } 57 | }, 58 | "root": "root", 59 | "version": 7 60 | } 61 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "IPFS APIs"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/release-22.11"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | 8 | flake-compat = { 9 | url = "github:edolstra/flake-compat"; 10 | flake = false; 11 | }; 12 | }; 13 | 14 | outputs = { self, nixpkgs, flake-utils, ... }: 15 | flake-utils.lib.eachDefaultSystem 16 | (system: 17 | let 18 | 19 | pkgs = nixpkgs.legacyPackages.${system}; 20 | 21 | # Inspired by https://www.tweag.io/blog/2022-06-02-haskell-stack-nix-shell/ 22 | stack-wrapped = pkgs.symlinkJoin { 23 | name = "stack"; 24 | paths = [ pkgs.stack ]; 25 | buildInputs = [ pkgs.makeWrapper ]; 26 | postBuild = '' 27 | wrapProgram $out/bin/stack \ 28 | --add-flags "\ 29 | --nix \ 30 | --nix-shell-file=nix/stack-integration.nix \ 31 | " 32 | ''; 33 | }; 34 | 35 | # Wrapper commands for convenience 36 | commands = import ./nix/commands.nix; 37 | tasks = commands { 38 | inherit pkgs; 39 | inherit stack-wrapped; 40 | }; 41 | 42 | # The default version of HLS (with binary cache) is built with GHC 9.0.1 43 | # We can get this version working with our current set up, but it builds 44 | # from source (and takes a long time). 45 | # 46 | # The prebuilt package is marked as broken on aarch64-darwin 47 | haskellPackages = pkgs.haskell.packages.ghc8107; 48 | in 49 | { 50 | devShells.default = pkgs.mkShell { 51 | name = "fission"; 52 | buildInputs = [ 53 | stack-wrapped 54 | haskellPackages.haskell-language-server 55 | pkgs.cachix 56 | pkgs.zlib 57 | pkgs.nixpkgs-fmt 58 | pkgs.stylish-haskell 59 | tasks 60 | ]; 61 | NIX_PATH = "nixpkgs=" + pkgs.path; 62 | }; 63 | } 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /ipfs.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.0 2 | 3 | -- This file has been generated from package.yaml by hpack version 0.35.2. 4 | -- 5 | -- see: https://github.com/sol/hpack 6 | 7 | name: ipfs 8 | version: 1.4.0 9 | synopsis: Access IPFS locally and remotely 10 | description: Interact with the IPFS network by shelling out to a local IPFS node or communicating via the HTTP interface of a remote IPFS node. 11 | category: Network 12 | homepage: https://github.com/fission-suite/ipfs-haskell#readme 13 | bug-reports: https://github.com/fission-suite/ipfs-haskell/issues 14 | author: Brooklyn Zelenka, 15 | Daniel Holmgren, 16 | Steven Vandevelde, 17 | James Walker 18 | maintainer: brooklyn@fission.codes, 19 | daniel@fission.codes, 20 | steven@fission.codes, 21 | james@fission.codes 22 | copyright: © 2021 Fission Internet Software Services for Open Networks Inc. 23 | license: Apache-2.0 24 | license-file: LICENSE 25 | build-type: Simple 26 | tested-with: 27 | GHC==8.10.7 28 | extra-source-files: 29 | README.md 30 | 31 | source-repository head 32 | type: git 33 | location: https://github.com/fission-suite/ipfs-haskell 34 | 35 | library 36 | exposed-modules: 37 | Network.IPFS 38 | Network.IPFS.Add 39 | Network.IPFS.Add.Error 40 | Network.IPFS.BinPath.Types 41 | Network.IPFS.Bytes.Types 42 | Network.IPFS.CID.Types 43 | Network.IPFS.Client 44 | Network.IPFS.Client.Add 45 | Network.IPFS.Client.Cat 46 | Network.IPFS.Client.DAG.Put.Types 47 | Network.IPFS.Client.DAG.Types 48 | Network.IPFS.Client.Error.Types 49 | Network.IPFS.Client.Param 50 | Network.IPFS.Client.Pin 51 | Network.IPFS.Client.Stat 52 | Network.IPFS.Client.Streaming.Pin 53 | Network.IPFS.DAG 54 | Network.IPFS.DAG.Link 55 | Network.IPFS.DAG.Link.Types 56 | Network.IPFS.DAG.Node.Types 57 | Network.IPFS.Error 58 | Network.IPFS.File.Form.Types 59 | Network.IPFS.File.Types 60 | Network.IPFS.Gateway.Types 61 | Network.IPFS.Get 62 | Network.IPFS.Get.Error 63 | Network.IPFS.Ignored.Types 64 | Network.IPFS.Info.Types 65 | Network.IPFS.Internal.Orphanage.ByteString.Lazy 66 | Network.IPFS.Internal.Orphanage.Natural 67 | Network.IPFS.Internal.Orphanage.Utf8Builder 68 | Network.IPFS.Internal.UTF8 69 | Network.IPFS.Local.Class 70 | Network.IPFS.MIME.RawPlainText.Types 71 | Network.IPFS.Name.Types 72 | Network.IPFS.Path.Types 73 | Network.IPFS.Peer 74 | Network.IPFS.Peer.Error 75 | Network.IPFS.Peer.Types 76 | Network.IPFS.Pin 77 | Network.IPFS.Prelude 78 | Network.IPFS.Process 79 | Network.IPFS.Process.Error 80 | Network.IPFS.Process.Types 81 | Network.IPFS.Remote.Class 82 | Network.IPFS.Remote.Error 83 | Network.IPFS.SparseTree 84 | Network.IPFS.SparseTree.Types 85 | Network.IPFS.Stat 86 | Network.IPFS.Stat.Error 87 | Network.IPFS.Stat.Types 88 | Network.IPFS.Timeout.Types 89 | Network.IPFS.Types 90 | Network.IPFS.URL.Types 91 | Paths_ipfs 92 | autogen-modules: 93 | Paths_ipfs 94 | hs-source-dirs: 95 | library 96 | default-extensions: 97 | ApplicativeDo 98 | BangPatterns 99 | BinaryLiterals 100 | BlockArguments 101 | ConstraintKinds 102 | DataKinds 103 | DeriveAnyClass 104 | DeriveFoldable 105 | DeriveFunctor 106 | DeriveGeneric 107 | DeriveLift 108 | DeriveTraversable 109 | DerivingStrategies 110 | DuplicateRecordFields 111 | FlexibleContexts 112 | FlexibleInstances 113 | FunctionalDependencies 114 | GADTs 115 | GeneralizedNewtypeDeriving 116 | KindSignatures 117 | LambdaCase 118 | LiberalTypeSynonyms 119 | MultiParamTypeClasses 120 | MultiWayIf 121 | NamedFieldPuns 122 | NoImplicitPrelude 123 | NoMonomorphismRestriction 124 | OverloadedStrings 125 | OverloadedLabels 126 | OverloadedLists 127 | PostfixOperators 128 | RankNTypes 129 | RecordWildCards 130 | ScopedTypeVariables 131 | StandaloneDeriving 132 | TupleSections 133 | TypeApplications 134 | TypeFamilies 135 | TypeSynonymInstances 136 | TemplateHaskell 137 | TypeOperators 138 | ViewPatterns 139 | ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wpartial-fields -Wredundant-constraints -fhide-source-paths 140 | build-depends: 141 | Glob 142 | , aeson 143 | , base <5 144 | , bytestring 145 | , envy 146 | , flow 147 | , http-media 148 | , lens 149 | , monad-logger 150 | , network-ip 151 | , regex-compat 152 | , rio 153 | , servant 154 | , servant-client 155 | , servant-multipart 156 | , servant-multipart-api 157 | , servant-multipart-client 158 | , swagger2 159 | , text 160 | , vector 161 | default-language: Haskell2010 162 | 163 | test-suite ipfs-doctest 164 | type: exitcode-stdio-1.0 165 | main-is: Main.hs 166 | other-modules: 167 | Paths_ipfs 168 | autogen-modules: 169 | Paths_ipfs 170 | hs-source-dirs: 171 | test/doctest 172 | default-extensions: 173 | ApplicativeDo 174 | BangPatterns 175 | BinaryLiterals 176 | BlockArguments 177 | ConstraintKinds 178 | DataKinds 179 | DeriveAnyClass 180 | DeriveFoldable 181 | DeriveFunctor 182 | DeriveGeneric 183 | DeriveLift 184 | DeriveTraversable 185 | DerivingStrategies 186 | DuplicateRecordFields 187 | FlexibleContexts 188 | FlexibleInstances 189 | FunctionalDependencies 190 | GADTs 191 | GeneralizedNewtypeDeriving 192 | KindSignatures 193 | LambdaCase 194 | LiberalTypeSynonyms 195 | MultiParamTypeClasses 196 | MultiWayIf 197 | NamedFieldPuns 198 | NoImplicitPrelude 199 | NoMonomorphismRestriction 200 | OverloadedStrings 201 | OverloadedLabels 202 | OverloadedLists 203 | PostfixOperators 204 | RankNTypes 205 | RecordWildCards 206 | ScopedTypeVariables 207 | StandaloneDeriving 208 | TupleSections 209 | TypeApplications 210 | TypeFamilies 211 | TypeSynonymInstances 212 | TemplateHaskell 213 | TypeOperators 214 | ViewPatterns 215 | ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wpartial-fields -Wredundant-constraints -fhide-source-paths 216 | build-depends: 217 | Glob 218 | , QuickCheck 219 | , aeson 220 | , base <5 221 | , bytestring 222 | , directory 223 | , directory-tree 224 | , doctest 225 | , envy 226 | , flow 227 | , http-media 228 | , lens 229 | , lens-aeson 230 | , monad-logger 231 | , network-ip 232 | , regex-compat 233 | , rio 234 | , servant 235 | , servant-client 236 | , servant-multipart 237 | , servant-multipart-api 238 | , servant-multipart-client 239 | , swagger2 240 | , text 241 | , vector 242 | , yaml 243 | default-language: Haskell2010 244 | -------------------------------------------------------------------------------- /library/Network/IPFS.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS 2 | ( MonadLocalIPFS 3 | , runLocal 4 | , MonadRemoteIPFS 5 | , runRemote 6 | , ipfsAdd 7 | , ipfsCat 8 | , ipfsPin 9 | , ipfsUnpin 10 | ) where 11 | 12 | import Network.IPFS.Local.Class 13 | import Network.IPFS.Remote.Class 14 | -------------------------------------------------------------------------------- /library/Network/IPFS/Add.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Add 2 | ( addRaw 3 | , addFile 4 | , addPath 5 | , addDir 6 | ) where 7 | 8 | import Network.IPFS.Local.Class as IPFS 9 | import Network.IPFS.Prelude hiding (link) 10 | 11 | import Data.ByteString.Lazy.Char8 as CL 12 | 13 | import qualified System.FilePath.Glob as Glob 14 | 15 | import qualified RIO.ByteString.Lazy as Lazy 16 | import RIO.Directory 17 | import RIO.FilePath 18 | import qualified RIO.List as List 19 | 20 | import qualified Network.IPFS.Internal.UTF8 as UTF8 21 | 22 | import Network.IPFS.Add.Error as IPFS.Add 23 | import Network.IPFS.DAG.Link as DAG.Link 24 | import Network.IPFS.DAG.Node.Types as DAG 25 | import Network.IPFS.Types as IPFS 26 | 27 | import Network.IPFS.DAG as DAG 28 | 29 | addRaw :: 30 | MonadLocalIPFS m 31 | => Lazy.ByteString 32 | -> m (Either IPFS.Add.Error IPFS.CID) 33 | addRaw raw = 34 | IPFS.runLocal ["add", "-HQ"] raw >>= \case 35 | Right result -> 36 | case CL.lines result of 37 | [cid] -> 38 | return . Right . mkCID . UTF8.stripN 1 . decodeUtf8Lenient $ Lazy.toStrict cid 39 | 40 | bad -> 41 | return . Left . UnexpectedOutput $ UTF8.textShow bad 42 | 43 | Left err -> 44 | return . Left . UnknownAddErr $ UTF8.textShow err 45 | 46 | addFile :: 47 | MonadLocalIPFS m 48 | => Lazy.ByteString 49 | -> IPFS.Name 50 | -> m (Either IPFS.Add.Error (IPFS.SparseTree, IPFS.CID)) 51 | addFile raw name = 52 | IPFS.runLocal opts raw >>= \case 53 | Right result -> 54 | case CL.lines result of 55 | [inner, outer] -> 56 | let 57 | sparseTree = Directory [(Hash rootCID, fileWrapper)] 58 | fileWrapper = Directory [(fileName, Content fileCID)] 59 | rootCID = CID . decodeUtf8Lenient $ Lazy.toStrict outer 60 | fileCID = CID . UTF8.stripN 1 . decodeUtf8Lenient $ Lazy.toStrict inner 61 | fileName = Key name 62 | in 63 | return $ Right (sparseTree, rootCID) 64 | 65 | bad -> 66 | return . Left . UnexpectedOutput $ UTF8.textShow bad 67 | 68 | 69 | Left err -> 70 | return . Left . UnknownAddErr $ UTF8.textShow err 71 | 72 | where 73 | opts = [ "add" 74 | , "-wq" 75 | , "--stdin-name" 76 | , unName name 77 | ] 78 | 79 | addPath :: 80 | MonadLocalIPFS m 81 | => FilePath 82 | -> m (Either IPFS.Add.Error CID) 83 | addPath path = IPFS.runLocal ["add", "-HQ", path] "" >>= pure . \case 84 | Right result -> 85 | case CL.lines result of 86 | [cid] -> Right . mkCID . UTF8.stripN 1 $ UTF8.textShow cid 87 | bad -> Left . UnexpectedOutput $ UTF8.textShow bad 88 | 89 | Left err -> 90 | Left . UnknownAddErr $ UTF8.textShow err 91 | 92 | addDir :: 93 | ( MonadIO m 94 | , MonadLocalIPFS m 95 | ) 96 | => IPFS.Ignored 97 | -> FilePath 98 | -> m (Either IPFS.Add.Error IPFS.CID) 99 | addDir ignored path = doesFileExist path >>= \case 100 | True -> addPath path 101 | False -> walkDir ignored path 102 | 103 | walkDir :: 104 | ( MonadIO m 105 | , MonadLocalIPFS m 106 | ) 107 | => IPFS.Ignored 108 | -> FilePath 109 | -> m (Either IPFS.Add.Error IPFS.CID) 110 | walkDir ignored path = do 111 | files <- listDirectory path 112 | 113 | let 114 | toAdd = removeIgnored ignored files 115 | reducer = foldResults path ignored 116 | seed = Right $ Node 117 | { dataBlock = "CAE=" 118 | , links = [] 119 | } 120 | 121 | foldM reducer seed toAdd >>= \case 122 | Left err -> return $ Left err 123 | Right node -> DAG.putNode node 124 | 125 | foldResults :: 126 | ( MonadIO m 127 | , MonadLocalIPFS m 128 | ) 129 | => FilePath 130 | -> IPFS.Ignored 131 | -> Either IPFS.Add.Error Node 132 | -> FilePath 133 | -> m (Either IPFS.Add.Error Node) 134 | foldResults _ _ (Left err) _ = return $ Left err 135 | foldResults path ignored (Right node) filename = do 136 | addDir ignored (path filename) >>= \case 137 | Left err -> return $ Left err 138 | Right cid -> 139 | DAG.Link.create cid (IPFS.Name filename) >>= \case 140 | Left err -> return . Left $ RecursiveAddErr err 141 | Right link -> 142 | return $ Right node { links = link: links node } 143 | 144 | removeIgnored :: IPFS.Ignored -> [FilePath] -> [FilePath] 145 | removeIgnored ignored files = List.filter (not . matchesAny ignored) files 146 | 147 | matchesAny :: IPFS.Ignored -> FilePath -> Bool 148 | matchesAny globs path = List.any (\x -> Glob.match x path) globs 149 | -------------------------------------------------------------------------------- /library/Network/IPFS/Add/Error.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Add.Error (Error (..)) where 2 | 3 | import qualified Network.IPFS.Get.Error as Get 4 | import Network.IPFS.Prelude 5 | 6 | data Error 7 | = InvalidFile 8 | | UnexpectedOutput Text 9 | | RecursiveAddErr Get.Error 10 | | IPFSDaemonErr Text 11 | | UnknownAddErr Text 12 | deriving ( Exception 13 | , Eq 14 | , Generic 15 | , Show 16 | ) 17 | 18 | instance Display Error where 19 | display = \case 20 | InvalidFile -> "Invalid file" 21 | UnexpectedOutput txt -> "Unexpected IPFS output: " <> display txt 22 | RecursiveAddErr err -> "Error while adding directory" <> display err 23 | IPFSDaemonErr txt -> "IPFS Daemon error: " <> display txt 24 | UnknownAddErr txt -> "Unknown IPFS add error: " <> display txt 25 | -------------------------------------------------------------------------------- /library/Network/IPFS/BinPath/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.BinPath.Types (BinPath (..)) where 2 | 3 | import qualified RIO.Text as Text 4 | 5 | import System.Envy 6 | 7 | import Network.IPFS.Internal.Orphanage.Natural () 8 | import Network.IPFS.Prelude 9 | 10 | -- | Path to the IPFS binary 11 | newtype BinPath = BinPath { getBinPath :: FilePath } 12 | deriving ( Show 13 | , Eq 14 | , Generic 15 | ) 16 | deriving newtype ( IsString ) 17 | 18 | instance FromEnv BinPath where 19 | fromEnv _ = BinPath <$> env "IPFS_PATH" 20 | 21 | instance FromJSON BinPath where 22 | parseJSON = withText "IPFS.BinPath" \txt -> 23 | BinPath <$> parseJSON (String txt) 24 | 25 | instance Display BinPath where 26 | textDisplay (BinPath path) = Text.pack path 27 | -------------------------------------------------------------------------------- /library/Network/IPFS/Bytes/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Bytes.Types (Bytes(..)) where 2 | 3 | import Network.IPFS.Prelude 4 | 5 | newtype Bytes = Bytes { unBytes :: Natural } 6 | deriving newtype ( Eq 7 | , Show 8 | ) 9 | 10 | instance FromJSON Bytes where 11 | parseJSON val = do 12 | nat <- parseJSON val 13 | return <| Bytes nat 14 | -------------------------------------------------------------------------------- /library/Network/IPFS/CID/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.CID.Types 2 | ( CID (..) 3 | , mkCID 4 | ) where 5 | 6 | import qualified RIO.ByteString.Lazy as Lazy 7 | import RIO.Char 8 | import qualified RIO.Text as Text 9 | 10 | import Data.Swagger 11 | import Servant.API 12 | 13 | import qualified Network.IPFS.Internal.UTF8 as UTF8 14 | import Network.IPFS.Prelude 15 | 16 | newtype CID = CID { unaddress :: Text } 17 | deriving ( Eq 18 | , Generic 19 | , Ord 20 | , Read 21 | , Show 22 | ) 23 | deriving anyclass ( ToParamSchema ) 24 | deriving newtype ( IsString 25 | , ToHttpApiData 26 | ) 27 | 28 | instance ToJSON CID where 29 | toJSON (CID cid) = cid |> normalize |> toJSON 30 | where 31 | normalize (Text.take 1 -> "\"") = UTF8.stripN 1 cid 32 | normalize cid' = cid' 33 | 34 | instance FromJSON CID where 35 | parseJSON = withText "ContentAddress" (pure . CID) 36 | 37 | instance ToSchema CID where 38 | declareNamedSchema _ = 39 | mempty 40 | |> type_ ?~ SwaggerString 41 | |> example ?~ "QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ" 42 | |> NamedSchema (Just "IPFSAddress") 43 | |> pure 44 | 45 | instance Display CID where 46 | textDisplay = unaddress 47 | 48 | instance MimeRender PlainText CID where 49 | mimeRender _ = UTF8.textToLazyBS . unaddress 50 | 51 | instance MimeRender OctetStream CID where 52 | mimeRender _ = UTF8.textToLazyBS . unaddress 53 | 54 | instance MimeUnrender PlainText CID where 55 | mimeUnrender _proxy bs = 56 | case decodeUtf8' $ Lazy.toStrict bs of 57 | Left err -> Left $ show err 58 | Right txt -> Right $ CID txt 59 | 60 | instance MimeUnrender PlainText [CID] where 61 | mimeUnrender proxy bs = sequence cids 62 | where 63 | cids :: [Either String CID] 64 | cids = mimeUnrender proxy <$> Lazy.split (fromIntegral $ ord ',') bs 65 | 66 | instance FromHttpApiData CID where 67 | parseUrlPiece = Right . CID 68 | 69 | -- | Smart constructor for @CID@ 70 | mkCID :: Text -> CID 71 | mkCID = CID . Text.strip 72 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client 2 | ( API 3 | , add 4 | , cat 5 | , stat 6 | , pin 7 | , unpin 8 | , dagPut 9 | ) where 10 | 11 | import qualified RIO.ByteString.Lazy as Lazy 12 | 13 | import Servant.API 14 | import Servant.Client 15 | import Servant.Multipart.Client () 16 | 17 | import Network.IPFS.Internal.Orphanage.ByteString.Lazy () 18 | import Network.IPFS.Prelude hiding (object) 19 | 20 | import Network.IPFS.CID.Types 21 | import qualified Network.IPFS.File.Form.Types as File 22 | import qualified Network.IPFS.File.Types as File 23 | import Network.IPFS.Stat.Types 24 | 25 | import qualified Network.IPFS.Client.Add as Add 26 | import qualified Network.IPFS.Client.Cat as Cat 27 | import qualified Network.IPFS.Client.DAG.Put.Types as DAG.Put 28 | import qualified Network.IPFS.Client.DAG.Types as DAG 29 | import qualified Network.IPFS.Client.Pin as Pin 30 | import qualified Network.IPFS.Client.Stat as Stat 31 | 32 | type API 33 | = "api" 34 | :> "v0" 35 | :> V0API 36 | 37 | type V0API = "add" :> Add.API 38 | :<|> "cat" :> Cat.API 39 | :<|> "object" :> Stat.API 40 | :<|> "dag" :> DAG.API 41 | :<|> "pin" :> Pin.API 42 | 43 | cat :: CID -> ClientM File.Serialized 44 | stat :: CID -> ClientM (Either OverflowDetected Stat) 45 | pin :: CID -> ClientM Pin.Response 46 | unpin :: CID -> Bool -> ClientM Pin.Response 47 | dagPut :: Bool -> (Lazy.ByteString, File.Form) -> ClientM DAG.Put.Response 48 | add :: Lazy.ByteString -> ClientM CID 49 | 50 | add :<|> cat 51 | :<|> stat 52 | :<|> dagPut 53 | :<|> pin 54 | :<|> unpin = client $ Proxy @API 55 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client/Add.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client.Add (API) where 2 | 3 | import qualified RIO.ByteString.Lazy as Lazy 4 | 5 | import Servant.API 6 | 7 | import Network.IPFS.CID.Types (CID) 8 | 9 | type API = ReqBody '[PlainText] Lazy.ByteString 10 | :> Post '[PlainText] CID 11 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client/Cat.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client.Cat (API) where 2 | 3 | import Servant.API 4 | 5 | import qualified Network.IPFS.Client.Param as Param 6 | import qualified Network.IPFS.File.Types as File 7 | import Network.IPFS.MIME.RawPlainText.Types 8 | 9 | type API = Param.CID' 10 | :> Post '[RawPlainText] File.Serialized 11 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client/DAG/Put/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client.DAG.Put.Types (API, Response (..)) where 2 | 3 | import Servant.API 4 | import Servant.Multipart 5 | 6 | import Network.IPFS.Prelude 7 | 8 | import Network.IPFS.CID.Types 9 | import qualified Network.IPFS.File.Form.Types as File 10 | 11 | type API 12 | = QueryParam' '[Required, Strict] "pin" Bool 13 | :> MultipartForm Tmp File.Form 14 | :> Post '[JSON] Response 15 | 16 | newtype Response = Response CID 17 | 18 | instance FromJSON Response where 19 | parseJSON = withObject "IPFS.DAG.Response" \obj -> do 20 | cidField <- obj .: "Cid" 21 | cid <- cidField .: "/" 22 | return $ Response cid 23 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client/DAG/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client.DAG.Types (API) where 2 | 3 | import Servant.API 4 | 5 | import qualified Network.IPFS.Client.DAG.Put.Types as Put 6 | 7 | type API 8 | = "put" 9 | :> Put.API 10 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client/Error/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client.Error.Types (ErrorBody (..)) where 2 | 3 | import Network.IPFS.Prelude 4 | 5 | data ErrorBody = ErrorBody {message :: String} 6 | 7 | instance FromJSON ErrorBody where 8 | parseJSON = withObject "ErrorBody" \obj -> do 9 | message <- obj .: "Message" 10 | return ErrorBody {..} 11 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client/Param.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client.Param 2 | ( CID' 3 | , IsRecursive 4 | ) where 5 | 6 | import Servant.API 7 | 8 | import Network.IPFS.CID.Types 9 | 10 | type CID' = QueryParam' '[Required, Strict] "arg" CID 11 | type IsRecursive = QueryFlag "recursive" 12 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client/Pin.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client.Pin 2 | ( API 3 | , AddAPI 4 | , RemoveAPI 5 | , Response (..) 6 | ) where 7 | 8 | import qualified RIO.Text as Text 9 | 10 | import Servant.API 11 | 12 | import Network.IPFS.Prelude 13 | 14 | import Network.IPFS.CID.Types 15 | import qualified Network.IPFS.Client.Param as Param 16 | 17 | type API = AddAPI :<|> RemoveAPI 18 | 19 | type AddAPI 20 | = "add" 21 | :> Param.CID' 22 | :> Post '[JSON] Response 23 | 24 | -- IPFS v0.5 disallows GET requests 25 | -- https://docs.ipfs.io/recent-releases/go-ipfs-0-5/#breaking-changes-upgrade-notes 26 | type RemoveAPI 27 | = "rm" 28 | :> Param.CID' 29 | :> Param.IsRecursive 30 | :> Post '[JSON] Response 31 | 32 | newtype Response = Response { cids :: [CID] } 33 | deriving (Eq, Show) 34 | 35 | instance Display Response where 36 | textDisplay Response {cids} = "[" <> inner <> "]" 37 | where 38 | inner = Text.intercalate ", " $ fmap textDisplay cids 39 | 40 | instance FromJSON Response where 41 | parseJSON = withObject "Pin Response" \obj -> 42 | Response <$> obj .: "Pins" 43 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client/Stat.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client.Stat (API) where 2 | 3 | import Servant.API 4 | 5 | import Network.IPFS.Prelude 6 | 7 | import qualified Network.IPFS.Client.Param as Param 8 | import Network.IPFS.Stat.Types 9 | 10 | -- IPFS v0.5 disallows GET requests 11 | -- https://docs.ipfs.io/recent-releases/go-ipfs-0-5/#breaking-changes-upgrade-notes 12 | type API = "stat" 13 | :> Param.CID' 14 | :> Post '[JSON] (Either OverflowDetected Stat) 15 | -------------------------------------------------------------------------------- /library/Network/IPFS/Client/Streaming/Pin.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Client.Streaming.Pin 2 | ( PinComplete 3 | , PinStatus (..) 4 | ) where 5 | 6 | import Servant.API 7 | 8 | import Network.IPFS.Prelude 9 | 10 | import Network.IPFS.CID.Types 11 | import qualified Network.IPFS.Client.Param as Param 12 | 13 | type PinComplete 14 | = "api" 15 | :> "v0" 16 | :> "pin" 17 | :> "add" 18 | :> Param.CID' 19 | :> QueryParam "progress" Bool 20 | :> StreamPost NewlineFraming JSON (SourceIO PinStatus) 21 | 22 | data PinStatus = PinStatus 23 | { pins :: [CID] 24 | , progress :: Maybe Natural 25 | } 26 | deriving (Eq, Show) 27 | 28 | instance Display PinStatus where 29 | display status = displayShow status 30 | 31 | instance FromJSON PinStatus where 32 | parseJSON = withObject "IPFS.PinStatus" \obj -> do 33 | pins <- obj .:? "Pins" .!= [] 34 | progress <- obj .:? "Progress" 35 | return PinStatus {..} 36 | -------------------------------------------------------------------------------- /library/Network/IPFS/DAG.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.DAG 2 | ( put 3 | , putNode 4 | , putRemote 5 | ) where 6 | 7 | import Data.ByteString.Lazy.Char8 as CL 8 | import qualified Network.IPFS.Internal.UTF8 as UTF8 9 | import qualified RIO.ByteString.Lazy as Lazy 10 | 11 | import Servant.Client 12 | import qualified Servant.Multipart.Client as Multipart.Client 13 | 14 | import Network.IPFS.Prelude 15 | 16 | import Network.IPFS.Add.Error as IPFS.Add 17 | import qualified Network.IPFS.Client as IPFS.Client 18 | import Network.IPFS.Client.DAG.Put.Types as DAG.Put 19 | import Network.IPFS.DAG.Node.Types as DAG 20 | import Network.IPFS.File.Form.Types as File 21 | import Network.IPFS.File.Types as File 22 | import Network.IPFS.Local.Class as IPFS 23 | import Network.IPFS.Remote.Class as IPFS 24 | import Network.IPFS.Types as IPFS 25 | 26 | put :: MonadLocalIPFS m => Lazy.ByteString -> m (Either IPFS.Add.Error IPFS.CID) 27 | put raw = IPFS.runLocal ["dag", "put", "-f", "dag-pb"] raw >>= \case 28 | Right result -> 29 | case CL.lines result of 30 | [cid] -> 31 | cid 32 | |> UTF8.textShow 33 | |> UTF8.stripN 1 34 | |> mkCID 35 | |> Right 36 | |> return 37 | 38 | bad -> 39 | pure . Left . UnexpectedOutput $ UTF8.textShow bad 40 | 41 | Left err -> 42 | pure . Left . UnknownAddErr $ UTF8.textShow err 43 | 44 | putNode :: MonadLocalIPFS m => DAG.Node -> m (Either IPFS.Add.Error IPFS.CID) 45 | putNode node = put $ encode node 46 | 47 | putRemote :: MonadRemoteIPFS m => File.Serialized -> m (Either ClientError DAG.Put.Response) 48 | putRemote file = do 49 | boundary <- liftIO Multipart.Client.genBoundary 50 | runRemote (IPFS.Client.dagPut True (boundary, File.Form "file" file)) 51 | -------------------------------------------------------------------------------- /library/Network/IPFS/DAG/Link.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.DAG.Link (create) where 2 | 3 | import Network.IPFS.Prelude 4 | import Network.IPFS.Local.Class 5 | 6 | import Network.IPFS.Get.Error as IPFS.Get 7 | import Network.IPFS.Types as IPFS 8 | import Network.IPFS.DAG.Link.Types as DAG 9 | import qualified Network.IPFS.Stat as Stat 10 | 11 | create :: 12 | MonadLocalIPFS m 13 | => IPFS.CID 14 | -> IPFS.Name 15 | -> m (Either IPFS.Get.Error Link) 16 | create cid name = Stat.getSize cid >>= \case 17 | Left err -> return <| Left err 18 | Right size -> return . Right <| Link 19 | { cid = cid 20 | , name = name 21 | , size = size 22 | } 23 | -------------------------------------------------------------------------------- /library/Network/IPFS/DAG/Link/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.DAG.Link.Types (Link (..)) where 2 | 3 | import Network.IPFS.Prelude 4 | import Network.IPFS.Types as IPFS 5 | import Data.Text as T 6 | 7 | 8 | data Link = Link 9 | { cid :: IPFS.CID 10 | , name :: IPFS.Name 11 | , size :: Integer 12 | } deriving (Show, Eq, Generic) 13 | 14 | 15 | instance ToJSON Link where 16 | toJSON (Link cid name size) = 17 | Object [ ("Name", String . T.pack <| unName name) 18 | , ("Size", Number <| fromIntegral size) 19 | , ("Cid", Object [("/", String <| unaddress cid)]) 20 | ] 21 | 22 | -------------------------------------------------------------------------------- /library/Network/IPFS/DAG/Node/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.DAG.Node.Types (Node (..)) where 2 | 3 | import Network.IPFS.Prelude 4 | 5 | import Data.Vector 6 | import Network.IPFS.DAG.Link.Types as DAG 7 | 8 | 9 | data Node = Node 10 | { dataBlock :: Text 11 | , links :: [DAG.Link] 12 | } deriving (Show, Eq) 13 | 14 | instance ToJSON Node where 15 | toJSON (Node dataBlock links) = 16 | Object [ ("data", String dataBlock) 17 | , ("links", Array . fromList $ fmap toJSON links) 18 | ] 19 | 20 | -------------------------------------------------------------------------------- /library/Network/IPFS/Error.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Error 2 | ( Error (..) 3 | , Linearization (..) 4 | ) where 5 | 6 | import Network.IPFS.Prelude 7 | import Network.IPFS.Types 8 | 9 | import qualified Network.IPFS.Add.Error as Add 10 | import qualified Network.IPFS.Get.Error as Get 11 | 12 | data Error 13 | = AddErr Add.Error 14 | | GetErr Get.Error 15 | | LinearizationErr Linearization 16 | deriving ( Exception 17 | , Eq 18 | , Generic 19 | , Show 20 | ) 21 | 22 | -- NOTE Will not stay as a newtype in the long term 23 | newtype Linearization = NonLinear SparseTree 24 | deriving ( Eq 25 | , Generic 26 | , Show 27 | ) 28 | deriving anyclass ( Exception 29 | , ToJSON 30 | ) 31 | 32 | instance Display Linearization where 33 | display (NonLinear sparseTree) = "Unable to linearize IPFS result: " <> display sparseTree 34 | -------------------------------------------------------------------------------- /library/Network/IPFS/File/Form/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.File.Form.Types (Form (..)) where 2 | 3 | import RIO 4 | import qualified RIO.ByteString.Lazy as Lazy 5 | 6 | import Servant.Multipart 7 | import Servant.Multipart.API 8 | 9 | import qualified Network.IPFS.File.Types as File 10 | 11 | data Form = Form 12 | { name :: Text 13 | , serialized :: File.Serialized 14 | } 15 | 16 | instance ToMultipart Tmp Form where 17 | toMultipart Form { name = name, serialized = File.Serialized fileLBS } = 18 | MultipartData 19 | [ Input 20 | { iName = name 21 | , iValue = decodeUtf8Lenient $ Lazy.toStrict fileLBS 22 | } 23 | ] 24 | [] 25 | -------------------------------------------------------------------------------- /library/Network/IPFS/File/Types.hs: -------------------------------------------------------------------------------- 1 | -- | File types 2 | module Network.IPFS.File.Types (Serialized (..)) where 3 | 4 | import qualified Data.ByteString.Builder as Builder 5 | import Data.Swagger 6 | import qualified RIO.ByteString.Lazy as Lazy 7 | 8 | import Servant.API 9 | 10 | import Network.IPFS.MIME.RawPlainText.Types 11 | import Network.IPFS.Prelude 12 | 13 | -- | A file serialized as a lazy bytestring 14 | newtype Serialized = Serialized { unserialize :: Lazy.ByteString } 15 | deriving ( Eq 16 | , Show 17 | ) 18 | deriving newtype ( IsString ) 19 | 20 | instance ToSchema Serialized where 21 | declareNamedSchema _ = 22 | mempty 23 | |> example ?~ "hello world" 24 | |> description ?~ "A typical file's contents" 25 | |> type_ ?~ SwaggerString 26 | |> NamedSchema (Just "SerializedFile") 27 | |> pure 28 | 29 | instance Display Serialized where 30 | display = Utf8Builder . Builder.lazyByteString . unserialize 31 | 32 | ----- 33 | 34 | instance MimeRender PlainText Serialized where 35 | mimeRender _proxy = unserialize 36 | 37 | instance MimeRender RawPlainText Serialized where 38 | mimeRender _proxy = unserialize 39 | 40 | instance MimeRender OctetStream Serialized where 41 | mimeRender _proxy = unserialize 42 | 43 | ----- 44 | 45 | instance MimeUnrender PlainText Serialized where 46 | mimeUnrender _proxy = Right . Serialized 47 | 48 | instance MimeUnrender RawPlainText Serialized where 49 | mimeUnrender _proxy = Right . Serialized 50 | 51 | instance MimeUnrender OctetStream Serialized where 52 | mimeUnrender _proxy = Right . Serialized 53 | -------------------------------------------------------------------------------- /library/Network/IPFS/Gateway/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Gateway.Types (Gateway (..)) where 2 | 3 | import Network.IPFS.Prelude 4 | import Data.Swagger (ToSchema (..)) 5 | 6 | -- | Type safety wrapper for IPFS Gateway 7 | -- Used as cname value for DNS updates 8 | newtype Gateway = Gateway { getGateway :: Text } 9 | deriving ( Eq 10 | , Generic 11 | , Show 12 | ) 13 | deriving anyclass ( ToSchema ) 14 | deriving newtype ( IsString ) 15 | 16 | instance FromJSON Gateway where 17 | parseJSON = withText "AWS.Gateway" \txt -> 18 | Gateway <$> parseJSON (String txt) 19 | -------------------------------------------------------------------------------- /library/Network/IPFS/Get.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Get 2 | ( getFile 3 | , getFileOrDirectory 4 | ) where 5 | 6 | import qualified Network.IPFS.Internal.UTF8 as UTF8 7 | import Network.IPFS.Local.Class as IPFS 8 | import Network.IPFS.Prelude 9 | 10 | import Data.ByteString.Lazy.Char8 as CL 11 | import qualified RIO.ByteString.Lazy as Lazy 12 | import qualified RIO.Text as Text 13 | 14 | import qualified Network.IPFS.File.Types as File 15 | import Network.IPFS.Get.Error as IPFS.Get 16 | import qualified Network.IPFS.Process.Error as Process 17 | import Network.IPFS.Types as IPFS 18 | 19 | getFileOrDirectory :: MonadLocalIPFS m => IPFS.CID -> m (Either IPFS.Get.Error CL.ByteString) 20 | getFileOrDirectory cid@(IPFS.CID hash) = IPFS.runLocal ["get", Text.unpack hash] "" >>= \case 21 | Right contents -> return $ Right contents 22 | Left err -> case err of 23 | Process.Timeout secs -> return . Left $ TimedOut cid secs 24 | Process.UnknownErr raw -> return . Left . UnknownErr $ UTF8.textShow raw 25 | 26 | getFile :: MonadLocalIPFS m => IPFS.CID -> m (Either IPFS.Get.Error File.Serialized) 27 | getFile cid@(IPFS.CID hash) = IPFS.runLocal ["cat"] (UTF8.textToLazyBS hash) >>= \case 28 | Right contents -> return . Right $ File.Serialized contents 29 | Left err -> case err of 30 | Process.Timeout secs -> return . Left $ TimedOut cid secs 31 | Process.UnknownErr raw -> 32 | if Lazy.isPrefixOf "Error: invalid 'ipfs ref' path" raw 33 | then return . Left $ InvalidCID hash 34 | else return . Left . UnknownErr $ UTF8.textShow raw 35 | -------------------------------------------------------------------------------- /library/Network/IPFS/Get/Error.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Get.Error (Error (..)) where 2 | 3 | import Servant.Client 4 | 5 | import Network.IPFS.Prelude 6 | 7 | import Network.IPFS.Stat.Error 8 | import Network.IPFS.Types 9 | 10 | data Error 11 | = InvalidCID Text 12 | | TimedOut CID Natural 13 | | WebError ClientError 14 | | SizeError OverflowDetected 15 | | UnexpectedOutput Text 16 | | UnknownErr Text 17 | deriving ( Exception 18 | , Eq 19 | , Generic 20 | , Show 21 | ) 22 | 23 | instance Display Error where 24 | display = \case 25 | InvalidCID hash -> 26 | "Invalid CID: " <> display hash 27 | 28 | TimedOut (CID hash) sec -> 29 | mconcat 30 | [ "Unable to find CID " 31 | , display hash 32 | , " before the timeout of " 33 | , display sec 34 | , " seconds." 35 | ] 36 | 37 | WebError err -> 38 | "WebError: " <> displayShow err 39 | 40 | SizeError err -> 41 | "SizeError: " <> display err 42 | 43 | UnexpectedOutput raw -> 44 | "Unexpected IPFS output: " <> display raw 45 | 46 | UnknownErr raw -> 47 | "Unknown IPFS get error: " <> display raw 48 | -------------------------------------------------------------------------------- /library/Network/IPFS/Ignored/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Ignored.Types (Ignored) where 2 | 3 | import qualified System.FilePath.Glob as Glob 4 | 5 | type Ignored = [Glob.Pattern] 6 | -------------------------------------------------------------------------------- /library/Network/IPFS/Info/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Info.Types (Info (..)) where 2 | 3 | import Network.IPFS.Prelude 4 | import Network.IPFS.Peer.Types 5 | 6 | data Info = Info 7 | { id :: Text 8 | , publicKey :: Text 9 | , addresses :: [Peer] 10 | , agentVersion :: Text 11 | , protocolVersion :: Text 12 | } deriving (Show, Eq) 13 | 14 | instance FromJSON Info where 15 | parseJSON = withObject "IPFS.Info" \obj -> do 16 | id <- obj .: "ID" 17 | publicKey <- obj .: "PublicKey" 18 | addresses <- obj .: "Addresses" 19 | agentVersion <- obj .: "AgentVersion" 20 | protocolVersion <- obj .: "ProtocolVersion" 21 | 22 | return Info {..} 23 | -------------------------------------------------------------------------------- /library/Network/IPFS/Internal/Orphanage/ByteString/Lazy.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -fno-warn-orphans #-} 2 | 3 | module Network.IPFS.Internal.Orphanage.ByteString.Lazy () where 4 | 5 | import qualified RIO.ByteString.Lazy as Lazy 6 | import Servant.API 7 | 8 | import Network.IPFS.Prelude 9 | 10 | instance MimeRender PlainText Lazy.ByteString where 11 | mimeRender _proxy = identity 12 | 13 | instance FromJSON Lazy.ByteString where 14 | parseJSON = withText "ByteString" (pure . Lazy.fromStrict . encodeUtf8) 15 | -------------------------------------------------------------------------------- /library/Network/IPFS/Internal/Orphanage/Natural.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -fno-warn-orphans #-} 2 | {-# OPTIONS_GHC -fno-warn-missing-methods #-} 3 | 4 | module Network.IPFS.Internal.Orphanage.Natural () where 5 | 6 | import System.Envy 7 | 8 | import Network.IPFS.Prelude 9 | 10 | instance Display Natural where 11 | display nat = display (fromIntegral nat :: Integer) 12 | 13 | instance Var Natural where 14 | fromVar = readMaybe 15 | -------------------------------------------------------------------------------- /library/Network/IPFS/Internal/Orphanage/Utf8Builder.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -fno-warn-orphans #-} 2 | 3 | module Network.IPFS.Internal.Orphanage.Utf8Builder () where 4 | 5 | import RIO 6 | 7 | import Control.Monad.Logger 8 | 9 | instance ToLogStr Utf8Builder where 10 | toLogStr (Utf8Builder builder) = toLogStr builder 11 | -------------------------------------------------------------------------------- /library/Network/IPFS/Internal/UTF8.hs: -------------------------------------------------------------------------------- 1 | -- | UTF8 text helpers 2 | module Network.IPFS.Internal.UTF8 3 | ( Textable (..) 4 | , stripN 5 | , textToLazyBS 6 | , textShow 7 | ) where 8 | 9 | import RIO 10 | import qualified RIO.ByteString.Lazy as Lazy 11 | import qualified RIO.Text as Text 12 | 13 | class Textable a where 14 | encode :: a -> Either UnicodeException Text 15 | 16 | instance Textable ByteString where 17 | encode = decodeUtf8' 18 | 19 | instance Textable Lazy.ByteString where 20 | encode = encode . Lazy.toStrict 21 | 22 | textToLazyBS :: Text -> Lazy.ByteString 23 | textToLazyBS = Lazy.fromStrict . Text.encodeUtf8 24 | 25 | textShow :: Show a => a -> Text 26 | textShow = textDisplay . displayShow 27 | 28 | stripN :: Natural -> Text -> Text 29 | stripN n = Text.dropEnd i . Text.drop i 30 | where 31 | i :: Int 32 | i = fromIntegral n 33 | -------------------------------------------------------------------------------- /library/Network/IPFS/Local/Class.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Local.Class 2 | ( MonadLocalIPFS 3 | , runLocal 4 | ) where 5 | 6 | import Network.IPFS.Prelude 7 | 8 | import qualified RIO.ByteString.Lazy as Lazy 9 | 10 | import Network.IPFS.Types as IPFS 11 | import qualified Network.IPFS.Process.Error as Process 12 | 13 | class Monad m => MonadLocalIPFS m where 14 | runLocal :: 15 | [Opt] 16 | -> Lazy.ByteString 17 | -> m (Either Process.Error Process.RawMessage) 18 | -------------------------------------------------------------------------------- /library/Network/IPFS/MIME/RawPlainText/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.MIME.RawPlainText.Types (RawPlainText) where 2 | 3 | import Network.HTTP.Media 4 | import qualified Servant.API as API 5 | 6 | import RIO 7 | import qualified RIO.ByteString.Lazy as Lazy 8 | 9 | -- Built-in version includes charset 10 | -- https://github.com/haskell-servant/servant/issues/1002 11 | data RawPlainText 12 | 13 | instance API.Accept RawPlainText where 14 | contentType _ = "text" // "plain" 15 | 16 | instance API.MimeRender RawPlainText Text where 17 | mimeRender _ = Lazy.fromStrict . encodeUtf8 18 | 19 | instance API.MimeRender RawPlainText ByteString where 20 | mimeRender _ = Lazy.fromStrict 21 | 22 | instance API.MimeRender RawPlainText Lazy.ByteString where 23 | mimeRender _ = id 24 | 25 | instance API.MimeUnrender RawPlainText Text where 26 | mimeUnrender _ bs = 27 | case decodeUtf8' $ Lazy.toStrict bs of 28 | Left err -> Left $ show err 29 | Right txt -> Right txt 30 | 31 | instance API.MimeUnrender RawPlainText ByteString where 32 | mimeUnrender _ = Right . Lazy.toStrict 33 | 34 | instance API.MimeUnrender RawPlainText Lazy.ByteString where 35 | mimeUnrender _ = Right 36 | -------------------------------------------------------------------------------- /library/Network/IPFS/Name/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Name.Types (Name (..)) where 2 | 3 | import qualified RIO.Text as Text 4 | 5 | import Data.Swagger (ToParamSchema, ToSchema (..)) 6 | import Servant.API 7 | 8 | import Network.IPFS.Prelude 9 | 10 | newtype Name = Name { unName :: String } 11 | deriving ( Eq 12 | , Generic 13 | , Show 14 | , Ord 15 | ) 16 | deriving newtype ( IsString 17 | , ToSchema 18 | , ToParamSchema 19 | ) 20 | 21 | instance Display Name where 22 | display = displayShow 23 | 24 | instance ToJSON Name where 25 | toJSON (Name n) = toJSON n 26 | 27 | instance FromJSON Name where 28 | parseJSON = withText "IPFSName" (pure . Name . Text.unpack) 29 | 30 | instance FromHttpApiData Name where 31 | parseUrlPiece = \case 32 | "" -> Left "Empty Name field" 33 | txt -> Right . Name <| Text.unpack txt 34 | -------------------------------------------------------------------------------- /library/Network/IPFS/Path/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Path.Types (Path (..)) where 2 | 3 | import Data.Swagger (ToSchema (..)) 4 | import Servant.API 5 | 6 | import qualified Network.IPFS.Internal.UTF8 as UTF8 7 | import Network.IPFS.Prelude 8 | 9 | -- | CID path 10 | -- 11 | -- Exmaple 12 | -- 13 | -- > "QmcaHAFzUPRCRaUK12dC6YyhcqEEtdfg94XrPwgCxZ1ihD/myfile.txt" 14 | newtype Path = Path { unpath :: Text } 15 | deriving ( Eq 16 | , Generic 17 | , Show 18 | , Ord 19 | ) 20 | deriving newtype ( IsString 21 | , ToHttpApiData 22 | , ToSchema 23 | ) 24 | 25 | instance MimeRender PlainText Path where 26 | mimeRender _ = UTF8.textToLazyBS . unpath 27 | 28 | instance MimeRender OctetStream Path where 29 | mimeRender _ = UTF8.textToLazyBS . unpath 30 | -------------------------------------------------------------------------------- /library/Network/IPFS/Peer.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Peer 2 | ( all 3 | , rawList 4 | , connect 5 | , connectRetry 6 | , disconnect 7 | , getExternalAddress 8 | ) where 9 | 10 | import qualified RIO.List as List 11 | import qualified RIO.Text as Text 12 | 13 | import qualified Network.IP.Addr as Addr 14 | 15 | import Text.Regex 16 | 17 | import qualified Network.IPFS.Internal.UTF8 as UTF8 18 | import Network.IPFS.Prelude hiding (all) 19 | 20 | import Network.IPFS.Info.Types 21 | import Network.IPFS.Local.Class as IPFS 22 | import Network.IPFS.Peer.Error as IPFS.Peer 23 | import Network.IPFS.Peer.Types 24 | import qualified Network.IPFS.Process.Error as Process 25 | import qualified Network.IPFS.Types as IPFS 26 | 27 | all :: MonadLocalIPFS m => m (Either IPFS.Peer.Error [IPFS.Peer]) 28 | all = rawList <&> \case 29 | Right raw -> case UTF8.encode raw of 30 | Left _ -> Left . DecodeFailure $ show raw 31 | Right text -> Right $ IPFS.Peer <$> Text.lines text 32 | Left err -> Left . UnknownErr $ UTF8.textShow err 33 | 34 | rawList :: MonadLocalIPFS m => m (Either Process.Error Process.RawMessage) 35 | rawList = IPFS.runLocal ["bootstrap", "list"] "" 36 | 37 | connect :: MonadLocalIPFS m => Peer -> m (Either IPFS.Peer.Error ()) 38 | connect peer@(Peer peerID) = IPFS.runLocal ["swarm", "connect"] (UTF8.textToLazyBS peerID) >>= pure . \case 39 | Left _ -> Left $ CannotConnect peer 40 | Right _ -> Right () 41 | 42 | disconnect :: MonadLocalIPFS m => Peer -> m (Either IPFS.Peer.Error ()) 43 | disconnect peer@(Peer peerID) = 44 | IPFS.runLocal ["swarm", "disconnect"] (UTF8.textToLazyBS peerID) >>= pure . \case 45 | Left _ -> Left $ CannotDisconnect peer 46 | Right _ -> Right () 47 | 48 | connectRetry :: MonadLocalIPFS m => Peer -> Natural -> m (Either IPFS.Peer.Error ()) 49 | connectRetry peer 0 = return . Left $ CannotConnect peer 50 | connectRetry peer tries = connect peer >>= \case 51 | Right _ -> return $ Right () 52 | Left _err -> connectRetry peer (tries - 1) 53 | 54 | peerAddressRe :: Regex 55 | peerAddressRe = mkRegex "^/ip[46]/([a-zA-Z0-9.:]*)/" 56 | 57 | -- | Retrieve just the ip address from a peer address 58 | extractIPfromPeerAddress :: String -> Maybe String 59 | extractIPfromPeerAddress peer = matchRegex peerAddressRe peer >>= List.headMaybe 60 | 61 | -- | True if a given peer address is externally accessable 62 | isExternalIPv4 :: Text -> Bool 63 | isExternalIPv4 ip = maybe False not isReserved 64 | where 65 | isReserved :: Maybe Bool 66 | isReserved = do 67 | ipAddress <- extractIPfromPeerAddress $ Text.unpack ip 68 | normalized <- readMaybe ipAddress 69 | return (Addr.ip4Range normalized == Addr.ReservedIP4) 70 | 71 | -- | Filter a list of peers to include only the externally accessable addresses 72 | filterExternalPeers :: [Peer] -> [Peer] 73 | filterExternalPeers = filter (isExternalIPv4 . peer) 74 | 75 | -- | Get all external ipfs peer addresses 76 | getExternalAddress :: MonadLocalIPFS m => m (Either IPFS.Peer.Error [Peer]) 77 | getExternalAddress = 78 | IPFS.runLocal ["id"] "" >>= \case 79 | Left err -> 80 | return . Left . UnknownErr $ UTF8.textShow err 81 | 82 | Right raw -> 83 | raw 84 | |> decode 85 | |> maybe [] addresses 86 | |> Right . filterExternalPeers 87 | |> pure 88 | -------------------------------------------------------------------------------- /library/Network/IPFS/Peer/Error.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Peer.Error (Error (..)) where 2 | 3 | import Network.IPFS.Peer.Types 4 | import Network.IPFS.Prelude 5 | 6 | data Error 7 | = DecodeFailure String 8 | | CannotConnect Peer 9 | | CannotDisconnect Peer 10 | | UnknownErr Text 11 | deriving ( Exception 12 | , Eq 13 | , Generic 14 | , Show 15 | ) 16 | 17 | instance Display Error where 18 | display = \case 19 | DecodeFailure err -> "Unable to decode: " <> displayShow err 20 | CannotConnect peer -> "Unable to connect to " <> display peer 21 | CannotDisconnect peer -> "Unable to disconnect from " <> display peer 22 | UnknownErr msg -> "Unknown IPFS peer list error: " <> display msg 23 | -------------------------------------------------------------------------------- /library/Network/IPFS/Peer/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Peer.Types (Peer (..)) where 2 | 3 | import RIO 4 | 5 | import Control.Lens 6 | import Data.Aeson 7 | import Data.Swagger 8 | import Servant.API 9 | 10 | import qualified Network.IPFS.Internal.UTF8 as UTF8 11 | 12 | newtype Peer = Peer { peer :: Text } 13 | deriving ( Eq 14 | , Show 15 | ) 16 | deriving newtype ( Display 17 | , IsString 18 | , FromJSON 19 | ) 20 | 21 | instance ToJSON Peer where 22 | toJSON = String . peer 23 | 24 | instance ToSchema Peer where 25 | declareNamedSchema _ = 26 | return $ NamedSchema (Just "IPFSPeer") $ mempty 27 | & type_ ?~ SwaggerString 28 | & example ?~ "/ip4/178.62.158.247/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd" 29 | & description ?~ "An IPFS peer address" 30 | 31 | instance MimeRender PlainText Peer where 32 | mimeRender _ = UTF8.textToLazyBS . peer 33 | 34 | instance MimeRender OctetStream Peer where 35 | mimeRender _ = UTF8.textToLazyBS . peer 36 | -------------------------------------------------------------------------------- /library/Network/IPFS/Pin.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Pin 2 | ( add 3 | , rm 4 | ) where 5 | 6 | import Network.IPFS.Prelude 7 | import Network.IPFS.Remote.Class 8 | import qualified Network.IPFS.Internal.UTF8 as UTF8 9 | 10 | import qualified Network.IPFS.Client.Pin as Pin 11 | import Network.IPFS.Add.Error as IPFS.Add 12 | import Network.IPFS.Types as IPFS 13 | import Servant.Client 14 | 15 | -- | Pin a CID 16 | add :: (MonadRemoteIPFS m, MonadLogger m) => IPFS.CID -> m (Either IPFS.Add.Error CID) 17 | add cid = ipfsPin cid >>= \case 18 | Right Pin.Response { cids } -> 19 | case cids of 20 | [cid'] -> do 21 | logDebug <| "Pinned CID " <> display cid' 22 | return <| Right cid' 23 | 24 | _ -> do 25 | formattedErr <- parseUnexpectedOutput <| UTF8.textShow cids 26 | return <| Left formattedErr 27 | 28 | Left err -> do 29 | formattedError <- parseClientError err 30 | return <| Left formattedError 31 | 32 | -- | Unpin a CID 33 | rm :: (MonadRemoteIPFS m, MonadLogger m) => IPFS.CID -> m (Either IPFS.Add.Error CID) 34 | rm cid = ipfsUnpin cid False >>= \case 35 | Right Pin.Response { cids } -> 36 | case cids of 37 | [cid'] -> do 38 | logDebug <| "Unpinned CID " <> display cid' 39 | return <| Right cid' 40 | 41 | _ -> do 42 | formattedErr <- parseUnexpectedOutput <| UTF8.textShow cids 43 | return <| Left formattedErr 44 | 45 | Left _ -> do 46 | logDebug <| "Cannot unpin CID " <> display cid <> " because it was not pinned" 47 | return <| Right cid 48 | 49 | -- | Parse and Log the Servant Client Error returned from the IPFS Daemon 50 | parseClientError :: MonadLogger m => ClientError -> m Error 51 | parseClientError err = do 52 | logError <| displayShow err 53 | return <| case err of 54 | FailureResponse _ response -> 55 | response 56 | |> responseBody 57 | |> decode 58 | |> \case 59 | Just IPFS.ErrorBody {message} -> 60 | IPFSDaemonErr <| UTF8.textShow message 61 | 62 | _ -> 63 | UnexpectedOutput <| UTF8.textShow err 64 | 65 | unknownClientError -> 66 | UnknownAddErr <| UTF8.textShow unknownClientError 67 | 68 | -- | Parse and Log unexpected output when attempting to pin 69 | parseUnexpectedOutput :: MonadLogger m => Text -> m IPFS.Add.Error 70 | parseUnexpectedOutput errStr = do 71 | let 72 | baseError = UnexpectedOutput errStr 73 | err = UnknownAddErr <| UTF8.textShow baseError 74 | 75 | logError <| display baseError 76 | return err 77 | -------------------------------------------------------------------------------- /library/Network/IPFS/Prelude.hs: -------------------------------------------------------------------------------- 1 | -- | A custom @Prelude@-like module for this project 2 | module Network.IPFS.Prelude 3 | ( module Control.Lens 4 | , module Control.Monad.Logger 5 | , module Data.Aeson 6 | , module Flow 7 | , module RIO 8 | , module RIO.Process 9 | , identity 10 | , logInfo 11 | , logDebug 12 | , logWarn 13 | , logError 14 | , logOther 15 | ) where 16 | 17 | import Control.Lens ((?~)) 18 | import Control.Monad.Logger (LogLevel (..), 19 | MonadLogger (..), 20 | ToLogStr (..), 21 | logWithoutLoc) 22 | import Data.Aeson 23 | 24 | import Network.IPFS.Internal.Orphanage.Utf8Builder () 25 | 26 | import Flow 27 | 28 | import RIO hiding (Handler, 29 | LogLevel (..), 30 | LogSource, id, 31 | logDebug, 32 | logDebugS, 33 | logError, 34 | logErrorS, 35 | logInfo, logInfoS, 36 | logOther, 37 | logOtherS, 38 | logWarn, logWarnS, 39 | timeout, (&)) 40 | import RIO.Process 41 | 42 | identity :: a -> a 43 | identity a = a 44 | 45 | logInfo :: (ToLogStr msg, MonadLogger m) => msg -> m () 46 | logInfo = logWithoutLoc "" LevelInfo 47 | 48 | logDebug :: (ToLogStr msg, MonadLogger m) => msg -> m () 49 | logDebug = logWithoutLoc "" LevelDebug 50 | 51 | logWarn :: (ToLogStr msg, MonadLogger m) => msg -> m () 52 | logWarn = logWithoutLoc "" LevelWarn 53 | 54 | logError :: (ToLogStr msg, MonadLogger m) => msg -> m () 55 | logError = logWithoutLoc "" LevelError 56 | 57 | logOther :: (ToLogStr msg, MonadLogger m) => LogLevel -> msg -> m () 58 | logOther = logWithoutLoc "" 59 | -------------------------------------------------------------------------------- /library/Network/IPFS/Process.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Process (runProc) where 2 | 3 | import Network.IPFS.Prelude 4 | import Network.IPFS.Process.Types 5 | 6 | runProc :: 7 | ( MonadIO m 8 | , MonadReader cfg m 9 | , HasProcessContext cfg 10 | , HasLogFunc cfg 11 | ) 12 | => (ProcessConfig stdin stdout () -> m a) 13 | -> FilePath 14 | -> StreamIn stdin 15 | -> StreamOut stdout 16 | -> [Opt] 17 | -> m a 18 | runProc processor binPath inStream outStream opts = 19 | proc binPath opts <| processor 20 | . setStdin inStream 21 | . setStdout outStream 22 | -------------------------------------------------------------------------------- /library/Network/IPFS/Process/Error.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Process.Error 2 | ( Error (..) 3 | , RawMessage 4 | ) where 5 | 6 | import Network.IPFS.Prelude 7 | import Network.IPFS.Process.Types 8 | 9 | data Error 10 | = Timeout Natural 11 | | UnknownErr RawMessage 12 | deriving ( Exception 13 | , Eq 14 | , Generic 15 | , Show 16 | ) 17 | 18 | instance Display Error where 19 | display = \case 20 | Timeout _ -> "IPFS timed out" 21 | UnknownErr raw -> displayShow raw 22 | -------------------------------------------------------------------------------- /library/Network/IPFS/Process/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Process.Types 2 | ( Opt 3 | , Command 4 | , StreamIn 5 | , StreamOut 6 | , RawMessage 7 | ) where 8 | 9 | import Network.IPFS.Prelude 10 | import Data.ByteString.Lazy.Char8 as CL 11 | 12 | type Opt = String 13 | type Command = String 14 | type StreamIn stdin = StreamSpec 'STInput stdin 15 | type StreamOut stdout = StreamSpec 'STOutput stdout 16 | type RawMessage = CL.ByteString 17 | -------------------------------------------------------------------------------- /library/Network/IPFS/Remote/Class.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Remote.Class 2 | ( MonadRemoteIPFS 3 | , runRemote 4 | , ipfsAdd 5 | , ipfsCat 6 | , ipfsStat 7 | , ipfsPin 8 | , ipfsUnpin 9 | ) where 10 | 11 | import Network.IPFS.Prelude 12 | 13 | import qualified RIO.ByteString.Lazy as Lazy 14 | import Servant.Client 15 | 16 | import Network.IPFS.Types as IPFS 17 | 18 | import qualified Network.IPFS.Client as IPFS.Client 19 | import qualified Network.IPFS.Client.Pin as Pin 20 | import qualified Network.IPFS.File.Types as File 21 | 22 | import Network.IPFS.Remote.Error 23 | 24 | class MonadIO m => MonadRemoteIPFS m where 25 | runRemote :: ClientM a -> m (Either ClientError a) 26 | ipfsAdd :: Lazy.ByteString -> m (Either ClientError CID) 27 | ipfsCat :: CID -> m (Either ClientError File.Serialized) 28 | ipfsStat :: CID -> m (Either StatError Stat) 29 | ipfsPin :: CID -> m (Either ClientError Pin.Response) 30 | ipfsUnpin :: CID -> Bool -> m (Either ClientError Pin.Response) 31 | 32 | -- defaults 33 | ipfsAdd raw = runRemote $ IPFS.Client.add raw 34 | ipfsCat cid = runRemote $ IPFS.Client.cat cid 35 | ipfsPin cid = runRemote $ IPFS.Client.pin cid 36 | ipfsUnpin cid recursive = runRemote $ IPFS.Client.unpin cid recursive 37 | 38 | ipfsStat cid = 39 | runRemote (IPFS.Client.stat cid) >>= \case 40 | Left clientErr -> return . Left $ WebError clientErr 41 | Right (Left sizeErr) -> return . Left $ SizeError sizeErr 42 | Right (Right payload) -> return $ Right payload 43 | -------------------------------------------------------------------------------- /library/Network/IPFS/Remote/Error.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Remote.Error (StatError (..)) where 2 | 3 | import Servant.Client 4 | 5 | import Network.IPFS.Prelude 6 | 7 | import Network.IPFS.Stat.Error 8 | 9 | data StatError 10 | = WebError ClientError 11 | | SizeError OverflowDetected 12 | deriving (Show, Eq) 13 | -------------------------------------------------------------------------------- /library/Network/IPFS/SparseTree.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.SparseTree 2 | ( SparseTree (..) 3 | , Error.Linearization (..) 4 | , linearize 5 | , cIDs 6 | ) where 7 | 8 | import Network.IPFS.Prelude 9 | import qualified Network.IPFS.Internal.UTF8 as UTF8 10 | import qualified Network.IPFS.Error as Error 11 | 12 | import Network.IPFS.CID.Types 13 | import Network.IPFS.Name.Types 14 | import Network.IPFS.Path.Types 15 | import Network.IPFS.SparseTree.Types 16 | 17 | linearize :: SparseTree -> Either Error.Linearization Path 18 | linearize = fmap Path . go 19 | where 20 | go :: SparseTree -> Either Error.Linearization Text 21 | go = \case 22 | Stub (Name name) -> Right <| UTF8.textShow name 23 | Content (CID _) -> Right "" 24 | Directory [(tag, value)] -> fromPath tag <$> go value 25 | badDir -> Left <| Error.NonLinear badDir 26 | where 27 | fromPath tag "" = fromKey tag 28 | fromPath tag text = fromKey tag <> "/" <> text 29 | 30 | fromKey :: Tag -> Text 31 | fromKey = UTF8.stripN 1 . \case 32 | Hash (CID cid) -> cid 33 | Key (Name name) -> UTF8.textShow name 34 | 35 | -- | Get all CIDs from a 'SparseTree' (all levels) 36 | cIDs :: (Monoid (f CID), Applicative f) => SparseTree -> f CID 37 | cIDs (Stub _) = mempty 38 | cIDs (Content cid) = pure cid 39 | cIDs (Directory kv) = foldMap cIDs kv 40 | -------------------------------------------------------------------------------- /library/Network/IPFS/SparseTree/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.SparseTree.Types 2 | ( SparseTree (..) 3 | , Tag (..) 4 | ) where 5 | 6 | import qualified Data.Aeson.KeyMap as Aeson.KeyMap 7 | import qualified Data.Aeson.Key as Aeson.Key 8 | import qualified RIO.Map as Map 9 | import qualified RIO.Text as Text 10 | 11 | import Data.Swagger hiding (Tag, name) 12 | import Servant.API 13 | 14 | import Network.IPFS.CID.Types 15 | import qualified Network.IPFS.Internal.UTF8 as UTF8 16 | import Network.IPFS.Name.Types 17 | import Network.IPFS.Prelude 18 | 19 | -- | Directory structure for CIDs and other identifiers 20 | -- 21 | -- Examples: 22 | -- 23 | -- > Content "abcdef" 24 | -- 25 | -- > show $ Directory [(Key "abcdef", Stub "myfile.txt")])] 26 | -- "abcdef/myfile.txt" 27 | data SparseTree 28 | = Stub Name 29 | | Content CID 30 | | Directory (Map Tag SparseTree) 31 | deriving ( Eq 32 | , Generic 33 | , Show 34 | ) 35 | 36 | instance ToSchema SparseTree where 37 | declareNamedSchema _ = 38 | mempty 39 | |> type_ ?~ SwaggerString 40 | |> description ?~ "A tree of IPFS paths" 41 | |> example ?~ toJSON (Directory [(Key "abcdef", Stub "myfile.txt")]) 42 | |> NamedSchema (Just "IPFSTree") 43 | |> pure 44 | 45 | instance Display (Map Tag SparseTree) where 46 | display sparseMap = 47 | "{" <> foldr (\e acc -> e <> ", " <> acc) "}" (prettyKV <$> Map.toList sparseMap) 48 | where 49 | prettyKV (k, v) = display k <> " => " <> display v 50 | 51 | instance Display SparseTree where 52 | display = \case 53 | Stub name -> display name 54 | Content cid -> display cid 55 | Directory dir -> display dir 56 | 57 | instance ToJSON SparseTree where 58 | toJSON = \case 59 | Stub (Name name) -> String <| Text.pack name 60 | Content (CID cid) -> String <| UTF8.stripN 1 cid 61 | Directory dirMap -> Object <| Aeson.KeyMap.fromList (jsonKV <$> Map.toList dirMap) 62 | where 63 | jsonKV :: (Tag, SparseTree) -> (Aeson.Key.Key, Value) 64 | jsonKV (tag, subtree) = (jsonTag tag, toJSON subtree) 65 | 66 | jsonTag (Key (Name n)) = Aeson.Key.fromText (Text.pack n) 67 | jsonTag (Hash (CID cid)) = Aeson.Key.fromText (UTF8.stripN 1 cid) 68 | 69 | data Tag 70 | = Key Name 71 | | Hash CID 72 | deriving ( Eq 73 | , Generic 74 | , Ord 75 | , Show 76 | ) 77 | 78 | instance Display Tag where 79 | display (Key name) = display name 80 | display (Hash cid) = display cid 81 | 82 | instance FromJSON Tag 83 | instance ToJSON Tag where 84 | toJSON (Key k) = toJSON k 85 | toJSON (Hash h) = toJSON h 86 | 87 | instance FromJSONKey Tag 88 | instance ToJSONKey Tag 89 | instance ToSchema Tag 90 | 91 | instance FromHttpApiData Tag where 92 | parseUrlPiece txt = Key <$> parseUrlPiece txt 93 | -------------------------------------------------------------------------------- /library/Network/IPFS/Stat.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Stat 2 | ( getStatRemote 3 | , getSizeRemote 4 | , getSize 5 | , module Network.IPFS.Stat.Types 6 | ) where 7 | 8 | import Data.ByteString.Lazy.Char8 as CL 9 | 10 | import qualified RIO.ByteString.Lazy as Lazy 11 | import qualified RIO.List as List 12 | 13 | import qualified Network.IPFS.Internal.UTF8 as UTF8 14 | import Network.IPFS.Local.Class as IPFS 15 | import Network.IPFS.Prelude 16 | import Network.IPFS.Remote.Class as Remote 17 | 18 | import Network.IPFS.Get.Error as IPFS.Get 19 | import qualified Network.IPFS.Process.Error as Process 20 | 21 | import Network.IPFS.Bytes.Types 22 | import qualified Network.IPFS.Remote.Error as Remote 23 | import Network.IPFS.Stat.Types 24 | import Network.IPFS.Types as IPFS 25 | 26 | getStatRemote :: MonadRemoteIPFS m => IPFS.CID -> m (Either IPFS.Get.Error Stat) 27 | getStatRemote cid = 28 | Remote.ipfsStat cid >>= \case 29 | Right statPayload -> return $ Right statPayload 30 | Left (Remote.WebError err) -> return . Left $ IPFS.Get.WebError err 31 | Left (Remote.SizeError err) -> return . Left $ IPFS.Get.SizeError err 32 | 33 | getSizeRemote :: MonadRemoteIPFS m => IPFS.CID -> m (Either IPFS.Get.Error Bytes) 34 | getSizeRemote cid = 35 | getStatRemote cid >>= \case 36 | Left err -> 37 | return $ Left err 38 | 39 | Right Stat {cumulativeSize} -> 40 | case cumulativeSize of 41 | Left err -> return $ Left $ IPFS.Get.SizeError err 42 | Right size -> return $ Right size 43 | 44 | getSize :: MonadLocalIPFS m => IPFS.CID -> m (Either IPFS.Get.Error Integer) 45 | getSize cid@(CID hash) = IPFS.runLocal ["object", "stat"] (Lazy.fromStrict <| encodeUtf8 hash) >>= \case 46 | Left err -> case err of 47 | Process.Timeout secs -> return . Left $ TimedOut cid secs 48 | Process.UnknownErr raw -> return . Left . UnknownErr $ UTF8.textShow raw 49 | 50 | Right contents -> 51 | case parseSize contents of 52 | Nothing -> return . Left . UnexpectedOutput $ "Could not parse CumulativeSize" 53 | Just (size, _) -> return $ Right size 54 | 55 | parseSize :: Lazy.ByteString -> Maybe (Integer, Lazy.ByteString) 56 | parseSize lbs = do 57 | finalLine <- List.lastMaybe $ CL.lines lbs 58 | finalWord <- List.lastMaybe $ CL.words finalLine 59 | CL.readInteger finalWord 60 | -------------------------------------------------------------------------------- /library/Network/IPFS/Stat/Error.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Stat.Error (OverflowDetected (..)) where 2 | 3 | import qualified RIO.Text as Text 4 | 5 | import Network.IPFS.Prelude 6 | 7 | data OverflowDetected = OverflowDetected 8 | deriving (Eq, Show) 9 | 10 | instance Display OverflowDetected where 11 | display OverflowDetected = "OverflowDetected" 12 | 13 | instance ToJSON OverflowDetected where 14 | toJSON OverflowDetected = String "OverflowDetected" 15 | 16 | instance FromJSON OverflowDetected where 17 | parseJSON = 18 | withText "OverflowDetected" \txt -> 19 | if "-" `Text.isPrefixOf` txt 20 | then return OverflowDetected 21 | else fail "Not an overflow" 22 | -------------------------------------------------------------------------------- /library/Network/IPFS/Stat/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Stat.Types 2 | ( Stat (..) 3 | , module Network.IPFS.Stat.Error 4 | ) where 5 | 6 | import Network.IPFS.Bytes.Types 7 | import Network.IPFS.Stat.Error 8 | 9 | import Network.IPFS.Prelude 10 | 11 | data Stat = Stat 12 | { blockSize :: Either OverflowDetected Bytes 13 | , cumulativeSize :: Either OverflowDetected Bytes 14 | , dataSize :: Either OverflowDetected Bytes 15 | , hash :: Text 16 | , linksSize :: Bytes 17 | , numLinks :: Natural 18 | } 19 | 20 | instance FromJSON Stat where 21 | parseJSON = withObject "Stat" \obj -> do 22 | blockSize <- obj .: "BlockSize" 23 | cumulativeSize <- obj .: "CumulativeSize" 24 | dataSize <- obj .: "DataSize" 25 | 26 | hash <- obj .: "Hash" 27 | linksSize <- obj .: "LinksSize" 28 | numLinks <- obj .: "NumLinks" 29 | 30 | return Stat {..} 31 | 32 | -------------------------------------------------------------------------------- /library/Network/IPFS/Timeout/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.Timeout.Types (Timeout (..)) where 2 | 3 | import System.Envy 4 | 5 | import Network.IPFS.Prelude 6 | import Network.IPFS.Internal.Orphanage.Natural () 7 | 8 | newtype Timeout = Timeout { getSeconds :: Natural } 9 | deriving ( Eq 10 | , Show 11 | , Generic 12 | ) 13 | deriving newtype ( Num ) 14 | 15 | instance FromEnv Timeout where 16 | fromEnv _ = Timeout <$> env "IPFS_TIMEOUT" 17 | 18 | instance FromJSON Timeout where 19 | parseJSON = withScientific "IPFS.Timeout" \num -> 20 | Timeout <$> parseJSON (Number num) 21 | -------------------------------------------------------------------------------- /library/Network/IPFS/Types.hs: -------------------------------------------------------------------------------- 1 | -- | Types related to IPFS 2 | module Network.IPFS.Types 3 | ( BinPath (..) 4 | , CID (..) 5 | , mkCID 6 | , Name (..) 7 | , Opt 8 | , Command 9 | , RawMessage 10 | , Peer (..) 11 | , Path (..) 12 | , SparseTree (..) 13 | , Tag (..) 14 | , Timeout (..) 15 | , URL (..) 16 | , Ignored 17 | , Gateway (..) 18 | , ErrorBody (..) 19 | , Stat (..) 20 | , Bytes (..) 21 | ) where 22 | 23 | import Network.IPFS.BinPath.Types 24 | import Network.IPFS.CID.Types 25 | import Network.IPFS.Name.Types 26 | import Network.IPFS.Path.Types 27 | import Network.IPFS.Peer.Types 28 | import Network.IPFS.Process.Types 29 | import Network.IPFS.SparseTree.Types 30 | import Network.IPFS.Timeout.Types 31 | import Network.IPFS.URL.Types 32 | import Network.IPFS.Ignored.Types 33 | import Network.IPFS.Gateway.Types 34 | import Network.IPFS.Client.Error.Types 35 | import Network.IPFS.Stat.Types 36 | import Network.IPFS.Bytes.Types 37 | -------------------------------------------------------------------------------- /library/Network/IPFS/URL/Types.hs: -------------------------------------------------------------------------------- 1 | module Network.IPFS.URL.Types (URL (..)) where 2 | 3 | import qualified Servant.Client as Client 4 | 5 | import Network.IPFS.Prelude 6 | 7 | -- | IPFS client URL 8 | newtype URL = URL { getURL :: Client.BaseUrl } 9 | deriving ( Eq 10 | , Generic 11 | , Show 12 | ) 13 | deriving newtype ( FromJSON ) 14 | -------------------------------------------------------------------------------- /nix/commands.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | let 3 | bash = "${pkgs.bash}/bin/bash"; 4 | stack = "${pkgs.stack}/bin/stack"; 5 | 6 | cmd = description: script: 7 | { inherit description; 8 | inherit script; 9 | }; 10 | 11 | command = {name, script, description ? ""}: 12 | let 13 | package = 14 | pkgs.writeScriptBin name '' 15 | #!${bash} 16 | echo "⚙️ Running ${name}..." 17 | ${script} 18 | ''; 19 | 20 | bin = "${package}/bin/${name}"; 21 | in 22 | { package = package; 23 | description = description; 24 | bin = bin; 25 | }; 26 | 27 | commands = defs: 28 | let 29 | names = 30 | builtins.attrNames defs; 31 | 32 | helper = 33 | let 34 | lengths = map builtins.stringLength names; 35 | maxLen = builtins.foldl' (acc: x: if x > acc then x else acc) 0 lengths; 36 | maxPad = 37 | let 38 | go = acc: 39 | if builtins.stringLength acc >= maxLen 40 | then acc 41 | else go (" " + acc); 42 | in 43 | go ""; 44 | 45 | folder = acc: name: 46 | let 47 | nameLen = builtins.stringLength name; 48 | padLen = maxLen - nameLen; 49 | padding = builtins.substring 0 padLen maxPad; 50 | in 51 | acc + " && echo '${name} ${padding}| ${(builtins.getAttr name defs).description}'"; 52 | 53 | lines = 54 | builtins.foldl' folder "echo ''" names; 55 | 56 | in 57 | pkgs.writeScriptBin "helpme" '' 58 | #!${pkgs.stdenv.shell} 59 | ${pkgs.figlet}/bin/figlet "Commands" | ${pkgs.lolcat}/bin/lolcat 60 | ${toString lines} 61 | ''; 62 | 63 | mapper = name: 64 | let 65 | element = 66 | builtins.getAttr name defs; 67 | 68 | task = command { 69 | inherit name; 70 | description = element.description; 71 | script = element.script; 72 | }; 73 | in 74 | task.package; 75 | 76 | packages = 77 | map mapper names; 78 | 79 | in 80 | [helper] ++ packages; 81 | 82 | in 83 | commands { 84 | build = cmd "Build entire project" "${stack} build"; 85 | runtests = cmd "Run the complete test suite" "${stack} test"; 86 | repl = cmd "Enter the project REPL" "${stack} repl --no-nix-pure"; 87 | watch = cmd "Autobuild with file watcher" "${stack} build --file-watch"; 88 | } 89 | -------------------------------------------------------------------------------- /nix/pkgs.nix: -------------------------------------------------------------------------------- 1 | let 2 | # (1) 3 | # FIXME pin to specific version 4 | haskellNix = import (builtins.fetchTarball https://github.com/input-output-hk/haskell.nix/archive/master.tar.gz) {}; #/archive/21a6d6090a64ef5956c9625bcf00a15045d65042.tar.gz) {}; 5 | 6 | # (2) 7 | nixpkgsSrc = haskellNix.sources.nixpkgs-2003; 8 | nixpkgsArgs = haskellNix.nixpkgsArgs; 9 | 10 | # (3) 11 | native = import nixpkgsSrc nixpkgsArgs; 12 | 13 | crossRpi = import nixpkgsSrc (nixpkgsArgs // { 14 | crossSystem = native.lib.systems.examples.raspberryPi; 15 | }); 16 | 17 | crossArmv7l = import nixpkgsSrc (nixpkgsArgs // { 18 | crossSystem = native.lib.systems.examples.raspberryPi; 19 | }); 20 | 21 | crossMusl = import nixpkgsSrc (nixpkgsArgs // { 22 | crossSystem = native.lib.systems.examples.musl64; 23 | }); 24 | in { 25 | # (4) 26 | 27 | inherit haskellNix; 28 | 29 | inherit nixpkgsSrc nixpkgsArgs; 30 | 31 | inherit native crossRpi crossArmv7l crossMusl; 32 | } 33 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "darwin": { 3 | "branch": "nixpkgs-20.09-darwin", 4 | "description": "Nix Packages collection", 5 | "homepage": "", 6 | "owner": "NixOS", 7 | "repo": "nixpkgs", 8 | "rev": "66b0db71f463164486a36dded50bedee185e45c2", 9 | "sha256": "0wam1m12qw9rrijhvbvhm5psj2a0ksms77xzxzyr5laz94j60cb0", 10 | "type": "tarball", 11 | "url": "https://github.com/NixOS/nixpkgs/archive/66b0db71f463164486a36dded50bedee185e45c2.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "haskellNix": { 15 | "branch": "master", 16 | "description": "Alternative Haskell Infrastructure for Nixpkgs", 17 | "homepage": "https://input-output-hk.github.io/haskell.nix", 18 | "owner": "input-output-hk", 19 | "repo": "haskell.nix", 20 | "rev": "32015f30c3df9f8fad4289a9474dfb1794095803", 21 | "sha256": "1frrnhf8v7n2hz94df8yxlgd4qxgi0bin851w6xa2fsa6c9sifx6", 22 | "type": "tarball", 23 | "url": "https://github.com/input-output-hk/haskell.nix/archive/32015f30c3df9f8fad4289a9474dfb1794095803.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "niv": { 27 | "branch": "master", 28 | "description": "Easy dependency management for Nix projects", 29 | "homepage": "https://github.com/nmattia/niv", 30 | "owner": "nmattia", 31 | "repo": "niv", 32 | "rev": "ecabfde837ccfb392ccca235f96bfcf4bb8ab186", 33 | "sha256": "1aij19grvzbxj0dal49bsnhq1lc23nrglv6p0f00gwznl6109snj", 34 | "type": "tarball", 35 | "url": "https://github.com/nmattia/niv/archive/ecabfde837ccfb392ccca235f96bfcf4bb8ab186.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | }, 38 | "nixpkgs": { 39 | "branch": "nixos-20.09", 40 | "description": "Nix Packages collection", 41 | "homepage": "", 42 | "owner": "NixOS", 43 | "repo": "nixpkgs", 44 | "rev": "1c1f5649bb9c1b0d98637c8c365228f57126f361", 45 | "sha256": "0f2nvdijyxfgl5kwyb4465pppd5vkhqxddx6v40k2s0z9jfhj0xl", 46 | "type": "tarball", 47 | "url": "https://github.com/NixOS/nixpkgs/archive/1c1f5649bb9c1b0d98637c8c365228f57126f361.tar.gz", 48 | "url_template": "https://github.com///archive/.tar.gz" 49 | }, 50 | "unstable": { 51 | "branch": "nixpkgs-unstable", 52 | "description": "Nix Packages collection", 53 | "homepage": null, 54 | "owner": "NixOS", 55 | "repo": "nixpkgs", 56 | "rev": "53dad94e874c9586e71decf82d972dfb640ef044", 57 | "sha256": "03knsdlm2f4hd46y100vcii07xsa95rdk3b1i2jh8rj3pjm4hlzl", 58 | "type": "tarball", 59 | "url": "https://github.com/NixOS/nixpkgs/archive/53dad94e874c9586e71decf82d972dfb640ef044.tar.gz", 60 | "url_template": "https://github.com///archive/.tar.gz" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | spec.ref or ( 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!" 34 | ); 35 | submodules = spec.submodules or false; 36 | submoduleArg = 37 | let 38 | nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; 39 | emptyArgWithWarning = 40 | if submodules 41 | then 42 | builtins.trace 43 | ( 44 | "The niv input \"${name}\" uses submodules " 45 | + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " 46 | + "does not support them" 47 | ) 48 | { } 49 | else { }; 50 | in 51 | if nixSupportsSubmodules 52 | then { inherit submodules; } 53 | else emptyArgWithWarning; 54 | in 55 | builtins.fetchGit 56 | ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); 57 | 58 | fetch_local = spec: spec.path; 59 | 60 | fetch_builtin-tarball = name: throw 61 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 62 | $ niv modify ${name} -a type=tarball -a builtin=true''; 63 | 64 | fetch_builtin-url = name: throw 65 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 66 | $ niv modify ${name} -a type=file -a builtin=true''; 67 | 68 | # 69 | # Various helpers 70 | # 71 | 72 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 73 | sanitizeName = name: 74 | ( 75 | concatMapStrings (s: if builtins.isList s then "-" else s) 76 | ( 77 | builtins.split "[^[:alnum:]+._?=-]+" 78 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 79 | ) 80 | ); 81 | 82 | # The set of packages used when specs are fetched using non-builtins. 83 | mkPkgs = sources: system: 84 | let 85 | sourcesNixpkgs = 86 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 87 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 88 | hasThisAsNixpkgsPath = == ./.; 89 | in 90 | if builtins.hasAttr "nixpkgs" sources 91 | then sourcesNixpkgs 92 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 93 | import { } 94 | else 95 | abort 96 | '' 97 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 98 | add a package called "nixpkgs" to your sources.json. 99 | ''; 100 | 101 | # The actual fetching function. 102 | fetch = pkgs: name: spec: 103 | 104 | if ! builtins.hasAttr "type" spec then 105 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 106 | else if spec.type == "file" then fetch_file pkgs name spec 107 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 108 | else if spec.type == "git" then fetch_git name spec 109 | else if spec.type == "local" then fetch_local spec 110 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 111 | else if spec.type == "builtin-url" then fetch_builtin-url name 112 | else 113 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 114 | 115 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 116 | # the path directly as opposed to the fetched source. 117 | replace = name: drv: 118 | let 119 | saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; 120 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 121 | in 122 | if ersatz == "" then drv else 123 | # this turns the string into an actual Nix path (for both absolute and 124 | # relative paths) 125 | if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; 126 | 127 | # Ports of functions for older nix versions 128 | 129 | # a Nix version of mapAttrs if the built-in doesn't exist 130 | mapAttrs = builtins.mapAttrs or ( 131 | f: set: with builtins; 132 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 133 | ); 134 | 135 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 136 | range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); 137 | 138 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 139 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 140 | 141 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 142 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 143 | concatMapStrings = f: list: concatStrings (map f list); 144 | concatStrings = builtins.concatStringsSep ""; 145 | 146 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 147 | optionalAttrs = cond: as: if cond then as else { }; 148 | 149 | # fetchTarball version that is compatible between all the versions of Nix 150 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 151 | let 152 | inherit (builtins) lessThan nixVersion fetchTarball; 153 | in 154 | if lessThan nixVersion "1.12" then 155 | fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) 156 | else 157 | fetchTarball attrs; 158 | 159 | # fetchurl version that is compatible between all the versions of Nix 160 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 161 | let 162 | inherit (builtins) lessThan nixVersion fetchurl; 163 | in 164 | if lessThan nixVersion "1.12" then 165 | fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) 166 | else 167 | fetchurl attrs; 168 | 169 | # Create the final "sources" from the config 170 | mkSources = config: 171 | mapAttrs 172 | ( 173 | name: spec: 174 | if builtins.hasAttr "outPath" spec 175 | then 176 | abort 177 | "The values in sources.json should not have an 'outPath' attribute" 178 | else 179 | spec // { outPath = replace name (fetch config.pkgs name spec); } 180 | ) 181 | config.sources; 182 | 183 | # The "config" used by the fetchers 184 | mkConfig = 185 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 186 | , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile) 187 | , system ? builtins.currentSystem 188 | , pkgs ? mkPkgs sources system 189 | }: rec { 190 | # The sources, i.e. the attribute set of spec name to spec 191 | inherit sources; 192 | 193 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 194 | inherit pkgs; 195 | }; 196 | 197 | in 198 | mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); } 199 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: ipfs 2 | version: '1.4.1' 3 | synopsis: Access IPFS locally and remotely 4 | description: Interact with the IPFS network by shelling out to a local IPFS node or communicating via the HTTP interface of a remote IPFS node. 5 | category: Network 6 | author: 7 | - Brooklyn Zelenka 8 | - Daniel Holmgren 9 | - Steven Vandevelde 10 | - James Walker 11 | maintainer: 12 | - brooklyn@fission.codes 13 | - daniel@fission.codes 14 | - steven@fission.codes 15 | - james@fission.codes 16 | copyright: © 2023 Fission Internet Software Services for Open Networks Inc. 17 | license: Apache-2.0 18 | license-file: LICENSE 19 | github: fission-suite/ipfs-haskell 20 | tested-with: GHC==9.2.7 21 | extra-source-files: 22 | - README.md 23 | 24 | ghc-options: 25 | - -Wall 26 | - -Wcompat 27 | - -Widentities 28 | # Warn about too little 29 | - -Wincomplete-record-updates 30 | - -Wincomplete-uni-patterns 31 | - -Wmissing-export-lists 32 | - -Wpartial-fields 33 | # Warn about too much 34 | - -Wredundant-constraints 35 | # Prettier Development 36 | - -fhide-source-paths 37 | 38 | default-extensions: 39 | - ApplicativeDo 40 | - BangPatterns 41 | - BinaryLiterals 42 | - BlockArguments 43 | - ConstraintKinds 44 | - DataKinds 45 | - DeriveAnyClass 46 | - DeriveFoldable 47 | - DeriveFunctor 48 | - DeriveGeneric 49 | - DeriveLift 50 | - DeriveTraversable 51 | - DerivingStrategies 52 | - DuplicateRecordFields 53 | - FlexibleContexts 54 | - FlexibleInstances 55 | - FunctionalDependencies 56 | - GADTs 57 | - GeneralizedNewtypeDeriving 58 | - KindSignatures 59 | - LambdaCase 60 | - LiberalTypeSynonyms 61 | - MultiParamTypeClasses 62 | - MultiWayIf 63 | - NamedFieldPuns 64 | - NoImplicitPrelude 65 | - NoMonomorphismRestriction 66 | - OverloadedStrings 67 | - OverloadedLabels 68 | - OverloadedLists 69 | - PostfixOperators 70 | - RankNTypes 71 | - RecordWildCards 72 | - ScopedTypeVariables 73 | - StandaloneDeriving 74 | - TupleSections 75 | - TypeApplications 76 | - TypeFamilies 77 | - TypeSynonymInstances 78 | - TemplateHaskell 79 | - TypeOperators 80 | - ViewPatterns 81 | 82 | dependencies: 83 | - aeson 84 | - base < 5 85 | - bytestring 86 | - envy 87 | - flow 88 | - Glob 89 | - http-media 90 | - lens 91 | - monad-logger 92 | - network-ip 93 | - regex-compat 94 | - rio 95 | - servant 96 | - servant-client 97 | - servant-multipart 98 | - servant-multipart-api 99 | - servant-multipart-client 100 | - swagger2 101 | - text 102 | - vector 103 | 104 | library: 105 | source-dirs: library 106 | 107 | generated-exposed-modules: 108 | - Paths_ipfs 109 | 110 | tests: 111 | ipfs-doctest: 112 | main: Main.hs 113 | source-dirs: test/doctest 114 | dependencies: 115 | - directory 116 | - directory-tree 117 | - doctest 118 | - Glob 119 | - lens-aeson 120 | - QuickCheck 121 | - yaml 122 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { rosetta ? false }: 2 | let 3 | sources = import ./nix/sources.nix; 4 | 5 | overrides = if rosetta then { system = "x86_64-darwin"; } else {}; 6 | 7 | nixos = import sources.nixos overrides; 8 | darwin = import sources.darwin overrides; 9 | unstable = import sources.unstable overrides; 10 | 11 | pkgs = if darwin.stdenv.isDarwin then darwin else nixos; 12 | 13 | ghc = unstable.ghc; 14 | 15 | deps = { 16 | haskell = [ 17 | unstable.stack 18 | unstable.stylish-haskell 19 | ]; 20 | }; 21 | in 22 | 23 | unstable.haskell.lib.buildStackProject { 24 | inherit ghc; 25 | name = "Fisson"; 26 | nativeBuildInputs = builtins.concatLists [ 27 | deps.haskell 28 | ]; 29 | 30 | shellHook = '' 31 | export LANG=C.UTF8 32 | ''; 33 | } 34 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-20.17 2 | 3 | packages: 4 | - . 5 | 6 | nix: 7 | enable: true 8 | pure: true 9 | shell-file: shell.nix 10 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | sha256: 14ca51a9a597c32dd7804c10d079feea3d0ae40c5fbbb346cbd67b3ae49f6d01 10 | size: 649598 11 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/17.yaml 12 | original: lts-20.17 13 | -------------------------------------------------------------------------------- /test/coverage-code/Main.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -fno-warn-type-defaults #-} 2 | 3 | module Main (main) where 4 | 5 | import RIO 6 | import qualified RIO.Partial as Part 7 | import RIO.Process 8 | 9 | import Data.List (genericLength) 10 | import Data.Maybe (catMaybes) 11 | import System.Exit (exitFailure, exitSuccess) 12 | import Text.Regex (matchRegex, mkRegex) 13 | 14 | main :: IO () 15 | main = runSimpleApp do 16 | output <- proc "hpc" ["report", "dist/hpc/tix/hspec/hspec.tix"] readProcessStdout_ 17 | 18 | if average (match $ show output) >= expected 19 | then liftIO exitSuccess 20 | else do 21 | logWarn $ displayShow output 22 | liftIO exitFailure 23 | 24 | match :: String -> [Int] 25 | match = fmap Part.read . concat . catMaybes . fmap (matchRegex pattern) . lines 26 | where 27 | pattern = mkRegex "^ *([0-9]*)% " 28 | 29 | average :: (Fractional a, Real b) => [b] -> a 30 | average xs = realToFrac (sum xs) / genericLength xs 31 | 32 | expected :: Fractional a => a 33 | expected = 90 34 | -------------------------------------------------------------------------------- /test/coverage-docs/Main.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -fno-warn-type-defaults #-} 2 | 3 | module Main (main) where 4 | 5 | import RIO 6 | import qualified RIO.Partial as Part 7 | import RIO.Process 8 | 9 | import Data.List (genericLength) 10 | import Data.Maybe (catMaybes) 11 | import System.Exit (exitFailure, exitSuccess) 12 | import Text.Regex (matchRegex, mkRegex) 13 | 14 | main :: IO () 15 | main = runSimpleApp do 16 | output <- proc "cabal" ["new-haddock"] readProcessStdout_ 17 | 18 | if average (match $ show output) >= expected 19 | then liftIO exitSuccess 20 | else do 21 | logWarn $ displayShow output 22 | liftIO exitFailure 23 | 24 | match :: String -> [Int] 25 | match = fmap Part.read 26 | . concat 27 | . catMaybes 28 | . fmap (matchRegex $ mkRegex "^ *([0-9]*)% ") 29 | . lines 30 | 31 | average :: (Fractional a, Real b) => [b] -> a 32 | average xs = realToFrac (sum xs) / genericLength xs 33 | 34 | expected :: Fractional a => a 35 | expected = 0 36 | -------------------------------------------------------------------------------- /test/doctest/Main.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import RIO 4 | import qualified RIO.List as List 5 | 6 | import Data.Aeson.Lens 7 | import Data.Yaml 8 | 9 | import System.Directory 10 | import System.Directory.Tree 11 | import System.FilePath.Glob (glob) 12 | 13 | import Test.DocTest (doctest) 14 | 15 | main :: IO () 16 | main = do 17 | let tmp = ".doctest-tmp" 18 | setup tmp "library" 19 | 20 | source <- glob tmp 21 | doctest source 22 | 23 | removeDirectoryRecursive tmp 24 | 25 | setup :: FilePath -> FilePath -> IO () 26 | setup tmp src = do 27 | pwd <- getCurrentDirectory 28 | hasTmp <- doesDirectoryExist tmp 29 | if hasTmp 30 | then removeDirectoryRecursive tmp 31 | else return () 32 | 33 | createDirectory tmp 34 | exts <- getExts 35 | 36 | (_ :/ files) <- readDirectoryWithL readFileBinary src 37 | go (pwd <> "/" <> tmp) (header exts) files 38 | where 39 | go :: FilePath -> ByteString -> DirTree ByteString -> IO () 40 | go dirPath exts' = \case 41 | Failed { err } -> 42 | error $ show err 43 | 44 | Dir { name, contents } -> do 45 | let path = dirPath <> "/" <> name 46 | createDirectory path 47 | contents `forM_` go path exts' 48 | 49 | File { name, file } -> 50 | writeFileBinary (dirPath <> "/" <> name) (exts' <> file) 51 | 52 | header :: [ByteString] -> ByteString 53 | header raw = mconcat 54 | [ "{-# LANGUAGE " 55 | , mconcat $ List.intersperse ", " raw 56 | , " #-}\n" 57 | ] 58 | 59 | getExts :: IO [ByteString] 60 | getExts = do 61 | pkg <- decodeFileThrow "package.yaml" :: IO Value 62 | let exts = pkg ^. key "default-extensions" . _Array 63 | return $ extract <$> toList exts 64 | where 65 | extract (String txt) = encodeUtf8 txt 66 | extract _ = error "Malformed package.yaml" 67 | -------------------------------------------------------------------------------- /test/lint/Main.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import RIO 4 | 5 | import Language.Haskell.HLint (hlint) 6 | 7 | arguments :: [String] 8 | arguments = 9 | [ "benchmark" 10 | , "app" 11 | , "library" 12 | , "test/testsuite" 13 | ] 14 | 15 | main :: IO () 16 | main = hlint arguments >> exitSuccess 17 | 18 | -- main = do 19 | -- hints <- hlint arguments 20 | -- if null hints 21 | -- then exitSuccess 22 | -- else exitFailure 23 | -------------------------------------------------------------------------------- /test/testsuite/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | 3 | -- {-# LANGUAGE QuasiQuotes #-} 4 | 5 | module Main (main) where 6 | 7 | import RIO 8 | 9 | import Test.Tasty (TestTree, defaultMainWithIngredients, testGroup) 10 | import Test.Tasty.HUnit (Assertion, testCase, (@?=)) 11 | import Test.Tasty.Ingredients.Rerun (rerunningTests) 12 | import Test.Tasty.Runners (consoleTestReporter, listingTests) 13 | import Test.Tasty.SmallCheck (testProperty) 14 | 15 | -- import Test.Hspec 16 | -- import Test.Hspec.Wai 17 | -- import Test.Hspec.Wai.JSON 18 | 19 | -- import Network.Wai.Handler.Warp (run) 20 | 21 | -- main :: IO () 22 | -- main = return () 23 | 24 | -- main = do 25 | -- -- port <- lookupSetting "PORT" 8081 26 | -- -- pool <- makePool Test 27 | 28 | -- -- runSqlPool doMigrate pool 29 | 30 | -- -- let logger = setLogger Test 31 | -- -- config = Config { getPool = pool 32 | -- -- , getEnv = Test 33 | -- -- } 34 | 35 | -- putStrLn "Testing..." 36 | -- hspec . spec . run port . logger $ app config 37 | 38 | -- spec :: IO () -> Spec 39 | -- spec server = with server do 40 | -- describe "GET /ping" $ do 41 | -- it "responds with 200" do 42 | -- 1 `shouldBe` 1 43 | -- -- get "/users" `shouldRespondWith` 200 44 | 45 | -- -- it "responds with 'Simple'" do 46 | -- -- get "/" `shouldRespondWith` "Simple" 47 | 48 | -- config = Config { getPool = , getEnv = Test } 49 | 50 | -- main :: IO () 51 | -- main = return () 52 | 53 | main :: IO () 54 | main = 55 | defaultMainWithIngredients 56 | [ rerunningTests [listingTests, consoleTestReporter] ] 57 | (testGroup "all-tests" tests) 58 | 59 | tests :: [TestTree] 60 | tests = 61 | [ testGroup "SmallCheck" scTests 62 | -- , testGroup "Unit tests" huTests 63 | ] 64 | 65 | scTests :: [TestTree] 66 | scTests = 67 | [ testProperty "inc == succ" prop_succ 68 | , testProperty "inc . negate == negate . pred" prop_pred 69 | ] 70 | 71 | -- huTests :: [TestTree] 72 | -- huTests = 73 | -- [ testCase "Increment below TheAnswer" case_inc_below 74 | -- , testCase "Decrement above TheAnswer" case_dec_above 75 | -- ] 76 | 77 | prop_succ :: Int -> Bool 78 | prop_succ n = 1 + n == 1 + n 79 | 80 | prop_pred :: Int -> Bool 81 | prop_pred n = 1 + (negate n) == negate (n - 1) 82 | 83 | -- case_inc_below :: Assertion 84 | -- case_inc_below = inc 41 @?= (42 :: Int) 85 | 86 | -- case_dec_above :: Assertion 87 | -- case_dec_above = negate (inc (negate 43)) @?= (42 :: Int) 88 | --------------------------------------------------------------------------------