From b604f4d1b2a34de6d58c8e8a242b88c0fd6ac38e Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 1 Dec 2025 16:09:47 -0700 Subject: [PATCH 1/4] init --- .github/workflows/add-asana-comment.yml | 16 - .github/workflows/ci.yml | 6 +- .github/workflows/release.yml | 5 +- .github/workflows/restyled.yml | 2 +- .stack-all | 2 +- CHANGELOG.md | 25 +- LICENSE | 2 +- README.lhs | 84 - README.md | 8 +- flake.lock | 297 +- flake.nix | 50 +- freckle-http.cabal | 140 + haskell-library-template.cabal | 167 - library/Freckle/App/Http.hs | 306 + library/Freckle/App/Http/Cache.hs | 341 + library/Freckle/App/Http/Cache/Gzip.hs | 62 + library/Freckle/App/Http/Cache/Memcached.hs | 151 + library/Freckle/App/Http/Cache/State.hs | 83 + library/Freckle/App/Http/Header.hs | 47 + library/Freckle/App/Http/Paginate.hs | 101 + library/Freckle/App/Http/Retry.hs | 101 + library/Freckle/App/Test/Http.hs | 364 + library/Freckle/App/Test/Http/MatchRequest.hs | 186 + package.yaml | 114 +- stack-lts12.yaml | 15 - stack-lts14.yaml | 12 - stack-lts16.yaml | 11 - stack-lts18.yaml | 10 - stack-lts19.yaml | 10 - stack-lts20.yaml | 14 + stack-lts21.yaml | 13 + stack-lts22.yaml | 15 + stack-lts23.yaml | 16 + stack-lts24.yaml | 31 + stack-nightly.yaml | 18 +- stack.yaml | 2 +- test/Spec.hs | 1 - test/SpecHook.hs | 11 - test/WhateverSpec.hs | 8 - tests/Freckle/App/Http/CacheSpec.hs | 401 + tests/Freckle/App/HttpSpec.hs | 51 + .../Freckle/App/Test/Http/MatchRequestSpec.hs | 115 + tests/Main.hs | 1 + tests/files/constructed-responses.gzip | Bin 0 -> 3840 bytes tests/files/https/www.stackage.org/lts-17.10 | 16012 ++++++++++++++++ 45 files changed, 18747 insertions(+), 680 deletions(-) delete mode 100644 .github/workflows/add-asana-comment.yml delete mode 100644 README.lhs mode change 120000 => 100644 README.md create mode 100644 freckle-http.cabal delete mode 100644 haskell-library-template.cabal create mode 100644 library/Freckle/App/Http.hs create mode 100644 library/Freckle/App/Http/Cache.hs create mode 100644 library/Freckle/App/Http/Cache/Gzip.hs create mode 100644 library/Freckle/App/Http/Cache/Memcached.hs create mode 100644 library/Freckle/App/Http/Cache/State.hs create mode 100644 library/Freckle/App/Http/Header.hs create mode 100644 library/Freckle/App/Http/Paginate.hs create mode 100644 library/Freckle/App/Http/Retry.hs create mode 100644 library/Freckle/App/Test/Http.hs create mode 100644 library/Freckle/App/Test/Http/MatchRequest.hs delete mode 100644 stack-lts12.yaml delete mode 100644 stack-lts14.yaml delete mode 100644 stack-lts16.yaml delete mode 100644 stack-lts18.yaml delete mode 100644 stack-lts19.yaml create mode 100644 stack-lts24.yaml delete mode 100644 test/Spec.hs delete mode 100644 test/SpecHook.hs delete mode 100644 test/WhateverSpec.hs create mode 100644 tests/Freckle/App/Http/CacheSpec.hs create mode 100644 tests/Freckle/App/HttpSpec.hs create mode 100644 tests/Freckle/App/Test/Http/MatchRequestSpec.hs create mode 100644 tests/Main.hs create mode 100644 tests/files/constructed-responses.gzip create mode 100644 tests/files/https/www.stackage.org/lts-17.10 diff --git a/.github/workflows/add-asana-comment.yml b/.github/workflows/add-asana-comment.yml deleted file mode 100644 index aaa3f6d..0000000 --- a/.github/workflows/add-asana-comment.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Asana - -on: - pull_request: - types: [opened] - -jobs: - link-asana-task: - if: ${{ github.actor != 'dependabot[bot]' }} - runs-on: ubuntu-latest - steps: - - uses: Asana/create-app-attachment-github-action@v1.3 - id: postAttachment - with: - asana-secret: ${{ secrets.ASANA_API_ACCESS_KEY }} - - run: echo "Status is ${{ steps.postAttachment.outputs.status }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c52b4b0..c367f7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: generate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - id: generate uses: freckle/stack-action/generate-matrix@v5 outputs: @@ -29,7 +29,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - id: stack uses: freckle/stack-action@v5 env: @@ -38,7 +38,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: haskell-actions/hlint-setup@v2 - uses: haskell-actions/hlint-run@v2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 06f4080..1b191d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,10 +6,9 @@ on: jobs: release: - if: false # Remove when ready to release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - id: tag uses: freckle/haskell-tag-action@v1 @@ -18,4 +17,4 @@ jobs: run: stack upload --pvp-bounds lower . env: HACKAGE_KEY: ${{ secrets.HACKAGE_UPLOAD_API_KEY }} - STACK_YAML: stack-lts12.yaml + STACK_YAML: stack-lts20.yaml diff --git a/.github/workflows/restyled.yml b/.github/workflows/restyled.yml index 26bb617..9ccece6 100644 --- a/.github/workflows/restyled.yml +++ b/.github/workflows/restyled.yml @@ -11,7 +11,7 @@ jobs: restyled: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: restyled-io/actions/setup@v4 - uses: restyled-io/actions/run@v4 with: diff --git a/.stack-all b/.stack-all index 7b4bdbf..f596c48 100644 --- a/.stack-all +++ b/.stack-all @@ -1,2 +1,2 @@ [versions] -oldest = lts-12 +oldest = lts-20 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4566140..5c980f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ -## [_Unreleased_](https://github.com/freckle/haskell-library-template/compare/v__...main) +## [_Unreleased_](https://github.com/freckle/freckle-http/compare/v0.3.0.1...main) -## [v0.0.0.0](https://github.com/freckle/haskell-library-template/tree/v0.0.0.0) +## [v0.3.0.1](https://github.com/freckle/freckle-http/tree/v0.3.0.1) -First tagged release. +Metadata change only; moved to new source repository. + +## [v0.3.0.0](https://github.com/freckle/freckle-app/compare/freckle-http-v0.2.0.0...freckle-http-v0.3.0.0) + +- Update `HttpCache.set` to accept TTL (and use it in memcached implementation) + +## [v0.2.0.0](https://github.com/freckle/freckle-app/compare/freckle-http-v0.1.0.0...freckle-http-v0.2.0.0) + +`MonadHttp.httpLbs` has a `HasCallStack` constraint, and instances throw `AnnotatedException` + +Breaking change: `httpStubbed` is now monadic rather than pure. Its errors are thrown into `IO` as +`AnnotatedException`-wrapped `NoStubsMatched`. + +## [v0.1.0.0](https://github.com/freckle/freckle-app/compare/freckle-http-v0.0.0.0...freckle-http-v0.1.0.0) + +Removes `Freckle.App.HttpSpec` which had been included by mistake. + +## [v0.0.0.0](https://github.com/freckle/freckle-app/tree/freckle-http-v0.0.0.0/freckle-http) + +First release, sprouted from `freckle-app-1.19.0.0`. diff --git a/LICENSE b/LICENSE index 68ec97f..5788a64 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2025 Renaissance Learning Inc +Copyright (c) 2024 Renaissance Learning Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.lhs b/README.lhs deleted file mode 100644 index 517b492..0000000 --- a/README.lhs +++ /dev/null @@ -1,84 +0,0 @@ -# haskell-library-template - -[![Hackage](https://img.shields.io/hackage/v/haskell-library-template.svg?style=flat)](https://hackage.haskell.org/package/haskell-library-template) -[![Stackage Nightly](http://stackage.org/package/haskell-library-template/badge/nightly)](http://stackage.org/nightly/package/haskell-library-template) -[![Stackage LTS](http://stackage.org/package/haskell-library-template/badge/lts)](http://stackage.org/lts/package/haskell-library-template) -[![CI](https://github.com/freckle/haskell-library-template/actions/workflows/ci.yml/badge.svg)](https://github.com/freckle/haskell-library-template/actions/workflows/ci.yml) - -_Synopsis_ - -## Example - - - -```haskell -someExample :: IO () -someExample = putStrLn "Hello world" -``` - - - -## Development & Tests - -```console -stack build --fast --pedantic --test --file-watch -``` - ---- - -## How to use this Template - -Haskell library template used at Freckle. - -### Create your repo - -If you are working within the freckle org, use [github-vending-machine][ghvm]. Otherwise: - -[ghvm]: https://github.com/freckle/github-vending-machine -```sh -gh repo create --template freckle/haskell-library-template --public freckle/ -git clone git@github.com:freckle/ -cd ./ -``` - -### Rename your package - -```sh -find -type f -exec \ - sed -i s/haskell-library-template/my-name/ {} + -``` - -Edit `package.yaml` as necessary. - -### Enable release - -When you are ready to release your library, simply remove the conditional from -the release workflow. - -```diff -- - if: false # Remove when ready to release -``` - -### Open repo up to [hacktoberfest][hacktoberfest] contributions - -Add the `hacktoberfest` topic to your repo if - -- you're planning on releasing it as open source, and -- you think it would benefit from and be amenable to public contributions - -[hacktoberfest]: https://hacktoberfest.digitalocean.com/ - ---- - -[CHANGELOG](./CHANGELOG.md) | [LICENSE](./LICENSE) diff --git a/README.md b/README.md deleted file mode 120000 index 4e381b2..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -README.lhs \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..105dd29 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# freckle-http + +`freckle-http` is a general-purpose toolkit for making HTTP requests. + +--- + +[CHANGELOG](./CHANGELOG.md) | [LICENSE](./LICENSE) diff --git a/flake.lock b/flake.lock index 66faf8d..d8bf77f 100644 --- a/flake.lock +++ b/flake.lock @@ -1,21 +1,5 @@ { "nodes": { - "autodocodec": { - "flake": false, - "locked": { - "lastModified": 1722952210, - "narHash": "sha256-+vz0En2dkS0nfOq12QAJtV6GcdP0WoDv7HwzrMIsnGo=", - "owner": "NorfairKing", - "repo": "autodocodec", - "rev": "ac36615cb344ec43259fb65cafe3e55308ce4662", - "type": "github" - }, - "original": { - "owner": "NorfairKing", - "repo": "autodocodec", - "type": "github" - } - }, "flake-compat": { "flake": false, "locked": { @@ -37,11 +21,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -55,11 +39,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -73,11 +57,11 @@ "systems": "systems_3" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -91,22 +75,20 @@ "flake-utils": "flake-utils_2", "haskell-openapi-code-generator": "haskell-openapi-code-generator", "nix-github-actions": "nix-github-actions", - "nixpkgs-22-11": "nixpkgs-22-11", "nixpkgs-23-05": "nixpkgs-23-05", "nixpkgs-23-11": "nixpkgs-23-11", "nixpkgs-24-05": "nixpkgs-24-05", "nixpkgs-24-11": "nixpkgs-24-11", - "nixpkgs-haskell-updates": "nixpkgs-haskell-updates", - "nixpkgs-stable": "nixpkgs-stable_2", - "stack-lint-extra-deps": "stack-lint-extra-deps" + "nixpkgs-25-05": "nixpkgs-25-05", + "nixpkgs-unstable": "nixpkgs-unstable" }, "locked": { "dir": "main", - "lastModified": 1743440174, - "narHash": "sha256-c4E4uWjTaqovRCU64Mug7E5GxfIPeQdp/S4UPP/tl+Y=", + "lastModified": 1763806570, + "narHash": "sha256-ePETIKEli6r1X/8wl7mjkEsNlsCeYuQjr7HUxFU4V/Q=", "owner": "freckle", "repo": "flakes", - "rev": "55ee41d355a69ca8df6bc5e7d35273ba35b67743", + "rev": "6273f62508fbed25a32e4a664b4933fad1c84d4b", "type": "github" }, "original": { @@ -141,20 +123,16 @@ }, "haskell-openapi-code-generator": { "inputs": { - "autodocodec": "autodocodec", "flake-utils": "flake-utils_3", "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks", - "safe-coloured-text": "safe-coloured-text", - "sydtest": "sydtest", - "validity": "validity" + "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1731515792, - "narHash": "sha256-EoU807Pqii6hnLvXIu8G2fDOyKLBrOSesXsEEh3YoZQ=", + "lastModified": 1752914035, + "narHash": "sha256-QghINu6JPxiUyK3XSBhjgT/CvFez4hL8hGwfNvr9vPI=", "owner": "Haskell-OpenAPI-Code-Generator", "repo": "Haskell-OpenAPI-Client-Code-Generator", - "rev": "0eb15be1a8ae9c311e727e300e7564bf2eb7096f", + "rev": "08fa0eb1d2baef4e3f328ae155bd0ff4ad08efcf", "type": "github" }, "original": { @@ -167,15 +145,15 @@ "inputs": { "nixpkgs": [ "freckle", - "nixpkgs-stable" + "nixpkgs-25-05" ] }, "locked": { - "lastModified": 1703863825, - "narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=", + "lastModified": 1737420293, + "narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=", "owner": "nix-community", "repo": "nix-github-actions", - "rev": "5163432afc817cf8bd1f031418d1869e4c9d5547", + "rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9", "type": "github" }, "original": { @@ -186,32 +164,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1722987190, - "narHash": "sha256-68hmex5efCiM2aZlAAEcQgmFI4ZwWt8a80vOeB/5w3A=", + "lastModified": 1748162331, + "narHash": "sha256-rqc2RKYTxP3tbjA+PB3VMRQNnjesrT0pEofXQTrMsS8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "21cc704b5e918c5fbf4f9fff22b4ac2681706d90", + "rev": "7c43f080a7f28b2774f3b3f43234ca11661bf334", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-24.05", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-22-11": { - "locked": { - "lastModified": 1688392541, - "narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-22.11", + "ref": "nixos-25.05", "repo": "nixpkgs", "type": "github" } @@ -234,11 +196,11 @@ }, "nixpkgs-23-11": { "locked": { - "lastModified": 1717530100, - "narHash": "sha256-b4Dn+PnrZoVZ/BoR9JN2fTxXxplJrAsdSUIePf4Cacs=", + "lastModified": 1720535198, + "narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "a2e1d0414259a144ebdc048408a807e69e0565af", + "rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5", "type": "github" }, "original": { @@ -266,11 +228,11 @@ }, "nixpkgs-24-11": { "locked": { - "lastModified": 1743367904, - "narHash": "sha256-sOos1jZGKmT6xxPvxGQyPTApOunXvScV4lNjBCXd/CI=", + "lastModified": 1751274312, + "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", "owner": "nixos", "repo": "nixpkgs", - "rev": "7ffe0edc685f14b8c635e3d6591b0bbb97365e6c", + "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", "type": "github" }, "original": { @@ -280,77 +242,61 @@ "type": "github" } }, - "nixpkgs-haskell-updates": { + "nixpkgs-25-05": { "locked": { - "lastModified": 1717667911, - "narHash": "sha256-naJoCoUil31xjrUixCsKFogTkTiI02fv2X/6QukpacA=", + "lastModified": 1761173472, + "narHash": "sha256-m9W0dYXflzeGgKNravKJvTMR4Qqa2MVD11AwlGMufeE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "f166c7778ccf61d7f8b89a9a94060dce070458d0", + "rev": "c8aa8cc00a5cb57fada0851a038d35c08a36a2bb", "type": "github" }, "original": { "owner": "nixos", - "ref": "haskell-updates", + "ref": "nixos-25.05", "repo": "nixpkgs", "type": "github" } }, - "nixpkgs-haskell-updates_2": { + "nixpkgs-stable": { "locked": { - "lastModified": 1742210880, - "narHash": "sha256-bFR9Tthdaz1mR/IzdtHgzPuw5nEHn49AWay8iUqFEPM=", + "lastModified": 1764522689, + "narHash": "sha256-SqUuBFjhl/kpDiVaKLQBoD8TLD+/cTUzzgVFoaHrkqY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "1f00f46d8b14f6956005ab80f2e0e9c875d0dace", + "rev": "8bb5646e0bed5dbd3ab08c7a7cc15b75ab4e1d0f", "type": "github" }, "original": { "owner": "nixos", - "ref": "haskell-updates", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-stable": { - "locked": { - "lastModified": 1720386169, - "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-24.05", + "ref": "nixos-25.11", "repo": "nixpkgs", "type": "github" } }, - "nixpkgs-stable_2": { + "nixpkgs-unstable": { "locked": { - "lastModified": 1743367904, - "narHash": "sha256-sOos1jZGKmT6xxPvxGQyPTApOunXvScV4lNjBCXd/CI=", + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "7ffe0edc685f14b8c635e3d6591b0bbb97365e6c", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-24.11", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1719082008, - "narHash": "sha256-jHJSUH619zBQ6WdC21fFAlDxHErKVDJ5fpN0Hgx4sjs=", + "lastModified": 1730768919, + "narHash": "sha256-8AKquNnnSaJRXZxc5YmF/WfmxiHX6MMZZasRP6RRQkE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9693852a2070b398ee123a329e68f0dab5526681", + "rev": "a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc", "type": "github" }, "original": { @@ -360,51 +306,18 @@ "type": "github" } }, - "nixpkgs_3": { - "locked": { - "lastModified": 1742136038, - "narHash": "sha256-DDe16FJk18sadknQKKG/9FbwEro7A57tg9vB5kxZ8kY=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "a1185f4064c18a5db37c5c84e5638c78b46e3341", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-24.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "pgp-wordlist": { - "flake": false, - "locked": { - "lastModified": 1714149562, - "narHash": "sha256-WCFQgtWqq+gLst4lXkFYlmlW7L8PQOJfChsKMApy3Ng=", - "owner": "quchen", - "repo": "pgp-wordlist", - "rev": "1f0cfd90d62179952cbfd59c3405283a1d364272", - "type": "github" - }, - "original": { - "owner": "quchen", - "repo": "pgp-wordlist", - "type": "github" - } - }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", "gitignore": "gitignore", - "nixpkgs": "nixpkgs_2", - "nixpkgs-stable": "nixpkgs-stable" + "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1723202784, - "narHash": "sha256-qbhjc/NEGaDbyy0ucycubq4N3//gDFFH3DOmp1D3u1Q=", + "lastModified": 1742649964, + "narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "c7012d0c18567c889b948781bc74a501e92275d1", + "rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82", "type": "github" }, "original": { @@ -417,91 +330,7 @@ "inputs": { "flake-utils": "flake-utils", "freckle": "freckle", - "stable": "stable" - } - }, - "safe-coloured-text": { - "flake": false, - "locked": { - "lastModified": 1722709522, - "narHash": "sha256-zglWqKISYz34A+/PvqwvwrbQYSEcko8C595VAeINitw=", - "owner": "NorfairKing", - "repo": "safe-coloured-text", - "rev": "046f10147a058c00c2706d98c341219e3cbc7669", - "type": "github" - }, - "original": { - "owner": "NorfairKing", - "repo": "safe-coloured-text", - "type": "github" - } - }, - "stable": { - "locked": { - "lastModified": 1743367904, - "narHash": "sha256-sOos1jZGKmT6xxPvxGQyPTApOunXvScV4lNjBCXd/CI=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "7ffe0edc685f14b8c635e3d6591b0bbb97365e6c", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-24.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "stack-lint-extra-deps": { - "inputs": { - "nixpkgs": "nixpkgs_3", - "nixpkgs-haskell-updates": "nixpkgs-haskell-updates_2", - "pgp-wordlist": "pgp-wordlist", - "stacklock2nix": "stacklock2nix" - }, - "locked": { - "lastModified": 1742840028, - "narHash": "sha256-1zMbaz1lTP/R70jvVCQclkNTbvfsyFIlA8qYd0OdmJ4=", - "owner": "freckle", - "repo": "stack-lint-extra-deps", - "rev": "7c3d047b4faf6ff946197097a4df4cd31f0b7373", - "type": "github" - }, - "original": { - "owner": "freckle", - "repo": "stack-lint-extra-deps", - "type": "github" - } - }, - "stacklock2nix": { - "locked": { - "lastModified": 1741332755, - "narHash": "sha256-ZQlzcx4f2cx4CQBQzMvEwJEAWJrfZEdZrGY7Iu5L9MA=", - "owner": "cdepillabout", - "repo": "stacklock2nix", - "rev": "47dd3fb2827fc0868142ab41a7daeaec0a6ddda1", - "type": "github" - }, - "original": { - "owner": "cdepillabout", - "repo": "stacklock2nix", - "type": "github" - } - }, - "sydtest": { - "flake": false, - "locked": { - "lastModified": 1722766036, - "narHash": "sha256-i7N8HKkFPUfnOOmj47Di/XnQS66mUzTG6HISUccNhOA=", - "owner": "NorfairKing", - "repo": "sydtest", - "rev": "23baea50a08857baf3121fc55096c40a93ff5f17", - "type": "github" - }, - "original": { - "owner": "NorfairKing", - "repo": "sydtest", - "type": "github" + "nixpkgs-stable": "nixpkgs-stable" } }, "systems": { @@ -548,22 +377,6 @@ "repo": "default", "type": "github" } - }, - "validity": { - "flake": false, - "locked": { - "lastModified": 1722943924, - "narHash": "sha256-hq5FwDFW+02u5Qsx8v1KWoQUsY6S6ufI4WYKZ6yhYUA=", - "owner": "NorfairKing", - "repo": "validity", - "rev": "51b8843b9bd5228160b99f653d3271147245d689", - "type": "github" - }, - "original": { - "owner": "NorfairKing", - "repo": "validity", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 2040717..20221c7 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - stable.url = "github:nixos/nixpkgs/nixos-24.11"; + nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-25.11"; freckle.url = "github:freckle/flakes?dir=main"; flake-utils.url = "github:numtide/flake-utils"; }; @@ -9,41 +9,31 @@ inputs.flake-utils.lib.eachDefaultSystem ( system: let - nixpkgsArgs = { - inherit system; - config = { }; - }; - - nixpkgs = { - stable = import inputs.stable nixpkgsArgs; - }; + nixpkgs-stable = inputs.nixpkgs-stable.legacyPackages.${system}; freckle = inputs.freckle.packages.${system}; freckleLib = inputs.freckle.lib.${system}; - in - rec { - packages = { - fourmolu = freckle.fourmolu-0-17-x; - - ghc = freckleLib.haskellBundle { - ghcVersion = "ghc-9-8-4"; - enableHLS = true; - }; - }; - - devShells.default = nixpkgs.stable.mkShell { - buildInputs = with (nixpkgs.stable); [ - zlib + { + devShells.default = nixpkgs-stable.mkShell { + buildInputs = [ nixpkgs-stable.zlib ]; + nativeBuildInputs = [ + freckle.fourmolu-0-17-x + (freckleLib.haskellBundle { + ghcVersion = "ghc-9-10-3"; + enableHLS = true; + }) ]; + shellHook = ''export STACK_YAML=stack.yaml''; + }; - nativeBuildInputs = with (packages); [ - fourmolu - ghc + devShells.nightly = nixpkgs-stable.mkShell { + buildInputs = [ nixpkgs-stable.zlib ]; + nativeBuildInputs = [ + freckle.fourmolu-0-17-x + nixpkgs-stable.haskell.compiler.ghc9122 + nixpkgs-stable.stack ]; - - shellHook = '' - export STACK_YAML=stack.yaml - ''; + shellHook = ''export STACK_YAML=stack-nightly.yaml''; }; } ); diff --git a/freckle-http.cabal b/freckle-http.cabal new file mode 100644 index 0000000..ac748e6 --- /dev/null +++ b/freckle-http.cabal @@ -0,0 +1,140 @@ +cabal-version: 1.18 + +-- This file has been generated from package.yaml by hpack version 0.38.1. +-- +-- see: https://github.com/sol/hpack + +name: freckle-http +version: 0.3.0.1 +synopsis: Toolkit for making HTTP requests +description: Please see README.md +category: HTTP +homepage: https://github.com/freckle/freckle-http#readme +bug-reports: https://github.com/freckle/freckle-http/issues +maintainer: Freckle Education +license: MIT +license-file: LICENSE +build-type: Simple +extra-source-files: + package.yaml +extra-doc-files: + README.md + CHANGELOG.md + +source-repository head + type: git + location: https://github.com/freckle/freckle-http + +library + exposed-modules: + Freckle.App.Http + Freckle.App.Http.Cache + Freckle.App.Http.Cache.Gzip + Freckle.App.Http.Cache.Memcached + Freckle.App.Http.Cache.State + Freckle.App.Http.Header + Freckle.App.Http.Paginate + Freckle.App.Http.Retry + Freckle.App.Test.Http + Freckle.App.Test.Http.MatchRequest + other-modules: + Paths_freckle_http + hs-source-dirs: + library + default-extensions: + DataKinds + DeriveAnyClass + DerivingVia + DerivingStrategies + DuplicateRecordFields + GADTs + LambdaCase + NoImplicitPrelude + NoMonomorphismRestriction + OverloadedRecordDot + OverloadedStrings + RecordWildCards + TypeFamilies + ghc-options: -fignore-optim-changes -fwrite-ide-info -Weverything -Wno-all-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-kind-signatures -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-safe -Wno-unsafe + build-depends: + Blammo + , Glob + , aeson + , annotated-exception + , base <5 + , bytestring + , case-insensitive + , conduit + , directory + , errors + , extra + , filepath + , freckle-memcached + , hs-opentelemetry-api + , http-client + , http-conduit >=2.3.5 + , http-link-header + , http-types + , lens + , memcache + , monad-logger + , monad-validate + , mtl + , network-uri + , retry >=0.8.1.0 + , safe + , semigroupoids + , serialise + , text + , time + , transformers + , unliftio + , unordered-containers + default-language: GHC2021 + if impl(ghc >= 9.8) + ghc-options: -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures + +test-suite spec + type: exitcode-stdio-1.0 + main-is: Main.hs + other-modules: + Freckle.App.Http.CacheSpec + Freckle.App.HttpSpec + Freckle.App.Test.Http.MatchRequestSpec + Paths_freckle_http + hs-source-dirs: + tests + default-extensions: + DataKinds + DeriveAnyClass + DerivingVia + DerivingStrategies + DuplicateRecordFields + GADTs + LambdaCase + NoImplicitPrelude + NoMonomorphismRestriction + OverloadedRecordDot + OverloadedStrings + RecordWildCards + TypeFamilies + ghc-options: -fignore-optim-changes -fwrite-ide-info -Weverything -Wno-all-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-kind-signatures -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-safe -Wno-unsafe -threaded -rtsopts "-with-rtsopts=-N" + build-depends: + aeson + , base <5 + , bytestring + , freckle-http + , freckle-prelude + , hspec >=2.8.1 + , hspec-expectations-json + , hspec-expectations-lifted + , http-types + , lens + , lens-aeson + , mtl + , time + , unordered-containers + , zlib + default-language: GHC2021 + if impl(ghc >= 9.8) + ghc-options: -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures diff --git a/haskell-library-template.cabal b/haskell-library-template.cabal deleted file mode 100644 index c3f48c1..0000000 --- a/haskell-library-template.cabal +++ /dev/null @@ -1,167 +0,0 @@ -cabal-version: 1.18 - --- This file has been generated from package.yaml by hpack version 0.38.1. --- --- see: https://github.com/sol/hpack - -name: haskell-library-template -version: 0.0.0.0 -synopsis: Short synopsis here -description: Longer description here - . - Haddocks are more valuable if you reproduce the main README example here, but - you can also just direct users to the README if you prefer not to take on that - duplication burden. -category: Utils -homepage: https://github.com/freckle/haskell-library-template#readme -bug-reports: https://github.com/freckle/haskell-library-template/issues -maintainer: Freckle Education -license: MIT -license-file: LICENSE -build-type: Simple -extra-source-files: - package.yaml -extra-doc-files: - README.md - CHANGELOG.md - -source-repository head - type: git - location: https://github.com/freckle/haskell-library-template - -library - other-modules: - Paths_haskell_library_template - hs-source-dirs: - src - default-extensions: - BangPatterns - DataKinds - DeriveAnyClass - DeriveFoldable - DeriveFunctor - DeriveGeneric - DeriveLift - DeriveTraversable - DerivingStrategies - FlexibleContexts - FlexibleInstances - GADTs - GeneralizedNewtypeDeriving - LambdaCase - MultiParamTypeClasses - NoImplicitPrelude - NoMonomorphismRestriction - OverloadedStrings - RankNTypes - RecordWildCards - ScopedTypeVariables - StandaloneDeriving - TypeApplications - TypeFamilies - ghc-options: -Weverything -Wno-all-missed-specialisations -Wno-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe - build-depends: - base <5 - default-language: Haskell2010 - if impl(ghc >= 9.8) - ghc-options: -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures - if impl(ghc >= 9.2) - ghc-options: -Wno-missing-kind-signatures - if impl(ghc >= 8.10) - ghc-options: -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module - if impl(ghc >= 8.8) - ghc-options: -fwrite-ide-info - -test-suite readme - type: exitcode-stdio-1.0 - main-is: README.lhs - other-modules: - Paths_haskell_library_template - default-extensions: - BangPatterns - DataKinds - DeriveAnyClass - DeriveFoldable - DeriveFunctor - DeriveGeneric - DeriveLift - DeriveTraversable - DerivingStrategies - FlexibleContexts - FlexibleInstances - GADTs - GeneralizedNewtypeDeriving - LambdaCase - MultiParamTypeClasses - NoImplicitPrelude - NoMonomorphismRestriction - OverloadedStrings - RankNTypes - RecordWildCards - ScopedTypeVariables - StandaloneDeriving - TypeApplications - TypeFamilies - ghc-options: -Weverything -Wno-all-missed-specialisations -Wno-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe -pgmL markdown-unlit - build-tool-depends: - markdown-unlit:markdown-unlit - build-depends: - base <5 - default-language: Haskell2010 - if impl(ghc >= 9.8) - ghc-options: -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures - if impl(ghc >= 9.2) - ghc-options: -Wno-missing-kind-signatures - if impl(ghc >= 8.10) - ghc-options: -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module - if impl(ghc >= 8.8) - ghc-options: -fwrite-ide-info - -test-suite spec - type: exitcode-stdio-1.0 - main-is: Spec.hs - other-modules: - SpecHook - WhateverSpec - Paths_haskell_library_template - hs-source-dirs: - test - default-extensions: - BangPatterns - DataKinds - DeriveAnyClass - DeriveFoldable - DeriveFunctor - DeriveGeneric - DeriveLift - DeriveTraversable - DerivingStrategies - FlexibleContexts - FlexibleInstances - GADTs - GeneralizedNewtypeDeriving - LambdaCase - MultiParamTypeClasses - NoImplicitPrelude - NoMonomorphismRestriction - OverloadedStrings - RankNTypes - RecordWildCards - ScopedTypeVariables - StandaloneDeriving - TypeApplications - TypeFamilies - ghc-options: -Weverything -Wno-all-missed-specialisations -Wno-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe -threaded -rtsopts "-with-rtsopts=-N" - build-depends: - base <5 - , hspec - , hspec-junit-formatter - default-language: Haskell2010 - if impl(ghc >= 9.8) - ghc-options: -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures - if impl(ghc >= 9.2) - ghc-options: -Wno-missing-kind-signatures - if impl(ghc >= 8.10) - ghc-options: -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module - if impl(ghc >= 8.8) - ghc-options: -fwrite-ide-info diff --git a/library/Freckle/App/Http.hs b/library/Freckle/App/Http.hs new file mode 100644 index 0000000..3399cf2 --- /dev/null +++ b/library/Freckle/App/Http.hs @@ -0,0 +1,306 @@ +-- | Centralized module for making HTTP requests +-- +-- These functions: +-- +-- - Do not throw exceptions on non-200 +-- - May throw for other 'HttpException' cases (e.g. 'ConnectionTimeout') +-- - Capture decoding failures with 'Either' values as the 'Response' body +-- - Handle 429-@Retry-In@ for you (if using an 'IO'-based instance) +module Freckle.App.Http + ( MonadHttp (..) + + -- * Decoding responses + , httpJson + , HttpDecodeError (..) + , httpDecode + + -- * Pagination + , httpPaginated + , sourcePaginated + + -- * Request builders + , Request + , parseRequest + , parseRequest_ + + -- * Request modifiers + , addRequestHeader + , addAcceptHeader + , addBearerAuthorizationHeader + , addToRequestQueryString + , setRequestBasicAuth + , setRequestBodyJSON + , setRequestBodyURLEncoded + , setRequestCheckStatus + , setRequestMethod + , setRequestPath + , disableRequestDecompress + + -- * Response accessors + , Response + , getResponseStatus + , getResponseBody + + -- ** Unsafe access + , getResponseBodyUnsafe + + -- * Exceptions + , HttpException (..) + -- | Predicates useful for handling 'HttpException's + -- + -- For example, given a function 'guarded', which returns 'Just' a given value + -- when a predicate holds for it (otherwise 'Nothing'), you can add + -- error-handling specific to exceptions caused by 4XX responses: + -- + -- @ + -- flip 'catchJust' (guard 'httpExceptionIsClientError' *> handle4XXError) $ do + -- resp <- 'httpJson' $ 'setRequestCheckStatus' $ parseRequest_ "http://..." + -- body <- 'getResponseBodyUnsafe' resp + -- + -- -- ... + -- @ + , httpExceptionIsInformational + , httpExceptionIsRedirection + , httpExceptionIsClientError + , httpExceptionIsServerError + + -- * "Network.HTTP.Types" re-exports + , Status + , statusCode + , statusIsInformational + , statusIsSuccessful + , statusIsRedirection + , statusIsClientError + , statusIsServerError + , StdMethod (..) + ) where + +import Prelude + +import Conduit (foldC, mapMC, runConduit, (.|)) +import Control.Exception.Annotated.UnliftIO + ( Exception (..) + , checkpointCallStack + , throwWithCallStack + ) +import Control.Monad.Except (ExceptT) +import Control.Monad.IO.Class (MonadIO) +import Control.Monad.Reader (ReaderT) +import Control.Monad.State (StateT) +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Maybe (MaybeT) +import Control.Monad.Validate (ValidateT) +import Control.Monad.Writer (WriterT) +import Data.Aeson (FromJSON) +import Data.Aeson qualified as Aeson +import Data.Bifunctor (first) +import Data.ByteString (ByteString) +import Data.ByteString.Lazy qualified as BSL +import Data.ByteString.Lazy.Char8 qualified as BSL8 +import Data.List.NonEmpty (NonEmpty) +import Data.List.NonEmpty qualified as NE +import Data.Text qualified as T +import Freckle.App.Http.Paginate +import Freckle.App.Http.Retry +import GHC.Stack (HasCallStack) +import Network.HTTP.Client qualified as HTTP (Request (..)) +import Network.HTTP.Conduit (HttpExceptionContent (..)) +import Network.HTTP.Simple hiding (httpLbs, httpNoBody, setRequestMethod) +import Network.HTTP.Simple qualified as HTTP +import Network.HTTP.Types (StdMethod (..), renderStdMethod) +import Network.HTTP.Types.Header (hAccept, hAuthorization) +import Network.HTTP.Types.Status + ( Status + , statusCode + , statusIsClientError + , statusIsInformational + , statusIsRedirection + , statusIsServerError + , statusIsSuccessful + ) + +-- | Type-class for making HTTP requests +-- +-- Functions of this module require the 'MonadHttp' constraint. This type class +-- allows us to instantiate differently in different contexts, most usefully +-- with stubbed responses in test. (See "Freckle.App.Test.Http".) +-- +-- The 'IO' instance does what you would expect, and can be used to either build +-- your own instances: +-- +-- @ +-- instance MonadIO m => MonadHttp (AppT m) where +-- httpLbs = liftIO . httpLbs +-- +-- instance MonadHttp (HandlerFor App) where +-- httpLbs = liftIO . httpLbs +-- @ +-- +-- Or directly, +-- +-- @ +-- resp <- liftIO $ httpLbs ... +-- @ +class Monad m => MonadHttp m where + httpLbs :: HasCallStack => Request -> m (Response BSL.ByteString) + +instance MonadHttp IO where + httpLbs x = checkpointCallStack $ rateLimited HTTP.httpLbs x + +instance MonadHttp m => MonadHttp (MaybeT m) where + httpLbs = lift . httpLbs + +instance MonadHttp m => MonadHttp (ReaderT r m) where + httpLbs = lift . httpLbs + +instance (Monoid w, MonadHttp m) => MonadHttp (WriterT w m) where + httpLbs = lift . httpLbs + +instance MonadHttp m => MonadHttp (StateT s m) where + httpLbs = lift . httpLbs + +instance MonadHttp m => MonadHttp (ExceptT e m) where + httpLbs = lift . httpLbs + +instance MonadHttp m => MonadHttp (ValidateT e m) where + httpLbs = lift . httpLbs + +data HttpDecodeError = HttpDecodeError + { hdeBody :: BSL.ByteString + , hdeErrors :: NonEmpty String + } + deriving stock (Eq, Show) + +instance Exception HttpDecodeError where + displayException HttpDecodeError {..} = + T.unpack $ + T.unlines $ + ["Error decoding HTTP Response:", "Raw body:", T.pack $ BSL8.unpack hdeBody] + <> fromErrors hdeErrors + where + fromErrors = \case + err NE.:| [] -> ["Error:", T.pack err] + errs -> "Errors:" : map (bullet . T.pack) (NE.toList errs) + bullet = (" • " <>) + +-- | Make a request and parse the body as JSON +-- +-- @ +-- -- Throws, but only on a complete failure to perform the request +-- resp <- 'httpJson' $ 'parseRequest_' "https://example.com" +-- +-- -- Safe access +-- 'getResponseBody' resp :: Either 'HttpDecodeError' a +-- +-- -- Unsafe access (throws on Left) +-- 'getResponseBodyUnsafe' resp :: m a +-- @ +httpJson + :: (MonadHttp m, FromJSON a) + => Request + -> m (Response (Either HttpDecodeError a)) +httpJson = + httpDecode (first pure . Aeson.eitherDecode) + . addAcceptHeader "application/json" + +-- | Make a request and decode the body using the given function +-- +-- This be used to request other formats, e.g. CSV. +httpDecode + :: MonadHttp m + => (BSL.ByteString -> Either (NonEmpty String) a) + -> Request + -> m (Response (Either HttpDecodeError a)) +httpDecode decode req = do + resp <- httpLbs req + let body = getResponseBody resp + pure $ first (HttpDecodeError body) . decode <$> resp + +-- | Request all pages of a paginated endpoint into some 'Monoid' +-- +-- For example, +-- +-- Interact with a paginated endpoint where each page is a JSON list, combining +-- all the pages into one list (i.e. 'concat') and throw on any decoding errors: +-- +-- @ +-- 'httpPaginated' 'httpJson' 'getResponseBodyUnsafe' $ 'parseRequest_' "https://..." +-- @ +-- +-- This uses 'sourcePaginated', and so reads a @Link@ header. To do otherwise, +-- drop down to 'sourcePaginatedBy' directly. +-- +-- The second argument is used to extract the data to combine out of the +-- response. This is particularly useful for 'Either' values, like you may get +-- from 'httpJson'. It lives in @m@ to support functions such as +-- 'getResponseBodyUnsafe'. +-- +-- Decoding errors can be handled differently by adjusting what 'Monoid' you +-- convert each page's response into: +-- +-- @ +-- 'httpPaginated' 'httpJson' fromResponseLenient $ 'parseRequest_' "https://..." +-- +-- fromResponseLenient +-- :: MonadLogger m +-- => Response (Either e [MyJsonThing]) +-- -> m [MyJsonThing] +-- fromResponseLenient r = case getResponseBody r of +-- Left _ -> [] <$ logWarn "..." +-- Right a -> pure a +-- @ +-- +-- See "Freckle.Http.App.Paginate" to process requested pages in a streaming +-- fashion, or perform pagination based on somethign other than @Link@. +httpPaginated + :: (MonadHttp m, Monoid b) + => (Request -> m (Response a)) + -> (Response a -> m b) + -> Request + -> m b +httpPaginated runRequest getBody req = + runConduit $ sourcePaginated runRequest req .| mapMC getBody .| foldC + +addAcceptHeader :: ByteString -> Request -> Request +addAcceptHeader = addRequestHeader hAccept + +addBearerAuthorizationHeader :: ByteString -> Request -> Request +addBearerAuthorizationHeader = addRequestHeader hAuthorization . ("Bearer " <>) + +setRequestMethod :: StdMethod -> Request -> Request +setRequestMethod method req = req {HTTP.method = renderStdMethod method} + +disableRequestDecompress :: Request -> Request +disableRequestDecompress req = + req + { HTTP.decompress = const False + } + +-- | Read an 'Either' response body, throwing any 'Left' as an exception +-- +-- If you plan to use this function, and haven't built your decoding to handle +-- error response bodies too, you'll want to use 'setRequestCheckStatus' so that +-- you see status-code exceptions before 'HttpDecodeError's. +getResponseBodyUnsafe + :: (MonadIO m, Exception e, HasCallStack) + => Response (Either e a) + -> m a +getResponseBodyUnsafe = either throwWithCallStack pure . getResponseBody + +httpExceptionIsInformational :: HttpException -> Bool +httpExceptionIsInformational = filterStatusException statusIsInformational + +httpExceptionIsRedirection :: HttpException -> Bool +httpExceptionIsRedirection = filterStatusException statusIsRedirection + +httpExceptionIsClientError :: HttpException -> Bool +httpExceptionIsClientError = filterStatusException statusIsClientError + +httpExceptionIsServerError :: HttpException -> Bool +httpExceptionIsServerError = filterStatusException statusIsServerError + +filterStatusException :: (Status -> Bool) -> HttpException -> Bool +filterStatusException predicate = \case + HttpExceptionRequest _ (StatusCodeException resp _) -> + predicate $ getResponseStatus resp + _ -> False diff --git a/library/Freckle/App/Http/Cache.hs b/library/Freckle/App/Http/Cache.hs new file mode 100644 index 0000000..5ca9558 --- /dev/null +++ b/library/Freckle/App/Http/Cache.hs @@ -0,0 +1,341 @@ +{-# LANGUAGE NoFieldSelectors #-} + +-- | Cache HTTP responses like a CDN or browser would +module Freckle.App.Http.Cache + ( HttpCacheSettings (..) + , HttpCacheCodec (..) + , HttpCache (..) + , httpCached + , CachedResponse (..) + , PotentiallyGzipped + ) where + +import Prelude + +import Blammo.Logging (Message (..), (.=)) +import Control.Applicative ((<|>)) +import Control.Exception.Annotated.UnliftIO (SomeException, displayException) +import Control.Monad (guard) +import Control.Monad.IO.Class (MonadIO) +import Data.ByteString (ByteString) +import Data.ByteString.Char8 qualified as BS8 +import Data.ByteString.Lazy qualified as BSL +import Data.CaseInsensitive qualified as CI +import Data.Foldable (for_) +import Data.List.Extra (firstJust) +import Data.Maybe (fromMaybe, mapMaybe) +import Data.Text.Encoding qualified as T +import Data.Text.Encoding.Error qualified as T +import Data.Time (UTCTime, addUTCTime, defaultTimeLocale, parseTimeM) +import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) +import Freckle.App.Http.Cache.Gzip +import Freckle.App.Http.Header +import Freckle.App.Memcached +import Network.HTTP.Client (Request, Response) +import Network.HTTP.Client qualified as HTTP +import Network.HTTP.Simple + ( addRequestHeader + , getRequestHeader + , getResponseStatus + ) +import Network.HTTP.Types.Header + ( HeaderName + , hAge + , hCacheControl + , hETag + , hExpires + , hIfNoneMatch + , hVary + ) +import Network.HTTP.Types.Status (Status, statusCode) +import Text.Read (readMaybe) + +data HttpCacheSettings m t = HttpCacheSettings + { shared :: Bool + , cacheable :: Request -> Bool + , defaultTTL :: CacheTTL + , getCurrentTime :: m UTCTime + , logDebug :: Message -> m () + , logWarn :: Message -> m () + , codec :: HttpCacheCodec t + , cache :: HttpCache m t + } + +data HttpCacheCodec t = HttpCacheCodec + { serialise :: CachedResponse -> t + , deserialise :: Request -> t -> Either String CachedResponse + } + +data HttpCache m t = HttpCache + { get :: CacheKey -> m (Either SomeException (Maybe t)) + , set :: CacheKey -> t -> CacheTTL -> m (Either SomeException ()) + , evict :: CacheKey -> m (Either SomeException ()) + } + +data CachedResponse = CachedResponse + { response :: Response (PotentiallyGzipped BSL.ByteString) + , inserted :: UTCTime + , ttl :: CacheTTL + } + deriving stock (Show) + +isCachedResponseStale :: CachedResponse -> UTCTime -> Bool +isCachedResponseStale cached now = + addUTCTime (fromIntegral cached.ttl) cached.inserted < now + +-- Wrap a function from "Freckle.App.Http" with caching +-- +-- Verify that the request is cacheable (e.g. a @GET@), then cache it at a +-- derived key (from URL and considering any @Vary@ headers). The response will +-- only be cached if @Cache-Control@ allows it. @Cache-Control@ is also used to +-- determine TTL (e.g. @max-age@) +-- +-- - +-- - +-- +-- If a cached response is stale, but it has an @ETag@ header, we will make the +-- request using @If-None-Match@ and still return (and retain) that cached +-- response if we receive a @304@ response. +-- +-- - +-- +httpCached + :: forall m t + . MonadIO m + => HttpCacheSettings m t + -> (Request -> m (Response BSL.ByteString)) + -> Request + -> m (Response BSL.ByteString) +httpCached settings doHttp req = + maybe (doHttp req) handleCachableRequest $ getCachableRequestKey settings req + where + handleCachableRequest key = do + now <- settings.getCurrentTime + result <- fromEx Nothing $ settings.cache.get key + + let tkey = T.decodeUtf8With T.lenientDecode $ fromCacheKey key + + case result of + Nothing -> do + settings.logDebug $ "Cache miss" :# ["key" .= tkey] + writeCache now key =<< getResponse req + Just val -> do + settings.logDebug $ "Cache hit" :# ["key" .= tkey] + case settings.codec.deserialise req val of + Left err -> do + settings.logWarn $ "Error deserialising" :# ["error" .= err] + writeCache now key =<< getResponse req + Right cresp | isCachedResponseStale cresp now -> do + settings.logDebug $ + "Cached value stale" + :# [ "key" .= tkey + , "inserted" .= cresp.inserted + , "ttl" .= fromCacheTTL cresp.ttl + , "now" .= now + ] + case lookupHeader hETag cresp.response of + Nothing -> do + fromEx () $ settings.cache.evict key + writeCache now key =<< getResponse req + Just etag -> do + settings.logDebug $ + "Retrying with If-None-Match" + :# [ "key" .= tkey + , "etag" .= T.decodeUtf8With T.lenientDecode etag + ] + resp <- getResponse $ addRequestHeader hIfNoneMatch etag req + case statusCode (getResponseStatus resp) of + 304 -> do + settings.logDebug "ETag matched (304), retaining cached response" + + -- We want to rewrite the cache entry based on Cache-Control + -- from base do now. Otherwise, we'll continue to treat it + -- as stale and do this 304 dance every time. But we use the + -- Cache-Control header from this response, in case it + -- differs + writeCache now key $ setCacheControlFrom resp cresp.response + _ -> do + settings.logDebug "ETag not matched, evicting cache" + fromEx () $ settings.cache.evict key + writeCache now key resp + Right cresp -> gunzipResponseBody req cresp.response + + getResponse :: Request -> m (Response (PotentiallyGzipped BSL.ByteString)) + getResponse = requestPotentiallyGzipped doHttp + + writeCache + :: UTCTime + -> CacheKey + -> Response (PotentiallyGzipped BSL.ByteString) + -> m (Response BSL.ByteString) + writeCache now key resp = do + for_ (getCachableResponseTTL settings resp) $ \ttl -> do + settings.logDebug $ + "Write cache" + :# [ "key" .= T.decodeUtf8With T.lenientDecode (fromCacheKey key) + , "ttl" .= fromCacheTTL ttl + ] + let cresp = CachedResponse {response = resp, inserted = now, ttl = ttl} + fromEx () $ settings.cache.set key (settings.codec.serialise cresp) ttl + + gunzipResponseBody req resp + + fromEx :: a -> m (Either SomeException a) -> m a + fromEx a f = do + result <- f + case result of + Left ex -> do + settings.logWarn $ "Caching error" :# ["error" .= displayException ex] + pure a + Right v -> pure v + +-- | Return a 'CacheKey' for a 'Request', if it's cacheable +-- +-- A 'Request' is cacheable if all are true: +-- +-- - The given predicate succeeds +-- - The method is @GET@ +-- - A @Cache-Control@ header with @no-store@ is not present +-- +-- If cacheable, the 'CacheKey' is built from: method, scheme, host, port, path, +-- query + any @Vary@ headers. +getCachableRequestKey + :: HttpCacheSettings m t -> Request -> Maybe CacheKey +getCachableRequestKey settings req = do + guard $ settings.cacheable req + guard $ HTTP.method req == "GET" + guard $ NoStore `notElem` requestHeaders.cacheControl + guard $ not settings.shared || Private `notElem` requestHeaders.cacheControl + pure $ md5CacheKey cacheKeyAttributes + where + requestHeaders = getRequestHeaders req + + cacheKeyAttributes = + ( HTTP.method req + , HTTP.secure req + , HTTP.host req + , HTTP.port req + , HTTP.path req + , HTTP.queryString req + , concatMap (`getRequestHeader` req) requestHeaders.vary + ) + +-- | Return a 'CacheTTL' for a 'Response', if it's cacheable +-- +-- A 'Response' is cacheable if all are true: +-- +-- - A @Cache-Control@ header with @no-store@ is not present +-- - If the cache is shared (first argument), a @Cache-Control@ header with +-- @private@ is not preset +-- - The response has a cacheable status code +-- +-- If cacheable, the @Cache-Control[max-age]@, @Age@, and @Expires@ response +-- headers are used to compute the 'CacheTTL'. +getCachableResponseTTL + :: HttpCacheSettings m t -> Response body -> Maybe CacheTTL +getCachableResponseTTL settings resp = do + guard $ NoStore `notElem` responseHeaders.cacheControl + guard $ + not settings.shared || Private `notElem` responseHeaders.cacheControl + guard $ statusIsCacheable $ HTTP.responseStatus resp + pure $ fromMaybe settings.defaultTTL $ responseHeadersToTTL responseHeaders + where + responseHeaders = getResponseHeaders resp + +statusIsCacheable :: Status -> Bool +statusIsCacheable = (`elem` cacheableStatusCodes) . statusCode + +-- | As per RFC 7231 +-- +-- +cacheableStatusCodes :: [Int] +cacheableStatusCodes = + [ 200 -- OK + , 203 -- Non-Authoritative Information + , 204 -- No Content + , 206 -- Partial Content + , 300 -- Multiple Choices + , 301 -- Moved Permanently + , 404 -- Not Found + , 405 -- Method Not Allowed + , 410 -- Gone + , 414 -- URI Too Long + , 501 -- Not Implemented + ] + +newtype Seconds = Seconds {unwrap :: Int} + deriving stock (Eq) + deriving newtype (Num, Show, Read) + +data CacheControl + = Private + | NoStore + | MaxAge Seconds + deriving stock (Eq, Show) + +cacheControlMaxAge :: [CacheControl] -> Maybe Seconds +cacheControlMaxAge = firstJust $ \case + MaxAge s -> Just s + _ -> Nothing + +readCacheControl :: ByteString -> Maybe CacheControl +readCacheControl = go . CI.foldCase + where + go = \case + "private" -> Just Private + "no-store" -> Just NoStore + h | Just s <- BS8.stripPrefix "max-age=" h -> MaxAge <$> readMaybe (BS8.unpack s) + _ -> Nothing + +getCacheControl :: HasHeaders a => a -> [CacheControl] +getCacheControl = mapMaybe readCacheControl . getHeaderCsv hCacheControl + +setCacheControlFrom :: Response a -> Response b -> Response b +setCacheControlFrom from to = + to + { HTTP.responseHeaders = toNonCCHeader <> fromCCHeader + } + where + fromCCHeader = filter ((== hCacheControl) . fst) $ getHeaders from + toNonCCHeader = filter ((/= hCacheControl) . fst) $ getHeaders to + +data RequestHeaders = RequestHeaders + { cacheControl :: [CacheControl] + , vary :: [HeaderName] + } + +getRequestHeaders :: Request -> RequestHeaders +getRequestHeaders req = + RequestHeaders + { cacheControl = getCacheControl req + , vary = map CI.mk $ concatMap splitHeader $ getRequestHeader hVary req + } + +data ResponseHeaders = ResponseHeaders + { cacheControl :: [CacheControl] + , age :: Seconds + -- ^ Defaults to 0 if missing + , expires :: Maybe UTCTime + } + +getResponseHeaders :: Response body -> ResponseHeaders +getResponseHeaders resp = + ResponseHeaders + { cacheControl = getCacheControl resp + , age = fromMaybe 0 $ do + h <- lookupHeader hAge resp + readMaybe $ BS8.unpack h + , expires = do + h <- lookupHeader hExpires resp + parseTimeM True defaultTimeLocale httpDateFormat $ BS8.unpack h + } + +-- | +httpDateFormat :: String +httpDateFormat = "%a, %d %b %Y %H:%M:%S GMT" + +responseHeadersToTTL :: ResponseHeaders -> Maybe CacheTTL +responseHeadersToTTL hs = cacheTTL . (.unwrap) <$> viaMaxAge <|> viaExpires + where + viaMaxAge = subtract hs.age <$> cacheControlMaxAge hs.cacheControl + viaExpires = round . utcTimeToPOSIXSeconds <$> hs.expires diff --git a/library/Freckle/App/Http/Cache/Gzip.hs b/library/Freckle/App/Http/Cache/Gzip.hs new file mode 100644 index 0000000..16f1a7b --- /dev/null +++ b/library/Freckle/App/Http/Cache/Gzip.hs @@ -0,0 +1,62 @@ +{-# LANGUAGE NoFieldSelectors #-} + +-- | Type and functions for handling gzipped HTTP responses +-- +-- In order to optimize caching of responses in storage with size limitations, +-- we cache gzipped responses as-is. This requires disabling the automatic +-- decompression of @http-client@ and handling it ourselves. +-- +-- The module makes that a type-enforced process: +-- +-- - 'requestPotentiallyGzipped' is the only way to get a 'PotentiallyGzipped' +-- - Which is the type needed for the response field in 'CachedResponse' +-- - 'gunzipResponseBody' is the only way to erase 'PotentiallyGzipped' +-- - Which is what you actually need to return +module Freckle.App.Http.Cache.Gzip + ( PotentiallyGzipped + , requestPotentiallyGzipped + , gunzipResponseBody + ) where + +import Prelude + +import Codec.Serialise (Serialise) +import Control.Monad.IO.Class +import Data.ByteString.Lazy qualified as BSL +import Freckle.App.Http (disableRequestDecompress) +import Freckle.App.Http.Header +import Network.HTTP.Client (Request, Response) +import Network.HTTP.Client.Internal qualified as HTTP + +newtype PotentiallyGzipped a = PotentiallyGzipped + { unwrap :: a + } + deriving stock (Show, Eq) + deriving newtype (Serialise) + +-- | Run a request /without/ automatic 'decompress' and tag the @body@ type +requestPotentiallyGzipped + :: Functor m + => (Request -> m (Response body)) + -> Request + -> m (Response (PotentiallyGzipped body)) +requestPotentiallyGzipped doHttp = + fmap (fmap PotentiallyGzipped) . doHttp . disableRequestDecompress + +-- | Gunzip a 'PotentiallyGzipped' body, if necessary +gunzipResponseBody + :: MonadIO m + => Request + -> Response (PotentiallyGzipped BSL.ByteString) + -> m (Response BSL.ByteString) +gunzipResponseBody req resp + | HTTP.needsGunzip req (getHeaders resp) = liftIO $ do + body <- gunzipBody $ HTTP.responseBody resp + pure $ body <$ resp + | otherwise = pure $ (.unwrap) <$> resp + +gunzipBody :: PotentiallyGzipped BSL.ByteString -> IO BSL.ByteString +gunzipBody body = do + body1 <- HTTP.constBodyReader $ BSL.toChunks body.unwrap + reader' <- HTTP.makeGzipReader body1 + BSL.fromChunks <$> HTTP.brConsume reader' diff --git a/library/Freckle/App/Http/Cache/Memcached.hs b/library/Freckle/App/Http/Cache/Memcached.hs new file mode 100644 index 0000000..a0a2934 --- /dev/null +++ b/library/Freckle/App/Http/Cache/Memcached.hs @@ -0,0 +1,151 @@ +{-# LANGUAGE CPP #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +module Freckle.App.Http.Cache.Memcached + ( memcachedHttpCacheSettings + , memcachedHttpCodec + , memcachedHttpCache + ) where + +import Prelude + +import Blammo.Logging (MonadLogger, logDebugNS, logWarnNS) +import Codec.Serialise (Serialise (..), deserialiseOrFail, serialise) +import Control.Exception.Annotated.UnliftIO (try) +import Control.Monad.IO.Class (liftIO) +import Control.Monad.Reader (MonadReader) +import Data.Bifunctor (bimap) +import Data.ByteString.Lazy qualified as BSL +import Data.CaseInsensitive (CI) +import Data.CaseInsensitive qualified as CI +import Data.Time (UTCTime, getCurrentTime) +import Database.Memcache.Types (Value) +import Freckle.App.Http.Cache +import Freckle.App.Memcached +import Freckle.App.Memcached.Client qualified as Memcached +import GHC.Generics (Generic) +import Network.HTTP.Client (Request) +import Network.HTTP.Client.Internal qualified as HTTP +import Network.HTTP.Types.Header (ResponseHeaders) +import Network.HTTP.Types.Status (Status (..)) +import Network.HTTP.Types.Version (HttpVersion (..)) +import OpenTelemetry.Trace.Monad (MonadTracer (..)) +import UnliftIO (MonadUnliftIO) + +memcachedHttpCacheSettings + :: ( MonadUnliftIO m + , MonadLogger m + , MonadTracer m + , MonadReader env m + , HasMemcachedClient env + ) + => CacheTTL + -- ^ Default TTL, used when @max-age@ is not present + -> HttpCacheSettings m Value +memcachedHttpCacheSettings defaultTTL = + HttpCacheSettings + { shared = True + , cacheable = const True + , defaultTTL + , getCurrentTime = liftIO getCurrentTime + , logDebug = logDebugNS "http.cache" + , logWarn = logWarnNS "http.cache" + , codec = memcachedHttpCodec + , cache = memcachedHttpCache + } + +memcachedHttpCodec :: HttpCacheCodec Value +memcachedHttpCodec = + HttpCacheCodec + { serialise = BSL.toStrict . serialise . fromResponse + , deserialise = \req -> + bimap show (toResponse req) + . deserialiseOrFail + . BSL.fromStrict + } + +memcachedHttpCache + :: ( MonadUnliftIO m + , MonadTracer m + , MonadReader env m + , HasMemcachedClient env + ) + => HttpCache m Value +memcachedHttpCache = + HttpCache + { get = try . Memcached.get + , set = \k v t -> try $ Memcached.set k v t + , evict = try . Memcached.delete + } + +-- | Representation of 'CachedResponse' that can be given a 'Serialise' instance +-- +-- In 'fromResponse' we need to flatten the 'Response' down and remove fields +-- that can't (or shouldn't) be cached, then restore them again later in +-- 'toResponse'. +data SerialiseResponse = SerialiseResponse + { sresponseStatus :: Status + , sresponseVersion :: HttpVersion + , sresponseHeaders :: ResponseHeaders + , sresponseBody :: PotentiallyGzipped BSL.ByteString + , sresponseEarlyHints :: ResponseHeaders + , sinserted :: UTCTime + , sttl :: CacheTTL + } + deriving stock (Generic) + deriving anyclass (Serialise) + +{- FOURMOLU_DISABLE -} +-- Fourmolu has trouble with this bit of CPP + +toResponse :: Request -> SerialiseResponse -> CachedResponse +toResponse req c = CachedResponse + { response = HTTP.Response + { HTTP.responseStatus = sresponseStatus c + , HTTP.responseVersion = sresponseVersion c + , HTTP.responseHeaders = sresponseHeaders c + , HTTP.responseBody = sresponseBody c + , HTTP.responseCookieJar = mempty + , HTTP.responseClose' = HTTP.ResponseClose (pure ()) + , HTTP.responseOriginalRequest = req +#if MIN_VERSION_http_client(0,7,16) + , HTTP.responseEarlyHints = sresponseEarlyHints c +#endif + } + , inserted = c.sinserted + , ttl = c.sttl + } + +fromResponse :: CachedResponse -> SerialiseResponse +fromResponse cr = + SerialiseResponse + { sresponseStatus = HTTP.responseStatus r + , sresponseVersion = HTTP.responseVersion r + , sresponseHeaders = HTTP.responseHeaders r + , sresponseBody = HTTP.responseBody r +#if MIN_VERSION_http_client(0,7,16) + , sresponseEarlyHints = HTTP.responseEarlyHints r +#else + , sresponseEarlyHints = [] +#endif + , sinserted = cr.inserted + , sttl = cr.ttl + } + where + r = cr.response + +#if !MIN_VERSION_http_types(0,12,4) +deriving stock instance Generic HttpVersion + +deriving stock instance Generic Status +#endif + +{- FOURMOLU_ENABLE -} + +deriving anyclass instance Serialise HttpVersion + +deriving anyclass instance Serialise Status + +instance (CI.FoldCase a, Serialise a) => Serialise (CI a) where + encode = encode . CI.original + decode = CI.mk <$> decode diff --git a/library/Freckle/App/Http/Cache/State.hs b/library/Freckle/App/Http/Cache/State.hs new file mode 100644 index 0000000..f2d11da --- /dev/null +++ b/library/Freckle/App/Http/Cache/State.hs @@ -0,0 +1,83 @@ +{-# LANGUAGE NoFieldSelectors #-} + +-- | HTTP caching via 'MonadState' +-- +-- This module implements HTTP caching for simple use-cases, such as testing +-- "Freckle.App.Http.Cache" itself. +module Freckle.App.Http.Cache.State + ( CachedResponse (..) + , Cache (..) + , HasCache (..) + , stateHttpCacheSettings + , stateHttpCacheCodec + , stateHttpCache + ) where + +import Prelude + +import Blammo.Logging (Message) +import Control.Lens (Lens', at, lens, use, (.=), (?=)) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Control.Monad.Logger (ToLogStr (..), fromLogStr) +import Control.Monad.State (MonadState) +import Data.HashMap.Strict (HashMap) +import Data.Text (Text) +import Data.Text.Encoding qualified as T +import Data.Text.Encoding.Error qualified as T +import Data.Text.IO qualified as T +import Data.Time (getCurrentTime) +import Freckle.App.Http.Cache +import Freckle.App.Memcached.CacheKey +import Freckle.App.Memcached.CacheTTL +import System.IO qualified as IO + +newtype Cache = Cache + { map :: HashMap CacheKey CachedResponse + } + deriving newtype (Semigroup, Monoid) + +mapL :: Lens' Cache (HashMap CacheKey CachedResponse) +mapL = lens (.map) $ \x y -> x {map = y} + +class HasCache env where + cacheL :: Lens' env Cache + +instance HasCache Cache where + cacheL = id + +stateHttpCacheSettings + :: ( MonadIO m + , MonadState s m + , HasCache s + ) + => HttpCacheSettings m CachedResponse +stateHttpCacheSettings = + HttpCacheSettings + { shared = False + , cacheable = const True + , defaultTTL = fiveMinuteTTL + , getCurrentTime = liftIO getCurrentTime + , logDebug = \_ -> pure () + , logWarn = liftIO . T.hPutStrLn IO.stderr . messageToText + , codec = stateHttpCacheCodec + , cache = stateHttpCache + } + +stateHttpCacheCodec :: HttpCacheCodec CachedResponse +stateHttpCacheCodec = + HttpCacheCodec + { serialise = id + , deserialise = const Right + } + +stateHttpCache + :: (MonadIO m, MonadState s m, HasCache s) => HttpCache m CachedResponse +stateHttpCache = + HttpCache + { get = \key -> fmap Right $ use $ cacheL . mapL . at key + , set = \key resp _ -> fmap Right $ cacheL . mapL . at key ?= resp + , evict = \key -> fmap Right $ cacheL . mapL . at key .= Nothing + } + +messageToText :: Message -> Text +messageToText = T.decodeUtf8With T.lenientDecode . fromLogStr . toLogStr diff --git a/library/Freckle/App/Http/Header.hs b/library/Freckle/App/Http/Header.hs new file mode 100644 index 0000000..055d041 --- /dev/null +++ b/library/Freckle/App/Http/Header.hs @@ -0,0 +1,47 @@ +module Freckle.App.Http.Header + ( HasHeaders (..) + , getHeaderCsv + , lookupHeader + + -- * Utilities + , splitHeader + ) where + +import Prelude + +import Data.ByteString (ByteString) +import Data.ByteString.Char8 qualified as BS8 +import Data.Char (isSpace) +import Data.Maybe (listToMaybe) +import Network.HTTP.Client (Request, Response, requestHeaders, responseHeaders) +import Network.HTTP.Simple (getRequestHeader, getResponseHeader) +import Network.HTTP.Types.Header (Header, HeaderName) + +class HasHeaders a where + getHeaders :: a -> [Header] + + getHeader :: HeaderName -> a -> [ByteString] + getHeader h = map snd . filter ((== h) . fst) . getHeaders + +instance HasHeaders [Header] where + getHeaders = id + +instance HasHeaders Request where + getHeaders = requestHeaders + getHeader = getRequestHeader + +instance HasHeaders (Response body) where + getHeaders = responseHeaders + getHeader = getResponseHeader + +getHeaderCsv :: HasHeaders a => HeaderName -> a -> [ByteString] +getHeaderCsv hn = concatMap splitHeader . getHeader hn + +splitHeader :: ByteString -> [ByteString] +splitHeader = map trimSpace . BS8.split ',' + +trimSpace :: ByteString -> ByteString +trimSpace = BS8.dropWhile isSpace . BS8.dropWhileEnd isSpace + +lookupHeader :: HasHeaders a => HeaderName -> a -> Maybe ByteString +lookupHeader h = listToMaybe . getHeader h diff --git a/library/Freckle/App/Http/Paginate.hs b/library/Freckle/App/Http/Paginate.hs new file mode 100644 index 0000000..2573236 --- /dev/null +++ b/library/Freckle/App/Http/Paginate.hs @@ -0,0 +1,101 @@ +-- | Streaming interface for paginated HTTP APIs +-- +-- == Examples +-- +-- Take an action on each page as it is requested: +-- +-- @ +-- let req = parseRequest_ "https://..." +-- +-- runConduit +-- $ sourcePaginated httpJson req +-- .| mapM_C onEachPage +-- +-- onEachPage :: Response (Either HttpDecodeError [MyJsonThing]) -> m () +-- onEachPage = undefined +-- @ +-- +-- Take and action /and/ collect: +-- +-- @ +-- allPages <- runConduit +-- $ 'sourcePaginated' httpJson req +-- .| iterM onEachPage +-- .| sinkList +-- @ +-- +-- For APIs that do pagination not via @Link@, you can use 'sourcePaginatedBy' +-- +-- @ +-- data Page a = Page +-- { pData :: [a] +-- , pNext :: Int +-- } +-- +-- instance FromJSON a => FromJSON (Item a) where +-- parseJSON = withObject "Page" $ \o -> Page +-- <$> o .: "data" +-- <*> o .: "next" +-- +-- runConduit +-- $ 'sourcePaginatedBy' nextPage httpJson req +-- .| mapMC (fmap pData . 'getResponseBodyUnsafe') +-- .| foldC +-- +-- nextPage +-- :: Request +-- -> Response (Either ('HttpDecodeError' String) (Page a)) +-- -> Maybe Request +-- nextPage req resp = do +-- body <- hush $ getResponseBody resp +-- let next = C8.pack $ show $ pNext body +-- pure $ addToRequestQueryString [("next", Just next)] req +-- @ +module Freckle.App.Http.Paginate + ( sourcePaginated + , sourcePaginatedBy + ) where + +import Prelude + +import Conduit +import Control.Error.Util (hush) +import Data.Foldable (traverse_) +import Data.List (find) +import Data.Maybe (listToMaybe) +import Data.Text.Encoding (decodeUtf8) +import Network.HTTP.Link hiding (linkHeader) +import Network.HTTP.Simple +import Network.URI (URI) + +-- | Stream pages of a paginated response, using @Link@ to find next pages +sourcePaginated + :: Monad m + => (Request -> m (Response body)) + -- ^ Run one request + -> Request + -- ^ Initial request + -> ConduitT i (Response body) m () +sourcePaginated = sourcePaginatedBy linkHeader + +-- | Stream pages of a paginated response, using a custom /find next/ +sourcePaginatedBy + :: Monad m + => (Request -> Response body -> Maybe Request) + -- ^ How to get the next page from each request + -> (Request -> m (Response body)) + -- ^ Run one request + -> Request + -- ^ Initial request + -> ConduitT i (Response body) m () +sourcePaginatedBy mNextRequest runRequest req = do + resp <- lift $ runRequest req + yield resp + traverse_ (sourcePaginatedBy mNextRequest runRequest) $ mNextRequest req resp + +linkHeader :: Request -> Response body -> Maybe Request +linkHeader _req resp = do + header <- listToMaybe $ getResponseHeader "Link" resp + links <- hush $ parseLinkHeader' @URI $ decodeUtf8 header + uri <- href <$> find (((Rel, "next") `elem`) . linkParams) links + parseRequest $ show uri diff --git a/library/Freckle/App/Http/Retry.hs b/library/Freckle/App/Http/Retry.hs new file mode 100644 index 0000000..a75a2f2 --- /dev/null +++ b/library/Freckle/App/Http/Retry.hs @@ -0,0 +1,101 @@ +module Freckle.App.Http.Retry + ( RetriesExhausted (..) + , rateLimited + , rateLimited' + ) where + +import Prelude + +import Control.Exception.Annotated.UnliftIO (Exception (..), throwWithCallStack) +import Control.Monad (guard, unless) +import Control.Monad.IO.Class (MonadIO) +import Control.Retry +import Data.ByteString.Char8 qualified as BS8 +import Data.Functor (void) +import Data.Maybe (listToMaybe) +import GHC.Stack (HasCallStack) +import Network.HTTP.Client (Request (..)) +import Network.HTTP.Simple +import Network.HTTP.Types.Status (status429) +import Text.Read (readMaybe) + +-- | Thrown if we exhaust our retries limit and still see a @429@ +-- +-- This typically means the API is not sending back accurate @Retry-In@ values +-- with 429 responses. +-- +-- __Rationale__: +-- +-- In order for 'rateLimited' to function in the case when the 'Request' is +-- using 'throwErrorStatusCodes' for 'checkResponse', we have to modify it to +-- not throw on 429s specifically. Otherwise, the first response would just +-- throw due to 4XX and never retry. However, in that case of someone expecting +-- invalid statuses to throw an exception, if we exhaust our retries and still +-- see a 429 at the end, an exception should be thrown. +-- +-- Unfortunately, it's not possible to reuse the user-defined 'checkResponse' in +-- order to throw a uniform 'HttpException' in this case; so we throw this +-- ourselves instead. +data RetriesExhausted = RetriesExhausted + { reLimit :: Int + , reResponse :: Response () + } + deriving stock (Show) + +instance Exception RetriesExhausted where + displayException RetriesExhausted {..} = + "Retries exhaused after " + <> show reLimit + <> " attempts. Final response:\n" + <> show reResponse + +rateLimited + :: MonadIO m => (Request -> m (Response body)) -> Request -> m (Response body) +rateLimited = rateLimited' 10 + +-- | 'rateLimited' but with configurable retry limit +rateLimited' + :: MonadIO m + => Int + -> (Request -> m (Response body)) + -> Request + -> m (Response body) +rateLimited' retryLimit f req = do + resp <- + retryingDynamic + (limitRetries retryLimit) + ( \_ -> + pure + . maybe DontRetry (ConsultPolicyOverrideDelay . microseconds) + . getRetryAfter + ) + (\_ -> f $ suppressRetryStatusError req) + + checkRetriesExhausted retryLimit resp + +suppressRetryStatusError :: Request -> Request +suppressRetryStatusError req = + req + { checkResponse = \req' resp -> + unless (getResponseStatus resp == status429) $ + originalCheckResponse req' resp + } + where + originalCheckResponse = checkResponse req + +checkRetriesExhausted + :: (MonadIO m, HasCallStack) => Int -> Response body -> m (Response body) +checkRetriesExhausted retryLimit resp + | getResponseStatus resp == status429 = + throwWithCallStack $ + RetriesExhausted {reLimit = retryLimit, reResponse = void resp} + | otherwise = pure resp + +getRetryAfter :: Response body -> Maybe Int +getRetryAfter resp = do + guard $ getResponseStatus resp == status429 + header <- listToMaybe $ getResponseHeader "Retry-After" resp + readMaybe $ BS8.unpack header + +microseconds :: Int -> Int +microseconds = (* 1000000) diff --git a/library/Freckle/App/Test/Http.hs b/library/Freckle/App/Test/Http.hs new file mode 100644 index 0000000..9949688 --- /dev/null +++ b/library/Freckle/App/Test/Http.hs @@ -0,0 +1,364 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE NoFieldSelectors #-} + +-- | Implements stubbing of an HTTP request function +module Freckle.App.Test.Http + ( -- $docs + httpStubbed + + -- * Defining stubs + , HttpStub (..) + , httpStub + , httpStubUrl + + -- * Stub modifiers + , labelL + , MatchRequest (..) + , matchL + + -- * Response modifiers + , statusL + , headersL + , bodyL + + -- * Response helpers + , json + + -- * FileSystem stubs + , loadHttpStubsDirectory + + -- * Exception + , NoStubsMatched(..) + + -- * 'MonadHttp' instances + + -- ** For use with @DerivingVia@ + , HasHttpStubs (..) + , ReaderHttpStubs (..) + + -- ** Concrete transformer + , HttpStubsT + , runHttpStubsT + ) where + +import Prelude + +import Control.Applicative (asum) +import Control.Exception (Exception (..)) +import Control.Lens (Lens', lens, view, (.~), (<>~)) +import Control.Monad.Reader (MonadReader, ReaderT, runReaderT) +import Data.Aeson (ToJSON, encode) +import Data.Bifunctor (bimap) +import Control.Monad(filterM) +import Control.Monad.IO.Class (MonadIO) +import Data.ByteString.Lazy qualified as BSL +import Data.Either (partitionEithers) +import Data.Function ((&)) +import Data.List (stripPrefix) +import Data.Maybe (mapMaybe) +import Data.String (IsString) +import Data.String qualified +import Data.Traversable (for) +import Control.Exception.Annotated.UnliftIO (throwWithCallStack) +import Freckle.App.Http (MonadHttp (..)) +import Freckle.App.Test.Http.MatchRequest +import GHC.Stack (HasCallStack) +import Network.HTTP.Client (Request, Response) +import Network.HTTP.Client.Internal qualified as HTTP +import Network.HTTP.Types.Header (ResponseHeaders, hAccept, hContentType) +import Network.HTTP.Types.Status (Status, status200) +import Safe (headMay) +import System.Directory (doesFileExist) +import System.FilePath (addTrailingPathSeparator) +import System.FilePath.Glob (globDir1) + +-- | Respond to a 'Request' with the first 'HttpStub' to match +-- +-- If no stubs match, 'throwWithCallStack' is used. If you'd rather experience +-- a 404, add a final stub for any request that does that: +-- +-- @ +-- stubs :: ['HttpStub'] +-- stubs = +-- [ -- ... +-- , -- ... +-- , 'httpStub' \"Anything\" 'MatchAnything' +-- & 'statusL' .~ 'status404' +-- & 'bodyL' .~ \"Not found\" +-- ] +-- @ +httpStubbed + :: (MonadIO m, HasCallStack) + => [HttpStub] + -> Request + -> m (Response BSL.ByteString) +httpStubbed stubs req = + maybe + (throwWithCallStack NoStubsMatched{req, unmatched}) + (pure . toResponse req) + $ headMay matched + where + (unmatched, matched) = + partitionEithers + $ map + ( \stub -> + bimap (stub,) (const stub.response) + $ matchRequest req stub.match + ) + stubs + +data NoStubsMatched = NoStubsMatched + { req :: Request + , unmatched :: [(HttpStub, String)] + } + +instance Show NoStubsMatched where + show = displayException + + +instance Exception NoStubsMatched where + displayException NoStubsMatched {req, unmatched} = + "No stubs were found that matched:\n" + <> show req + <> "\n" + <> ( + if length unmatched < 4 + then concatMap (uncurry unmatchedMessage) unmatched + else "\nNumber of stubs: " <> show (length unmatched) + ) + where + unmatchedMessage stub err = "\n== " <> stub.label <> " ==\n" <> err + +-- | Fields that can be defined for a response +data HttpStubResponse = HttpStubResponse + { status :: Status + , headers :: ResponseHeaders + , body :: BSL.ByteString + } + +toResponse :: Request -> HttpStubResponse -> Response BSL.ByteString +toResponse req stub = + HTTP.Response + { HTTP.responseStatus = stub.status + , HTTP.responseVersion = HTTP.requestVersion req + , HTTP.responseHeaders = stub.headers + , HTTP.responseBody = stub.body + , HTTP.responseCookieJar = mempty + , HTTP.responseClose' = HTTP.ResponseClose $ pure () + , HTTP.responseOriginalRequest = req +#if MIN_VERSION_http_client(0,7,16) + , HTTP.responseEarlyHints = [] +#endif + } + +rstatusL :: Lens' HttpStubResponse Status +rstatusL = lens (.status) $ \x y -> x {status = y} + +rheadersL :: Lens' HttpStubResponse ResponseHeaders +rheadersL = lens (.headers) $ \x y -> x {headers = y} + +rbodyL :: Lens' HttpStubResponse BSL.ByteString +rbodyL = lens (.body) $ \x y -> x {body = y} + +data HttpStub = HttpStub + { label :: String + , match :: MatchRequest + , response :: HttpStubResponse + } + +instance IsString HttpStub where + fromString = httpStubUrl + +labelL :: Lens' HttpStub String +labelL = lens (.label) $ \x y -> x {label = y} + +matchL :: Lens' HttpStub MatchRequest +matchL = lens (.match) $ \x y -> x {match = y} + +responseL :: Lens' HttpStub HttpStubResponse +responseL = lens (.response) $ \x y -> x {response = y} + +-- | Respond 200 with empty body for matching requests +httpStub :: String -> MatchRequest -> HttpStub +httpStub label match = HttpStub {label, match, response} + where + response = + HttpStubResponse + { status = status200 + , headers = [] + , body = "" + } + +-- | Respond 200 with empty body for requests parsed from the given URL +httpStubUrl :: String -> HttpStub +httpStubUrl url = httpStub url $ matchRequestFromUrl url + +statusL :: Lens' HttpStub Status +statusL = responseL . rstatusL + +headersL :: Lens' HttpStub ResponseHeaders +headersL = responseL . rheadersL + +bodyL :: Lens' HttpStub BSL.ByteString +bodyL = responseL . rbodyL + +-- | Modify the stub to match JSON requests and respond with the given value +json :: ToJSON a => a -> HttpStub -> HttpStub +json a stub = + stub + & matchL <>~ MatchHeader (hAccept, "application/json") + & headersL <>~ [(hContentType, "application/json")] + & bodyL .~ encode a + +-- | Load stubs from the filesystem +-- +-- Within the given directory, files are expected to be named for scheme, then +-- host, then path\/port\/query. +-- +-- Given, +-- +-- @ +-- files/ +-- https/ +-- www.example.com/ +-- hello => "Hello" +-- world => "World" +-- http/ +-- localhost:3000/ +-- hello?world=1 => "Hello 2" +-- @ +-- +-- Then @'loadHttpStubsDirectory' "files"@ is equivalent to, +-- +-- @ +-- [ 'stubUrl' \"https:\/\/www.example.com\/hello\" & 'bodyL' .~ \"Hello\" +-- , 'stubUrl' \"https:\/\/www.example.com\/world\" & 'bodyL' .~ \"World\" +-- , 'stubUrl' \"http:\/\/localhost:3000\/hello?world=1\" & 'bodyL' .~ \"Hello 2\" +-- ] +-- @ +-- +-- NB. This function currently abuses the fact that @/@ within filenames is the +-- same for URLs, and so will not work on Windows. Patches welcome. +loadHttpStubsDirectory :: FilePath -> IO [HttpStub] +loadHttpStubsDirectory dir = do + paths <- filterM doesFileExist =<< globDir1 "**/*" dir + + let pathUrls = mapMaybe (\p -> (,) p <$> toUrl p) paths + + for pathUrls $ \(path, url) -> do + bs <- BSL.readFile path + pure $ httpStubUrl url & bodyL .~ bs + where + toUrl p = do + relative <- stripPrefix (addTrailingPathSeparator dir) p + asum + [ ("https://" <>) <$> stripPrefix "https/" relative + , ("http://" <>) <$> stripPrefix "http/" relative + ] + +class HasHttpStubs env where + httpStubsL :: Lens' env [HttpStub] + +instance HasHttpStubs [HttpStub] where + httpStubsL = id + +newtype ReaderHttpStubs m a = ReaderHttpStubs {unwrap :: m a} + deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader env) + +instance (MonadReader env m, HasHttpStubs env, MonadIO m) => MonadHttp (ReaderHttpStubs m) where + httpLbs req = do + stubs <- view httpStubsL + httpStubbed stubs req + +newtype HttpStubsT m a = HttpStubsT {unwrap :: ReaderT [HttpStub] m a} + deriving newtype (Functor, Applicative, Monad, MonadReader [HttpStub]) + deriving (MonadIO, MonadHttp) via ReaderHttpStubs (HttpStubsT m) + +runHttpStubsT :: HttpStubsT m a -> [HttpStub] -> m a +runHttpStubsT f = runReaderT f.unwrap + +-- $docs +-- +-- Stubbing is accomplished by holding a list of 'HttpStub' somewhere, which +-- defines how to respond to requests that match. The simplest way to do so +-- is to use the 'IsString' instance: +-- +-- > stubs :: [HttpStub] +-- > stubs = +-- > [ "https://example.com" +-- > ] +-- +-- You can now use, +-- +-- @ +-- 'httpStubbed' stubs :: Request -> Response ByteString +-- @ +-- +-- Anywhere you need an HTTP requesting function and it will respond 200 with an +-- empty body for any @GET@ requests made to this domain. +-- +-- Stubbed responses can be modified through lenses: +-- +-- > stubs :: [HttpStub] +-- > stubs = +-- > [ "https://example.com" +-- > & statusL .~ status400 +-- > & bodyL .~ "Let's test a Bad Request" +-- > ] +-- +-- The string is passed to 'parseRequest_', so anything valid there is valid +-- here, such as setting the method: +-- +-- > data MyItem = MyItem +-- > { -- ... +-- > } +-- > deriving stock Generic +-- > deriving anyclass ToJSON +-- > +-- > stubs :: [HttpStub] +-- > stubs = +-- > [ "POST https://example.com/items" +-- > & json [MyItem] +-- > -- ^ Now matches requests with JSON in the Accept Header only +-- > -- Responds with Content-Type JSON +-- > -- Responds with a body of the JSON-encoded items +-- > ] +-- +-- == 'MonadHttp' +-- +-- Once we have the @stubs@, we can set up a 'MonadHttp' context that uses it: +-- +-- > data TestApp = TestApp +-- > { appHttpStubs :: [HttpStubs] +-- > } +-- > +-- > -- Assume TestAppT is a ReaderT TestApp +-- > instance MonadHttp (TestAppT m a) where +-- > httpLbs req = do +-- > stubs <- asks appHttpStubs +-- > pure $ httpStubbed stubs req +-- +-- Additionally, there are tools for @DerivingVia@ or running things in a +-- concrete 'HttpStubsT' stack. +-- +-- == Handling Un-stubbed Requests +-- +-- When no stubs match a given request, we call 'error' -- this seems uncouth, +-- but is actually the best possible behavior for the intended use-case in +-- (e.g.) HSpec: +-- +-- ![Error screenshot](https://files.pbrisbin.com/screenshots/screenshot.281851.png) +-- +-- One other reasonable behavior would be to respond 404 to any un-matched +-- requests. This can be accomplished by adding a "match anything" stub at the +-- end: +-- +-- > stubs :: [HttpStub] +-- > stubs = +-- > [ -- ... +-- > , -- ... +-- > , httpStub "Anything" MatchAnything +-- > & statusL .~ status404 +-- > & bodyL .~ "Not found" +-- > ] diff --git a/library/Freckle/App/Test/Http/MatchRequest.hs b/library/Freckle/App/Test/Http/MatchRequest.hs new file mode 100644 index 0000000..51de98d --- /dev/null +++ b/library/Freckle/App/Test/Http/MatchRequest.hs @@ -0,0 +1,186 @@ +-- | 'Request' predicates for matching 'HttpStub's +-- +-- == Usage +-- +-- @ +-- stubs :: ['HttpStub'] +-- stubs = +-- [ \"https://example.com\" +-- & 'matchL' <>~ 'MatchMethod' \"POST\" +-- & 'matchL' <>~ 'MatchHeaders' [(hAccept, \"text/plain+csv\")] +-- & 'matchL' <>~ 'MatchBody' \"id,name\n42,Pat\n\" +-- & 'statusL' .~ 'status201' +-- & 'bodyL' .~ \"OK\n\" +-- ] +-- @ +module Freckle.App.Test.Http.MatchRequest + ( MatchRequest (..) + , matchRequestFromUrl + , matchRequest + , showMatchRequest + , showMatchRequestWithMismatches + ) where + +import Prelude + +import Control.Applicative ((<|>)) +import Control.Monad (guard) +import Data.ByteString (ByteString) +import Data.ByteString.Char8 qualified as BS8 +import Data.ByteString.Lazy qualified as BSL +import Data.List (isPrefixOf) +import Data.List.NonEmpty (NonEmpty ((:|))) +import Data.List.NonEmpty qualified as NE +import Data.Maybe (catMaybes) +import Data.Semigroup.Foldable (fold1) +import Network.HTTP.Client (Request, RequestBody (..), parseRequest_) +import Network.HTTP.Client.Internal qualified as HTTP +import Network.HTTP.Types.Header (Header, RequestHeaders) +import Network.HTTP.Types.Method (Method) + +data MatchRequest + = MatchAnything + | MatchAnd MatchRequest MatchRequest + | MatchMethod Method + | MatchSecure Bool + | MatchHost ByteString + | MatchPort Int + | MatchPath ByteString + | MatchQuery ByteString + | MatchHeaders RequestHeaders + | MatchHeader Header + | MatchBody ByteString + deriving stock (Show) + +instance Semigroup MatchRequest where + a <> b = MatchAnd a b + +matchRequestFromUrl :: String -> MatchRequest +matchRequestFromUrl url = + fold1 $ maybe id (<>) optionalMatches requiredMatches + where + req = parseRequest_ url + + method = HTTP.method req + secure = HTTP.secure req + host = HTTP.host req + port = HTTP.port req + path = HTTP.path req + query = HTTP.queryString req + headers = HTTP.requestHeaders req + body = simplifyRequestBody req + + requiredMatches = MatchMethod method :| [MatchSecure secure, MatchPort port] + + optionalMatches = + NE.nonEmpty $ + catMaybes + [ MatchHost host <$ guard (host /= "") + , MatchPath path <$ guard (hasExplicitPath secure host port url) + , MatchQuery query <$ guard (query /= "") + , MatchHeaders headers <$ guard (not $ null headers) + , MatchBody body <$ guard (body /= "") + ] + +hasExplicitPath :: Bool -> ByteString -> Int -> String -> Bool +hasExplicitPath secure host port url = + any + (any ((`isPrefixOf` url) . toUrlPrefix)) + [ [Just port] + , Nothing <$ guard (secure && port == 443) + , Nothing <$ guard (not secure && port == 80) + ] + where + toUrlPrefix mport = + mconcat + [ "http" + , if secure then "s" else "" + , "://" + , BS8.unpack host + , maybe "" ((":" <>) . show) mport + , "/" + ] + +-- | Match a 'Request' +-- +-- Success is @'Right' ()@, failure is a message in 'Left'. +matchRequest :: Request -> MatchRequest -> Either String () +matchRequest req mr = + maybe (Right ()) (Left . showMatchRequestWithMismatches mr) $ + buildMismatch req mr + +showMatchRequest :: MatchRequest -> String +showMatchRequest mr = + "MatchRequest {" + <> concatMap (("\n " <>) . show) (flattenMatchRequest mr) + <> "\n}" + <> "\n" + +showMatchRequestWithMismatches :: MatchRequest -> NonEmpty String -> String +showMatchRequestWithMismatches mr mismatches = + showMatchRequest mr + <> "\nMismatches {" + <> concatMap ("\n " <>) mismatches + <> "\n}" + <> "\n" + +flattenMatchRequest :: MatchRequest -> [MatchRequest] +flattenMatchRequest = \case + MatchAnd a b -> flattenMatchRequest a <> flattenMatchRequest b + x -> [x] + +buildMismatch :: Request -> MatchRequest -> Maybe (NonEmpty String) +buildMismatch req = \case + MatchAnything -> Nothing + MatchAnd a b -> buildMismatch req a <|> buildMismatch req b + MatchMethod m -> propMismatch "!=" (==) "method" m HTTP.method req + MatchSecure s -> propMismatch "!=" (==) "secure" s HTTP.secure req + MatchHost h -> propMismatch "!=" (==) "host" h HTTP.host req + MatchPort p -> propMismatch "!=" (==) "port" p HTTP.port req + MatchPath p -> propMismatch "!=" (==) "path" p (ensureLeadingSlash . HTTP.path) req + MatchQuery q -> propMismatch "!=" (==) "query" q HTTP.queryString req + MatchHeaders hs -> propMismatch "!=" (==) "headers" hs HTTP.requestHeaders req + MatchHeader h -> propMismatch "not in" elem "header" h HTTP.requestHeaders req + MatchBody bs -> propMismatch "!=" (==) "body" bs simplifyRequestBody req + +propMismatch + :: (Show a, Show b) + => String + -- ^ Label to show infix when comparison fails, e.g. "!=" + -> (a -> b -> Bool) + -- ^ How to compare values + -> String + -- ^ Label for the property itself + -> a + -- ^ Value to compare to property + -> (Request -> b) + -- ^ Function to get property from 'Request' + -> Request + -> Maybe (NonEmpty String) +propMismatch opLabel op propLabel a f req + | a `op` b = Nothing + | otherwise = Just $ pure msg + where + b = f req + msg = + "✗ " + <> propLabel + <> ": " + <> show a + <> " " + <> opLabel + <> " " + <> show b + +simplifyRequestBody :: Request -> ByteString +simplifyRequestBody = go . HTTP.requestBody + where + go = \case + RequestBodyLBS lbs -> BSL.toStrict lbs + RequestBodyBS bs -> bs + _ -> "" + +ensureLeadingSlash :: ByteString -> ByteString +ensureLeadingSlash bs + | Just ('/', _) <- BS8.uncons bs = bs + | otherwise = BS8.cons '/' bs diff --git a/package.yaml b/package.yaml index 731dbb0..4318d2d 100644 --- a/package.yaml +++ b/package.yaml @@ -1,15 +1,10 @@ -name: haskell-library-template -version: 0.0.0.0 +name: freckle-http +version: 0.3.0.1 maintainer: Freckle Education -category: Utils -github: freckle/haskell-library-template -synopsis: Short synopsis here -description: | - Longer description here - - Haddocks are more valuable if you reproduce the main README example here, but - you can also just direct users to the README if you prefer not to take on that - duplication burden. +category: HTTP +github: freckle/freckle-http +synopsis: Toolkit for making HTTP requests +description: Please see README.md extra-doc-files: - README.md @@ -18,14 +13,20 @@ extra-doc-files: extra-source-files: - package.yaml +language: GHC2021 + ghc-options: + - -fignore-optim-changes + - -fwrite-ide-info - -Weverything - -Wno-all-missed-specialisations - - -Wno-missed-specialisations - -Wno-missing-exported-signatures # re-enables missing-signatures - -Wno-missing-import-lists + - -Wno-missing-kind-signatures - -Wno-missing-local-signatures + - -Wno-missing-safe-haskell-mode - -Wno-monomorphism-restriction + - -Wno-prepositive-qualified-module - -Wno-safe - -Wno-unsafe @@ -34,61 +35,78 @@ when: ghc-options: - -Wno-missing-role-annotations - -Wno-missing-poly-kind-signatures - - condition: "impl(ghc >= 9.2)" - ghc-options: - - -Wno-missing-kind-signatures - - condition: "impl(ghc >= 8.10)" - ghc-options: - - -Wno-missing-safe-haskell-mode - - -Wno-prepositive-qualified-module - - condition: "impl(ghc >= 8.8)" - ghc-options: - - -fwrite-ide-info dependencies: - base < 5 default-extensions: - - BangPatterns - DataKinds - DeriveAnyClass - - DeriveFoldable - - DeriveFunctor - - DeriveGeneric - - DeriveLift - - DeriveTraversable + - DerivingVia - DerivingStrategies - - FlexibleContexts - - FlexibleInstances + - DuplicateRecordFields - GADTs - - GeneralizedNewtypeDeriving - LambdaCase - - MultiParamTypeClasses - NoImplicitPrelude - NoMonomorphismRestriction + - OverloadedRecordDot - OverloadedStrings - - RankNTypes - RecordWildCards - - ScopedTypeVariables - - StandaloneDeriving - - TypeApplications - TypeFamilies library: - source-dirs: src + source-dirs: library + dependencies: + - Glob + - Blammo + - aeson + - annotated-exception + - bytestring + - case-insensitive + - conduit + - directory + - errors + - extra + - filepath + - freckle-memcached + - hs-opentelemetry-api + - http-client + - http-conduit >= 2.3.5 # addToRequestQueryString + - http-link-header + - http-types + - lens + - memcache + - monad-logger + - monad-validate + - mtl + - network-uri + - retry >= 0.8.1.0 # retryingDynamic + - safe + - serialise + - semigroupoids + - text + - time + - transformers + - unliftio + - unordered-containers tests: spec: - main: Spec.hs - source-dirs: test + main: Main.hs + source-dirs: tests ghc-options: -threaded -rtsopts "-with-rtsopts=-N" dependencies: - #- haskell-library-template - - hspec - - hspec-junit-formatter - - readme: - main: README.lhs - ghc-options: -pgmL markdown-unlit - build-tools: - - markdown-unlit + - aeson + - bytestring + - freckle-http + - freckle-prelude + - hspec >= 2.8.1 + - hspec-expectations-json + - hspec-expectations-lifted + - http-types + - lens + - lens-aeson + - mtl + - time + - unordered-containers + - zlib diff --git a/stack-lts12.yaml b/stack-lts12.yaml deleted file mode 100644 index 935f2f2..0000000 --- a/stack-lts12.yaml +++ /dev/null @@ -1,15 +0,0 @@ -resolver: lts-12.26 - -extra-deps: - - call-stack-0.2.0 - - hspec-2.10.0 - - hspec-api-2.10.0 - - hspec-core-2.10.0 - - hspec-discover-2.10.0 - - hspec-golden-0.2.1.0 - - hspec-junit-formatter-1.1.2.1 - - QuickCheck-2.14.2@sha256:4ce29211223d5e6620ebceba34a3ca9ccf1c10c0cf387d48aea45599222ee5aa,7736 - - random-1.2.1@sha256:8bee24dc0c985a90ee78d94c61f8aed21c49633686f0f1c14c5078d818ee43a2,6598 - - regex-base-0.94.0.2@sha256:3a76c313f9f75e8e0b3c103c1bff5bbaf754da30cbddedc1d5b7061d001030e0 - - regex-tdfa-1.3.2.2@sha256:cdf462f6280804baa264e42e3e328ba02ec71d9335de706f3dee778890a25c0e,7042 - - splitmix-0.1.0.4@sha256:714a55fd28d3e2533bd5b49e74f604ef8e5d7b06f249c8816f6c54aed431dcf1,6483 diff --git a/stack-lts14.yaml b/stack-lts14.yaml deleted file mode 100644 index 7615a6f..0000000 --- a/stack-lts14.yaml +++ /dev/null @@ -1,12 +0,0 @@ -resolver: lts-14.27 - -extra-deps: - - call-stack-0.2.0 - - hspec-2.10.0 - - hspec-api-2.10.0 - - hspec-core-2.10.0 - - hspec-discover-2.10.0 - - hspec-golden-0.2.1.0 - - hspec-junit-formatter-1.1.2.1 - - regex-base-0.94.0.2@sha256:3a76c313f9f75e8e0b3c103c1bff5bbaf754da30cbddedc1d5b7061d001030e0 - - regex-tdfa-1.3.2.2@sha256:cdf462f6280804baa264e42e3e328ba02ec71d9335de706f3dee778890a25c0e,7042 diff --git a/stack-lts16.yaml b/stack-lts16.yaml deleted file mode 100644 index a15bb71..0000000 --- a/stack-lts16.yaml +++ /dev/null @@ -1,11 +0,0 @@ -resolver: lts-16.31 - -extra-deps: - - hspec-2.10.0 - - hspec-api-2.10.0 - - hspec-core-2.10.0 - - hspec-discover-2.10.0 - - hspec-golden-0.2.1.0 - - hspec-junit-formatter-1.1.2.1 - - regex-base-0.94.0.2 - - regex-tdfa-1.3.2.2 diff --git a/stack-lts18.yaml b/stack-lts18.yaml deleted file mode 100644 index 9481656..0000000 --- a/stack-lts18.yaml +++ /dev/null @@ -1,10 +0,0 @@ -resolver: lts-18.28 - -extra-deps: - - hspec-2.10.0 - - hspec-api-2.10.0 - - hspec-core-2.10.0 - - hspec-discover-2.10.0 - - hspec-golden-0.2.1.0 - - hspec-junit-formatter-1.1.2.1 - - regex-tdfa-1.3.2.2 diff --git a/stack-lts19.yaml b/stack-lts19.yaml deleted file mode 100644 index 9237d4e..0000000 --- a/stack-lts19.yaml +++ /dev/null @@ -1,10 +0,0 @@ -resolver: lts-19.33 - -extra-deps: - - hspec-2.10.0 - - hspec-api-2.10.0 - - hspec-core-2.10.0 - - hspec-discover-2.10.0 - - hspec-golden-0.2.1.0 - - hspec-junit-formatter-1.1.2.1 - - regex-tdfa-1.3.2.2 diff --git a/stack-lts20.yaml b/stack-lts20.yaml index aa53c3f..28ca5d5 100644 --- a/stack-lts20.yaml +++ b/stack-lts20.yaml @@ -1,9 +1,23 @@ resolver: lts-20.26 extra-deps: + - Blammo-2.1.0.0 + - freckle-exception-0.0.0.0 + - freckle-memcached-0.0.0.1 + - freckle-otel-0.0.0.1 + - freckle-prelude-0.0.4.1 + - hs-opentelemetry-api-0.1.0.0 + - hs-opentelemetry-exporter-otlp-0.0.1.5 + - hs-opentelemetry-otlp-0.0.1.0 + - hs-opentelemetry-propagator-b3-0.0.1.1 + - hs-opentelemetry-propagator-w3c-0.0.1.3 + - hs-opentelemetry-sdk-0.0.3.6 - hspec-2.10.0 - hspec-api-2.10.0 - hspec-core-2.10.0 - hspec-discover-2.10.0 - hspec-junit-formatter-1.1.2.1 + - monad-validate-1.3.0.0 - regex-tdfa-1.3.2.2 + - thread-utils-context-0.3.0.4 + - thread-utils-finalizers-0.1.1.0 diff --git a/stack-lts21.yaml b/stack-lts21.yaml index ab8c094..da92eac 100644 --- a/stack-lts21.yaml +++ b/stack-lts21.yaml @@ -1,9 +1,22 @@ resolver: lts-21.25 extra-deps: + - freckle-exception-0.0.0.1 + - freckle-memcached-0.0.0.2 + - freckle-otel-0.0.0.2 + - freckle-prelude-0.0.4.1 + - hs-opentelemetry-api-0.1.0.0 + - hs-opentelemetry-exporter-otlp-0.0.1.5 + - hs-opentelemetry-otlp-0.0.1.0 + - hs-opentelemetry-propagator-b3-0.0.1.1 + - hs-opentelemetry-propagator-w3c-0.0.1.3 + - hs-opentelemetry-sdk-0.0.3.6 - hspec-2.11.10 - hspec-api-2.11.10 - hspec-core-2.11.10 - hspec-discover-2.11.10 - hspec-expectations-0.8.4 - hspec-junit-formatter-1.1.2.1 + - monad-validate-1.3.0.0 + - thread-utils-context-0.3.0.4 + - thread-utils-finalizers-0.1.1.0 diff --git a/stack-lts22.yaml b/stack-lts22.yaml index c757eef..9c5b7dc 100644 --- a/stack-lts22.yaml +++ b/stack-lts22.yaml @@ -1 +1,16 @@ resolver: lts-22.43 + +extra-deps: + - freckle-exception-0.0.0.2 + - freckle-memcached-0.0.0.2 + - freckle-otel-0.0.0.2 + - freckle-prelude-0.0.4.1 + - hs-opentelemetry-api-0.1.0.0 + - hs-opentelemetry-exporter-otlp-0.0.1.5 + - hs-opentelemetry-otlp-0.0.1.0 + - hs-opentelemetry-propagator-b3-0.0.1.1 + - hs-opentelemetry-propagator-w3c-0.0.1.3 + - hs-opentelemetry-sdk-0.0.3.6 + - monad-validate-1.3.0.0 + - thread-utils-context-0.3.0.4 + - thread-utils-finalizers-0.1.1.0 diff --git a/stack-lts23.yaml b/stack-lts23.yaml index e7f5eb7..de36603 100644 --- a/stack-lts23.yaml +++ b/stack-lts23.yaml @@ -1 +1,17 @@ resolver: lts-23.17 + +extra-deps: + - freckle-exception-0.0.0.2 + - freckle-memcached-0.0.0.2 + - freckle-otel-0.0.0.2 + - freckle-prelude-0.0.4.1 + - hs-opentelemetry-api-0.2.0.0 + - hs-opentelemetry-exporter-otlp-0.1.0.0 + - hs-opentelemetry-otlp-0.1.0.0 + - hs-opentelemetry-propagator-b3-0.0.1.2 + - hs-opentelemetry-propagator-datadog-0.0.1.0 + - hs-opentelemetry-propagator-w3c-0.0.1.4 + - hs-opentelemetry-sdk-0.1.0.0 + - monad-validate-1.3.0.0 + - thread-utils-context-0.3.0.4 + - thread-utils-finalizers-0.1.1.0 diff --git a/stack-lts24.yaml b/stack-lts24.yaml new file mode 100644 index 0000000..a8b6196 --- /dev/null +++ b/stack-lts24.yaml @@ -0,0 +1,31 @@ +resolver: lts-24.9 +extra-deps: + - Blammo-wai-0.0.0.2 + - buffer-builder-0.2.4.9 + - data-default-class-0.1.2.2 + - datadog-0.3.0.0 + - dotenv-0.12.0.0 + - esqueleto-3.6.0.0 + - freckle-ecs-0.0.0.1 + - freckle-exception-0.0.0.0 + - freckle-memcached-0.0.0.2 + - freckle-otel-0.0.0.2 + - freckle-prelude-0.0.4.0 + - monad-validate-1.3.0.0 + - persistent-sql-lifted-0.4.3.1 + - thread-utils-context-0.3.0.4 + - thread-utils-finalizers-0.1.1.0 + + - github: iand675/hs-opentelemetry + commit: 2e577f2dbd28c67f01b80c648c8e45d972763559 + subdirs: + - api + - otlp + - sdk + - exporters/otlp + - instrumentation/persistent + - instrumentation/wai + - instrumentation/yesod + - propagators/b3 + - propagators/datadog + - propagators/w3c diff --git a/stack-nightly.yaml b/stack-nightly.yaml index d0dca43..affa44f 100644 --- a/stack-nightly.yaml +++ b/stack-nightly.yaml @@ -1 +1,17 @@ -resolver: nightly-2024-07-02 +resolver: nightly-2025-12-01 + +extra-deps: + - freckle-exception-0.0.0.2 + - freckle-memcached-0.0.0.2 + - freckle-otel-0.0.0.2 + - freckle-prelude-0.0.4.1 + - hs-opentelemetry-api-0.3.0.0 + - hs-opentelemetry-exporter-otlp-0.1.0.1 + - hs-opentelemetry-otlp-0.1.1.0 + - hs-opentelemetry-propagator-b3-0.0.1.3 + - hs-opentelemetry-propagator-datadog-0.0.1.1 + - hs-opentelemetry-propagator-w3c-0.1.0.0 + - hs-opentelemetry-sdk-0.1.0.1 + - monad-validate-1.3.0.0 + - thread-utils-context-0.3.0.4 + - thread-utils-finalizers-0.1.1.0 diff --git a/stack.yaml b/stack.yaml index c2d19ad..ad18933 120000 --- a/stack.yaml +++ b/stack.yaml @@ -1 +1 @@ -./stack-lts23.yaml \ No newline at end of file +stack-lts24.yaml \ No newline at end of file diff --git a/test/Spec.hs b/test/Spec.hs deleted file mode 100644 index 8044961..0000000 --- a/test/Spec.hs +++ /dev/null @@ -1 +0,0 @@ -{-# OPTIONS_GHC -F -pgmF hspec-discover -Wno-missing-export-lists #-} diff --git a/test/SpecHook.hs b/test/SpecHook.hs deleted file mode 100644 index e064a69..0000000 --- a/test/SpecHook.hs +++ /dev/null @@ -1,11 +0,0 @@ -module SpecHook - ( hook - ) where - -import Prelude - -import Test.Hspec -import Test.Hspec.JUnit.Formatter.Env as Formatter - -hook :: Spec -> Spec -hook = Formatter.whenEnabled Formatter.add . parallel diff --git a/test/WhateverSpec.hs b/test/WhateverSpec.hs deleted file mode 100644 index 4705b6b..0000000 --- a/test/WhateverSpec.hs +++ /dev/null @@ -1,8 +0,0 @@ -module WhateverSpec (spec) where - -import Prelude - -import Test.Hspec - -spec :: Spec -spec = pure () -- Tests go here diff --git a/tests/Freckle/App/Http/CacheSpec.hs b/tests/Freckle/App/Http/CacheSpec.hs new file mode 100644 index 0000000..408c8bc --- /dev/null +++ b/tests/Freckle/App/Http/CacheSpec.hs @@ -0,0 +1,401 @@ +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE NoFieldSelectors #-} + +module Freckle.App.Http.CacheSpec + ( spec + ) where + +import Prelude + +import Codec.Compression.GZip qualified as GZip +import Control.Lens ((.~), (<>~)) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Control.Monad.State (StateT, execStateT) +import Data.Aeson (FromJSON, eitherDecode) +import Data.ByteString.Lazy qualified as BSL +import Data.Foldable (for_) +import Data.Function ((&)) +import Data.Functor (void) +import Data.HashMap.Strict qualified as HashMap +import Data.Time (addUTCTime, getCurrentTime) +import Freckle.App.Http +import Freckle.App.Http.Cache +import Freckle.App.Http.Cache.State +import Freckle.App.Test.Http +import Network.HTTP.Types.Header + ( hAcceptEncoding + , hAcceptLanguage + , hAge + , hCacheControl + , hContentEncoding + , hETag + , hExpires + , hIfNoneMatch + , hVary + ) +import Network.HTTP.Types.Status + ( status100 + , status304 + , status307 + , status400 + , status503 + ) +import Test.Hspec (Spec, context, describe, it) +import Test.Hspec.Expectations.Json.Lifted (shouldMatchJson) +import Test.Hspec.Expectations.Lifted + +type CacheSettings = HttpCacheSettings (StateT Cache IO) CachedResponse + +spec :: Spec +spec = do + describe "httpCached" $ do + it "caches successful GET requests" $ do + let + stubs = + [ "https://example.com/1" & bodyL .~ "Hello\n" + , "https://example.com/2" & bodyL .~ "World\n" + ] + + req1 = parseRequest_ "https://example.com/1" + req2 = parseRequest_ "https://example.com/2" + + cache <- execCached $ do + requestBodyCached settings stubs req1 `shouldReturn` "Hello\n" + requestBodyCached settings stubs req2 `shouldReturn` "World\n" + + -- No stubs, so these would fail if not cached + requestBodyCached settings [] req1 `shouldReturn` "Hello\n" + requestBodyCached settings [] req2 `shouldReturn` "World\n" + + cache.map `shouldSatisfy` ((== 2) . HashMap.size) + + it "evicts stale caches" $ do + let + stubs = + [ "https://example.com/1" + & headersL <>~ [(hCacheControl, "max-age=2")] + & bodyL .~ "Hi\n" + ] + + -- On the request that we expect to evict, we'll use this so that we + -- don't store a cache from that and we can observe the eviction. + stubsNoStore = + [ "https://example.com/1" + & headersL <>~ [(hCacheControl, "no-store")] + & bodyL .~ "Hi\n" + ] + + req = parseRequest_ "https://example.com/1" + + cache <- execCached $ do + requestBodyCached settings stubs req `shouldReturn` "Hi\n" + + -- Cached, no requests made + requestBodyCached settings [] req `shouldReturn` "Hi\n" + + -- Expired, trigger eviction + requestBodyCached settingsFuture stubsNoStore req `shouldReturn` "Hi\n" + + cache.map `shouldSatisfy` ((== 0) . HashMap.size) + + it "incorporates Vary headers into the cache key" $ do + let + stubs = + [ "https://example.com/1" + & matchL <>~ MatchHeader (hAcceptLanguage, "en") + & bodyL .~ "Hello\n" + , "https://example.com/1" + & matchL <>~ MatchHeader (hAcceptLanguage, "es") + & bodyL .~ "Hola\n" + , "https://example.com/2" + & matchL <>~ MatchHeader (hAcceptLanguage, "en") + & bodyL .~ "World\n" + , "https://example.com/2" + & matchL <>~ MatchHeader (hAcceptLanguage, "es") + & bodyL .~ "Mundo\n" + ] + + reqEn1 = + parseRequest_ "https://example.com/1" + & addRequestHeader hAcceptLanguage "en" + & addRequestHeader hVary "Accept, Accept-Language" + reqEn2 = + parseRequest_ "https://example.com/2" + & addRequestHeader hAcceptLanguage "en" + & addRequestHeader hVary "Accept, Accept-Language" + reqEs1 = + parseRequest_ "https://example.com/1" + & addRequestHeader hAcceptLanguage "es" + & addRequestHeader hVary "Accept, Accept-Language" + reqEs2 = + parseRequest_ "https://example.com/2" + & addRequestHeader hAcceptLanguage "es" + & addRequestHeader hVary "Accept, Accept-Language" + + cache <- execCached $ do + requestBodyCached settings stubs reqEn1 `shouldReturn` "Hello\n" + requestBodyCached settings stubs reqEn2 `shouldReturn` "World\n" + requestBodyCached settings stubs reqEs1 `shouldReturn` "Hola\n" + requestBodyCached settings stubs reqEs2 `shouldReturn` "Mundo\n" + + -- No stubs, so these would fail if not cached + requestBodyCached settings [] reqEn1 `shouldReturn` "Hello\n" + requestBodyCached settings [] reqEn2 `shouldReturn` "World\n" + requestBodyCached settings [] reqEs1 `shouldReturn` "Hola\n" + requestBodyCached settings [] reqEs2 `shouldReturn` "Mundo\n" + + cache.map `shouldSatisfy` ((== 4) . HashMap.size) + + context "compression" $ do + it "caches gzipped responses as gzipped" $ do + let + gzipped = GZip.compress "Hi (zipped)\n" + + stubs = + [ "https://example.com/1" + & matchL <>~ MatchHeader (hAcceptEncoding, "gzip") + & headersL <>~ [(hContentEncoding, "gzip")] + & bodyL .~ gzipped + , "https://example.com/1" + & bodyL .~ "Hi (not zipped)\n" + ] + + req = + parseRequest_ "https://example.com/1" + & addRequestHeader hVary "accept-encoding" + reqGzipped = + parseRequest_ "https://example.com/1" + & addRequestHeader hVary "accept-encoding" + & addRequestHeader hAcceptEncoding "gzip" + reqGzippedAsIs = + parseRequest_ "https://example.com/1" + & addRequestHeader hVary "accept-encoding" + & addRequestHeader hAcceptEncoding "gzip" + & disableRequestDecompress + + cache <- execCached $ do + requestBodyCached settings stubs req `shouldReturn` "Hi (not zipped)\n" + requestBodyCached settings stubs reqGzipped `shouldReturn` "Hi (zipped)\n" + requestBodyCached settings stubs reqGzippedAsIs `shouldReturn` gzipped + + cache.map `shouldSatisfy` ((== 2) . HashMap.size) + + -- We don't want to expose the constructor, but we do want to verify the + -- cache contains the gzipped form. + map (show . getResponseBody . (.response) . snd) (HashMap.toList cache.map) + `shouldMatchList` [ "PotentiallyGzipped {unwrap = \"Hi (not zipped)\\n\"}" :: String + , "PotentiallyGzipped {unwrap = " <> show gzipped <> "}" + ] + + it "handles large gzip responses correctly" $ do + bs <- BSL.readFile "tests/files/constructed-responses.gzip" + val <- expectDecode $ GZip.decompress bs + + let + stubs = + [ "https://example.com/1" + & matchL <>~ MatchHeader (hAcceptEncoding, "gzip") + & headersL <>~ [(hContentEncoding, "gzip")] + & bodyL .~ bs + ] + + req = + parseRequest_ "https://example.com/1" + & addRequestHeader hVary "accept-encoding" + & addRequestHeader hAcceptEncoding "gzip" + + void $ execCached $ do + actual <- expectDecode =<< requestBodyCached settings stubs req + actual `shouldMatchJson` val + + context "Handling ETag" $ do + let etag = "W/\"99\"" + + it "uses cached response and doesn't evict on 304 from If-None-Match" $ do + let stubs = + [ "https://example.com/1" + & matchL <>~ MatchHeader (hIfNoneMatch, etag) + & statusL .~ status304 + & bodyL .~ "\n" + , "https://example.com/1" + & headersL <>~ [(hCacheControl, "max-age=-1")] + & headersL <>~ [(hETag, etag)] + & bodyL .~ "Original body\n" + ] + + cache <- execCached $ do + let req = parseRequest_ "https://example.com/1" + requestBodyCached settings stubs req `shouldReturn` "Original body\n" + requestBodyCached settings stubs req `shouldReturn` "Original body\n" + + cache.map `shouldSatisfy` ((== 1) . HashMap.size) + + it "updates cached response on 304 from If-None-Match" $ do + let stubs = + [ "https://example.com/1" + & matchL <>~ MatchHeader (hIfNoneMatch, etag) + & statusL .~ status304 + & headersL <>~ [(hCacheControl, "max-age=120")] + & bodyL .~ "\n" + , "https://example.com/1" + & headersL <>~ [(hCacheControl, "max-age=-1")] + & headersL <>~ [(hETag, etag)] + & bodyL .~ "Original body\n" + ] + + cache <- execCached $ do + let req = parseRequest_ "https://example.com/1" + requestBodyCached settings stubs req `shouldReturn` "Original body\n" + requestBodyCached settings stubs req `shouldReturn` "Original body\n" + + cache.map `shouldSatisfy` ((== 1) . HashMap.size) + map (.ttl) (HashMap.elems cache.map) `shouldBe` [120] + + it "evicts a stale response after trying If-None-Match" $ do + let stubs = + [ "https://example.com/1" + & matchL <>~ MatchHeader (hIfNoneMatch, etag) + & headersL <>~ [(hCacheControl, "no-store")] + & bodyL .~ "Newer body\n" + , "https://example.com/1" + & headersL <>~ [(hCacheControl, "max-age=-1")] + & headersL <>~ [(hETag, etag)] + & bodyL .~ "Original body\n" + ] + + cache <- execCached $ do + let req = parseRequest_ "https://example.com/1" + requestBodyCached settings stubs req `shouldReturn` "Original body\n" + requestBodyCached settings stubs req `shouldReturn` "Newer body\n" + + cache.map `shouldSatisfy` ((== 0) . HashMap.size) + + context "setting TTL" $ do + let req = parseRequest_ "https://example.com" + + it "sets TTL based on max-age" $ do + let stubs = + [ "https://example.com" + & headersL <>~ [(hCacheControl, "max-age=42")] + ] + + cache <- execCached $ requestBodyCached settings stubs req + map (.ttl) (HashMap.elems cache.map) `shouldBe` [42] + + it "sets TTL based on max-age + Age" $ do + let stubs = + [ "https://example.com" + & headersL <>~ [(hAge, "78000"), (hCacheControl, "max-age=78250")] + ] + + cache <- execCached $ requestBodyCached settings stubs req + map (.ttl) (HashMap.elems cache.map) `shouldBe` [250] + + it "sets TTL based on Expires" $ do + let + expDate = "Wed, 21 Oct 2015 07:28:00 GMT" + expSeconds = 1445412480 -- `date --date '{eDate}' +%s` + stubs = ["https://example.com" & headersL <>~ [(hExpires, expDate)]] + + cache <- execCached $ requestBodyCached settings stubs req + map (.ttl) (HashMap.elems cache.map) `shouldBe` [expSeconds] + + context "un-cacheable requests" $ do + it "does not cache if told not to" $ do + let req = parseRequest_ "https://example.com" + cache <- execCached $ requestBodyCached settingsDisabled stubAnything req + cache.map `shouldSatisfy` ((== 0) . HashMap.size) + + it "does not cache non-GET methods" $ do + let req = parseRequest_ "POST https://example.com" + cache <- execCached $ requestBodyCached settings stubAnything req + cache.map `shouldSatisfy` ((== 0) . HashMap.size) + + it "does not cache no-store" $ do + let req = + parseRequest_ "https://example.com" + & addRequestHeader hCacheControl "no-store" + cache <- execCached $ requestBodyCached settings stubAnything req + cache.map `shouldSatisfy` ((== 0) . HashMap.size) + + it "does not cache private in a shared cache" $ do + let req = + parseRequest_ "https://example.com" + & addRequestHeader hCacheControl "private" + cache <- execCached $ requestBodyCached settingsShared stubAnything req + cache.map `shouldSatisfy` ((== 0) . HashMap.size) + + context "un-cacheable responses" $ do + let req = parseRequest_ "https://example.com" + + it "does not cache no-store" $ do + let stubs = + [ "https://example.com" + & headersL <>~ [(hCacheControl, "no-store, max-age=0, public")] + ] + + cache <- execCached $ requestBodyCached settings stubs req + cache.map `shouldSatisfy` ((== 0) . HashMap.size) + + it "does not cache private in a shared cache" $ do + let stubs = + [ "https://example.com" + & headersL <>~ [(hCacheControl, "max-age=0, private")] + ] + + cache <- execCached $ requestBodyCached settingsShared stubs req + cache.map `shouldSatisfy` ((== 0) . HashMap.size) + + for_ [status100, status307, status400, status503] $ \s -> do + it ("does not cache un-cacheable status " <> show (statusCode s)) $ do + let stubs = ["https://example.com" & statusL .~ s] + + cache <- execCached $ requestBodyCached settingsShared stubs req + cache.map `shouldSatisfy` ((== 0) . HashMap.size) + +execCached :: StateT Cache IO a -> IO Cache +execCached = flip execStateT mempty + +requestBodyCached + :: CacheSettings + -> [HttpStub] + -> Request + -> StateT Cache IO BSL.ByteString +requestBodyCached ss stubs req = + getResponseBody <$> httpCached ss (httpStubbed stubs) req + +settings :: CacheSettings +settings = stateHttpCacheSettings + +settingsDisabled :: CacheSettings +settingsDisabled = + settings + { cacheable = const False + } + +settingsShared :: CacheSettings +settingsShared = + settings + { shared = True + } + +settingsFuture :: CacheSettings +settingsFuture = + settings + { getCurrentTime = liftIO $ addUTCTime 5 <$> getCurrentTime + } + +stubAnything :: [HttpStub] +stubAnything = [httpStub "Anything" MatchAnything] + +expectDecode :: (HasCallStack, MonadIO m, FromJSON a) => BSL.ByteString -> m a +expectDecode bs = case eitherDecode bs of + Left err -> do + expectationFailure $ + mconcat + [ "Expected input to decode as JSON" + , "\nInput: " <> show bs + , "\nErrors: " <> err + ] + error "" + Right a -> pure a diff --git a/tests/Freckle/App/HttpSpec.hs b/tests/Freckle/App/HttpSpec.hs new file mode 100644 index 0000000..6bba423 --- /dev/null +++ b/tests/Freckle/App/HttpSpec.hs @@ -0,0 +1,51 @@ +module Freckle.App.HttpSpec + ( spec + ) where + +import Prelude + +import Control.Lens (to, (^?), _Left, _Right) +import Data.Aeson +import Data.Aeson.Lens +import Data.List.NonEmpty qualified as NE +import Freckle.App.Http +import Freckle.App.Test.Http +import Network.HTTP.Types.Status (status200) +import Test.Hspec + +spec :: Spec +spec = do + describe "httpJson" $ do + stubs <- runIO $ loadHttpStubsDirectory "tests/files" + + it "fetches JSON via HTTP" $ do + resp <- + flip runHttpStubsT stubs + . httpJson @_ @Value + $ parseRequest_ "https://www.stackage.org/lts-17.10" + + getResponseStatus resp `shouldBe` status200 + getResponseBody resp + ^? _Right + . key "snapshot" + . key "ghc" + . _String + `shouldBe` Just "8.10.4" + + it "places JSON parse errors in a Left body" $ do + resp <- + flip runHttpStubsT stubs + . httpJson @_ @[()] + $ parseRequest_ "https://www.stackage.org/lts-17.10" + + let expectedErrorMessages = + [ "Error in $: expected [a], encountered Object" + , "Error in $: parsing [] failed, expected Array, but encountered Object" + ] + + getResponseStatus resp `shouldBe` status200 + getResponseBody resp + ^? _Left + . to hdeErrors + . to NE.head + `shouldSatisfy` maybe False (`elem` expectedErrorMessages) diff --git a/tests/Freckle/App/Test/Http/MatchRequestSpec.hs b/tests/Freckle/App/Test/Http/MatchRequestSpec.hs new file mode 100644 index 0000000..5cd7a96 --- /dev/null +++ b/tests/Freckle/App/Test/Http/MatchRequestSpec.hs @@ -0,0 +1,115 @@ +module Freckle.App.Test.Http.MatchRequestSpec + ( spec + ) where + +import Freckle.App.Prelude + +import Freckle.App.Http + ( Request + , addAcceptHeader + , addRequestHeader + , parseRequest_ + , setRequestPath + ) +import Freckle.App.Test.Http.MatchRequest +import Network.HTTP.Types.Header (hAccept) +import Test.Hspec + +spec :: Spec +spec = do + describe "matchRequestFromUrl" $ do + context "matching complete requests" $ do + let + url = "https://localhost:3000/hello?world" + mr = matchRequestFromUrl url + + it "matches the same request" $ do + mr `shouldMatchRequest` parseRequest_ url + + it "matches on path, even if built relative" $ do + let req = parseRequest_ "https://localhost:3000/world?world" + mr `shouldMatchRequest` setRequestPath "hello" req + + it "matches on method" $ do + mr `shouldNotMatchRequest` parseRequest_ ("POST " <> url) + + it "matches on scheme" $ do + mr `shouldNotMatchRequest` parseRequest_ "http://localhost:3000/hello?world" + + it "matches on host" $ do + mr `shouldNotMatchRequest` parseRequest_ "https://localhost2:3000/hello?world" + + it "matches on port" $ do + mr `shouldNotMatchRequest` parseRequest_ "https://localhost:3001/hello?world" + + it "matches on path" $ do + mr `shouldNotMatchRequest` parseRequest_ "https://localhost:3000/world?world" + + it "matches on query" $ do + mr `shouldNotMatchRequest` parseRequest_ "https://localhost:3000/hello?wut" + + it "can match on everything unspecified" $ do + let mr = matchRequestFromUrl "https://" + + mr `shouldMatchRequest` parseRequest_ "https://example.com" + mr `shouldMatchRequest` parseRequest_ "https://example.com/" + mr `shouldMatchRequest` parseRequest_ "https://example.com/foo" + mr `shouldMatchRequest` parseRequest_ "https://example.com/bar" + + it "can match any path" $ do + let mr = matchRequestFromUrl "https://example.com" + + mr `shouldMatchRequest` parseRequest_ "https://example.com" + mr `shouldMatchRequest` parseRequest_ "https://example.com/" + mr `shouldMatchRequest` parseRequest_ "https://example.com/foo" + mr `shouldMatchRequest` parseRequest_ "https://example.com/bar" + + it "can match the root path explicitly" $ do + let mr = matchRequestFromUrl "https://example.com/" + + mr `shouldMatchRequest` parseRequest_ "https://example.com/" + mr `shouldMatchRequest` parseRequest_ "https://example.com" + mr `shouldNotMatchRequest` parseRequest_ "https://example.com/foo" + mr `shouldNotMatchRequest` parseRequest_ "https://example.com/bar" + + context "matching headers" $ do + let + url = "https://example.com/" + accept = "text/plain" + hasNoHeaders = parseRequest_ url + hasOnlyTheHeader = addAcceptHeader accept hasNoHeaders + hasExtraHeaders = addRequestHeader "User-Agent" "me" hasOnlyTheHeader + + it "can match headers exactly" $ do + let mr = matchRequestFromUrl url <> MatchHeaders [(hAccept, accept)] + + mr `shouldMatchRequest` hasOnlyTheHeader + mr `shouldNotMatchRequest` hasExtraHeaders + mr `shouldNotMatchRequest` hasNoHeaders + + it "can match headers-include" $ do + let mr = matchRequestFromUrl url <> MatchHeader (hAccept, accept) + + mr `shouldMatchRequest` hasOnlyTheHeader + mr `shouldMatchRequest` hasExtraHeaders + mr `shouldNotMatchRequest` hasNoHeaders + +shouldMatchRequest :: HasCallStack => MatchRequest -> Request -> IO () +mr `shouldMatchRequest` req = do + case matchRequest req mr of + Left err -> do + let preamble = unlines ["Expected to match request, but did not", "", show req] + expectationFailure $ preamble <> err + Right () -> pure () + +infix 1 `shouldMatchRequest` + +shouldNotMatchRequest :: HasCallStack => MatchRequest -> Request -> IO () +mr `shouldNotMatchRequest` req = do + case matchRequest req mr of + Left {} -> pure () + Right () -> do + let preamble = unlines ["Expected to NOT match request, but did", "", show req] + expectationFailure $ preamble <> showMatchRequest mr + +infix 1 `shouldNotMatchRequest` diff --git a/tests/Main.hs b/tests/Main.hs new file mode 100644 index 0000000..1fcc19c --- /dev/null +++ b/tests/Main.hs @@ -0,0 +1 @@ +{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Main -Wno-missing-export-lists #-} diff --git a/tests/files/constructed-responses.gzip b/tests/files/constructed-responses.gzip new file mode 100644 index 0000000000000000000000000000000000000000..044573706e1c67ea24496031a362e213f13a9a9a GIT binary patch literal 3840 zcmV+b5C8BViwFP!000001I?XVZ{s!^hX2YU*V{yrC6Ve~XU5J=u~=<@y(x-Ll z#B^;dhbi{IuMYN1Cb6AwP}uJa6exQ1OFa_#o%!2e7t57@dj4a5>+bIUaMi`n7gb{7 zqBbU0S!E|-SSVEnK@udX4#LWW7yrCi*|l$1rCneA{M%m_3v+L~|L&t-lyy`KSKXre$oAqVAvgPfay)6CxRoSjqu59nx`>T7iX|77Y zSZ`Ksxv|wx-489hzqD7&s=l&|tMu|e|Go@=`u0KxN+Uz-8jEEgNhfw z^;*V%-TEBQVf>l#$A9mC{w+*J4l|;F8Np#j0+>anl3)^89hWMuveYDD6<2jwn<|^s zbpnSu`GtaJCj*VMlkh%I(|oq>-V}IlA2_&uz;pW`*;H%tO?C2-gXc#)&yND0He%=b ziG$}SJkL)8o>?54s!k`B4Wc-V%R*aKbmwFlhCv;~HJ0bUZWt`sK?RiNpg{0(mdqb# zw+I3Yc2I$aDY0PdkR0ZO0_Fq`b0RT=%DA*aSye?=#6^^4Nue?`DT_d*MUsKtk!}$& z1`ccz$Dx}j|C9U^Bg6Pd(Cc3 zrUZ`rC69Vr zJ*a@f925v(7UVD^0?T%kfyNn0EZZn!=Sg7MjxyjpC6;YevhyUcY)2Vzo)XJ8s>yj0 zB&G%xXquA5RAk9v5+tSu6=;}}#8hRPjkTqRLnBB)4Jy#kBnhYqbs(=>L`nfOg~LoG zSrcdH)-5d@nxcTF;Lw!h3uJdV2tqr93N%bfXlGK9!^|jPW^kC9q>H0eMPjv$LTd(? zX>534UNeO0uF+AtYf+FcEtty*t{Ey*uhE66*P^=Al)9tin!)?OMmzu4BHv$8_i|h_ zxc=8@&;MHF_?Mxs$~rVU3+uQFb!NgW)@h)#Osi-TU`gIk^daBC`;c#BeaIiV^ThxRmum*yCpijsT1GbA_z|JK?QoFkeuJ^#`|0A9^lXj4(dS# z8k*#&PN}n@-69f#YK2h-8fPM@R!FJiP5mkesue~VaGsKCg_JrW+OLA3T49s{=P9XH zP$4@{f;!w$2Arp)4%be2gBFTn36$oGw_jxLcws{3_n^)4d`8Xwq858)RqYO08SmJ}m)G_&f6$CNdQ3jl+B!=rs z@8^wgu-ph51QohLPzf6Zm1eH%A!ra(=mtS0Y!Fl#H%S6J2izd2gbjkNU$o1{z;{u2 ze}O_b0xDr6pdy~y-md{Z7-$p$CMg*Uq%1c{_*9@#1el~`CXgbYyss#la}~TfS4o?5 z6?O7i-kf`K6=;|uOO?3OT2XYvDtI@nl6J$obz|(KfsS5N6y2~2-VLjy-LQ%}?$-At zQNRRStb+oP^T7?1z-Ap(pkYd^R!w}5+OP=xeNj6ZXq=+^qIAs8lOV$}%7F8fWH>bO zB$r_on&bP_`0rCozRx0!Y&n_qiO47}6O|Pm;H=J~uqbquLT?BjSHbapYW(-9Mc*fN z)oZ^Bf~wb12Arqp)(hfVEIss2pmA;WtQW=?zi|aRb+|2z=aqCj*UB6n9UEZ_6B40gvnL zWWaff=ED-_#fDYjgX=Sf2;K}Gz?+HUcNulaXT}h|o1vq3Gg0s^qt21c z7-Dxbbm(p-iri(ybwB+Y2m*IAblh$x3fpPon+$Z{wJmfcPdR;WtApX5w>?r0-1ByZ z2QvQ9+h?&AaQerv74YGml2h&%oWQfb29HJFDiG{e2{}vxcl#PV6M3sZ05j!=N#LS^ zhi!}kL}lQAljNyStCvn1YIN=!9hkwnqYUs{C2!Xp))iTqnLXL%$v?gfskK3qe`_=m|$!I^jrD zcVI9~IMVP5M_M}JD6MqaZSiRtrYg2|ttTp}RH|dEY!IXw940{u7d_!fOD7y@>QYjM z$wwMK`AADA9~ItjOtXYu#hYFJ%Fv#;lL5rGlkh&b#@f$j*+Az>N z+B_QjMDV@AkG!1>I5)|mSGHy2|7@XS{YJI$(`_e1c<KzN@bgI$Qe7EW zSi2yrpIjJlJi1PY@Y&su*Bd<%JUbTmk->kIIFP<@?XzhJ zHjMC?2YIF^CUM>TZJ#f8OM)jx;=6?e&RZz)-fU+_cPHcn0Xh=*Erf{bO9o2+g2(fh@``$25xevlgxWbWSz!DQ~Y zI)rw4VcfE@@T?9&YwT79`pc6TDaJnfw!;R8#$cj=+hDf}1ZAXdWqnBijlnnpcf4*D zybtZc*}~cletTqytIx3Y+FJ$hLtB_;NF~9c9SiB7zVmh~0oQr^y?~>gw;N;2#x&1x zWDL24*}hc5fkVaGRcqkE{24_oe}=7u-YO7;2REi!Hy(eeP^1xNd$S1V_ICpTw_ERP zXUi7P?QNLiK<$^epRJn<-n7#mMAKJQdpIBcjgq6s(!F2IjkyPZsZ);HxhcMOZYr#u ztGZt^i#gcWn#ybEPXGCR7ukT#q}iwC=ojdoy?-`MfhS22>zMvXHJ$4%>h4RyKRJSc z!}Le0={obQ@|%`m*C+7$r$16nXPRZ(eX!s>&Xl8?Zo0pk?%e04q+!~+-uqsm_X>*q z+Z3LEo67QUrmw%88*l;UlwdyXRs|ZG#4=qN_vkxXaA*YiwkbT{HkIYu9&B%h{)yin zkJ+H-2q;g#rMfTy<(C4E`V9-;y-^JTG6v&!Z?^4#?Q8A4GX*O6b7L@l;R&y)EaCNJ zY{v~9n4BI@c%2U#+UTB%Y;Mz+nm!wF>Snqx<#nL_q3IQ`!HvXIid@rlZ?5S;Af~-_ z?YzO8d<^NHDL&mZ6{dThon1HX9{;{eJ629^`+eWCf(#IzQGR5?b8iIdT&qxr8X#dADU}d#y+;-1G*DSU{?() z(9k4SRcYMZRSsNu)`=xZ?+q%@&_om4j4%6i5gZzU{WPdRLz7rf)~%dfTl|+skl-6s zprJ|H>3s8K-IE8Kyq#DA`*Bc#h9u>={`K?NF`q?>QkE{uI5Fhm&AyQ2&=Hc@)lH(z{HGH^Q< z6*cb zXX94r)JRTpK|)S&K|(IQAi+KwyYlFpaPs#qNH`}taJ5D*y{W<4P2)s^f=+zk%t#so zUJF6Zu+H~RVb~9G*?7OPk|}2=R?P7A!3QDavOx%I*V%ezj@~pW1|j6|K?u2c5JKlw zHF#(Y0X_KO@mw}|yz2f>#;wtox{bXC0+aMX`0T-mfGJbodb!fZO48xJU50&w$`v=jGD!B_MD_x}SF!l#;UQ2_v` C(qxbT literal 0 HcmV?d00001 diff --git a/tests/files/https/www.stackage.org/lts-17.10 b/tests/files/https/www.stackage.org/lts-17.10 new file mode 100644 index 0000000..9b2b2c3 --- /dev/null +++ b/tests/files/https/www.stackage.org/lts-17.10 @@ -0,0 +1,16012 @@ +{ + "packages": [ + { + "name": "abstract-deque", + "origin": "hackage", + "synopsis": "Abstract, parameterized interface to mutable Deques.", + "version": "0.3" + }, + { + "name": "abstract-par", + "origin": "hackage", + "synopsis": "Type classes generalizing the functionality of the 'monad-par' library.", + "version": "0.3.3" + }, + { + "name": "AC-Angle", + "origin": "hackage", + "synopsis": "Angles in degrees and radians.", + "version": "1.0" + }, + { + "name": "accuerr", + "origin": "hackage", + "synopsis": "Data type like Either but with accumulating error type", + "version": "0.2.0.2" + }, + { + "name": "ace", + "origin": "hackage", + "synopsis": "Attempto Controlled English parser and printer", + "version": "0.6" + }, + { + "name": "action-permutations", + "origin": "hackage", + "synopsis": "Execute a set of actions (e.g. parsers) in each possible order", + "version": "0.0.0.1" + }, + { + "name": "ad", + "origin": "hackage", + "synopsis": "Automatic Differentiation", + "version": "4.4.1" + }, + { + "name": "adjunctions", + "origin": "hackage", + "synopsis": "Adjunctions and representable functors", + "version": "4.4" + }, + { + "name": "adler32", + "origin": "hackage", + "synopsis": "An implementation of Adler-32, supporting rolling checksum operation", + "version": "0.1.2.0" + }, + { + "name": "aeson", + "origin": "hackage", + "synopsis": "Fast JSON parsing and encoding", + "version": "1.5.6.0" + }, + { + "name": "aeson-attoparsec", + "origin": "hackage", + "synopsis": "Embed an Attoparsec text parser into an Aeson parser", + "version": "0.0.0" + }, + { + "name": "aeson-better-errors", + "origin": "hackage", + "synopsis": "Better error messages when decoding JSON values.", + "version": "0.9.1.0" + }, + { + "name": "aeson-casing", + "origin": "hackage", + "synopsis": "Tools to change the formatting of field names in Aeson\ninstances.", + "version": "0.2.0.0" + }, + { + "name": "aeson-combinators", + "origin": "hackage", + "synopsis": "Aeson combinators for dead simple JSON decoding", + "version": "0.0.5.0" + }, + { + "name": "aeson-commit", + "origin": "hackage", + "synopsis": "Parse Aeson data with commitment", + "version": "1.3" + }, + { + "name": "aeson-compat", + "origin": "hackage", + "synopsis": "Compatibility layer for aeson", + "version": "0.3.9" + }, + { + "name": "aeson-default", + "origin": "hackage", + "synopsis": "Apply default value to FromJSON instacnes' Maybe fields", + "version": "0.9.1.0" + }, + { + "name": "aeson-diff", + "origin": "hackage", + "synopsis": "Extract and apply patches to JSON documents.", + "version": "1.1.0.9" + }, + { + "name": "aeson-generic-compat", + "origin": "hackage", + "synopsis": "Compatible generic class names of Aeson", + "version": "0.0.1.3" + }, + { + "name": "aeson-lens", + "origin": "hackage", + "synopsis": "Lens of Aeson", + "version": "0.5.0.0" + }, + { + "name": "aeson-optics", + "origin": "hackage", + "synopsis": "Law-abiding optics for aeson", + "version": "1.1.0.1" + }, + { + "name": "aeson-picker", + "origin": "hackage", + "synopsis": "Tiny library to get fields from JSON format", + "version": "0.1.0.5" + }, + { + "name": "aeson-pretty", + "origin": "hackage", + "synopsis": "JSON pretty-printing library and command-line tool.", + "version": "0.8.8" + }, + { + "name": "aeson-qq", + "origin": "hackage", + "synopsis": "JSON quasiquoter for Haskell", + "version": "0.8.3" + }, + { + "name": "aeson-schemas", + "origin": "hackage", + "synopsis": "Easily consume JSON data on-demand with type-safety", + "version": "1.3.3" + }, + { + "name": "aeson-with", + "origin": "hackage", + "synopsis": "withXField combinators for aeson", + "version": "0.1.2.0" + }, + { + "name": "aeson-yak", + "origin": "hackage", + "synopsis": "Handle JSON that may or may not be a list, or exist", + "version": "0.1.1.3" + }, + { + "name": "aeson-yaml", + "origin": "hackage", + "synopsis": "Output any Aeson value as YAML (pure Haskell library)", + "version": "1.1.0.0" + }, + { + "name": "Agda", + "origin": "hackage", + "synopsis": "A dependently typed functional programming language and proof assistant", + "version": "2.6.1.3" + }, + { + "name": "agda2lagda", + "origin": "hackage", + "synopsis": "Translate .agda files into .lagda.tex files.", + "version": "0.2020.11.1" + }, + { + "name": "al", + "origin": "hackage", + "synopsis": "OpenAL 1.1 raw API.", + "version": "0.1.4.2" + }, + { + "name": "alarmclock", + "origin": "hackage", + "synopsis": "Wake up and perform an action at a certain time.", + "version": "0.7.0.5" + }, + { + "name": "alerts", + "origin": "hackage", + "synopsis": "Alert messages for web applications", + "version": "0.1.2.0" + }, + { + "name": "alex", + "origin": "hackage", + "synopsis": "Alex is a tool for generating lexical analysers in Haskell", + "version": "3.2.6" + }, + { + "name": "alg", + "origin": "hackage", + "synopsis": "Algebraic structures", + "version": "0.2.13.1" + }, + { + "name": "algebraic-graphs", + "origin": "hackage", + "synopsis": "A library for algebraic graph construction and transformation", + "version": "0.5" + }, + { + "name": "Allure", + "origin": "hackage", + "synopsis": "Near-future Sci-Fi roguelike and tactical squad combat game", + "version": "0.9.5.0" + }, + { + "name": "almost-fix", + "origin": "hackage", + "synopsis": "Recurse while a predicate is satisfied", + "version": "0.0.2" + }, + { + "name": "alsa-core", + "origin": "hackage", + "synopsis": "Binding to the ALSA Library API (Exceptions).", + "version": "0.5.0.1" + }, + { + "name": "alsa-mixer", + "origin": "hackage", + "synopsis": "Bindings to the ALSA simple mixer API.", + "version": "0.3.0" + }, + { + "name": "alsa-pcm", + "origin": "hackage", + "synopsis": "Binding to the ALSA Library API (PCM audio).", + "version": "0.6.1.1" + }, + { + "name": "alsa-seq", + "origin": "hackage", + "synopsis": "Binding to the ALSA Library API (MIDI sequencer).", + "version": "0.6.0.8" + }, + { + "name": "alternative-vector", + "origin": "hackage", + "synopsis": "Use vectors instead of lists for many and some", + "version": "0.0.0" + }, + { + "name": "ALUT", + "origin": "hackage", + "synopsis": "A binding for the OpenAL Utility Toolkit", + "version": "2.4.0.3" + }, + { + "name": "amazonka-apigateway", + "origin": "hackage", + "synopsis": "Amazon API Gateway SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-application-autoscaling", + "origin": "hackage", + "synopsis": "Amazon Application Auto Scaling SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-appstream", + "origin": "hackage", + "synopsis": "Amazon AppStream SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-athena", + "origin": "hackage", + "synopsis": "Amazon Athena SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-autoscaling", + "origin": "hackage", + "synopsis": "Amazon Auto Scaling SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-budgets", + "origin": "hackage", + "synopsis": "Amazon Budgets SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-certificatemanager", + "origin": "hackage", + "synopsis": "Amazon Certificate Manager SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cloudformation", + "origin": "hackage", + "synopsis": "Amazon CloudFormation SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cloudfront", + "origin": "hackage", + "synopsis": "Amazon CloudFront SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cloudhsm", + "origin": "hackage", + "synopsis": "Amazon CloudHSM SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cloudsearch", + "origin": "hackage", + "synopsis": "Amazon CloudSearch SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cloudsearch-domains", + "origin": "hackage", + "synopsis": "Amazon CloudSearch Domain SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cloudtrail", + "origin": "hackage", + "synopsis": "Amazon CloudTrail SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cloudwatch", + "origin": "hackage", + "synopsis": "Amazon CloudWatch SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cloudwatch-events", + "origin": "hackage", + "synopsis": "Amazon CloudWatch Events SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cloudwatch-logs", + "origin": "hackage", + "synopsis": "Amazon CloudWatch Logs SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-codebuild", + "origin": "hackage", + "synopsis": "Amazon CodeBuild SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-codecommit", + "origin": "hackage", + "synopsis": "Amazon CodeCommit SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-codedeploy", + "origin": "hackage", + "synopsis": "Amazon CodeDeploy SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-codepipeline", + "origin": "hackage", + "synopsis": "Amazon CodePipeline SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cognito-identity", + "origin": "hackage", + "synopsis": "Amazon Cognito Identity SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cognito-idp", + "origin": "hackage", + "synopsis": "Amazon Cognito Identity Provider SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-cognito-sync", + "origin": "hackage", + "synopsis": "Amazon Cognito Sync SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-config", + "origin": "hackage", + "synopsis": "Amazon Config SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-core", + "origin": "hackage", + "synopsis": "Core data types and functionality for Amazonka libraries.", + "version": "1.6.1" + }, + { + "name": "amazonka-datapipeline", + "origin": "hackage", + "synopsis": "Amazon Data Pipeline SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-devicefarm", + "origin": "hackage", + "synopsis": "Amazon Device Farm SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-directconnect", + "origin": "hackage", + "synopsis": "Amazon Direct Connect SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-discovery", + "origin": "hackage", + "synopsis": "Amazon Application Discovery Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-dms", + "origin": "hackage", + "synopsis": "Amazon Database Migration Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-ds", + "origin": "hackage", + "synopsis": "Amazon Directory Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-dynamodb", + "origin": "hackage", + "synopsis": "Amazon DynamoDB SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-dynamodb-streams", + "origin": "hackage", + "synopsis": "Amazon DynamoDB Streams SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-ecr", + "origin": "hackage", + "synopsis": "Amazon EC2 Container Registry SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-ecs", + "origin": "hackage", + "synopsis": "Amazon EC2 Container Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-efs", + "origin": "hackage", + "synopsis": "Amazon Elastic File System SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-elasticache", + "origin": "hackage", + "synopsis": "Amazon ElastiCache SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-elasticbeanstalk", + "origin": "hackage", + "synopsis": "Amazon Elastic Beanstalk SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-elasticsearch", + "origin": "hackage", + "synopsis": "Amazon Elasticsearch Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-elastictranscoder", + "origin": "hackage", + "synopsis": "Amazon Elastic Transcoder SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-elb", + "origin": "hackage", + "synopsis": "Amazon Elastic Load Balancing SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-elbv2", + "origin": "hackage", + "synopsis": "Amazon Elastic Load Balancing SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-emr", + "origin": "hackage", + "synopsis": "Amazon Elastic MapReduce SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-gamelift", + "origin": "hackage", + "synopsis": "Amazon GameLift SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-glacier", + "origin": "hackage", + "synopsis": "Amazon Glacier SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-glue", + "origin": "hackage", + "synopsis": "Amazon Glue SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-health", + "origin": "hackage", + "synopsis": "Amazon Health APIs and Notifications SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-iam", + "origin": "hackage", + "synopsis": "Amazon Identity and Access Management SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-importexport", + "origin": "hackage", + "synopsis": "Amazon Import/Export SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-inspector", + "origin": "hackage", + "synopsis": "Amazon Inspector SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-iot", + "origin": "hackage", + "synopsis": "Amazon IoT SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-iot-dataplane", + "origin": "hackage", + "synopsis": "Amazon IoT Data Plane SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-kinesis", + "origin": "hackage", + "synopsis": "Amazon Kinesis SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-kinesis-analytics", + "origin": "hackage", + "synopsis": "Amazon Kinesis Analytics SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-kinesis-firehose", + "origin": "hackage", + "synopsis": "Amazon Kinesis Firehose SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-kms", + "origin": "hackage", + "synopsis": "Amazon Key Management Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-lambda", + "origin": "hackage", + "synopsis": "Amazon Lambda SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-lightsail", + "origin": "hackage", + "synopsis": "Amazon Lightsail SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-marketplace-analytics", + "origin": "hackage", + "synopsis": "Amazon Marketplace Commerce Analytics SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-marketplace-metering", + "origin": "hackage", + "synopsis": "Amazon Marketplace Metering SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-ml", + "origin": "hackage", + "synopsis": "Amazon Machine Learning SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-opsworks", + "origin": "hackage", + "synopsis": "Amazon OpsWorks SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-opsworks-cm", + "origin": "hackage", + "synopsis": "Amazon OpsWorks for Chef Automate SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-pinpoint", + "origin": "hackage", + "synopsis": "Amazon Pinpoint SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-polly", + "origin": "hackage", + "synopsis": "Amazon Polly SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-rds", + "origin": "hackage", + "synopsis": "Amazon Relational Database Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-redshift", + "origin": "hackage", + "synopsis": "Amazon Redshift SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-rekognition", + "origin": "hackage", + "synopsis": "Amazon Rekognition SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-route53", + "origin": "hackage", + "synopsis": "Amazon Route 53 SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-route53-domains", + "origin": "hackage", + "synopsis": "Amazon Route 53 Domains SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-s3", + "origin": "hackage", + "synopsis": "Amazon Simple Storage Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-sdb", + "origin": "hackage", + "synopsis": "Amazon SimpleDB SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-servicecatalog", + "origin": "hackage", + "synopsis": "Amazon Service Catalog SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-ses", + "origin": "hackage", + "synopsis": "Amazon Simple Email Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-shield", + "origin": "hackage", + "synopsis": "Amazon Shield SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-sms", + "origin": "hackage", + "synopsis": "Amazon Server Migration Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-snowball", + "origin": "hackage", + "synopsis": "Amazon Import/Export Snowball SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-sns", + "origin": "hackage", + "synopsis": "Amazon Simple Notification Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-sqs", + "origin": "hackage", + "synopsis": "Amazon Simple Queue Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-ssm", + "origin": "hackage", + "synopsis": "Amazon Simple Systems Manager (SSM) SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-stepfunctions", + "origin": "hackage", + "synopsis": "Amazon Step Functions SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-storagegateway", + "origin": "hackage", + "synopsis": "Amazon Storage Gateway SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-sts", + "origin": "hackage", + "synopsis": "Amazon Security Token Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-support", + "origin": "hackage", + "synopsis": "Amazon Support SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-swf", + "origin": "hackage", + "synopsis": "Amazon Simple Workflow Service SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-test", + "origin": "hackage", + "synopsis": "Common functionality for Amazonka library test-suites.", + "version": "1.6.1" + }, + { + "name": "amazonka-waf", + "origin": "hackage", + "synopsis": "Amazon WAF SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-workspaces", + "origin": "hackage", + "synopsis": "Amazon WorkSpaces SDK.", + "version": "1.6.1" + }, + { + "name": "amazonka-xray", + "origin": "hackage", + "synopsis": "Amazon X-Ray SDK.", + "version": "1.6.1" + }, + { + "name": "amqp", + "origin": "hackage", + "synopsis": "Client library for AMQP servers (currently only RabbitMQ)", + "version": "0.20.0.1" + }, + { + "name": "amqp-utils", + "origin": "hackage", + "synopsis": "AMQP toolset for the command line", + "version": "0.4.5.1" + }, + { + "name": "annotated-wl-pprint", + "origin": "hackage", + "synopsis": "The Wadler/Leijen Pretty Printer, with annotation support", + "version": "0.7.0" + }, + { + "name": "ansi-terminal", + "origin": "hackage", + "synopsis": "Simple ANSI terminal support, with Windows compatibility", + "version": "0.10.3" + }, + { + "name": "ansi-wl-pprint", + "origin": "hackage", + "synopsis": "The Wadler/Leijen Pretty Printer for colored ANSI terminal output", + "version": "0.6.9" + }, + { + "name": "ANum", + "origin": "hackage", + "synopsis": "Num instance for Applicatives provided via the ANum newtype", + "version": "0.2.0.2" + }, + { + "name": "apecs", + "origin": "hackage", + "synopsis": "Fast Entity-Component-System library for game programming", + "version": "0.9.2" + }, + { + "name": "apecs-gloss", + "origin": "hackage", + "synopsis": "Simple gloss renderer for apecs", + "version": "0.2.4" + }, + { + "name": "apecs-physics", + "origin": "hackage", + "synopsis": "2D physics for apecs", + "version": "0.4.5" + }, + { + "name": "api-field-json-th", + "origin": "hackage", + "synopsis": "option of aeson's deriveJSON ", + "version": "0.1.0.2" + }, + { + "name": "api-maker", + "origin": "hackage", + "synopsis": "Package to make APIs", + "version": "0.1.0.0" + }, + { + "name": "ap-normalize", + "origin": "hackage", + "synopsis": "Self-normalizing applicative expressions", + "version": "0.1.0.0" + }, + { + "name": "appar", + "origin": "hackage", + "synopsis": "A simple applicative parser", + "version": "0.1.8" + }, + { + "name": "appendmap", + "origin": "hackage", + "synopsis": "Map with a Semigroup and Monoid instances delegating to Semigroup of the elements", + "version": "0.1.5" + }, + { + "name": "apply-refact", + "origin": "hackage", + "synopsis": "Perform refactorings specified by the refact library.", + "version": "0.9.2.0" + }, + { + "name": "apportionment", + "origin": "hackage", + "synopsis": "Round a set of numbers while maintaining its sum", + "version": "0.0.0.3" + }, + { + "name": "approximate", + "origin": "hackage", + "synopsis": "Approximate discrete values and numbers", + "version": "0.3.4" + }, + { + "name": "approximate-equality", + "origin": "hackage", + "synopsis": "Newtype wrappers for approximate equality", + "version": "1.1.0.2" + }, + { + "name": "app-settings", + "origin": "hackage", + "synopsis": "A library to manage application settings (INI file-like)", + "version": "0.2.0.12" + }, + { + "name": "arbor-lru-cache", + "origin": "hackage", + "synopsis": "LRU cache based on STM", + "version": "0.1.1.1" + }, + { + "name": "arbor-postgres", + "origin": "hackage", + "synopsis": "Convenience types and functions for postgresql-simple.", + "version": "0.0.5" + }, + { + "name": "arithmoi", + "origin": "hackage", + "synopsis": "Efficient basic number-theoretic functions.", + "version": "0.11.0.1" + }, + { + "name": "array", + "origin": "core", + "synopsis": "Mutable and immutable arrays", + "version": "0.5.4.0" + }, + { + "name": "array-memoize", + "origin": "hackage", + "synopsis": "Memoization combinators using arrays for finite sub-domains of functions", + "version": "0.6.0" + }, + { + "name": "arrow-extras", + "origin": "hackage", + "synopsis": "Extra functions for Control.Arrow", + "version": "0.1.0.1" + }, + { + "name": "ascii", + "origin": "hackage", + "synopsis": "The ASCII character set and encoding", + "version": "1.0.1.4" + }, + { + "name": "ascii-case", + "origin": "hackage", + "synopsis": "ASCII letter case", + "version": "1.0.0.4" + }, + { + "name": "ascii-char", + "origin": "hackage", + "synopsis": "A Char type representing an ASCII character", + "version": "1.0.0.8" + }, + { + "name": "asciidiagram", + "origin": "hackage", + "synopsis": "Pretty rendering of Ascii diagram into svg or png.", + "version": "1.3.3.3" + }, + { + "name": "ascii-group", + "origin": "hackage", + "synopsis": "ASCII character groups", + "version": "1.0.0.4" + }, + { + "name": "ascii-predicates", + "origin": "hackage", + "synopsis": "Various categorizations of ASCII characters", + "version": "1.0.0.4" + }, + { + "name": "ascii-progress", + "origin": "hackage", + "synopsis": "A simple progress bar for the console.", + "version": "0.3.3.0" + }, + { + "name": "ascii-superset", + "origin": "hackage", + "synopsis": "Representing ASCII with refined supersets", + "version": "1.0.1.4" + }, + { + "name": "ascii-th", + "origin": "hackage", + "synopsis": "Template Haskell support for ASCII", + "version": "1.0.0.4" + }, + { + "name": "asif", + "origin": "hackage", + "synopsis": "Library for creating and querying segmented feeds", + "version": "6.0.4" + }, + { + "name": "asn1-encoding", + "origin": "hackage", + "synopsis": "ASN1 data reader and writer in RAW, BER and DER forms", + "version": "0.9.6" + }, + { + "name": "asn1-parse", + "origin": "hackage", + "synopsis": "Simple monadic parser for ASN1 stream types.", + "version": "0.9.5" + }, + { + "name": "asn1-types", + "origin": "hackage", + "synopsis": "ASN.1 types", + "version": "0.3.4" + }, + { + "name": "assert-failure", + "origin": "hackage", + "synopsis": "Syntactic sugar improving 'assert' and 'error'", + "version": "0.1.2.5" + }, + { + "name": "assoc", + "origin": "hackage", + "synopsis": "swap and assoc: Symmetric and Semigroupy Bifunctors", + "version": "1.0.2" + }, + { + "name": "astro", + "origin": "hackage", + "synopsis": "Amateur astronomical computations", + "version": "0.4.2.1" + }, + { + "name": "async", + "origin": "hackage", + "synopsis": "Run IO operations asynchronously and wait for their results", + "version": "2.2.3" + }, + { + "name": "async-extra", + "origin": "hackage", + "synopsis": "Useful concurrent combinators", + "version": "0.2.0.0" + }, + { + "name": "async-pool", + "origin": "hackage", + "synopsis": "A modified version of async that supports worker groups and many-to-many task dependencies", + "version": "0.9.1" + }, + { + "name": "async-refresh", + "origin": "hackage", + "synopsis": "Package implementing core logic for refreshing of expiring data.", + "version": "0.3.0.0" + }, + { + "name": "async-refresh-tokens", + "origin": "hackage", + "synopsis": "Package implementing core logic for refreshing of expiring access tokens", + "version": "0.4.0.0" + }, + { + "name": "atom-basic", + "origin": "hackage", + "synopsis": "Basic Atom feed construction", + "version": "0.2.5" + }, + { + "name": "atomic-primops", + "origin": "hackage", + "synopsis": "A safe approach to CAS and other atomic ops in Haskell.", + "version": "0.8.4" + }, + { + "name": "atomic-write", + "origin": "hackage", + "synopsis": "Atomically write to a file", + "version": "0.2.0.7" + }, + { + "name": "attoparsec", + "origin": "hackage", + "synopsis": "Fast combinator parsing for bytestrings and text", + "version": "0.13.2.5" + }, + { + "name": "attoparsec-base64", + "origin": "hackage", + "synopsis": "Fetch only base64 characters, erroring in the attoparsec monad on failure", + "version": "0.0.0" + }, + { + "name": "attoparsec-binary", + "origin": "hackage", + "synopsis": "Binary processing extensions to Attoparsec.", + "version": "0.2" + }, + { + "name": "attoparsec-expr", + "origin": "hackage", + "synopsis": "Port of parsec's expression parser to attoparsec.", + "version": "0.1.1.2" + }, + { + "name": "attoparsec-iso8601", + "origin": "hackage", + "synopsis": "Parsing of ISO 8601 dates, originally from aeson.", + "version": "1.0.2.0" + }, + { + "name": "attoparsec-path", + "origin": "hackage", + "synopsis": "Convenience bindings between path and attoparsec", + "version": "0.0.0.1" + }, + { + "name": "audacity", + "origin": "hackage", + "synopsis": "Interchange with the Audacity sound signal editor", + "version": "0.0.2" + }, + { + "name": "aur", + "origin": "hackage", + "synopsis": "Access metadata from the Arch Linux User Repository.", + "version": "7.0.6" + }, + { + "name": "aura", + "origin": "hackage", + "synopsis": "A secure package manager for Arch Linux and the AUR.", + "version": "3.2.4" + }, + { + "name": "authenticate", + "origin": "hackage", + "synopsis": "Authentication methods for Haskell web applications.", + "version": "1.3.5" + }, + { + "name": "authenticate-oauth", + "origin": "hackage", + "synopsis": "Library to authenticate with OAuth for Haskell web applications.", + "version": "1.6.0.1" + }, + { + "name": "auto", + "origin": "hackage", + "synopsis": "Denotative, locally stateful programming DSL & platform", + "version": "0.4.3.1" + }, + { + "name": "autoexporter", + "origin": "hackage", + "synopsis": "Automatically re-export modules.", + "version": "1.1.20" + }, + { + "name": "auto-update", + "origin": "hackage", + "synopsis": "Efficiently run periodic, on-demand actions", + "version": "0.1.6" + }, + { + "name": "avers", + "origin": "hackage", + "synopsis": "Server-side implementation of the Avers storage model", + "version": "0.0.17.1" + }, + { + "name": "avro", + "origin": "hackage", + "synopsis": "Avro serialization support for Haskell", + "version": "0.5.2.0" + }, + { + "name": "aws-cloudfront-signed-cookies", + "origin": "hackage", + "synopsis": "Generate signed cookies for AWS CloudFront", + "version": "0.2.0.6" + }, + { + "name": "backprop", + "origin": "hackage", + "synopsis": "Heterogeneous automatic differentation", + "version": "0.2.6.4" + }, + { + "name": "backtracking", + "origin": "hackage", + "synopsis": "A backtracking monad", + "version": "0.1.0" + }, + { + "name": "bank-holidays-england", + "origin": "hackage", + "synopsis": "Calculation of bank holidays in England and Wales", + "version": "0.2.0.6" + }, + { + "name": "barbies", + "origin": "hackage", + "synopsis": "Classes for working with types that can change clothes.", + "version": "2.0.2.0" + }, + { + "name": "base", + "origin": "core", + "synopsis": "Basic libraries", + "version": "4.14.1.0" + }, + { + "name": "base16", + "origin": "hackage", + "synopsis": "Fast RFC 4648-compliant Base16 encoding", + "version": "0.3.0.1" + }, + { + "name": "base16-bytestring", + "origin": "hackage", + "synopsis": "Fast base16 (hex) encoding and decoding for ByteStrings", + "version": "0.1.1.7" + }, + { + "name": "base16-lens", + "origin": "hackage", + "synopsis": "Optics for the Base16 library", + "version": "0.1.3.2" + }, + { + "name": "base32", + "origin": "hackage", + "synopsis": "Fast RFC 4648-compliant Base32 encoding", + "version": "0.2.0.0" + }, + { + "name": "base32-lens", + "origin": "hackage", + "synopsis": "Optics for the Base32 library", + "version": "0.1.1.1" + }, + { + "name": "base32string", + "origin": "hackage", + "synopsis": "Fast and safe representation of a Base-32 string", + "version": "0.9.1" + }, + { + "name": "base58-bytestring", + "origin": "hackage", + "synopsis": "Implementation of BASE58 transcoding for ByteStrings", + "version": "0.1.0" + }, + { + "name": "base58string", + "origin": "hackage", + "synopsis": "Fast and safe representation of a Base-58 string", + "version": "0.10.0" + }, + { + "name": "base64", + "origin": "hackage", + "synopsis": "A modern RFC 4648-compliant Base64 library", + "version": "0.4.2.3" + }, + { + "name": "base64-bytestring", + "origin": "hackage", + "synopsis": "Fast base64 encoding and decoding for ByteStrings", + "version": "1.1.0.0" + }, + { + "name": "base64-bytestring-type", + "origin": "hackage", + "synopsis": "A newtype around ByteString, for base64 encoding", + "version": "1.0.1" + }, + { + "name": "base64-lens", + "origin": "hackage", + "synopsis": "Optics for the Base64 library", + "version": "0.3.1" + }, + { + "name": "base64-string", + "origin": "hackage", + "synopsis": "Base64 implementation for String's.", + "version": "0.2" + }, + { + "name": "base-compat", + "origin": "hackage", + "synopsis": "A compatibility layer for base", + "version": "0.11.2" + }, + { + "name": "base-compat-batteries", + "origin": "hackage", + "synopsis": "base-compat with extra batteries", + "version": "0.11.2" + }, + { + "name": "basement", + "origin": "hackage", + "synopsis": "Foundation scrap box of array & string", + "version": "0.0.11" + }, + { + "name": "base-orphans", + "origin": "hackage", + "synopsis": "Backwards-compatible orphan instances for base", + "version": "0.8.4" + }, + { + "name": "base-prelude", + "origin": "hackage", + "synopsis": "The most complete prelude formed solely from the \"base\" package", + "version": "1.4" + }, + { + "name": "base-unicode-symbols", + "origin": "hackage", + "synopsis": "Unicode alternatives for common functions and operators", + "version": "0.2.4.2" + }, + { + "name": "basic-prelude", + "origin": "hackage", + "synopsis": "An enhanced core prelude; a common foundation for alternate preludes.", + "version": "0.7.0" + }, + { + "name": "bazel-runfiles", + "origin": "hackage", + "synopsis": "Locate Bazel runfiles location", + "version": "0.12" + }, + { + "name": "bbdb", + "origin": "hackage", + "synopsis": "Ability to read, write, and modify BBDB files", + "version": "0.8" + }, + { + "name": "bcrypt", + "origin": "hackage", + "synopsis": "Haskell bindings to the bcrypt password hash", + "version": "0.0.11" + }, + { + "name": "bech32", + "origin": "hackage", + "synopsis": "Implementation of the Bech32 cryptocurrency address format (BIP 0173).", + "version": "1.1.0" + }, + { + "name": "bech32-th", + "origin": "hackage", + "synopsis": "Template Haskell extensions to the Bech32 library.", + "version": "1.0.2" + }, + { + "name": "bench", + "origin": "hackage", + "synopsis": "Command-line benchmark tool", + "version": "1.0.12" + }, + { + "name": "benchpress", + "origin": "hackage", + "synopsis": "Micro-benchmarking with detailed statistics.", + "version": "0.2.2.16" + }, + { + "name": "between", + "origin": "hackage", + "synopsis": "Function combinator \"between\" and derived combinators", + "version": "0.11.0.0" + }, + { + "name": "bibtex", + "origin": "hackage", + "synopsis": "Parse, format and processing BibTeX files", + "version": "0.1.0.6" + }, + { + "name": "bifunctors", + "origin": "hackage", + "synopsis": "Bifunctors", + "version": "5.5.10" + }, + { + "name": "bimap", + "origin": "hackage", + "synopsis": "Bidirectional mapping between two key types", + "version": "0.4.0" + }, + { + "name": "bimaps", + "origin": "hackage", + "synopsis": "bijections with multiple implementations.", + "version": "0.1.0.2" + }, + { + "name": "bimap-server", + "origin": "hackage", + "synopsis": "Two-column database server.", + "version": "0.1.0.1" + }, + { + "name": "bin", + "origin": "hackage", + "synopsis": "Bin: binary natural numbers.", + "version": "0.1" + }, + { + "name": "binary", + "origin": "core", + "synopsis": "Binary serialisation for Haskell values using lazy ByteStrings", + "version": "0.8.8.0" + }, + { + "name": "binary-conduit", + "origin": "hackage", + "synopsis": "data serialization/deserialization conduit library", + "version": "1.3.1" + }, + { + "name": "binary-ext", + "origin": "hackage", + "synopsis": "An alternate with strong-typed errors for `Data.Binary.Get` monad from `binary` package.", + "version": "2.0.4" + }, + { + "name": "binary-ieee754", + "origin": "hackage", + "synopsis": "Backport ieee754 float double combinators to older binary", + "version": "0.1.0.0" + }, + { + "name": "binary-instances", + "origin": "hackage", + "synopsis": "Orphan instances for binary", + "version": "1.0.1" + }, + { + "name": "binary-list", + "origin": "hackage", + "synopsis": "Lists of length a power of two.", + "version": "1.1.1.2" + }, + { + "name": "binary-orphans", + "origin": "hackage", + "synopsis": "Compatibility package for binary; provides instances", + "version": "1.0.1" + }, + { + "name": "binary-parser", + "origin": "hackage", + "synopsis": "A highly-efficient but limited parser API specialised for bytestrings", + "version": "0.5.7" + }, + { + "name": "binary-parsers", + "origin": "hackage", + "synopsis": "Extends binary with parsec/attoparsec style parsing combinators.", + "version": "0.2.4.0" + }, + { + "name": "binary-search", + "origin": "hackage", + "synopsis": "Binary and exponential searches", + "version": "1.0.0.3" + }, + { + "name": "binary-shared", + "origin": "hackage", + "synopsis": "Sharing for the binary package", + "version": "0.8.3" + }, + { + "name": "binary-tagged", + "origin": "hackage", + "synopsis": "Tagged binary serialisation.", + "version": "0.3" + }, + { + "name": "bindings-DSL", + "origin": "hackage", + "synopsis": "FFI domain specific language, on top of hsc2hs.", + "version": "1.0.25" + }, + { + "name": "bindings-GLFW", + "origin": "hackage", + "synopsis": "Low-level bindings to GLFW OpenGL library", + "version": "3.3.2.0" + }, + { + "name": "bindings-libzip", + "origin": "hackage", + "synopsis": "Low level bindings to libzip.", + "version": "1.0.1" + }, + { + "name": "bindings-uname", + "origin": "hackage", + "synopsis": "Low-level binding to POSIX uname(3)", + "version": "0.1" + }, + { + "name": "bins", + "origin": "hackage", + "synopsis": "Aggregate continuous values into discrete bins", + "version": "0.1.2.0" + }, + { + "name": "bitarray", + "origin": "hackage", + "synopsis": "Mutable and immutable bit arrays", + "version": "0.0.1.1" + }, + { + "name": "bits", + "origin": "hackage", + "synopsis": "Various bit twiddling and bitwise serialization primitives", + "version": "0.5.3" + }, + { + "name": "bitset-word8", + "origin": "hackage", + "synopsis": "Space efficient set of Word8 and some pre-canned sets useful for parsing HTTP", + "version": "0.1.1.2" + }, + { + "name": "bits-extra", + "origin": "hackage", + "synopsis": "Useful bitwise operations", + "version": "0.0.2.0" + }, + { + "name": "bitvec", + "origin": "hackage", + "synopsis": "Space-efficient bit vectors", + "version": "1.0.3.0" + }, + { + "name": "bitwise-enum", + "origin": "hackage", + "synopsis": "Bitwise operations on bounded enumerations", + "version": "1.0.1.0" + }, + { + "name": "blake2", + "origin": "hackage", + "synopsis": "A library providing BLAKE2", + "version": "0.3.0" + }, + { + "name": "blanks", + "origin": "hackage", + "synopsis": "Fill-in-the-blanks - A library factoring out substitution from ASTs", + "version": "0.5.0" + }, + { + "name": "blas-carray", + "origin": "hackage", + "synopsis": "Auto-generated interface to Fortran BLAS via CArrays", + "version": "0.1.0.1" + }, + { + "name": "blas-comfort-array", + "origin": "hackage", + "synopsis": "Auto-generated interface to Fortran BLAS via comfort-array", + "version": "0.0.0.2" + }, + { + "name": "blas-ffi", + "origin": "hackage", + "synopsis": "Auto-generated interface to Fortran BLAS", + "version": "0.1" + }, + { + "name": "blaze-bootstrap", + "origin": "hackage", + "synopsis": "Blaze helper functions for bootstrap pages", + "version": "0.1.0.1" + }, + { + "name": "blaze-builder", + "origin": "hackage", + "synopsis": "Efficient buffered output.", + "version": "0.4.2.1" + }, + { + "name": "blaze-html", + "origin": "hackage", + "synopsis": "A blazingly fast HTML combinator library for Haskell", + "version": "0.9.1.2" + }, + { + "name": "blaze-markup", + "origin": "hackage", + "synopsis": "A blazingly fast markup combinator library for Haskell", + "version": "0.8.2.8" + }, + { + "name": "blaze-svg", + "origin": "hackage", + "synopsis": "SVG combinator library", + "version": "0.3.6.1" + }, + { + "name": "blaze-textual", + "origin": "hackage", + "synopsis": "Fast rendering of common datatypes", + "version": "0.2.1.0" + }, + { + "name": "bmp", + "origin": "hackage", + "synopsis": "Read and write uncompressed BMP image files.", + "version": "1.2.6.3" + }, + { + "name": "BNFC", + "origin": "hackage", + "synopsis": "A compiler front-end generator.", + "version": "2.9.1" + }, + { + "name": "board-games", + "origin": "hackage", + "synopsis": "Three games for inclusion in a web server", + "version": "0.3" + }, + { + "name": "boltzmann-samplers", + "origin": "hackage", + "synopsis": "Uniform random generators", + "version": "0.1.1.0" + }, + { + "name": "Boolean", + "origin": "hackage", + "synopsis": "Generalized booleans and numbers", + "version": "0.2.4" + }, + { + "name": "boolean-like", + "origin": "hackage", + "synopsis": "Logical combinatory operations dealing with datatypes\nrepresenting booleans by their constructors.", + "version": "0.1.1.0" + }, + { + "name": "boolsimplifier", + "origin": "hackage", + "synopsis": "Simplification tools for simple propositional formulas.", + "version": "0.1.8" + }, + { + "name": "boots", + "origin": "hackage", + "synopsis": "IoC Monad in Haskell", + "version": "0.2.0.1" + }, + { + "name": "bordacount", + "origin": "hackage", + "synopsis": "Implementation of the Borda count election method.", + "version": "0.1.0.0" + }, + { + "name": "boring", + "origin": "hackage", + "synopsis": "Boring and Absurd types", + "version": "0.1.3" + }, + { + "name": "both", + "origin": "hackage", + "synopsis": "Like Maybe, but with a different Monoid instance.", + "version": "0.1.1.1" + }, + { + "name": "bound", + "origin": "hackage", + "synopsis": "Making de Bruijn Succ Less", + "version": "2.0.3" + }, + { + "name": "BoundedChan", + "origin": "hackage", + "synopsis": "Implementation of bounded channels.", + "version": "1.0.3.0" + }, + { + "name": "bounded-queue", + "origin": "hackage", + "synopsis": "A strict, immutable, thread-safe, single-ended, bounded queue.", + "version": "1.0.0" + }, + { + "name": "boundingboxes", + "origin": "hackage", + "synopsis": "A generic boundingbox for an arbitrary vector", + "version": "0.2.3" + }, + { + "name": "bower-json", + "origin": "hackage", + "synopsis": "Read bower.json from Haskell", + "version": "1.0.0.1" + }, + { + "name": "boxes", + "origin": "hackage", + "synopsis": "2D text pretty-printing library", + "version": "0.1.5" + }, + { + "name": "brick", + "origin": "hackage", + "synopsis": "A declarative terminal user interface library", + "version": "0.58.1" + }, + { + "name": "broadcast-chan", + "origin": "hackage", + "synopsis": "Closable, fair, single-wakeup channel type that avoids 0\nreader space leaks.", + "version": "0.2.1.1" + }, + { + "name": "bsb-http-chunked", + "origin": "hackage", + "synopsis": "Chunked HTTP transfer encoding for bytestring builders", + "version": "0.0.0.4" + }, + { + "name": "bson", + "origin": "hackage", + "synopsis": "BSON documents are JSON-like objects with a standard binary\nencoding.", + "version": "0.4.0.1" + }, + { + "name": "btrfs", + "origin": "hackage", + "synopsis": "Bindings to the btrfs API", + "version": "0.2.0.0" + }, + { + "name": "buffer-builder", + "origin": "hackage", + "synopsis": "Library for efficiently building up buffers, one piece at a time", + "version": "0.2.4.7" + }, + { + "name": "buffer-pipe", + "origin": "hackage", + "synopsis": "Read from stdin and write to stdout in large blocks", + "version": "0.0" + }, + { + "name": "bugsnag-haskell", + "origin": "hackage", + "synopsis": "Bugsnag error reporter for Haskell", + "version": "0.0.4.1" + }, + { + "name": "bugsnag-hs", + "origin": "hackage", + "synopsis": "A Bugsnag client for Haskell.", + "version": "0.2.0.3" + }, + { + "name": "bugzilla-redhat", + "origin": "hackage", + "synopsis": "A Haskell interface to the Bugzilla native REST API", + "version": "0.3.1" + }, + { + "name": "burrito", + "origin": "hackage", + "synopsis": "Parse and render URI templates.", + "version": "1.2.0.1" + }, + { + "name": "butcher", + "origin": "hackage", + "synopsis": "Chops a command or program invocation into digestable pieces.", + "version": "1.3.3.2" + }, + { + "name": "bv", + "origin": "hackage", + "synopsis": "Bit-vector arithmetic library", + "version": "0.5" + }, + { + "name": "bv-little", + "origin": "hackage", + "synopsis": "Efficient little-endian bit vector library", + "version": "1.1.1" + }, + { + "name": "byteable", + "origin": "hackage", + "synopsis": "Type class for sequence of bytes", + "version": "0.1.1" + }, + { + "name": "byte-count-reader", + "origin": "hackage", + "synopsis": "Read strings describing a number of bytes like 2Kb and 0.5 MiB", + "version": "0.10.1.2" + }, + { + "name": "bytedump", + "origin": "hackage", + "synopsis": "Flexible byte dump helpers for human readers.", + "version": "1.0" + }, + { + "name": "byte-order", + "origin": "hackage", + "synopsis": "Portable big-endian and little-endian conversions", + "version": "0.1.2.0" + }, + { + "name": "byteorder", + "origin": "hackage", + "synopsis": "Exposes the native endianness or byte ordering of the system.", + "version": "1.0.4" + }, + { + "name": "bytes", + "origin": "hackage", + "synopsis": "Sharing code for serialization between binary and cereal", + "version": "0.17.1" + }, + { + "name": "byteset", + "origin": "hackage", + "synopsis": "Set of bytes.", + "version": "0.1.1.0" + }, + { + "name": "bytestring", + "origin": "core", + "synopsis": "Fast, compact, strict and lazy byte strings with a list interface", + "version": "0.10.12.0" + }, + { + "name": "bytestring-builder", + "origin": "hackage", + "synopsis": "The new bytestring builder, packaged outside of GHC", + "version": "0.10.8.2.0" + }, + { + "name": "bytestring-conversion", + "origin": "hackage", + "synopsis": "Type-classes to convert values to and from ByteString.", + "version": "0.3.1" + }, + { + "name": "bytestring-lexing", + "origin": "hackage", + "synopsis": "Parse and produce literals efficiently from strict or lazy bytestrings.", + "version": "0.5.0.2" + }, + { + "name": "bytestring-mmap", + "origin": "hackage", + "synopsis": "mmap support for strict ByteStrings", + "version": "0.2.2" + }, + { + "name": "bytestring-strict-builder", + "origin": "hackage", + "synopsis": "An efficient strict bytestring builder", + "version": "0.4.5.4" + }, + { + "name": "bytestring-to-vector", + "origin": "hackage", + "synopsis": "Convert between ByteString and Vector.Storable without copying", + "version": "0.3.0.1" + }, + { + "name": "bytestring-tree-builder", + "origin": "hackage", + "synopsis": "A very efficient ByteString builder implementation based on the binary tree", + "version": "0.2.7.9" + }, + { + "name": "bz2", + "origin": "hackage", + "synopsis": "Bindings to libbz2", + "version": "1.0.1.0" + }, + { + "name": "bzlib", + "origin": "hackage", + "synopsis": "Compression and decompression in the bzip2 format", + "version": "0.5.1.0" + }, + { + "name": "bzlib-conduit", + "origin": "hackage", + "synopsis": "Streaming compression/decompression via conduits.", + "version": "0.3.0.2" + }, + { + "name": "c14n", + "origin": "hackage", + "synopsis": "Bindings to the c14n implementation in libxml.", + "version": "0.1.0.1" + }, + { + "name": "c2hs", + "origin": "hackage", + "synopsis": "C->Haskell FFI tool that gives some cross-language type safety", + "version": "0.28.7" + }, + { + "name": "Cabal", + "origin": "core", + "synopsis": "A framework for packaging Haskell software", + "version": "3.2.1.0" + }, + { + "name": "cabal2nix", + "origin": "hackage", + "synopsis": "Convert Cabal files into Nix build instructions.", + "version": "2.16.0" + }, + { + "name": "cabal2spec", + "origin": "hackage", + "synopsis": "Convert Cabal files into rpm spec files", + "version": "2.6.2" + }, + { + "name": "cabal-appimage", + "origin": "hackage", + "synopsis": "Cabal support for creating AppImage applications", + "version": "0.3.0.2" + }, + { + "name": "cabal-doctest", + "origin": "hackage", + "synopsis": "A Setup.hs helper for doctests running", + "version": "1.0.8" + }, + { + "name": "cabal-file", + "origin": "hackage", + "synopsis": "Cabal file access", + "version": "0.1.1" + }, + { + "name": "cabal-flatpak", + "origin": "hackage", + "synopsis": "Generate a FlatPak manifest from a Cabal package description", + "version": "0.1.0.2" + }, + { + "name": "cabal-plan", + "origin": "hackage", + "synopsis": "Library and utility for processing cabal's plan.json file", + "version": "0.7.2.0" + }, + { + "name": "cabal-rpm", + "origin": "hackage", + "synopsis": "RPM packaging tool for Haskell Cabal-based packages", + "version": "2.0.8" + }, + { + "name": "cache", + "origin": "hackage", + "synopsis": "An in-memory key/value store with expiration support", + "version": "0.1.3.0" + }, + { + "name": "cacophony", + "origin": "hackage", + "synopsis": "A library implementing the Noise protocol.", + "version": "0.10.1" + }, + { + "name": "calendar-recycling", + "origin": "hackage", + "synopsis": "List years with the same calendars", + "version": "0.0.0.1" + }, + { + "name": "call-stack", + "origin": "hackage", + "synopsis": "Use GHC call-stacks in a backward compatible way", + "version": "0.2.0" + }, + { + "name": "can-i-haz", + "origin": "hackage", + "synopsis": "Generic implementation of the Has and CoHas patterns", + "version": "0.3.1.0" + }, + { + "name": "ca-province-codes", + "origin": "hackage", + "synopsis": "ISO 3166-2:CA Province Codes and Names", + "version": "1.0.0.0" + }, + { + "name": "cardano-coin-selection", + "origin": "hackage", + "synopsis": "Algorithms for coin selection and fee balancing.", + "version": "1.0.1" + }, + { + "name": "carray", + "origin": "hackage", + "synopsis": "A C-compatible array library.", + "version": "0.1.6.8" + }, + { + "name": "casa-client", + "origin": "hackage", + "synopsis": "Client for Casa", + "version": "0.0.1" + }, + { + "name": "casa-types", + "origin": "hackage", + "synopsis": "Types for Casa", + "version": "0.0.1" + }, + { + "name": "cased", + "origin": "hackage", + "synopsis": "Track string casing in its type", + "version": "0.1.0.0" + }, + { + "name": "case-insensitive", + "origin": "hackage", + "synopsis": "Case insensitive string comparison", + "version": "1.2.1.0" + }, + { + "name": "cases", + "origin": "hackage", + "synopsis": "A converter for spinal, snake and camel cases", + "version": "0.1.4.1" + }, + { + "name": "casing", + "origin": "hackage", + "synopsis": "Convert between various source code casing conventions", + "version": "0.1.4.1" + }, + { + "name": "cassava", + "origin": "hackage", + "synopsis": "A CSV parsing and encoding library", + "version": "0.5.2.0" + }, + { + "name": "cassava-conduit", + "origin": "hackage", + "synopsis": "Conduit interface for cassava package", + "version": "0.6.0" + }, + { + "name": "cassava-megaparsec", + "origin": "hackage", + "synopsis": "Megaparsec parser of CSV files that plays nicely with Cassava", + "version": "2.0.2" + }, + { + "name": "cast", + "origin": "hackage", + "synopsis": "Abstact cast pattern ", + "version": "0.1.0.2" + }, + { + "name": "category", + "origin": "hackage", + "synopsis": "Categorical types and classes", + "version": "0.2.5.0" + }, + { + "name": "cayley-client", + "origin": "hackage", + "synopsis": "A Haskell client for the Cayley graph database", + "version": "0.4.15" + }, + { + "name": "cborg", + "origin": "hackage", + "synopsis": "Concise Binary Object Representation (CBOR)", + "version": "0.2.5.0" + }, + { + "name": "cborg-json", + "origin": "hackage", + "synopsis": "A library for encoding JSON as CBOR", + "version": "0.2.2.0" + }, + { + "name": "cereal", + "origin": "hackage", + "synopsis": "A binary serialization library", + "version": "0.5.8.1" + }, + { + "name": "cereal-conduit", + "origin": "hackage", + "synopsis": "Turn Data.Serialize Gets and Puts into Sources, Sinks, and Conduits", + "version": "0.8.0" + }, + { + "name": "cereal-text", + "origin": "hackage", + "synopsis": "Data.Text instances for the cereal serialization library", + "version": "0.1.0.2" + }, + { + "name": "cereal-vector", + "origin": "hackage", + "synopsis": "Serialize instances for Data.Vector types.", + "version": "0.2.0.1" + }, + { + "name": "cfenv", + "origin": "hackage", + "synopsis": "A library getting the environment when running on Cloud Foundry", + "version": "0.1.0.0" + }, + { + "name": "cgi", + "origin": "hackage", + "synopsis": "A library for writing CGI programs", + "version": "3001.5.0.0" + }, + { + "name": "chan", + "origin": "hackage", + "synopsis": "Some extra kit for Chans", + "version": "0.0.4.1" + }, + { + "name": "ChannelT", + "origin": "hackage", + "synopsis": "Generalized stream processors", + "version": "0.0.0.7" + }, + { + "name": "character-cases", + "origin": "hackage", + "synopsis": "Exposes subspecies types of Char. And naming cases.", + "version": "0.1.0.6" + }, + { + "name": "charset", + "origin": "hackage", + "synopsis": "Fast unicode character sets based on complemented PATRICIA tries", + "version": "0.3.8" + }, + { + "name": "charsetdetect-ae", + "origin": "hackage", + "synopsis": "Character set detection using Mozilla's Universal Character Set Detector", + "version": "1.1.0.4" + }, + { + "name": "Chart", + "origin": "hackage", + "synopsis": "A library for generating 2D Charts and Plots", + "version": "1.9.3" + }, + { + "name": "chaselev-deque", + "origin": "hackage", + "synopsis": "Chase & Lev work-stealing lock-free double-ended queues (deques).", + "version": "0.5.0.5" + }, + { + "name": "ChasingBottoms", + "origin": "hackage", + "synopsis": "For testing partial and infinite values.", + "version": "1.3.1.10" + }, + { + "name": "cheapskate", + "origin": "hackage", + "synopsis": "Experimental markdown processor.", + "version": "0.1.1.2" + }, + { + "name": "cheapskate-highlight", + "origin": "hackage", + "synopsis": "Code highlighting for cheapskate", + "version": "0.1.0.0" + }, + { + "name": "cheapskate-lucid", + "origin": "hackage", + "synopsis": "Use cheapskate with Lucid", + "version": "0.1.0.0" + }, + { + "name": "checkers", + "origin": "hackage", + "synopsis": "Check properties on standard classes and data structures.", + "version": "0.5.6" + }, + { + "name": "checksum", + "origin": "hackage", + "synopsis": "Compute and verify checksums of ISBN, IBAN, etc.", + "version": "0.0" + }, + { + "name": "chimera", + "origin": "hackage", + "synopsis": "Lazy infinite streams with O(1) indexing", + "version": "0.3.1.0" + }, + { + "name": "chiphunk", + "origin": "hackage", + "synopsis": "Haskell bindings for Chipmunk2D physics engine", + "version": "0.1.4.0" + }, + { + "name": "choice", + "origin": "hackage", + "synopsis": "A solution to boolean blindness.", + "version": "0.2.2" + }, + { + "name": "chronologique", + "origin": "hackage", + "synopsis": "Time to manipulate time", + "version": "0.3.1.3" + }, + { + "name": "chronos", + "origin": "hackage", + "synopsis": "A performant time library", + "version": "1.1.1" + }, + { + "name": "chronos-bench", + "origin": "hackage", + "synopsis": "Benchmarking tool with focus on comparing results.", + "version": "0.2.0.2" + }, + { + "name": "chunked-data", + "origin": "hackage", + "synopsis": "Typeclasses for dealing with various chunked data representations", + "version": "0.3.1" + }, + { + "name": "cipher-aes", + "origin": "hackage", + "synopsis": "Fast AES cipher implementation with advanced mode of operations", + "version": "0.2.11" + }, + { + "name": "cipher-camellia", + "origin": "hackage", + "synopsis": "Camellia block cipher primitives", + "version": "0.0.2" + }, + { + "name": "cipher-des", + "origin": "hackage", + "synopsis": "DES and 3DES primitives", + "version": "0.0.6" + }, + { + "name": "cipher-rc4", + "origin": "hackage", + "synopsis": "Fast RC4 cipher implementation", + "version": "0.1.4" + }, + { + "name": "circle-packing", + "origin": "hackage", + "synopsis": "Simple heuristic for packing discs of varying radii in a circle", + "version": "0.1.0.6" + }, + { + "name": "circular", + "origin": "hackage", + "synopsis": "Circular fixed-sized mutable vectors", + "version": "0.3.1.1" + }, + { + "name": "citeproc", + "origin": "hackage", + "synopsis": "Generates citations and bibliography from CSL styles.", + "version": "0.3.0.9" + }, + { + "name": "clash-ghc", + "origin": "hackage", + "synopsis": "CAES Language for Synchronous Hardware", + "version": "1.2.5" + }, + { + "name": "clash-lib", + "origin": "hackage", + "synopsis": "CAES Language for Synchronous Hardware - As a Library", + "version": "1.2.5" + }, + { + "name": "clash-prelude", + "origin": "hackage", + "synopsis": "CAES Language for Synchronous Hardware - Prelude library", + "version": "1.2.5" + }, + { + "name": "classy-prelude", + "origin": "hackage", + "synopsis": "A typeclass-based Prelude.", + "version": "1.5.0" + }, + { + "name": "classy-prelude-conduit", + "origin": "hackage", + "synopsis": "classy-prelude together with conduit functions", + "version": "1.5.0" + }, + { + "name": "clay", + "origin": "hackage", + "synopsis": "CSS preprocessor as embedded Haskell.", + "version": "0.13.3" + }, + { + "name": "clientsession", + "origin": "hackage", + "synopsis": "Securely store session data in a client-side cookie.", + "version": "0.9.1.2" + }, + { + "name": "climb", + "origin": "hackage", + "synopsis": "Building blocks for a GHCi-like REPL with colon-commands", + "version": "0.3.3" + }, + { + "name": "Clipboard", + "origin": "hackage", + "synopsis": "System clipboard interface.", + "version": "2.3.2.0" + }, + { + "name": "clock", + "origin": "hackage", + "synopsis": "High-resolution clock functions: monotonic, realtime, cputime.", + "version": "0.8" + }, + { + "name": "clock-extras", + "origin": "hackage", + "synopsis": "A couple functions that probably should be in the 'clock' package", + "version": "0.1.0.2" + }, + { + "name": "closed", + "origin": "hackage", + "synopsis": "Integers bounded by a closed interval", + "version": "0.2.0.1" + }, + { + "name": "clumpiness", + "origin": "hackage", + "synopsis": "Calculate the clumpiness of leaf properties in a tree", + "version": "0.17.0.2" + }, + { + "name": "ClustalParser", + "origin": "hackage", + "synopsis": "Libary for parsing Clustal tools output", + "version": "1.3.0" + }, + { + "name": "cmark", + "origin": "hackage", + "synopsis": "Fast, accurate CommonMark (Markdown) parser and renderer", + "version": "0.6" + }, + { + "name": "cmark-gfm", + "origin": "hackage", + "synopsis": "Fast, accurate GitHub Flavored Markdown parser and renderer", + "version": "0.2.2" + }, + { + "name": "cmark-lucid", + "origin": "hackage", + "synopsis": "Use cmark with Lucid", + "version": "0.1.0.0" + }, + { + "name": "cmdargs", + "origin": "hackage", + "synopsis": "Command line argument processing", + "version": "0.10.21" + }, + { + "name": "codec-beam", + "origin": "hackage", + "synopsis": "Erlang VM byte code assembler", + "version": "0.2.0" + }, + { + "name": "codec-rpm", + "origin": "hackage", + "synopsis": "A library for manipulating RPM files", + "version": "0.2.2" + }, + { + "name": "code-page", + "origin": "hackage", + "synopsis": "Windows code page library for Haskell", + "version": "0.2.1" + }, + { + "name": "co-log", + "origin": "hackage", + "synopsis": "Composable Contravariant Comonadic Logging Library", + "version": "0.4.0.1" + }, + { + "name": "co-log-concurrent", + "origin": "hackage", + "synopsis": "Asynchronous backend for co-log library", + "version": "0.5.0.0" + }, + { + "name": "co-log-core", + "origin": "hackage", + "synopsis": "Composable Contravariant Comonadic Logging Library", + "version": "0.2.1.1" + }, + { + "name": "Color", + "origin": "hackage", + "synopsis": "Color spaces and conversions between them", + "version": "0.3.1" + }, + { + "name": "colorful-monoids", + "origin": "hackage", + "synopsis": "Styled console text output using ANSI escape sequences.", + "version": "0.2.1.3" + }, + { + "name": "colorize-haskell", + "origin": "hackage", + "synopsis": "Highligt Haskell source", + "version": "1.0.1" + }, + { + "name": "colour", + "origin": "hackage", + "synopsis": "A model for human colour/color perception", + "version": "2.3.5" + }, + { + "name": "colourista", + "origin": "hackage", + "synopsis": "Convenient interface for printing colourful messages", + "version": "0.1.0.1" + }, + { + "name": "combinatorial", + "origin": "hackage", + "synopsis": "Count, enumerate, rank and unrank combinatorial objects", + "version": "0.1.0.1" + }, + { + "name": "comfort-array", + "origin": "hackage", + "synopsis": "Arrays where the index type is a function of the shape type", + "version": "0.4.1" + }, + { + "name": "comfort-graph", + "origin": "hackage", + "synopsis": "Graph structure with type parameters for nodes and edges", + "version": "0.0.3.1" + }, + { + "name": "commonmark", + "origin": "hackage", + "synopsis": "Pure Haskell commonmark parser.", + "version": "0.1.1.4" + }, + { + "name": "commonmark-extensions", + "origin": "hackage", + "synopsis": "Pure Haskell commonmark parser.", + "version": "0.2.0.4" + }, + { + "name": "commonmark-pandoc", + "origin": "hackage", + "synopsis": "Bridge between commonmark and pandoc AST.", + "version": "0.2.0.1" + }, + { + "name": "commutative", + "origin": "hackage", + "synopsis": "Commutative binary operations.", + "version": "0.0.2" + }, + { + "name": "comonad", + "origin": "hackage", + "synopsis": "Comonads", + "version": "5.0.8" + }, + { + "name": "comonad-extras", + "origin": "hackage", + "synopsis": "Exotic comonad transformers", + "version": "4.0.1" + }, + { + "name": "compactmap", + "origin": "hackage", + "synopsis": "A read-only memory-efficient key-value store.", + "version": "0.1.4.2.1" + }, + { + "name": "compensated", + "origin": "hackage", + "synopsis": "Compensated floating-point arithmetic", + "version": "0.8.3" + }, + { + "name": "compiler-warnings", + "origin": "hackage", + "synopsis": "Parser for common compiler warning formats", + "version": "0.1.0" + }, + { + "name": "composable-associations", + "origin": "hackage", + "synopsis": "Types and helpers for composing types into a single larger key-value type.", + "version": "0.1.0.0" + }, + { + "name": "composable-associations-aeson", + "origin": "hackage", + "synopsis": "Aeson ToJSON/FromJSON implementation for the types of composable-associations", + "version": "0.1.0.1" + }, + { + "name": "composite-aeson", + "origin": "hackage", + "synopsis": "JSON for Vinyl records", + "version": "0.7.5.0" + }, + { + "name": "composite-aeson-path", + "origin": "hackage", + "synopsis": "Formatting data for the path library.", + "version": "0.7.5.0" + }, + { + "name": "composite-aeson-refined", + "origin": "hackage", + "synopsis": "composite-aeson support for Refined from the refined package", + "version": "0.7.5.0" + }, + { + "name": "composite-base", + "origin": "hackage", + "synopsis": "Shared utilities for composite-* packages.", + "version": "0.7.5.0" + }, + { + "name": "composite-binary", + "origin": "hackage", + "synopsis": "Orphan binary instances.", + "version": "0.7.5.0" + }, + { + "name": "composite-ekg", + "origin": "hackage", + "synopsis": "EKG Metrics for Vinyl records", + "version": "0.7.5.0" + }, + { + "name": "composite-hashable", + "origin": "hackage", + "synopsis": "Orphan hashable instances.", + "version": "0.7.5.0" + }, + { + "name": "composite-tuple", + "origin": "hackage", + "synopsis": "Tuple functions for composite records.", + "version": "0.1.2.0" + }, + { + "name": "composite-xstep", + "origin": "hackage", + "synopsis": "ReaderT transformer pattern for higher kinded composite data.", + "version": "0.1.0.0" + }, + { + "name": "composition", + "origin": "hackage", + "synopsis": "Combinators for unorthodox function composition", + "version": "1.0.2.2" + }, + { + "name": "composition-extra", + "origin": "hackage", + "synopsis": "Combinators for unorthodox structure composition", + "version": "2.0.0" + }, + { + "name": "concise", + "origin": "hackage", + "synopsis": "Utilities for Control.Lens.Cons", + "version": "0.1.0.1" + }, + { + "name": "concurrency", + "origin": "hackage", + "synopsis": "Typeclasses, functions, and data types for concurrency and STM.", + "version": "1.11.0.1" + }, + { + "name": "concurrent-extra", + "origin": "hackage", + "synopsis": "Extra concurrency primitives", + "version": "0.7.0.12" + }, + { + "name": "concurrent-output", + "origin": "hackage", + "synopsis": "Ungarble output from several threads or commands", + "version": "1.10.12" + }, + { + "name": "concurrent-split", + "origin": "hackage", + "synopsis": "MVars and Channels with distinguished input and output side", + "version": "0.0.1.1" + }, + { + "name": "concurrent-supply", + "origin": "hackage", + "synopsis": "A fast concurrent unique identifier supply with a pure API", + "version": "0.1.8" + }, + { + "name": "cond", + "origin": "hackage", + "synopsis": "Basic conditional and boolean operators with monadic variants.", + "version": "0.4.1.1" + }, + { + "name": "conduit", + "origin": "hackage", + "synopsis": "Streaming data processing library.", + "version": "1.3.4.1" + }, + { + "name": "conduit-algorithms", + "origin": "hackage", + "synopsis": "Conduit-based algorithms", + "version": "0.0.11.0" + }, + { + "name": "conduit-combinators", + "origin": "hackage", + "synopsis": "DEPRECATED Functionality merged into the conduit package itself", + "version": "1.3.0" + }, + { + "name": "conduit-concurrent-map", + "origin": "hackage", + "synopsis": "Concurrent, order-preserving mapping Conduit", + "version": "0.1.1" + }, + { + "name": "conduit-extra", + "origin": "hackage", + "synopsis": "Batteries included conduit: adapters for common libraries.", + "version": "1.3.5" + }, + { + "name": "conduit-parse", + "origin": "hackage", + "synopsis": "Parsing framework based on conduit.", + "version": "0.2.1.0" + }, + { + "name": "conduit-zstd", + "origin": "hackage", + "synopsis": "Conduit-based ZStd Compression", + "version": "0.0.2.0" + }, + { + "name": "conferer", + "origin": "hackage", + "synopsis": "Configuration management library", + "version": "1.0.0.1" + }, + { + "name": "conferer-aeson", + "origin": "hackage", + "synopsis": "conferer's source for reading json files", + "version": "1.0.0.0" + }, + { + "name": "conferer-hspec", + "origin": "hackage", + "synopsis": "conferer's FromConfig instances for hspec Config", + "version": "1.0.0.0" + }, + { + "name": "conferer-warp", + "origin": "hackage", + "synopsis": "conferer's FromConfig instances for warp settings", + "version": "1.0.0.0" + }, + { + "name": "ConfigFile", + "origin": "hackage", + "synopsis": "Configuration file reading & writing", + "version": "1.1.4" + }, + { + "name": "config-ini", + "origin": "hackage", + "synopsis": "A library for simple INI-based configuration files.", + "version": "0.2.4.0" + }, + { + "name": "configurator", + "origin": "hackage", + "synopsis": "Configuration management", + "version": "0.3.0.0" + }, + { + "name": "configurator-export", + "origin": "hackage", + "synopsis": "Pretty printer and exporter for configurations from\nthe \"configurator\" library.", + "version": "0.1.0.1" + }, + { + "name": "configurator-pg", + "origin": "hackage", + "synopsis": "Reduced parser for configurator-ng config files", + "version": "0.2.5" + }, + { + "name": "connection", + "origin": "hackage", + "synopsis": "Simple and easy network connections API", + "version": "0.3.1" + }, + { + "name": "connection-pool", + "origin": "hackage", + "synopsis": "Connection pool built on top of resource-pool and streaming-commons.", + "version": "0.2.2" + }, + { + "name": "console-style", + "origin": "hackage", + "synopsis": "Styled console text output using ANSI escape sequences.", + "version": "0.0.2.1" + }, + { + "name": "constraint", + "origin": "hackage", + "synopsis": "Reified constraints", + "version": "0.1.4.0" + }, + { + "name": "constraints", + "origin": "hackage", + "synopsis": "Constraint manipulation", + "version": "0.12" + }, + { + "name": "constraint-tuples", + "origin": "hackage", + "synopsis": "Partially applicable constraint tuples", + "version": "0.1.2" + }, + { + "name": "construct", + "origin": "hackage", + "synopsis": "Haskell version of the Construct library for easy specification of file formats", + "version": "0.3.0.2" + }, + { + "name": "containers", + "origin": "core", + "synopsis": "Assorted concrete container types", + "version": "0.6.2.1" + }, + { + "name": "contravariant", + "origin": "hackage", + "synopsis": "Contravariant functors", + "version": "1.5.3" + }, + { + "name": "contravariant-extras", + "origin": "hackage", + "synopsis": "Extras for the \"contravariant\" package", + "version": "0.3.5.2" + }, + { + "name": "control-bool", + "origin": "hackage", + "synopsis": "Useful combinators for boolean expressions", + "version": "0.2.1" + }, + { + "name": "control-monad-free", + "origin": "hackage", + "synopsis": "Free monads and monad transformers", + "version": "0.6.2" + }, + { + "name": "control-monad-omega", + "origin": "hackage", + "synopsis": "A breadth-first list monad.", + "version": "0.3.2" + }, + { + "name": "convertible", + "origin": "hackage", + "synopsis": "Typeclasses and instances for converting between types", + "version": "1.1.1.0" + }, + { + "name": "cookie", + "origin": "hackage", + "synopsis": "HTTP cookie parsing and rendering", + "version": "0.4.5" + }, + { + "name": "core-data", + "origin": "hackage", + "synopsis": "Convenience wrappers around common data structures and encodings", + "version": "0.2.1.9" + }, + { + "name": "core-program", + "origin": "hackage", + "synopsis": "Opinionated Haskell Interoperability", + "version": "0.2.6.0" + }, + { + "name": "core-text", + "origin": "hackage", + "synopsis": "A rope type based on a finger tree over UTF-8 fragments", + "version": "0.3.0.0" + }, + { + "name": "countable", + "origin": "hackage", + "synopsis": "Countable, Searchable, Finite, Empty classes", + "version": "1.0" + }, + { + "name": "country", + "origin": "hackage", + "synopsis": "Country data type and functions", + "version": "0.2.1" + }, + { + "name": "cpio-conduit", + "origin": "hackage", + "synopsis": "Conduit-based CPIO", + "version": "0.7.0" + }, + { + "name": "cpphs", + "origin": "hackage", + "synopsis": "A liberalised re-implementation of cpp, the C pre-processor.", + "version": "1.20.9.1" + }, + { + "name": "cprng-aes", + "origin": "hackage", + "synopsis": "Crypto Pseudo Random Number Generator using AES in counter mode.", + "version": "0.6.1" + }, + { + "name": "cpu", + "origin": "hackage", + "synopsis": "Cpu information and properties helpers.", + "version": "0.1.2" + }, + { + "name": "cpuinfo", + "origin": "hackage", + "synopsis": "Haskell Library for Checking CPU Information", + "version": "0.1.0.2" + }, + { + "name": "crackNum", + "origin": "hackage", + "synopsis": "Crack various integer, floating-point data formats", + "version": "2.4" + }, + { + "name": "crc32c", + "origin": "hackage", + "synopsis": "Haskell bindings for crc32c", + "version": "0.0.0" + }, + { + "name": "credential-store", + "origin": "hackage", + "synopsis": "Library to access secure credential storage providers", + "version": "0.1.2" + }, + { + "name": "criterion", + "origin": "hackage", + "synopsis": "Robust, reliable performance measurement and analysis", + "version": "1.5.9.0" + }, + { + "name": "criterion-measurement", + "origin": "hackage", + "synopsis": "Criterion measurement functionality and associated types", + "version": "0.1.2.0" + }, + { + "name": "cron", + "origin": "hackage", + "synopsis": "Cron datatypes and Attoparsec parser", + "version": "0.7.0" + }, + { + "name": "crypto-api", + "origin": "hackage", + "synopsis": "A generic interface for cryptographic operations", + "version": "0.13.3" + }, + { + "name": "crypto-cipher-types", + "origin": "hackage", + "synopsis": "Generic cryptography cipher types", + "version": "0.0.9" + }, + { + "name": "cryptocompare", + "origin": "hackage", + "synopsis": "Haskell wrapper for the cryptocompare API", + "version": "0.1.2" + }, + { + "name": "crypto-enigma", + "origin": "hackage", + "synopsis": "An Enigma machine simulator with display. ", + "version": "0.1.1.6" + }, + { + "name": "cryptohash", + "origin": "hackage", + "synopsis": "collection of crypto hashes, fast, pure and practical", + "version": "0.11.9" + }, + { + "name": "cryptohash-cryptoapi", + "origin": "hackage", + "synopsis": "Crypto-api interfaces for cryptohash", + "version": "0.1.4" + }, + { + "name": "cryptohash-md5", + "origin": "hackage", + "synopsis": "Fast, pure and practical MD5 implementation", + "version": "0.11.100.1" + }, + { + "name": "cryptohash-sha1", + "origin": "hackage", + "synopsis": "Fast, pure and practical SHA-1 implementation", + "version": "0.11.100.1" + }, + { + "name": "cryptohash-sha256", + "origin": "hackage", + "synopsis": "Fast, pure and practical SHA-256 implementation", + "version": "0.11.102.0" + }, + { + "name": "cryptonite", + "origin": "hackage", + "synopsis": "Cryptography Primitives sink", + "version": "0.27" + }, + { + "name": "cryptonite-conduit", + "origin": "hackage", + "synopsis": "cryptonite conduit", + "version": "0.2.2" + }, + { + "name": "cryptonite-openssl", + "origin": "hackage", + "synopsis": "Crypto stuff using OpenSSL cryptographic library", + "version": "0.7" + }, + { + "name": "crypto-numbers", + "origin": "hackage", + "synopsis": "Cryptographic numbers: functions and algorithms", + "version": "0.2.7" + }, + { + "name": "crypto-pubkey", + "origin": "hackage", + "synopsis": "Public Key cryptography", + "version": "0.2.8" + }, + { + "name": "crypto-pubkey-types", + "origin": "hackage", + "synopsis": "Generic cryptography Public keys algorithm types", + "version": "0.4.3" + }, + { + "name": "crypto-random", + "origin": "hackage", + "synopsis": "Simple cryptographic random related types", + "version": "0.0.9" + }, + { + "name": "crypto-random-api", + "origin": "hackage", + "synopsis": "Simple random generators API for cryptography related code", + "version": "0.2.0" + }, + { + "name": "csp", + "origin": "hackage", + "synopsis": "Discrete constraint satisfaction problem (CSP) solver.", + "version": "1.4.0" + }, + { + "name": "css-syntax", + "origin": "hackage", + "synopsis": "High-performance CSS tokenizer and serializer.", + "version": "0.1.0.0" + }, + { + "name": "css-text", + "origin": "hackage", + "synopsis": "CSS parser and renderer.", + "version": "0.1.3.0" + }, + { + "name": "csv", + "origin": "hackage", + "synopsis": "CSV loader and dumper", + "version": "0.1.2" + }, + { + "name": "ctrie", + "origin": "hackage", + "synopsis": "Non-blocking concurrent map", + "version": "0.2" + }, + { + "name": "cubicbezier", + "origin": "hackage", + "synopsis": "Efficient manipulating of 2D cubic bezier curves.", + "version": "0.6.0.6" + }, + { + "name": "cubicspline", + "origin": "hackage", + "synopsis": "Natural cubic spline interpolation.", + "version": "0.1.2" + }, + { + "name": "cuckoo-filter", + "origin": "hackage", + "synopsis": "Pure and impure Cuckoo Filter", + "version": "0.2.0.2" + }, + { + "name": "cue-sheet", + "origin": "hackage", + "synopsis": "Support for construction, rendering, and parsing of CUE sheets", + "version": "2.0.1" + }, + { + "name": "curl", + "origin": "hackage", + "synopsis": "Haskell binding to libcurl", + "version": "1.3.8" + }, + { + "name": "currencies", + "origin": "hackage", + "synopsis": "Currencies representation, pretty printing and conversion", + "version": "0.2.0.0" + }, + { + "name": "currency", + "origin": "hackage", + "synopsis": "Types representing standard and non-standard currencies", + "version": "0.2.0.0" + }, + { + "name": "cursor", + "origin": "hackage", + "synopsis": "Purely Functional Cursors", + "version": "0.3.0.0" + }, + { + "name": "cursor-brick", + "origin": "hackage", + "synopsis": "", + "version": "0.1.0.0" + }, + { + "name": "cursor-fuzzy-time", + "origin": "hackage", + "synopsis": "", + "version": "0.0.0.0" + }, + { + "name": "cursor-gen", + "origin": "hackage", + "synopsis": "Generators for Purely Functional Cursors", + "version": "0.3.0.0" + }, + { + "name": "cutter", + "origin": "hackage", + "synopsis": "Cut files according to a position list", + "version": "0.0" + }, + { + "name": "cyclotomic", + "origin": "hackage", + "synopsis": "A subfield of the complex numbers for exact calculation.", + "version": "1.1.1" + }, + { + "name": "czipwith", + "origin": "hackage", + "synopsis": "CZipWith class and deriving via TH", + "version": "1.0.1.3" + }, + { + "name": "d10", + "origin": "hackage", + "synopsis": "Digits 0-9", + "version": "0.2.1.6" + }, + { + "name": "data-accessor", + "origin": "hackage", + "synopsis": "Utilities for accessing and manipulating fields of records", + "version": "0.2.3" + }, + { + "name": "data-accessor-mtl", + "origin": "hackage", + "synopsis": "Use Accessor to access state in mtl State monad class", + "version": "0.2.0.4" + }, + { + "name": "data-accessor-template", + "origin": "hackage", + "synopsis": "Utilities for accessing and manipulating fields of records", + "version": "0.2.1.16" + }, + { + "name": "data-accessor-transformers", + "origin": "hackage", + "synopsis": "Use Accessor to access state in transformers State monad", + "version": "0.2.1.7" + }, + { + "name": "data-ascii", + "origin": "hackage", + "synopsis": "Type-safe, bytestring-based ASCII values", + "version": "1.0.0.6" + }, + { + "name": "data-binary-ieee754", + "origin": "hackage", + "synopsis": "Parser/Serialiser for IEEE-754 floating-point values", + "version": "0.4.4" + }, + { + "name": "data-bword", + "origin": "hackage", + "synopsis": "Extra operations on binary words of fixed length", + "version": "0.1.0.1" + }, + { + "name": "data-checked", + "origin": "hackage", + "synopsis": "Type-indexed runtime-checked properties ", + "version": "0.3" + }, + { + "name": "data-clist", + "origin": "hackage", + "synopsis": "Simple functional ring type.", + "version": "0.1.2.3" + }, + { + "name": "data-compat", + "origin": "hackage", + "synopsis": "Define Backwards Compatibility Schemes for Arbitrary Data", + "version": "0.1.0.3" + }, + { + "name": "data-default", + "origin": "hackage", + "synopsis": "A class for types with a default value", + "version": "0.7.1.1" + }, + { + "name": "data-default-class", + "origin": "hackage", + "synopsis": "A class for types with a default value", + "version": "0.1.2.0" + }, + { + "name": "data-default-instances-containers", + "origin": "hackage", + "synopsis": "Default instances for types in containers", + "version": "0.0.1" + }, + { + "name": "data-default-instances-dlist", + "origin": "hackage", + "synopsis": "Default instances for types in dlist", + "version": "0.0.1" + }, + { + "name": "data-default-instances-old-locale", + "origin": "hackage", + "synopsis": "Default instances for types in old-locale", + "version": "0.0.1" + }, + { + "name": "data-diverse", + "origin": "hackage", + "synopsis": "Extensible records and polymorphic variants.", + "version": "4.7.0.0" + }, + { + "name": "datadog", + "origin": "hackage", + "synopsis": "Datadog client for Haskell. Supports both the HTTP API and StatsD.", + "version": "0.2.5.0" + }, + { + "name": "data-dword", + "origin": "hackage", + "synopsis": "Stick two binary words together to get a bigger one", + "version": "0.3.2" + }, + { + "name": "data-endian", + "origin": "hackage", + "synopsis": "Endian-sensitive data", + "version": "0.1.1" + }, + { + "name": "data-fix", + "origin": "hackage", + "synopsis": "Fixpoint data types", + "version": "0.3.1" + }, + { + "name": "data-forest", + "origin": "hackage", + "synopsis": "A simple multi-way tree data structure.", + "version": "0.1.0.8" + }, + { + "name": "data-has", + "origin": "hackage", + "synopsis": "Simple extensible product", + "version": "0.4.0.0" + }, + { + "name": "data-hash", + "origin": "hackage", + "synopsis": "Combinators for building fast hashing functions.", + "version": "0.2.0.1" + }, + { + "name": "data-interval", + "origin": "hackage", + "synopsis": "Interval datatype, interval arithmetic and interval-based containers", + "version": "2.0.1" + }, + { + "name": "data-inttrie", + "origin": "hackage", + "synopsis": "A simple lazy, infinite trie from integers", + "version": "0.1.4" + }, + { + "name": "data-lens-light", + "origin": "hackage", + "synopsis": "Simple lenses, minimum dependencies", + "version": "0.1.2.2" + }, + { + "name": "data-memocombinators", + "origin": "hackage", + "synopsis": "Combinators for building memo tables.", + "version": "0.5.1" + }, + { + "name": "data-msgpack", + "origin": "hackage", + "synopsis": "A Haskell implementation of MessagePack", + "version": "0.0.13" + }, + { + "name": "data-msgpack-types", + "origin": "hackage", + "synopsis": "A Haskell implementation of MessagePack.", + "version": "0.0.3" + }, + { + "name": "data-or", + "origin": "hackage", + "synopsis": "A data type for non-exclusive disjunction.", + "version": "1.0.0.5" + }, + { + "name": "data-ordlist", + "origin": "hackage", + "synopsis": "Set and bag operations on ordered lists", + "version": "0.4.7.0" + }, + { + "name": "data-ref", + "origin": "hackage", + "synopsis": "Unify STRef and IORef in plain Haskell 98", + "version": "0.0.2" + }, + { + "name": "data-reify", + "origin": "hackage", + "synopsis": "Reify a recursive data structure into an explicit graph.", + "version": "0.6.3" + }, + { + "name": "data-serializer", + "origin": "hackage", + "synopsis": "Common API for serialization libraries", + "version": "0.3.5" + }, + { + "name": "data-textual", + "origin": "hackage", + "synopsis": "Human-friendly textual representations.", + "version": "0.3.0.3" + }, + { + "name": "dataurl", + "origin": "hackage", + "synopsis": "Handle data-urls", + "version": "0.1.0.0" + }, + { + "name": "DAV", + "origin": "hackage", + "synopsis": "RFC 4918 WebDAV support", + "version": "1.3.4" + }, + { + "name": "DBFunctor", + "origin": "hackage", + "synopsis": "DBFunctor - Functional Data Management => ETL/ELT Data Processing in Haskell", + "version": "0.1.1.1" + }, + { + "name": "dbus", + "origin": "hackage", + "synopsis": "A client library for the D-Bus IPC system.", + "version": "1.2.17" + }, + { + "name": "dbus-hslogger", + "origin": "hackage", + "synopsis": "Expose a dbus server to control hslogger", + "version": "0.1.0.1" + }, + { + "name": "debian", + "origin": "hackage", + "synopsis": "Modules for working with the Debian package system", + "version": "4.0.2" + }, + { + "name": "debian-build", + "origin": "hackage", + "synopsis": "Debian package build sequence tools", + "version": "0.10.2.0" + }, + { + "name": "debug-trace-var", + "origin": "hackage", + "synopsis": "You do not have to write variable names twice in Debug.Trace", + "version": "0.2.0" + }, + { + "name": "dec", + "origin": "hackage", + "synopsis": "Decidable propositions.", + "version": "0.0.4" + }, + { + "name": "Decimal", + "origin": "hackage", + "synopsis": "Decimal numbers with variable precision", + "version": "0.5.2" + }, + { + "name": "declarative", + "origin": "hackage", + "synopsis": "DIY Markov Chains.", + "version": "0.5.4" + }, + { + "name": "deepseq", + "origin": "core", + "synopsis": "Deep evaluation of data structures", + "version": "1.4.4.0" + }, + { + "name": "deepseq-generics", + "origin": "hackage", + "synopsis": "GHC.Generics-based Control.DeepSeq.rnf implementation", + "version": "0.2.0.0" + }, + { + "name": "deepseq-instances", + "origin": "hackage", + "synopsis": "Candidate NFData Instances for Types in base", + "version": "0.1.0.1" + }, + { + "name": "deferred-folds", + "origin": "hackage", + "synopsis": "Abstractions over deferred folds", + "version": "0.9.17" + }, + { + "name": "dejafu", + "origin": "hackage", + "synopsis": "A library for unit-testing concurrent programs.", + "version": "2.4.0.2" + }, + { + "name": "dense-linear-algebra", + "origin": "hackage", + "synopsis": "Simple and incomplete pure haskell implementation of linear algebra", + "version": "0.1.0.0" + }, + { + "name": "depq", + "origin": "hackage", + "synopsis": "Double-ended priority queues", + "version": "0.4.2" + }, + { + "name": "deque", + "origin": "hackage", + "synopsis": "Double-ended queues", + "version": "0.4.3" + }, + { + "name": "deriveJsonNoPrefix", + "origin": "hackage", + "synopsis": "Derive ToJSON/FromJSON instances in a more prefix-friendly manner.", + "version": "0.1.0.1" + }, + { + "name": "derive-topdown", + "origin": "hackage", + "synopsis": "Help Haskellers derive class instances for composited data types.", + "version": "0.0.2.2" + }, + { + "name": "deriving-aeson", + "origin": "hackage", + "synopsis": "Type driven generic aeson instance customisation", + "version": "0.2.6.1" + }, + { + "name": "deriving-compat", + "origin": "hackage", + "synopsis": "Backports of GHC deriving extensions", + "version": "0.5.10" + }, + { + "name": "derulo", + "origin": "hackage", + "synopsis": "Parse and render JSON simply.", + "version": "1.0.10" + }, + { + "name": "dhall", + "origin": "hackage", + "synopsis": "A configuration language guaranteed to terminate", + "version": "1.37.1" + }, + { + "name": "dhall-bash", + "origin": "hackage", + "synopsis": "Compile Dhall to Bash", + "version": "1.0.36" + }, + { + "name": "dhall-json", + "origin": "hackage", + "synopsis": "Convert between Dhall and JSON or YAML", + "version": "1.7.4" + }, + { + "name": "dhall-lsp-server", + "origin": "hackage", + "synopsis": "Language Server Protocol (LSP) server for Dhall", + "version": "1.0.12" + }, + { + "name": "dhall-yaml", + "origin": "hackage", + "synopsis": "Convert between Dhall and YAML", + "version": "1.2.6" + }, + { + "name": "diagrams-solve", + "origin": "hackage", + "synopsis": "Pure Haskell solver routines used by diagrams", + "version": "0.1.3" + }, + { + "name": "dialogflow-fulfillment", + "origin": "hackage", + "synopsis": "A Dialogflow Fulfillment library for Haskell.", + "version": "0.1.1.3" + }, + { + "name": "di-core", + "origin": "hackage", + "synopsis": "Typeful hierarchical structured logging without monad towers.", + "version": "1.0.4" + }, + { + "name": "dictionary-sharing", + "origin": "hackage", + "synopsis": "Sharing/memoization of class members", + "version": "0.1.0.0" + }, + { + "name": "Diff", + "origin": "hackage", + "synopsis": "O(ND) diff algorithm in haskell.", + "version": "0.4.0" + }, + { + "name": "digest", + "origin": "hackage", + "synopsis": "Various cryptographic hashes for bytestrings; CRC32 and Adler32 for now.", + "version": "0.0.1.2" + }, + { + "name": "digits", + "origin": "hackage", + "synopsis": "Converts integers to lists of digits and back.", + "version": "0.3.1" + }, + { + "name": "dimensional", + "origin": "hackage", + "synopsis": "Statically checked physical dimensions,\nusing Type Families and Data Kinds.", + "version": "1.3" + }, + { + "name": "di-monad", + "origin": "hackage", + "synopsis": "mtl flavoured typeful hierarchical structured logging for di-core.", + "version": "1.3.1" + }, + { + "name": "directory", + "origin": "core", + "synopsis": "Platform-agnostic library for filesystem operations", + "version": "1.3.6.0" + }, + { + "name": "directory-tree", + "origin": "hackage", + "synopsis": "A simple directory-like tree datatype, with useful IO functions", + "version": "0.12.1" + }, + { + "name": "direct-sqlite", + "origin": "hackage", + "synopsis": "Low-level binding to SQLite3. Includes UTF8 and BLOB support.", + "version": "2.3.26" + }, + { + "name": "dirichlet", + "origin": "hackage", + "synopsis": "Multivariate Dirichlet distribution", + "version": "0.1.0.2" + }, + { + "name": "discount", + "origin": "hackage", + "synopsis": "Haskell bindings to the discount Markdown library.", + "version": "0.1.1" + }, + { + "name": "disk-free-space", + "origin": "hackage", + "synopsis": "Retrieve information about disk space usage", + "version": "0.1.0.1" + }, + { + "name": "distributed-closure", + "origin": "hackage", + "synopsis": "Serializable closures for distributed programming.", + "version": "0.4.2.0" + }, + { + "name": "distribution-nixpkgs", + "origin": "hackage", + "synopsis": "Types and functions to manipulate the Nixpkgs distribution", + "version": "1.4.0" + }, + { + "name": "distribution-opensuse", + "origin": "hackage", + "synopsis": "Types, functions, and tools to manipulate the openSUSE distribution", + "version": "1.1.1" + }, + { + "name": "distributive", + "origin": "hackage", + "synopsis": "Distributive functors -- Dual to Traversable", + "version": "0.6.2.1" + }, + { + "name": "dl-fedora", + "origin": "hackage", + "synopsis": "Fedora image download tool", + "version": "0.7.7" + }, + { + "name": "dlist", + "origin": "hackage", + "synopsis": "Difference lists", + "version": "0.8.0.8" + }, + { + "name": "dlist-instances", + "origin": "hackage", + "synopsis": "Difference lists instances", + "version": "0.1.1.1" + }, + { + "name": "dlist-nonempty", + "origin": "hackage", + "synopsis": "Non-empty difference lists", + "version": "0.1.1" + }, + { + "name": "dns", + "origin": "hackage", + "synopsis": "DNS library in Haskell", + "version": "4.0.1" + }, + { + "name": "dockerfile", + "origin": "hackage", + "synopsis": "A Haskell DSL for generating Dockerfiles", + "version": "0.2.0" + }, + { + "name": "doclayout", + "origin": "hackage", + "synopsis": "A prettyprinting library for laying out text documents.", + "version": "0.3.0.2" + }, + { + "name": "doctemplates", + "origin": "hackage", + "synopsis": "Pandoc-style document templates", + "version": "0.9" + }, + { + "name": "doctest", + "origin": "hackage", + "synopsis": "Test interactive Haskell examples", + "version": "0.16.3" + }, + { + "name": "doctest-discover", + "origin": "hackage", + "synopsis": "Easy way to run doctests via cabal", + "version": "0.2.0.0" + }, + { + "name": "doctest-driver-gen", + "origin": "hackage", + "synopsis": "Generate driver file for doctest's cabal integration", + "version": "0.3.0.3" + }, + { + "name": "doctest-exitcode-stdio", + "origin": "hackage", + "synopsis": "Run doctest's in a Cabal.Test.exitcode-stdio environment", + "version": "0.0" + }, + { + "name": "doctest-lib", + "origin": "hackage", + "synopsis": "Parts of doctest exposed as library", + "version": "0.1" + }, + { + "name": "doldol", + "origin": "hackage", + "synopsis": "Flag packer & handler for flaggable data", + "version": "0.4.1.2" + }, + { + "name": "do-list", + "origin": "hackage", + "synopsis": "Do notation for free", + "version": "1.0.1" + }, + { + "name": "do-notation", + "origin": "hackage", + "synopsis": "Generalize do-notation to work on monads and indexed monads simultaneously.", + "version": "0.1.0.2" + }, + { + "name": "dot", + "origin": "hackage", + "synopsis": "Datatypes and encoding for graphviz dot files", + "version": "0.3" + }, + { + "name": "dotenv", + "origin": "hackage", + "synopsis": "Loads environment variables from dotenv files", + "version": "0.8.0.7" + }, + { + "name": "dotgen", + "origin": "hackage", + "synopsis": "A simple interface for building .dot graph files.", + "version": "0.4.3" + }, + { + "name": "dotnet-timespan", + "origin": "hackage", + "synopsis": ".NET TimeSpan", + "version": "0.0.1.0" + }, + { + "name": "double-conversion", + "origin": "hackage", + "synopsis": "Fast conversion between double precision floating point and text", + "version": "2.0.2.0" + }, + { + "name": "download", + "origin": "hackage", + "synopsis": "High-level file download based on URLs", + "version": "0.3.2.7" + }, + { + "name": "drinkery", + "origin": "hackage", + "synopsis": "Boozy streaming library", + "version": "0.4" + }, + { + "name": "dsp", + "origin": "hackage", + "synopsis": "Haskell Digital Signal Processing", + "version": "0.2.5.1" + }, + { + "name": "dual", + "origin": "hackage", + "synopsis": "Dual category", + "version": "0.1.1.1" + }, + { + "name": "dublincore-xml-conduit", + "origin": "hackage", + "synopsis": "XML streaming parser/renderer for the Dublin Core standard elements.", + "version": "0.1.0.2" + }, + { + "name": "dunai", + "origin": "hackage", + "synopsis": "Generalised reactive framework supporting classic, arrowized and monadic FRP.", + "version": "0.7.0" + }, + { + "name": "duration", + "origin": "hackage", + "synopsis": "A tiny compile-time time utility library inspired by zeit/ms", + "version": "0.1.0.0" + }, + { + "name": "dvorak", + "origin": "hackage", + "synopsis": "Dvorak encoding for Haskell.", + "version": "0.1.0.0" + }, + { + "name": "dynamic-state", + "origin": "hackage", + "synopsis": "Optionally serializable dynamic state keyed by type", + "version": "0.3.1" + }, + { + "name": "dyre", + "origin": "hackage", + "synopsis": "Dynamic reconfiguration in Haskell", + "version": "0.8.12" + }, + { + "name": "eap", + "origin": "hackage", + "synopsis": "Extensible Authentication Protocol (EAP)", + "version": "0.9.0.2" + }, + { + "name": "earcut", + "origin": "hackage", + "synopsis": "Binding to C++ earcut library.", + "version": "0.1.0.4" + }, + { + "name": "Earley", + "origin": "hackage", + "synopsis": "Parsing all context-free grammars using Earley's algorithm.", + "version": "0.13.0.1" + }, + { + "name": "easy-file", + "origin": "hackage", + "synopsis": "Cross-platform File handling", + "version": "0.2.2" + }, + { + "name": "Ebnf2ps", + "origin": "hackage", + "synopsis": "Peter's Syntax Diagram Drawing Tool", + "version": "1.0.15" + }, + { + "name": "echo", + "origin": "hackage", + "synopsis": "A cross-platform, cross-console way to handle echoing terminal input", + "version": "0.1.4" + }, + { + "name": "ecstasy", + "origin": "hackage", + "synopsis": "A GHC.Generics based entity component system.", + "version": "0.2.1.0" + }, + { + "name": "ed25519", + "origin": "hackage", + "synopsis": "Ed25519 cryptographic signatures", + "version": "0.0.5.0" + }, + { + "name": "edit-distance", + "origin": "hackage", + "synopsis": "Levenshtein and restricted Damerau-Levenshtein edit distances", + "version": "0.2.2.1" + }, + { + "name": "edit-distance-vector", + "origin": "hackage", + "synopsis": "Calculate edit distances and edit scripts between vectors.", + "version": "1.0.0.4" + }, + { + "name": "editor-open", + "origin": "hackage", + "synopsis": "Open the user's $VISUAL or $EDITOR for text input.", + "version": "0.6.0.0" + }, + { + "name": "egison", + "origin": "hackage", + "synopsis": "Programming language with non-linear pattern-matching against non-free data", + "version": "4.1.2" + }, + { + "name": "egison-pattern-src", + "origin": "hackage", + "synopsis": "Manipulating Egison patterns: abstract syntax, parser, and pretty-printer", + "version": "0.2.1.2" + }, + { + "name": "egison-pattern-src-th-mode", + "origin": "hackage", + "synopsis": "Parser and pretty printer for Egison pattern expressions to use with TH", + "version": "0.2.1.2" + }, + { + "name": "either", + "origin": "hackage", + "synopsis": "Combinators for working with sums", + "version": "5.0.1.1" + }, + { + "name": "either-both", + "origin": "hackage", + "synopsis": "Either or both", + "version": "0.1.1.1" + }, + { + "name": "either-unwrap", + "origin": "hackage", + "synopsis": "Functions for probing and unwrapping values inside of Either.", + "version": "1.1" + }, + { + "name": "ekg", + "origin": "hackage", + "synopsis": "Remote monitoring of processes", + "version": "0.4.0.15" + }, + { + "name": "ekg-core", + "origin": "hackage", + "synopsis": "Tracking of system metrics", + "version": "0.1.1.7" + }, + { + "name": "ekg-json", + "origin": "hackage", + "synopsis": "JSON encoding of ekg metrics", + "version": "0.1.0.6" + }, + { + "name": "ekg-statsd", + "origin": "hackage", + "synopsis": "Push metrics to statsd", + "version": "0.2.5.0" + }, + { + "name": "elerea", + "origin": "hackage", + "synopsis": "A minimalistic FRP library", + "version": "2.9.0" + }, + { + "name": "elf", + "origin": "hackage", + "synopsis": "An Elf parser", + "version": "0.30" + }, + { + "name": "eliminators", + "origin": "hackage", + "synopsis": "Dependently typed elimination functions using singletons", + "version": "0.7" + }, + { + "name": "elm2nix", + "origin": "hackage", + "synopsis": "Turn your Elm project into buildable Nix project", + "version": "0.2.1" + }, + { + "name": "elm-bridge", + "origin": "hackage", + "synopsis": "Derive Elm types and Json code from Haskell types, using aeson's options", + "version": "0.6.1" + }, + { + "name": "elm-core-sources", + "origin": "hackage", + "synopsis": "Source files for the Elm runtime and standard libraries", + "version": "1.0.0" + }, + { + "name": "elm-export", + "origin": "hackage", + "synopsis": "A library to generate Elm types from Haskell source.", + "version": "0.6.0.1" + }, + { + "name": "elynx", + "origin": "hackage", + "synopsis": "Validate and (optionally) redo ELynx analyses", + "version": "0.5.0.1" + }, + { + "name": "elynx-markov", + "origin": "hackage", + "synopsis": "Simulate molecular sequences along trees", + "version": "0.5.0.1" + }, + { + "name": "elynx-nexus", + "origin": "hackage", + "synopsis": "Import and export Nexus files", + "version": "0.5.0.1" + }, + { + "name": "elynx-seq", + "origin": "hackage", + "synopsis": "Handle molecular sequences", + "version": "0.5.0.1" + }, + { + "name": "elynx-tools", + "origin": "hackage", + "synopsis": "Tools for ELynx", + "version": "0.5.0.1" + }, + { + "name": "elynx-tree", + "origin": "hackage", + "synopsis": "Handle phylogenetic trees", + "version": "0.5.0.1" + }, + { + "name": "email-validate", + "origin": "hackage", + "synopsis": "Email address validation", + "version": "2.3.2.13" + }, + { + "name": "emojis", + "origin": "hackage", + "synopsis": "Conversion between emoji characters and their names.", + "version": "0.1" + }, + { + "name": "enclosed-exceptions", + "origin": "hackage", + "synopsis": "Catching all exceptions from within an enclosed computation", + "version": "1.0.3" + }, + { + "name": "ENIG", + "origin": "hackage", + "synopsis": "Auto Korean conjugator/adjustor/adopter/converter", + "version": "0.0.1.0" + }, + { + "name": "entropy", + "origin": "hackage", + "synopsis": "A platform independent entropy source", + "version": "0.4.1.6" + }, + { + "name": "enummapset", + "origin": "hackage", + "synopsis": "IntMap and IntSet with Enum keys/elements.", + "version": "0.6.0.3" + }, + { + "name": "enumset", + "origin": "hackage", + "synopsis": "Sets of enumeration values represented by machine words", + "version": "0.0.5" + }, + { + "name": "enum-subset-generate", + "origin": "hackage", + "synopsis": "Generate an ADT being a subset of another ADT, and the corresponding mappings.", + "version": "0.1.0.0" + }, + { + "name": "envelope", + "origin": "hackage", + "synopsis": "Defines generic 'Envelope' type to wrap reponses from a JSON API.", + "version": "0.2.2.0" + }, + { + "name": "envparse", + "origin": "hackage", + "synopsis": "Parse environment variables", + "version": "0.4.1" + }, + { + "name": "envy", + "origin": "hackage", + "synopsis": "An environmentally friendly way to deal with environment variables", + "version": "2.1.0.0" + }, + { + "name": "epub-metadata", + "origin": "hackage", + "synopsis": "Library for parsing epub document metadata", + "version": "4.5" + }, + { + "name": "eq", + "origin": "hackage", + "synopsis": "Leibnizian equality", + "version": "4.2.1" + }, + { + "name": "equal-files", + "origin": "hackage", + "synopsis": "Shell command for finding equal files", + "version": "0.0.5.3" + }, + { + "name": "equational-reasoning", + "origin": "hackage", + "synopsis": "Proof assistant for Haskell using DataKinds & PolyKinds", + "version": "0.6.0.4" + }, + { + "name": "equivalence", + "origin": "hackage", + "synopsis": "Maintaining an equivalence relation implemented as union-find using STT.", + "version": "0.3.5" + }, + { + "name": "erf", + "origin": "hackage", + "synopsis": "The error function, erf, and related functions.", + "version": "2.0.0.0" + }, + { + "name": "error-or", + "origin": "hackage", + "synopsis": "Composable, hierarchical errors.", + "version": "0.1.2.0" + }, + { + "name": "error-or-utils", + "origin": "hackage", + "synopsis": "Utilities using ErrorOr datatype", + "version": "0.1.1" + }, + { + "name": "errors", + "origin": "hackage", + "synopsis": "Simplified error-handling", + "version": "2.3.0" + }, + { + "name": "errors-ext", + "origin": "hackage", + "synopsis": "`bracket`-like functions for `ExceptT` over `IO` monad.", + "version": "0.4.2" + }, + { + "name": "ersatz", + "origin": "hackage", + "synopsis": "A monad for expressing SAT or QSAT problems using observable sharing.", + "version": "0.4.9" + }, + { + "name": "esqueleto", + "origin": "hackage", + "synopsis": "Type-safe EDSL for SQL queries on persistent backends.", + "version": "3.4.1.1" + }, + { + "name": "essence-of-live-coding", + "origin": "hackage", + "synopsis": "General purpose live coding framework", + "version": "0.2.5" + }, + { + "name": "essence-of-live-coding-gloss", + "origin": "hackage", + "synopsis": "General purpose live coding framework - Gloss backend", + "version": "0.2.5" + }, + { + "name": "essence-of-live-coding-pulse", + "origin": "hackage", + "synopsis": "General purpose live coding framework - pulse backend", + "version": "0.2.5" + }, + { + "name": "essence-of-live-coding-quickcheck", + "origin": "hackage", + "synopsis": "General purpose live coding framework - QuickCheck integration", + "version": "0.2.5" + }, + { + "name": "etc", + "origin": "hackage", + "synopsis": "Declarative configuration spec for Haskell projects", + "version": "0.4.1.0" + }, + { + "name": "eve", + "origin": "hackage", + "synopsis": "An extensible event framework", + "version": "0.1.9.0" + }, + { + "name": "eventful-core", + "origin": "hackage", + "synopsis": "Core module for eventful", + "version": "0.2.0" + }, + { + "name": "eventful-test-helpers", + "origin": "hackage", + "synopsis": "Common module used for eventful tests", + "version": "0.2.0" + }, + { + "name": "event-list", + "origin": "hackage", + "synopsis": "Event lists with relative or absolute time stamps", + "version": "0.1.2" + }, + { + "name": "eventstore", + "origin": "hackage", + "synopsis": "EventStore TCP Client", + "version": "1.4.1" + }, + { + "name": "every", + "origin": "hackage", + "synopsis": "Run a process every so often.", + "version": "0.0.1" + }, + { + "name": "exact-combinatorics", + "origin": "hackage", + "synopsis": "Efficient exact computation of combinatoric functions.", + "version": "0.2.0.9" + }, + { + "name": "exact-pi", + "origin": "hackage", + "synopsis": "Exact rational multiples of pi (and integer powers of pi)", + "version": "0.5.0.1" + }, + { + "name": "exception-hierarchy", + "origin": "hackage", + "synopsis": "Exception type hierarchy with TemplateHaskell", + "version": "0.1.0.4" + }, + { + "name": "exception-mtl", + "origin": "hackage", + "synopsis": "Exception monad transformer instances for mtl classes.", + "version": "0.4.0.1" + }, + { + "name": "exceptions", + "origin": "hackage", + "synopsis": "Extensible optionally-pure exceptions", + "version": "0.10.4" + }, + { + "name": "exception-transformers", + "origin": "hackage", + "synopsis": "Type classes and monads for unchecked extensible exceptions.", + "version": "0.4.0.9" + }, + { + "name": "exception-via", + "origin": "hackage", + "synopsis": "DerivingVia for your hierarchical exceptions", + "version": "0.1.0.0" + }, + { + "name": "executable-path", + "origin": "hackage", + "synopsis": "Finding out the full path of the executable.", + "version": "0.0.3.1" + }, + { + "name": "exit-codes", + "origin": "hackage", + "synopsis": "Exit codes as defined by BSD", + "version": "1.0.0" + }, + { + "name": "exomizer", + "origin": "hackage", + "synopsis": "Compression and decompression in the exomizer format", + "version": "1.0.0" + }, + { + "name": "experimenter", + "origin": "hackage", + "synopsis": "Perform scientific experiments stored in a DB, and generate reports.", + "version": "0.1.0.12" + }, + { + "name": "expiring-cache-map", + "origin": "hackage", + "synopsis": "General purpose simple caching.", + "version": "0.0.6.1" + }, + { + "name": "explicit-exception", + "origin": "hackage", + "synopsis": "Exceptions which are explicit in the type signature.", + "version": "0.1.10" + }, + { + "name": "exp-pairs", + "origin": "hackage", + "synopsis": "Linear programming over exponent pairs", + "version": "0.2.1.0" + }, + { + "name": "express", + "origin": "hackage", + "synopsis": "Dynamically-typed expressions involving applications and variables.", + "version": "0.1.6" + }, + { + "name": "extended-reals", + "origin": "hackage", + "synopsis": "Extension of real numbers with positive/negative infinities", + "version": "0.2.4.0" + }, + { + "name": "extensible-effects", + "origin": "hackage", + "synopsis": "An Alternative to Monad Transformers", + "version": "5.0.0.1" + }, + { + "name": "extensible-exceptions", + "origin": "hackage", + "synopsis": "Extensible exceptions", + "version": "0.1.1.4" + }, + { + "name": "extra", + "origin": "hackage", + "synopsis": "Extra functions I use.", + "version": "1.7.9" + }, + { + "name": "extractable-singleton", + "origin": "hackage", + "synopsis": "A functor, where the \"stored\" value is isomorphic to Identity", + "version": "0.0.1" + }, + { + "name": "extrapolate", + "origin": "hackage", + "synopsis": "generalize counter-examples of test properties", + "version": "0.4.4" + }, + { + "name": "fail", + "origin": "hackage", + "synopsis": "Forward-compatible MonadFail class", + "version": "4.9.0.0" + }, + { + "name": "failable", + "origin": "hackage", + "synopsis": "A 'Failable' error monad class to unify failure across monads that can fail", + "version": "1.2.4.0" + }, + { + "name": "fakedata", + "origin": "hackage", + "synopsis": "Library for producing fake data", + "version": "0.8.0" + }, + { + "name": "fakedata-parser", + "origin": "hackage", + "synopsis": "", + "version": "0.1.0.0" + }, + { + "name": "fakefs", + "origin": "hackage", + "synopsis": "Extensible fake file system for testing.", + "version": "0.3.0.2" + }, + { + "name": "fakepull", + "origin": "hackage", + "synopsis": "Monad to pull from fake stream-like objects.", + "version": "0.3.0.2" + }, + { + "name": "fast-digits", + "origin": "hackage", + "synopsis": "Integer-to-digits conversion.", + "version": "0.3.0.0" + }, + { + "name": "fast-logger", + "origin": "hackage", + "synopsis": "A fast logging system", + "version": "3.0.3" + }, + { + "name": "fast-math", + "origin": "hackage", + "synopsis": "Non IEEE-754 compliant compile-time floating-point optimisations", + "version": "1.0.2" + }, + { + "name": "fb", + "origin": "hackage", + "synopsis": "Bindings to Facebook's API.", + "version": "2.1.1" + }, + { + "name": "feature-flags", + "origin": "hackage", + "synopsis": "A simple library for dynamically enabling and disabling functionality.", + "version": "0.1.0.1" + }, + { + "name": "fedora-dists", + "origin": "hackage", + "synopsis": "Library for Fedora distribution versions", + "version": "1.1.2" + }, + { + "name": "fedora-haskell-tools", + "origin": "hackage", + "synopsis": "Building and maintenance tools for Fedora Haskell", + "version": "0.9" + }, + { + "name": "feed", + "origin": "hackage", + "synopsis": "Interfacing with RSS (v 0.9x, 2.x, 1.0) + Atom feeds.", + "version": "1.3.2.0" + }, + { + "name": "FenwickTree", + "origin": "hackage", + "synopsis": "Data structure for fast query and update of cumulative sums", + "version": "0.1.2.1" + }, + { + "name": "fft", + "origin": "hackage", + "synopsis": "Bindings to the FFTW library.", + "version": "0.1.8.6" + }, + { + "name": "fgl", + "origin": "hackage", + "synopsis": "Martin Erwig's Functional Graph Library", + "version": "5.7.0.3" + }, + { + "name": "file-embed", + "origin": "hackage", + "synopsis": "Use Template Haskell to embed file contents directly.", + "version": "0.0.13.0" + }, + { + "name": "file-embed-lzma", + "origin": "hackage", + "synopsis": "Use Template Haskell to embed (LZMA compressed) data.", + "version": "0" + }, + { + "name": "filelock", + "origin": "hackage", + "synopsis": "Portable interface to file locking (flock / LockFileEx)", + "version": "0.1.1.5" + }, + { + "name": "filemanip", + "origin": "hackage", + "synopsis": "Expressive file and directory manipulation for Haskell.", + "version": "0.3.6.3" + }, + { + "name": "file-modules", + "origin": "hackage", + "synopsis": "Takes a Haskell source-code file and outputs its modules.", + "version": "0.1.2.4" + }, + { + "name": "filepath", + "origin": "core", + "synopsis": "Library for manipulating FilePaths in a cross platform way.", + "version": "1.4.2.1" + }, + { + "name": "file-path-th", + "origin": "hackage", + "synopsis": "Template Haskell utilities for filepaths.", + "version": "0.1.0.0" + }, + { + "name": "filepattern", + "origin": "hackage", + "synopsis": "File path glob-like matching", + "version": "0.1.2" + }, + { + "name": "fileplow", + "origin": "hackage", + "synopsis": "Library to process and search large files or a collection of files", + "version": "0.1.0.0" + }, + { + "name": "filtrable", + "origin": "hackage", + "synopsis": "Class of filtrable containers", + "version": "0.1.4.0" + }, + { + "name": "fin", + "origin": "hackage", + "synopsis": "Nat and Fin: peano naturals and finite numbers", + "version": "0.1.1" + }, + { + "name": "FindBin", + "origin": "hackage", + "synopsis": "Locate directory of original program", + "version": "0.0.5" + }, + { + "name": "fingertree", + "origin": "hackage", + "synopsis": "Generic finger-tree structure, with example instances", + "version": "0.1.4.2" + }, + { + "name": "finite-typelits", + "origin": "hackage", + "synopsis": "A type inhabited by finitely many values, indexed by type-level naturals.", + "version": "0.1.4.2" + }, + { + "name": "first-class-families", + "origin": "hackage", + "synopsis": "First-class type families", + "version": "0.8.0.1" + }, + { + "name": "first-class-patterns", + "origin": "hackage", + "synopsis": "First class patterns and pattern matching, using type families", + "version": "0.3.2.5" + }, + { + "name": "fitspec", + "origin": "hackage", + "synopsis": "refining property sets for testing Haskell programs", + "version": "0.4.8" + }, + { + "name": "fixed", + "origin": "hackage", + "synopsis": "Signed 15.16 precision fixed point arithmetic", + "version": "0.3" + }, + { + "name": "fixed-length", + "origin": "hackage", + "synopsis": "Lists with statically known length based on non-empty package.", + "version": "0.2.2.1" + }, + { + "name": "fixed-vector", + "origin": "hackage", + "synopsis": "Generic vectors with statically known size.", + "version": "1.2.0.0" + }, + { + "name": "fixed-vector-hetero", + "origin": "hackage", + "synopsis": "Library for working with product types generically", + "version": "0.6.1.0" + }, + { + "name": "flac", + "origin": "hackage", + "synopsis": "Complete high-level binding to libFLAC", + "version": "0.2.0" + }, + { + "name": "flac-picture", + "origin": "hackage", + "synopsis": "Support for writing picture to FLAC metadata blocks with JuicyPixels", + "version": "0.1.2" + }, + { + "name": "flags-applicative", + "origin": "hackage", + "synopsis": "Applicative flag parsing", + "version": "0.1.0.3" + }, + { + "name": "flat", + "origin": "hackage", + "synopsis": "Principled and efficient bit-oriented binary serialization.", + "version": "0.4.4" + }, + { + "name": "flat-mcmc", + "origin": "hackage", + "synopsis": "Painless general-purpose sampling.", + "version": "1.5.2" + }, + { + "name": "flexible-defaults", + "origin": "hackage", + "synopsis": "Generate default function implementations for complex type classes.", + "version": "0.0.3" + }, + { + "name": "FloatingHex", + "origin": "hackage", + "synopsis": "Read and write hexadecimal floating point numbers", + "version": "0.5" + }, + { + "name": "floatshow", + "origin": "hackage", + "synopsis": "Alternative faster String representations for Double and Float,\nString representations for more general numeric types.", + "version": "0.2.4" + }, + { + "name": "flow", + "origin": "hackage", + "synopsis": "Write more understandable Haskell.", + "version": "1.0.22" + }, + { + "name": "flush-queue", + "origin": "hackage", + "synopsis": "Concurrent bouded blocking queues optimized for flushing. Both IO and STM implementations.", + "version": "1.0.0" + }, + { + "name": "fmlist", + "origin": "hackage", + "synopsis": "FoldMap lists", + "version": "0.9.4" + }, + { + "name": "fmt", + "origin": "hackage", + "synopsis": "A new formatting library", + "version": "0.6.1.2" + }, + { + "name": "fn", + "origin": "hackage", + "synopsis": "A functional web framework.", + "version": "0.3.0.2" + }, + { + "name": "focus", + "origin": "hackage", + "synopsis": "A general abstraction for manipulating elements of container data structures", + "version": "1.0.2" + }, + { + "name": "focuslist", + "origin": "hackage", + "synopsis": "Lists with a focused element", + "version": "0.1.0.2" + }, + { + "name": "foldable1", + "origin": "hackage", + "synopsis": "Foldable types with at least 1 element", + "version": "0.1.0.0" + }, + { + "name": "fold-debounce", + "origin": "hackage", + "synopsis": "Fold multiple events that happen in a given period of time.", + "version": "0.2.0.9" + }, + { + "name": "fold-debounce-conduit", + "origin": "hackage", + "synopsis": "Regulate input traffic from conduit Source with Control.FoldDebounce", + "version": "0.2.0.6" + }, + { + "name": "foldl", + "origin": "hackage", + "synopsis": "Composable, streaming, and efficient left folds", + "version": "1.4.11" + }, + { + "name": "folds", + "origin": "hackage", + "synopsis": "Beautiful Folding", + "version": "0.7.6" + }, + { + "name": "follow-file", + "origin": "hackage", + "synopsis": "Be notified when a file gets appended, solely with what was added. Warning - only works on linux and for files that are strictly appended, like log files.", + "version": "0.0.3" + }, + { + "name": "FontyFruity", + "origin": "hackage", + "synopsis": "A true type file format loader", + "version": "0.5.3.5" + }, + { + "name": "foreign-store", + "origin": "hackage", + "synopsis": "Store a stable pointer in a foreign context to be retrieved later.", + "version": "0.2" + }, + { + "name": "ForestStructures", + "origin": "hackage", + "synopsis": "Tree- and forest structures", + "version": "0.0.1.0" + }, + { + "name": "forkable-monad", + "origin": "hackage", + "synopsis": "An implementation of forkIO for monad stacks.", + "version": "0.2.0.3" + }, + { + "name": "forma", + "origin": "hackage", + "synopsis": "Parse and validate forms in JSON format", + "version": "1.1.3" + }, + { + "name": "format-numbers", + "origin": "hackage", + "synopsis": "Various number formatting functions", + "version": "0.1.0.1" + }, + { + "name": "formatting", + "origin": "hackage", + "synopsis": "Combinator-based type-safe formatting (like printf() or FORMAT)", + "version": "6.3.7" + }, + { + "name": "foundation", + "origin": "hackage", + "synopsis": "Alternative prelude with batteries and no dependencies", + "version": "0.0.25" + }, + { + "name": "free", + "origin": "hackage", + "synopsis": "Monads for free", + "version": "5.1.5" + }, + { + "name": "free-categories", + "origin": "hackage", + "synopsis": "free categories", + "version": "0.2.0.2" + }, + { + "name": "freenect", + "origin": "hackage", + "synopsis": "Interface to the Kinect device.", + "version": "1.2.1" + }, + { + "name": "freer-simple", + "origin": "hackage", + "synopsis": "Implementation of a friendly effect system for Haskell.", + "version": "1.2.1.1" + }, + { + "name": "freetype2", + "origin": "hackage", + "synopsis": "Haskell bindings for FreeType 2 library", + "version": "0.2.0" + }, + { + "name": "free-vl", + "origin": "hackage", + "synopsis": "van Laarhoven encoded Free Monad with Extensible Effects", + "version": "0.1.4" + }, + { + "name": "friendly-time", + "origin": "hackage", + "synopsis": "Print time information in friendly ways", + "version": "0.4.1" + }, + { + "name": "from-sum", + "origin": "hackage", + "synopsis": "Combinators for working with Maybe and Either", + "version": "0.2.3.0" + }, + { + "name": "frontmatter", + "origin": "hackage", + "synopsis": "Parses frontmatter as used in Jekyll markdown files.", + "version": "0.1.0.2" + }, + { + "name": "fsnotify", + "origin": "hackage", + "synopsis": "Cross platform library for file change notification.", + "version": "0.3.0.1" + }, + { + "name": "fsnotify-conduit", + "origin": "hackage", + "synopsis": "Get filesystem notifications as a stream of events", + "version": "0.1.1.1" + }, + { + "name": "ftp-client", + "origin": "hackage", + "synopsis": "Transfer files with FTP and FTPS", + "version": "0.5.1.4" + }, + { + "name": "ftp-client-conduit", + "origin": "hackage", + "synopsis": "Transfer file with FTP and FTPS with Conduit", + "version": "0.5.0.5" + }, + { + "name": "funcmp", + "origin": "hackage", + "synopsis": "Functional MetaPost is a Haskell frontend to the MetaPost language", + "version": "1.9" + }, + { + "name": "function-builder", + "origin": "hackage", + "synopsis": "Create poly variadic functions for monoidal results", + "version": "0.3.0.1" + }, + { + "name": "functor-classes-compat", + "origin": "hackage", + "synopsis": "Data.Functor.Classes instances for core packages", + "version": "1.0.1" + }, + { + "name": "fusion-plugin", + "origin": "hackage", + "synopsis": "GHC plugin to make stream fusion more predictable.", + "version": "0.2.2" + }, + { + "name": "fusion-plugin-types", + "origin": "hackage", + "synopsis": "Types for the fusion-plugin package.", + "version": "0.1.0" + }, + { + "name": "fuzzcheck", + "origin": "hackage", + "synopsis": "A simple checker for stress testing monadic code", + "version": "0.1.1" + }, + { + "name": "fuzzy", + "origin": "hackage", + "synopsis": "Filters a list based on a fuzzy string search.", + "version": "0.1.0.0" + }, + { + "name": "fuzzy-dates", + "origin": "hackage", + "synopsis": "Libary for parsing dates in strings in varied formats.", + "version": "0.1.1.2" + }, + { + "name": "fuzzyset", + "origin": "hackage", + "synopsis": "Fuzzy set for approximate string matching", + "version": "0.2.0" + }, + { + "name": "fuzzy-time", + "origin": "hackage", + "synopsis": "", + "version": "0.1.0.0" + }, + { + "name": "gauge", + "origin": "hackage", + "synopsis": "small framework for performance measurement and analysis", + "version": "0.2.5" + }, + { + "name": "gd", + "origin": "hackage", + "synopsis": "A Haskell binding to a subset of the GD graphics library", + "version": "3000.7.3" + }, + { + "name": "gdp", + "origin": "hackage", + "synopsis": "Reason about invariants and preconditions with ghosts of departed proofs.", + "version": "0.0.3.0" + }, + { + "name": "general-games", + "origin": "hackage", + "synopsis": "Library supporting simulation of a number of games", + "version": "1.1.1" + }, + { + "name": "generic-aeson", + "origin": "hackage", + "synopsis": "Derivation of Aeson instances using GHC generics.", + "version": "0.2.0.12" + }, + { + "name": "generic-arbitrary", + "origin": "hackage", + "synopsis": "Generic implementation for QuickCheck's Arbitrary", + "version": "0.1.0" + }, + { + "name": "generic-constraints", + "origin": "hackage", + "synopsis": "Constraints via Generic", + "version": "1.1.1.1" + }, + { + "name": "generic-data", + "origin": "hackage", + "synopsis": "Deriving instances with GHC.Generics and related utilities", + "version": "0.9.2.0" + }, + { + "name": "generic-data-surgery", + "origin": "hackage", + "synopsis": "Surgery for generic data types", + "version": "0.3.0.0" + }, + { + "name": "generic-deriving", + "origin": "hackage", + "synopsis": "Generic programming library for generalised deriving.", + "version": "1.13.1" + }, + { + "name": "generic-lens", + "origin": "hackage", + "synopsis": "Generically derive traversals, lenses and prisms.", + "version": "2.0.0.0" + }, + { + "name": "generic-lens-core", + "origin": "hackage", + "synopsis": "Generically derive traversals, lenses and prisms.", + "version": "2.0.0.0" + }, + { + "name": "generic-monoid", + "origin": "hackage", + "synopsis": "Derive monoid instances for product types.", + "version": "0.1.0.1" + }, + { + "name": "generic-optics", + "origin": "hackage", + "synopsis": "Generically derive traversals, lenses and prisms.", + "version": "2.0.0.0" + }, + { + "name": "GenericPretty", + "origin": "hackage", + "synopsis": "A generic, derivable, haskell pretty printer.", + "version": "1.2.2" + }, + { + "name": "generic-random", + "origin": "hackage", + "synopsis": "Generic random generators for QuickCheck", + "version": "1.3.0.1" + }, + { + "name": "generics-sop", + "origin": "hackage", + "synopsis": "Generic Programming using True Sums of Products", + "version": "0.5.1.1" + }, + { + "name": "generics-sop-lens", + "origin": "hackage", + "synopsis": "Lenses for types in generics-sop", + "version": "0.2.0.1" + }, + { + "name": "geniplate-mirror", + "origin": "hackage", + "synopsis": "Use Template Haskell to generate Uniplate-like functions.", + "version": "0.7.7" + }, + { + "name": "genvalidity", + "origin": "hackage", + "synopsis": "Testing utilities for the validity library", + "version": "0.11.0.0" + }, + { + "name": "genvalidity-aeson", + "origin": "hackage", + "synopsis": "GenValidity support for aeson", + "version": "0.3.0.0" + }, + { + "name": "genvalidity-bytestring", + "origin": "hackage", + "synopsis": "GenValidity support for ByteString", + "version": "0.6.0.0" + }, + { + "name": "genvalidity-containers", + "origin": "hackage", + "synopsis": "GenValidity support for containers", + "version": "0.9.0.0" + }, + { + "name": "genvalidity-criterion", + "origin": "hackage", + "synopsis": "Criterion benchmarks for generators", + "version": "0.2.0.0" + }, + { + "name": "genvalidity-hspec", + "origin": "hackage", + "synopsis": "Standard spec's for GenValidity instances", + "version": "0.7.0.4" + }, + { + "name": "genvalidity-hspec-aeson", + "origin": "hackage", + "synopsis": "Standard spec's for aeson-related instances", + "version": "0.3.1.1" + }, + { + "name": "genvalidity-hspec-binary", + "origin": "hackage", + "synopsis": "Standard spec's for binary-related Instances", + "version": "0.2.0.4" + }, + { + "name": "genvalidity-hspec-cereal", + "origin": "hackage", + "synopsis": "Standard spec's for cereal-related instances", + "version": "0.2.0.4" + }, + { + "name": "genvalidity-hspec-hashable", + "origin": "hackage", + "synopsis": "Standard spec's for Hashable instances", + "version": "0.2.0.5" + }, + { + "name": "genvalidity-hspec-optics", + "origin": "hackage", + "synopsis": "Standard spec's for lens", + "version": "0.1.1.2" + }, + { + "name": "genvalidity-hspec-persistent", + "origin": "hackage", + "synopsis": "Standard spec's for persistent-related instances", + "version": "0.0.0.1" + }, + { + "name": "genvalidity-mergeful", + "origin": "hackage", + "synopsis": "", + "version": "0.2.0.0" + }, + { + "name": "genvalidity-mergeless", + "origin": "hackage", + "synopsis": "", + "version": "0.2.0.0" + }, + { + "name": "genvalidity-path", + "origin": "hackage", + "synopsis": "GenValidity support for Path", + "version": "0.3.0.4" + }, + { + "name": "genvalidity-property", + "origin": "hackage", + "synopsis": "Standard properties for functions on `Validity` types", + "version": "0.5.0.1" + }, + { + "name": "genvalidity-scientific", + "origin": "hackage", + "synopsis": "GenValidity support for Scientific", + "version": "0.2.1.1" + }, + { + "name": "genvalidity-text", + "origin": "hackage", + "synopsis": "GenValidity support for Text", + "version": "0.7.0.2" + }, + { + "name": "genvalidity-time", + "origin": "hackage", + "synopsis": "GenValidity support for time", + "version": "0.3.0.0" + }, + { + "name": "genvalidity-typed-uuid", + "origin": "hackage", + "synopsis": "Generators for Phantom-Typed version of UUID", + "version": "0.0.0.2" + }, + { + "name": "genvalidity-unordered-containers", + "origin": "hackage", + "synopsis": "GenValidity support for unordered-containers", + "version": "0.3.0.1" + }, + { + "name": "genvalidity-uuid", + "origin": "hackage", + "synopsis": "GenValidity support for UUID", + "version": "0.1.0.4" + }, + { + "name": "genvalidity-vector", + "origin": "hackage", + "synopsis": "GenValidity support for vector", + "version": "0.3.0.1" + }, + { + "name": "geojson", + "origin": "hackage", + "synopsis": "A thin GeoJSON Layer above the aeson library", + "version": "4.0.2" + }, + { + "name": "getopt-generics", + "origin": "hackage", + "synopsis": "Create command line interfaces with ease", + "version": "0.13.0.4" + }, + { + "name": "ghc-byteorder", + "origin": "hackage", + "synopsis": "\"GHC.ByteOrder\" API Compatibility Layer", + "version": "4.11.0.0.10" + }, + { + "name": "ghc-check", + "origin": "hackage", + "synopsis": "detect mismatches between compile-time and run-time versions of the ghc api", + "version": "0.5.0.4" + }, + { + "name": "ghc-compact", + "origin": "core", + "synopsis": "In memory storage of deeply evaluated data structure", + "version": "0.1.0.0" + }, + { + "name": "ghc-core", + "origin": "hackage", + "synopsis": "Display GHC's core and assembly output in a pager", + "version": "0.5.6" + }, + { + "name": "ghc-events", + "origin": "hackage", + "synopsis": "Library and tool for parsing .eventlog files from GHC", + "version": "0.15.1" + }, + { + "name": "ghc-exactprint", + "origin": "hackage", + "synopsis": "ExactPrint for GHC", + "version": "0.6.4" + }, + { + "name": "ghcid", + "origin": "hackage", + "synopsis": "GHCi based bare bones IDE", + "version": "0.8.7" + }, + { + "name": "ghci-hexcalc", + "origin": "hackage", + "synopsis": "GHCi as a Hex Calculator interactive", + "version": "0.1.1.0" + }, + { + "name": "ghcjs-codemirror", + "origin": "hackage", + "synopsis": "Installs CodeMirror JavaScript files", + "version": "0.0.0.2" + }, + { + "name": "ghc-lib", + "origin": "hackage", + "synopsis": "The GHC API, decoupled from GHC versions", + "version": "8.10.4.20210206" + }, + { + "name": "ghc-lib-parser", + "origin": "hackage", + "synopsis": "The GHC API, decoupled from GHC versions", + "version": "8.10.4.20210206" + }, + { + "name": "ghc-lib-parser-ex", + "origin": "hackage", + "synopsis": "Algorithms on GHC parse trees", + "version": "8.10.0.19" + }, + { + "name": "ghc-parser", + "origin": "hackage", + "synopsis": "Haskell source parser from GHC.", + "version": "0.2.2.0" + }, + { + "name": "ghc-paths", + "origin": "hackage", + "synopsis": "Knowledge of GHC's installation directories", + "version": "0.1.0.12" + }, + { + "name": "ghc-prim", + "origin": "core", + "synopsis": "GHC primitives", + "version": "0.6.1" + }, + { + "name": "ghc-prof", + "origin": "hackage", + "synopsis": "Library for parsing GHC time and allocation profiling reports", + "version": "1.4.1.8" + }, + { + "name": "ghc-source-gen", + "origin": "hackage", + "synopsis": "Constructs Haskell syntax trees for the GHC API.", + "version": "0.4.0.0" + }, + { + "name": "ghc-syntax-highlighter", + "origin": "hackage", + "synopsis": "Syntax highlighter for Haskell using lexer of GHC itself", + "version": "0.0.6.0" + }, + { + "name": "ghc-tcplugins-extra", + "origin": "hackage", + "synopsis": "Utilities for writing GHC type-checker plugins", + "version": "0.4.1" + }, + { + "name": "ghc-trace-events", + "origin": "hackage", + "synopsis": "Faster traceEvent and traceMarker, and binary object logging for\neventlog", + "version": "0.1.2.2" + }, + { + "name": "ghc-typelits-extra", + "origin": "hackage", + "synopsis": "Additional type-level operations on GHC.TypeLits.Nat", + "version": "0.4.2" + }, + { + "name": "ghc-typelits-knownnat", + "origin": "hackage", + "synopsis": "Derive KnownNat constraints from other KnownNat constraints", + "version": "0.7.5" + }, + { + "name": "ghc-typelits-natnormalise", + "origin": "hackage", + "synopsis": "GHC typechecker plugin for types of kind GHC.TypeLits.Nat", + "version": "0.7.4" + }, + { + "name": "ghc-typelits-presburger", + "origin": "hackage", + "synopsis": "Presburger Arithmetic Solver for GHC Type-level natural numbers.", + "version": "0.5.2.0" + }, + { + "name": "ghost-buster", + "origin": "hackage", + "synopsis": "Existential type utilites", + "version": "0.1.1.0" + }, + { + "name": "gi-atk", + "origin": "hackage", + "synopsis": "Atk bindings", + "version": "2.0.22" + }, + { + "name": "gi-cairo", + "origin": "hackage", + "synopsis": "Cairo bindings", + "version": "1.0.24" + }, + { + "name": "gi-cairo-connector", + "origin": "hackage", + "synopsis": "GI friendly Binding to the Cairo library.", + "version": "0.1.0" + }, + { + "name": "gi-cairo-render", + "origin": "hackage", + "synopsis": "GI friendly Binding to the Cairo library.", + "version": "0.1.0" + }, + { + "name": "gi-dbusmenu", + "origin": "hackage", + "synopsis": "Dbusmenu bindings", + "version": "0.4.8" + }, + { + "name": "gi-dbusmenugtk3", + "origin": "hackage", + "synopsis": "DbusmenuGtk bindings", + "version": "0.4.9" + }, + { + "name": "gi-gdk", + "origin": "hackage", + "synopsis": "Gdk bindings", + "version": "3.0.23" + }, + { + "name": "gi-gdkpixbuf", + "origin": "hackage", + "synopsis": "GdkPixbuf bindings", + "version": "2.0.24" + }, + { + "name": "gi-gdkx11", + "origin": "hackage", + "synopsis": "GdkX11 bindings", + "version": "3.0.10" + }, + { + "name": "gi-gio", + "origin": "hackage", + "synopsis": "Gio bindings", + "version": "2.0.27" + }, + { + "name": "gi-glib", + "origin": "hackage", + "synopsis": "GLib bindings", + "version": "2.0.24" + }, + { + "name": "gi-gobject", + "origin": "hackage", + "synopsis": "GObject bindings", + "version": "2.0.25" + }, + { + "name": "gi-graphene", + "origin": "hackage", + "synopsis": "Graphene bindings", + "version": "1.0.2" + }, + { + "name": "gi-gtk", + "origin": "hackage", + "synopsis": "Gtk bindings", + "version": "3.0.36" + }, + { + "name": "gi-gtk-hs", + "origin": "hackage", + "synopsis": "A wrapper for gi-gtk, adding a few more idiomatic API parts on top", + "version": "0.3.9" + }, + { + "name": "gi-harfbuzz", + "origin": "hackage", + "synopsis": "HarfBuzz bindings", + "version": "0.0.3" + }, + { + "name": "ginger", + "origin": "hackage", + "synopsis": "An implementation of the Jinja2 template language in Haskell", + "version": "0.10.1.0" + }, + { + "name": "gingersnap", + "origin": "hackage", + "synopsis": "Consistent and safe JSON APIs with snap-core and (by default) postgresql-simple", + "version": "0.3.1.0" + }, + { + "name": "gi-pango", + "origin": "hackage", + "synopsis": "Pango bindings", + "version": "1.0.23" + }, + { + "name": "githash", + "origin": "hackage", + "synopsis": "Compile git revision info into Haskell projects", + "version": "0.1.5.0" + }, + { + "name": "github", + "origin": "hackage", + "synopsis": "Access to the GitHub API, v3.", + "version": "0.26" + }, + { + "name": "github-release", + "origin": "hackage", + "synopsis": "Upload files to GitHub releases.", + "version": "1.3.7" + }, + { + "name": "github-rest", + "origin": "hackage", + "synopsis": "Query the GitHub REST API programmatically", + "version": "1.0.3" + }, + { + "name": "github-types", + "origin": "hackage", + "synopsis": "Type definitions for objects used by the GitHub v3 API", + "version": "0.2.1" + }, + { + "name": "github-webhooks", + "origin": "hackage", + "synopsis": "Aeson instances for GitHub Webhook payloads.", + "version": "0.15.0" + }, + { + "name": "gitlab-haskell", + "origin": "hackage", + "synopsis": "A Haskell library for the GitLab web API", + "version": "0.2.5" + }, + { + "name": "gitrev", + "origin": "hackage", + "synopsis": "Compile git revision info into Haskell projects", + "version": "1.3.1" + }, + { + "name": "gi-xlib", + "origin": "hackage", + "synopsis": "xlib bindings", + "version": "2.0.9" + }, + { + "name": "gl", + "origin": "hackage", + "synopsis": "Complete OpenGL raw bindings", + "version": "0.9" + }, + { + "name": "glabrous", + "origin": "hackage", + "synopsis": "A template DSL library", + "version": "2.0.3" + }, + { + "name": "GLFW-b", + "origin": "hackage", + "synopsis": "Bindings to GLFW OpenGL library", + "version": "3.3.0.0" + }, + { + "name": "Glob", + "origin": "hackage", + "synopsis": "Globbing library", + "version": "0.10.1" + }, + { + "name": "gloss", + "origin": "hackage", + "synopsis": "Painless 2D vector graphics, animations and simulations.", + "version": "1.13.2.1" + }, + { + "name": "gloss-rendering", + "origin": "hackage", + "synopsis": "Gloss picture data types and rendering functions.", + "version": "1.13.1.1" + }, + { + "name": "GLURaw", + "origin": "hackage", + "synopsis": "A raw binding for the OpenGL graphics system", + "version": "2.0.0.4" + }, + { + "name": "GLUT", + "origin": "hackage", + "synopsis": "A binding for the OpenGL Utility Toolkit", + "version": "2.7.0.16" + }, + { + "name": "gluturtle", + "origin": "hackage", + "synopsis": "turtle like LOGO with glut", + "version": "0.0.58.1" + }, + { + "name": "gnuplot", + "origin": "hackage", + "synopsis": "2D and 3D plots using gnuplot", + "version": "0.5.6.1" + }, + { + "name": "google-isbn", + "origin": "hackage", + "synopsis": "", + "version": "1.0.3" + }, + { + "name": "gothic", + "origin": "hackage", + "synopsis": "A Haskell Vault KVv2 secret engine client", + "version": "0.1.6" + }, + { + "name": "gpolyline", + "origin": "hackage", + "synopsis": "Pure module for encoding/decoding Google Polyline", + "version": "0.1.0.1" + }, + { + "name": "graph-core", + "origin": "hackage", + "synopsis": "Fast, memory efficient and persistent graph implementation", + "version": "0.3.0.0" + }, + { + "name": "graphite", + "origin": "hackage", + "synopsis": "Graphs and networks library", + "version": "0.10.0.1" + }, + { + "name": "graphql-client", + "origin": "hackage", + "synopsis": "A client for Haskell programs to query a GraphQL API", + "version": "1.1.1" + }, + { + "name": "graphs", + "origin": "hackage", + "synopsis": "A simple monadic graph library", + "version": "0.7.1" + }, + { + "name": "graphviz", + "origin": "hackage", + "synopsis": "Bindings to Graphviz for graph visualisation.", + "version": "2999.20.1.0" + }, + { + "name": "graph-wrapper", + "origin": "hackage", + "synopsis": "A wrapper around the standard Data.Graph with a less awkward interface", + "version": "0.2.6.0" + }, + { + "name": "gravatar", + "origin": "hackage", + "synopsis": "Generate Gravatar image URLs", + "version": "0.8.0" + }, + { + "name": "greskell", + "origin": "hackage", + "synopsis": "Haskell binding for Gremlin graph query language", + "version": "1.2.0.1" + }, + { + "name": "greskell-core", + "origin": "hackage", + "synopsis": "Haskell binding for Gremlin graph query language - core data types and tools", + "version": "0.1.3.6" + }, + { + "name": "greskell-websocket", + "origin": "hackage", + "synopsis": "Haskell client for Gremlin Server using WebSocket serializer", + "version": "0.1.2.5" + }, + { + "name": "groom", + "origin": "hackage", + "synopsis": "Pretty printing for well-behaved Show\ninstances.", + "version": "0.1.2.1" + }, + { + "name": "group-by-date", + "origin": "hackage", + "synopsis": "Shell command for grouping files by dates into folders", + "version": "0.1.0.4" + }, + { + "name": "groups", + "origin": "hackage", + "synopsis": "Groups", + "version": "0.5.2" + }, + { + "name": "gtk-sni-tray", + "origin": "hackage", + "synopsis": "A standalone StatusNotifierItem/AppIndicator tray", + "version": "0.1.6.0" + }, + { + "name": "gtk-strut", + "origin": "hackage", + "synopsis": "Libary for creating strut windows with gi-gtk", + "version": "0.1.3.0" + }, + { + "name": "guarded-allocation", + "origin": "hackage", + "synopsis": "Memory allocation with added stress tests and integrity checks", + "version": "0.0.1" + }, + { + "name": "H", + "origin": "hackage", + "synopsis": "The Haskell/R mixed programming environment.", + "version": "0.9.0.1" + }, + { + "name": "hackage-db", + "origin": "hackage", + "synopsis": "Access cabal-install's Hackage database via Data.Map", + "version": "2.1.0" + }, + { + "name": "hackage-security", + "origin": "hackage", + "synopsis": "Hackage security library", + "version": "0.6.0.1" + }, + { + "name": "haddock-library", + "origin": "hackage", + "synopsis": "Library exposing some functionality of Haddock.", + "version": "1.9.0" + }, + { + "name": "hadolint", + "origin": "hackage", + "synopsis": "Dockerfile Linter JavaScript API", + "version": "1.19.0" + }, + { + "name": "hadoop-streaming", + "origin": "hackage", + "synopsis": "A simple Hadoop streaming library", + "version": "0.2.0.3" + }, + { + "name": "hakyll-convert", + "origin": "hackage", + "synopsis": "Convert from other blog engines to Hakyll.", + "version": "0.3.0.4" + }, + { + "name": "half", + "origin": "hackage", + "synopsis": "Half-precision floating-point", + "version": "0.3.1" + }, + { + "name": "hall-symbols", + "origin": "hackage", + "synopsis": "Symmetry operations generater of Hall Symbols", + "version": "0.1.0.6" + }, + { + "name": "hamtsolo", + "origin": "hackage", + "synopsis": "Intel AMT serial-over-lan (SOL) client", + "version": "1.0.3" + }, + { + "name": "HandsomeSoup", + "origin": "hackage", + "synopsis": "Work with HTML more easily in HXT", + "version": "0.4.2" + }, + { + "name": "hapistrano", + "origin": "hackage", + "synopsis": "A deployment library for Haskell applications", + "version": "0.4.1.3" + }, + { + "name": "happstack-server", + "origin": "hackage", + "synopsis": "Web related tools and services.", + "version": "7.7.0" + }, + { + "name": "happy", + "origin": "hackage", + "synopsis": "Happy is a parser generator for Haskell", + "version": "1.20.0" + }, + { + "name": "HasBigDecimal", + "origin": "hackage", + "synopsis": "A library for arbitrary precision decimal numbers.", + "version": "0.1.1" + }, + { + "name": "hashable", + "origin": "hackage", + "synopsis": "A class for types that can be converted to a hash value", + "version": "1.3.0.0" + }, + { + "name": "hashable-time", + "origin": "hackage", + "synopsis": "Hashable instances for Data.Time", + "version": "0.2.1" + }, + { + "name": "hashids", + "origin": "hackage", + "synopsis": "Hashids generates short, unique, non-sequential ids from numbers.", + "version": "1.0.2.4" + }, + { + "name": "hashing", + "origin": "hackage", + "synopsis": "A pure haskell library implements several hash algorithms.", + "version": "0.1.0.1" + }, + { + "name": "hashmap", + "origin": "hackage", + "synopsis": "Persistent containers Map and Set based on hashing.", + "version": "1.3.3" + }, + { + "name": "hashtables", + "origin": "hackage", + "synopsis": "Mutable hash tables in the ST monad", + "version": "1.2.4.1" + }, + { + "name": "haskeline", + "origin": "hackage", + "synopsis": "A command-line interface for user input, written in Haskell.", + "version": "0.8.1.2" + }, + { + "name": "haskell-gi", + "origin": "hackage", + "synopsis": "Generate Haskell bindings for GObject Introspection capable libraries", + "version": "0.24.7" + }, + { + "name": "haskell-gi-base", + "origin": "hackage", + "synopsis": "Foundation for libraries generated by haskell-gi", + "version": "0.24.5" + }, + { + "name": "haskell-gi-overloading", + "origin": "hackage", + "synopsis": "Overloading support for haskell-gi", + "version": "1.0" + }, + { + "name": "haskell-import-graph", + "origin": "hackage", + "synopsis": "create haskell import graph for graphviz", + "version": "1.0.4" + }, + { + "name": "haskell-lexer", + "origin": "hackage", + "synopsis": "A fully compliant Haskell 98 lexer.", + "version": "1.1" + }, + { + "name": "haskell-lsp", + "origin": "hackage", + "synopsis": "Haskell library for the Microsoft Language Server Protocol", + "version": "0.22.0.0" + }, + { + "name": "haskell-lsp-types", + "origin": "hackage", + "synopsis": "Haskell library for the Microsoft Language Server Protocol, data types", + "version": "0.22.0.0" + }, + { + "name": "haskell-names", + "origin": "hackage", + "synopsis": "Name resolution library for Haskell", + "version": "0.9.9" + }, + { + "name": "haskell-src", + "origin": "hackage", + "synopsis": "Support for manipulating Haskell source code", + "version": "1.0.3.1" + }, + { + "name": "haskell-src-exts", + "origin": "hackage", + "synopsis": "Manipulating Haskell source: abstract syntax, lexer, parser, and pretty-printer", + "version": "1.23.1" + }, + { + "name": "haskell-src-exts-util", + "origin": "hackage", + "synopsis": "Helper functions for working with haskell-src-exts trees", + "version": "0.2.5" + }, + { + "name": "haskell-src-meta", + "origin": "hackage", + "synopsis": "Parse source to template-haskell abstract syntax.", + "version": "0.8.7" + }, + { + "name": "haskey-btree", + "origin": "hackage", + "synopsis": "B+-tree implementation in Haskell.", + "version": "0.3.0.1" + }, + { + "name": "hasql", + "origin": "hackage", + "synopsis": "An efficient PostgreSQL driver with a flexible mapping API", + "version": "1.4.5.1" + }, + { + "name": "hasql-notifications", + "origin": "hackage", + "synopsis": "LISTEN/NOTIFY support for Hasql", + "version": "0.1.0.0" + }, + { + "name": "hasql-optparse-applicative", + "origin": "hackage", + "synopsis": "\"optparse-applicative\" parsers for \"hasql\"", + "version": "0.3.0.6" + }, + { + "name": "hasql-pool", + "origin": "hackage", + "synopsis": "A pool of connections for Hasql", + "version": "0.5.2" + }, + { + "name": "hasql-queue", + "origin": "hackage", + "synopsis": "A PostgreSQL backed queue", + "version": "1.2.0.2" + }, + { + "name": "hasql-transaction", + "origin": "hackage", + "synopsis": "Composable abstraction over retryable transactions for Hasql", + "version": "1.0.0.2" + }, + { + "name": "hasty-hamiltonian", + "origin": "hackage", + "synopsis": "Speedy traversal through parameter space.", + "version": "1.3.4" + }, + { + "name": "HaTeX", + "origin": "hackage", + "synopsis": "The Haskell LaTeX library.", + "version": "3.22.3.0" + }, + { + "name": "HaXml", + "origin": "hackage", + "synopsis": "Utilities for manipulating XML documents", + "version": "1.25.5" + }, + { + "name": "haxr", + "origin": "hackage", + "synopsis": "XML-RPC client and server library.", + "version": "3000.11.4.1" + }, + { + "name": "HCodecs", + "origin": "hackage", + "synopsis": "A library to read, write and manipulate MIDI, WAVE, and SoundFont2 files.", + "version": "0.5.2" + }, + { + "name": "hdaemonize", + "origin": "hackage", + "synopsis": "Library to handle the details of writing daemons for UNIX", + "version": "0.5.6" + }, + { + "name": "HDBC", + "origin": "hackage", + "synopsis": "Haskell Database Connectivity", + "version": "2.4.0.3" + }, + { + "name": "HDBC-session", + "origin": "hackage", + "synopsis": "Bracketed connection for HDBC", + "version": "0.1.2.0" + }, + { + "name": "headroom", + "origin": "hackage", + "synopsis": "License Header Manager", + "version": "0.3.2.0" + }, + { + "name": "heap", + "origin": "hackage", + "synopsis": "Heaps in Haskell", + "version": "1.0.4" + }, + { + "name": "heaps", + "origin": "hackage", + "synopsis": "Asymptotically optimal Brodal/Okasaki heaps.", + "version": "0.3.6.1" + }, + { + "name": "hebrew-time", + "origin": "hackage", + "synopsis": "Hebrew dates and prayer times.", + "version": "0.1.2" + }, + { + "name": "hedgehog", + "origin": "hackage", + "synopsis": "Release with confidence.", + "version": "1.0.5" + }, + { + "name": "hedgehog-corpus", + "origin": "hackage", + "synopsis": "hedgehog-corpus", + "version": "0.2.0" + }, + { + "name": "hedgehog-fakedata", + "origin": "hackage", + "synopsis": "Use 'fakedata' with 'hedgehog'", + "version": "0.0.1.4" + }, + { + "name": "hedgehog-fn", + "origin": "hackage", + "synopsis": "Function generation for `hedgehog`", + "version": "1.0" + }, + { + "name": "hedgehog-quickcheck", + "origin": "hackage", + "synopsis": "Use QuickCheck generators in Hedgehog and vice versa.", + "version": "0.1.1" + }, + { + "name": "hedis", + "origin": "hackage", + "synopsis": "Client library for the Redis datastore: supports full command set,\npipelining.", + "version": "0.14.2" + }, + { + "name": "hedn", + "origin": "hackage", + "synopsis": "EDN parsing and encoding", + "version": "0.3.0.2" + }, + { + "name": "here", + "origin": "hackage", + "synopsis": "Here docs & interpolated strings via quasiquotation", + "version": "1.2.13" + }, + { + "name": "heredoc", + "origin": "hackage", + "synopsis": "multi-line string / here document using QuasiQuotes", + "version": "0.2.0.0" + }, + { + "name": "heterocephalus", + "origin": "hackage", + "synopsis": "A type-safe template engine for working with front end development tools", + "version": "1.0.5.4" + }, + { + "name": "hexml", + "origin": "hackage", + "synopsis": "XML subset DOM parser", + "version": "0.3.4" + }, + { + "name": "hexml-lens", + "origin": "hackage", + "synopsis": "Lenses for the hexml package", + "version": "0.2.1" + }, + { + "name": "hexpat", + "origin": "hackage", + "synopsis": "XML parser/formatter based on expat", + "version": "0.20.13" + }, + { + "name": "hexstring", + "origin": "hackage", + "synopsis": "Fast and safe representation of a hex string", + "version": "0.11.1" + }, + { + "name": "hformat", + "origin": "hackage", + "synopsis": "Simple Haskell formatting", + "version": "0.3.3.1" + }, + { + "name": "hfsevents", + "origin": "hackage", + "synopsis": "File/folder watching for OS X", + "version": "0.1.6" + }, + { + "name": "hgeometry", + "origin": "hackage", + "synopsis": "Geometric Algorithms, Data structures, and Data types.", + "version": "0.11.0.0" + }, + { + "name": "hgeometry-combinatorial", + "origin": "hackage", + "synopsis": "Data structures, and Data types.", + "version": "0.11.0.0" + }, + { + "name": "hgrev", + "origin": "hackage", + "synopsis": "Compile Mercurial (hg) version info into Haskell code", + "version": "0.2.6" + }, + { + "name": "hidapi", + "origin": "hackage", + "synopsis": "Haskell bindings to HIDAPI", + "version": "0.1.7" + }, + { + "name": "hie-bios", + "origin": "hackage", + "synopsis": "Set up a GHC API session", + "version": "0.7.5" + }, + { + "name": "hi-file-parser", + "origin": "hackage", + "synopsis": "Parser for GHC's hi files", + "version": "0.1.2.0" + }, + { + "name": "higher-leveldb", + "origin": "hackage", + "synopsis": "A rich monadic API for working with leveldb databases.", + "version": "0.6.0.0" + }, + { + "name": "highlighting-kate", + "origin": "hackage", + "synopsis": "Syntax highlighting", + "version": "0.6.4" + }, + { + "name": "hinfo", + "origin": "hackage", + "synopsis": "Command Line App With Info on your Haskell App", + "version": "0.0.3.0" + }, + { + "name": "hinotify", + "origin": "hackage", + "synopsis": "Haskell binding to inotify", + "version": "0.4.1" + }, + { + "name": "hint", + "origin": "hackage", + "synopsis": "Runtime Haskell interpreter (GHC API wrapper)", + "version": "0.9.0.4" + }, + { + "name": "hjsmin", + "origin": "hackage", + "synopsis": "Haskell implementation of a javascript minifier", + "version": "0.2.0.4" + }, + { + "name": "hkd-default", + "origin": "hackage", + "synopsis": "Apply default value for optional field of HKD", + "version": "1.1.0.0" + }, + { + "name": "hkgr", + "origin": "hackage", + "synopsis": "Simple Hackage release workflow for package maintainers", + "version": "0.2.7" + }, + { + "name": "hledger", + "origin": "hackage", + "synopsis": "Command-line interface for the hledger accounting system", + "version": "1.21" + }, + { + "name": "hledger-iadd", + "origin": "hackage", + "synopsis": "A terminal UI as drop-in replacement for hledger add", + "version": "1.3.14" + }, + { + "name": "hledger-interest", + "origin": "hackage", + "synopsis": "computes interest for a given account", + "version": "1.6.1" + }, + { + "name": "hledger-lib", + "origin": "hackage", + "synopsis": "A reusable library providing the core functionality of hledger", + "version": "1.21" + }, + { + "name": "hledger-stockquotes", + "origin": "hackage", + "synopsis": "Generate HLedger Price Directives From Daily Stock Quotes.", + "version": "0.1.1.0" + }, + { + "name": "hledger-ui", + "origin": "hackage", + "synopsis": "Curses-style terminal interface for the hledger accounting system", + "version": "1.21" + }, + { + "name": "hledger-web", + "origin": "hackage", + "synopsis": "Web-based user interface for the hledger accounting system", + "version": "1.21" + }, + { + "name": "hlibcpuid", + "origin": "hackage", + "synopsis": "Bindings to https://github.com/anrieff/libcpuid", + "version": "0.2.0" + }, + { + "name": "hlibgit2", + "origin": "hackage", + "synopsis": "Low-level bindings to libgit2", + "version": "0.18.0.16" + }, + { + "name": "hlibsass", + "origin": "hackage", + "synopsis": "Low-level bindings to Libsass", + "version": "0.1.10.1" + }, + { + "name": "hlint", + "origin": "hackage", + "synopsis": "Source code suggestions", + "version": "3.2.7" + }, + { + "name": "hmatrix", + "origin": "hackage", + "synopsis": "Numeric Linear Algebra", + "version": "0.20.2" + }, + { + "name": "hmatrix-backprop", + "origin": "hackage", + "synopsis": "hmatrix operations lifted for backprop", + "version": "0.1.3.0" + }, + { + "name": "hmatrix-gsl", + "origin": "hackage", + "synopsis": "Numerical computation", + "version": "0.19.0.1" + }, + { + "name": "hmatrix-gsl-stats", + "origin": "hackage", + "synopsis": "GSL Statistics interface", + "version": "0.4.1.8" + }, + { + "name": "hmatrix-morpheus", + "origin": "hackage", + "synopsis": "Low-level machine learning auxiliary functions.", + "version": "0.1.1.2" + }, + { + "name": "hmatrix-vector-sized", + "origin": "hackage", + "synopsis": "Conversions between hmatrix and vector-sized types", + "version": "0.1.3.0" + }, + { + "name": "hmm-lapack", + "origin": "hackage", + "synopsis": "Hidden Markov Models using LAPACK primitives", + "version": "0.4" + }, + { + "name": "hmpfr", + "origin": "hackage", + "synopsis": "Haskell binding to the MPFR library", + "version": "0.4.4" + }, + { + "name": "hnix-store-core", + "origin": "hackage", + "synopsis": "Core effects for interacting with the Nix store.", + "version": "0.2.0.0" + }, + { + "name": "hnock", + "origin": "hackage", + "synopsis": "A Nock interpreter.", + "version": "0.4.0" + }, + { + "name": "hoauth2", + "origin": "hackage", + "synopsis": "Haskell OAuth2 authentication client", + "version": "1.16.0" + }, + { + "name": "hocon", + "origin": "hackage", + "synopsis": "Small library for typesafe's configuration specification", + "version": "0.1.0.4" + }, + { + "name": "hoogle", + "origin": "hackage", + "synopsis": "Haskell API Search", + "version": "5.0.18.1" + }, + { + "name": "hOpenPGP", + "origin": "hackage", + "synopsis": "native Haskell implementation of OpenPGP (RFC4880)", + "version": "2.9.5" + }, + { + "name": "hopenpgp-tools", + "origin": "hackage", + "synopsis": "hOpenPGP-based command-line tools", + "version": "0.23.6" + }, + { + "name": "hopenssl", + "origin": "hackage", + "synopsis": "FFI Bindings to OpenSSL's EVP Digest Interface", + "version": "2.2.4" + }, + { + "name": "hopfli", + "origin": "hackage", + "synopsis": "Bidings to Google's Zopfli compression library", + "version": "0.2.2.1" + }, + { + "name": "hosc", + "origin": "hackage", + "synopsis": "Haskell Open Sound Control", + "version": "0.18.1" + }, + { + "name": "hostname", + "origin": "hackage", + "synopsis": "A very simple package providing a cross-platform means of determining the hostname", + "version": "1.0" + }, + { + "name": "hostname-validate", + "origin": "hackage", + "synopsis": "Validate hostnames e.g. localhost or foo.co.uk.", + "version": "1.0.0" + }, + { + "name": "hourglass", + "origin": "hackage", + "synopsis": "simple performant time related library", + "version": "0.2.12" + }, + { + "name": "hourglass-orphans", + "origin": "hackage", + "synopsis": "Orphan Aeson instances to hourglass", + "version": "0.1.0.0" + }, + { + "name": "hp2pretty", + "origin": "hackage", + "synopsis": "generate pretty graphs from heap profiles", + "version": "0.9" + }, + { + "name": "hpack", + "origin": "hackage", + "synopsis": "A modern format for Haskell packages", + "version": "0.34.4" + }, + { + "name": "hpack-dhall", + "origin": "hackage", + "synopsis": "hpack's dhalling", + "version": "0.5.2" + }, + { + "name": "hpc", + "origin": "core", + "synopsis": "Code Coverage Library for Haskell", + "version": "0.6.1.0" + }, + { + "name": "hpc-codecov", + "origin": "hackage", + "synopsis": "Generate codecov report from hpc data", + "version": "0.2.0.2" + }, + { + "name": "hpc-lcov", + "origin": "hackage", + "synopsis": "Convert HPC output into LCOV format", + "version": "1.0.1" + }, + { + "name": "hprotoc", + "origin": "hackage", + "synopsis": "Parse Google Protocol Buffer specifications", + "version": "2.4.17" + }, + { + "name": "hruby", + "origin": "hackage", + "synopsis": "Embed a Ruby intepreter in your Haskell program !", + "version": "0.3.8.1" + }, + { + "name": "hsass", + "origin": "hackage", + "synopsis": "Integrating Sass into Haskell applications.", + "version": "0.8.0" + }, + { + "name": "hs-bibutils", + "origin": "hackage", + "synopsis": "Haskell bindings to bibutils, the bibliography\nconversion utilities.", + "version": "6.10.0.0" + }, + { + "name": "hsc2hs", + "origin": "hackage", + "synopsis": "A preprocessor that helps with writing Haskell bindings to C code", + "version": "0.68.7" + }, + { + "name": "hscolour", + "origin": "hackage", + "synopsis": "Colourise Haskell code.", + "version": "1.24.4" + }, + { + "name": "hsdns", + "origin": "hackage", + "synopsis": "Asynchronous DNS Resolver", + "version": "1.8" + }, + { + "name": "hsebaysdk", + "origin": "hackage", + "synopsis": "Haskell eBay SDK", + "version": "0.4.1.0" + }, + { + "name": "hsemail", + "origin": "hackage", + "synopsis": "Parsec parsers for the Internet Message format (e-mail)", + "version": "2.2.1" + }, + { + "name": "hs-functors", + "origin": "hackage", + "synopsis": "Functors from products of Haskell and its dual to Haskell", + "version": "0.1.7.1" + }, + { + "name": "hs-GeoIP", + "origin": "hackage", + "synopsis": "Haskell bindings to the MaxMind GeoIPCity database via the C library", + "version": "0.3" + }, + { + "name": "hsini", + "origin": "hackage", + "synopsis": "ini configuration files", + "version": "0.5.1.2" + }, + { + "name": "hsinstall", + "origin": "hackage", + "synopsis": "Install Haskell software", + "version": "2.6" + }, + { + "name": "HSlippyMap", + "origin": "hackage", + "synopsis": "OpenStreetMap Slippy Map", + "version": "3.0.1" + }, + { + "name": "hslogger", + "origin": "hackage", + "synopsis": "Versatile logging framework", + "version": "1.3.1.0" + }, + { + "name": "hslua", + "origin": "hackage", + "synopsis": "Bindings to Lua, an embeddable scripting language", + "version": "1.2.0" + }, + { + "name": "hslua-aeson", + "origin": "hackage", + "synopsis": "Allow aeson data types to be used with lua.", + "version": "1.0.3.1" + }, + { + "name": "hslua-module-doclayout", + "origin": "hackage", + "synopsis": "Lua module wrapping Text.DocLayout.", + "version": "0.2.0.1" + }, + { + "name": "hslua-module-system", + "origin": "hackage", + "synopsis": "Lua module wrapper around Haskell's System module.", + "version": "0.2.2.1" + }, + { + "name": "hslua-module-text", + "origin": "hackage", + "synopsis": "Lua module for text", + "version": "0.3.0.1" + }, + { + "name": "HsOpenSSL", + "origin": "hackage", + "synopsis": "Partial OpenSSL binding for Haskell", + "version": "0.11.7" + }, + { + "name": "HsOpenSSL-x509-system", + "origin": "hackage", + "synopsis": "Use the system's native CA certificate store with HsOpenSSL", + "version": "0.1.0.4" + }, + { + "name": "hsp", + "origin": "hackage", + "synopsis": "Haskell Server Pages is a library for writing dynamic server-side web pages.", + "version": "0.10.0" + }, + { + "name": "hspec", + "origin": "hackage", + "synopsis": "A Testing Framework for Haskell", + "version": "2.7.8" + }, + { + "name": "hspec-attoparsec", + "origin": "hackage", + "synopsis": "Utility functions for testing your attoparsec parsers with hspec", + "version": "0.1.0.2" + }, + { + "name": "hspec-checkers", + "origin": "hackage", + "synopsis": "Allows to use checkers properties from hspec", + "version": "0.1.0.2" + }, + { + "name": "hspec-contrib", + "origin": "hackage", + "synopsis": "Contributed functionality for Hspec", + "version": "0.5.1" + }, + { + "name": "hspec-core", + "origin": "hackage", + "synopsis": "A Testing Framework for Haskell", + "version": "2.7.8" + }, + { + "name": "hspec-discover", + "origin": "hackage", + "synopsis": "Automatically discover and run Hspec tests", + "version": "2.7.8" + }, + { + "name": "hspec-expectations", + "origin": "hackage", + "synopsis": "Catchy combinators for HUnit", + "version": "0.8.2" + }, + { + "name": "hspec-expectations-lifted", + "origin": "hackage", + "synopsis": "A version of hspec-expectations generalized to MonadIO", + "version": "0.10.0" + }, + { + "name": "hspec-expectations-pretty-diff", + "origin": "hackage", + "synopsis": "Catchy combinators for HUnit", + "version": "0.7.2.5" + }, + { + "name": "hspec-golden", + "origin": "hackage", + "synopsis": "Golden tests for hspec", + "version": "0.1.0.3" + }, + { + "name": "hspec-golden-aeson", + "origin": "hackage", + "synopsis": "Use tests to monitor changes in Aeson serialization", + "version": "0.7.0.0" + }, + { + "name": "hspec-hedgehog", + "origin": "hackage", + "synopsis": "Integrate Hedgehog and Hspec!", + "version": "0.0.1.2" + }, + { + "name": "hspec-leancheck", + "origin": "hackage", + "synopsis": "LeanCheck support for the Hspec test framework.", + "version": "0.0.4" + }, + { + "name": "hspec-megaparsec", + "origin": "hackage", + "synopsis": "Utility functions for testing Megaparsec parsers with Hspec", + "version": "2.2.0" + }, + { + "name": "hspec-meta", + "origin": "hackage", + "synopsis": "A version of Hspec which is used to test Hspec itself", + "version": "2.6.0" + }, + { + "name": "hspec-need-env", + "origin": "hackage", + "synopsis": "Read environment variables for hspec tests", + "version": "0.1.0.6" + }, + { + "name": "hspec-parsec", + "origin": "hackage", + "synopsis": "Hspec expectations for testing Parsec parsers", + "version": "0" + }, + { + "name": "hspec-smallcheck", + "origin": "hackage", + "synopsis": "SmallCheck support for the Hspec testing framework", + "version": "0.5.2" + }, + { + "name": "hspec-tables", + "origin": "hackage", + "synopsis": "Table-driven (by-example) HSpec tests", + "version": "0.0.1" + }, + { + "name": "hspec-wai", + "origin": "hackage", + "synopsis": "Experimental Hspec support for testing WAI applications", + "version": "0.10.1" + }, + { + "name": "hspec-wai-json", + "origin": "hackage", + "synopsis": "Testing JSON APIs with hspec-wai", + "version": "0.10.1" + }, + { + "name": "hs-php-session", + "origin": "hackage", + "synopsis": "PHP session and values serialization", + "version": "0.0.9.3" + }, + { + "name": "hsshellscript", + "origin": "hackage", + "synopsis": "Haskell for Unix shell scripting tasks", + "version": "3.4.5" + }, + { + "name": "HStringTemplate", + "origin": "hackage", + "synopsis": "StringTemplate implementation in Haskell.", + "version": "0.8.7" + }, + { + "name": "HSvm", + "origin": "hackage", + "synopsis": "Haskell Bindings for libsvm", + "version": "0.1.1.3.22" + }, + { + "name": "HsYAML", + "origin": "hackage", + "synopsis": "Pure Haskell YAML 1.2 processor", + "version": "0.2.1.0" + }, + { + "name": "HsYAML-aeson", + "origin": "hackage", + "synopsis": "JSON to YAML Adapter", + "version": "0.2.0.0" + }, + { + "name": "hsyslog", + "origin": "hackage", + "synopsis": "FFI interface to syslog(3) from POSIX.1-2001", + "version": "5.0.2" + }, + { + "name": "htaglib", + "origin": "hackage", + "synopsis": "Bindings to TagLib, audio meta-data library", + "version": "1.2.0" + }, + { + "name": "HTF", + "origin": "hackage", + "synopsis": "The Haskell Test Framework", + "version": "0.14.0.6" + }, + { + "name": "html", + "origin": "hackage", + "synopsis": "HTML combinator library", + "version": "1.0.1.2" + }, + { + "name": "html-conduit", + "origin": "hackage", + "synopsis": "Parse HTML documents using xml-conduit datatypes.", + "version": "1.3.2.1" + }, + { + "name": "html-entities", + "origin": "hackage", + "synopsis": "A codec library for HTML-escaped text and HTML-entities", + "version": "1.1.4.5" + }, + { + "name": "html-entity-map", + "origin": "hackage", + "synopsis": "Map from HTML5 entity names to the corresponding Unicode text", + "version": "0.1.0.0" + }, + { + "name": "htoml", + "origin": "hackage", + "synopsis": "Parser for TOML files", + "version": "1.0.0.3" + }, + { + "name": "HTTP", + "origin": "hackage", + "synopsis": "A library for client-side HTTP", + "version": "4000.3.16" + }, + { + "name": "http2", + "origin": "hackage", + "synopsis": "HTTP/2 library", + "version": "2.0.5" + }, + { + "name": "http-api-data", + "origin": "hackage", + "synopsis": "Converting to/from HTTP API data like URL pieces, headers and query parameters.", + "version": "0.4.1.1" + }, + { + "name": "http-client", + "origin": "hackage", + "synopsis": "An HTTP client engine", + "version": "0.6.4.1" + }, + { + "name": "http-client-openssl", + "origin": "hackage", + "synopsis": "http-client backend using the OpenSSL library.", + "version": "0.3.2.0" + }, + { + "name": "http-client-overrides", + "origin": "hackage", + "synopsis": "HTTP client overrides", + "version": "0.1.1.0" + }, + { + "name": "http-client-tls", + "origin": "hackage", + "synopsis": "http-client backend using the connection package and tls library", + "version": "0.3.5.3" + }, + { + "name": "http-common", + "origin": "hackage", + "synopsis": "Common types for HTTP clients and servers", + "version": "0.8.2.1" + }, + { + "name": "http-conduit", + "origin": "hackage", + "synopsis": "HTTP client package with conduit interface and HTTPS support.", + "version": "2.3.8" + }, + { + "name": "http-date", + "origin": "hackage", + "synopsis": "HTTP Date parser/formatter", + "version": "0.0.11" + }, + { + "name": "http-directory", + "origin": "hackage", + "synopsis": "http directory listing library", + "version": "0.1.8" + }, + { + "name": "http-download", + "origin": "hackage", + "synopsis": "Verified downloads with retries", + "version": "0.2.0.0" + }, + { + "name": "httpd-shed", + "origin": "hackage", + "synopsis": "A simple web-server with an interact style API", + "version": "0.4.1.1" + }, + { + "name": "http-link-header", + "origin": "hackage", + "synopsis": "A parser and writer for the HTTP Link header as specified in RFC 5988 \"Web Linking\".", + "version": "1.0.3.1" + }, + { + "name": "http-media", + "origin": "hackage", + "synopsis": "Processing HTTP Content-Type and Accept headers", + "version": "0.8.0.0" + }, + { + "name": "http-query", + "origin": "hackage", + "synopsis": "Simple http queries", + "version": "0.1.0.1" + }, + { + "name": "http-reverse-proxy", + "origin": "hackage", + "synopsis": "Reverse proxy HTTP requests, either over raw sockets or with WAI", + "version": "0.6.0" + }, + { + "name": "http-streams", + "origin": "hackage", + "synopsis": "An HTTP client using io-streams", + "version": "0.8.7.2" + }, + { + "name": "http-types", + "origin": "hackage", + "synopsis": "Generic HTTP types for Haskell (for both client and server code).", + "version": "0.12.3" + }, + { + "name": "human-readable-duration", + "origin": "hackage", + "synopsis": "Provide duration helper", + "version": "0.2.1.4" + }, + { + "name": "HUnit", + "origin": "hackage", + "synopsis": "A unit testing framework for Haskell", + "version": "1.6.1.0" + }, + { + "name": "HUnit-approx", + "origin": "hackage", + "synopsis": "Approximate equality for floating point numbers with HUnit", + "version": "1.1.1.1" + }, + { + "name": "hunit-dejafu", + "origin": "hackage", + "synopsis": "Deja Fu support for the HUnit test framework.", + "version": "2.0.0.4" + }, + { + "name": "hvect", + "origin": "hackage", + "synopsis": "Simple strict heterogeneous lists", + "version": "0.4.0.0" + }, + { + "name": "hvega", + "origin": "hackage", + "synopsis": "Create Vega-Lite visualizations (version 4) in Haskell.", + "version": "0.11.0.0" + }, + { + "name": "hw-balancedparens", + "origin": "hackage", + "synopsis": "Balanced parentheses", + "version": "0.4.1.1" + }, + { + "name": "hw-bits", + "origin": "hackage", + "synopsis": "Bit manipulation", + "version": "0.7.2.1" + }, + { + "name": "hw-conduit", + "origin": "hackage", + "synopsis": "Conduits for tokenizing streams.", + "version": "0.2.1.0" + }, + { + "name": "hw-conduit-merges", + "origin": "hackage", + "synopsis": "Additional merges and joins for Conduit", + "version": "0.2.1.0" + }, + { + "name": "hw-diagnostics", + "origin": "hackage", + "synopsis": "Diagnostics library", + "version": "0.0.1.0" + }, + { + "name": "hw-dsv", + "origin": "hackage", + "synopsis": "Unbelievably fast streaming DSV file parser", + "version": "0.4.1.0" + }, + { + "name": "hweblib", + "origin": "hackage", + "synopsis": "Haskell Web Library", + "version": "0.6.3" + }, + { + "name": "hw-eliasfano", + "origin": "hackage", + "synopsis": "Elias-Fano", + "version": "0.1.2.0" + }, + { + "name": "hw-excess", + "origin": "hackage", + "synopsis": "Excess", + "version": "0.2.3.0" + }, + { + "name": "hw-fingertree", + "origin": "hackage", + "synopsis": "Generic finger-tree structure, with example instances", + "version": "0.1.2.0" + }, + { + "name": "hw-fingertree-strict", + "origin": "hackage", + "synopsis": "Generic strict finger-tree structure", + "version": "0.1.2.0" + }, + { + "name": "hw-hedgehog", + "origin": "hackage", + "synopsis": "Extra hedgehog functionality", + "version": "0.1.1.0" + }, + { + "name": "hw-hspec-hedgehog", + "origin": "hackage", + "synopsis": "Interoperability between hspec and hedgehog", + "version": "0.1.1.0" + }, + { + "name": "hw-int", + "origin": "hackage", + "synopsis": "Additional facilities for Integers", + "version": "0.0.2.0" + }, + { + "name": "hw-ip", + "origin": "hackage", + "synopsis": "Library for manipulating IP addresses and CIDR blocks", + "version": "2.4.2.0" + }, + { + "name": "hw-json", + "origin": "hackage", + "synopsis": "Memory efficient JSON parser", + "version": "1.3.2.2" + }, + { + "name": "hw-json-simd", + "origin": "hackage", + "synopsis": "SIMD-based JSON semi-indexer", + "version": "0.1.1.0" + }, + { + "name": "hw-json-simple-cursor", + "origin": "hackage", + "synopsis": "Memory efficient JSON parser", + "version": "0.1.1.0" + }, + { + "name": "hw-json-standard-cursor", + "origin": "hackage", + "synopsis": "Memory efficient JSON parser", + "version": "0.2.3.1" + }, + { + "name": "hw-kafka-client", + "origin": "hackage", + "synopsis": "Kafka bindings for Haskell", + "version": "4.0.3" + }, + { + "name": "hw-mquery", + "origin": "hackage", + "synopsis": "Monadic query DSL", + "version": "0.2.1.0" + }, + { + "name": "hw-packed-vector", + "origin": "hackage", + "synopsis": "Packed Vector", + "version": "0.2.1.0" + }, + { + "name": "hw-parser", + "origin": "hackage", + "synopsis": "Simple parser support", + "version": "0.1.1.0" + }, + { + "name": "hw-prim", + "origin": "hackage", + "synopsis": "Primitive functions and data types", + "version": "0.6.3.0" + }, + { + "name": "hw-rankselect", + "origin": "hackage", + "synopsis": "Rank-select", + "version": "0.13.4.0" + }, + { + "name": "hw-rankselect-base", + "origin": "hackage", + "synopsis": "Rank-select base", + "version": "0.3.4.1" + }, + { + "name": "hw-simd", + "origin": "hackage", + "synopsis": "SIMD library", + "version": "0.1.2.0" + }, + { + "name": "hw-streams", + "origin": "hackage", + "synopsis": "Primitive functions and data types", + "version": "0.0.1.0" + }, + { + "name": "hw-string-parse", + "origin": "hackage", + "synopsis": "String parser", + "version": "0.0.0.4" + }, + { + "name": "hw-succinct", + "origin": "hackage", + "synopsis": "Succint datastructures", + "version": "0.1.0.1" + }, + { + "name": "hw-xml", + "origin": "hackage", + "synopsis": "XML parser based on succinct data structures.", + "version": "0.5.1.0" + }, + { + "name": "hxt", + "origin": "hackage", + "synopsis": "A collection of tools for processing XML with Haskell.", + "version": "9.3.1.22" + }, + { + "name": "hxt-charproperties", + "origin": "hackage", + "synopsis": "Character properties and classes for XML and Unicode", + "version": "9.4.0.0" + }, + { + "name": "hxt-css", + "origin": "hackage", + "synopsis": "CSS selectors for HXT", + "version": "0.1.0.3" + }, + { + "name": "hxt-curl", + "origin": "hackage", + "synopsis": "LibCurl interface for HXT", + "version": "9.1.1.1" + }, + { + "name": "hxt-expat", + "origin": "hackage", + "synopsis": "Expat parser for HXT", + "version": "9.1.1" + }, + { + "name": "hxt-http", + "origin": "hackage", + "synopsis": "Interface to native Haskell HTTP package HTTP", + "version": "9.1.5.2" + }, + { + "name": "hxt-regex-xmlschema", + "origin": "hackage", + "synopsis": "A regular expression library for W3C XML Schema regular expressions", + "version": "9.2.0.7" + }, + { + "name": "hxt-tagsoup", + "origin": "hackage", + "synopsis": "TagSoup parser for HXT", + "version": "9.1.4" + }, + { + "name": "hxt-unicode", + "origin": "hackage", + "synopsis": "Unicode en-/decoding functions for utf8, iso-latin-* and other encodings", + "version": "9.0.2.4" + }, + { + "name": "hybrid-vectors", + "origin": "hackage", + "synopsis": "Hybrid vectors e.g. Mixed Boxed/Unboxed vectors", + "version": "0.2.2" + }, + { + "name": "hyper", + "origin": "hackage", + "synopsis": "Display class for the HyperHaskell graphical Haskell interpreter", + "version": "0.2.1.0" + }, + { + "name": "hyperloglog", + "origin": "hackage", + "synopsis": "An approximate streaming (constant space) unique object counter", + "version": "0.4.4" + }, + { + "name": "hyphenation", + "origin": "hackage", + "synopsis": "Configurable Knuth-Liang hyphenation", + "version": "0.8.1" + }, + { + "name": "iconv", + "origin": "hackage", + "synopsis": "String encoding conversion", + "version": "0.4.1.3" + }, + { + "name": "identicon", + "origin": "hackage", + "synopsis": "Flexible generation of identicons", + "version": "0.2.2" + }, + { + "name": "ieee754", + "origin": "hackage", + "synopsis": "Utilities for dealing with IEEE floating point numbers", + "version": "0.8.0" + }, + { + "name": "if", + "origin": "hackage", + "synopsis": "(?) and (?>) conditional operator", + "version": "0.1.0.0" + }, + { + "name": "iff", + "origin": "hackage", + "synopsis": "Constructing and dissecting IFF files", + "version": "0.0.6" + }, + { + "name": "ihaskell", + "origin": "hackage", + "synopsis": "A Haskell backend kernel for the IPython project.", + "version": "0.10.1.2" + }, + { + "name": "ihs", + "origin": "hackage", + "synopsis": "Interpolated Haskell", + "version": "0.1.0.3" + }, + { + "name": "ilist", + "origin": "hackage", + "synopsis": "Optimised list functions for doing index-related things", + "version": "0.4.0.1" + }, + { + "name": "imagesize-conduit", + "origin": "hackage", + "synopsis": "Determine the size of some common image formats.", + "version": "1.1" + }, + { + "name": "Imlib", + "origin": "hackage", + "synopsis": "", + "version": "0.1.2" + }, + { + "name": "immortal", + "origin": "hackage", + "synopsis": "Spawn threads that never die (unless told to do so)", + "version": "0.3" + }, + { + "name": "immortal-queue", + "origin": "hackage", + "synopsis": "Build a pool of queue-processing worker threads.", + "version": "0.1.0.1" + }, + { + "name": "inbox", + "origin": "hackage", + "synopsis": "Inbox for asychronous messages", + "version": "0.1.0" + }, + { + "name": "include-file", + "origin": "hackage", + "synopsis": "Inclusion of files in executables at compile-time.", + "version": "0.1.0.4" + }, + { + "name": "incremental-parser", + "origin": "hackage", + "synopsis": "Generic parser library capable of providing partial results from partial input.", + "version": "0.5.0.2" + }, + { + "name": "indents", + "origin": "hackage", + "synopsis": "indentation sensitive parser-combinators for parsec", + "version": "0.5.0.1" + }, + { + "name": "indexed", + "origin": "hackage", + "synopsis": "Haskell98 indexed functors, monads, comonads", + "version": "0.1.3" + }, + { + "name": "indexed-containers", + "origin": "hackage", + "synopsis": "Simple, no-frills indexed lists.", + "version": "0.1.0.2" + }, + { + "name": "indexed-list-literals", + "origin": "hackage", + "synopsis": "Type safe indexed list literals", + "version": "0.2.1.3" + }, + { + "name": "indexed-profunctors", + "origin": "hackage", + "synopsis": "Utilities for indexed profunctors", + "version": "0.1.1" + }, + { + "name": "indexed-traversable", + "origin": "hackage", + "synopsis": "FunctorWithIndex, FoldableWithIndex, TraversableWithIndex", + "version": "0.1.1" + }, + { + "name": "infer-license", + "origin": "hackage", + "synopsis": "Infer software license from a given license file", + "version": "0.2.0" + }, + { + "name": "inflections", + "origin": "hackage", + "synopsis": "Inflections library for Haskell", + "version": "0.4.0.6" + }, + { + "name": "influxdb", + "origin": "hackage", + "synopsis": "InfluxDB client library for Haskell", + "version": "1.9.1.2" + }, + { + "name": "ini", + "origin": "hackage", + "synopsis": "Quick and easy configuration files in the INI format.", + "version": "0.4.1" + }, + { + "name": "inj", + "origin": "hackage", + "synopsis": "A class for injective (one-to-one) functions", + "version": "1.0" + }, + { + "name": "inline-c", + "origin": "hackage", + "synopsis": "Write Haskell source files including C code inline. No FFI required.", + "version": "0.9.1.4" + }, + { + "name": "inline-c-cpp", + "origin": "hackage", + "synopsis": "Lets you embed C++ code into Haskell.", + "version": "0.4.0.3" + }, + { + "name": "inline-r", + "origin": "hackage", + "synopsis": "Seamlessly call R from Haskell and vice versa. No FFI required.", + "version": "0.10.4" + }, + { + "name": "inliterate", + "origin": "hackage", + "synopsis": "Interactive literate programming", + "version": "0.1.0" + }, + { + "name": "input-parsers", + "origin": "hackage", + "synopsis": "Extension of the parsers library with more capability and efficiency", + "version": "0.1.0.1" + }, + { + "name": "insert-ordered-containers", + "origin": "hackage", + "synopsis": "Associative containers retaining insertion order for traversals.", + "version": "0.2.4" + }, + { + "name": "inspection-testing", + "origin": "hackage", + "synopsis": "GHC plugin to do inspection testing", + "version": "0.4.4.0" + }, + { + "name": "instance-control", + "origin": "hackage", + "synopsis": "Controls how the compiler searches for instances using type families.", + "version": "0.1.2.0" + }, + { + "name": "integer-gmp", + "origin": "core", + "synopsis": "Integer library based on GMP", + "version": "1.0.3.0" + }, + { + "name": "integer-logarithms", + "origin": "hackage", + "synopsis": "Integer logarithms.", + "version": "1.0.3.1" + }, + { + "name": "integer-roots", + "origin": "hackage", + "synopsis": "Integer roots and perfect powers", + "version": "1.0" + }, + { + "name": "integration", + "origin": "hackage", + "synopsis": "Fast robust numeric integration via tanh-sinh quadrature", + "version": "0.2.1" + }, + { + "name": "intern", + "origin": "hackage", + "synopsis": "Efficient hash-consing for arbitrary data types", + "version": "0.9.4" + }, + { + "name": "interpolate", + "origin": "hackage", + "synopsis": "String interpolation done right", + "version": "0.2.1" + }, + { + "name": "interpolatedstring-perl6", + "origin": "hackage", + "synopsis": "QuasiQuoter for Perl6-style multi-line interpolated strings", + "version": "1.0.2" + }, + { + "name": "interpolation", + "origin": "hackage", + "synopsis": "piecewise linear and cubic Hermite interpolation", + "version": "0.1.1.1" + }, + { + "name": "interpolator", + "origin": "hackage", + "synopsis": "Runtime interpolation of environment variables in records using profunctors", + "version": "1.1.0.2" + }, + { + "name": "IntervalMap", + "origin": "hackage", + "synopsis": "Containers for intervals, with efficient search.", + "version": "0.6.1.2" + }, + { + "name": "intervals", + "origin": "hackage", + "synopsis": "Interval Arithmetic", + "version": "0.9.2" + }, + { + "name": "intro", + "origin": "hackage", + "synopsis": "Safe and minimal prelude", + "version": "0.9.0.0" + }, + { + "name": "intset-imperative", + "origin": "hackage", + "synopsis": "An imperative integer set written in Haskell.", + "version": "0.1.0.0" + }, + { + "name": "invariant", + "origin": "hackage", + "synopsis": "Haskell98 invariant functors", + "version": "0.5.4" + }, + { + "name": "invertible", + "origin": "hackage", + "synopsis": "bidirectional arrows, bijective functions, and invariant functors", + "version": "0.2.0.7" + }, + { + "name": "invertible-grammar", + "origin": "hackage", + "synopsis": "Invertible parsing combinators framework", + "version": "0.1.3" + }, + { + "name": "io-machine", + "origin": "hackage", + "synopsis": "Easy I/O model to learn IO monad", + "version": "0.2.0.0" + }, + { + "name": "io-manager", + "origin": "hackage", + "synopsis": "Skeleton library around the IO monad.", + "version": "0.1.0.3" + }, + { + "name": "io-memoize", + "origin": "hackage", + "synopsis": "Memoize IO actions", + "version": "1.1.1.0" + }, + { + "name": "io-region", + "origin": "hackage", + "synopsis": "Exception safe resource management with dynamic regions", + "version": "0.1.1" + }, + { + "name": "io-storage", + "origin": "hackage", + "synopsis": "A key-value store in the IO monad.", + "version": "0.3" + }, + { + "name": "io-streams", + "origin": "hackage", + "synopsis": "Simple, composable, and easy-to-use stream I/O", + "version": "1.5.2.0" + }, + { + "name": "io-streams-haproxy", + "origin": "hackage", + "synopsis": "HAProxy protocol 1.5 support for io-streams", + "version": "1.0.1.0" + }, + { + "name": "ip6addr", + "origin": "hackage", + "synopsis": "Commandline tool to deal with IPv6 address text representations", + "version": "1.0.1" + }, + { + "name": "iproute", + "origin": "hackage", + "synopsis": "IP Routing Table", + "version": "1.7.11" + }, + { + "name": "IPv6Addr", + "origin": "hackage", + "synopsis": "Library to deal with IPv6 address text representations.", + "version": "1.1.5" + }, + { + "name": "ipynb", + "origin": "hackage", + "synopsis": "Data structure for working with Jupyter notebooks (ipynb).", + "version": "0.1.0.1" + }, + { + "name": "ipython-kernel", + "origin": "hackage", + "synopsis": "A library for creating kernels for IPython frontends", + "version": "0.10.2.1" + }, + { + "name": "irc", + "origin": "hackage", + "synopsis": "A small library for parsing IRC messages.", + "version": "0.6.1.0" + }, + { + "name": "irc-client", + "origin": "hackage", + "synopsis": "An IRC client library.", + "version": "1.1.2.0" + }, + { + "name": "irc-conduit", + "origin": "hackage", + "synopsis": "Streaming IRC message library using conduits.", + "version": "0.3.0.4" + }, + { + "name": "irc-ctcp", + "origin": "hackage", + "synopsis": "A CTCP encoding and decoding library for IRC clients.", + "version": "0.1.3.0" + }, + { + "name": "isbn", + "origin": "hackage", + "synopsis": "ISBN Validation and Manipulation", + "version": "1.1.0.2" + }, + { + "name": "islink", + "origin": "hackage", + "synopsis": "Check if an HTML element is a link", + "version": "0.1.0.0" + }, + { + "name": "iso3166-country-codes", + "origin": "hackage", + "synopsis": "A datatype for ISO 3166 country codes", + "version": "0.20140203.8" + }, + { + "name": "iso639", + "origin": "hackage", + "synopsis": "ISO-639-1 language codes ", + "version": "0.1.0.3" + }, + { + "name": "iso8601-time", + "origin": "hackage", + "synopsis": "Convert to/from the ISO 8601 time format", + "version": "0.1.5" + }, + { + "name": "iterable", + "origin": "hackage", + "synopsis": "API for hierarchical multilevel collections.", + "version": "3.0" + }, + { + "name": "it-has", + "origin": "hackage", + "synopsis": "Automatically derivable Has instances.", + "version": "0.2.0.0" + }, + { + "name": "ixset-typed", + "origin": "hackage", + "synopsis": "Efficient relational queries on Haskell sets.", + "version": "0.5" + }, + { + "name": "ixset-typed-binary-instance", + "origin": "hackage", + "synopsis": "Binary instance for ixset-typed.", + "version": "0.1.0.2" + }, + { + "name": "ixset-typed-conversions", + "origin": "hackage", + "synopsis": "Conversions from ixset-typed to other containers.", + "version": "0.1.2.0" + }, + { + "name": "ixset-typed-hashable-instance", + "origin": "hackage", + "synopsis": "Hashable instance for ixset-typed.", + "version": "0.1.0.2" + }, + { + "name": "ix-shapable", + "origin": "hackage", + "synopsis": "Reshape multi-dimensional arrays.", + "version": "0.1.0" + }, + { + "name": "jack", + "origin": "hackage", + "synopsis": "Bindings for the JACK Audio Connection Kit", + "version": "0.7.2" + }, + { + "name": "jailbreak-cabal", + "origin": "hackage", + "synopsis": "Strip version restrictions from Cabal files", + "version": "1.3.5" + }, + { + "name": "jalaali", + "origin": "hackage", + "synopsis": "Jalaali calendar systems", + "version": "1.0.0.0" + }, + { + "name": "jira-wiki-markup", + "origin": "hackage", + "synopsis": "Handle Jira wiki markup", + "version": "1.3.4" + }, + { + "name": "jose", + "origin": "hackage", + "synopsis": "Javascript Object Signing and Encryption and JSON Web Token library", + "version": "0.8.4" + }, + { + "name": "jose-jwt", + "origin": "hackage", + "synopsis": "JSON Object Signing and Encryption Library", + "version": "0.8.0" + }, + { + "name": "js-chart", + "origin": "hackage", + "synopsis": "Obtain minified chart.js code", + "version": "2.9.4.1" + }, + { + "name": "js-dgtable", + "origin": "hackage", + "synopsis": "Obtain minified jquery.dgtable code", + "version": "0.5.2" + }, + { + "name": "js-flot", + "origin": "hackage", + "synopsis": "Obtain minified flot code", + "version": "0.8.3" + }, + { + "name": "js-jquery", + "origin": "hackage", + "synopsis": "Obtain minified jQuery code", + "version": "3.3.1" + }, + { + "name": "json-feed", + "origin": "hackage", + "synopsis": "JSON Feed", + "version": "1.0.12" + }, + { + "name": "jsonpath", + "origin": "hackage", + "synopsis": "Library to parse and execute JSONPath", + "version": "0.2.0.0" + }, + { + "name": "json-rpc", + "origin": "hackage", + "synopsis": "Fully-featured JSON-RPC 2.0 library", + "version": "1.0.3" + }, + { + "name": "json-rpc-generic", + "origin": "hackage", + "synopsis": "Generic encoder and decode for JSON-RPC", + "version": "0.2.1.5" + }, + { + "name": "JuicyPixels", + "origin": "hackage", + "synopsis": "Picture loading/serialization (in png, jpeg, bitmap, gif, tga, tiff and radiance)", + "version": "3.3.5" + }, + { + "name": "JuicyPixels-blurhash", + "origin": "hackage", + "synopsis": "Blurhash is a very compact represenation of a placeholder for an image", + "version": "0.1.0.3" + }, + { + "name": "JuicyPixels-extra", + "origin": "hackage", + "synopsis": "Efficiently scale, crop, flip images with JuicyPixels", + "version": "0.4.1" + }, + { + "name": "JuicyPixels-scale-dct", + "origin": "hackage", + "synopsis": "Scale JuicyPixels images with DCT", + "version": "0.1.2" + }, + { + "name": "junit-xml", + "origin": "hackage", + "synopsis": "Producing JUnit-style XML test reports.", + "version": "0.1.0.2" + }, + { + "name": "justified-containers", + "origin": "hackage", + "synopsis": "Keyed container types with type-checked proofs of key presence.", + "version": "0.3.0.0" + }, + { + "name": "jwt", + "origin": "hackage", + "synopsis": "JSON Web Token (JWT) decoding and encoding", + "version": "0.10.0" + }, + { + "name": "kan-extensions", + "origin": "hackage", + "synopsis": "Kan extensions, Kan lifts, the Yoneda lemma, and (co)density (co)monads", + "version": "5.2.2" + }, + { + "name": "kanji", + "origin": "hackage", + "synopsis": "Perform 漢字検定 (Japan Kanji Aptitude Test) level analysis on Japanese Kanji", + "version": "3.4.1" + }, + { + "name": "katip", + "origin": "hackage", + "synopsis": "A structured logging framework.", + "version": "0.8.5.0" + }, + { + "name": "katip-logstash", + "origin": "hackage", + "synopsis": "Logstash backend for katip.", + "version": "0.1.0.0" + }, + { + "name": "kawhi", + "origin": "hackage", + "synopsis": "stats.NBA.com library", + "version": "0.3.0" + }, + { + "name": "kazura-queue", + "origin": "hackage", + "synopsis": "Fast concurrent queues much inspired by unagi-chan", + "version": "0.1.0.4" + }, + { + "name": "kdt", + "origin": "hackage", + "synopsis": "Fast and flexible k-d trees for various types of point queries.", + "version": "0.2.4" + }, + { + "name": "keycode", + "origin": "hackage", + "synopsis": "Maps web browser keycodes to their corresponding keyboard keys", + "version": "0.2.2" + }, + { + "name": "keys", + "origin": "hackage", + "synopsis": "Keyed functors and containers", + "version": "3.12.3" + }, + { + "name": "ki", + "origin": "hackage", + "synopsis": "A lightweight, structured-concurrency library", + "version": "0.2.0.1" + }, + { + "name": "kind-apply", + "origin": "hackage", + "synopsis": "Utilities to work with lists of types", + "version": "0.3.2.0" + }, + { + "name": "kind-generics", + "origin": "hackage", + "synopsis": "Generic programming in GHC style for arbitrary kinds and GADTs.", + "version": "0.4.1.0" + }, + { + "name": "kind-generics-th", + "origin": "hackage", + "synopsis": "Template Haskell support for generating `GenericK` instances", + "version": "0.2.2.2" + }, + { + "name": "kmeans", + "origin": "hackage", + "synopsis": "K-means clustering algorithm", + "version": "0.1.3" + }, + { + "name": "koji", + "origin": "hackage", + "synopsis": "Koji buildsystem XML-RPC API bindings", + "version": "0.0.1" + }, + { + "name": "koofr-client", + "origin": "hackage", + "synopsis": "Client to Koofr API", + "version": "1.0.0.3" + }, + { + "name": "krank", + "origin": "hackage", + "synopsis": "Krank checks your code source comments for important markers", + "version": "0.2.2" + }, + { + "name": "kubernetes-webhook-haskell", + "origin": "hackage", + "synopsis": "Create Kubernetes Admission Webhooks in Haskell", + "version": "0.2.0.3" + }, + { + "name": "l10n", + "origin": "hackage", + "synopsis": "Enables providing localization as typeclass instances in separate files.", + "version": "0.1.0.1" + }, + { + "name": "labels", + "origin": "hackage", + "synopsis": "Anonymous records via named tuples", + "version": "0.3.3" + }, + { + "name": "LambdaHack", + "origin": "hackage", + "synopsis": "A game engine library for tactical squad ASCII roguelike dungeon crawlers", + "version": "0.9.5.0" + }, + { + "name": "lame", + "origin": "hackage", + "synopsis": "Fairly complete high-level binding to LAME encoder", + "version": "0.2.0" + }, + { + "name": "language-avro", + "origin": "hackage", + "synopsis": "Language definition and parser for AVRO files.", + "version": "0.1.3.1" + }, + { + "name": "language-bash", + "origin": "hackage", + "synopsis": "Parsing and pretty-printing Bash shell scripts", + "version": "0.9.2" + }, + { + "name": "language-c", + "origin": "hackage", + "synopsis": "Analysis and generation of C code", + "version": "0.8.3" + }, + { + "name": "language-c-quote", + "origin": "hackage", + "synopsis": "C/CUDA/OpenCL/Objective-C quasiquoting library.", + "version": "0.12.2.1" + }, + { + "name": "language-docker", + "origin": "hackage", + "synopsis": "Dockerfile parser, pretty-printer and embedded DSL", + "version": "9.1.3" + }, + { + "name": "language-java", + "origin": "hackage", + "synopsis": "Java source manipulation", + "version": "0.2.9" + }, + { + "name": "language-javascript", + "origin": "hackage", + "synopsis": "Parser for JavaScript", + "version": "0.7.1.0" + }, + { + "name": "language-nix", + "origin": "hackage", + "synopsis": "Data types and functions to represent the Nix language", + "version": "2.2.0" + }, + { + "name": "language-protobuf", + "origin": "hackage", + "synopsis": "Language definition and parser for Protocol Buffers.", + "version": "1.0.1" + }, + { + "name": "language-python", + "origin": "hackage", + "synopsis": "Parsing and pretty printing of Python code. ", + "version": "0.5.8" + }, + { + "name": "language-thrift", + "origin": "hackage", + "synopsis": "Parser and pretty printer for the Thrift IDL format.", + "version": "0.12.0.0" + }, + { + "name": "lapack", + "origin": "hackage", + "synopsis": "Numerical Linear Algebra using LAPACK", + "version": "0.3.2" + }, + { + "name": "lapack-carray", + "origin": "hackage", + "synopsis": "Auto-generated interface to Fortran LAPACK via CArrays", + "version": "0.0.3" + }, + { + "name": "lapack-comfort-array", + "origin": "hackage", + "synopsis": "Auto-generated interface to Fortran LAPACK via comfort-array", + "version": "0.0.0.1" + }, + { + "name": "lapack-ffi", + "origin": "hackage", + "synopsis": "Auto-generated interface to Fortran LAPACK", + "version": "0.0.3" + }, + { + "name": "lapack-ffi-tools", + "origin": "hackage", + "synopsis": "Generator for Haskell interface to Fortran LAPACK", + "version": "0.1.2.1" + }, + { + "name": "largeword", + "origin": "hackage", + "synopsis": "Provides Word128, Word192 and Word256 and a way of producing other large words if required.", + "version": "1.2.5" + }, + { + "name": "latex", + "origin": "hackage", + "synopsis": "Parse, format and process LaTeX files", + "version": "0.1.0.4" + }, + { + "name": "lattices", + "origin": "hackage", + "synopsis": "Fine-grained library for constructing and manipulating lattices", + "version": "2.0.2" + }, + { + "name": "lawful", + "origin": "hackage", + "synopsis": "Assert the lawfulness of your typeclass instances.", + "version": "0.1.0.0" + }, + { + "name": "lazy-csv", + "origin": "hackage", + "synopsis": "Efficient lazy parsers for CSV (comma-separated values).", + "version": "0.5.1" + }, + { + "name": "lazyio", + "origin": "hackage", + "synopsis": "Run IO actions lazily while respecting their order", + "version": "0.1.0.4" + }, + { + "name": "lca", + "origin": "hackage", + "synopsis": "O(log n) persistent online lowest common ancestor search without preprocessing", + "version": "0.3.1" + }, + { + "name": "leancheck", + "origin": "hackage", + "synopsis": "Enumerative property-based testing", + "version": "0.9.4" + }, + { + "name": "leancheck-instances", + "origin": "hackage", + "synopsis": "Common LeanCheck instances", + "version": "0.0.4" + }, + { + "name": "leapseconds-announced", + "origin": "hackage", + "synopsis": "Leap seconds announced at library release time.", + "version": "2017.1.0.1" + }, + { + "name": "learn-physics", + "origin": "hackage", + "synopsis": "Haskell code for learning physics", + "version": "0.6.5" + }, + { + "name": "lens", + "origin": "hackage", + "synopsis": "Lenses, Folds and Traversals", + "version": "4.19.2" + }, + { + "name": "lens-action", + "origin": "hackage", + "synopsis": "Monadic Getters and Folds", + "version": "0.2.5" + }, + { + "name": "lens-aeson", + "origin": "hackage", + "synopsis": "Law-abiding lenses for aeson", + "version": "1.1.1" + }, + { + "name": "lens-csv", + "origin": "hackage", + "synopsis": "", + "version": "0.1.1.0" + }, + { + "name": "lens-datetime", + "origin": "hackage", + "synopsis": "Lenses for Data.Time.* types", + "version": "0.3" + }, + { + "name": "lens-family", + "origin": "hackage", + "synopsis": "Lens Families", + "version": "2.0.0" + }, + { + "name": "lens-family-core", + "origin": "hackage", + "synopsis": "Haskell 2022 Lens Families", + "version": "2.0.0" + }, + { + "name": "lens-family-th", + "origin": "hackage", + "synopsis": "Generate lens-family style lenses", + "version": "0.5.2.0" + }, + { + "name": "lens-misc", + "origin": "hackage", + "synopsis": "Miscellaneous lens utilities.", + "version": "0.0.2.0" + }, + { + "name": "lens-process", + "origin": "hackage", + "synopsis": "Optics for system processes", + "version": "0.3.0.2" + }, + { + "name": "lens-properties", + "origin": "hackage", + "synopsis": "QuickCheck properties for lens", + "version": "4.11.1" + }, + { + "name": "lens-regex", + "origin": "hackage", + "synopsis": "Lens powered regular expression", + "version": "0.1.3" + }, + { + "name": "lens-regex-pcre", + "origin": "hackage", + "synopsis": "A lensy interface to regular expressions", + "version": "1.1.0.0" + }, + { + "name": "lenz", + "origin": "hackage", + "synopsis": "Van Laarhoven lenses", + "version": "0.4.2.0" + }, + { + "name": "leveldb-haskell", + "origin": "hackage", + "synopsis": "Haskell bindings to LevelDB", + "version": "0.6.5" + }, + { + "name": "libffi", + "origin": "hackage", + "synopsis": "A binding to libffi", + "version": "0.1" + }, + { + "name": "libgit", + "origin": "hackage", + "synopsis": "Simple Git Wrapper", + "version": "0.3.1" + }, + { + "name": "libgraph", + "origin": "hackage", + "synopsis": "Store and manipulate data in a graph.", + "version": "1.14" + }, + { + "name": "libjwt-typed", + "origin": "hackage", + "synopsis": "A Haskell implementation of JSON Web Token (JWT)", + "version": "0.2" + }, + { + "name": "libmpd", + "origin": "hackage", + "synopsis": "An MPD client library.", + "version": "0.9.3.0" + }, + { + "name": "liboath-hs", + "origin": "hackage", + "synopsis": "Bindings to liboath", + "version": "0.0.1.2" + }, + { + "name": "libyaml", + "origin": "hackage", + "synopsis": "Low-level, streaming YAML interface.", + "version": "0.1.2" + }, + { + "name": "LibZip", + "origin": "hackage", + "synopsis": "Bindings to libzip, a library for manipulating zip archives.", + "version": "1.0.1" + }, + { + "name": "life-sync", + "origin": "hackage", + "synopsis": "Synchronize personal configs across multiple machines.", + "version": "1.1.1.0" + }, + { + "name": "lifted-async", + "origin": "hackage", + "synopsis": "Run lifted IO operations asynchronously and wait for their results", + "version": "0.10.2" + }, + { + "name": "lifted-base", + "origin": "hackage", + "synopsis": "lifted IO operations from the base library", + "version": "0.2.3.12" + }, + { + "name": "lift-generics", + "origin": "hackage", + "synopsis": "GHC.Generics-based Language.Haskell.TH.Syntax.lift implementation", + "version": "0.2" + }, + { + "name": "line", + "origin": "hackage", + "synopsis": "Haskell SDK for the LINE API", + "version": "4.0.1" + }, + { + "name": "linear", + "origin": "hackage", + "synopsis": "Linear Algebra", + "version": "1.21.5" + }, + { + "name": "linear-circuit", + "origin": "hackage", + "synopsis": "Compute resistance of linear electrical circuits", + "version": "0.1.0.2" + }, + { + "name": "linenoise", + "origin": "hackage", + "synopsis": "A lightweight readline-replacement library for Haskell", + "version": "0.3.2" + }, + { + "name": "linux-file-extents", + "origin": "hackage", + "synopsis": "Retrieve file fragmentation information under Linux", + "version": "0.2.0.0" + }, + { + "name": "linux-namespaces", + "origin": "hackage", + "synopsis": "Work with linux namespaces: create new or enter existing ones", + "version": "0.1.3.0" + }, + { + "name": "liquid-fixpoint", + "origin": "hackage", + "synopsis": "Predicate Abstraction-based Horn-Clause/Implication Constraint Solver", + "version": "0.8.10.2" + }, + { + "name": "List", + "origin": "hackage", + "synopsis": "List monad transformer and class", + "version": "0.6.2" + }, + { + "name": "ListLike", + "origin": "hackage", + "synopsis": "Generalized support for list-like structures", + "version": "4.7.4" + }, + { + "name": "list-predicate", + "origin": "hackage", + "synopsis": "Predicates on lists", + "version": "0.1.0.1" + }, + { + "name": "listsafe", + "origin": "hackage", + "synopsis": "Safe wrappers for partial list functions, supporting MonadThrow.", + "version": "0.1.0.1" + }, + { + "name": "list-singleton", + "origin": "hackage", + "synopsis": "Easily and clearly create lists with only one element in them.", + "version": "1.0.0.5" + }, + { + "name": "list-t", + "origin": "hackage", + "synopsis": "ListT done right", + "version": "1.0.4" + }, + { + "name": "ListTree", + "origin": "hackage", + "synopsis": "Trees and monadic trees expressed as monadic lists where the underlying monad is a list", + "version": "0.2.3" + }, + { + "name": "little-logger", + "origin": "hackage", + "synopsis": "Basic logging based on co-log", + "version": "0.3.1" + }, + { + "name": "little-rio", + "origin": "hackage", + "synopsis": "When you need just the RIO monad", + "version": "0.2.2" + }, + { + "name": "llvm-hs", + "origin": "hackage", + "synopsis": "General purpose LLVM bindings", + "version": "9.0.1" + }, + { + "name": "llvm-hs-pure", + "origin": "hackage", + "synopsis": "Pure Haskell LLVM functionality (no FFI).", + "version": "9.0.0" + }, + { + "name": "lmdb", + "origin": "hackage", + "synopsis": "Lightning MDB bindings ", + "version": "0.2.5" + }, + { + "name": "load-env", + "origin": "hackage", + "synopsis": "Load environment variables from a file.", + "version": "0.2.1.0" + }, + { + "name": "loc", + "origin": "hackage", + "synopsis": "Types representing line and column positions and ranges in text files.", + "version": "0.1.3.10" + }, + { + "name": "locators", + "origin": "hackage", + "synopsis": "Human exchangable identifiers and locators", + "version": "0.3.0.3" + }, + { + "name": "loch-th", + "origin": "hackage", + "synopsis": "Support for precise error locations in source files (Template Haskell version)", + "version": "0.2.2" + }, + { + "name": "lockfree-queue", + "origin": "hackage", + "synopsis": "Michael and Scott lock-free queues.", + "version": "0.2.3.1" + }, + { + "name": "log-domain", + "origin": "hackage", + "synopsis": "Log-domain arithmetic", + "version": "0.13.1" + }, + { + "name": "logfloat", + "origin": "hackage", + "synopsis": "Log-domain floating point numbers", + "version": "0.13.3.3" + }, + { + "name": "logging", + "origin": "hackage", + "synopsis": "Simplified logging in IO for application writers.", + "version": "3.0.5" + }, + { + "name": "logging-facade", + "origin": "hackage", + "synopsis": "Simple logging abstraction that allows multiple back-ends", + "version": "0.3.0" + }, + { + "name": "logging-facade-syslog", + "origin": "hackage", + "synopsis": "A logging back-end to syslog(3) for the logging-facade library", + "version": "1" + }, + { + "name": "logict", + "origin": "hackage", + "synopsis": "A backtracking logic-programming monad.", + "version": "0.7.1.0" + }, + { + "name": "logstash", + "origin": "hackage", + "synopsis": "Logstash client library for Haskell", + "version": "0.1.0.1" + }, + { + "name": "loop", + "origin": "hackage", + "synopsis": "Fast loops (for when GHC can't optimize forM_)", + "version": "0.3.0" + }, + { + "name": "lrucache", + "origin": "hackage", + "synopsis": "a simple, pure LRU cache", + "version": "1.2.0.1" + }, + { + "name": "lrucaching", + "origin": "hackage", + "synopsis": "LRU cache", + "version": "0.3.3" + }, + { + "name": "lsp-test", + "origin": "hackage", + "synopsis": "Functional test framework for LSP servers.", + "version": "0.11.0.5" + }, + { + "name": "lucid", + "origin": "hackage", + "synopsis": "Clear to write, read and edit DSL for HTML", + "version": "2.9.12.1" + }, + { + "name": "lucid-cdn", + "origin": "hackage", + "synopsis": "Curated list of CDN imports for lucid.", + "version": "0.2.2.0" + }, + { + "name": "lucid-extras", + "origin": "hackage", + "synopsis": "Generate more HTML with Lucid - Bootstrap, Rdash, Vega-Lite, Leaflet JS, Email.", + "version": "0.2.2" + }, + { + "name": "lukko", + "origin": "hackage", + "synopsis": "File locking", + "version": "0.1.1.3" + }, + { + "name": "lz4-frame-conduit", + "origin": "hackage", + "synopsis": "Conduit implementing the official LZ4 frame streaming format", + "version": "0.1.0.1" + }, + { + "name": "lzma", + "origin": "hackage", + "synopsis": "LZMA/XZ compression and decompression", + "version": "0.0.0.3" + }, + { + "name": "lzma-conduit", + "origin": "hackage", + "synopsis": "Conduit interface for lzma/xz compression.", + "version": "1.2.1" + }, + { + "name": "machines", + "origin": "hackage", + "synopsis": "Networked stream transducers", + "version": "0.7.2" + }, + { + "name": "magic", + "origin": "hackage", + "synopsis": "Interface to C file/magic library", + "version": "1.1" + }, + { + "name": "magico", + "origin": "hackage", + "synopsis": "Compute solutions for Magico puzzle", + "version": "0.0.2.1" + }, + { + "name": "mainland-pretty", + "origin": "hackage", + "synopsis": "Pretty printing designed for printing source code.", + "version": "0.7.0.1" + }, + { + "name": "main-tester", + "origin": "hackage", + "synopsis": "Capture stdout/stderr/exit code, and replace stdin of your main function.", + "version": "0.2.0.1" + }, + { + "name": "makefile", + "origin": "hackage", + "synopsis": "Simple Makefile parser and generator", + "version": "1.1.0.0" + }, + { + "name": "managed", + "origin": "hackage", + "synopsis": "A monad for managed values", + "version": "1.0.8" + }, + { + "name": "MapWith", + "origin": "hackage", + "synopsis": "mapWith: like fmap, but with additional parameters (isFirst, isLast, etc).", + "version": "0.2.0.0" + }, + { + "name": "markdown", + "origin": "hackage", + "synopsis": "Convert Markdown to HTML, with XSS protection", + "version": "0.1.17.4" + }, + { + "name": "markdown-unlit", + "origin": "hackage", + "synopsis": "Literate Haskell support for Markdown", + "version": "0.5.1" + }, + { + "name": "markov-chain", + "origin": "hackage", + "synopsis": "Markov Chains for generating random sequences with a user definable behaviour.", + "version": "0.0.3.4" + }, + { + "name": "massiv", + "origin": "hackage", + "synopsis": "Massiv (Массив) is an Array Library.", + "version": "0.6.0.0" + }, + { + "name": "massiv-io", + "origin": "hackage", + "synopsis": "Import/export of Image files into massiv Arrays", + "version": "0.4.1.0" + }, + { + "name": "massiv-persist", + "origin": "hackage", + "synopsis": "Compatibility of 'massiv' with 'persist'", + "version": "0.1.0.0" + }, + { + "name": "massiv-serialise", + "origin": "hackage", + "synopsis": "Compatibility of 'massiv' with 'serialise'", + "version": "0.1.0.0" + }, + { + "name": "massiv-test", + "origin": "hackage", + "synopsis": "Library that contains generators, properties and tests for Massiv Array Library.", + "version": "0.1.6.1" + }, + { + "name": "mathexpr", + "origin": "hackage", + "synopsis": "Parse and evaluate math expressions with variables and functions", + "version": "0.3.0.0" + }, + { + "name": "math-extras", + "origin": "hackage", + "synopsis": "A variety of mathematical utilities", + "version": "0.1.1.0" + }, + { + "name": "math-functions", + "origin": "hackage", + "synopsis": "Collection of tools for numeric computations", + "version": "0.3.4.2" + }, + { + "name": "matplotlib", + "origin": "hackage", + "synopsis": "Bindings to Matplotlib; a Python plotting library", + "version": "0.7.5" + }, + { + "name": "matrices", + "origin": "hackage", + "synopsis": "native matrix based on vector", + "version": "0.5.0" + }, + { + "name": "matrix", + "origin": "hackage", + "synopsis": "A native implementation of matrix operations.", + "version": "0.3.6.1" + }, + { + "name": "matrix-as-xyz", + "origin": "hackage", + "synopsis": "Read and Display Jones-Faithful notation for spacegroup and planegroup", + "version": "0.1.2.2" + }, + { + "name": "matrix-market-attoparsec", + "origin": "hackage", + "synopsis": "Parsing and serialization functions for the NIST Matrix Market format", + "version": "0.1.1.3" + }, + { + "name": "matrix-static", + "origin": "hackage", + "synopsis": "Type-safe matrix operations", + "version": "0.3" + }, + { + "name": "maximal-cliques", + "origin": "hackage", + "synopsis": "Enumerate all maximal cliques of a graph.", + "version": "0.1.1" + }, + { + "name": "mbox", + "origin": "hackage", + "synopsis": "Read and write standard mailbox files.", + "version": "0.3.4" + }, + { + "name": "mbox-utility", + "origin": "hackage", + "synopsis": "List contents of an mbox file containing e-mails", + "version": "0.0.3.1" + }, + { + "name": "mcmc", + "origin": "hackage", + "synopsis": "Sample from a posterior using Markov chain Monte Carlo", + "version": "0.4.0.0" + }, + { + "name": "mcmc-types", + "origin": "hackage", + "synopsis": "Common types for sampling.", + "version": "1.0.3" + }, + { + "name": "medea", + "origin": "hackage", + "synopsis": "A schema language for JSON.", + "version": "1.2.0" + }, + { + "name": "median-stream", + "origin": "hackage", + "synopsis": "Constant-time queries for the median of a stream of numeric\ndata.", + "version": "0.7.0.0" + }, + { + "name": "med-module", + "origin": "hackage", + "synopsis": "Parse song module files from Amiga MED and OctaMED", + "version": "0.1.2.1" + }, + { + "name": "megaparsec", + "origin": "hackage", + "synopsis": "Monadic parser combinators", + "version": "9.0.1" + }, + { + "name": "megaparsec-tests", + "origin": "hackage", + "synopsis": "Test utilities and the test suite of Megaparsec", + "version": "9.0.1" + }, + { + "name": "membrain", + "origin": "hackage", + "synopsis": "Type-safe memory units", + "version": "0.0.0.2" + }, + { + "name": "memory", + "origin": "hackage", + "synopsis": "memory and related abstraction stuff", + "version": "0.15.0" + }, + { + "name": "MemoTrie", + "origin": "hackage", + "synopsis": "Trie-based memo functions", + "version": "0.6.10" + }, + { + "name": "mercury-api", + "origin": "hackage", + "synopsis": "Haskell binding to Mercury API for ThingMagic RFID readers", + "version": "0.1.0.2" + }, + { + "name": "mergeful", + "origin": "hackage", + "synopsis": "", + "version": "0.2.0.0" + }, + { + "name": "mergeless", + "origin": "hackage", + "synopsis": "", + "version": "0.3.0.0" + }, + { + "name": "mersenne-random-pure64", + "origin": "hackage", + "synopsis": "Generate high quality pseudorandom numbers purely using a Mersenne Twister", + "version": "0.2.2.0" + }, + { + "name": "messagepack", + "origin": "hackage", + "synopsis": "Serialize instance for Message Pack Object", + "version": "0.5.4" + }, + { + "name": "metrics", + "origin": "hackage", + "synopsis": "High-performance application metric tracking", + "version": "0.4.1.1" + }, + { + "name": "mfsolve", + "origin": "hackage", + "synopsis": "Equation solver and calculator à la metafont", + "version": "0.3.2.0" + }, + { + "name": "microlens", + "origin": "hackage", + "synopsis": "A tiny lens library with no dependencies", + "version": "0.4.11.2" + }, + { + "name": "microlens-aeson", + "origin": "hackage", + "synopsis": "Law-abiding lenses for Aeson, using microlens.", + "version": "2.3.1" + }, + { + "name": "microlens-contra", + "origin": "hackage", + "synopsis": "True folds and getters for microlens", + "version": "0.1.0.2" + }, + { + "name": "microlens-ghc", + "origin": "hackage", + "synopsis": "microlens + array, bytestring, containers, transformers", + "version": "0.4.12" + }, + { + "name": "microlens-mtl", + "origin": "hackage", + "synopsis": "microlens support for Reader/Writer/State from mtl", + "version": "0.2.0.1" + }, + { + "name": "microlens-platform", + "origin": "hackage", + "synopsis": "microlens + all batteries included (best for apps)", + "version": "0.4.1" + }, + { + "name": "microlens-process", + "origin": "hackage", + "synopsis": "Micro-optics for the process library", + "version": "0.2.0.2" + }, + { + "name": "microlens-th", + "origin": "hackage", + "synopsis": "Automatic generation of record lenses for microlens", + "version": "0.4.3.9" + }, + { + "name": "microspec", + "origin": "hackage", + "synopsis": "Tiny QuickCheck test library with minimal dependencies", + "version": "0.2.1.3" + }, + { + "name": "microstache", + "origin": "hackage", + "synopsis": "Mustache templates for Haskell", + "version": "1.0.1.2" + }, + { + "name": "midair", + "origin": "hackage", + "synopsis": "Hot-swappable FRP", + "version": "0.2.0.1" + }, + { + "name": "midi", + "origin": "hackage", + "synopsis": "Handling of MIDI messages and files", + "version": "0.2.2.2" + }, + { + "name": "mighty-metropolis", + "origin": "hackage", + "synopsis": "The Metropolis algorithm.", + "version": "2.0.0" + }, + { + "name": "mime-mail", + "origin": "hackage", + "synopsis": "Compose MIME email messages.", + "version": "0.5.1" + }, + { + "name": "mime-mail-ses", + "origin": "hackage", + "synopsis": "Send mime-mail messages via Amazon SES", + "version": "0.4.3" + }, + { + "name": "mime-types", + "origin": "hackage", + "synopsis": "Basic mime-type handling types and functions", + "version": "0.1.0.9" + }, + { + "name": "mini-egison", + "origin": "hackage", + "synopsis": "Template Haskell Implementation of Egison Pattern Matching", + "version": "1.0.0" + }, + { + "name": "minimal-configuration", + "origin": "hackage", + "synopsis": "Minimal ini like configuration library with a few extras", + "version": "0.1.4" + }, + { + "name": "minimorph", + "origin": "hackage", + "synopsis": "English spelling functions with an emphasis on simplicity.", + "version": "0.3.0.0" + }, + { + "name": "minio-hs", + "origin": "hackage", + "synopsis": "A MinIO Haskell Library for Amazon S3 compatible cloud\nstorage.", + "version": "1.5.3" + }, + { + "name": "miniutter", + "origin": "hackage", + "synopsis": "Simple English clause creation from arbitrary words", + "version": "0.5.1.1" + }, + { + "name": "min-max-pqueue", + "origin": "hackage", + "synopsis": "Double-ended priority queues.", + "version": "0.1.0.2" + }, + { + "name": "mintty", + "origin": "hackage", + "synopsis": "A reliable way to detect the presence of a MinTTY console on Windows", + "version": "0.1.2" + }, + { + "name": "missing-foreign", + "origin": "hackage", + "synopsis": "Convenience functions for FFI work", + "version": "0.1.1" + }, + { + "name": "MissingH", + "origin": "hackage", + "synopsis": "Large utility library", + "version": "1.4.3.0" + }, + { + "name": "mixed-types-num", + "origin": "hackage", + "synopsis": "Alternative Prelude with numeric and logic expressions typed bottom-up", + "version": "0.4.1" + }, + { + "name": "mltool", + "origin": "hackage", + "synopsis": "Machine Learning Toolbox", + "version": "0.2.0.1" + }, + { + "name": "mmap", + "origin": "hackage", + "synopsis": "Memory mapped files for POSIX and Windows", + "version": "0.5.9" + }, + { + "name": "mmark", + "origin": "hackage", + "synopsis": "Strict markdown processor for writers", + "version": "0.0.7.2" + }, + { + "name": "mmark-cli", + "origin": "hackage", + "synopsis": "Command line interface to the MMark markdown processor", + "version": "0.0.5.0" + }, + { + "name": "mmark-ext", + "origin": "hackage", + "synopsis": "Commonly useful extensions for the MMark markdown processor", + "version": "0.2.1.3" + }, + { + "name": "mmorph", + "origin": "hackage", + "synopsis": "Monad morphisms", + "version": "1.1.5" + }, + { + "name": "mnist-idx", + "origin": "hackage", + "synopsis": "Read and write IDX data that is used in e.g. the MNIST database.", + "version": "0.1.2.8" + }, + { + "name": "mockery", + "origin": "hackage", + "synopsis": "Support functions for automated testing", + "version": "0.3.5" + }, + { + "name": "mock-time", + "origin": "hackage", + "synopsis": "Mock time in tests", + "version": "0.1.0" + }, + { + "name": "mod", + "origin": "hackage", + "synopsis": "Fast type-safe modular arithmetic", + "version": "0.1.2.2" + }, + { + "name": "model", + "origin": "hackage", + "synopsis": "Derive a model of a data type using Generics", + "version": "0.5" + }, + { + "name": "modern-uri", + "origin": "hackage", + "synopsis": "Modern library for working with URIs", + "version": "0.3.4.1" + }, + { + "name": "modular", + "origin": "hackage", + "synopsis": "Type-safe modular arithmetic", + "version": "0.1.0.8" + }, + { + "name": "monad-chronicle", + "origin": "hackage", + "synopsis": "These as a transformer, ChronicleT", + "version": "1.0.0.1" + }, + { + "name": "monad-control", + "origin": "hackage", + "synopsis": "Lift control operations, like exception catching, through monad transformers", + "version": "1.0.2.3" + }, + { + "name": "monad-control-aligned", + "origin": "hackage", + "synopsis": "Just like monad-control, except less efficient, and the monadic state terms are all * -> *", + "version": "0.0.1.1" + }, + { + "name": "monad-coroutine", + "origin": "hackage", + "synopsis": "Coroutine monad transformer for suspending and resuming monadic computations", + "version": "0.9.1" + }, + { + "name": "monad-extras", + "origin": "hackage", + "synopsis": "Extra utility functions for working with monads", + "version": "0.6.0" + }, + { + "name": "monadic-arrays", + "origin": "hackage", + "synopsis": "Boxed and unboxed arrays for monad transformers", + "version": "0.2.2" + }, + { + "name": "monad-journal", + "origin": "hackage", + "synopsis": "Pure logger typeclass and monad transformer", + "version": "0.8.1" + }, + { + "name": "monadlist", + "origin": "hackage", + "synopsis": "Monadic versions of list functions", + "version": "0.0.2" + }, + { + "name": "monad-logger", + "origin": "hackage", + "synopsis": "A class of monads which can log messages.", + "version": "0.3.36" + }, + { + "name": "monad-logger-json", + "origin": "hackage", + "synopsis": "JSON-friendly Logging APIs", + "version": "0.1.0.0" + }, + { + "name": "monad-logger-logstash", + "origin": "hackage", + "synopsis": "Logstash backend for monad-logger.", + "version": "0.1.0.0" + }, + { + "name": "monad-logger-prefix", + "origin": "hackage", + "synopsis": "Add prefixes to your monad-logger output", + "version": "0.1.12" + }, + { + "name": "monad-loops", + "origin": "hackage", + "synopsis": "Monadic loops", + "version": "0.4.3" + }, + { + "name": "monad-memo", + "origin": "hackage", + "synopsis": "Memoization monad transformer", + "version": "0.5.3" + }, + { + "name": "monad-metrics", + "origin": "hackage", + "synopsis": "A convenient wrapper around EKG metrics", + "version": "0.2.2.0" + }, + { + "name": "monad-par", + "origin": "hackage", + "synopsis": "A library for parallel programming based on a monad", + "version": "0.3.5" + }, + { + "name": "monad-parallel", + "origin": "hackage", + "synopsis": "Parallel execution of monadic computations", + "version": "0.7.2.4" + }, + { + "name": "monad-par-extras", + "origin": "hackage", + "synopsis": "Combinators and extra features for Par monads", + "version": "0.3.3" + }, + { + "name": "monad-peel", + "origin": "hackage", + "synopsis": "Lift control operations like exception catching through monad transformers", + "version": "0.2.1.2" + }, + { + "name": "monad-primitive", + "origin": "hackage", + "synopsis": "Type class for monad transformers stack with pirimitive base monad.", + "version": "0.1" + }, + { + "name": "monad-products", + "origin": "hackage", + "synopsis": "Monad products", + "version": "4.0.1" + }, + { + "name": "MonadPrompt", + "origin": "hackage", + "synopsis": "MonadPrompt, implementation & examples", + "version": "1.0.0.5" + }, + { + "name": "MonadRandom", + "origin": "hackage", + "synopsis": "Random-number generation monad.", + "version": "0.5.3" + }, + { + "name": "monad-resumption", + "origin": "hackage", + "synopsis": "Resumption and reactive resumption monads for Haskell.", + "version": "0.1.4.0" + }, + { + "name": "monad-skeleton", + "origin": "hackage", + "synopsis": "Monads of program skeleta", + "version": "0.1.5" + }, + { + "name": "monad-st", + "origin": "hackage", + "synopsis": "Provides a MonadST class", + "version": "0.2.4.1" + }, + { + "name": "monads-tf", + "origin": "hackage", + "synopsis": "Monad classes, using type families", + "version": "0.1.0.3" + }, + { + "name": "monad-time", + "origin": "hackage", + "synopsis": "Type class for monads which carry\nthe notion of the current time.", + "version": "0.3.1.0" + }, + { + "name": "monad-unlift", + "origin": "hackage", + "synopsis": "Typeclasses for representing monad transformer unlifting", + "version": "0.2.0" + }, + { + "name": "monad-unlift-ref", + "origin": "hackage", + "synopsis": "Typeclasses for representing monad transformer unlifting", + "version": "0.2.1" + }, + { + "name": "mongoDB", + "origin": "hackage", + "synopsis": "Driver (client) for MongoDB, a free, scalable, fast, document\nDBMS", + "version": "2.7.0.0" + }, + { + "name": "monoid-subclasses", + "origin": "hackage", + "synopsis": "Subclasses of Monoid", + "version": "1.0.1" + }, + { + "name": "monoid-transformer", + "origin": "hackage", + "synopsis": "Monoid counterparts to some ubiquitous monad transformers", + "version": "0.0.4" + }, + { + "name": "mono-traversable", + "origin": "hackage", + "synopsis": "Type classes for mapping, folding, and traversing monomorphic containers", + "version": "1.0.15.1" + }, + { + "name": "mono-traversable-instances", + "origin": "hackage", + "synopsis": "Extra typeclass instances for mono-traversable", + "version": "0.1.1.0" + }, + { + "name": "mono-traversable-keys", + "origin": "hackage", + "synopsis": "Type-classes for interacting with monomorphic containers with a key", + "version": "0.1.0" + }, + { + "name": "more-containers", + "origin": "hackage", + "synopsis": "A few more collections", + "version": "0.2.2.2" + }, + { + "name": "morpheus-graphql", + "origin": "hackage", + "synopsis": "Morpheus GraphQL", + "version": "0.16.0" + }, + { + "name": "morpheus-graphql-client", + "origin": "hackage", + "synopsis": "Morpheus GraphQL Client", + "version": "0.16.0" + }, + { + "name": "morpheus-graphql-core", + "origin": "hackage", + "synopsis": "Morpheus GraphQL Core", + "version": "0.16.0" + }, + { + "name": "morpheus-graphql-subscriptions", + "origin": "hackage", + "synopsis": "Morpheus GraphQL Subscriptions", + "version": "0.16.0" + }, + { + "name": "moss", + "origin": "hackage", + "synopsis": "Haskell client for Moss", + "version": "0.2.0.0" + }, + { + "name": "mountpoints", + "origin": "hackage", + "synopsis": "list mount points", + "version": "1.0.2" + }, + { + "name": "mpi-hs", + "origin": "hackage", + "synopsis": "MPI bindings for Haskell", + "version": "0.7.2.0" + }, + { + "name": "mpi-hs-binary", + "origin": "hackage", + "synopsis": "MPI bindings for Haskell", + "version": "0.1.1.0" + }, + { + "name": "mpi-hs-cereal", + "origin": "hackage", + "synopsis": "MPI bindings for Haskell", + "version": "0.1.0.0" + }, + { + "name": "mtl", + "origin": "core", + "synopsis": "Monad classes, using functional dependencies", + "version": "2.2.2" + }, + { + "name": "mtl-compat", + "origin": "hackage", + "synopsis": "Backported Control.Monad.Except module from mtl", + "version": "0.2.2" + }, + { + "name": "mtl-prelude", + "origin": "hackage", + "synopsis": "Reexports of most definitions from \"mtl\" and \"transformers\" ", + "version": "2.0.3.1" + }, + { + "name": "multiarg", + "origin": "hackage", + "synopsis": "Command lines for options that take multiple arguments", + "version": "0.30.0.10" + }, + { + "name": "multi-containers", + "origin": "hackage", + "synopsis": "A few multimap variants.", + "version": "0.1.1" + }, + { + "name": "multimap", + "origin": "hackage", + "synopsis": "A multimap.", + "version": "1.2.1" + }, + { + "name": "multipart", + "origin": "hackage", + "synopsis": "Parsers for the HTTP multipart format", + "version": "0.2.1" + }, + { + "name": "multiset", + "origin": "hackage", + "synopsis": "The Data.MultiSet container type", + "version": "0.3.4.3" + }, + { + "name": "multistate", + "origin": "hackage", + "synopsis": "like mtl's ReaderT / WriterT / StateT, but more than one\ncontained value/type.", + "version": "0.8.0.3" + }, + { + "name": "murmur3", + "origin": "hackage", + "synopsis": "Pure Haskell implementation of the MurmurHash3 x86_32 algorithm.", + "version": "1.0.4" + }, + { + "name": "murmur-hash", + "origin": "hackage", + "synopsis": "MurmurHash2 implementation for Haskell.", + "version": "0.1.0.9" + }, + { + "name": "MusicBrainz", + "origin": "hackage", + "synopsis": "interface to MusicBrainz XML2 and JSON web services", + "version": "0.4.1" + }, + { + "name": "mustache", + "origin": "hackage", + "synopsis": "A mustache template parser library.", + "version": "2.3.1" + }, + { + "name": "mutable-containers", + "origin": "hackage", + "synopsis": "Abstactions and concrete implementations of mutable containers", + "version": "0.3.4" + }, + { + "name": "mwc-probability", + "origin": "hackage", + "synopsis": "Sampling function-based probability distributions.", + "version": "2.3.1" + }, + { + "name": "mwc-random", + "origin": "hackage", + "synopsis": "Fast, high quality pseudo random number generation", + "version": "0.14.0.0" + }, + { + "name": "mwc-random-monad", + "origin": "hackage", + "synopsis": "Monadic interface for mwc-random", + "version": "0.7.3.1" + }, + { + "name": "mx-state-codes", + "origin": "hackage", + "synopsis": "ISO 3166-2:MX State Codes and Names", + "version": "1.0.0.0" + }, + { + "name": "mysql", + "origin": "hackage", + "synopsis": "A low-level MySQL client library.", + "version": "0.1.7.3" + }, + { + "name": "mysql-simple", + "origin": "hackage", + "synopsis": "A mid-level MySQL client library.", + "version": "0.4.5" + }, + { + "name": "n2o", + "origin": "hackage", + "synopsis": "Abstract Protocol Loop", + "version": "0.11.1" + }, + { + "name": "nagios-check", + "origin": "hackage", + "synopsis": "Package for writing monitoring plugins", + "version": "0.3.2" + }, + { + "name": "names-th", + "origin": "hackage", + "synopsis": "Manipulate name strings for TH", + "version": "0.3.0.1" + }, + { + "name": "nano-erl", + "origin": "hackage", + "synopsis": "Small library for Erlang-style actor semantics", + "version": "0.1.0.1" + }, + { + "name": "nanospec", + "origin": "hackage", + "synopsis": "A lightweight implementation of a subset of Hspec's API", + "version": "0.2.2" + }, + { + "name": "nats", + "origin": "hackage", + "synopsis": "Natural numbers", + "version": "1.1.2" + }, + { + "name": "natural-induction", + "origin": "hackage", + "synopsis": "Induction over natural numbers", + "version": "0.2.0.0" + }, + { + "name": "natural-sort", + "origin": "hackage", + "synopsis": "User-friendly text collation", + "version": "0.1.2" + }, + { + "name": "natural-transformation", + "origin": "hackage", + "synopsis": "A natural transformation package.", + "version": "0.4" + }, + { + "name": "ndjson-conduit", + "origin": "hackage", + "synopsis": "Conduit-based parsing and serialization for newline delimited JSON.", + "version": "0.1.0.5" + }, + { + "name": "neat-interpolation", + "origin": "hackage", + "synopsis": "A quasiquoter for neat and simple multiline text interpolation", + "version": "0.5.1.2" + }, + { + "name": "netcode-io", + "origin": "hackage", + "synopsis": "Bindings to the low-level netcode.io library.", + "version": "0.0.2" + }, + { + "name": "netlib-carray", + "origin": "hackage", + "synopsis": "Helper modules for CArray wrappers to BLAS and LAPACK", + "version": "0.1" + }, + { + "name": "netlib-comfort-array", + "origin": "hackage", + "synopsis": "Helper modules for comfort-array wrappers to BLAS and LAPACK", + "version": "0.0.0.1" + }, + { + "name": "netlib-ffi", + "origin": "hackage", + "synopsis": "Helper modules for FFI to BLAS and LAPACK", + "version": "0.1.1" + }, + { + "name": "netpbm", + "origin": "hackage", + "synopsis": "Loading PBM, PGM, PPM image files", + "version": "1.0.4" + }, + { + "name": "nettle", + "origin": "hackage", + "synopsis": "safe nettle binding", + "version": "0.3.0" + }, + { + "name": "netwire", + "origin": "hackage", + "synopsis": "Functional reactive programming library", + "version": "5.0.3" + }, + { + "name": "netwire-input", + "origin": "hackage", + "synopsis": "Input handling abstractions for netwire", + "version": "0.0.7" + }, + { + "name": "netwire-input-glfw", + "origin": "hackage", + "synopsis": "GLFW instance of netwire-input", + "version": "0.0.11" + }, + { + "name": "network", + "origin": "hackage", + "synopsis": "Low-level networking interface", + "version": "3.1.1.1" + }, + { + "name": "network-bsd", + "origin": "hackage", + "synopsis": "POSIX network database () API", + "version": "2.8.1.0" + }, + { + "name": "network-byte-order", + "origin": "hackage", + "synopsis": "Network byte order utilities", + "version": "0.1.6" + }, + { + "name": "network-conduit-tls", + "origin": "hackage", + "synopsis": "Create TLS-aware network code with conduits", + "version": "1.3.2" + }, + { + "name": "network-info", + "origin": "hackage", + "synopsis": "Access the local computer's basic network configuration", + "version": "0.2.0.10" + }, + { + "name": "network-ip", + "origin": "hackage", + "synopsis": "Internet Protocol data structures", + "version": "0.3.0.3" + }, + { + "name": "network-messagepack-rpc", + "origin": "hackage", + "synopsis": "MessagePack RPC", + "version": "0.1.2.0" + }, + { + "name": "network-messagepack-rpc-websocket", + "origin": "hackage", + "synopsis": "WebSocket backend for MessagePack RPC", + "version": "0.1.1.1" + }, + { + "name": "network-simple", + "origin": "hackage", + "synopsis": "Simple network sockets usage patterns.", + "version": "0.4.5" + }, + { + "name": "network-simple-tls", + "origin": "hackage", + "synopsis": "Simple interface to TLS secured network sockets.", + "version": "0.4" + }, + { + "name": "network-transport", + "origin": "hackage", + "synopsis": "Network abstraction layer", + "version": "0.5.4" + }, + { + "name": "network-transport-composed", + "origin": "hackage", + "synopsis": "Compose network transports", + "version": "0.2.1" + }, + { + "name": "network-uri", + "origin": "hackage", + "synopsis": "URI manipulation", + "version": "2.6.4.1" + }, + { + "name": "newtype", + "origin": "hackage", + "synopsis": "A typeclass and set of functions for working with newtypes.", + "version": "0.2.2.0" + }, + { + "name": "newtype-generics", + "origin": "hackage", + "synopsis": "A typeclass and set of functions for working with newtypes", + "version": "0.5.4" + }, + { + "name": "nicify-lib", + "origin": "hackage", + "synopsis": "Pretty print the standard output of default `Show` instances.", + "version": "1.0.1" + }, + { + "name": "NineP", + "origin": "hackage", + "synopsis": "9P2000 in pure Haskell", + "version": "0.0.2.1" + }, + { + "name": "nix-paths", + "origin": "hackage", + "synopsis": "Knowledge of Nix's installation directories.", + "version": "1.0.1" + }, + { + "name": "nonce", + "origin": "hackage", + "synopsis": "Generate cryptographic nonces.", + "version": "1.0.7" + }, + { + "name": "nondeterminism", + "origin": "hackage", + "synopsis": "A monad and monad transformer for nondeterministic computations.", + "version": "1.4" + }, + { + "name": "non-empty", + "origin": "hackage", + "synopsis": "List-like structures with static restrictions on the number of elements", + "version": "0.3.3" + }, + { + "name": "nonempty-containers", + "origin": "hackage", + "synopsis": "Non-empty variants of containers data types, with full API", + "version": "0.3.4.1" + }, + { + "name": "nonemptymap", + "origin": "hackage", + "synopsis": "A NonEmptyMap Implementation", + "version": "0.0.6.0" + }, + { + "name": "non-empty-sequence", + "origin": "hackage", + "synopsis": "Non-empty sequence", + "version": "0.2.0.4" + }, + { + "name": "nonempty-vector", + "origin": "hackage", + "synopsis": "Non-empty vectors", + "version": "0.2.1.0" + }, + { + "name": "non-negative", + "origin": "hackage", + "synopsis": "Non-negative numbers", + "version": "0.1.2" + }, + { + "name": "not-gloss", + "origin": "hackage", + "synopsis": "Painless 3D graphics, no affiliation with gloss", + "version": "0.7.7.0" + }, + { + "name": "no-value", + "origin": "hackage", + "synopsis": "A type class for choosing sentinel-like values", + "version": "1.0.0.0" + }, + { + "name": "nowdoc", + "origin": "hackage", + "synopsis": "Here document without variable expansion like PHP Nowdoc", + "version": "0.1.1.0" + }, + { + "name": "nqe", + "origin": "hackage", + "synopsis": "Concurrency library in the style of Erlang/OTP", + "version": "0.6.3" + }, + { + "name": "nri-env-parser", + "origin": "hackage", + "synopsis": "Read environment variables as settings to build 12-factor apps.", + "version": "0.1.0.3" + }, + { + "name": "nri-prelude", + "origin": "hackage", + "synopsis": "A Prelude inspired by the Elm programming language", + "version": "0.3.1.0" + }, + { + "name": "nsis", + "origin": "hackage", + "synopsis": "DSL for producing Windows Installer using NSIS.", + "version": "0.3.3" + }, + { + "name": "numbers", + "origin": "hackage", + "synopsis": "Various number types", + "version": "3000.2.0.2" + }, + { + "name": "numeric-extras", + "origin": "hackage", + "synopsis": "Useful tools from the C standard library", + "version": "0.1" + }, + { + "name": "numeric-prelude", + "origin": "hackage", + "synopsis": "An experimental alternative hierarchy of numeric type classes", + "version": "0.4.3.3" + }, + { + "name": "numhask", + "origin": "hackage", + "synopsis": "numeric classes", + "version": "0.6.0.2" + }, + { + "name": "NumInstances", + "origin": "hackage", + "synopsis": "Instances of numeric classes for functions and tuples", + "version": "1.4" + }, + { + "name": "numtype-dk", + "origin": "hackage", + "synopsis": "Type-level integers, using TypeNats, Data\nKinds, and Closed Type Families.", + "version": "0.5.0.2" + }, + { + "name": "nuxeo", + "origin": "hackage", + "synopsis": "", + "version": "0.3.2" + }, + { + "name": "nvim-hs", + "origin": "hackage", + "synopsis": "Haskell plugin backend for neovim", + "version": "2.1.0.4" + }, + { + "name": "nvim-hs-contrib", + "origin": "hackage", + "synopsis": "Haskell plugin backend for neovim", + "version": "2.0.0.0" + }, + { + "name": "nvim-hs-ghcid", + "origin": "hackage", + "synopsis": "Neovim plugin that runs ghcid to update the quickfix list", + "version": "2.0.0.0" + }, + { + "name": "oauthenticated", + "origin": "hackage", + "synopsis": "Simple OAuth for http-client", + "version": "0.2.1.0" + }, + { + "name": "ObjectName", + "origin": "hackage", + "synopsis": "Explicitly handled object names", + "version": "1.1.0.1" + }, + { + "name": "o-clock", + "origin": "hackage", + "synopsis": "Type-safe time library.", + "version": "1.2.0.1" + }, + { + "name": "odbc", + "origin": "hackage", + "synopsis": "Haskell binding to the ODBC API, aimed at SQL Server driver", + "version": "0.2.2" + }, + { + "name": "oeis2", + "origin": "hackage", + "synopsis": "Interface for Online Encyclopedia of Integer Sequences (OEIS).", + "version": "1.0.5" + }, + { + "name": "ofx", + "origin": "hackage", + "synopsis": "Parser for OFX data", + "version": "0.4.4.0" + }, + { + "name": "old-locale", + "origin": "hackage", + "synopsis": "locale library", + "version": "1.0.0.7" + }, + { + "name": "old-time", + "origin": "hackage", + "synopsis": "Time library", + "version": "1.1.0.3" + }, + { + "name": "once", + "origin": "hackage", + "synopsis": "memoization for IO actions and functions", + "version": "0.4" + }, + { + "name": "one-liner", + "origin": "hackage", + "synopsis": "Constraint-based generics", + "version": "1.0" + }, + { + "name": "one-liner-instances", + "origin": "hackage", + "synopsis": "Generics-based implementations for common typeclasses", + "version": "0.1.2.1" + }, + { + "name": "OneTuple", + "origin": "hackage", + "synopsis": "Singleton Tuple", + "version": "0.2.2.1" + }, + { + "name": "Only", + "origin": "hackage", + "synopsis": "The 1-tuple type or single-value \"collection\"", + "version": "0.1" + }, + { + "name": "oo-prototypes", + "origin": "hackage", + "synopsis": "Support for OO-like prototypes", + "version": "0.1.0.0" + }, + { + "name": "opaleye", + "origin": "hackage", + "synopsis": "An SQL-generating DSL targeting PostgreSQL", + "version": "0.7.1.0" + }, + { + "name": "OpenAL", + "origin": "hackage", + "synopsis": "A binding to the OpenAL cross-platform 3D audio API", + "version": "1.7.0.5" + }, + { + "name": "openapi3", + "origin": "hackage", + "synopsis": "OpenAPI 3.0 data model", + "version": "3.0.2.0" + }, + { + "name": "open-browser", + "origin": "hackage", + "synopsis": "Open a web browser from Haskell.", + "version": "0.2.1.0" + }, + { + "name": "openexr-write", + "origin": "hackage", + "synopsis": "Library for writing images in OpenEXR HDR file format.", + "version": "0.1.0.2" + }, + { + "name": "OpenGL", + "origin": "hackage", + "synopsis": "A binding for the OpenGL graphics system", + "version": "3.0.3.0" + }, + { + "name": "OpenGLRaw", + "origin": "hackage", + "synopsis": "A raw binding for the OpenGL graphics system", + "version": "3.3.4.0" + }, + { + "name": "openpgp-asciiarmor", + "origin": "hackage", + "synopsis": "OpenPGP (RFC4880) ASCII Armor codec", + "version": "0.1.2" + }, + { + "name": "opensource", + "origin": "hackage", + "synopsis": "Haskell API Wrapper for the Open Source License API", + "version": "0.1.1.0" + }, + { + "name": "openssl-streams", + "origin": "hackage", + "synopsis": "OpenSSL network support for io-streams.", + "version": "1.2.3.0" + }, + { + "name": "opentelemetry", + "origin": "hackage", + "synopsis": "", + "version": "0.6.1" + }, + { + "name": "opentelemetry-extra", + "origin": "hackage", + "synopsis": "", + "version": "0.6.1" + }, + { + "name": "opentelemetry-lightstep", + "origin": "hackage", + "synopsis": "", + "version": "0.6.1" + }, + { + "name": "opentelemetry-wai", + "origin": "hackage", + "synopsis": "", + "version": "0.6.1" + }, + { + "name": "operational", + "origin": "hackage", + "synopsis": "Implementation of difficult monads made easy\nwith operational semantics.", + "version": "0.2.3.5" + }, + { + "name": "operational-class", + "origin": "hackage", + "synopsis": "MonadProgram typeclass for the operational package", + "version": "0.3.0.0" + }, + { + "name": "optics", + "origin": "hackage", + "synopsis": "Optics as an abstract interface", + "version": "0.3" + }, + { + "name": "optics-core", + "origin": "hackage", + "synopsis": "Optics as an abstract interface: core definitions", + "version": "0.3.0.1" + }, + { + "name": "optics-extra", + "origin": "hackage", + "synopsis": "Extra utilities and instances for optics-core", + "version": "0.3" + }, + { + "name": "optics-th", + "origin": "hackage", + "synopsis": "Optics construction using TemplateHaskell", + "version": "0.3.0.2" + }, + { + "name": "optics-vl", + "origin": "hackage", + "synopsis": "Utilities for compatibility with van Laarhoven optics", + "version": "0.2.1" + }, + { + "name": "optional-args", + "origin": "hackage", + "synopsis": "Optional function arguments", + "version": "1.0.2" + }, + { + "name": "options", + "origin": "hackage", + "synopsis": "A powerful and easy-to-use command-line option parser.", + "version": "1.2.1.1" + }, + { + "name": "optparse-applicative", + "origin": "hackage", + "synopsis": "Utilities and combinators for parsing command line options", + "version": "0.15.1.0" + }, + { + "name": "optparse-generic", + "origin": "hackage", + "synopsis": "Auto-generate a command-line parser for your datatype", + "version": "1.3.1" + }, + { + "name": "optparse-simple", + "origin": "hackage", + "synopsis": "Simple interface to optparse-applicative", + "version": "0.1.1.3" + }, + { + "name": "optparse-text", + "origin": "hackage", + "synopsis": "Data.Text helpers for optparse-applicative", + "version": "0.1.1.0" + }, + { + "name": "ordered-containers", + "origin": "hackage", + "synopsis": "Set- and Map-like types that remember the order elements were inserted", + "version": "0.2.2" + }, + { + "name": "ormolu", + "origin": "hackage", + "synopsis": "A formatter for Haskell source code", + "version": "0.1.4.1" + }, + { + "name": "overhang", + "origin": "hackage", + "synopsis": "Hang loose with your lambdas!", + "version": "1.0.0" + }, + { + "name": "packcheck", + "origin": "hackage", + "synopsis": "Universal build and CI testing for Haskell packages", + "version": "0.5.1" + }, + { + "name": "packdeps", + "origin": "hackage", + "synopsis": "Check your cabal packages for lagging dependencies.", + "version": "0.6.0.0" + }, + { + "name": "pager", + "origin": "hackage", + "synopsis": "Open up a pager, like 'less' or 'more'", + "version": "0.1.1.0" + }, + { + "name": "pagination", + "origin": "hackage", + "synopsis": "Framework-agnostic pagination boilerplate", + "version": "0.2.2" + }, + { + "name": "pagure-cli", + "origin": "hackage", + "synopsis": "Pagure client", + "version": "0.2" + }, + { + "name": "pandoc", + "origin": "hackage", + "synopsis": "Conversion between markup formats", + "version": "2.11.4" + }, + { + "name": "pandoc-types", + "origin": "hackage", + "synopsis": "Types for representing a structured document", + "version": "1.22" + }, + { + "name": "pantry", + "origin": "hackage", + "synopsis": "Content addressable Haskell package management", + "version": "0.5.1.5" + }, + { + "name": "parallel", + "origin": "hackage", + "synopsis": "Parallel programming library", + "version": "3.2.2.0" + }, + { + "name": "parallel-io", + "origin": "hackage", + "synopsis": "Combinators for executing IO actions in parallel on a thread pool.", + "version": "0.3.3" + }, + { + "name": "parameterized", + "origin": "hackage", + "synopsis": "Parameterized/indexed monoids and monads using only a single parameter type variable.", + "version": "0.5.0.0" + }, + { + "name": "paripari", + "origin": "hackage", + "synopsis": "Parser combinators with fast-path and slower fallback for error reporting", + "version": "0.7.0.0" + }, + { + "name": "parseargs", + "origin": "hackage", + "synopsis": "Parse command-line arguments", + "version": "0.2.0.9" + }, + { + "name": "parsec", + "origin": "core", + "synopsis": "Monadic parser combinators", + "version": "3.1.14.0" + }, + { + "name": "parsec-class", + "origin": "hackage", + "synopsis": "Class of types that can be constructed from their text representation", + "version": "1.0.0.0" + }, + { + "name": "parsec-numbers", + "origin": "hackage", + "synopsis": "Utilities for parsing numbers from strings", + "version": "0.1.0" + }, + { + "name": "parsec-numeric", + "origin": "hackage", + "synopsis": "Parsec combinators for parsing Haskell numeric types.", + "version": "0.1.0.0" + }, + { + "name": "ParsecTools", + "origin": "hackage", + "synopsis": "Parsec combinators for more complex objects.", + "version": "0.0.2.0" + }, + { + "name": "parser-combinators", + "origin": "hackage", + "synopsis": "Lightweight package providing commonly useful parser combinators", + "version": "1.2.1" + }, + { + "name": "parser-combinators-tests", + "origin": "hackage", + "synopsis": "Test suite of parser-combinators", + "version": "1.2.1" + }, + { + "name": "parsers", + "origin": "hackage", + "synopsis": "Parsing combinators", + "version": "0.12.10" + }, + { + "name": "partial-handler", + "origin": "hackage", + "synopsis": "A composable exception handler", + "version": "1.0.3" + }, + { + "name": "partial-isomorphisms", + "origin": "hackage", + "synopsis": "Partial isomorphisms.", + "version": "0.2.2.1" + }, + { + "name": "partial-semigroup", + "origin": "hackage", + "synopsis": "A partial binary associative operator", + "version": "0.5.1.8" + }, + { + "name": "password", + "origin": "hackage", + "synopsis": "Hashing and checking of passwords", + "version": "2.1.1.0" + }, + { + "name": "password-instances", + "origin": "hackage", + "synopsis": "typeclass instances for password package", + "version": "2.0.0.2" + }, + { + "name": "path", + "origin": "hackage", + "synopsis": "Support for well-typed paths", + "version": "0.7.0" + }, + { + "name": "path-binary-instance", + "origin": "hackage", + "synopsis": "Binary instance for Path.", + "version": "0.1.0.1" + }, + { + "name": "path-extensions", + "origin": "hackage", + "synopsis": "Enumeration of common filetype extensions for use with the path library.", + "version": "0.1.1.0" + }, + { + "name": "path-extra", + "origin": "hackage", + "synopsis": "URLs without host information", + "version": "0.2.0" + }, + { + "name": "path-io", + "origin": "hackage", + "synopsis": "Interface to ‘directory’ package for users of ‘path’", + "version": "1.6.2" + }, + { + "name": "path-like", + "origin": "hackage", + "synopsis": "PathLike, FileLike and DirLike type classes for the Path library.", + "version": "0.2.0.2" + }, + { + "name": "path-pieces", + "origin": "hackage", + "synopsis": "Components of paths.", + "version": "0.2.1" + }, + { + "name": "path-text-utf8", + "origin": "hackage", + "synopsis": "Read and write UTF-8 text files", + "version": "0.0.1.6" + }, + { + "name": "pathtype", + "origin": "hackage", + "synopsis": "Type-safe replacement for System.FilePath etc", + "version": "0.8.1.1" + }, + { + "name": "pathwalk", + "origin": "hackage", + "synopsis": "Path walking utilities for Haskell programs", + "version": "0.3.1.2" + }, + { + "name": "pattern-arrows", + "origin": "hackage", + "synopsis": "Arrows for Pretty Printing", + "version": "0.0.2" + }, + { + "name": "pava", + "origin": "hackage", + "synopsis": "Greatest convex majorants and least concave minorants", + "version": "0.1.1.1" + }, + { + "name": "pcg-random", + "origin": "hackage", + "synopsis": "Haskell bindings to the PCG random number generator.", + "version": "0.1.3.7" + }, + { + "name": "pcre2", + "origin": "hackage", + "synopsis": "Regular expressions via the PCRE2 C library (included)", + "version": "1.1.4" + }, + { + "name": "pcre-heavy", + "origin": "hackage", + "synopsis": "A regexp (regex) library on top of pcre-light you can actually use.", + "version": "1.0.0.2" + }, + { + "name": "pcre-light", + "origin": "hackage", + "synopsis": "Portable regex library for Perl 5 compatible regular expressions", + "version": "0.4.1.0" + }, + { + "name": "pcre-utils", + "origin": "hackage", + "synopsis": "Perl-like substitute and split for PCRE regexps.", + "version": "0.1.8.2" + }, + { + "name": "pdfinfo", + "origin": "hackage", + "synopsis": "Wrapper around the pdfinfo command.", + "version": "1.5.4" + }, + { + "name": "peano", + "origin": "hackage", + "synopsis": "Peano numbers", + "version": "0.1.0.1" + }, + { + "name": "pem", + "origin": "hackage", + "synopsis": "Privacy Enhanced Mail (PEM) format reader and writer.", + "version": "0.2.4" + }, + { + "name": "percent-format", + "origin": "hackage", + "synopsis": "simple printf-style string formatting", + "version": "0.0.1" + }, + { + "name": "perfect-hash-generator", + "origin": "hackage", + "synopsis": "Perfect minimal hashing implementation in native Haskell", + "version": "0.2.0.6" + }, + { + "name": "perfect-vector-shuffle", + "origin": "hackage", + "synopsis": "Library for performing vector shuffles", + "version": "0.1.1.1" + }, + { + "name": "persist", + "origin": "hackage", + "synopsis": "Minimal serialization library with focus on performance", + "version": "0.1.1.5" + }, + { + "name": "persistable-record", + "origin": "hackage", + "synopsis": "Binding between SQL database values and haskell records.", + "version": "0.6.0.5" + }, + { + "name": "persistable-types-HDBC-pg", + "origin": "hackage", + "synopsis": "HDBC and Relational-Record instances of PostgreSQL extended types", + "version": "0.0.3.5" + }, + { + "name": "persistent", + "origin": "hackage", + "synopsis": "Type-safe, multi-backend data serialization.", + "version": "2.11.0.4" + }, + { + "name": "persistent-documentation", + "origin": "hackage", + "synopsis": "Documentation DSL for persistent entities", + "version": "0.1.0.2" + }, + { + "name": "persistent-mtl", + "origin": "hackage", + "synopsis": "Monad transformer for the persistent API", + "version": "0.2.1.0" + }, + { + "name": "persistent-mysql", + "origin": "hackage", + "synopsis": "Backend for the persistent library using MySQL database server.", + "version": "2.10.3.1" + }, + { + "name": "persistent-pagination", + "origin": "hackage", + "synopsis": "Efficient and correct pagination for persistent or esqueleto queries.", + "version": "0.1.1.2" + }, + { + "name": "persistent-postgresql", + "origin": "hackage", + "synopsis": "Backend for the persistent library using postgresql.", + "version": "2.11.0.1" + }, + { + "name": "persistent-qq", + "origin": "hackage", + "synopsis": "Provides a quasi-quoter for raw SQL for persistent", + "version": "2.9.2.1" + }, + { + "name": "persistent-sqlite", + "origin": "hackage", + "synopsis": "Backend for the persistent library using sqlite3.", + "version": "2.11.1.0" + }, + { + "name": "persistent-template", + "origin": "hackage", + "synopsis": "Type-safe, non-relational, multi-backend persistence.", + "version": "2.9.1.0" + }, + { + "name": "persistent-test", + "origin": "hackage", + "synopsis": "Tests for Persistent", + "version": "2.0.3.5" + }, + { + "name": "persistent-typed-db", + "origin": "hackage", + "synopsis": "Type safe access to multiple database schemata.", + "version": "0.1.0.2" + }, + { + "name": "pg-harness-client", + "origin": "hackage", + "synopsis": "Client library for pg-harness-server", + "version": "0.6.0" + }, + { + "name": "pgp-wordlist", + "origin": "hackage", + "synopsis": "Translate between binary data and a human-readable\ncollection of words.", + "version": "0.1.0.3" + }, + { + "name": "pg-transact", + "origin": "hackage", + "synopsis": "A postgresql-simple transaction monad", + "version": "0.3.1.1" + }, + { + "name": "phantom-state", + "origin": "hackage", + "synopsis": "Phantom State Transformer. Like State Monad, but without values.", + "version": "0.2.1.2" + }, + { + "name": "pid1", + "origin": "hackage", + "synopsis": "Do signal handling and orphan reaping for Unix PID1 init processes", + "version": "0.1.2.0" + }, + { + "name": "pinboard", + "origin": "hackage", + "synopsis": "Access to the Pinboard API", + "version": "0.10.2.0" + }, + { + "name": "pipes", + "origin": "hackage", + "synopsis": "Compositional pipelines", + "version": "4.3.15" + }, + { + "name": "pipes-aeson", + "origin": "hackage", + "synopsis": "Encode and decode JSON streams using Aeson and Pipes.", + "version": "0.4.1.8" + }, + { + "name": "pipes-attoparsec", + "origin": "hackage", + "synopsis": "Attoparsec and Pipes integration.", + "version": "0.5.1.5" + }, + { + "name": "pipes-binary", + "origin": "hackage", + "synopsis": "Encode and decode binary streams using the pipes and binary libraries.", + "version": "0.4.2" + }, + { + "name": "pipes-bytestring", + "origin": "hackage", + "synopsis": "ByteString support for pipes", + "version": "2.1.7" + }, + { + "name": "pipes-concurrency", + "origin": "hackage", + "synopsis": "Concurrency for the pipes ecosystem", + "version": "2.0.12" + }, + { + "name": "pipes-csv", + "origin": "hackage", + "synopsis": "Fast, streaming csv parser", + "version": "1.4.3" + }, + { + "name": "pipes-extras", + "origin": "hackage", + "synopsis": "Extra utilities for pipes", + "version": "1.0.15" + }, + { + "name": "pipes-fastx", + "origin": "hackage", + "synopsis": "Streaming parsers for Fasta and Fastq", + "version": "0.3.0.0" + }, + { + "name": "pipes-group", + "origin": "hackage", + "synopsis": "Group streams into substreams", + "version": "1.0.12" + }, + { + "name": "pipes-http", + "origin": "hackage", + "synopsis": "HTTP client with pipes interface", + "version": "1.0.6" + }, + { + "name": "pipes-network", + "origin": "hackage", + "synopsis": "Use network sockets together with the pipes library.", + "version": "0.6.5" + }, + { + "name": "pipes-network-tls", + "origin": "hackage", + "synopsis": "TLS-secured network connections support for pipes.", + "version": "0.4" + }, + { + "name": "pipes-ordered-zip", + "origin": "hackage", + "synopsis": "merge two ordered Producers into a new Producer", + "version": "1.1.0" + }, + { + "name": "pipes-parse", + "origin": "hackage", + "synopsis": "Parsing infrastructure for the pipes ecosystem", + "version": "3.0.9" + }, + { + "name": "pipes-random", + "origin": "hackage", + "synopsis": "Producers for handling randomness.", + "version": "1.0.0.5" + }, + { + "name": "pipes-safe", + "origin": "hackage", + "synopsis": "Safety for the pipes ecosystem", + "version": "2.3.3" + }, + { + "name": "pipes-wai", + "origin": "hackage", + "synopsis": "A port of wai-conduit for the pipes ecosystem", + "version": "3.2.0" + }, + { + "name": "pkcs10", + "origin": "hackage", + "synopsis": "PKCS#10 library", + "version": "0.2.0.0" + }, + { + "name": "pkgtreediff", + "origin": "hackage", + "synopsis": "Package tree diff tool", + "version": "0.4.1" + }, + { + "name": "placeholders", + "origin": "hackage", + "synopsis": "Placeholders for use while developing Haskell code", + "version": "0.1" + }, + { + "name": "plaid", + "origin": "hackage", + "synopsis": "Plaid.com api integration library", + "version": "0.1.0.4" + }, + { + "name": "plotlyhs", + "origin": "hackage", + "synopsis": "Haskell bindings to Plotly.js", + "version": "0.2.1" + }, + { + "name": "pointed", + "origin": "hackage", + "synopsis": "Pointed and copointed data", + "version": "5.0.2" + }, + { + "name": "pointedlist", + "origin": "hackage", + "synopsis": "A zipper-like comonad which works as a list, tracking a position.", + "version": "0.6.1" + }, + { + "name": "pointless-fun", + "origin": "hackage", + "synopsis": "Some common point-free combinators.", + "version": "1.1.0.6" + }, + { + "name": "poll", + "origin": "hackage", + "synopsis": "Bindings to poll.h", + "version": "0.0.0.2" + }, + { + "name": "poly", + "origin": "hackage", + "synopsis": "Polynomials", + "version": "0.5.0.0" + }, + { + "name": "poly-arity", + "origin": "hackage", + "synopsis": "Tools for working with functions of undetermined arity", + "version": "0.1.0" + }, + { + "name": "polynomials-bernstein", + "origin": "hackage", + "synopsis": "A solver for systems of polynomial equations in bernstein form", + "version": "1.1.2" + }, + { + "name": "polyparse", + "origin": "hackage", + "synopsis": "A variety of alternative parser combinator libraries.", + "version": "1.13" + }, + { + "name": "pooled-io", + "origin": "hackage", + "synopsis": "Run jobs on a limited number of threads and support data dependencies", + "version": "0.0.2.2" + }, + { + "name": "port-utils", + "origin": "hackage", + "synopsis": "Utilities for creating and waiting on ports", + "version": "0.2.1.0" + }, + { + "name": "posix-paths", + "origin": "hackage", + "synopsis": "POSIX filepath/directory functionality", + "version": "0.2.1.6" + }, + { + "name": "possibly", + "origin": "hackage", + "synopsis": "type Possibly a = Either String a", + "version": "1.0.0.0" + }, + { + "name": "postgres-options", + "origin": "hackage", + "synopsis": "An Options type representing options for postgres connections", + "version": "0.2.0.0" + }, + { + "name": "postgresql-binary", + "origin": "hackage", + "synopsis": "Encoders and decoders for the PostgreSQL's binary format", + "version": "0.12.4" + }, + { + "name": "postgresql-libpq", + "origin": "hackage", + "synopsis": "low-level binding to libpq", + "version": "0.9.4.3" + }, + { + "name": "postgresql-libpq-notify", + "origin": "hackage", + "synopsis": "Minimal dependency PostgreSQL notifications library", + "version": "0.2.0.0" + }, + { + "name": "postgresql-orm", + "origin": "hackage", + "synopsis": "An ORM (Object Relational Mapping) and migrations DSL for PostgreSQL.", + "version": "0.5.1" + }, + { + "name": "postgresql-simple", + "origin": "hackage", + "synopsis": "Mid-Level PostgreSQL client library", + "version": "0.6.4" + }, + { + "name": "postgresql-typed", + "origin": "hackage", + "synopsis": "PostgreSQL interface with compile-time SQL type checking, optional HDBC backend", + "version": "0.6.2.0" + }, + { + "name": "postgrest", + "origin": "hackage", + "synopsis": "REST API for any Postgres database", + "version": "7.0.1" + }, + { + "name": "post-mess-age", + "origin": "hackage", + "synopsis": "Send messages to a handle concurrently without getting them mixed.", + "version": "0.2.1.0" + }, + { + "name": "pptable", + "origin": "hackage", + "synopsis": "Pretty Print containers in a tabular format", + "version": "0.3.0.0" + }, + { + "name": "pqueue", + "origin": "hackage", + "synopsis": "Reliable, persistent, fast priority queues.", + "version": "1.4.1.3" + }, + { + "name": "prairie", + "origin": "hackage", + "synopsis": "A first class record field library", + "version": "0.0.1.0" + }, + { + "name": "prefix-units", + "origin": "hackage", + "synopsis": "A basic library for SI/binary prefix units", + "version": "0.2.0" + }, + { + "name": "prelude-compat", + "origin": "hackage", + "synopsis": "Provide Prelude and Data.List with fixed content across GHC versions", + "version": "0.0.0.2" + }, + { + "name": "prelude-safeenum", + "origin": "hackage", + "synopsis": "A redefinition of the Prelude's Enum class in order to render it safe.", + "version": "0.1.1.2" + }, + { + "name": "pretty", + "origin": "core", + "synopsis": "Pretty-printing library", + "version": "1.1.3.6" + }, + { + "name": "pretty-class", + "origin": "hackage", + "synopsis": "Pretty printing class similar to Show.", + "version": "1.0.1.1" + }, + { + "name": "prettyclass", + "origin": "hackage", + "synopsis": "Pretty printing class similar to Show.", + "version": "1.0.0.0" + }, + { + "name": "pretty-diff", + "origin": "hackage", + "synopsis": "Pretty printing a diff of two values.", + "version": "0.2.0.3" + }, + { + "name": "pretty-hex", + "origin": "hackage", + "synopsis": "A library for hex dumps of ByteStrings", + "version": "1.1" + }, + { + "name": "prettyprinter", + "origin": "hackage", + "synopsis": "A modern, easy to use, well-documented, extensible pretty-printer.", + "version": "1.7.0" + }, + { + "name": "prettyprinter-ansi-terminal", + "origin": "hackage", + "synopsis": "ANSI terminal backend for the »prettyprinter« package.", + "version": "1.1.2" + }, + { + "name": "prettyprinter-compat-annotated-wl-pprint", + "origin": "hackage", + "synopsis": "Drop-in compatibility package to migrate from »annotated-wl-pprint« to »prettyprinter«.", + "version": "1.1" + }, + { + "name": "prettyprinter-compat-ansi-wl-pprint", + "origin": "hackage", + "synopsis": "Drop-in compatibility package to migrate from »ansi-wl-pprint« to »prettyprinter«.", + "version": "1.0.1" + }, + { + "name": "prettyprinter-compat-wl-pprint", + "origin": "hackage", + "synopsis": "Prettyprinter compatibility module for previous users of the wl-pprint package.", + "version": "1.0.0.1" + }, + { + "name": "prettyprinter-convert-ansi-wl-pprint", + "origin": "hackage", + "synopsis": "Converter from »ansi-wl-pprint« documents to »prettyprinter«-based ones.", + "version": "1.1.1" + }, + { + "name": "pretty-relative-time", + "origin": "hackage", + "synopsis": "Pretty relative time", + "version": "0.2.0.0" + }, + { + "name": "pretty-show", + "origin": "hackage", + "synopsis": "Tools for working with derived `Show` instances and generic\ninspection of values.", + "version": "1.10" + }, + { + "name": "pretty-simple", + "origin": "hackage", + "synopsis": "pretty printer for data types with a 'Show' instance.", + "version": "4.0.0.0" + }, + { + "name": "pretty-sop", + "origin": "hackage", + "synopsis": "A generic pretty-printer using generics-sop", + "version": "0.2.0.3" + }, + { + "name": "pretty-terminal", + "origin": "hackage", + "synopsis": "Styling and coloring terminal output with ANSI escape sequences.", + "version": "0.1.0.0" + }, + { + "name": "primes", + "origin": "hackage", + "synopsis": "Efficient, purely functional generation of prime numbers", + "version": "0.2.1.0" + }, + { + "name": "primitive", + "origin": "hackage", + "synopsis": "Primitive memory-related operations", + "version": "0.7.1.0" + }, + { + "name": "primitive-addr", + "origin": "hackage", + "synopsis": "Addresses to unmanaged memory", + "version": "0.1.0.2" + }, + { + "name": "primitive-extras", + "origin": "hackage", + "synopsis": "Extras for the \"primitive\" library", + "version": "0.8.2" + }, + { + "name": "primitive-unaligned", + "origin": "hackage", + "synopsis": "Unaligned access to primitive arrays", + "version": "0.1.1.1" + }, + { + "name": "primitive-unlifted", + "origin": "hackage", + "synopsis": "Primitive GHC types with unlifted types inside", + "version": "0.1.3.0" + }, + { + "name": "print-console-colors", + "origin": "hackage", + "synopsis": "Print all ANSI console colors", + "version": "0.1.0.0" + }, + { + "name": "probability", + "origin": "hackage", + "synopsis": "Probabilistic Functional Programming", + "version": "0.2.7" + }, + { + "name": "process", + "origin": "core", + "synopsis": "Process libraries", + "version": "1.6.9.0" + }, + { + "name": "process-extras", + "origin": "hackage", + "synopsis": "Process extras", + "version": "0.7.4" + }, + { + "name": "product-isomorphic", + "origin": "hackage", + "synopsis": "Weaken applicative functor on products", + "version": "0.0.3.3" + }, + { + "name": "product-profunctors", + "origin": "hackage", + "synopsis": "product-profunctors", + "version": "0.11.0.2" + }, + { + "name": "profiterole", + "origin": "hackage", + "synopsis": "Restructure GHC profile reports", + "version": "0.1" + }, + { + "name": "profunctors", + "origin": "hackage", + "synopsis": "Profunctors", + "version": "5.5.2" + }, + { + "name": "projectroot", + "origin": "hackage", + "synopsis": "Bindings to the projectroot C logic", + "version": "0.2.0.1" + }, + { + "name": "project-template", + "origin": "hackage", + "synopsis": "Specify Haskell project templates and generate files", + "version": "0.2.1.0" + }, + { + "name": "prometheus", + "origin": "hackage", + "synopsis": "Prometheus Haskell Client", + "version": "2.2.2" + }, + { + "name": "prometheus-client", + "origin": "hackage", + "synopsis": "Haskell client library for http://prometheus.io.", + "version": "1.0.1" + }, + { + "name": "prometheus-wai-middleware", + "origin": "hackage", + "synopsis": "Instrument a wai application with various metrics", + "version": "1.0.1.0" + }, + { + "name": "promises", + "origin": "hackage", + "synopsis": "Lazy demand-driven promises", + "version": "0.3" + }, + { + "name": "prompt", + "origin": "hackage", + "synopsis": "Monad (and transformer) for deferred-effect pure\nprompt-response queries", + "version": "0.1.1.2" + }, + { + "name": "prospect", + "origin": "hackage", + "synopsis": "Explore continuations with trepidation", + "version": "0.1.0.0" + }, + { + "name": "proto3-wire", + "origin": "hackage", + "synopsis": "A low-level implementation of the Protocol Buffers (version 3) wire format", + "version": "1.1.0" + }, + { + "name": "protobuf", + "origin": "hackage", + "synopsis": "Google Protocol Buffers via GHC.Generics", + "version": "0.2.1.3" + }, + { + "name": "protobuf-simple", + "origin": "hackage", + "synopsis": "Simple Protocol Buffers library (proto2)", + "version": "0.1.1.0" + }, + { + "name": "protocol-buffers", + "origin": "hackage", + "synopsis": "Parse Google Protocol Buffer specifications", + "version": "2.4.17" + }, + { + "name": "protocol-buffers-descriptor", + "origin": "hackage", + "synopsis": "Text.DescriptorProto.Options and code generated from the Google Protocol Buffer specification", + "version": "2.4.17" + }, + { + "name": "protocol-radius", + "origin": "hackage", + "synopsis": "parser and printer for radius protocol packet", + "version": "0.0.1.1" + }, + { + "name": "protocol-radius-test", + "origin": "hackage", + "synopsis": "testsuit of protocol-radius haskell package", + "version": "0.1.0.1" + }, + { + "name": "proto-lens", + "origin": "hackage", + "synopsis": "A lens-based implementation of protocol buffers in Haskell.", + "version": "0.7.0.0" + }, + { + "name": "proto-lens-optparse", + "origin": "hackage", + "synopsis": "Adapting proto-lens to optparse-applicative ReadMs.", + "version": "0.1.1.7" + }, + { + "name": "proto-lens-protobuf-types", + "origin": "hackage", + "synopsis": "Basic protocol buffer message types.", + "version": "0.7.0.0" + }, + { + "name": "proto-lens-protoc", + "origin": "hackage", + "synopsis": "Protocol buffer compiler for the proto-lens library.", + "version": "0.7.0.0" + }, + { + "name": "proto-lens-runtime", + "origin": "hackage", + "synopsis": "", + "version": "0.7.0.0" + }, + { + "name": "proto-lens-setup", + "origin": "hackage", + "synopsis": "Cabal support for codegen with proto-lens.", + "version": "0.4.0.4" + }, + { + "name": "protolude", + "origin": "hackage", + "synopsis": "A small prelude.", + "version": "0.3.0" + }, + { + "name": "proxied", + "origin": "hackage", + "synopsis": "Make functions consume Proxy instead of undefined", + "version": "0.3.1" + }, + { + "name": "psqueues", + "origin": "hackage", + "synopsis": "Pure priority search queues", + "version": "0.2.7.2" + }, + { + "name": "publicsuffix", + "origin": "hackage", + "synopsis": "The publicsuffix list exposed as proper Haskell types", + "version": "0.20200526" + }, + { + "name": "pulse-simple", + "origin": "hackage", + "synopsis": "binding to Simple API of pulseaudio", + "version": "0.1.14" + }, + { + "name": "pureMD5", + "origin": "hackage", + "synopsis": "A Haskell-only implementation of the MD5 digest (hash) algorithm.", + "version": "2.1.3" + }, + { + "name": "purescript-bridge", + "origin": "hackage", + "synopsis": "Generate PureScript data types from Haskell data types", + "version": "0.14.0.0" + }, + { + "name": "pushbullet-types", + "origin": "hackage", + "synopsis": "Datatypes used by the Pushbullet APIs", + "version": "0.4.1.0" + }, + { + "name": "pusher-http-haskell", + "origin": "hackage", + "synopsis": "Haskell client library for the Pusher Channels HTTP API", + "version": "2.0.0.3" + }, + { + "name": "pvar", + "origin": "hackage", + "synopsis": "Mutable variable with primitive values", + "version": "1.0.0.0" + }, + { + "name": "PyF", + "origin": "hackage", + "synopsis": "Quasiquotations for a python like interpolated string formater", + "version": "0.9.0.3" + }, + { + "name": "qchas", + "origin": "hackage", + "synopsis": "A library for implementing Quantum Algorithms", + "version": "1.1.0.1" + }, + { + "name": "qm-interpolated-string", + "origin": "hackage", + "synopsis": "Implementation of interpolated multiline strings", + "version": "0.3.0.0" + }, + { + "name": "qrcode-core", + "origin": "hackage", + "synopsis": "QR code library in pure Haskell", + "version": "0.9.4" + }, + { + "name": "qrcode-juicypixels", + "origin": "hackage", + "synopsis": "Converts a qrcode-core image to JuicyPixels", + "version": "0.8.2" + }, + { + "name": "quadratic-irrational", + "origin": "hackage", + "synopsis": "An implementation of quadratic irrationals", + "version": "0.1.1" + }, + { + "name": "QuasiText", + "origin": "hackage", + "synopsis": "A QuasiQuoter for Text.", + "version": "0.1.2.6" + }, + { + "name": "QuickCheck", + "origin": "hackage", + "synopsis": "Automatic testing of Haskell programs", + "version": "2.14.2" + }, + { + "name": "quickcheck-arbitrary-adt", + "origin": "hackage", + "synopsis": "Generic typeclasses for generating arbitrary ADTs", + "version": "0.3.1.0" + }, + { + "name": "quickcheck-assertions", + "origin": "hackage", + "synopsis": "HUnit like assertions for QuickCheck", + "version": "0.3.0" + }, + { + "name": "quickcheck-classes", + "origin": "hackage", + "synopsis": "QuickCheck common typeclasses", + "version": "0.6.5.0" + }, + { + "name": "quickcheck-classes-base", + "origin": "hackage", + "synopsis": "QuickCheck common typeclasses from `base`", + "version": "0.6.2.0" + }, + { + "name": "quickcheck-higherorder", + "origin": "hackage", + "synopsis": "QuickCheck extension for higher-order properties", + "version": "0.1.0.0" + }, + { + "name": "quickcheck-instances", + "origin": "hackage", + "synopsis": "Common quickcheck instances", + "version": "0.3.25.2" + }, + { + "name": "quickcheck-io", + "origin": "hackage", + "synopsis": "Use HUnit assertions as QuickCheck properties", + "version": "0.2.0" + }, + { + "name": "quickcheck-simple", + "origin": "hackage", + "synopsis": "Test properties and default-mains for QuickCheck", + "version": "0.1.1.1" + }, + { + "name": "quickcheck-special", + "origin": "hackage", + "synopsis": "Edge cases and special values for QuickCheck Arbitrary instances", + "version": "0.1.0.6" + }, + { + "name": "quickcheck-text", + "origin": "hackage", + "synopsis": "Alternative arbitrary instance for Text", + "version": "0.1.2.1" + }, + { + "name": "quickcheck-transformer", + "origin": "hackage", + "synopsis": "A GenT monad transformer for QuickCheck library.", + "version": "0.3.1.1" + }, + { + "name": "quickcheck-unicode", + "origin": "hackage", + "synopsis": "Generator and shrink functions for testing\nUnicode-related software.", + "version": "1.0.1.0" + }, + { + "name": "quiet", + "origin": "hackage", + "synopsis": "Generic deriving of Read/Show with no record labels.", + "version": "0.2" + }, + { + "name": "quote-quot", + "origin": "hackage", + "synopsis": "Divide without division", + "version": "0.1.0.0" + }, + { + "name": "radius", + "origin": "hackage", + "synopsis": "Remote Authentication Dial In User Service (RADIUS)", + "version": "0.7.1.0" + }, + { + "name": "rainbow", + "origin": "hackage", + "synopsis": "Print text to terminal with colors and effects", + "version": "0.34.2.2" + }, + { + "name": "rainbox", + "origin": "hackage", + "synopsis": "Two-dimensional box pretty printing, with colors", + "version": "0.26.0.0" + }, + { + "name": "ral", + "origin": "hackage", + "synopsis": "Random access lists", + "version": "0.1" + }, + { + "name": "rampart", + "origin": "hackage", + "synopsis": "Determine how intervals relate to each other.", + "version": "1.1.0.2" + }, + { + "name": "ramus", + "origin": "hackage", + "synopsis": "Elm signal system for Haskell", + "version": "0.1.2" + }, + { + "name": "rando", + "origin": "hackage", + "synopsis": "Easy-to-use randomness for livecoding", + "version": "0.0.0.4" + }, + { + "name": "random", + "origin": "hackage", + "synopsis": "random number library", + "version": "1.1" + }, + { + "name": "random-bytestring", + "origin": "hackage", + "synopsis": "Efficient generation of random bytestrings", + "version": "0.1.4" + }, + { + "name": "random-fu", + "origin": "hackage", + "synopsis": "Random number generation", + "version": "0.2.7.4" + }, + { + "name": "random-shuffle", + "origin": "hackage", + "synopsis": "Random shuffle implementation.", + "version": "0.0.4" + }, + { + "name": "random-source", + "origin": "hackage", + "synopsis": "Generic basis for random number generators", + "version": "0.3.0.8" + }, + { + "name": "random-tree", + "origin": "hackage", + "synopsis": "Create random trees", + "version": "0.6.0.5" + }, + { + "name": "range", + "origin": "hackage", + "synopsis": "An efficient and versatile range library.", + "version": "0.3.0.2" + }, + { + "name": "Ranged-sets", + "origin": "hackage", + "synopsis": "Ranged sets for Haskell", + "version": "0.4.0" + }, + { + "name": "range-set-list", + "origin": "hackage", + "synopsis": "Memory efficient sets with ranges of elements.", + "version": "0.1.3.1" + }, + { + "name": "rank1dynamic", + "origin": "hackage", + "synopsis": "Like Data.Dynamic/Data.Typeable but with support for rank-1 polymorphic types", + "version": "0.4.1" + }, + { + "name": "rank2classes", + "origin": "hackage", + "synopsis": "standard type constructor class hierarchy, only with methods of rank 2 types", + "version": "1.4.1" + }, + { + "name": "Rasterific", + "origin": "hackage", + "synopsis": "A pure haskell drawing engine.", + "version": "0.7.5.3" + }, + { + "name": "rasterific-svg", + "origin": "hackage", + "synopsis": "SVG renderer based on Rasterific.", + "version": "0.3.3.2" + }, + { + "name": "ratel", + "origin": "hackage", + "synopsis": "Notify Honeybadger about exceptions.", + "version": "1.0.14" + }, + { + "name": "rate-limit", + "origin": "hackage", + "synopsis": "A basic library for rate-limiting IO actions.", + "version": "1.4.2" + }, + { + "name": "ratel-wai", + "origin": "hackage", + "synopsis": "Notify Honeybadger about exceptions via a WAI middleware.", + "version": "1.1.5" + }, + { + "name": "rattle", + "origin": "hackage", + "synopsis": "Forward build system, with caching and speculation", + "version": "0.2" + }, + { + "name": "rawfilepath", + "origin": "hackage", + "synopsis": "Use RawFilePath instead of FilePath", + "version": "0.2.4" + }, + { + "name": "rawstring-qm", + "origin": "hackage", + "synopsis": "Simple raw string quotation and dictionary interpolation", + "version": "0.2.3.0" + }, + { + "name": "raw-strings-qq", + "origin": "hackage", + "synopsis": "Raw string literals for Haskell.", + "version": "1.1" + }, + { + "name": "rcu", + "origin": "hackage", + "synopsis": "Read-Copy-Update for Haskell", + "version": "0.2.5" + }, + { + "name": "rdf", + "origin": "hackage", + "synopsis": "Representation and Incremental Processing of RDF Data", + "version": "0.1.0.5" + }, + { + "name": "rdtsc", + "origin": "hackage", + "synopsis": "Binding for the rdtsc machine instruction", + "version": "1.3.0.1" + }, + { + "name": "re2", + "origin": "hackage", + "synopsis": "Bindings to the re2 regular expression library", + "version": "0.3" + }, + { + "name": "readable", + "origin": "hackage", + "synopsis": "Reading from Text and ByteString", + "version": "0.3.1" + }, + { + "name": "read-editor", + "origin": "hackage", + "synopsis": "Opens a temporary file on the system's EDITOR and returns the resulting edits", + "version": "0.1.0.2" + }, + { + "name": "read-env-var", + "origin": "hackage", + "synopsis": "Functions for safely reading environment variables.", + "version": "1.0.0.0" + }, + { + "name": "reanimate", + "origin": "hackage", + "synopsis": "Animation library based on SVGs.", + "version": "1.1.4.0" + }, + { + "name": "reanimate-svg", + "origin": "hackage", + "synopsis": "SVG file loader and serializer", + "version": "0.13.0.1" + }, + { + "name": "rebase", + "origin": "hackage", + "synopsis": "A more progressive alternative to the \"base\" package", + "version": "1.6.1" + }, + { + "name": "record-dot-preprocessor", + "origin": "hackage", + "synopsis": "Preprocessor to allow record.field syntax", + "version": "0.2.10" + }, + { + "name": "record-hasfield", + "origin": "hackage", + "synopsis": "A version of GHC.Records as available in future GHCs.", + "version": "1.0" + }, + { + "name": "records-sop", + "origin": "hackage", + "synopsis": "Record subtyping and record utilities with generics-sop", + "version": "0.1.1.0" + }, + { + "name": "record-wrangler", + "origin": "hackage", + "synopsis": "Alter your records with ease", + "version": "0.1.1.0" + }, + { + "name": "recursion-schemes", + "origin": "hackage", + "synopsis": "Representing common recursion patterns as higher-order functions", + "version": "5.2.2.1" + }, + { + "name": "reducers", + "origin": "hackage", + "synopsis": "Semigroups, specialized containers and a general map/reduce framework", + "version": "3.12.3" + }, + { + "name": "refact", + "origin": "hackage", + "synopsis": "Specify refactorings to perform with apply-refact", + "version": "0.3.0.2" + }, + { + "name": "ref-fd", + "origin": "hackage", + "synopsis": "A type class for monads with references using functional\ndependencies.", + "version": "0.4.0.2" + }, + { + "name": "refined", + "origin": "hackage", + "synopsis": "Refinement types with static and runtime checking", + "version": "0.6.2" + }, + { + "name": "reflection", + "origin": "hackage", + "synopsis": "Reifies arbitrary terms into types that can be reflected back into terms", + "version": "2.1.6" + }, + { + "name": "reform", + "origin": "hackage", + "synopsis": "reform is a type-safe HTML form generation and validation library", + "version": "0.2.7.4" + }, + { + "name": "reform-blaze", + "origin": "hackage", + "synopsis": "Add support for using blaze-html with Reform", + "version": "0.2.4.3" + }, + { + "name": "reform-hamlet", + "origin": "hackage", + "synopsis": "Add support for using Hamlet with Reform", + "version": "0.0.5.3" + }, + { + "name": "reform-happstack", + "origin": "hackage", + "synopsis": "Happstack support for reform.", + "version": "0.2.5.4" + }, + { + "name": "RefSerialize", + "origin": "hackage", + "synopsis": "Write to and read from ByteStrings maintaining internal memory references", + "version": "0.4.0" + }, + { + "name": "ref-tf", + "origin": "hackage", + "synopsis": "A type class for monads with references using type families.", + "version": "0.4.0.2" + }, + { + "name": "regex", + "origin": "hackage", + "synopsis": "Toolkit for regex-base", + "version": "1.1.0.0" + }, + { + "name": "regex-applicative", + "origin": "hackage", + "synopsis": "Regex-based parsing with applicative interface", + "version": "0.3.4" + }, + { + "name": "regex-applicative-text", + "origin": "hackage", + "synopsis": "regex-applicative on text", + "version": "0.1.0.1" + }, + { + "name": "regex-base", + "origin": "hackage", + "synopsis": "Common \"Text.Regex.*\" API for Regex matching", + "version": "0.94.0.1" + }, + { + "name": "regex-compat", + "origin": "hackage", + "synopsis": "Replaces/enhances \"Text.Regex\"", + "version": "0.95.2.1" + }, + { + "name": "regex-compat-tdfa", + "origin": "hackage", + "synopsis": "Unicode Support version of Text.Regex, using regex-tdfa", + "version": "0.95.1.4" + }, + { + "name": "regex-pcre", + "origin": "hackage", + "synopsis": "PCRE Backend for \"Text.Regex\" (regex-base)", + "version": "0.95.0.0" + }, + { + "name": "regex-pcre-builtin", + "origin": "hackage", + "synopsis": "PCRE Backend for \"Text.Regex\" (regex-base)", + "version": "0.95.2.3.8.43" + }, + { + "name": "regex-posix", + "origin": "hackage", + "synopsis": "POSIX Backend for \"Text.Regex\" (regex-base)", + "version": "0.96.0.0" + }, + { + "name": "regex-tdfa", + "origin": "hackage", + "synopsis": "Pure Haskell Tagged DFA Backend for \"Text.Regex\" (regex-base)", + "version": "1.3.1.0" + }, + { + "name": "regex-with-pcre", + "origin": "hackage", + "synopsis": "Toolkit for regex-base", + "version": "1.1.0.0" + }, + { + "name": "registry", + "origin": "hackage", + "synopsis": "data structure for assembling components", + "version": "0.2.0.3" + }, + { + "name": "reinterpret-cast", + "origin": "hackage", + "synopsis": "Memory reinterpretation casts for Float/Double and Word32/Word64", + "version": "0.1.0" + }, + { + "name": "relapse", + "origin": "hackage", + "synopsis": "Sensible RLP encoding", + "version": "1.0.0.0" + }, + { + "name": "relational-query", + "origin": "hackage", + "synopsis": "Typeful, Modular, Relational, algebraic query engine", + "version": "0.12.2.3" + }, + { + "name": "relational-query-HDBC", + "origin": "hackage", + "synopsis": "HDBC instance of relational-query and typed query interface for HDBC", + "version": "0.7.2.0" + }, + { + "name": "relational-record", + "origin": "hackage", + "synopsis": "Meta package of Relational Record", + "version": "0.2.2.0" + }, + { + "name": "relational-schemas", + "origin": "hackage", + "synopsis": "RDBMSs' schema templates for relational-query", + "version": "0.1.8.0" + }, + { + "name": "reliable-io", + "origin": "hackage", + "synopsis": "Bindings to the low-level reliable.io library.", + "version": "0.0.1" + }, + { + "name": "relude", + "origin": "hackage", + "synopsis": "Safe, performant, user-friendly and lightweight Haskell Standard Library", + "version": "0.7.0.0" + }, + { + "name": "renderable", + "origin": "hackage", + "synopsis": "An API for managing renderable resources.", + "version": "0.2.0.1" + }, + { + "name": "replace-attoparsec", + "origin": "hackage", + "synopsis": "Find, replace, and split string patterns with Attoparsec parsers (instead of regex)", + "version": "1.4.4.0" + }, + { + "name": "replace-megaparsec", + "origin": "hackage", + "synopsis": "Find, replace, and split string patterns with Megaparsec parsers (instead of regex)", + "version": "1.4.4.0" + }, + { + "name": "repline", + "origin": "hackage", + "synopsis": "Haskeline wrapper for GHCi-like REPL interfaces.", + "version": "0.4.0.0" + }, + { + "name": "req", + "origin": "hackage", + "synopsis": "Easy-to-use, type-safe, expandable, high-level HTTP client library", + "version": "3.8.0" + }, + { + "name": "req-conduit", + "origin": "hackage", + "synopsis": "Conduit helpers for the req HTTP client library", + "version": "1.0.0" + }, + { + "name": "rerebase", + "origin": "hackage", + "synopsis": "Reexports from \"base\" with a bunch of other standard libraries", + "version": "1.6.1" + }, + { + "name": "resistor-cube", + "origin": "hackage", + "synopsis": "Compute total resistance of a cube of resistors", + "version": "0.0.1.2" + }, + { + "name": "resolv", + "origin": "hackage", + "synopsis": "Domain Name Service (DNS) lookup via the libresolv standard library routines", + "version": "0.1.2.0" + }, + { + "name": "resource-pool", + "origin": "hackage", + "synopsis": "A high-performance striped resource pooling implementation", + "version": "0.2.3.2" + }, + { + "name": "resourcet", + "origin": "hackage", + "synopsis": "Deterministic allocation and freeing of scarce resources.", + "version": "1.2.4.2" + }, + { + "name": "resourcet-pool", + "origin": "hackage", + "synopsis": "A small library to convert a Pool into an Acquire", + "version": "0.1.0.0" + }, + { + "name": "result", + "origin": "hackage", + "synopsis": "Encode success or at least one error", + "version": "0.2.6.0" + }, + { + "name": "rethinkdb-client-driver", + "origin": "hackage", + "synopsis": "Client driver for RethinkDB", + "version": "0.0.25" + }, + { + "name": "retry", + "origin": "hackage", + "synopsis": "Retry combinators for monadic actions that may fail", + "version": "0.8.1.2" + }, + { + "name": "rev-state", + "origin": "hackage", + "synopsis": "Reverse State monad transformer", + "version": "0.1.2" + }, + { + "name": "rfc1751", + "origin": "hackage", + "synopsis": "RFC-1751 library for Haskell", + "version": "0.1.3" + }, + { + "name": "rfc5051", + "origin": "hackage", + "synopsis": "Simple unicode collation as per RFC5051.", + "version": "0.2" + }, + { + "name": "rhine", + "origin": "hackage", + "synopsis": "Functional Reactive Programming with type-level clocks", + "version": "0.7.0" + }, + { + "name": "rhine-gloss", + "origin": "hackage", + "synopsis": "Gloss backend for Rhine", + "version": "0.7.0" + }, + { + "name": "rigel-viz", + "origin": "hackage", + "synopsis": "A mid-level wrapper for vega-lite", + "version": "0.2.0.0" + }, + { + "name": "rio", + "origin": "hackage", + "synopsis": "A standard library for Haskell", + "version": "0.1.20.0" + }, + { + "name": "rio-orphans", + "origin": "hackage", + "synopsis": "Orphan instances for the RIO type in the rio package", + "version": "0.1.2.0" + }, + { + "name": "rio-prettyprint", + "origin": "hackage", + "synopsis": "Pretty-printing for RIO", + "version": "0.1.1.0" + }, + { + "name": "roc-id", + "origin": "hackage", + "synopsis": "Implementation of the ROC National ID standard.", + "version": "0.1.0.0" + }, + { + "name": "rocksdb-haskell", + "origin": "hackage", + "synopsis": "Haskell bindings to RocksDB", + "version": "1.0.1" + }, + { + "name": "rocksdb-haskell-jprupp", + "origin": "hackage", + "synopsis": "Haskell bindings for RocksDB", + "version": "2.1.3" + }, + { + "name": "rocksdb-query", + "origin": "hackage", + "synopsis": "RocksDB database querying library for Haskell", + "version": "0.4.2" + }, + { + "name": "roles", + "origin": "hackage", + "synopsis": "Composable class-based roles", + "version": "0.2.0.0" + }, + { + "name": "rope-utf16-splay", + "origin": "hackage", + "synopsis": "Ropes optimised for updating using UTF-16 code units and\nrow/column pairs.", + "version": "0.3.2.0" + }, + { + "name": "rosezipper", + "origin": "hackage", + "synopsis": "Generic zipper implementation for Data.Tree", + "version": "0.2" + }, + { + "name": "rot13", + "origin": "hackage", + "synopsis": "Fast ROT13 cipher for Haskell.", + "version": "0.2.0.1" + }, + { + "name": "rpmbuild-order", + "origin": "hackage", + "synopsis": "Order RPM packages by dependencies", + "version": "0.4.3.2" + }, + { + "name": "RSA", + "origin": "hackage", + "synopsis": "Implementation of RSA, using the padding schemes of PKCS#1 v2.1.", + "version": "2.4.1" + }, + { + "name": "runmemo", + "origin": "hackage", + "synopsis": "A simple memoization helper library", + "version": "1.0.0.1" + }, + { + "name": "rvar", + "origin": "hackage", + "synopsis": "Random Variables", + "version": "0.2.0.6" + }, + { + "name": "safe", + "origin": "hackage", + "synopsis": "Library of safe (exception free) functions", + "version": "0.3.19" + }, + { + "name": "safe-coloured-text", + "origin": "hackage", + "synopsis": "Safely output coloured text", + "version": "0.0.0.0" + }, + { + "name": "safecopy", + "origin": "hackage", + "synopsis": "Binary serialization with version control.", + "version": "0.10.4.2" + }, + { + "name": "safe-decimal", + "origin": "hackage", + "synopsis": "Safe and very efficient arithmetic operations on fixed decimal point numbers", + "version": "0.2.0.0" + }, + { + "name": "safe-exceptions", + "origin": "hackage", + "synopsis": "Safe, consistent, and easy exception handling", + "version": "0.1.7.1" + }, + { + "name": "safe-foldable", + "origin": "hackage", + "synopsis": "Safe wrappers for null-partial Foldable operations", + "version": "0.1.0.0" + }, + { + "name": "safeio", + "origin": "hackage", + "synopsis": "Write output to disk atomically", + "version": "0.0.5.0" + }, + { + "name": "safe-json", + "origin": "hackage", + "synopsis": "Automatic JSON format versioning", + "version": "1.1.1.1" + }, + { + "name": "safe-money", + "origin": "hackage", + "synopsis": "Type-safe and lossless encoding and manipulation of money, fiat\ncurrencies, crypto currencies and precious metals.", + "version": "0.9" + }, + { + "name": "SafeSemaphore", + "origin": "hackage", + "synopsis": "Much safer replacement for QSemN, QSem, and SampleVar", + "version": "0.10.1" + }, + { + "name": "safe-tensor", + "origin": "hackage", + "synopsis": "Dependently typed tensor algebra", + "version": "0.2.1.1" + }, + { + "name": "salak", + "origin": "hackage", + "synopsis": "Configuration (re)Loader and Parser.", + "version": "0.3.6" + }, + { + "name": "salak-yaml", + "origin": "hackage", + "synopsis": "Configuration Loader for yaml", + "version": "0.3.5.3" + }, + { + "name": "saltine", + "origin": "hackage", + "synopsis": "Cryptography that's easy to digest (NaCl/libsodium bindings).", + "version": "0.1.1.1" + }, + { + "name": "salve", + "origin": "hackage", + "synopsis": "Semantic version numbers and constraints.", + "version": "1.0.11" + }, + { + "name": "sample-frame", + "origin": "hackage", + "synopsis": "Handling of samples in an (audio) signal", + "version": "0.0.3" + }, + { + "name": "sample-frame-np", + "origin": "hackage", + "synopsis": "Orphan instances for types from sample-frame and numericprelude", + "version": "0.0.4.1" + }, + { + "name": "sampling", + "origin": "hackage", + "synopsis": "Sample values from collections.", + "version": "0.3.5" + }, + { + "name": "say", + "origin": "hackage", + "synopsis": "Send textual messages to a Handle in a thread-friendly way", + "version": "0.1.0.1" + }, + { + "name": "sbp", + "origin": "hackage", + "synopsis": "SwiftNav's SBP Library", + "version": "2.6.3" + }, + { + "name": "scalpel", + "origin": "hackage", + "synopsis": "A high level web scraping library for Haskell.", + "version": "0.6.2" + }, + { + "name": "scalpel-core", + "origin": "hackage", + "synopsis": "A high level web scraping library for Haskell.", + "version": "0.6.2" + }, + { + "name": "scanf", + "origin": "hackage", + "synopsis": "Easy and type-safe format strings for parsing and printing", + "version": "0.1.0.0" + }, + { + "name": "scanner", + "origin": "hackage", + "synopsis": "Fast non-backtracking incremental combinator parsing for bytestrings", + "version": "0.3.1" + }, + { + "name": "scheduler", + "origin": "hackage", + "synopsis": "Work stealing scheduler.", + "version": "1.5.0" + }, + { + "name": "scientific", + "origin": "hackage", + "synopsis": "Numbers represented using scientific notation", + "version": "0.3.6.2" + }, + { + "name": "scotty", + "origin": "hackage", + "synopsis": "Haskell web framework inspired by Ruby's Sinatra, using WAI and Warp", + "version": "0.12" + }, + { + "name": "scrypt", + "origin": "hackage", + "synopsis": "Stronger password hashing via sequential memory-hard functions.", + "version": "0.5.0" + }, + { + "name": "sdl2", + "origin": "hackage", + "synopsis": "Both high- and low-level bindings to the SDL library (version 2.0.6+).", + "version": "2.5.3.0" + }, + { + "name": "sdl2-gfx", + "origin": "hackage", + "synopsis": "Bindings to SDL2_gfx.", + "version": "0.2" + }, + { + "name": "sdl2-image", + "origin": "hackage", + "synopsis": "Bindings to SDL2_image.", + "version": "2.0.0" + }, + { + "name": "sdl2-mixer", + "origin": "hackage", + "synopsis": "Bindings to SDL2_mixer.", + "version": "1.1.0" + }, + { + "name": "sdl2-ttf", + "origin": "hackage", + "synopsis": "Bindings to SDL2_ttf.", + "version": "2.1.2" + }, + { + "name": "search-algorithms", + "origin": "hackage", + "synopsis": "Common graph search algorithms", + "version": "0.3.1" + }, + { + "name": "secp256k1-haskell", + "origin": "hackage", + "synopsis": "Bindings for secp256k1", + "version": "0.5.0" + }, + { + "name": "securemem", + "origin": "hackage", + "synopsis": "abstraction to an auto scrubbing and const time eq, memory chunk.", + "version": "0.1.10" + }, + { + "name": "selda", + "origin": "hackage", + "synopsis": "Multi-backend, high-level EDSL for interacting with SQL databases.", + "version": "0.5.1.0" + }, + { + "name": "selda-json", + "origin": "hackage", + "synopsis": "JSON support for the Selda database library.", + "version": "0.1.1.0" + }, + { + "name": "selda-postgresql", + "origin": "hackage", + "synopsis": "PostgreSQL backend for the Selda database EDSL.", + "version": "0.1.8.1" + }, + { + "name": "selda-sqlite", + "origin": "hackage", + "synopsis": "SQLite backend for the Selda database EDSL.", + "version": "0.1.7.1" + }, + { + "name": "selections", + "origin": "hackage", + "synopsis": "Combinators for operating with selections over an underlying functor", + "version": "0.3.0.0" + }, + { + "name": "selective", + "origin": "hackage", + "synopsis": "Selective applicative functors", + "version": "0.4.2" + }, + { + "name": "semialign", + "origin": "hackage", + "synopsis": "Align and Zip type-classes from the common Semialign ancestor.", + "version": "1.1.0.1" + }, + { + "name": "semialign-indexed", + "origin": "hackage", + "synopsis": "SemialignWithIndex, i.e. izipWith and ialignWith", + "version": "1.1" + }, + { + "name": "semialign-optics", + "origin": "hackage", + "synopsis": "SemialignWithIndex, i.e. izipWith and ialignWith", + "version": "1.1" + }, + { + "name": "semigroupoid-extras", + "origin": "hackage", + "synopsis": "Semigroupoids that depend on PolyKinds", + "version": "5" + }, + { + "name": "semigroupoids", + "origin": "hackage", + "synopsis": "Semigroupoids: Category sans id", + "version": "5.3.5" + }, + { + "name": "semigroups", + "origin": "hackage", + "synopsis": "Anything that associates", + "version": "0.19.1" + }, + { + "name": "semirings", + "origin": "hackage", + "synopsis": "two monoids as one, in holy haskimony", + "version": "0.6" + }, + { + "name": "semiring-simple", + "origin": "hackage", + "synopsis": "A module for dealing with semirings.", + "version": "1.0.0.1" + }, + { + "name": "semver", + "origin": "hackage", + "synopsis": "Representation, manipulation, and de/serialisation of Semantic Versions.", + "version": "0.4.0.1" + }, + { + "name": "sendfile", + "origin": "hackage", + "synopsis": "A portable sendfile library", + "version": "0.7.11.1" + }, + { + "name": "seqalign", + "origin": "hackage", + "synopsis": "Sequence Alignment", + "version": "0.2.0.4" + }, + { + "name": "seqid", + "origin": "hackage", + "synopsis": "Sequence ID production and consumption", + "version": "0.6.2" + }, + { + "name": "seqid-streams", + "origin": "hackage", + "synopsis": "Sequence ID IO-Streams", + "version": "0.7.2" + }, + { + "name": "sequence-formats", + "origin": "hackage", + "synopsis": "A package with basic parsing utilities for several Bioinformatic data formats.", + "version": "1.5.2" + }, + { + "name": "sequenceTools", + "origin": "hackage", + "synopsis": "A package with tools for processing DNA sequencing data", + "version": "1.4.0.5" + }, + { + "name": "serf", + "origin": "hackage", + "synopsis": "Interact with Serf via Haskell.", + "version": "0.1.1.0" + }, + { + "name": "serialise", + "origin": "hackage", + "synopsis": "A binary serialisation library for Haskell values.", + "version": "0.2.3.0" + }, + { + "name": "servant", + "origin": "hackage", + "synopsis": "A family of combinators for defining webservices APIs", + "version": "0.18.2" + }, + { + "name": "servant-auth", + "origin": "hackage", + "synopsis": "Authentication combinators for servant", + "version": "0.4.0.0" + }, + { + "name": "servant-auth-client", + "origin": "hackage", + "synopsis": "servant-client/servant-auth compatibility", + "version": "0.4.1.0" + }, + { + "name": "servant-auth-docs", + "origin": "hackage", + "synopsis": "servant-docs/servant-auth compatibility", + "version": "0.2.10.0" + }, + { + "name": "servant-auth-server", + "origin": "hackage", + "synopsis": "servant-server/servant-auth compatibility", + "version": "0.4.6.0" + }, + { + "name": "servant-auth-swagger", + "origin": "hackage", + "synopsis": "servant-swagger/servant-auth compatibility", + "version": "0.2.10.1" + }, + { + "name": "servant-blaze", + "origin": "hackage", + "synopsis": "Blaze-html support for servant", + "version": "0.9.1" + }, + { + "name": "servant-client", + "origin": "hackage", + "synopsis": "Automatic derivation of querying functions for servant", + "version": "0.18.2" + }, + { + "name": "servant-client-core", + "origin": "hackage", + "synopsis": "Core functionality and class for client function generation for servant APIs", + "version": "0.18.2" + }, + { + "name": "servant-conduit", + "origin": "hackage", + "synopsis": "Servant Stream support for conduit.", + "version": "0.15.1" + }, + { + "name": "servant-docs", + "origin": "hackage", + "synopsis": "generate API docs for your servant webservice", + "version": "0.11.8" + }, + { + "name": "servant-elm", + "origin": "hackage", + "synopsis": "Automatically derive Elm functions to query servant webservices.", + "version": "0.7.2" + }, + { + "name": "servant-errors", + "origin": "hackage", + "synopsis": "Servant Errors wai-middlware", + "version": "0.1.6.0" + }, + { + "name": "servant-exceptions", + "origin": "hackage", + "synopsis": "Extensible exceptions for servant APIs", + "version": "0.2.1" + }, + { + "name": "servant-exceptions-server", + "origin": "hackage", + "synopsis": "Extensible exceptions for servant API servers", + "version": "0.2.1" + }, + { + "name": "servant-foreign", + "origin": "hackage", + "synopsis": "Helpers for generating clients for servant APIs in any programming language", + "version": "0.15.3" + }, + { + "name": "servant-github-webhook", + "origin": "hackage", + "synopsis": "Servant combinators to facilitate writing GitHub webhooks.", + "version": "0.4.2.0" + }, + { + "name": "servant-http-streams", + "origin": "hackage", + "synopsis": "Automatic derivation of querying functions for servant", + "version": "0.18.2" + }, + { + "name": "servant-machines", + "origin": "hackage", + "synopsis": "Servant Stream support for machines", + "version": "0.15.1" + }, + { + "name": "servant-multipart", + "origin": "hackage", + "synopsis": "multipart/form-data (e.g file upload) support for servant", + "version": "0.12" + }, + { + "name": "servant-openapi3", + "origin": "hackage", + "synopsis": "Generate a Swagger/OpenAPI/OAS 3.0 specification for your servant API.", + "version": "2.0.1.2" + }, + { + "name": "servant-pipes", + "origin": "hackage", + "synopsis": "Servant Stream support for pipes", + "version": "0.15.2" + }, + { + "name": "servant-rawm", + "origin": "hackage", + "synopsis": "Embed a raw 'Application' in a Servant API", + "version": "1.0.0.0" + }, + { + "name": "servant-server", + "origin": "hackage", + "synopsis": "A family of combinators for defining webservices APIs and serving them", + "version": "0.18.2" + }, + { + "name": "servant-swagger", + "origin": "hackage", + "synopsis": "Generate a Swagger/OpenAPI/OAS 2.0 specification for your servant API.", + "version": "1.1.10" + }, + { + "name": "servant-swagger-ui", + "origin": "hackage", + "synopsis": "Servant swagger ui", + "version": "0.3.5.3.47.1" + }, + { + "name": "servant-swagger-ui-core", + "origin": "hackage", + "synopsis": "Servant swagger ui core components", + "version": "0.3.5" + }, + { + "name": "serverless-haskell", + "origin": "hackage", + "synopsis": "Deploying Haskell code onto AWS Lambda using Serverless", + "version": "0.12.6" + }, + { + "name": "serversession", + "origin": "hackage", + "synopsis": "Secure, modular server-side sessions.", + "version": "1.0.2" + }, + { + "name": "serversession-frontend-wai", + "origin": "hackage", + "synopsis": "wai-session bindings for serversession.", + "version": "1.0" + }, + { + "name": "ses-html", + "origin": "hackage", + "synopsis": "Send HTML formatted emails using Amazon's SES REST API with blaze", + "version": "0.4.0.0" + }, + { + "name": "set-cover", + "origin": "hackage", + "synopsis": "Solve exact set cover problems like Sudoku, 8 Queens, Soma Cube, Tetris Cube", + "version": "0.1.1" + }, + { + "name": "setenv", + "origin": "hackage", + "synopsis": "A cross-platform library for setting environment variables", + "version": "0.1.1.3" + }, + { + "name": "setlocale", + "origin": "hackage", + "synopsis": "Haskell bindings to setlocale", + "version": "1.0.0.10" + }, + { + "name": "sexp-grammar", + "origin": "hackage", + "synopsis": "Invertible grammar combinators for S-expressions", + "version": "2.3.0" + }, + { + "name": "SHA", + "origin": "hackage", + "synopsis": "Implementations of the SHA suite of message digest functions", + "version": "1.6.4.4" + }, + { + "name": "shake", + "origin": "hackage", + "synopsis": "Build system library, like Make, but more accurate dependencies.", + "version": "0.19.4" + }, + { + "name": "shake-plus", + "origin": "hackage", + "synopsis": "Re-export of Shake using well-typed paths and ReaderT.", + "version": "0.3.3.1" + }, + { + "name": "shake-plus-extended", + "origin": "hackage", + "synopsis": "Experimental extensions to shake-plus", + "version": "0.4.1.0" + }, + { + "name": "shakespeare", + "origin": "hackage", + "synopsis": "A toolkit for making compile-time interpolated templates", + "version": "2.0.25" + }, + { + "name": "shared-memory", + "origin": "hackage", + "synopsis": "POSIX shared memory", + "version": "0.2.0.0" + }, + { + "name": "ShellCheck", + "origin": "hackage", + "synopsis": "Shell script analysis tool", + "version": "0.7.2" + }, + { + "name": "shell-conduit", + "origin": "hackage", + "synopsis": "Write shell scripts with Conduit", + "version": "5.0.0" + }, + { + "name": "shell-escape", + "origin": "hackage", + "synopsis": "Shell escaping library.", + "version": "0.2.0" + }, + { + "name": "shellmet", + "origin": "hackage", + "synopsis": "Out of the shell solution for scripting in Haskell", + "version": "0.0.4.0" + }, + { + "name": "shelltestrunner", + "origin": "hackage", + "synopsis": "Easy, repeatable testing of CLI programs/commands", + "version": "1.9" + }, + { + "name": "shell-utility", + "origin": "hackage", + "synopsis": "Utility functions for writing command-line programs", + "version": "0.1" + }, + { + "name": "shelly", + "origin": "hackage", + "synopsis": "shell-like (systems) programming in Haskell", + "version": "1.9.0" + }, + { + "name": "shikensu", + "origin": "hackage", + "synopsis": "Run a sequence of functions on in-memory representations of files", + "version": "0.3.11" + }, + { + "name": "should-not-typecheck", + "origin": "hackage", + "synopsis": "A HUnit/hspec assertion library to verify that an expression does not typecheck", + "version": "2.1.0" + }, + { + "name": "show-combinators", + "origin": "hackage", + "synopsis": "Combinators to write Show instances", + "version": "0.2.0.0" + }, + { + "name": "siggy-chardust", + "origin": "hackage", + "synopsis": "Rounding rationals to significant digits and decimal places.", + "version": "1.0.0" + }, + { + "name": "signal", + "origin": "hackage", + "synopsis": "Multiplatform signal support for Haskell", + "version": "0.1.0.4" + }, + { + "name": "silently", + "origin": "hackage", + "synopsis": "Prevent or capture writing to stdout and other handles.", + "version": "1.2.5.1" + }, + { + "name": "simple-affine-space", + "origin": "hackage", + "synopsis": "A simple library for affine and vector spaces.", + "version": "0.1.1" + }, + { + "name": "simple-cabal", + "origin": "hackage", + "synopsis": "Cabal file wrapper library", + "version": "0.1.3" + }, + { + "name": "simple-cmd", + "origin": "hackage", + "synopsis": "Simple String-based process commands", + "version": "0.2.3" + }, + { + "name": "simple-cmd-args", + "origin": "hackage", + "synopsis": "Simple command args parsing and execution", + "version": "0.1.6" + }, + { + "name": "simple-log", + "origin": "hackage", + "synopsis": "Simple log for Haskell", + "version": "0.9.12" + }, + { + "name": "simple-reflect", + "origin": "hackage", + "synopsis": "Simple reflection of expressions containing variables", + "version": "0.3.3" + }, + { + "name": "simple-sendfile", + "origin": "hackage", + "synopsis": "Cross platform library for the sendfile system call", + "version": "0.2.30" + }, + { + "name": "simple-templates", + "origin": "hackage", + "synopsis": "A basic template language for the Simple web framework", + "version": "1.0.0" + }, + { + "name": "simple-vec3", + "origin": "hackage", + "synopsis": "Three-dimensional vectors of doubles with basic operations", + "version": "0.6.0.1" + }, + { + "name": "simplistic-generics", + "origin": "hackage", + "synopsis": "Generic programming without too many type classes", + "version": "2.0.0" + }, + { + "name": "since", + "origin": "hackage", + "synopsis": "Get the number of seconds since the last invocation", + "version": "0.0.0" + }, + { + "name": "singleton-bool", + "origin": "hackage", + "synopsis": "Type level booleans", + "version": "0.1.5" + }, + { + "name": "singleton-nats", + "origin": "hackage", + "synopsis": "Unary natural numbers relying on the singletons infrastructure.", + "version": "0.4.5" + }, + { + "name": "singletons", + "origin": "hackage", + "synopsis": "A framework for generating singleton types", + "version": "2.7" + }, + { + "name": "singletons-presburger", + "origin": "hackage", + "synopsis": "Presburger Arithmetic Solver for GHC Type-level natural numbers with Singletons package.", + "version": "0.5.0.0" + }, + { + "name": "siphash", + "origin": "hackage", + "synopsis": "siphash: a fast short input PRF", + "version": "1.0.3" + }, + { + "name": "sitemap-gen", + "origin": "hackage", + "synopsis": "Generate XML Sitemaps & Sitemap Indexes", + "version": "0.1.0.0" + }, + { + "name": "sized", + "origin": "hackage", + "synopsis": "Sized sequence data-types", + "version": "1.0.0.0" + }, + { + "name": "skein", + "origin": "hackage", + "synopsis": "Skein, a family of cryptographic hash functions. Includes Skein-MAC as well.", + "version": "1.0.9.4" + }, + { + "name": "skews", + "origin": "hackage", + "synopsis": "A very quick-and-dirty WebSocket server.", + "version": "0.1.0.3" + }, + { + "name": "skip-var", + "origin": "hackage", + "synopsis": "Skip variables", + "version": "0.1.1.0" + }, + { + "name": "skylighting", + "origin": "hackage", + "synopsis": "syntax highlighting library", + "version": "0.10.5.1" + }, + { + "name": "skylighting-core", + "origin": "hackage", + "synopsis": "syntax highlighting library", + "version": "0.10.5.1" + }, + { + "name": "slack-api", + "origin": "hackage", + "synopsis": "Bindings to the Slack RTM API.", + "version": "0.12" + }, + { + "name": "slack-progressbar", + "origin": "hackage", + "synopsis": "", + "version": "0.1.0.1" + }, + { + "name": "slist", + "origin": "hackage", + "synopsis": "Sized list", + "version": "0.1.1.0" + }, + { + "name": "slynx", + "origin": "hackage", + "synopsis": "Handle molecular sequences", + "version": "0.5.0.1" + }, + { + "name": "smallcheck", + "origin": "hackage", + "synopsis": "A property-based testing library", + "version": "1.2.1" + }, + { + "name": "smash", + "origin": "hackage", + "synopsis": "Smash products, wedge products, and pointed products", + "version": "0.1.2" + }, + { + "name": "smash-aeson", + "origin": "hackage", + "synopsis": "Aeson support for the smash library", + "version": "0.1.0.0" + }, + { + "name": "smash-lens", + "origin": "hackage", + "synopsis": "Optics for the `smash` library", + "version": "0.1.0.1" + }, + { + "name": "smash-microlens", + "origin": "hackage", + "synopsis": "Optics for the `smash` library", + "version": "0.1.0.0" + }, + { + "name": "smoothie", + "origin": "hackage", + "synopsis": "Smooth curves via several interpolation modes", + "version": "0.4.2.11" + }, + { + "name": "smtp-mail", + "origin": "hackage", + "synopsis": "Simple email sending via SMTP", + "version": "0.3.0.0" + }, + { + "name": "snap-blaze", + "origin": "hackage", + "synopsis": "blaze-html integration for Snap", + "version": "0.2.1.5" + }, + { + "name": "snap-core", + "origin": "hackage", + "synopsis": "Snap: A Haskell Web Framework (core interfaces and types)", + "version": "1.0.4.2" + }, + { + "name": "snap-server", + "origin": "hackage", + "synopsis": "A web server for the Snap Framework", + "version": "1.1.2.0" + }, + { + "name": "snowflake", + "origin": "hackage", + "synopsis": "A loose port of Twitter Snowflake to Haskell. Generates arbitrary precision, unique, time-sortable identifiers.", + "version": "0.1.1.1" + }, + { + "name": "soap", + "origin": "hackage", + "synopsis": "SOAP client tools", + "version": "0.2.3.6" + }, + { + "name": "soap-openssl", + "origin": "hackage", + "synopsis": "TLS-enabled SOAP transport (using openssl bindings)", + "version": "0.1.0.2" + }, + { + "name": "soap-tls", + "origin": "hackage", + "synopsis": "TLS-enabled SOAP transport (using tls package)", + "version": "0.1.1.4" + }, + { + "name": "socks", + "origin": "hackage", + "synopsis": "Socks proxy (ver 5)", + "version": "0.6.1" + }, + { + "name": "some", + "origin": "hackage", + "synopsis": "Existential type: Some", + "version": "1.0.3" + }, + { + "name": "sop-core", + "origin": "hackage", + "synopsis": "True Sums of Products", + "version": "0.5.0.1" + }, + { + "name": "sort", + "origin": "hackage", + "synopsis": "A Haskell sorting toolkit", + "version": "1.0.0.0" + }, + { + "name": "sorted-list", + "origin": "hackage", + "synopsis": "Type-enforced sorted lists and related functions.", + "version": "0.2.1.0" + }, + { + "name": "sourcemap", + "origin": "hackage", + "synopsis": "Implementation of source maps as proposed by Google and Mozilla.", + "version": "0.1.6" + }, + { + "name": "sox", + "origin": "hackage", + "synopsis": "Play, write, read, convert audio signals using Sox", + "version": "0.2.3.1" + }, + { + "name": "soxlib", + "origin": "hackage", + "synopsis": "Write, read, convert audio signals using libsox", + "version": "0.0.3.1" + }, + { + "name": "sparse-linear-algebra", + "origin": "hackage", + "synopsis": "Numerical computing in native Haskell", + "version": "0.3.1" + }, + { + "name": "sparse-tensor", + "origin": "hackage", + "synopsis": "typesafe tensor algebra library", + "version": "0.2.1.5" + }, + { + "name": "spatial-math", + "origin": "hackage", + "synopsis": "3d math including quaternions/euler angles/dcms and utility functions", + "version": "0.5.0.1" + }, + { + "name": "special-values", + "origin": "hackage", + "synopsis": "Typeclass providing special values", + "version": "0.1.0.0" + }, + { + "name": "speculate", + "origin": "hackage", + "synopsis": "discovery of properties about Haskell functions", + "version": "0.4.6" + }, + { + "name": "speedy-slice", + "origin": "hackage", + "synopsis": "Speedy slice sampling.", + "version": "0.3.2" + }, + { + "name": "Spintax", + "origin": "hackage", + "synopsis": "Random text generation based on spintax", + "version": "0.3.6" + }, + { + "name": "splice", + "origin": "hackage", + "synopsis": "Cross-platform Socket to Socket Data Splicing", + "version": "0.6.1.1" + }, + { + "name": "splint", + "origin": "hackage", + "synopsis": "HLint as a GHC source plugin.", + "version": "1.0.1.4" + }, + { + "name": "split", + "origin": "hackage", + "synopsis": "Combinator library for splitting lists.", + "version": "0.2.3.4" + }, + { + "name": "splitmix", + "origin": "hackage", + "synopsis": "Fast Splittable PRNG", + "version": "0.1.0.3" + }, + { + "name": "spoon", + "origin": "hackage", + "synopsis": "Catch errors thrown from pure computations.", + "version": "0.3.1" + }, + { + "name": "spreadsheet", + "origin": "hackage", + "synopsis": "Read and write spreadsheets from and to CSV files in a lazy way", + "version": "0.1.3.8" + }, + { + "name": "sqlcli", + "origin": "hackage", + "synopsis": "Bindings for SQL/CLI (ODBC) C API.", + "version": "0.2.2.0" + }, + { + "name": "sqlcli-odbc", + "origin": "hackage", + "synopsis": "ODBC specific definitions to be used by SQL CLI clients.", + "version": "0.2.0.1" + }, + { + "name": "sqlite-simple", + "origin": "hackage", + "synopsis": "Mid-Level SQLite client library", + "version": "0.4.18.0" + }, + { + "name": "sql-words", + "origin": "hackage", + "synopsis": "SQL keywords data constructors into OverloadedString", + "version": "0.1.6.4" + }, + { + "name": "squeal-postgresql", + "origin": "hackage", + "synopsis": "Squeal PostgreSQL Library", + "version": "0.7.0.1" + }, + { + "name": "squeather", + "origin": "hackage", + "synopsis": "Use databases with the version 3 series of the SQLite C library", + "version": "0.6.0.0" + }, + { + "name": "srcloc", + "origin": "hackage", + "synopsis": "Data types for managing source code locations.", + "version": "0.5.1.2" + }, + { + "name": "stache", + "origin": "hackage", + "synopsis": "Mustache templates for Haskell", + "version": "2.2.1" + }, + { + "name": "stack", + "origin": "hackage", + "synopsis": "The Haskell Tool Stack", + "version": "2.5.1.1" + }, + { + "name": "stackcollapse-ghc", + "origin": "hackage", + "synopsis": "Program to fold GHC prof files into flamegraph input", + "version": "0.0.1.3" + }, + { + "name": "stack-templatizer", + "origin": "hackage", + "synopsis": "Generate a stack template from a folder.", + "version": "0.1.0.2" + }, + { + "name": "stateref", + "origin": "hackage", + "synopsis": "Abstraction for things that work like IORef.", + "version": "0.3" + }, + { + "name": "StateVar", + "origin": "hackage", + "synopsis": "State variables", + "version": "1.2.1" + }, + { + "name": "static-text", + "origin": "hackage", + "synopsis": "Lists, Texts, ByteStrings and Vectors of statically known length", + "version": "0.2.0.6" + }, + { + "name": "statistics", + "origin": "hackage", + "synopsis": "A library of statistical types, data, and functions", + "version": "0.15.2.0" + }, + { + "name": "status-notifier-item", + "origin": "hackage", + "synopsis": "A wrapper over the StatusNotifierItem/libappindicator dbus specification", + "version": "0.3.0.5" + }, + { + "name": "stb-image-redux", + "origin": "hackage", + "synopsis": "Image loading and writing microlibrary", + "version": "0.2.1.3" + }, + { + "name": "step-function", + "origin": "hackage", + "synopsis": "Staircase functions or piecewise constant functions", + "version": "0.2" + }, + { + "name": "stm", + "origin": "core", + "synopsis": "Software Transactional Memory", + "version": "2.5.0.0" + }, + { + "name": "stm-chans", + "origin": "hackage", + "synopsis": "Additional types of channels for STM.", + "version": "3.0.0.4" + }, + { + "name": "stm-conduit", + "origin": "hackage", + "synopsis": "Introduces conduits to channels, and promotes using conduits concurrently.", + "version": "4.0.1" + }, + { + "name": "stm-containers", + "origin": "hackage", + "synopsis": "Containers for STM", + "version": "1.2" + }, + { + "name": "stm-delay", + "origin": "hackage", + "synopsis": "Updatable one-shot timer polled with STM", + "version": "0.1.1.1" + }, + { + "name": "stm-extras", + "origin": "hackage", + "synopsis": "Extra STM functions", + "version": "0.1.0.3" + }, + { + "name": "stm-hamt", + "origin": "hackage", + "synopsis": "STM-specialised Hash Array Mapped Trie", + "version": "1.2.0.4" + }, + { + "name": "stm-lifted", + "origin": "hackage", + "synopsis": "Software Transactional Memory lifted to MonadIO", + "version": "2.5.0.0" + }, + { + "name": "STMonadTrans", + "origin": "hackage", + "synopsis": "A monad transformer version of the ST monad", + "version": "0.4.5" + }, + { + "name": "stm-split", + "origin": "hackage", + "synopsis": "TMVars, TVars and TChans with distinguished input and output side", + "version": "0.0.2.1" + }, + { + "name": "stopwatch", + "origin": "hackage", + "synopsis": "A simple stopwatch utility", + "version": "0.1.0.6" + }, + { + "name": "storable-complex", + "origin": "hackage", + "synopsis": "Storable instance for Complex", + "version": "0.2.3.0" + }, + { + "name": "storable-endian", + "origin": "hackage", + "synopsis": "Storable instances with endianness", + "version": "0.2.6" + }, + { + "name": "storable-record", + "origin": "hackage", + "synopsis": "Elegant definition of Storable instances for records", + "version": "0.0.5" + }, + { + "name": "storable-tuple", + "origin": "hackage", + "synopsis": "Storable instance for pairs and triples", + "version": "0.0.3.3" + }, + { + "name": "storablevector", + "origin": "hackage", + "synopsis": "Fast, packed, strict storable arrays with a list interface like ByteString", + "version": "0.2.13.1" + }, + { + "name": "store", + "origin": "hackage", + "synopsis": "Fast binary serialization", + "version": "0.7.11" + }, + { + "name": "store-core", + "origin": "hackage", + "synopsis": "Fast and lightweight binary serialization", + "version": "0.4.4.4" + }, + { + "name": "store-streaming", + "origin": "hackage", + "synopsis": "Streaming interfaces for `store`", + "version": "0.2.0.3" + }, + { + "name": "stratosphere", + "origin": "hackage", + "synopsis": "EDSL for AWS CloudFormation", + "version": "0.59.1" + }, + { + "name": "streaming", + "origin": "hackage", + "synopsis": "an elementary streaming prelude and general stream type.", + "version": "0.2.3.0" + }, + { + "name": "streaming-attoparsec", + "origin": "hackage", + "synopsis": "Attoparsec integration for the streaming ecosystem", + "version": "1.0.0.1" + }, + { + "name": "streaming-bytestring", + "origin": "hackage", + "synopsis": "Fast, effectful byte streams.", + "version": "0.2.0" + }, + { + "name": "streaming-commons", + "origin": "hackage", + "synopsis": "Common lower-level functions needed by various streaming data libraries", + "version": "0.2.2.1" + }, + { + "name": "streams", + "origin": "hackage", + "synopsis": "Various Haskell 2010 stream comonads", + "version": "3.3" + }, + { + "name": "strict", + "origin": "hackage", + "synopsis": "Strict data types and String IO.", + "version": "0.4.0.1" + }, + { + "name": "strict-concurrency", + "origin": "hackage", + "synopsis": "Strict concurrency abstractions", + "version": "0.2.4.3" + }, + { + "name": "strict-list", + "origin": "hackage", + "synopsis": "Strict linked list", + "version": "0.1.5" + }, + { + "name": "strict-tuple", + "origin": "hackage", + "synopsis": "Strict tuples", + "version": "0.1.4" + }, + { + "name": "strict-tuple-lens", + "origin": "hackage", + "synopsis": "Optics for the `strict-tuple` library", + "version": "0.1.0.1" + }, + { + "name": "stringbuilder", + "origin": "hackage", + "synopsis": "A writer monad for multi-line string literals", + "version": "0.5.1" + }, + { + "name": "string-class", + "origin": "hackage", + "synopsis": "String class library", + "version": "0.1.7.0" + }, + { + "name": "string-combinators", + "origin": "hackage", + "synopsis": "Polymorphic functions to build and combine stringlike values", + "version": "0.6.0.5" + }, + { + "name": "string-conv", + "origin": "hackage", + "synopsis": "Standardized conversion between string types", + "version": "0.1.2" + }, + { + "name": "string-conversions", + "origin": "hackage", + "synopsis": "Simplifies dealing with different types for strings", + "version": "0.4.0.1" + }, + { + "name": "string-interpolate", + "origin": "hackage", + "synopsis": "Haskell string/text/bytestring interpolation that just works", + "version": "0.3.1.0" + }, + { + "name": "string-qq", + "origin": "hackage", + "synopsis": "QuasiQuoter for non-interpolated strings, texts and bytestrings.", + "version": "0.0.4" + }, + { + "name": "string-random", + "origin": "hackage", + "synopsis": "A library for generating random string from a regular experession", + "version": "0.1.4.1" + }, + { + "name": "stringsearch", + "origin": "hackage", + "synopsis": "Fast searching, splitting and replacing of ByteStrings", + "version": "0.3.6.6" + }, + { + "name": "string-transform", + "origin": "hackage", + "synopsis": "simple and easy haskell string transform wrapper", + "version": "1.1.1" + }, + { + "name": "stripe-concepts", + "origin": "hackage", + "synopsis": "Types for the Stripe API", + "version": "1.0.2.6" + }, + { + "name": "stripe-core", + "origin": "hackage", + "synopsis": "Stripe API for Haskell - Pure Core", + "version": "2.6.2" + }, + { + "name": "stripe-haskell", + "origin": "hackage", + "synopsis": "Stripe API for Haskell", + "version": "2.6.2" + }, + { + "name": "stripe-http-client", + "origin": "hackage", + "synopsis": "Stripe API for Haskell - http-client backend", + "version": "2.6.2" + }, + { + "name": "stripe-tests", + "origin": "hackage", + "synopsis": "Tests for Stripe API bindings for Haskell", + "version": "2.6.2" + }, + { + "name": "strive", + "origin": "hackage", + "synopsis": "A client for the Strava V3 API.", + "version": "5.0.14" + }, + { + "name": "structs", + "origin": "hackage", + "synopsis": "Strict GC'd imperative object-oriented programming with cheap pointers.", + "version": "0.1.5" + }, + { + "name": "structured", + "origin": "hackage", + "synopsis": "Structure (hash) of your data types", + "version": "0.1.0.1" + }, + { + "name": "structured-cli", + "origin": "hackage", + "synopsis": "Application library for building interactive console CLIs", + "version": "2.6.0.0" + }, + { + "name": "subcategories", + "origin": "hackage", + "synopsis": "Subcategories induced by class constraints", + "version": "0.1.1.0" + }, + { + "name": "sum-type-boilerplate", + "origin": "hackage", + "synopsis": "Library for reducing the boilerplate involved with sum types", + "version": "0.1.1" + }, + { + "name": "sundown", + "origin": "hackage", + "synopsis": "Bindings to the sundown markdown library", + "version": "0.6" + }, + { + "name": "superbuffer", + "origin": "hackage", + "synopsis": "Efficiently build a bytestring from smaller chunks", + "version": "0.3.1.1" + }, + { + "name": "svg-tree", + "origin": "hackage", + "synopsis": "SVG file loader and serializer", + "version": "0.6.2.4" + }, + { + "name": "swagger", + "origin": "hackage", + "synopsis": "Implementation of swagger data model", + "version": "0.3.0" + }, + { + "name": "swagger2", + "origin": "hackage", + "synopsis": "Swagger 2.0 data model", + "version": "2.6" + }, + { + "name": "sweet-egison", + "origin": "hackage", + "synopsis": "Shallow embedding implementation of non-linear pattern matching", + "version": "0.1.1.3" + }, + { + "name": "swish", + "origin": "hackage", + "synopsis": "A semantic web toolkit. ", + "version": "0.10.0.4" + }, + { + "name": "syb", + "origin": "hackage", + "synopsis": "Scrap Your Boilerplate", + "version": "0.7.2.1" + }, + { + "name": "symbol", + "origin": "hackage", + "synopsis": "A 'Symbol' type for fast symbol comparison.", + "version": "0.2.4" + }, + { + "name": "symengine", + "origin": "hackage", + "synopsis": "SymEngine symbolic mathematics engine for Haskell", + "version": "0.1.2.0" + }, + { + "name": "symmetry-operations-symbols", + "origin": "hackage", + "synopsis": "Derivation of symbols and coordinate triplets Library", + "version": "0.0.2.1" + }, + { + "name": "sysinfo", + "origin": "hackage", + "synopsis": "Haskell Interface for getting overall system statistics", + "version": "0.1.1" + }, + { + "name": "system-argv0", + "origin": "hackage", + "synopsis": "Get argv[0] as a FilePath.", + "version": "0.1.1" + }, + { + "name": "systemd", + "origin": "hackage", + "synopsis": "Systemd facilities (Socket activation, Notify)", + "version": "2.3.0" + }, + { + "name": "system-fileio", + "origin": "hackage", + "synopsis": "Consistent filesystem interaction across GHC versions (deprecated)", + "version": "0.3.16.4" + }, + { + "name": "system-filepath", + "origin": "hackage", + "synopsis": "High-level, byte-based file and directory path manipulations (deprecated)", + "version": "0.4.14" + }, + { + "name": "system-info", + "origin": "hackage", + "synopsis": "Get the name of the operating system", + "version": "0.5.2" + }, + { + "name": "tabular", + "origin": "hackage", + "synopsis": "Two-dimensional data tables with rendering functions", + "version": "0.2.2.8" + }, + { + "name": "taffybar", + "origin": "hackage", + "synopsis": "A desktop bar similar to xmobar, but with more GUI", + "version": "3.2.3" + }, + { + "name": "tagchup", + "origin": "hackage", + "synopsis": "alternative package for processing of tag soups", + "version": "0.4.1.1" + }, + { + "name": "tagged", + "origin": "hackage", + "synopsis": "Haskell 98 phantom types to avoid unsafely passing dummy arguments", + "version": "0.8.6.1" + }, + { + "name": "tagged-binary", + "origin": "hackage", + "synopsis": "Provides tools for serializing data tagged with type\ninformation.", + "version": "0.2.0.1" + }, + { + "name": "tagged-identity", + "origin": "hackage", + "synopsis": "Trivial monad transformer that allows identical monad stacks have different types", + "version": "0.1.3" + }, + { + "name": "tagged-transformer", + "origin": "hackage", + "synopsis": "Monad transformer carrying an extra phantom type tag", + "version": "0.8.1" + }, + { + "name": "tagshare", + "origin": "hackage", + "synopsis": "TagShare - explicit sharing with tags", + "version": "0.0" + }, + { + "name": "tagsoup", + "origin": "hackage", + "synopsis": "Parsing and extracting information from (possibly malformed) HTML/XML documents", + "version": "0.14.8" + }, + { + "name": "tao", + "origin": "hackage", + "synopsis": "Type-level assertion operators.", + "version": "1.0.0" + }, + { + "name": "tao-example", + "origin": "hackage", + "synopsis": "Example usage of the tao package.", + "version": "1.0.0" + }, + { + "name": "tar", + "origin": "hackage", + "synopsis": "Reading, writing and manipulating \".tar\" archive files.", + "version": "0.5.1.1" + }, + { + "name": "tar-conduit", + "origin": "hackage", + "synopsis": "Extract and create tar files using conduit for streaming", + "version": "0.3.2" + }, + { + "name": "tardis", + "origin": "hackage", + "synopsis": "Bidirectional state monad transformer", + "version": "0.4.3.0" + }, + { + "name": "tasty", + "origin": "hackage", + "synopsis": "Modern and extensible testing framework", + "version": "1.2.3" + }, + { + "name": "tasty-ant-xml", + "origin": "hackage", + "synopsis": "Render tasty output to XML for Jenkins", + "version": "1.1.7" + }, + { + "name": "tasty-bench", + "origin": "hackage", + "synopsis": "Featherlight benchmark framework", + "version": "0.2.3" + }, + { + "name": "tasty-dejafu", + "origin": "hackage", + "synopsis": "Deja Fu support for the Tasty test framework.", + "version": "2.0.0.7" + }, + { + "name": "tasty-discover", + "origin": "hackage", + "synopsis": "Test discovery for the tasty framework.", + "version": "4.2.2" + }, + { + "name": "tasty-expected-failure", + "origin": "hackage", + "synopsis": "Mark tasty tests as failure expected", + "version": "0.12.3" + }, + { + "name": "tasty-focus", + "origin": "hackage", + "synopsis": "Simple focus mechanism for tasty", + "version": "1.0.1" + }, + { + "name": "tasty-golden", + "origin": "hackage", + "synopsis": "Golden tests support for tasty", + "version": "2.3.3.2" + }, + { + "name": "tasty-hedgehog", + "origin": "hackage", + "synopsis": "Integration for tasty and hedgehog.", + "version": "1.0.1.0" + }, + { + "name": "tasty-hspec", + "origin": "hackage", + "synopsis": "Hspec support for the Tasty test framework.", + "version": "1.1.6" + }, + { + "name": "tasty-hunit", + "origin": "hackage", + "synopsis": "HUnit support for the Tasty test framework.", + "version": "0.10.0.3" + }, + { + "name": "tasty-hunit-compat", + "origin": "hackage", + "synopsis": "Integration of `HUnit` with `tasty`.", + "version": "0.2.0.1" + }, + { + "name": "tasty-kat", + "origin": "hackage", + "synopsis": "Known Answer Tests (KAT) framework for tasty", + "version": "0.0.3" + }, + { + "name": "tasty-leancheck", + "origin": "hackage", + "synopsis": "LeanCheck support for the Tasty test framework.", + "version": "0.0.1" + }, + { + "name": "tasty-lua", + "origin": "hackage", + "synopsis": "Write tests in Lua, integrate into tasty.", + "version": "0.2.3.2" + }, + { + "name": "tasty-program", + "origin": "hackage", + "synopsis": "Use tasty framework to test whether a program executes correctly", + "version": "1.0.5" + }, + { + "name": "tasty-quickcheck", + "origin": "hackage", + "synopsis": "QuickCheck support for the Tasty test framework.", + "version": "0.10.1.2" + }, + { + "name": "tasty-rerun", + "origin": "hackage", + "synopsis": "Rerun only tests which failed in a previous test run", + "version": "1.1.18" + }, + { + "name": "tasty-smallcheck", + "origin": "hackage", + "synopsis": "SmallCheck support for the Tasty test framework.", + "version": "0.8.2" + }, + { + "name": "tasty-test-reporter", + "origin": "hackage", + "synopsis": "Producing JUnit-style XML test reports.", + "version": "0.1.1.4" + }, + { + "name": "tasty-th", + "origin": "hackage", + "synopsis": "Automatic tasty test case discovery using TH", + "version": "0.1.7" + }, + { + "name": "tasty-wai", + "origin": "hackage", + "synopsis": "Test 'wai' endpoints via Test.Tasty", + "version": "0.1.1.1" + }, + { + "name": "Taxonomy", + "origin": "hackage", + "synopsis": "Libary for parsing, processing and vizualization of taxonomy data", + "version": "2.1.0" + }, + { + "name": "TCache", + "origin": "hackage", + "synopsis": "A Transactional cache with user-defined persistence", + "version": "0.12.1" + }, + { + "name": "tce-conf", + "origin": "hackage", + "synopsis": "Very simple config file reading", + "version": "1.3" + }, + { + "name": "tdigest", + "origin": "hackage", + "synopsis": "On-line accumulation of rank-based statistics", + "version": "0.2.1.1" + }, + { + "name": "template-haskell", + "origin": "core", + "synopsis": "Support library for Template Haskell", + "version": "2.16.0.0" + }, + { + "name": "template-haskell-compat-v0208", + "origin": "hackage", + "synopsis": "A backwards compatibility layer for Template Haskell newer than 2.8", + "version": "0.1.5" + }, + { + "name": "temporary", + "origin": "hackage", + "synopsis": "Portable temporary file and directory support", + "version": "1.3" + }, + { + "name": "temporary-rc", + "origin": "hackage", + "synopsis": "Portable temporary file and directory support for Windows and Unix, based on code from Cabal", + "version": "1.2.0.3" + }, + { + "name": "temporary-resourcet", + "origin": "hackage", + "synopsis": "Portable temporary files and directories with automatic deletion", + "version": "0.1.0.1" + }, + { + "name": "tensorflow-test", + "origin": "hackage", + "synopsis": "Some common functions for test suites.", + "version": "0.1.0.0" + }, + { + "name": "tensors", + "origin": "hackage", + "synopsis": "Tensor in Haskell", + "version": "0.1.5" + }, + { + "name": "termbox", + "origin": "hackage", + "synopsis": "termbox bindings", + "version": "0.3.0" + }, + { + "name": "terminal-progress-bar", + "origin": "hackage", + "synopsis": "A progress bar in the terminal", + "version": "0.4.1" + }, + { + "name": "terminal-size", + "origin": "hackage", + "synopsis": "Get terminal window height and width", + "version": "0.3.2.1" + }, + { + "name": "terminfo", + "origin": "core", + "synopsis": "Haskell bindings to the terminfo library.", + "version": "0.4.1.4" + }, + { + "name": "test-framework", + "origin": "hackage", + "synopsis": "Framework for running and organising tests, with HUnit and QuickCheck support", + "version": "0.8.2.0" + }, + { + "name": "test-framework-hunit", + "origin": "hackage", + "synopsis": "HUnit support for the test-framework package.", + "version": "0.3.0.2" + }, + { + "name": "test-framework-leancheck", + "origin": "hackage", + "synopsis": "LeanCheck support for test-framework.", + "version": "0.0.1" + }, + { + "name": "test-framework-quickcheck2", + "origin": "hackage", + "synopsis": "QuickCheck-2 support for the test-framework package.", + "version": "0.3.0.5" + }, + { + "name": "test-framework-smallcheck", + "origin": "hackage", + "synopsis": "Support for SmallCheck tests in test-framework", + "version": "0.2" + }, + { + "name": "test-fun", + "origin": "hackage", + "synopsis": "Testable functions", + "version": "0.1.0.0" + }, + { + "name": "testing-type-modifiers", + "origin": "hackage", + "synopsis": "Data type modifiers for property based testing", + "version": "0.1.0.1" + }, + { + "name": "texmath", + "origin": "hackage", + "synopsis": "Conversion between formats used to represent mathematics.", + "version": "0.12.2" + }, + { + "name": "text", + "origin": "core", + "synopsis": "An efficient packed Unicode text type.", + "version": "1.2.4.1" + }, + { + "name": "text-ansi", + "origin": "hackage", + "synopsis": "Text styling for ANSI terminals.", + "version": "0.1.1" + }, + { + "name": "text-binary", + "origin": "hackage", + "synopsis": "Binary instances for text types", + "version": "0.2.1.1" + }, + { + "name": "text-builder", + "origin": "hackage", + "synopsis": "An efficient strict text builder", + "version": "0.6.6.2" + }, + { + "name": "text-conversions", + "origin": "hackage", + "synopsis": "Safe conversions between textual types", + "version": "0.3.1" + }, + { + "name": "text-format", + "origin": "hackage", + "synopsis": "Text formatting", + "version": "0.3.2" + }, + { + "name": "text-icu", + "origin": "hackage", + "synopsis": "Bindings to the ICU library", + "version": "0.7.0.1" + }, + { + "name": "text-latin1", + "origin": "hackage", + "synopsis": "Latin-1 (including ASCII) utility functions", + "version": "0.3.1" + }, + { + "name": "text-ldap", + "origin": "hackage", + "synopsis": "Parser and Printer for LDAP text data stream", + "version": "0.1.1.13" + }, + { + "name": "textlocal", + "origin": "hackage", + "synopsis": "Haskell wrapper for textlocal SMS gateway", + "version": "0.1.0.5" + }, + { + "name": "text-manipulate", + "origin": "hackage", + "synopsis": "Case conversion, word boundary manipulation, and textual subjugation.", + "version": "0.2.0.1" + }, + { + "name": "text-metrics", + "origin": "hackage", + "synopsis": "Calculate various string metrics efficiently", + "version": "0.3.0" + }, + { + "name": "text-postgresql", + "origin": "hackage", + "synopsis": "Parser and Printer of PostgreSQL extended types", + "version": "0.0.3.1" + }, + { + "name": "text-printer", + "origin": "hackage", + "synopsis": "Abstract interface for text builders/printers.", + "version": "0.5.0.1" + }, + { + "name": "text-regex-replace", + "origin": "hackage", + "synopsis": "Easy replacement when using text-icu regexes.", + "version": "0.1.1.4" + }, + { + "name": "text-region", + "origin": "hackage", + "synopsis": "Marking text regions", + "version": "0.3.1.0" + }, + { + "name": "text-short", + "origin": "hackage", + "synopsis": "Memory-efficient representation of Unicode text strings", + "version": "0.1.3" + }, + { + "name": "text-show", + "origin": "hackage", + "synopsis": "Efficient conversion of values into Text", + "version": "3.9" + }, + { + "name": "text-show-instances", + "origin": "hackage", + "synopsis": "Additional instances for text-show", + "version": "3.8.4" + }, + { + "name": "text-zipper", + "origin": "hackage", + "synopsis": "A text editor zipper library", + "version": "0.11" + }, + { + "name": "tfp", + "origin": "hackage", + "synopsis": "Type-level integers, booleans, lists using type families", + "version": "1.0.2" + }, + { + "name": "tf-random", + "origin": "hackage", + "synopsis": "High-quality splittable pseudorandom number generator", + "version": "0.5" + }, + { + "name": "th-abstraction", + "origin": "hackage", + "synopsis": "Nicer interface for reified information about data types", + "version": "0.4.2.0" + }, + { + "name": "th-bang-compat", + "origin": "hackage", + "synopsis": "Compatibility for bang-type template", + "version": "0.0.1.0" + }, + { + "name": "th-compat", + "origin": "hackage", + "synopsis": "Backward- (and forward-)compatible Quote and Code types", + "version": "0.1.2" + }, + { + "name": "th-constraint-compat", + "origin": "hackage", + "synopsis": "Compatibility for type constraint template", + "version": "0.0.1.0" + }, + { + "name": "th-data-compat", + "origin": "hackage", + "synopsis": "Compatibility for data definition template of TH", + "version": "0.1.0.0" + }, + { + "name": "th-desugar", + "origin": "hackage", + "synopsis": "Functions to desugar Template Haskell", + "version": "1.11" + }, + { + "name": "th-env", + "origin": "hackage", + "synopsis": "Template Haskell splice that expands to an environment variable", + "version": "0.1.0.2" + }, + { + "name": "these", + "origin": "hackage", + "synopsis": "An either-or-both data type.", + "version": "1.1.1.1" + }, + { + "name": "these-lens", + "origin": "hackage", + "synopsis": "Lenses for These", + "version": "1.0.1.2" + }, + { + "name": "these-optics", + "origin": "hackage", + "synopsis": "Optics for These", + "version": "1.0.1.2" + }, + { + "name": "these-skinny", + "origin": "hackage", + "synopsis": "A fork of the 'these' package without the dependency bloat.", + "version": "0.7.4" + }, + { + "name": "th-expand-syns", + "origin": "hackage", + "synopsis": "Expands type synonyms in Template Haskell ASTs", + "version": "0.4.8.0" + }, + { + "name": "th-extras", + "origin": "hackage", + "synopsis": "A grab bag of functions for use with Template Haskell", + "version": "0.0.0.4" + }, + { + "name": "th-lift", + "origin": "hackage", + "synopsis": "Derive Template Haskell's Lift class for datatypes.", + "version": "0.8.2" + }, + { + "name": "th-lift-instances", + "origin": "hackage", + "synopsis": "Lift instances for template-haskell for common data types.", + "version": "0.1.18" + }, + { + "name": "th-nowq", + "origin": "hackage", + "synopsis": "Template Haskell splice that expands to current time", + "version": "0.1.0.5" + }, + { + "name": "th-orphans", + "origin": "hackage", + "synopsis": "Orphan instances for TH datatypes", + "version": "0.13.11" + }, + { + "name": "th-printf", + "origin": "hackage", + "synopsis": "Quasiquoters for printf", + "version": "0.7" + }, + { + "name": "thread-hierarchy", + "origin": "hackage", + "synopsis": "Simple Haskell thread management in hierarchical manner", + "version": "0.3.0.2" + }, + { + "name": "thread-local-storage", + "origin": "hackage", + "synopsis": "Several options for thread-local-storage (TLS) in Haskell.", + "version": "0.2" + }, + { + "name": "threads", + "origin": "hackage", + "synopsis": "Fork threads and wait for their result", + "version": "0.5.1.6" + }, + { + "name": "thread-supervisor", + "origin": "hackage", + "synopsis": "A simplified implementation of Erlang/OTP like supervisor over thread", + "version": "0.2.0.0" + }, + { + "name": "threepenny-gui", + "origin": "hackage", + "synopsis": "GUI framework that uses the web browser as a display.", + "version": "0.9.0.0" + }, + { + "name": "th-reify-compat", + "origin": "hackage", + "synopsis": "Compatibility for the result type of TH reify", + "version": "0.0.1.5" + }, + { + "name": "th-reify-many", + "origin": "hackage", + "synopsis": "Recurseively reify template haskell datatype info", + "version": "0.1.9" + }, + { + "name": "throttle-io-stream", + "origin": "hackage", + "synopsis": "Throttler between arbitrary IO producer and consumer functions", + "version": "0.2.0.1" + }, + { + "name": "through-text", + "origin": "hackage", + "synopsis": "Convert textual types through Text without needing O(n^2) instances.", + "version": "0.1.0.0" + }, + { + "name": "throwable-exceptions", + "origin": "hackage", + "synopsis": "throwable-exceptions gives the easy way to throw exceptions", + "version": "0.1.0.9" + }, + { + "name": "th-strict-compat", + "origin": "hackage", + "synopsis": "Compatibility shim for Bang and Strict in Template Haskell.", + "version": "0.1.0.1" + }, + { + "name": "th-test-utils", + "origin": "hackage", + "synopsis": "Utility functions for testing Template Haskell code", + "version": "1.1.0" + }, + { + "name": "th-utilities", + "origin": "hackage", + "synopsis": "Collection of useful functions for use with Template Haskell", + "version": "0.2.4.3" + }, + { + "name": "thyme", + "origin": "hackage", + "synopsis": "A faster time library", + "version": "0.3.5.5" + }, + { + "name": "tidal", + "origin": "hackage", + "synopsis": "Pattern language for improvised music", + "version": "1.6.1" + }, + { + "name": "tile", + "origin": "hackage", + "synopsis": "Slippy map tile functionality.", + "version": "0.3.0.0" + }, + { + "name": "time", + "origin": "core", + "synopsis": "A time library", + "version": "1.9.3" + }, + { + "name": "time-compat", + "origin": "hackage", + "synopsis": "Compatibility package for time", + "version": "1.9.5" + }, + { + "name": "timeit", + "origin": "hackage", + "synopsis": "Time monadic computations with an IO base.", + "version": "2.0" + }, + { + "name": "time-lens", + "origin": "hackage", + "synopsis": "Lens-based interface to Data.Time data structures", + "version": "0.4.0.2" + }, + { + "name": "timelens", + "origin": "hackage", + "synopsis": "Lenses for the time package", + "version": "0.2.0.2" + }, + { + "name": "time-locale-compat", + "origin": "hackage", + "synopsis": "Compatibile module for time-format locale", + "version": "0.1.1.5" + }, + { + "name": "time-locale-vietnamese", + "origin": "hackage", + "synopsis": "Vietnamese locale for date and time format", + "version": "1.0.0.0" + }, + { + "name": "time-manager", + "origin": "hackage", + "synopsis": "Scalable timer", + "version": "0.0.0" + }, + { + "name": "time-parsers", + "origin": "hackage", + "synopsis": "Parsers for types in `time`.", + "version": "0.1.2.1" + }, + { + "name": "timerep", + "origin": "hackage", + "synopsis": "Parse and display time according to some RFCs (RFC3339, RFC2822, RFC822)", + "version": "2.0.1.0" + }, + { + "name": "timer-wheel", + "origin": "hackage", + "synopsis": "A timer wheel", + "version": "0.3.0" + }, + { + "name": "time-units", + "origin": "hackage", + "synopsis": "A basic library for defining units of time as types.", + "version": "1.0.0" + }, + { + "name": "timezone-olson", + "origin": "hackage", + "synopsis": "A pure Haskell parser and renderer for binary Olson timezone files", + "version": "0.2.0" + }, + { + "name": "timezone-series", + "origin": "hackage", + "synopsis": "Enhanced timezone handling for Data.Time", + "version": "0.1.9" + }, + { + "name": "tinylog", + "origin": "hackage", + "synopsis": "Simplistic logging using fast-logger.", + "version": "0.15.0" + }, + { + "name": "titlecase", + "origin": "hackage", + "synopsis": "Convert English Words to Title Case", + "version": "1.0.1" + }, + { + "name": "tldr", + "origin": "hackage", + "synopsis": "Haskell tldr client", + "version": "0.9.0" + }, + { + "name": "tls", + "origin": "hackage", + "synopsis": "TLS/SSL protocol native implementation (Server and Client)", + "version": "1.5.5" + }, + { + "name": "tls-debug", + "origin": "hackage", + "synopsis": "Set of programs for TLS testing and debugging", + "version": "0.4.8" + }, + { + "name": "tls-session-manager", + "origin": "hackage", + "synopsis": "In-memory TLS session manager", + "version": "0.0.4" + }, + { + "name": "tlynx", + "origin": "hackage", + "synopsis": "Handle phylogenetic trees", + "version": "0.5.0.1" + }, + { + "name": "tmapchan", + "origin": "hackage", + "synopsis": "An insert-ordered multimap (indexed FIFO) which consumes values as you lookup", + "version": "0.0.3" + }, + { + "name": "tmapmvar", + "origin": "hackage", + "synopsis": "A single-entity stateful Map in STM, similar to tmapchan", + "version": "0.0.4" + }, + { + "name": "tmp-postgres", + "origin": "hackage", + "synopsis": "Start and stop a temporary postgres", + "version": "1.34.1.0" + }, + { + "name": "tomland", + "origin": "hackage", + "synopsis": "Bidirectional TOML serialization", + "version": "1.3.2.0" + }, + { + "name": "tonalude", + "origin": "hackage", + "synopsis": "A standard library for Tonatona framework.", + "version": "0.1.1.1" + }, + { + "name": "topograph", + "origin": "hackage", + "synopsis": "Directed acyclic graphs.", + "version": "1.0.0.1" + }, + { + "name": "torsor", + "origin": "hackage", + "synopsis": "Torsor Typeclass", + "version": "0.1" + }, + { + "name": "tostring", + "origin": "hackage", + "synopsis": "The ToString class", + "version": "0.2.1.1" + }, + { + "name": "tracing", + "origin": "hackage", + "synopsis": "Distributed tracing", + "version": "0.0.6.0" + }, + { + "name": "tracing-control", + "origin": "hackage", + "synopsis": "Distributed tracing", + "version": "0.0.6" + }, + { + "name": "transaction", + "origin": "hackage", + "synopsis": "Monadic representation of transactions.", + "version": "0.1.1.3" + }, + { + "name": "transformers", + "origin": "core", + "synopsis": "Concrete functor and monad transformers", + "version": "0.5.6.2" + }, + { + "name": "transformers-base", + "origin": "hackage", + "synopsis": "Lift computations from the bottom of a transformer stack", + "version": "0.4.5.2" + }, + { + "name": "transformers-bifunctors", + "origin": "hackage", + "synopsis": "Bifunctors over monad transformers.", + "version": "0.1" + }, + { + "name": "transformers-compat", + "origin": "hackage", + "synopsis": "A small compatibility shim for the transformers library", + "version": "0.6.6" + }, + { + "name": "transformers-fix", + "origin": "hackage", + "synopsis": "Monad transformer for evaluating to a fixpoint", + "version": "1.0" + }, + { + "name": "traverse-with-class", + "origin": "hackage", + "synopsis": "Generic applicative traversals", + "version": "1.0.1.0" + }, + { + "name": "tree-diff", + "origin": "hackage", + "synopsis": "Diffing of (expression) trees.", + "version": "0.1" + }, + { + "name": "tree-fun", + "origin": "hackage", + "synopsis": "Library for functions pertaining to tree exploration and manipulation", + "version": "0.8.1.0" + }, + { + "name": "trifecta", + "origin": "hackage", + "synopsis": "A modern parser combinator library with convenient diagnostics", + "version": "2.1.1" + }, + { + "name": "triplesec", + "origin": "hackage", + "synopsis": "TripleSec is a simple, triple-paranoid, symmetric encryption library", + "version": "0.2.2.1" + }, + { + "name": "tsv2csv", + "origin": "hackage", + "synopsis": "Convert tsv to csv", + "version": "0.1.0.2" + }, + { + "name": "ttc", + "origin": "hackage", + "synopsis": "Textual Type Classes", + "version": "0.3.0.0" + }, + { + "name": "ttl-hashtables", + "origin": "hackage", + "synopsis": "Extends hashtables so that entries added can be expired after a TTL", + "version": "1.4.1.0" + }, + { + "name": "ttrie", + "origin": "hackage", + "synopsis": "Contention-free STM hash map", + "version": "0.1.2.1" + }, + { + "name": "tuple", + "origin": "hackage", + "synopsis": "Various functions on tuples", + "version": "0.3.0.2" + }, + { + "name": "tuples-homogenous-h98", + "origin": "hackage", + "synopsis": "Wrappers for n-ary tuples with Traversable and Applicative/Monad instances.", + "version": "0.1.1.0" + }, + { + "name": "tuple-sop", + "origin": "hackage", + "synopsis": "functions on n-ary tuples using generics-sop", + "version": "0.3.1.0" + }, + { + "name": "tuple-th", + "origin": "hackage", + "synopsis": "Generate (non-recursive) utility functions for tuples of statically known size", + "version": "0.2.5" + }, + { + "name": "turtle", + "origin": "hackage", + "synopsis": "Shell programming, Haskell-style", + "version": "1.5.20" + }, + { + "name": "TypeCompose", + "origin": "hackage", + "synopsis": "Type composition classes & instances", + "version": "0.9.14" + }, + { + "name": "typed-process", + "origin": "hackage", + "synopsis": "Run external processes, with strong typing of streams", + "version": "0.2.6.0" + }, + { + "name": "typed-uuid", + "origin": "hackage", + "synopsis": "Phantom-Typed version of UUID", + "version": "0.0.0.2" + }, + { + "name": "type-equality", + "origin": "hackage", + "synopsis": "Data.Type.Equality compat package", + "version": "1" + }, + { + "name": "type-errors", + "origin": "hackage", + "synopsis": "Tools for writing better type errors", + "version": "0.2.0.0" + }, + { + "name": "type-errors-pretty", + "origin": "hackage", + "synopsis": "Combinators for writing pretty type errors easily", + "version": "0.0.1.1" + }, + { + "name": "type-hint", + "origin": "hackage", + "synopsis": "Guide type inference with proxy values", + "version": "0.1" + }, + { + "name": "type-level-integers", + "origin": "hackage", + "synopsis": "Provides integers lifted to the type level", + "version": "0.0.1" + }, + { + "name": "type-level-kv-list", + "origin": "hackage", + "synopsis": "A module for hash map like object with type level keys.", + "version": "1.1.0" + }, + { + "name": "type-level-natural-number", + "origin": "hackage", + "synopsis": "Simple type level natural numbers", + "version": "2.0" + }, + { + "name": "type-level-numbers", + "origin": "hackage", + "synopsis": "Type level numbers implemented using type families.", + "version": "0.1.1.1" + }, + { + "name": "type-map", + "origin": "hackage", + "synopsis": "Type-indexed maps", + "version": "0.1.6.0" + }, + { + "name": "type-natural", + "origin": "hackage", + "synopsis": "Type-level natural and proofs of their properties.", + "version": "1.0.0.0" + }, + { + "name": "type-of-html", + "origin": "hackage", + "synopsis": "High performance type driven html generation.", + "version": "1.6.2.0" + }, + { + "name": "type-of-html-static", + "origin": "hackage", + "synopsis": "Optimize static parts of type-of-html.", + "version": "0.1.0.2" + }, + { + "name": "type-operators", + "origin": "hackage", + "synopsis": "Various type-level operators", + "version": "0.2.0.0" + }, + { + "name": "typerep-map", + "origin": "hackage", + "synopsis": "Efficient implementation of a dependent map with types as keys", + "version": "0.3.3.0" + }, + { + "name": "type-spec", + "origin": "hackage", + "synopsis": "Type Level Specification by Example", + "version": "0.4.0.0" + }, + { + "name": "tzdata", + "origin": "hackage", + "synopsis": "Time zone database (as files and as a module)", + "version": "0.2.20201021.0" + }, + { + "name": "ua-parser", + "origin": "hackage", + "synopsis": "A library for parsing User-Agent strings, official Haskell port of ua-parser", + "version": "0.7.6.0" + }, + { + "name": "uglymemo", + "origin": "hackage", + "synopsis": "A simple (but internally ugly) memoization function.", + "version": "0.1.0.1" + }, + { + "name": "ulid", + "origin": "hackage", + "synopsis": "Implementation of ULID - Universally Unique\nLexicographically Sortable Identifier", + "version": "0.3.0.0" + }, + { + "name": "unagi-chan", + "origin": "hackage", + "synopsis": "Fast concurrent queues with a Chan-like API, and more", + "version": "0.4.1.3" + }, + { + "name": "unbounded-delays", + "origin": "hackage", + "synopsis": "Unbounded thread delays and timeouts", + "version": "0.1.1.1" + }, + { + "name": "unboxed-ref", + "origin": "hackage", + "synopsis": "Fast unboxed references for ST and IO monad", + "version": "0.4.0.0" + }, + { + "name": "unboxing-vector", + "origin": "hackage", + "synopsis": "A newtype-friendly variant of unboxed vectors", + "version": "0.2.0.0" + }, + { + "name": "uncaught-exception", + "origin": "hackage", + "synopsis": "Customize uncaught exception handling.", + "version": "0.1.0" + }, + { + "name": "uncertain", + "origin": "hackage", + "synopsis": "Manipulating numbers with inherent experimental/measurement uncertainty", + "version": "0.3.1.0" + }, + { + "name": "unconstrained", + "origin": "hackage", + "synopsis": "Null constraint", + "version": "0.1.0.2" + }, + { + "name": "unexceptionalio", + "origin": "hackage", + "synopsis": "IO without any non-error, synchronous exceptions", + "version": "0.5.1" + }, + { + "name": "unexceptionalio-trans", + "origin": "hackage", + "synopsis": "A wrapper around UnexceptionalIO using monad transformers", + "version": "0.5.1" + }, + { + "name": "unicode", + "origin": "hackage", + "synopsis": "Construct and transform unicode characters", + "version": "0.0.1.1" + }, + { + "name": "unicode-show", + "origin": "hackage", + "synopsis": "print and show in unicode", + "version": "0.1.0.4" + }, + { + "name": "unicode-transforms", + "origin": "hackage", + "synopsis": "Unicode normalization", + "version": "0.3.7.1" + }, + { + "name": "union-find", + "origin": "hackage", + "synopsis": "Efficient union and equivalence testing of sets.", + "version": "0.2" + }, + { + "name": "unipatterns", + "origin": "hackage", + "synopsis": "Helpers which allow safe partial pattern matching in lambdas", + "version": "0.0.0.0" + }, + { + "name": "uniplate", + "origin": "hackage", + "synopsis": "Help writing simple, concise and fast generic operations.", + "version": "1.6.13" + }, + { + "name": "uniprot-kb", + "origin": "hackage", + "synopsis": "UniProt-KB format parser", + "version": "0.1.2.0" + }, + { + "name": "uniq-deep", + "origin": "hackage", + "synopsis": "uniq-deep", + "version": "1.2.0" + }, + { + "name": "unique", + "origin": "hackage", + "synopsis": "Fully concurrent unique identifiers", + "version": "0.0.1" + }, + { + "name": "unique-logic", + "origin": "hackage", + "synopsis": "Solve simple simultaneous equations", + "version": "0.4" + }, + { + "name": "unique-logic-tf", + "origin": "hackage", + "synopsis": "Solve simple simultaneous equations", + "version": "0.5.1" + }, + { + "name": "unit-constraint", + "origin": "hackage", + "synopsis": "Extremely simple typeclass", + "version": "0.0.0" + }, + { + "name": "universe", + "origin": "hackage", + "synopsis": "A class for finite and recursively enumerable types.", + "version": "1.2.1" + }, + { + "name": "universe-base", + "origin": "hackage", + "synopsis": "A class for finite and recursively enumerable types.", + "version": "1.1.2" + }, + { + "name": "universe-instances-base", + "origin": "hackage", + "synopsis": "Universe instances for types from the base package", + "version": "1.1" + }, + { + "name": "universe-instances-extended", + "origin": "hackage", + "synopsis": "Universe instances for types from selected extra packages", + "version": "1.1.2" + }, + { + "name": "universe-instances-trans", + "origin": "hackage", + "synopsis": "Universe instances for types from the transformers and mtl packages", + "version": "1.1" + }, + { + "name": "universe-reverse-instances", + "origin": "hackage", + "synopsis": "Instances of standard classes that are made possible by enumerations", + "version": "1.1.1" + }, + { + "name": "universe-some", + "origin": "hackage", + "synopsis": "Universe instances for Some from some", + "version": "1.2.1" + }, + { + "name": "universum", + "origin": "hackage", + "synopsis": "Custom prelude used in Serokell", + "version": "1.5.0" + }, + { + "name": "unix", + "origin": "core", + "synopsis": "POSIX functionality", + "version": "2.7.2.2" + }, + { + "name": "unix-bytestring", + "origin": "hackage", + "synopsis": "Unix/Posix-specific functions for ByteStrings.", + "version": "0.3.7.3" + }, + { + "name": "unix-compat", + "origin": "hackage", + "synopsis": "Portable POSIX-compatibility layer.", + "version": "0.5.3" + }, + { + "name": "unix-time", + "origin": "hackage", + "synopsis": "Unix time parser/formatter and utilities", + "version": "0.4.7" + }, + { + "name": "unliftio", + "origin": "hackage", + "synopsis": "The MonadUnliftIO typeclass for unlifting monads to IO (batteries included)", + "version": "0.2.14" + }, + { + "name": "unliftio-core", + "origin": "hackage", + "synopsis": "The MonadUnliftIO typeclass for unlifting monads to IO", + "version": "0.2.0.1" + }, + { + "name": "unliftio-pool", + "origin": "hackage", + "synopsis": "Data.Pool generalized to MonadUnliftIO.", + "version": "0.2.1.1" + }, + { + "name": "unlit", + "origin": "hackage", + "synopsis": "Tool to convert literate code between styles or to code.", + "version": "0.4.0.0" + }, + { + "name": "unordered-containers", + "origin": "hackage", + "synopsis": "Efficient hashing-based container types", + "version": "0.2.13.0" + }, + { + "name": "unsafe", + "origin": "hackage", + "synopsis": "Unified interface to unsafe functions", + "version": "0.0" + }, + { + "name": "urbit-hob", + "origin": "hackage", + "synopsis": "Hoon-style atom manipulation and printing functions", + "version": "0.3.3" + }, + { + "name": "uri-bytestring", + "origin": "hackage", + "synopsis": "Haskell URI parsing as ByteStrings", + "version": "0.3.3.0" + }, + { + "name": "uri-bytestring-aeson", + "origin": "hackage", + "synopsis": "Aeson instances for URI Bytestring", + "version": "0.1.0.8" + }, + { + "name": "uri-encode", + "origin": "hackage", + "synopsis": "Unicode aware uri-encoding", + "version": "1.5.0.7" + }, + { + "name": "url", + "origin": "hackage", + "synopsis": "A library for working with URLs.", + "version": "2.1.3" + }, + { + "name": "users", + "origin": "hackage", + "synopsis": "A library simplifying user management for web applications", + "version": "0.5.0.0" + }, + { + "name": "utf8-conversions", + "origin": "hackage", + "synopsis": "A string conversion library that assumes utf8", + "version": "0.1.0.4" + }, + { + "name": "utf8-light", + "origin": "hackage", + "synopsis": "Unicode", + "version": "0.4.2" + }, + { + "name": "utf8-string", + "origin": "hackage", + "synopsis": "Support for reading and writing UTF8 Strings", + "version": "1.0.2" + }, + { + "name": "util", + "origin": "hackage", + "synopsis": "Utilities", + "version": "0.1.17.1" + }, + { + "name": "utility-ht", + "origin": "hackage", + "synopsis": "Various small helper functions for Lists, Maybes, Tuples, Functions", + "version": "0.0.16" + }, + { + "name": "uuid", + "origin": "hackage", + "synopsis": "For creating, comparing, parsing and printing Universally Unique Identifiers", + "version": "1.3.14" + }, + { + "name": "uuid-types", + "origin": "hackage", + "synopsis": "Type definitions for Universally Unique Identifiers", + "version": "1.0.4" + }, + { + "name": "validation", + "origin": "hackage", + "synopsis": "A data-type like Either but with an accumulating Applicative", + "version": "1.1.1" + }, + { + "name": "validation-selective", + "origin": "hackage", + "synopsis": "Lighweight pure data validation based on Applicative and Selective functors", + "version": "0.1.0.1" + }, + { + "name": "validity", + "origin": "hackage", + "synopsis": "Validity typeclass", + "version": "0.11.0.0" + }, + { + "name": "validity-aeson", + "origin": "hackage", + "synopsis": "Validity instances for aeson", + "version": "0.2.0.4" + }, + { + "name": "validity-bytestring", + "origin": "hackage", + "synopsis": "Validity instances for bytestring", + "version": "0.4.1.1" + }, + { + "name": "validity-containers", + "origin": "hackage", + "synopsis": "Validity instances for containers", + "version": "0.5.0.4" + }, + { + "name": "validity-path", + "origin": "hackage", + "synopsis": "Validity instances for Path", + "version": "0.4.0.1" + }, + { + "name": "validity-primitive", + "origin": "hackage", + "synopsis": "Validity instances for primitive", + "version": "0.0.0.1" + }, + { + "name": "validity-scientific", + "origin": "hackage", + "synopsis": "Validity instances for scientific", + "version": "0.2.0.3" + }, + { + "name": "validity-text", + "origin": "hackage", + "synopsis": "Validity instances for text", + "version": "0.3.1.1" + }, + { + "name": "validity-time", + "origin": "hackage", + "synopsis": "Validity instances for time", + "version": "0.4.0.0" + }, + { + "name": "validity-unordered-containers", + "origin": "hackage", + "synopsis": "Validity instances for unordered-containers", + "version": "0.2.0.3" + }, + { + "name": "validity-uuid", + "origin": "hackage", + "synopsis": "Validity instances for uuid", + "version": "0.1.0.3" + }, + { + "name": "validity-vector", + "origin": "hackage", + "synopsis": "Validity instances for vector", + "version": "0.2.0.3" + }, + { + "name": "valor", + "origin": "hackage", + "synopsis": "Simple general structured validation library", + "version": "0.1.0.0" + }, + { + "name": "vault", + "origin": "hackage", + "synopsis": "a persistent store for values of arbitrary types", + "version": "0.3.1.5" + }, + { + "name": "vec", + "origin": "hackage", + "synopsis": "Vec: length-indexed (sized) list", + "version": "0.3" + }, + { + "name": "vector", + "origin": "hackage", + "synopsis": "Efficient Arrays", + "version": "0.12.1.2" + }, + { + "name": "vector-algorithms", + "origin": "hackage", + "synopsis": "Efficient algorithms for vector arrays", + "version": "0.8.0.4" + }, + { + "name": "vector-binary-instances", + "origin": "hackage", + "synopsis": "Instances of Data.Binary for vector", + "version": "0.2.5.2" + }, + { + "name": "vector-buffer", + "origin": "hackage", + "synopsis": "A buffer compatible with Data.Vector.*", + "version": "0.4.1" + }, + { + "name": "vector-builder", + "origin": "hackage", + "synopsis": "Vector builder", + "version": "0.3.8.1" + }, + { + "name": "vector-bytes-instances", + "origin": "hackage", + "synopsis": "Serial (from the bytes package) for Vector (from the vector package)", + "version": "0.1.1" + }, + { + "name": "vector-instances", + "origin": "hackage", + "synopsis": "Orphan Instances for 'Data.Vector'", + "version": "3.4" + }, + { + "name": "vector-mmap", + "origin": "hackage", + "synopsis": "Memory map immutable and mutable vectors", + "version": "0.0.3" + }, + { + "name": "vector-rotcev", + "origin": "hackage", + "synopsis": "Vectors with O(1) reverse", + "version": "0.1.0.0" + }, + { + "name": "vector-sized", + "origin": "hackage", + "synopsis": "Size tagged vectors", + "version": "1.4.3.1" + }, + { + "name": "vector-space", + "origin": "hackage", + "synopsis": "Vector & affine spaces, linear maps, and derivatives", + "version": "0.16" + }, + { + "name": "vector-split", + "origin": "hackage", + "synopsis": "Combinator library for splitting vectors.", + "version": "1.0.0.2" + }, + { + "name": "vector-th-unbox", + "origin": "hackage", + "synopsis": "Deriver for Data.Vector.Unboxed using Template Haskell", + "version": "0.2.1.9" + }, + { + "name": "verbosity", + "origin": "hackage", + "synopsis": "Simple enum that encodes application verbosity.", + "version": "0.4.0.0" + }, + { + "name": "versions", + "origin": "hackage", + "synopsis": "Types and parsers for software version numbers.", + "version": "4.0.3" + }, + { + "name": "vformat", + "origin": "hackage", + "synopsis": "A Python str.format() like formatter", + "version": "0.14.1.0" + }, + { + "name": "vformat-aeson", + "origin": "hackage", + "synopsis": "Extend vformat to Aeson datatypes", + "version": "0.1.0.1" + }, + { + "name": "vformat-time", + "origin": "hackage", + "synopsis": "Extend vformat to time datatypes", + "version": "0.1.0.0" + }, + { + "name": "ViennaRNAParser", + "origin": "hackage", + "synopsis": "Libary for parsing ViennaRNA package output", + "version": "1.3.3" + }, + { + "name": "vinyl", + "origin": "hackage", + "synopsis": "Extensible Records", + "version": "0.13.1" + }, + { + "name": "void", + "origin": "hackage", + "synopsis": "A Haskell 98 logically uninhabited data type", + "version": "0.7.3" + }, + { + "name": "vty", + "origin": "hackage", + "synopsis": "A simple terminal UI library", + "version": "5.32" + }, + { + "name": "wai", + "origin": "hackage", + "synopsis": "Web Application Interface.", + "version": "3.2.3" + }, + { + "name": "wai-app-static", + "origin": "hackage", + "synopsis": "WAI application for static serving", + "version": "3.1.7.2" + }, + { + "name": "wai-conduit", + "origin": "hackage", + "synopsis": "conduit wrappers for WAI", + "version": "3.0.0.4" + }, + { + "name": "wai-cors", + "origin": "hackage", + "synopsis": "CORS for WAI", + "version": "0.2.7" + }, + { + "name": "wai-enforce-https", + "origin": "hackage", + "synopsis": "Enforce HTTPS in Wai server app safely.", + "version": "0.0.2.1" + }, + { + "name": "wai-eventsource", + "origin": "hackage", + "synopsis": "WAI support for server-sent events (deprecated)", + "version": "3.0.0" + }, + { + "name": "wai-extra", + "origin": "hackage", + "synopsis": "Provides some basic WAI handlers and middleware.", + "version": "3.1.6" + }, + { + "name": "wai-feature-flags", + "origin": "hackage", + "synopsis": "Feature flag support for WAI applications.", + "version": "0.1.0.1" + }, + { + "name": "wai-handler-launch", + "origin": "hackage", + "synopsis": "Launch a web app in the default browser.", + "version": "3.0.3.1" + }, + { + "name": "wai-logger", + "origin": "hackage", + "synopsis": "A logging system for WAI", + "version": "2.3.6" + }, + { + "name": "wai-middleware-auth", + "origin": "hackage", + "synopsis": "Authentication middleware that secures WAI application", + "version": "0.2.4.1" + }, + { + "name": "wai-middleware-caching", + "origin": "hackage", + "synopsis": "WAI Middleware to cache things", + "version": "0.1.0.2" + }, + { + "name": "wai-middleware-clacks", + "origin": "hackage", + "synopsis": "GNU Terry Pratchett - Add the X-Clacks-Overhead Header to Wai Responses.", + "version": "0.1.0.1" + }, + { + "name": "wai-middleware-static", + "origin": "hackage", + "synopsis": "WAI middleware that serves requests to static files.", + "version": "0.9.0" + }, + { + "name": "wai-rate-limit", + "origin": "hackage", + "synopsis": "Rate limiting as WAI middleware", + "version": "0.1.0.0" + }, + { + "name": "wai-rate-limit-redis", + "origin": "hackage", + "synopsis": "Redis backend for rate limiting as WAI middleware", + "version": "0.1.0.0" + }, + { + "name": "wai-saml2", + "origin": "hackage", + "synopsis": "SAML2 assertion validation as WAI middleware", + "version": "0.2.1.2" + }, + { + "name": "wai-session", + "origin": "hackage", + "synopsis": "Flexible session middleware for WAI", + "version": "0.3.3" + }, + { + "name": "wai-slack-middleware", + "origin": "hackage", + "synopsis": "A Slack middleware for WAI", + "version": "0.2.0" + }, + { + "name": "wai-websockets", + "origin": "hackage", + "synopsis": "Provide a bridge between WAI and the websockets package.", + "version": "3.0.1.2" + }, + { + "name": "wakame", + "origin": "hackage", + "synopsis": "Functions to manipulate records", + "version": "0.1.0.0" + }, + { + "name": "warp", + "origin": "hackage", + "synopsis": "A fast, light-weight web server for WAI applications.", + "version": "3.3.14" + }, + { + "name": "warp-tls", + "origin": "hackage", + "synopsis": "HTTP over TLS support for Warp via the TLS package", + "version": "3.3.0" + }, + { + "name": "warp-tls-uid", + "origin": "hackage", + "synopsis": "set group and user id before running server", + "version": "0.2.0.6" + }, + { + "name": "wave", + "origin": "hackage", + "synopsis": "Work with WAVE and RF64 files", + "version": "0.2.0" + }, + { + "name": "wcwidth", + "origin": "hackage", + "synopsis": "Native wcwidth.", + "version": "0.0.2" + }, + { + "name": "webby", + "origin": "hackage", + "synopsis": "A super-simple web server framework", + "version": "1.0.1" + }, + { + "name": "webdriver", + "origin": "hackage", + "synopsis": "a Haskell client for the Selenium WebDriver protocol", + "version": "0.9.0.1" + }, + { + "name": "webex-teams-api", + "origin": "hackage", + "synopsis": "A Haskell bindings for Webex Teams API", + "version": "0.2.0.1" + }, + { + "name": "webex-teams-conduit", + "origin": "hackage", + "synopsis": "Conduit wrapper of Webex Teams List API", + "version": "0.2.0.1" + }, + { + "name": "webex-teams-pipes", + "origin": "hackage", + "synopsis": "Pipes wrapper of Webex Teams List API", + "version": "0.2.0.1" + }, + { + "name": "webgear-server", + "origin": "hackage", + "synopsis": "Composable, type-safe library to build HTTP API servers", + "version": "0.2.1" + }, + { + "name": "webrtc-vad", + "origin": "hackage", + "synopsis": "Easy voice activity detection", + "version": "0.1.0.3" + }, + { + "name": "websockets", + "origin": "hackage", + "synopsis": "A sensible and clean way to write WebSocket-capable servers in Haskell.", + "version": "0.12.7.2" + }, + { + "name": "websockets-snap", + "origin": "hackage", + "synopsis": "Snap integration for the websockets library", + "version": "0.10.3.1" + }, + { + "name": "weigh", + "origin": "hackage", + "synopsis": "Measure allocations of a Haskell functions/values", + "version": "0.0.16" + }, + { + "name": "wide-word", + "origin": "hackage", + "synopsis": "Data types for large but fixed width signed and unsigned integers", + "version": "0.1.1.2" + }, + { + "name": "wikicfp-scraper", + "origin": "hackage", + "synopsis": "Scrape WikiCFP web site", + "version": "0.1.0.12" + }, + { + "name": "wild-bind", + "origin": "hackage", + "synopsis": "Dynamic key binding framework", + "version": "0.1.2.7" + }, + { + "name": "wild-bind-x11", + "origin": "hackage", + "synopsis": "X11-specific implementation for WildBind", + "version": "0.2.0.12" + }, + { + "name": "Win32", + "origin": "hackage", + "synopsis": "A binding to part of the Win32 library", + "version": "2.6.1.0" + }, + { + "name": "Win32-notify", + "origin": "hackage", + "synopsis": "A binding to part of the Win32 library for file notification", + "version": "0.3.0.3" + }, + { + "name": "windns", + "origin": "hackage", + "synopsis": "Domain Name Service (DNS) lookup via the /dnsapi.dll standard library", + "version": "0.1.0.1" + }, + { + "name": "witch", + "origin": "hackage", + "synopsis": "Convert values from one type into another.", + "version": "0.0.0.5" + }, + { + "name": "witherable-class", + "origin": "hackage", + "synopsis": "Witherable = Traversable + Filterable", + "version": "0" + }, + { + "name": "within", + "origin": "hackage", + "synopsis": "A value within another path.", + "version": "0.2.0.1" + }, + { + "name": "with-location", + "origin": "hackage", + "synopsis": "Use ImplicitParams-based source locations in a backward compatible way", + "version": "0.1.0" + }, + { + "name": "with-utf8", + "origin": "hackage", + "synopsis": "Get your IO right on the first try", + "version": "1.0.2.2" + }, + { + "name": "wizards", + "origin": "hackage", + "synopsis": "High level, generic library for interrogative user interfaces", + "version": "1.0.3" + }, + { + "name": "wl-pprint-annotated", + "origin": "hackage", + "synopsis": "Pretty printer with annotation support", + "version": "0.1.0.1" + }, + { + "name": "wl-pprint-console", + "origin": "hackage", + "synopsis": "Wadler/Leijen pretty printer supporting colorful console output.", + "version": "0.1.0.2" + }, + { + "name": "wl-pprint-text", + "origin": "hackage", + "synopsis": "A Wadler/Leijen Pretty Printer for Text values", + "version": "1.2.0.1" + }, + { + "name": "word24", + "origin": "hackage", + "synopsis": "24-bit word and int types for GHC", + "version": "2.0.1" + }, + { + "name": "word8", + "origin": "hackage", + "synopsis": "Word8 library", + "version": "0.1.3" + }, + { + "name": "word-trie", + "origin": "hackage", + "synopsis": "Implementation of a finite trie over words.", + "version": "0.3.0" + }, + { + "name": "word-wrap", + "origin": "hackage", + "synopsis": "A library for word-wrapping", + "version": "0.4.1" + }, + { + "name": "world-peace", + "origin": "hackage", + "synopsis": "Open Union and Open Product Types", + "version": "1.0.2.0" + }, + { + "name": "wrap", + "origin": "hackage", + "synopsis": "Wrap a function's return value with another function", + "version": "0.0.0" + }, + { + "name": "wreq", + "origin": "hackage", + "synopsis": "An easy-to-use HTTP client library.", + "version": "0.5.3.3" + }, + { + "name": "writer-cps-exceptions", + "origin": "hackage", + "synopsis": "Control.Monad.Catch instances for the stricter CPS WriterT and RWST", + "version": "0.1.0.1" + }, + { + "name": "writer-cps-mtl", + "origin": "hackage", + "synopsis": "MonadWriter orphan instances for writer-cps-transformers", + "version": "0.1.1.6" + }, + { + "name": "writer-cps-transformers", + "origin": "hackage", + "synopsis": "WriteT and RWST monad transformers", + "version": "0.5.6.1" + }, + { + "name": "wss-client", + "origin": "hackage", + "synopsis": "A-little-higher-level WebSocket client.", + "version": "0.3.0.0" + }, + { + "name": "wuss", + "origin": "hackage", + "synopsis": "Secure WebSocket (WSS) clients", + "version": "1.1.18" + }, + { + "name": "X11", + "origin": "hackage", + "synopsis": "A binding to the X11 graphics library", + "version": "1.9.2" + }, + { + "name": "X11-xft", + "origin": "hackage", + "synopsis": "Bindings to the Xft, X Free Type interface library, and some Xrender parts", + "version": "0.3.1" + }, + { + "name": "x11-xim", + "origin": "hackage", + "synopsis": "A binding to the xim of X11 graphics library", + "version": "0.0.9.0" + }, + { + "name": "x509", + "origin": "hackage", + "synopsis": "X509 reader and writer", + "version": "1.7.5" + }, + { + "name": "x509-store", + "origin": "hackage", + "synopsis": "X.509 collection accessing and storing methods", + "version": "1.6.7" + }, + { + "name": "x509-system", + "origin": "hackage", + "synopsis": "Handle per-operating-system X.509 accessors and storage", + "version": "1.6.6" + }, + { + "name": "x509-validation", + "origin": "hackage", + "synopsis": "X.509 Certificate and CRL validation", + "version": "1.6.11" + }, + { + "name": "Xauth", + "origin": "hackage", + "synopsis": "A binding to the X11 authentication library", + "version": "0.1" + }, + { + "name": "xdg-basedir", + "origin": "hackage", + "synopsis": "A basic implementation of the XDG Base Directory specification.", + "version": "0.2.2" + }, + { + "name": "xdg-desktop-entry", + "origin": "hackage", + "synopsis": "Parse files conforming to the xdg desktop entry spec", + "version": "0.1.1.1" + }, + { + "name": "xdg-userdirs", + "origin": "hackage", + "synopsis": "Basic implementation of XDG user directories specification", + "version": "0.1.0.2" + }, + { + "name": "xeno", + "origin": "hackage", + "synopsis": "A fast event-based XML parser in pure Haskell", + "version": "0.4.2" + }, + { + "name": "xhtml", + "origin": "core", + "synopsis": "An XHTML combinator library", + "version": "3000.2.2.1" + }, + { + "name": "xlsx", + "origin": "hackage", + "synopsis": "Simple and incomplete Excel file parser/writer", + "version": "0.8.3" + }, + { + "name": "xlsx-tabular", + "origin": "hackage", + "synopsis": "Xlsx table cell value extraction utility", + "version": "0.2.2.1" + }, + { + "name": "xml", + "origin": "hackage", + "synopsis": "A simple XML library.", + "version": "1.3.14" + }, + { + "name": "xml-basic", + "origin": "hackage", + "synopsis": "Basics for XML/HTML representation and processing", + "version": "0.1.3.1" + }, + { + "name": "xml-conduit", + "origin": "hackage", + "synopsis": "Pure-Haskell utilities for dealing with XML with the conduit package.", + "version": "1.9.1.1" + }, + { + "name": "xml-conduit-writer", + "origin": "hackage", + "synopsis": "Warm and fuzzy creation of XML documents.", + "version": "0.1.1.2" + }, + { + "name": "xmlgen", + "origin": "hackage", + "synopsis": "Fast XML generation library", + "version": "0.6.2.2" + }, + { + "name": "xml-hamlet", + "origin": "hackage", + "synopsis": "Hamlet-style quasiquoter for XML content", + "version": "0.5.0.1" + }, + { + "name": "xml-helpers", + "origin": "hackage", + "synopsis": "Some useful helper functions for the xml library.", + "version": "1.0.0" + }, + { + "name": "xml-html-qq", + "origin": "hackage", + "synopsis": "Quasi-quoters for XML and HTML Documents", + "version": "0.1.0.1" + }, + { + "name": "xml-indexed-cursor", + "origin": "hackage", + "synopsis": "Indexed XML cursors similar to 'Text.XML.Cursor' from xml-conduit", + "version": "0.1.1.0" + }, + { + "name": "xml-lens", + "origin": "hackage", + "synopsis": "Lenses, traversals, and prisms for xml-conduit", + "version": "0.3" + }, + { + "name": "xml-picklers", + "origin": "hackage", + "synopsis": "XML picklers based on xml-types, ported from hexpat-pickle", + "version": "0.3.6" + }, + { + "name": "xml-to-json", + "origin": "hackage", + "synopsis": "Library and command line tool for converting XML files to json", + "version": "2.0.1" + }, + { + "name": "xml-to-json-fast", + "origin": "hackage", + "synopsis": "Fast, light converter of xml to json capable of handling huge xml files", + "version": "2.0.0" + }, + { + "name": "xml-types", + "origin": "hackage", + "synopsis": "Basic types for representing XML", + "version": "0.3.8" + }, + { + "name": "xmonad", + "origin": "hackage", + "synopsis": "A tiling window manager", + "version": "0.15" + }, + { + "name": "xmonad-contrib", + "origin": "hackage", + "synopsis": "Third party extensions for xmonad", + "version": "0.16" + }, + { + "name": "xmonad-extras", + "origin": "hackage", + "synopsis": "Third party extensions for xmonad with wacky dependencies", + "version": "0.15.3" + }, + { + "name": "xss-sanitize", + "origin": "hackage", + "synopsis": "sanitize untrusted HTML to prevent XSS attacks", + "version": "0.3.6" + }, + { + "name": "xxhash-ffi", + "origin": "hackage", + "synopsis": "Bindings to the C implementation the xxHash algorithm", + "version": "0.2.0.0" + }, + { + "name": "yaml", + "origin": "hackage", + "synopsis": "Support for parsing and rendering YAML documents.", + "version": "0.11.5.0" + }, + { + "name": "yamlparse-applicative", + "origin": "hackage", + "synopsis": "Declaritive configuration parsing with free docs", + "version": "0.1.0.3" + }, + { + "name": "yesod", + "origin": "hackage", + "synopsis": "Creation of type-safe, RESTful web applications.", + "version": "1.6.1.1" + }, + { + "name": "yesod-auth", + "origin": "hackage", + "synopsis": "Authentication for Yesod.", + "version": "1.6.10.3" + }, + { + "name": "yesod-auth-hashdb", + "origin": "hackage", + "synopsis": "Authentication plugin for Yesod.", + "version": "1.7.1.6" + }, + { + "name": "yesod-auth-oauth2", + "origin": "hackage", + "synopsis": "OAuth 2.0 authentication plugins", + "version": "0.6.1.7" + }, + { + "name": "yesod-bin", + "origin": "hackage", + "synopsis": "The yesod helper executable.", + "version": "1.6.1" + }, + { + "name": "yesod-core", + "origin": "hackage", + "synopsis": "Creation of type-safe, RESTful web applications.", + "version": "1.6.19.0" + }, + { + "name": "yesod-fb", + "origin": "hackage", + "synopsis": "Useful glue functions between the fb library and Yesod.", + "version": "0.6.1" + }, + { + "name": "yesod-form", + "origin": "hackage", + "synopsis": "Form handling support for Yesod Web Framework", + "version": "1.6.7" + }, + { + "name": "yesod-gitrev", + "origin": "hackage", + "synopsis": "A subsite for displaying git information.", + "version": "0.2.1" + }, + { + "name": "yesod-newsfeed", + "origin": "hackage", + "synopsis": "Helper functions and data types for producing News feeds.", + "version": "1.7.0.0" + }, + { + "name": "yesod-page-cursor", + "origin": "hackage", + "synopsis": "", + "version": "2.0.0.6" + }, + { + "name": "yesod-paginator", + "origin": "hackage", + "synopsis": "A pagination approach for yesod", + "version": "1.1.1.0" + }, + { + "name": "yesod-persistent", + "origin": "hackage", + "synopsis": "Some helpers for using Persistent from Yesod.", + "version": "1.6.0.6" + }, + { + "name": "yesod-sitemap", + "origin": "hackage", + "synopsis": "Generate XML sitemaps.", + "version": "1.6.0" + }, + { + "name": "yesod-static", + "origin": "hackage", + "synopsis": "Static file serving subsite for Yesod Web Framework.", + "version": "1.6.1.0" + }, + { + "name": "yesod-test", + "origin": "hackage", + "synopsis": "integration testing for WAI/Yesod Applications", + "version": "1.6.12" + }, + { + "name": "yesod-websockets", + "origin": "hackage", + "synopsis": "WebSockets support for Yesod", + "version": "0.3.0.3" + }, + { + "name": "yes-precure5-command", + "origin": "hackage", + "synopsis": "Extended yes command to reproduce phrases in Yes! Precure 5.", + "version": "5.5.3" + }, + { + "name": "yi-rope", + "origin": "hackage", + "synopsis": "A rope data structure used by Yi", + "version": "0.11" + }, + { + "name": "yjsvg", + "origin": "hackage", + "synopsis": "make SVG string from Haskell data", + "version": "0.2.0.1" + }, + { + "name": "yjtools", + "origin": "hackage", + "synopsis": "some tools for Monad, List, Tuple and so on.", + "version": "0.9.18" + }, + { + "name": "yoga", + "origin": "hackage", + "synopsis": "Bindings to Facebook's Yoga layout library", + "version": "0.0.0.5" + }, + { + "name": "youtube", + "origin": "hackage", + "synopsis": "Upload video to YouTube via YouTube API", + "version": "0.2.1.1" + }, + { + "name": "zenacy-html", + "origin": "hackage", + "synopsis": "A standard compliant HTML parsing library", + "version": "2.0.3" + }, + { + "name": "zenacy-unicode", + "origin": "hackage", + "synopsis": "Unicode utilities for Haskell", + "version": "1.0.1" + }, + { + "name": "zero", + "origin": "hackage", + "synopsis": "Semigroups with absorption", + "version": "0.1.5" + }, + { + "name": "zeromq4-haskell", + "origin": "hackage", + "synopsis": "Bindings to ZeroMQ 4.x", + "version": "0.8.0" + }, + { + "name": "zeromq4-patterns", + "origin": "hackage", + "synopsis": "Haskell implementation of several ZeroMQ patterns.", + "version": "0.3.1.0" + }, + { + "name": "zim-parser", + "origin": "hackage", + "synopsis": "Read and parse ZIM files", + "version": "0.2.1.0" + }, + { + "name": "zio", + "origin": "hackage", + "synopsis": "App-centric Monad-transformer based on Scala ZIO (UIO + ReaderT + ExceptT).", + "version": "0.1.0.2" + }, + { + "name": "zip", + "origin": "hackage", + "synopsis": "Operations on zip archives", + "version": "1.7.0" + }, + { + "name": "zip-archive", + "origin": "hackage", + "synopsis": "Library for creating and modifying zip archives.", + "version": "0.4.1" + }, + { + "name": "zipper-extra", + "origin": "hackage", + "synopsis": "Zipper utils that weren't in Control.Comonad.Store.Zipper", + "version": "0.1.3.2" + }, + { + "name": "zippers", + "origin": "hackage", + "synopsis": "Traversal based zippers", + "version": "0.3.1" + }, + { + "name": "zip-stream", + "origin": "hackage", + "synopsis": "ZIP archive streaming using conduits", + "version": "0.2.1.0" + }, + { + "name": "zlib", + "origin": "hackage", + "synopsis": "Compression and decompression in the gzip and zlib formats", + "version": "0.6.2.3" + }, + { + "name": "zlib-bindings", + "origin": "hackage", + "synopsis": "Low-level bindings to the zlib package.", + "version": "0.1.1.5" + }, + { + "name": "zlib-lens", + "origin": "hackage", + "synopsis": "Lenses for zlib", + "version": "0.1.2.1" + }, + { + "name": "zot", + "origin": "hackage", + "synopsis": "Zot language", + "version": "0.0.3" + }, + { + "name": "zstd", + "origin": "hackage", + "synopsis": "Haskell bindings to the Zstandard compression algorithm", + "version": "0.1.2.0" + }, + { + "name": "ztail", + "origin": "hackage", + "synopsis": "Multi-file, colored, filtered log tailer.", + "version": "1.2.0.2" + }, + { + "name": "zydiskell", + "origin": "hackage", + "synopsis": "Haskell language binding for the Zydis library, a x86/x86-64 disassembler.", + "version": "0.2.0.0" + } + ], + "snapshot": { + "compiler": "ghc-8.10.4", + "created": "2021-04-25", + "ghc": "8.10.4", + "name": "lts-17.10" + } +} From c49585e5da3556b4eb3b03a664a0e0377054bdf1 Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 1 Dec 2025 16:19:53 -0700 Subject: [PATCH 2/4] fourmolu --- library/Freckle/App/Http.hs | 16 ++++---- library/Freckle/App/Http/Cache.hs | 18 ++++----- library/Freckle/App/Http/Cache/Gzip.hs | 2 +- library/Freckle/App/Http/Cache/Memcached.hs | 14 ++++--- library/Freckle/App/Http/Cache/State.hs | 10 +++-- library/Freckle/App/Http/Retry.hs | 10 ++--- library/Freckle/App/Test/Http.hs | 39 ++++++++++--------- library/Freckle/App/Test/Http/MatchRequest.hs | 8 ++-- tests/Freckle/App/Http/CacheSpec.hs | 7 ++-- 9 files changed, 66 insertions(+), 58 deletions(-) diff --git a/library/Freckle/App/Http.hs b/library/Freckle/App/Http.hs index 3399cf2..e758811 100644 --- a/library/Freckle/App/Http.hs +++ b/library/Freckle/App/Http.hs @@ -153,7 +153,7 @@ instance MonadHttp m => MonadHttp (MaybeT m) where instance MonadHttp m => MonadHttp (ReaderT r m) where httpLbs = lift . httpLbs -instance (Monoid w, MonadHttp m) => MonadHttp (WriterT w m) where +instance (MonadHttp m, Monoid w) => MonadHttp (WriterT w m) where httpLbs = lift . httpLbs instance MonadHttp m => MonadHttp (StateT s m) where @@ -173,10 +173,10 @@ data HttpDecodeError = HttpDecodeError instance Exception HttpDecodeError where displayException HttpDecodeError {..} = - T.unpack $ - T.unlines $ - ["Error decoding HTTP Response:", "Raw body:", T.pack $ BSL8.unpack hdeBody] - <> fromErrors hdeErrors + T.unpack + $ T.unlines + $ ["Error decoding HTTP Response:", "Raw body:", T.pack $ BSL8.unpack hdeBody] + <> fromErrors hdeErrors where fromErrors = \case err NE.:| [] -> ["Error:", T.pack err] @@ -196,7 +196,8 @@ instance Exception HttpDecodeError where -- 'getResponseBodyUnsafe' resp :: m a -- @ httpJson - :: (MonadHttp m, FromJSON a) + :: forall m a + . (FromJSON a, MonadHttp m) => Request -> m (Response (Either HttpDecodeError a)) httpJson = @@ -282,7 +283,8 @@ disableRequestDecompress req = -- error response bodies too, you'll want to use 'setRequestCheckStatus' so that -- you see status-code exceptions before 'HttpDecodeError's. getResponseBodyUnsafe - :: (MonadIO m, Exception e, HasCallStack) + :: forall m e a + . (Exception e, HasCallStack, MonadIO m) => Response (Either e a) -> m a getResponseBodyUnsafe = either throwWithCallStack pure . getResponseBody diff --git a/library/Freckle/App/Http/Cache.hs b/library/Freckle/App/Http/Cache.hs index 5ca9558..7b820f5 100644 --- a/library/Freckle/App/Http/Cache.hs +++ b/library/Freckle/App/Http/Cache.hs @@ -126,8 +126,8 @@ httpCached settings doHttp req = settings.logWarn $ "Error deserialising" :# ["error" .= err] writeCache now key =<< getResponse req Right cresp | isCachedResponseStale cresp now -> do - settings.logDebug $ - "Cached value stale" + settings.logDebug + $ "Cached value stale" :# [ "key" .= tkey , "inserted" .= cresp.inserted , "ttl" .= fromCacheTTL cresp.ttl @@ -138,8 +138,8 @@ httpCached settings doHttp req = fromEx () $ settings.cache.evict key writeCache now key =<< getResponse req Just etag -> do - settings.logDebug $ - "Retrying with If-None-Match" + settings.logDebug + $ "Retrying with If-None-Match" :# [ "key" .= tkey , "etag" .= T.decodeUtf8With T.lenientDecode etag ] @@ -170,8 +170,8 @@ httpCached settings doHttp req = -> m (Response BSL.ByteString) writeCache now key resp = do for_ (getCachableResponseTTL settings resp) $ \ttl -> do - settings.logDebug $ - "Write cache" + settings.logDebug + $ "Write cache" :# [ "key" .= T.decodeUtf8With T.lenientDecode (fromCacheKey key) , "ttl" .= fromCacheTTL ttl ] @@ -235,8 +235,8 @@ getCachableResponseTTL :: HttpCacheSettings m t -> Response body -> Maybe CacheTTL getCachableResponseTTL settings resp = do guard $ NoStore `notElem` responseHeaders.cacheControl - guard $ - not settings.shared || Private `notElem` responseHeaders.cacheControl + guard + $ not settings.shared || Private `notElem` responseHeaders.cacheControl guard $ statusIsCacheable $ HTTP.responseStatus resp pure $ fromMaybe settings.defaultTTL $ responseHeadersToTTL responseHeaders where @@ -265,7 +265,7 @@ cacheableStatusCodes = newtype Seconds = Seconds {unwrap :: Int} deriving stock (Eq) - deriving newtype (Num, Show, Read) + deriving newtype (Num, Read, Show) data CacheControl = Private diff --git a/library/Freckle/App/Http/Cache/Gzip.hs b/library/Freckle/App/Http/Cache/Gzip.hs index 16f1a7b..c5b2446 100644 --- a/library/Freckle/App/Http/Cache/Gzip.hs +++ b/library/Freckle/App/Http/Cache/Gzip.hs @@ -31,7 +31,7 @@ import Network.HTTP.Client.Internal qualified as HTTP newtype PotentiallyGzipped a = PotentiallyGzipped { unwrap :: a } - deriving stock (Show, Eq) + deriving stock (Eq, Show) deriving newtype (Serialise) -- | Run a request /without/ automatic 'decompress' and tag the @body@ type diff --git a/library/Freckle/App/Http/Cache/Memcached.hs b/library/Freckle/App/Http/Cache/Memcached.hs index a0a2934..19268a6 100644 --- a/library/Freckle/App/Http/Cache/Memcached.hs +++ b/library/Freckle/App/Http/Cache/Memcached.hs @@ -33,11 +33,12 @@ import OpenTelemetry.Trace.Monad (MonadTracer (..)) import UnliftIO (MonadUnliftIO) memcachedHttpCacheSettings - :: ( MonadUnliftIO m + :: forall m env + . ( HasMemcachedClient env , MonadLogger m - , MonadTracer m , MonadReader env m - , HasMemcachedClient env + , MonadTracer m + , MonadUnliftIO m ) => CacheTTL -- ^ Default TTL, used when @max-age@ is not present @@ -65,10 +66,11 @@ memcachedHttpCodec = } memcachedHttpCache - :: ( MonadUnliftIO m - , MonadTracer m + :: forall m env + . ( HasMemcachedClient env , MonadReader env m - , HasMemcachedClient env + , MonadTracer m + , MonadUnliftIO m ) => HttpCache m Value memcachedHttpCache = diff --git a/library/Freckle/App/Http/Cache/State.hs b/library/Freckle/App/Http/Cache/State.hs index f2d11da..68114ee 100644 --- a/library/Freckle/App/Http/Cache/State.hs +++ b/library/Freckle/App/Http/Cache/State.hs @@ -34,7 +34,7 @@ import System.IO qualified as IO newtype Cache = Cache { map :: HashMap CacheKey CachedResponse } - deriving newtype (Semigroup, Monoid) + deriving newtype (Monoid, Semigroup) mapL :: Lens' Cache (HashMap CacheKey CachedResponse) mapL = lens (.map) $ \x y -> x {map = y} @@ -46,9 +46,10 @@ instance HasCache Cache where cacheL = id stateHttpCacheSettings - :: ( MonadIO m + :: forall m s + . ( HasCache s + , MonadIO m , MonadState s m - , HasCache s ) => HttpCacheSettings m CachedResponse stateHttpCacheSettings = @@ -71,7 +72,8 @@ stateHttpCacheCodec = } stateHttpCache - :: (MonadIO m, MonadState s m, HasCache s) => HttpCache m CachedResponse + :: forall m s + . (HasCache s, MonadIO m, MonadState s m) => HttpCache m CachedResponse stateHttpCache = HttpCache { get = \key -> fmap Right $ use $ cacheL . mapL . at key diff --git a/library/Freckle/App/Http/Retry.hs b/library/Freckle/App/Http/Retry.hs index a75a2f2..6232ac4 100644 --- a/library/Freckle/App/Http/Retry.hs +++ b/library/Freckle/App/Http/Retry.hs @@ -77,18 +77,18 @@ suppressRetryStatusError :: Request -> Request suppressRetryStatusError req = req { checkResponse = \req' resp -> - unless (getResponseStatus resp == status429) $ - originalCheckResponse req' resp + unless (getResponseStatus resp == status429) + $ originalCheckResponse req' resp } where originalCheckResponse = checkResponse req checkRetriesExhausted - :: (MonadIO m, HasCallStack) => Int -> Response body -> m (Response body) + :: (HasCallStack, MonadIO m) => Int -> Response body -> m (Response body) checkRetriesExhausted retryLimit resp | getResponseStatus resp == status429 = - throwWithCallStack $ - RetriesExhausted {reLimit = retryLimit, reResponse = void resp} + throwWithCallStack + $ RetriesExhausted {reLimit = retryLimit, reResponse = void resp} | otherwise = pure resp getRetryAfter :: Response body -> Maybe Int diff --git a/library/Freckle/App/Test/Http.hs b/library/Freckle/App/Test/Http.hs index 9949688..8a3e752 100644 --- a/library/Freckle/App/Test/Http.hs +++ b/library/Freckle/App/Test/Http.hs @@ -29,7 +29,7 @@ module Freckle.App.Test.Http , loadHttpStubsDirectory -- * Exception - , NoStubsMatched(..) + , NoStubsMatched (..) -- * 'MonadHttp' instances @@ -46,12 +46,13 @@ import Prelude import Control.Applicative (asum) import Control.Exception (Exception (..)) -import Control.Lens (Lens', lens, view, (.~), (<>~)) +import Control.Exception.Annotated.UnliftIO (throwWithCallStack) +import Control.Lens (Lens', lens, view, (.~), (<>~)) +import Control.Monad (filterM) +import Control.Monad.IO.Class (MonadIO) import Control.Monad.Reader (MonadReader, ReaderT, runReaderT) import Data.Aeson (ToJSON, encode) import Data.Bifunctor (bimap) -import Control.Monad(filterM) -import Control.Monad.IO.Class (MonadIO) import Data.ByteString.Lazy qualified as BSL import Data.Either (partitionEithers) import Data.Function ((&)) @@ -60,7 +61,6 @@ import Data.Maybe (mapMaybe) import Data.String (IsString) import Data.String qualified import Data.Traversable (for) -import Control.Exception.Annotated.UnliftIO (throwWithCallStack) import Freckle.App.Http (MonadHttp (..)) import Freckle.App.Test.Http.MatchRequest import GHC.Stack (HasCallStack) @@ -89,13 +89,13 @@ import System.FilePath.Glob (globDir1) -- ] -- @ httpStubbed - :: (MonadIO m, HasCallStack) + :: (HasCallStack, MonadIO m) => [HttpStub] -> Request -> m (Response BSL.ByteString) httpStubbed stubs req = maybe - (throwWithCallStack NoStubsMatched{req, unmatched}) + (throwWithCallStack NoStubsMatched {req, unmatched}) (pure . toResponse req) $ headMay matched where @@ -116,17 +116,15 @@ data NoStubsMatched = NoStubsMatched instance Show NoStubsMatched where show = displayException - instance Exception NoStubsMatched where displayException NoStubsMatched {req, unmatched} = "No stubs were found that matched:\n" - <> show req - <> "\n" - <> ( - if length unmatched < 4 - then concatMap (uncurry unmatchedMessage) unmatched - else "\nNumber of stubs: " <> show (length unmatched) - ) + <> show req + <> "\n" + <> ( if length unmatched < 4 + then concatMap (uncurry unmatchedMessage) unmatched + else "\nNumber of stubs: " <> show (length unmatched) + ) where unmatchedMessage stub err = "\n== " <> stub.label <> " ==\n" <> err @@ -264,16 +262,19 @@ instance HasHttpStubs [HttpStub] where httpStubsL = id newtype ReaderHttpStubs m a = ReaderHttpStubs {unwrap :: m a} - deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader env) + deriving newtype (Applicative, Functor, Monad, MonadIO, MonadReader env) -instance (MonadReader env m, HasHttpStubs env, MonadIO m) => MonadHttp (ReaderHttpStubs m) where +instance + (HasHttpStubs env, MonadIO m, MonadReader env m) + => MonadHttp (ReaderHttpStubs m) + where httpLbs req = do stubs <- view httpStubsL httpStubbed stubs req newtype HttpStubsT m a = HttpStubsT {unwrap :: ReaderT [HttpStub] m a} - deriving newtype (Functor, Applicative, Monad, MonadReader [HttpStub]) - deriving (MonadIO, MonadHttp) via ReaderHttpStubs (HttpStubsT m) + deriving newtype (Applicative, Functor, Monad, MonadReader [HttpStub]) + deriving (MonadHttp, MonadIO) via ReaderHttpStubs (HttpStubsT m) runHttpStubsT :: HttpStubsT m a -> [HttpStub] -> m a runHttpStubsT f = runReaderT f.unwrap diff --git a/library/Freckle/App/Test/Http/MatchRequest.hs b/library/Freckle/App/Test/Http/MatchRequest.hs index 51de98d..6c03204 100644 --- a/library/Freckle/App/Test/Http/MatchRequest.hs +++ b/library/Freckle/App/Test/Http/MatchRequest.hs @@ -73,8 +73,8 @@ matchRequestFromUrl url = requiredMatches = MatchMethod method :| [MatchSecure secure, MatchPort port] optionalMatches = - NE.nonEmpty $ - catMaybes + NE.nonEmpty + $ catMaybes [ MatchHost host <$ guard (host /= "") , MatchPath path <$ guard (hasExplicitPath secure host port url) , MatchQuery query <$ guard (query /= "") @@ -106,8 +106,8 @@ hasExplicitPath secure host port url = -- Success is @'Right' ()@, failure is a message in 'Left'. matchRequest :: Request -> MatchRequest -> Either String () matchRequest req mr = - maybe (Right ()) (Left . showMatchRequestWithMismatches mr) $ - buildMismatch req mr + maybe (Right ()) (Left . showMatchRequestWithMismatches mr) + $ buildMismatch req mr showMatchRequest :: MatchRequest -> String showMatchRequest mr = diff --git a/tests/Freckle/App/Http/CacheSpec.hs b/tests/Freckle/App/Http/CacheSpec.hs index 408c8bc..ac3b22d 100644 --- a/tests/Freckle/App/Http/CacheSpec.hs +++ b/tests/Freckle/App/Http/CacheSpec.hs @@ -388,11 +388,12 @@ settingsFuture = stubAnything :: [HttpStub] stubAnything = [httpStub "Anything" MatchAnything] -expectDecode :: (HasCallStack, MonadIO m, FromJSON a) => BSL.ByteString -> m a +expectDecode + :: forall m a. (FromJSON a, HasCallStack, MonadIO m) => BSL.ByteString -> m a expectDecode bs = case eitherDecode bs of Left err -> do - expectationFailure $ - mconcat + expectationFailure + $ mconcat [ "Expected input to decode as JSON" , "\nInput: " <> show bs , "\nErrors: " <> err From 3c897dd116cbc26d3765a5f67b98d847443a4354 Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 1 Dec 2025 16:25:44 -0700 Subject: [PATCH 3/4] fiddle with dependencies --- stack-lts20.yaml | 3 ++- stack-lts21.yaml | 2 ++ stack-lts22.yaml | 1 + stack-nightly.yaml | 8 ++++++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/stack-lts20.yaml b/stack-lts20.yaml index 28ca5d5..87a3203 100644 --- a/stack-lts20.yaml +++ b/stack-lts20.yaml @@ -2,9 +2,10 @@ resolver: lts-20.26 extra-deps: - Blammo-2.1.0.0 + - fast-logger-3.2.3 - freckle-exception-0.0.0.0 - freckle-memcached-0.0.0.1 - - freckle-otel-0.0.0.1 + - freckle-otel-0.0.0.2 - freckle-prelude-0.0.4.1 - hs-opentelemetry-api-0.1.0.0 - hs-opentelemetry-exporter-otlp-0.0.1.5 diff --git a/stack-lts21.yaml b/stack-lts21.yaml index da92eac..0067381 100644 --- a/stack-lts21.yaml +++ b/stack-lts21.yaml @@ -1,6 +1,8 @@ resolver: lts-21.25 extra-deps: + - Blammo-2.1.0.0 + - fast-logger-3.2.3 - freckle-exception-0.0.0.1 - freckle-memcached-0.0.0.2 - freckle-otel-0.0.0.2 diff --git a/stack-lts22.yaml b/stack-lts22.yaml index 9c5b7dc..6b12ff8 100644 --- a/stack-lts22.yaml +++ b/stack-lts22.yaml @@ -1,6 +1,7 @@ resolver: lts-22.43 extra-deps: + - Blammo-2.1.1.0 - freckle-exception-0.0.0.2 - freckle-memcached-0.0.0.2 - freckle-otel-0.0.0.2 diff --git a/stack-nightly.yaml b/stack-nightly.yaml index affa44f..856fdd1 100644 --- a/stack-nightly.yaml +++ b/stack-nightly.yaml @@ -13,5 +13,13 @@ extra-deps: - hs-opentelemetry-propagator-w3c-0.1.0.0 - hs-opentelemetry-sdk-0.1.0.1 - monad-validate-1.3.0.0 + - proto-lens-0.7.1.6 + - proto-lens-runtime-0.7.0.7 - thread-utils-context-0.3.0.4 - thread-utils-finalizers-0.1.1.0 + +allow-newer: true + +allow-newer-deps: + - proto-lens + - proto-lens-runtime From a237ed9e8542bd0aa29d257e19e883a0a52830be Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Fri, 12 Dec 2025 12:16:22 -0700 Subject: [PATCH 4/4] copy restyled config from freckle-app --- .restyled.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.restyled.yaml b/.restyled.yaml index be7a0c0..bf8a6db 100644 --- a/.restyled.yaml +++ b/.restyled.yaml @@ -1,4 +1,5 @@ restylers: - - fourmolu - - "!stylish-haskell" - - "*" + - fourmolu: + image: restyled/restyler-fourmolu:v0.13.0.0 + include: + - "/**/*.hs"